这是在官方文档的基础上加上自己的理解。英文好的也可以查看官方文档。
当一个应用组件开始并且该应用没有其他组件在运行的时候,Android系统便为该应用新建一个包含了一个正在运行的线程的Linux进程。
默认情况下,一个应用的所有组件都在一个相同的进程和线程(叫做主线程)中运行。如果一个应用组件开始运行而且该应用中已经存在了一个进程(这是由于该应用中其他组件的存在),则该组件在该进程中开始并使用相同的线程去执行。
然而,你可以在你的应用中安排不同的的组件在不同的进程中执行,并且你可以为任何进程创建额外的线程。
进程
默认情况下,一个相同的应用中所有的组件都运行在相同的一个进程中,而且大多数应用都不应该有例外。然而,如果你需要去控制一个确切的组件应该属于哪一个进程,则可以在mainfest文件中进行如下操作:
android 的 mainfest 文件为每种类型的组件元素:、、、and 提供入口,这几种组件元素都支持 android|:process 属性,该属性是用来描述一种组件应该运行在什么进程中的。你可以设置这个属性,然后每个组件就会运行在它所属于的进程,或者是部分组件能够共享进程而其它组件没有共享,能够这样是因为每个应用都共享相同的Linus user ID 并且被分配了相同的证书。
元素同样支持 android:process属性,如果设定一个默认值,则可以应用到所有的组件中。
当 Android 系统内存不够或者是其它更加直接服务用户的进程需要内存的时候,系统可能会决策性地杀掉部分进程。当进程被杀掉的时候,进程中所有的组件也会被销毁。
当系统要决策性杀掉进程的时候,它会权衡每个进程相对于用户的相对重要程度。例如:
相对比一个运行着可见的activity,更好的是去杀掉一个运行着不可见的activity的进程。
因此,该杀掉那个进程,就决定于进程中运行的组件的状态。这条决策规则将在下面讨论。
进程的生命周期
Android 系统尝试去尽可能久地保存所有的应用进程,但是终究还是需要去移出一些旧的进程来为新的更重要的进程释放资源。系统根据进程中的运行的组件的状态,为每个进程都分配了一个重要级分级,并用这个来决定哪个进程应该被杀掉。重要性最低的进程最先被杀掉,然后是重要性次最低的,以此类推。
一下是5个重要级分级,第一个是最重要也是最后被killed的。
- Foreground Process:该进程是用户当前正在做的事所必须的。当一下的条件之一满足时,一个进程会被认为是 Foreground Process:
- 它运行一个用户正在进行交互的Activity(该Activity的onResume()方法被调用)
- 它运行一个Service,且该Service绑定了一个用户正在交互的Activity
- 它运行一个run in foreground的Service,该service 调用了startForeground()方法
- 它运行一个Service,且该Service正在执行一个生命周期(onCreate(), onStart(), or onDestroy())
- 它运行一个 BroadcastReceiver ,且该 BroadcastReceiver 正在执行它的onReceive() 方法
总的来说,在任意一给定时间点,只有很少一部分foreground (前台)进程存在。他们只有在系统内存非常低的时候才会被杀掉,在这种情况下,杀掉这类进程为了满足用户的交互相应。
- Visible process:可见进程本身并没有包含前台组件,但是它依旧会影响用户屏幕上所见到的东西。如果一个进程满足以下条件之一,我们则称之为可见进程:
- 它运行着一个非前台的Activity,但是这个Activity依旧对用户可见,(该Activity的onPause()方法被调用)这种情况时有可能出现的,比如:一个前台的Activity开启了一个dialog,此时该Activity在dialog后面,但是依旧可见
- 它运行着一个服务,并且该服务绑定着一个可见的或者是前台的Activity
可见进程也是非常重要的,一般来说它并不会被杀掉,除非前台进程内存不够
-
Service Process:服务进程是指一个进程运行着一个服务,该服务是由 startService() 方法启动的,并且该服务不会进入两个更高的categories。尽管服务进程并不会直接跟我们用户所见的相关。但是它们实际中做的事是用户所关心的,比如:后台播放音乐或者是从网上下载东西。所以,除非前台进程和可见进程所需要的内存不够,不然系统是不会杀掉这类进程的。
-
Background process:指的是一个进程保持一个当前并不可见的Activity(该Activity的 onStop() 方法被调用了)。这类进程对用户的体验没有直接的影响,并且如果前台、可见或者是服务进程所需要的内存不够的时候,系统可以随时杀掉它们。通常来说,系统中会有很多后台进程,所以这类进程通常是通过LRU(least recently used)表来维护的。这张表能够保证用户最近最常用的Activity是最后被杀掉的。如果一个activity能够正常调用它自身的生命周期并且能够保存当前的状态的话,杀掉它所属于的进程并不会对用户体验产生什么可见的影响,这是因为当用户按后退键或者是其他返回操作退出Activity的时候,activity会保存所有的可见状态。
-
Empty process:一个进程并没有运行任何一个活动的应用组件。该类进程存在的作用就是为了保持高速缓存,提高一个组件重新启动时的响应速度。系统为了平衡进程缓存和内核缓存之间的资源经常会杀掉这些进程。
Android 中进程的优先级高低取决于它所运行的组件中重要性最高的组件。比如一个进程运行着一个服务和一个可见Activity,由于可见Activity的重要性比服务高,所以该进程属于可见进程而不是服务进程。
另外,一个进程的优先级可能会由于其他进程依赖于它而提升。如果一个进程服务另外一个进程,那么服务的进程的优先级必须高于或等于被服务的进程的优先级。
由于一个正在运行一个服务的进程的优先级高于运行在后台的进程,所以如果我们想要在后台进程一个比较长时间的操作的话,相对于使用工作线程,更好的方法应该是使用service,尤其是当activity退出之后希望后台服务继续执行的情况。例如,一个activity要从网站上下载一张图片,那么久应该开启一个service去执行这项操作,因为service能够保证即使用户离开了当前的Activity也能够继续执行,因为服务进程的优先级比后台进程高。同样的,在使用广播接收的时候也应该使用服务,而不是使用耗时的线程。
线程
当应用程序启动的时候,系统会为该应用程序创建一个线程,叫做主线程。主线程非常重要,因为它负责把事件分发给用户界面的控件,包括屏幕绘制事件,同时它也是你的应用程序与Android UI工具箱中的组件(来自 android.widget 和 android.view 两个 packages)进行交互的线程。所以,主线程通常也叫做UI线程。
系统并不会为每一个控件实例创建一个独立的线程,所有的组件都运行在同一个进程中,并在UI线程中进程初始化,而且系统调用的组件也是从该线程中派送过来的。因此,系统的各种回调函数(例如:onKeyDown() ,用于响应用户的动作,或者是各种生命周期回调方法)都是运行在进程中的UI线程。
例如:当用户触摸屏幕上的一个按钮时,你的UI线程就会为该按钮控件分配一个触发事件,该事件将按钮控件设置为 pressed 状态,同时传递一个invalidate 请求给事件队列,UI线程就会处理这个事件队列并提醒按钮组件应该重新绘制。
如果你在你的应用交互界面执行了大量的操作,这个单线程模型可能就会表现得很差,除非你能正确实现这些大量操作。通常来说,如果全部得工作都在主线程中完成,其中如果包含一些比较耗时的操作比如上网下载资源或者是数据库查询等,就会导致UI线程阻塞,此时主线程什么都不会执行,在等待该耗时操作完成,包括一些绘制事件。此时从用户的角度来说,该应用好像是挂掉了。更糟糕的,如果UI线程阻塞超过5秒,系统就会判断该应用为ANR(application not responding)。
还有,由于所有的Android UI 工具都不是线程安全的,所以你不能在你的工作线程修改UI,所有的UI更新等操作都必须在UI线程中完成。因此,对于Android的单线程模型,有以下两条简单的规则:
- 不要阻塞UI线程
- 不要在UI线程以外的其它线程修改UI
工作线程
由于上面所述的单线程模型,为了不阻塞UI线程导致应用无响应,如果你的操作所需要的时间比较长,你最好让这些操作在其他线程中执行(叫做后台线程或者是工作线程)
例如:下面是一个按钮点击事件在工作线程从网上下载图片的实例:
<span style="font-family:Microsoft YaHei;font-size:14px;">public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("http://example.com/image.png");
mImageView.setImageBitmap(b);
}
}).start();
}</span>
乍一看上面这段代码好像是对的,它开启线程去从网上下载图片。但是它违反了单线程模型的第二条规则,它在工作线程中修改了 mImageView 的图片资源,这是不允许的,可能会产生意想不到的错误。
为了解决这个问题,Android提供了一下几种方法来实现在工作线程修改UI:
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable, long)
例如:上面的代码利用 View.post(Runnable) 方法来修改:
<span style="font-family:Microsoft YaHei;font-size:14px;">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();
}</span>
现在这种操作是线程安全的,在工作线程下载图片,然后在主线程中更新UI。
然而,你要在后台执行的操作可能会很复杂,这时候上面几种方式就不是很使用了,这是你可以考虑在工作线程中使用 Handler 来处理来自UI线程的 messages。不过最好用最简单的或许就该属于 AsyncTask 类了,下面简单介绍该方法的使用。更详细的关于 AsyncTask 的使用请参考博客:
Android实战技巧:深入解析AsyncTask 和 android异步任务详解 AsynTask
所以这里只提供一个实例:
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);
}
}