原文链接: Processes and Threads
进程和线程
当一个应用程序没有任何组件运行时,启动该应用的一个组件,Android 系统就会为该应用启动一个新的Linux进程,执行一个线程应用。默认,同一个应用的所有组建都运行在同一个进程和线程(该线程被称为“主线程”)里面。假如启动一个组件,该应用已经存在一个进程(可能是该应用的其他组件所启动的进程),那么该组件就会在该进程里面运行,使用同样的线程应用。但是,你可以在应用中为不同的组件安排不同的进程,你也可以为任何进程创建额外的线程。
该文档将会讨论Android内进程和线程的工作原理。
进程
默认,同一个应用的所有组件都运行在同一个进程里面,很多应用不能修改该实现。但是,如果你想控制或者改变某个组件的进程,可以在AndroidManifest.xml文件里面修改。
在AndroidManifest.xml文件里面,对于每一个组建----
<activity>
,
<service>
,
<receiver>
, 以及
<provider>
都提供了
android:process 属性,该属性指定了该组件所运行的进程。
你也可以设置该属性使得该组件运行在自己的进程中,或者,一些组件运行在同一个进程中,另一些却不。你也可以通过设置该属性,使同一个进程内运行不同的应用组件,即应用程序共享相同的Linux用户ID和相同的数字证书签证。
<application>
元素也支持
android:process 属性,通过设置默认值,适用与所有应用的组件。
当内存不足,系统需要运行其他进程时,Android可能会杀死某个进程。运行在该进程的所有组件都会被销毁。当重新运行这些组件时,进程会被重新开启。
Android系统根据进程对于用户的重要性来决定该杀死哪个进程。比如,相对于那些包含可见activities的进程来说,系统会更容易杀死那些不可见的进程。是否杀死某个进程,依赖于该进程中组件的状态。该杀死哪个进程的规则在后续会讨论。
进程生命周期
Android系统会尽可能长时间保持某个应用进程,但是,也会通过删除旧的进程回收内存用于新的或者更重要的进程。要决定到底那个进程保持还是被杀死,系统会将每个进程放入到一个“重要性层次列表”里面,这个列表是基于该进程中所运行的组件以及这些组件的状态。那些带有最低性能标志的进程首先会被淘汰掉,然后淘汰倒数第二个,一次类推,回复所需的系统资源。
有五个级别的重要性层次,下面列表里面列出了不同类型进程的重要性顺序(第一个类型最终要,最后被杀死):
1. 前端进程
改类型进程代表用户正在操作。假如某个进程符合以下任何条件中的一个,便认为是前端进程:
(1)它承载一个用户正在交互的Activity.
(2) 它承载一个
Service,该Service绑定用户正在交互的Activity.
一般情况下,只有一小部分前端进程存在与任何时候,如果内存很低,设备不能继续运行,他们会被最后杀死。通常,这时,设备达到一个内存页面的状态,为了保持用户界面的响应,杀死那些前端进程是必须的。
2. 可视进程
改进程不包含任何前端控件,但是依然存在与屏幕上面。若符合一下任一条件,则被认为属于可视进程:
(1) 它承载了一个
Activity
, 该活动不属于前端控件,但是一直可被用户可视(它的
onPause()
方法被调用)。比如,如果前端activity开启一个dialog,它运行前端activity在它背后可视。
(2) 它承载一个
Service
,该服务绑定一个可视的(或前端)的activity.
可视进程非常重要,一般不会被杀死,除非要所有的前端进程都运行。
3. 服务进程
该进程运行一个服务,该服务使用
startService()
方法被启动,该进程也不属于上述两个级别更高的类型。该服务进程也不直接绑定用户所能看到的视图。它只做那些用户关心的事情(比如说,后台播放音乐,或者网络下载数据)。系统将会一直保持它的运行,除非没有足够的内存来保存所有前台或可视进程。
4. 后台进程
该进程承载一个当前不被可视的activity(这个activity的
onStop() 方法被调用)。这些进程对用户体验没有直接影响,系统可在任何时候杀死它们,为上述三种类型的进程回收内存。通常,有许多后台进程在运行,它们被保存到LRU(最近最少使用)列表里面,以确保用户最近所看到的进程被最后杀死。如果一个Activity 正确的实现了它们的生周周期方法,并保存它的当前状态,在杀死进程时并不会影响用户体验,因为,大概你用户返回到该Activity时候,它会回复所有保存的可视数据。有关保存和回复状态信息,请参照
Activities文档。
5. 空进程
该进程不包含任何活动的控件,唯一保持该控件活动的原因就是缓存,用来提高下一次控件开启时间。系统经常会杀死这些进程,以平衡系统资源在进程缓存和底层内核缓存之间的平衡。
Android系统根据进程中所有控件的最高级别的给进程分类,比如,一个进程承载一个服务和一个可视的activity,这个进程就会被看作可视进程。而不是服务进程。
另外,一个进程的级别可能会上升,若有其他进程依赖它。如果一个进程服务与另一个进程,那么它不会比另一个进程的级别低。比如,在进程A中,一个content provide正在服务一个在进程B里面的客户端,或者在进程A里面的一个服务,被绑定到进程B里面的一个控件,那么进程A至少和B一样的级别。
因此一个进程运行一个服务的级别高于一个进程运行一个后台activites,一个activity若初始化为一个长时间操作,可以开启一个
service
代替,而不是简单的创建一个工作线程------特别是这个操作会超出这个activity的生命周期。比如,一个activity上传一副图片到web site,应该开启一个服务来操作上传,即使用户离开了该activity,该操作也可以在后台运行。使用服务保证了该操作至少拥有"服务进程"的优先级,不必在乎activity的运行情况。同样原因,broadcase receivers应该采用服务机制,而不是简单的在一个线程里面做耗时的操作。
线程
当一个应用被启动,系统会为它创建一个执行线程,叫做“主线程”。该线程非常重要,因为它主要负责调度用户事件到相应的交互的组件里面,包括重绘事件。该线程包括应用所交互的控件,这些控件出自Android UI工具包(包括,
android.widget
和
android.view包)。因此,主线程也被称为“UI线程”。
系统不会为每个组建的实例创建单独的线程。所有组件都运行在同一个进程内,并被实例化。系统都是从该线程调用每个组件。因此,那些与系统回调相关连的方法(如,
onKeyDown()
显示用户的操作,或者一个生命在后期的回调方法)都运行在该线程的UI进程里面。
例如,当用户触摸屏幕上的一个按钮,你的应用UI进程将该触摸事件调度到对应的控件上面,该控件反过来设置它的按下状态,将一个无效请求发送到事件队列里面。UI线程从队列里面取出请求,并通知该控件重绘。
当你的应用运行量大的任务来响应用户交互时,这种单一的线程模式的性能很差,除非你能很准确的实现你的应用。具体来说,如果在UI线程里面,那些执行时间长的动作,比如链接网络,查询数据库,等都会阻塞整个UI。当线程被阻塞,没有事件可以被调度,包括重绘。从用户的角度来讲,应用被挂起。更糟的是,假如UI进程被阻塞超过几秒种(目前约5秒),用户会看到
application not responding“应用程序无相应”(ANR)对话框。用户可能会退出你的程序,或者直接下载。
此外,UI工具包不是线程安全的,因此,你不能在工作线程中操作你的用户交互。因此,对于Android单系统模式,有两条原则:
(1)不要阻塞 UI进程。
(2)不要在UI进程外操作Android UI 工具箱。
工作进程
根据上面描述的单线程模式,对于应用中的UI响应非常重要,因此,你不能阻塞UI线程。如果你的执行不是瞬间的,你应该确保它们运行在单独的线程里面(“后台”或者“工作”线程)。
比如,下面代码就是一个从单独线程中监听下载图片时间,将它显示到一个
ImageView
:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("http://example.com/image.png");
mImageView.setImageBitmap(b);
}
}).start();
}
乍看,它看起来没有问题,因为它创建了一个新的线程来处理网络操作。但是,他违反了上面的第二条规则:不能在UI线程以外执行UI工具包。这个例子在工作线程而不是UI线程修改了
ImageView.这种情况能会产生不确定或者意外的行为,很难跟踪排查。
为了修复该问题,Android提供了好几种方式从其他线程访问UI线程。下面是这些方法的列表:
比如,你可以使用
View.post(Runnable)
方法修改上面的代码:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();
}
现在的代码是线程安全的:网络操作在一个单独的线程里面,
ImageView
操作却在 UI线程里面。
但是,随着操作复杂性的增加,这种代码会变得更加复杂,很难维护。使用一个工作线程处理复杂的交互,你应该考虑在工作线程中使用
Handler
,它能够处理从 UI线程传递的消息。但是,最好的解决方案就是继承
AsyncTask 类,它简化了UI交互在工作线程中的任务。
使用AsyncTask
AsyncTask
允许你在用户交互上面执行异步操作,他在工作进程中执行阻塞操作,然后将结果返回给UI线程,而不需要你来处理线程或者自己操作。
使用它时,你必须继承
AsyncTask类,实现
doInBackground() 回调方法,该方法在后台线程池里面运行,为了更新你的UI, 你应该实现
onPostExecute()
,,它会将从
doInBackground()获取结果,然后运行在UI线程里面,因此,你可以安全的更新你的UI。 在UI线程里面,你调用
execute()
方法来执行该任务。
例如,你可以使用
AsyncTask来实现前面的例子:
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
/** The system calls this to perform work in a worker thread and
* delivers it the parameters given to AsyncTask.execute() */
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
/** The system calls this to perform work in the UI thread and delivers
* the result from doInBackground() */
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
现在UI线程是安全的,代码简单,因为它将任务分成两部分,一部分在工作线程内执行,另一部分在UI线程里面执行。
你应该阅读
AsyncTask文档,对该类有个全面的了解,下面是它工作原理的简单概述:
(1)你必须使用泛型指定参数的类型, 进度值,最后结果。
(2)
doInBackground()方法在一个工作进程中自动执行。
(4)
doInBackground() 返回值被送到
onPostExecute()
(6) 你可以随时在任意的线程里面取消该任务。
注意:使用工作进程时,可能会遇到另外一个问题,因为运行的配置,可能会重启你的activity(比如,当用户改变屏幕的方向时),它可能会销毁掉你的线程。 为了搞清楚怎么让你的任务在重启中不被销毁, 以及在activity被销毁时,如何正确取消任务,请参考 例子
Shelves源码。
AsyncTask 的讲解延伸阅读:
点击打开链接
线程安全
在很多情况下,你实现的方法会被多个线程调用,因此,线程必须安全。
在远程调用方法中,这点显得有为重要,比如在一个
bound service 里面的方法。一个方法被调用,该方法在IBinder里面被实现,该方法和IBinder运行在同一个线程里面。该方法在被调用的线程里面被执行。然而,当调用者在另一个线程里面运行,该方法也会在一个线程里面被执行。比如,一个服务进程的
onBind()
方法将会被该服务进程的UI所调用,被实现的对象里面的
onBind()
返回值将会被线程池里面的线程所调用。因为一个服务可能会有多个客户端,多个线程池会在同一个时间会运行同一个
IBinder方法。
IBinder方法必须是线性安全的。
简单的说,一个 content provider 能够接受多个线程的数据请求。但是,
ContentResolver
和
ContentProvider
类都隐藏了线程交换间通信管理细节,
ContentProvider
提供的与这些请求相关连的方法-----
query()
,
insert()
,
delete()
,
update()
, 和
getType()
都会被线程池所调用,在content provider 的进程里面,而不是UI线程里面。因为i这些方法可能被很多线程在同一时间调用,因此必须线程安全。
进程间通信
Android 通过远程调用(RPCs)实现了一种进程间通行机制。在这里面,某个方法被一个Activity或者某个应用所调用,但是在远程执行(在另一个进程里面),将结果返回给调用者。该种机制将方法调用和它的数据分解为系统可理解的水平,将它从本地进程以及地址空间传送到远程进程以及地址空间,然后重新组织,重新呼叫。将结果按相反方向返回。Android提供了所有操作IPC事务的代码,用户只需定义以及事项RPC接口即可。
为了演示IPC,你的应用必须使用
bindService()绑定一个服务。 为了获取更多信息,请参照
Services开发文档。