Android进程与线程详解

来源:互联网 发布:淘宝在外国怎么样 编辑:程序博客网 时间:2024/04/29 08:07

进程

当一个应用的组件开始运行,并且这个应用没有其它的组件在运行,系统会为这个应用启动一个新的Linux进程,这个进程只有一个线程.默认情况下,一个应用的所有组件都运行在一个进程和线程(主线程)中.如果一个应用的线程开始运行,并且已经存在这个应用的线程了(因为有这个应用程序的另一个组件已经运行了),于是这个组件就会在这个已有的进程中启动并且运行在同一个线程中.然而,你完全可以安排不同的组件运行于不同的进程,并且你可以为任何程序创建另外的线程.

 

 

进程

默认下,同一个程序的所有组件都运行在同一个进程中并且大多数程序不必改变这一状况.然而,如果你非要与众不同,也可以通过修改manifest文件实现.

 

manifest文件中的所有支持Android:process属性的那些项(<activity>,<service>, <receiver>,<provider>)都可以指定一个进程,于是这些组件就会在这个进程中运行.你可以设置这个属性使每个组件运行于其自己的进程或只是其中一些组件共享一个进程.你也可以设置android:process以使不同应用的组件们可以运行于同一个进程—假如这些应用共享同一个用户ID并且有相同的数字证书.

 

<application>元素也支持android:process属性,用于为所有的组件指定一个默认值.

 

Android可能在某些时刻决定关闭一个进程,比如内存很少了并且另一个进程更迫切的需要启动时.进程被关闭时,其中的组件们都被销毁.如果重新需要这些组件工作时,进程又会被创建出来.

 

当决定关闭哪些线程时,Android系统会衡量进程们与用户的紧密程度.例如,比起一个具有可见的activity的进程,那些所含activity全部不可见的进程更容易被关闭.如何决定一个进程是否被关闭,取决于进程中运行的组件们的状态.决定关闭进程的规则将在下面讨论.

 

进程的生命期

 

 

Android系统会尽量维持一个进程的生命,直到最终需要为新的更重要的进程腾出内存空间。为了决定哪个该杀哪个该留,系统会跟据运行于进程内的组件的和组件的状态把进程置于不同的重要性等级。当需要系统资源时,重要性等级越低的先被淘汰。

 

重要性等级被分为5个档。下面列出了不同类型的进程的重要性等级(第一个进程类型是最重要的,也是最后才会被杀的):

 

1前台进程

 

用户当前正在做的事情需要这个进程。如果满足下面的条件,一个进程就被认为是前台进程:

 

这个进程拥有一个正在与用户交互的Activity(这个ActivityonResume()方法被调用)

 

这个进程拥有一个绑定到正在与用户交互的activity上的Service

 

这个进程拥有一个前台运行的Service — service调用了方法 startForeground().

 

这个进程拥有一个正在执行其任何一个生命周期回调方法(onCreate(),onStart(),onDestroy())的Service

 

这个进程拥有正在执行其onReceive()方法的BroadcastReceiver

 

 

通常,在任何时间点,只有很少的前台进程存在。它们只有在达到无法调合的矛盾时才会被杀--如果内存太小而不能继续运行时。通常,到了这时,设备就达到了一个内存分页调度状态,所以需要杀一些前台进程来保证用户界面的反应

 

2可见进程

 

 

一个进程不拥有运行于前台的组件,但是依然能影响用户所见。满足下列条件时,进程即为可见:

 

 

这个进程拥有一个不在前台但仍可见的Activity(它的onPause()方法被调用)。当一个前台activity启动一个对话框时,就出了这种情况。

 

3服务进程

 

一个可见进程被认为是极其重要的。并且,除非只有杀掉它才可以保证所有前台进程的运行,否则是不能动它的。

 

这个进程拥有一个绑定到可见activityService

 

一个进程不在上述两种之内,但它运行着一个被startService()所启动的service

 

尽管一个服务进程不直接影响用户所见,但是它们通常做一些用户关心的事情(比如播放音乐或下载数据),所以系统不到前台进程和可见进程活不下去时不会杀它。

4后台进程

 

 

一个进程拥有一个当前不可见的activity(activityonStop()方法被调用)

 

这样的进程们不会直接影响到用户体验,所以系统可以在任意时刻杀了它们从而为前台、可见、以及服务进程们提供存储空间。通常有很多后台进程在运行。它们被保存在一个LRU(最近最少使用)列表中来确保拥有最近刚被看到的activity的进程最后被杀。如果一个activity正确的实现了它的生命周期方法,并保存了它的当前状态,那么杀死它的进程将不会对用户的可视化体验造成影响。因为当用户返回到这个activity时,这个activity会恢复它所有的可见状态。

 

 

