[Android]Process&Thread-基本原理
目录 |
android中进程与线程 - Processes and Threads
当一个应用程序开始运行它的第一个组件时,Android会为它启动一个Linux进程,并在其中执行一个单一的线程。默认情况下,应用程序所有的组件均在这个进程的这个线程中运行。然而,你也可以安排组件在其他进程中运行,而且可以为任意进程衍生出其它线程.
下面将介绍如何在android系统中使用线程和进程.
进程 - Processes
默认情况下,同一应用程序的所有组件运行在同一进程中。不过,如果你需要控制某个组件属于哪个进程,也可以通过修改manifest文件来实现。
manifest文件中的所有支持android:process属性的那些项,例如[活动| ],[服务| ], ,和[内容提供|]都可以指定一个进程,这样这些组件就会在指定的进程中运行.你可以设置这个属性使每个组件运行于其自己的进程或只是其中一些组件共享一个进程.你也可以设置android:process以使不同应用的组件们可以运行于同一个进程—这样需要这些应用共享同一个用户ID并且有相同的数字证书.
元素也支持android:process属性,用于为所有的组件指定一个默认值.
Android系统可能在某些时刻决定关闭一个进程,比如内存很少了并且另一个进程更迫切的需要启动时.进程被关闭时,其中的组件们都被销毁.如果重新需要这些组件工作时,进程又会被创建出来。
当决定关闭哪些线程时,Android系统会衡量进程们与用户的紧密程度.例如,比起一个具有可见的activity的进程,那些所含activity全部不可见的进程更容易被关闭.如何决定一个进程是否被关闭,取决于进程中运行的组件们的状态.决定关闭进程的规则将在下面讨论.
进程的生命期
Android系统会尽量维持一个进程的生命,直到最终需要为新的更重要的进程腾出内存空间。为了决定哪个进程该终止,系统会跟据运行于进程内的组件的和组件的状态把进程置于不同的重要性等级。当需要系统资源时,重要性等级越低的先被淘汰。
重要性等级被分为5个档。下面列出了不同类型的进程的重要性等级(第一个进程类型是最重要的,也是最后才会被终止的)
1前台进程
用户当前正在做的事情需要这个进程。如果满足下面的条件,一个进程就被认为是前台进程:
1)这个进程拥有一个正在与用户交互的Activity(这个Activity的onResume() 方法被调用)。
2)这个进程拥有一个绑定到正在与用户交互的activity上的Service。
3)这个进程拥有一个前台运行的Service — service调用了方法 startForeground().
4)这个进程拥有一个正在执行其任何一个生命周期回调方法(onCreate(),onStart(), 或onDestroy())的Service。
5)这个进程拥有正在执行其onReceive()方法的BroadcastReceiver。
通常,在任何时间点,只有很少的前台进程存在。它们只有在达到无法调合的矛盾时才会被终止--如果内存太小而不能继续运行时。通常,到了这时,设备就达到了一个内存分页调度状态,所以需要终止一些前台进程来保证用户界面的反应.
2可见进程
一个进程不拥有运行于前台的组件,但是依然能影响用户所见。满足下列条件时,进程即为可见:
1)这个进程拥有一个不在前台但仍可见的Activity(它的onPause()方法被调用)。例如当一个前台activity启动一个对话框时,就出了这种情况(即没有被完全覆盖)。
2)这个进程拥有一个绑定在前台(或者可见)Activity的服务。 一个可见的进程是极其重要的,通常不会被终止,除非内存不够,需要释放内存以便前台进程运行。
3服务进程
一个进程不在上述两种之内,但它运行着一个被startService()所启动的service。
尽管一个服务进程不直接影响用户所见,但是它们通常做一些用户关心的事情(比如播放音乐或下载数据),所以除非系统没有足够的空间运行前台进程和可见进程时才会终止一个服务进程。
4 后台进程
一个进程拥有一个当前不可见的activity(activity的onStop()方法被调用)。
这样的进程们不会直接影响到用户体验,所以系统可以在任意时刻杀了它们从而为前台、可见、以及服务进程们提供存储空间。通常有很多后台进程在运行。它们被保存在一个LRU(最近最少使用)列表中来确保拥有最近刚被看到的activity的进程最后被杀。如果一个activity正确的实现了它的生命周期方法,并保存了它的当前状态,那么杀死它的进程将不会对用户的可视化体验造成影响。因为当用户返回到这个activity时,这个activity会恢复它所有的可见状态。
5空进程
一个没有任何active组件的进程。
保留这类进程的唯一理由是高速缓存,这样可以提高下一次一个组件要运行它时的启动速度。系统经常为了平衡在进程高速缓存和底层的内核高速缓存之间的整体系统资源而终止它们。
跟据进程中当前活动的组件的重要性,Android会把进程按排在其可能的最高级别。例如,如果一个进程拥有一个service和一个可见的activity,进程会被定为可见进程,而不是服务进程。
另外,如果被其它进程所依赖,一个进程的级别可能会被提高——一个服务于其它进程的进程,其级别不可能比被服务进程低。 因为拥有一个service的进程比拥有一个后台activitie的进程级别高,所以当一个activity启动一个需长时间执行的操作时,最好是启动一个服务,而不是简单的创建一个工作线程。尤其是当这个操作可能比activity的生命还要长时。例如,一个向网站上传图片的activity,应该启动一个service,从而使上传操作可以在用户离开这个activity时继续在后台执行。使用一个service保证了这个操作至少是在"服务进程"级别,而不用管activity是否发生了什么不幸。这同样是广播接收者应该使用service而不是简单地使用一个线程的理由。
线程 - Threads
当一个应用被启动,系统创建一个执行线程,叫做"main"。这个线程是十分重要的,因为它主管向用户界面控件派发事件。其中包含绘图事件。它也是你的应用与界面工具包(android.widget和 android.view包中的组件)交互的地方。于是main线程也被称为界面线程。
系统不会为每个组件的实例分别创建线程。所有运行于一个进程的组件都在界面线程中被实例化,并且系统对每个组件的调用都在这个线程中派发。因此,响应系统调用的方法(比如报告用户动作的onKeyDown()或一个生命周期回调方法)永远在界面线程中进程。
例如,当用户触摸屏幕上的一个按钮时,你的应用的界面线程把触摸事件派发给控件,然后控件设置它的按下状态再向事件队列发出一个自己界面变得无效的请求,界面线程从队列中取出这个请求并通知这个控件重绘它自己。
当你的应用在响应用户交互时需执行大量运算时,这种单线程的模式会带来低性能,除非你能正确的优化你的程序。如果所有事情都在界面线程中发生,执行比如网络连接或数据库请求这样的耗时操作,将会阻止整个界面的响应。当线程被阻塞时,就不能派发事件了,包括绘图事件。从用户的角度看,程序反应太慢了。甚至更糟的是,如果界面线程被阻塞几秒钟(5秒左右),用户就户抱怨说程序没反应了,用户可能会退出并删掉你的应用。
此外,Andoid界面不是线程安全的。所以你绝不能在一个工作线程中操作你的界面—你只能在界面线程中管理的你的界面。所以,对于单线程模式有两个简单的规则:
- 不要阻塞界面线程
- 不要在界面线程之外操作界面。
工作线程 - Worker threads
由于上述的单线程模式,不要阻塞你的界面线程以使你的应用的界面保持响应是非常重要的,那么如果你有不能很快完成的任务,你应把它们放在另一个线程中执行(后台线程或工作线程)。
例如,下面是的代码是响应click事件,在另外一个线程中下载一个图片并在一个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();
}
粗略看来,这样能很好的工作,因为它创建了一个新线程来进行网络操作。然而它违反了第二条规则:不要在界面线程之外操作界面。这段代码在工作线程中修改了ImageView。这会导至未定义的异常出现,并且难以调试追踪。
为了能改正这个问题,Android提供了很多从其它线程来操作界面的方法。下面是可用的方法:
- 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 在界面线程中改变。
然而,由于操作复杂性的增长,这样的代码就变得复杂并难以维护,为了处理更复杂的交互,你可能需要在线程中用到Handler对象来处理从UI线程中传递的消息。但最好的解决办法是继承AsyncTask类,这样可以使得线程和UI交互这一类任务变得更轻松。
使用-AsyncTask
AsyncTask可以让程序进行异步工作,它在一个线程中执行某些操作,之后将结果返回给UI线程。
使用AsyncTask类时,你需要继承AsyncTask类并实现doInBackground()回调方法。要更新UI界面,需要实现onPostExecute(),并从doInBackground()方法中获得结果,最后,你可以在UI线程中调用execute()方法来执行操作,这样就可以安全的更新UI界面。
例如,我们用AsynTask来实现上面的示例:
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask <String[], Bitmap> {
protected Bitmap doInBackground(String[] urls) {
return loadImageFromNetwork(urls[0]);
}
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
因为AsyncTask将工作分成了两部分,UI线程和工作线程都做自己应该做的那部分任务,因此我们就可以简单的实现一个安全的UI更新任务了。
更多关于AsyncTask的说明请参看AsyncTask类的参考,这里简单介绍下它是如何工作的:
- AsyncTask定义了三种泛型类型 Params,Progress和Result。Params是启动任务执行的输入参数,比如HTTP请求的URL。Progress是后台任务执行的百分比。Result是后台执行任务最终返回的结果,比如String,Integer等。
- doInBackground()会在工作线程中自动执行。
- onPreExecute(), onPostExecute(), and onProgressUpdate()这三个方法都是在UI线程中调用。
- doInBackground()方法返回的值会传递给onPostExecute()方法。
- 你可以在doInBackground()方法中随时调用publishProgress()方法以此在UI线程中更新执行进度。
- 你可以随时在任何线程中取消任务。
Caution: Another problem you might encounter when using a worker thread is unexpected restarts in your activity due to a runtime configuration change (such as when the user changes the screen orientation), which may destroy your worker thread. To see how you can persist your task during one of these restarts and how to properly cancel the task when the activity is destroyed, see the source code for the Shelves sample application.
线程安全的方法 - Thread-safe methods
在某些情况下,你实现的方法可能会被多个线程调用,因此要保证该方法是线程安全的。
一些远程调用的方法——例如绑定服务。当我们在同一个进程中调用了某一个正在运行的IBander中的方法,这个方法会运行在调用者的线程中。然而,当调用另一个进程中的方法时,该方法会在系统为这个进程开启的线程池中的某一个线程中执行。例如,绑定服务要在UI线程中调用,onBand()返回的对象会在线程池中被调用。因为一个服务可以有多个客户端,多个池线程可以在同一时间进行相同的IBinder方法。因此,实现IBinder方法必须是线程安全的。
Similarly, a content provider can receive data requests that originate in other processes. Although the ContentResolver and ContentProvider classes hide the details of how the interprocess communication is managed, ContentProvider methods that respond to those requests—the methods query(), insert(), delete(), update(), and getType()—are called from a pool of threads in the content provider's process, not the UI thread for the process. Because these methods might be called from any number of threads at the same time, they too must be implemented to be thread-safe.
进程间通信 - Interprocess Communication
Android提供了进程间通信(IPC)机制,使用远程过程调用(RPC),其中一个方法被称为活动(Activity)或其他应用程序组件,但任何返回给调用者的结果都在另一个进程中执行。这就需要在系统级别从本地进程和远程进程的地址空间中调用方法和参数,并将返回值按相同的方法传递回来。android系统能完美支持进程间通信(IPC),这样你就可以专注于制定和实现RPC编程接口。
要实现进程间通信(IPC),应用程序必须绑定到一个服务,使用bindService()。欲了解更多信息,请参阅服务(Service)开发指南。