Android线程,Android 进程
当一个应用组件启动时,如果该应用没有其他正在运行的组件,Android系统会为该应用创建一个新的进程(包括一个线程)来运行。默认情况下,应用程序的所有组件(活动、服务等。)运行在同一个进程和线程中(称为“主线程”)。如果一个应用程序组件被启动,该应用程序已经有一个进程在运行(因为该应用程序的其他组件存在),那么该应用程序组件将使用相同的进程和线程运行。当然,您可以使用不同的进程来运行不同的组件,或者在进程中创建新的线程。
默认情况下,应用程序的所有组件都运行在同一个进程中,应用程序不应该改变这一传统。但是,如果您发现需要控制组件在该进程中运行,可以通过应用程序清单对其进行配置。
在应用程序清单文件中,每种类型的应用程序组件——活动、服务、接收者和提供者都支持android:process属性,该属性用于指示应用程序组件运行的进程。您可以为应用程序组件设置此属性,以便每个组件在不同的进程中运行,或者某些组件使用相同的进程。还可以设置android:process,让不同应用程序中的组件在同一个进程中运行——前提是这些应用程序使用相同的Linux用户名,用相同的证书签名。
Application元素还支持android: process属性,该属性用于为应用程序的所有组件设置默认流程。
在Android系统中,系统资源太低,一些需要立即为用户提供服务的进程可能在需要启动的时候被终止。在这些终止的进程中运行的程序组件将被逐个销毁。之后,如果仍有工作需要这些应用程序组件,将会启动一个新的进程。
当系统决定哪些进程可以被杀死时,系统会权衡这些进程对用户的重要性。例如,运行不可见活动的进程比运行屏幕上可见活动的进程更容易被杀死。
流程生命周期
Android系统会尽可能长时间地保持应用程序进程运行,但总会有清除旧进程以释放资源来满足新进程或重要进程的需求的需求。为了决定哪些进程可以终止,哪些进程需要保留,系统根据这些进程中运行的应用程序组件以及这些组件的状态,将这些进程分配到“重要性层次表”中。重要性最低的进程首先被终止,重要性第二的进程次之,依此类推,直到系统恢复所需的资源。
“重要性等级表”可以分为五个级别。以下列表给出了不同类型流程的重要性级别(最重要的流程排在最前面):
1.前台进程
这种过程是当前用户所需要的。进程被视为前台进程,必须满足以下条件之一:
此流程中存在当前正在与用户交互的活动(此活动的onResume()已被调用)。
该流程中的服务与当前用户的交互活动之间存在绑定。
在这个流程中有一个服务在前台运行—这个服务调用了startForeground()。
在这个过程中,服务正在执行一个生命周期回调函数(onCreate()、onStart()或onDestroy())。
此进程中的某个BroadcastReceiver正在执行onReceive()方法。
2.可见过程
虽然这个进程不包含任何在前台运行的组件,但是它会影响用户屏幕上当前显示的内容。当满足以下两个条件之一时,流程被视为可见:
此流程包含一个部分可见的活动,尽管它不在前台(调用此活动的onPause())。可能发生的情况是前台活动显示一个对话框,此时前一个活动变得部分可见。
此流程包含绑定到可见活动的服务。
3.服务过程
这个进程运行一个以startService()开始的服务,但不属于以上两种情况。这个服务过程虽然和用户能看到的任何部分都没有直接关系,但是会运行一些用户关心的事情(比如后台播放音乐或者通过网络下载文件)。所以Android系统会尽量让它们运行,直到系统资源太低,满足不了前台和可见进程的运行。
4.后台进程
这个进程运行了一些目前用户看不到的活动(这个活动的onStop()已经被调用了),这个进程对用户体验没有直接影响。为了在系统资源不足时保证前台,可视或服务进程可以随时被杀死。通常,系统中有许多进程在后台运行,这些进程保存在LRU(最近使用的)列表中,以确保用户看到的最后一个进程最终被杀死。如果活动正确地实现了其生命周期功能并保存了其状态。杀死这个活动的进程对用户没有视觉上的影响,因为当用户稍后返回这个活动时,这个活动可以正确地恢复到上一个屏幕的状态。
5.空进程
该进程不运行任何活动的应用程序组件。保持这个过程运行的唯一原因是因为缓存,以便在程序组件下次运行时缩短启动时间。为了进程缓存和内核缓存之间的平衡,系统将总是清除空进程。
Android将根据当前活动程序组件在该过程中的重要性,对该过程进行尽可能高的评级。例如,如果一个流程同时运行一个服务和一个可见活动,那么该流程将被分级为可见流程,而不是服务流程(可见流程的优先级高于服务流程)。
此外,一个流程的级别可能会被依赖它的其他流程提升——为其他流程提供服务的流程的级别不会低于它所服务的流程的级别。例如,流程A中的内容提供者向流程B中的客户端提供数据服务,或者流程A中的服务被流程B中的组件绑定。那么流程A的重要性不亚于流程B.
因为运行服务的进程的级别高于运行后台活动的进程的级别,所以运行一个操作需要很长时间的活动可以启动一个可以完成该操作的服务,它也可能能够很好地完成任务,而不需要简单地创建一个新的工作线程——特别是如果操作运行的时间比活动长的话。例如,如果一个活动需要完成上传图片到服务器的任务,它应该使用一个服务来完成上传任务。即使用户离开活动,服务仍然可以在后台完成上传任务。服务可用于确保操作至少具有“服务流程”的优先级,而不管活动发生了什么。这也是一个应该使用服务而不是线程来完成耗时任务的广播接收器。
Android系统启动一个应用程序后,会创建一个线程来运行该应用程序,这个线程会成为“主”线程。主线程非常重要,因为它负责将消息和事件(包括绘制事件)分发到界面上相应的UI组件。这也是应用程序可以直接与UI组件交互的线程(在android.widget和android.view中定义)。所以主线程也俗称用户界面线程(UI线程)。
Android不会主动为应用程序的组件创建额外的线程。在同一过程中,所有程序组件都在UI线程中初始化,UI线程用于将系统调用分配给这些程序组件。可以看到,响应系统回调函数的方法(比如onKeyDown()响应用户键或者某个生命周期回调函数)总是使用UI线程来运行。
例如,当用户触摸屏幕上的一个按钮时,您的应用程序中的UI线程会将触摸事件发送到相应的UI小部件,然后UI小部件会设置其按下状态,并向事件队列发送刷新请求,然后UI线程会处理事件队列,并通知UI小部件重新绘制自己。
当你的应用在响应用户事件时需要完成一些费力的工作时,这种单线程的工作模式可能会导致非常差的用户响应性能。尤其是如果所有工作都在UI线程中完成,比如网络访问、数据库查询等耗时的工作都会阻塞UI线程。当UI线程被阻塞时,事件(包括绘图事件)无法分发。此时,从用户的角度来看,应用程序似乎不再响应。更糟糕的是,如果UI线程被阻塞超过几秒钟(目前是五秒),系统将向用户显示著名的“应用程序无响应”(ANR)对话框。用户可能会选择退出你的应用,甚至觉得很不满意会选择卸载你的应用。
另外,Android的UI组件包不是“线程安全”的,所以你不能在一个worker线程中调用UI组件。所有与UI相关的操作都必须在UI线程中完成,所以这里有两条使用UI单线程工作线程的规则:
1.永远不要阻塞UI线程。
2.不要在非UI线程中操作UI组件。
由于Android采用单线程工作模式,所以不阻塞UI线程对于应用的响应性能非常重要。如果您的应用程序包含一些不能立即完成的操作,您应该使用额外的线程(工作线程或后台线程)来执行这些操作。
例如,在下面的示例中,用户单击按钮后,将启动一个新线程来下载图像,然后该图像将显示在ImageView中:
公共void onClick(视图v) {
新线程(新Runnable() {
公共无效运行(){
bitmap b=loadImageFromNetwork( http://example . com/image . png );
mimage view . setimage bitmap(b);
专用位图loadImageFromNetwork(String String){
//TODO自动生成的方法存根
返回null
}).start();
乍一看,这段代码应该运行良好,因为它创建了一个新线程来完成网络操作。但是,它违反了上面提到的第二条规则:不要在非UI线程中操作UI组件。在这段代码中,直接在worker线程而不是UI线程中修改ImageView会导致一些无法预料的后果,这往往会导致找到这样的错误陷阱极其困难和耗时。
为了纠正这种错误,Android提供了多种方法来访问非UI线程中的UI组件。以下是其中的一些:
公共无效运行(){
final Bitmap Bitmap=loadImageFromNetwork( http://example . com/image . png));
mImageView.post(new Runnable() {
公共无效运行(){
mImageView.setImageBitmap(位图);
}).start();
这种实现符合“线程安全”的原则:网络操作在附加线程中完成,ImageView操作在UI线程中完成。
但是,随着操作的日益复杂,上述代码可能会变得非常复杂,导致维护困难。为了解决在工作线程中处理如此复杂的操作的问题,您可以考虑在工作线程中使用Handler类来处理UI线程发送的消息。然而,也许使用AsyncTask是这类问题的最佳解决方案,它大大简化了工作线程需要与UI组件交互的问题。
使用异步任务
AsyncTask允许你做一些与用户界面相关的异步工作。它在一个工作线程中完成一些阻塞工作任务,然后在任务完成后通知UI线程,不需要你自己管理工作线程。
您必须从AsyncTask派生一个子类,并实现doInBackground()回调函数来使用AsyncTask,它将使用后台进程池来执行异步任务。为了更新用户界面,必须实现onPostExecute()方法,该方法将传递doInBackground()的返回结果,并在UI线程中运行。然后,您可以在UI线程中调用execute()方法来执行此任务。
例如,使用AsyncTask完成前面的示例:
公共void onClick(视图v) {
新建DownloadImageTask()。执行( http://example . com/image . png );
私有类DownloadImageTask扩展了AsyncTask String、Void、Bitmap {
/**系统调用此方法在工作线程中执行工作
*向其传递给AsyncTask.execute() */的参数
受保护的位图背景(字符串.urls
返回loadImageFromNetwork(URL[0]);
/**系统调用它在UI线程中执行工作,并提供
doInBackground()的结果*/
受保护的void onPostExecute(位图结果){
mImageView.setImageBitmap(结果);
现在UI是安全的,代码变得更简单,因为它将工作线程中的工作与UI线程中的工作分开。
你应该参考AsyncTask的详细文档来更好地理解它的工作原理。以下是它的基本步骤:
您可以使用泛型来指定参数类型、返回值类型等。对于任务。
方法doInBackground()将在工作线程中自动执行。
方法onPreExecute()、onPostExecute()和onProgressUpdate都在UI线程中调用。
方法doInBackground()的返回值将被传递给onPostExecute()方法。
可以任意调用doInBackground()中的publishProgress()方法,该方法会调用UI线程中的onProgressUpdate()方法,可以用它来报告任务完成的进度。
您可以随时在任何线程中终止任务的执行。
需要注意的是,由于系统配置的改变(比如屏幕旋转的方向),你的工作线程可能会遇到意外重启。在这种情况下,您的工作线程可能会被破坏。可以参考Android开发工具包中的Shelves例子来处理线程重启的问题。
编写一个“线程安全”的方法
在某些情况下,您编写的方法可能会被多个线程调用,因此在实现该方法时,您应该确保它是“线程安全的”。
“线程安全”是一个基本规则,可以通过远程调用方法来实现,例如服务中支持“绑定”的方法。当在实现IBinder接口的同一进程中调用IBinder对象的方法时,该方法在调用方运行的同一线程中运行。但是,如果调用来自不同的进程,系统将使用与实现IBinder接口的进程相关联的线程池中的线程(而不是该进程中的UI线程)来执行IBinder的方法。比如服务的onBind()方法会在服务进程的UI线程中调用,而onBind()返回的对象的方法(比如实现远程调用RPC方法的子类)会在线程池中执行。由于服务可能服务于多个客户端,线程池中可能有多个线程同时执行IBinder对象的方法,因此IBinder对象的方法必须是线程安全的。
类似地,内容提供者可以接受来自其他进程的数据请求。虽然ContentResolver和ContentProvider类在处理这些数据请求时隐藏了进程间通信的详细机制,但是这些请求方法包括query()、insert()、delete()、update()和getType()等。这些方法在内容提供者的进程的线程池中的线程中执行。因为这些方法同时被无限数量的线程调用,所以这些方法也必须是线程安全的。
工业程序控制( industrial process control的缩写)
Android系统支持使用远程调用(RPC)实现进程间通信(IPC)的机制。这时,一个方法被一个活动或其他程序组件调用,这个方法的实现在另一个进程(远程进程)中。远程调用可能会向调用者返回结果。这就需要将方法调用和相关数据分离到一定的层次,让操作系统明白数据可以从本地进程转移到远程进程的地址空间,数据可以重构远程执行方法,返回的数据可以反向返回。Android支持所有能够完成这些进程间通信事务的代码,这样你就可以只专注于定义和实现远程调用的接口了。
为了使用进程间通信(IPC),您的应用程序需要使用bindService()绑定到服务。