5空进程

 

一个进程不拥有入何active组件。

 

保留这类进程的唯一理由是高速缓存,这样可以提高下一次一个组件要运行它时的启动速度。系统经常为了平衡在进程高速缓存和底层的内核高速缓存之间的整体系统资源而杀死它们。

 

 

 

 

跟据进程中当前活动的组件的重要性,Android会把进程按排在其可能的最高级别。例如,如果一个进程拥有一个service和一个可见的activity,进程会被定为可见进程,而不是服务进程。

 

另外,如果被其它进程所依赖,一个进程的级别可能会被提高—一个服务于其它进程的进程,其级别不可能比被服务进程低。

 

因为拥有一个service的进程比拥有一个后台activitie的进程级别高,所以当一个activity启动一个需长时间执行的操作时,最好是启动一个服务,而不是简单的创建一个工作线程。尤其是当这个操作可能比activity的生命还要长时。例如,一个向网站上传图片的activity,应该启动一个service,从而使上传操作可以在用户离开这个activity时继续在后台执行。使用一个service保证了这个操作至少是在"服务进程"级别,而不用管activity是否发生了什么不幸。这同样是广播接收者应该使用service而不是简单地使用一个线程的理由。

 

 

 

线程

 

当一个应用被启动,系统创建一个执行线程,叫做"main"。这个线程是十分重要的,因为它主管向用户界面控件派发事件。其中包含绘图事件。它也是你的应用与界面工具包(Android.widget android.view包中的组件)交互的地方。于是main线程也被称为界面线程。

 

系统不会为每个组件的实例分别创建线程。所有运行于一个进程的组件都在界面线程中被实例化,并且系统对每个组件的调用都在这个线程中派发。结果,响应系统调用的方法(比如报告用户动作的onKeyDown()或一个生命周期回调方法)永远在界面线程中进程。

 

例如,当用户触摸屏幕上的一个按钮时,你的应用的界面线程把触摸事件派发给控件,然后控件设置它的按下状态再向事件队列发出一个自己界面变得无效的请求,界面线程从队列中取出这个请求并通知这个控件重绘它自己。

 

当你的应用在响应用户交互时需执行大量运算时,这种单线程的模式会带来低性能,除非你能正确的优化你的程序。特别的,如果所有事情都在界面线程中发生,执行比如网络连接或数据库请求这样的耗时操作,将会阻止整个界面的响应。当线程被阻塞时,就不能派发事件了,包括绘图事件。从用户的角度看,程序反应太慢了。甚至更糟的是,如果界面线程被阻塞几秒钟(大5秒钟吧),用户就户抱怨说程序没反应了,用户可能因而退出并删掉你的应用。

 

此外,Andoid界面不是线程安全的。所以你绝不能在一个工作线程中操作你的界面—你只能在界面线程中管理的你的界面。所以,对于单线程模式有两个简单的规则:

 

1 不要阻塞界面线程

 

2不要在界面线程之外操作界面。

 

工作线程

 

由于上述的单线程模式,不要阻塞你的界面线程以使你的应用的界面保持响应是非常重要的,那么如果你有不能很快完成的任务,你应把它们放在另一个线程中执行(后台线程或工作线程)

 

例如,下面是的代码是响应click事件,在另外一个线程中下载一个图片并在一个ImageView中显示它:

 

 

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public void onClick(View v) {    
  2.   
  3.     new Thread(new Runnable() {    
  4.   
  5.         public void run() {    
  6.   
  7.             Bitmap b = loadImageFromNetwork("http://example.com/image.png");    
  8.   
  9.             mImageView.setImageBitmap(b);    
  10.   
  11.         }    
  12.   
  13.     }).start();    
  14.   
  15. }    


第一眼,这看起来能很好的工作,因为它创建了一个新线程来进行网络操作。然而它违反了第二条规则:不要在界面线程之外操作界面—它简单的在工作线程中修改了ImageView。这会导至未定义的异常出现,并且难以调试追踪。

为了能改正这个问题,Android提供了很多从其它线程来操作界面的方法。下面是可用的方法们:

 

 Activity.runOnUiThread(Runnable)

 

 View.post(Runnable)

 

 View.postDelayed(Runnable,long)

 

 

例如,你可以用View.post(Runnable)来修正上面的问题:

 

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public void onClick(View v) {    
  2.   
  3.     new Thread(new Runnable() {    
  4.   
  5.         public void run() {    
  6.   
  7.             final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");    
  8.   
  9.             mImageView.post(new Runnable() {    
  10.   
  11.                 public void run() {    
  12.   
  13.                     mImageView.setImageBitmap(bitmap);    
  14.   
  15.                 }    
  16.   
  17.             });    
  18.   
  19.         }    
  20.   
  21.     }).start();    
  22.   
  23. }    


 

