当一个应用程序刚开始运行,同时这个应用程没有其他的正在运行的组件,android系统将会以一个单独运行的线程方式为这个应用程序开启一个新的linux进程。默认的,一个应用程序上的所有的组件运行在相同的进程和线程(被叫做“main”的线程上)上。如果一个应用程序组件开始运行时,同时为这个应用程序也存在一个进程(因为这个应用程序存在的其他的组件),那么这个组件在这个进程中运行并且使用正在执行的那个线程。然而,您可以安排不同的组件在你的应用程序去运行在单独的进程中,你还可以为任何进程创建额外的线程
这篇文章讨论的是进程和线程是如何工作在一个android程序上的
进程
默认的情况是同一个应用程序上的所有的组件运行在相同的进程上,多数的应用程序不应改变这个默认的情况。当然,你也可以在清单列表中设置,那个组件运行在哪一个进程上。
这个清单文件为每一类型的组件设置一个清单条目-<activity>,<service>,<receiver>,<provider>-它们都支持android:process这种属性,这种属性能够控制摸一个进程供哪一个组件运行。你能够设置这个属性让每一个组件运行在它们自己的进程中或者一些组件共享一个进程。你还能够设置这个属性android:process,让不同的应用程序的组件运行在相同的进程上-实现方式是提供给这些应用程序相同的Linux用户ID和赋予同样的证书。
在<application>标签中也支持android:process属性,它设置一个值后,所有的组件都适用这个值。
当内存不够,同时又有其他的进程请求使用内存的时候,android就会关闭一个进程供使用者使用,当一个进程被关闭后,运行在这个进程上的应用程序组件都会被摧毁。直到这个应用程序上的组件重新运行工作的时候,这个进程也会重新运行。
对于决定关闭哪一个进程,android系统会衡量哪一个对使用者比较重要。例如,相比一个正在可见的活动的窗口它可能会比较容易关闭一个长时间没有活动的后台的活动窗口,因此,决定是否终止一个进程取决于组件中在这个进程中运行的状态,下面将讨论关闭哪个进程的判定的规则
进程的周期
Android系统试图尽可能保持应用程序进程,但终究还是需要删除旧的进程来回收内存给那些新的或更重要的是过程。为了确定哪个进程保留或终止,系统把每个进程都划入一个基于组件运行在这些组件的过程和状态的“重要性层次结构”表。重要性最低的进程是第一个被消除,然后是次重要性,等等,以方便必要时恢复系统资源。
有五个级别在重要性层次结构表。下面的列表给出了不同类型的进程的重要性(第一个过程是最重要的,也是最后被终止的)。
1、前台进程
用户去做一些事情所产生的一个进程。判定一个进程是不是前台进程,看是否符合下面描述的任何的一种情况
2、可见进程
3、服务进程
4、后台进程
后台进程又由一个叫做LRU(最近使用列表)的列表控制,最长时间没有使用的最先被终止
5、空进程
为什么会产生一个空进程呢,是因为系统会为某个程序预先产生一个进程,以便能够快速的加载
线程
当应用程序启动时,系统为应用程序创建一个线程的执行,称为"main",这个线程非常重要,因为它负责把事件分发给相应的用户界面部件,包括屏幕绘图事件。也是你的应用程序与组件交互的线程,这些组件来源于Android UI 工具包(组件从Android.widget和android.view)。因此,主线程有时也称为UI线程。
系统不为每个组件的实例创建一个单独的线程。所有组件运行在相同的进程并在UI线程被实例化,并且系统调用每一个组件都是通过这个线程派发的。通常,响应系统回调的方法(例如:onKeyDown)也是在这个进程的UI线程中运行的。
例如,当用户触摸屏幕上的按钮时,应用程序的UI线程分派触摸事件的小部件,进而设置按下状态,传递一个无效请求事件队列。UI线程出列请求和通知小部件应该改变自己。
当你的应用程序来响应用户交互执行密集的工作,这个单线程模型能产生不佳的表现,除非你正确的处理你的应用程序。具体地说,如果一切都发生在UI线程,执行长期操作如网络访问或数据库查询将阻止整个UI。当线程被阻塞时,所有事件都不能被分发,包括屏幕绘图事件。从用户的角度来看,应用程序挂起。更糟糕的是,如果UI线程被阻塞超过几秒钟目前(大约5秒钟),用户界面就会弹出“应用程序没有响应”(ANR)对话框。用户可能会决定退出你的应用程序和卸载它,通常都是带着嫌弃的情绪。
此外,Andoid UI工具包不是线程安全的。所以,你不能操作UI允许从一个工人操作的用户界面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();
}
首先,这段代码似乎运行的很好,因为它创建了一个新线程来处理网络操作。然而,它违反了单线程模型的第二条规则:不要从外部访问Android UI 工具包-这个例子修改了ImageView工作线程而不是UI线程。这可能导致未定义的和意想不到的行为,并且很难非常耗费时间去查出问题。
要修复这个问题,Android提供了几种方法来从其他线程访问UI线程。这里是一个列表的方法,可以得到一些帮助:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
例如,你可以修改上面的代码通过使用 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> {
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
现在UI是安全的,代码更简单,因为它分离的部分应该做的工作在一个工作线程的部分应该在UI线程上。