进程和线程
标签(空格分隔): Android API指导
此文档介绍Android应用的进程和线程是如何工作的。
进程
默认的,同一个应用中的所有组件都是运行在同一个进程。
所有组件在manifest文件中可以通过android:process属性来指定运行的进程。
进程生命周期
Android系统会给每个进程分级,如果系统需要回收内存时,就先销毁优先级低的进程,各层级如下(序号越高,重要性越低,越容易被销毁):
- 前台进程
用户正在使用的,有如下场景:
- 与用户交互的Activity界面(onResume已经执行)
- 绑定到与用户正在交互界面的Service
- 在前台运行的Service(用startForeground启动)
- 正在执行生命周期回调函数的Service(onCreate、onStart、onDestroy)
- 正在执行onReceiver的BroadcastReceiver
一般来说,某个时间点上只有少数的前台进程在运行。他们只有在内存低到无法继续运行的时候才有可能会被销毁。
3. 可视进程
进程不包含前台组件,但仍是用户可见的,有如下场景:
- Activity不在前台,但是仍然是可见的(onPause已经执行),例如A弹出前台对话框B,B不能完全覆盖A时。
- 绑定到可见或者前台Activity的service
- 服务进程
由startService启动的服务,并且不在1和2场景中。 - 后台进程
承载不可见的Activity(onStop已经执行) - 空进程
不承载任何组件的进程
线程
Android UI是单线程模型,应用中所有组件的实例都在同一个UI线程中执行,当UI线程阻塞时,会无法分发用户的点击事件导致ANR。
另外,UI线程是非线程安全的,所有与用户界面相关的交互必须都放在UI线程中,而不是用其他工作线程,因此,对于Android线程模型来说有两条简单的规则:
- 不要阻塞UI线程
- 不要在UI线程之外的其他线程中访问UI元素
工作线程
对于第一条规则,可以将耗时的操作放在新线程中,比如咋新线程中下载一个图片:
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线程中使用了界面元素mImageView。
为了避免这个问题,Android提供了如下方法:
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable, long)
例如,可以这么用:
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();
}
然而对于复杂的操作,上述代码很难维护,你可以使用Handler来处理,也可以扩展AsyncTask类
使用AsyncTask
AsyncTask允许执行UI元素的异步操作,它会在工作线程中执行阻塞UI的操作,然后将结果发送给UI线程,而不需要你自己处理线程。
使用方法:继承AsyncTask,实现在后台线程池执行的doInBackground,实现onPostExecute来更新UI。
例子:
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
/** 系统在工作线程中执行这个动作,其中入参是由AsyncTask.execute()提供 */
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
/** 系统调用这个刷新UI界面,其中result是doInBackground()的返回值 */
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
- AsyncTask用之前,建议看一下SDK文档
- 运行时的配置变化(比如横竖屏)会销毁你的工作线程,可以查看Shelves例子代码。
线程安全方法
某些情况下,你写的方法可能被多个线程调用,那就需要这个方法是线程安全的。
像Service的onBind,ContentProvider的query、insert、delete、update、getType都是类似需要线程安全的函数。
进程间通讯
IPC,RPC,详见Service章节