现在这个实现终于是线程安全的了:网络操作在另一个线程中并且ImageView在界面线程中改变。

 

 

 

AsyncTask使你可以在你的界面上执行异步工作。它在一个工作线程中执行耗时操作然后把结果传给界面线程,不需要你亲自去管理线程和句柄。

要使用它,你必须从AsyncTask派生一个类并实现回调方法doInBackground(),此方法在一个后台的线程池中运行。要更新你的界面,你应实现onPostExecute(),它把doInBackground()的结果弄过来并在界面线程中运行,于是你可以安全地更新你的界面。你可以在界面线程中调用execute()来执行AsyncTask任务。

例如,你可以把上一章中的例子用AsyncTask实现:

 

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public void onClick(View v) {    
  2.     new DownloadImageTask().execute("http://example.com/image.png");    
  3. }    
  4. private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {    
  5.     /** 系统把给予AsyncTask.execute() 的参数传给这个方法并且在后台线程中调用这个方法完成工作*/    
  6.     protected Bitmap doInBackground(String... urls) {    
  7.         return loadImageFromNetwork(urls[0]);    
  8.     }    
  9.         
  10.     /** 系统把doInBackground()的执行结果传送过来并且在界面线程中调用这个方法执行任务  */    
  11.     protected void onPostExecute(Bitmap result) {    
  12.         mImageView.setImageBitmap(result);    
  13.     }    
  14. }    


 

 

 

现在界面变得安全并且代码变得简单了。因为把工作分成了在工作线程中完成的部分和在界面线程中完成的部分。

你应该阅读AsyncTask的参考来完全理解如何使用这个类。这里是一个对它如何工作的概览:

你可以使用范型指定参数的类型,进度值,和任务的最终值。

方法doInBackground()自动在工作线程中执行

 

onPreExecute(),onPostExecute(), onProgressUpdate()都在界面线程中执行

doInBackground()返回的值被传递到onPostExecute()

 

你可以在任何时刻在doInBackground()中调用publishProgress()引起在界面线程中执行onProgressUpdate()你可以在任意时刻在任何线程中取消任务

 

警告:另一个你在使用工作线程时可能遇到的问题是activity的意外重启(比如屏幕的方向变了),这可能销毁你的工作线程。要了解如何才能在此类现象发生时避免你的任务被杀以及如何在activity死亡时正确的取消你的任务,请参阅Shelves例子的源码。

 

 

线程安全

 

在某些情况下,你实现的方法可能会在不同的线程中调用,所以它们必须被实现成“线程安全的”。

 

这首先体现在可以远程调用的方法上—比如在boundservice中的方法。当在一个进程中调用同一个进程中的IBinder上所实现的方法时,这个方法会在调用者的线程中运行(IBinder不理解的看这里:http://www.linuxidc.com/Linux/2012-02/53482.htm)。然而,当这个调用从另一个进程中发出时,方法会在线程池中某个被选出的线程中执行,这个线程在与IBinder相同的进程中(它不是运行于进程的界面线程中)。例如,尽管一个serviceonBind()方法在service所在进程的界面线程中调用,onBian()所返回的对象的方法(例如,一个子类所实现的那些RPC方法们)将在线程池的线程中调用。因为一个service可以拥有多个客户端,某个时刻可能一个或多个池中线程被用于执行一个Ibinder的同一个方法。IBinder的方法们因此必须被实现为线程安全的。

 

同样的,一个内容提供者也可能收到来自不同线程的数据请求。尽管ContentResolverContentProvider类隐藏了进程间通信的管理细节,ContentProvider的那些响应请求的方法们—query(),insert(), delete(), update(),getType()—会被内容提供者所在的进程中的线程池中的线程调用,而不是被进程的界面线程调用。因为这些方法们可能在同一时刻被任意数量的线程调用,所以它们必须被实现为线程安全的。

 

进程间通信

 

    Android通过远程调用(RPCs)提供了进程间通信(IPC)机制,于是一个方法可以被另一个应用的Activity调用,但是它却远程执行(在另一个进程中),而又把结果返回给调用者。这使得必须分解一个方法调用和它的数据到操作系统能理解的水平,把它们从本地进程和地址空间传到远程进程和地址空间,然后重组并重演调用过程。要返回的结果以相反的方向传送。Android提供了所有的执行这些IPC传输的代码,于是你可以专注于定义和实现RPC程序接口。

 

要执行IPC,你的应用必须绑定到一个service,使用方法bindService()

0 0
原创粉丝点击