android 应用中进程和线程是如何运行的

来源:互联网 发布:mac怎么关闭访客用户 编辑:程序博客网 时间:2024/04/25 13:30

       

翻译于:http://developer.android.com/guide/components/processes-and-threads.html#Processes

          当一个android应用组件启动时,若该应用此时无其他组件在运行状态,则新起一个进程,而该新启动组件将运行与该进程的主线程中MainThread。组件包括activity,service,receiver和provider。通常情况下,一个应用程序的所有组件都运行于同一个进程的主线程中。而一个android应用组件启动时,该应用已经存在一个进程(即有其他组件在该进程中运行),则新启动组件将被安排在已存在的进程中执行。当然我们也可以为应用程序的组件分配不同的进程。


一、Process

         默认情况下,同一个应用中的所有组件都运行在同一个进程中。除非必要情况下,不要违反该原则。如果你想特定的组件运行与某个特定进程中时,可以修改应用的Manifest文件来实现。

        android四大组件的Manifest entry(—<activity>,<service>,<receiver>, 和 <provider>)都支持android:process属性,设置该属性可以让每个组件都运行于不同进程,也可以让几个组件共享同一个进程。当然也可以设置该属性来让不同应用程序的组件运行于同一个进程中,但这些应用程序必须共享同一个Linux user ID(例如系统Settings应用android:sharedUserId="android.uid.system")和相同的签名(certificates).

         <application>元素也支持android:process属性,用来设置该应用中所有组件的默认进程。

         当android设备内存较低、且其他更重要的应用需要内存时,系统会决定是否杀死某些进程以释放内存。当进程被杀死,运行于其上的所有组件也将被销毁。

         Android系统衡量每个进程对用户的重要性,从而觉得杀死哪一个进程。例如,系统更容易杀死那些没有可见Activity的进程,相对于有可见Activity的进程来说。一个进程中的组件运行状态,决定了该进程是否被杀死的权重。

1.1进程生命周期--Process Lifecycle

        Android系统尝试尽可能长的保持一个应用进程但最终需要移除旧的进程来回收内存给新的或更重要的进程使用为了决定哪些进程需要保留、哪些需要kill,android系统根据进程中运行组件及其状态来配置一个进程的重要性等级。系统安装重要性等级从低到高,依次清除进程,直到系统必要资源恢复(主要是内存)。

        下面由高到低列出android进程的5个重要性等级。

------前台进程(Foreground process)

        正在和用户交互的进程。满足如下条件之一的,就会被设置为前台进程。

(1)进程的一个Activity组件正在和用户做交互,即这个Activity的onResume() 方法被执行后,onPause()方法执行前;

(2)进程的一个service组件绑定到了正在进行用户交互的Activity上时;

(3)进程的一个service组件运行在前台,即该service执行了startForeground()方法将其绑定到系统可见通知上;

(4)进程的的一个service组件正在执行其生命周期回调,包括onCreate(),onStart(), oronDestroy()  ;

(5)进程的广播组件Receiver正在执行其onReceive()回调方法;

        通常,在任何给定时间只会有个别几个前台进程。只有当系统内存低到前台进程都无法保持运行时,作为最后手段,才有可能被杀死一些前台进程,以保证用户交互界面的正常响应。

------可见进程(Visible process)

        进程中不包括任何前台组件,但依然对用户可见。满足以下条件之一的,就会被设置为可见进程。

(1)进程的一个Activity组件不在前台,但依旧可见。此时该Activity的onPause()方法刚被执行。常见的例子:前台Activity启动了一个Dialog,此时该Activity在Dialog后面,但依旧可见。

(2)进程的一个service组件绑定到了可见Activity上时;

        可见进程在android系统中被认为是非常重要的,他们不会被杀死,除非必须杀死他们来保证前台进程的运行。

------服务进程(Service process)

        进程中有一个正在运行的service(可通过startService()启动service),且尚未达到可见进程和前台进程条件,都属于服务进程。

        虽然服务进程与屏幕显示的不直接相关,但其也在处理一些用户关心的重要事情,比如后台下载音乐、下载网络数据。所以android系统只有在内存低到无法保证前台进程和可见进程正常运行情况下,才会杀死服务进程。

------后台进程(Background process)

       进程中的一个Activity当前对用户不可见,(刚执行过onStop())。这些进程对于用户体验没有任何影响,系统可以在任何时候杀死他们释放内存提供高优先级进程使用。通常,有很多后台进程在运行。他们被保存在LRU(least recently used)  List中,以保证用户最近看到的Activity所在进程最后被杀死。如果一个Activity正确地实现其生命周期方法、并保存了他的当前状态,杀死他对于用户体验没有任何可见的影响。因为当用户通过导航栏back键再次进入该Activity时,其重载了他的所有可见状态。如何保存和重载Activity的可见状态,参见Activity开发文档。

------空进程(Empty process)

        没有任何活动组件的进程---空进程。保持这类进程存活的唯一目的是缓存进程以提升下次android组件需要在该进程中运行的启动时间。为了在进程缓存和底层的kernel缓存间平衡总体的系统资源,系统时常需要杀死该类进程。

  


       基于当前活动组件的重要性,android系统会尽量给一个进程分配(它能获得的)最高的等级。例如,一个进程当前持有一个service和一个可见Activity,此时该进程将会被分配为可见进程吗,而非服务进程。

       此外,若一个进程被另外一个进程所依赖,则被依赖的进程等级会被提升。即一个进程服务于另一个进程,服务进程的等级必然要不低于被服务进程。例如,进程A中有一个content provider组件,并对进程B中的Client提供服务;或者A进程中的Service被绑定到进程B中的一个组件上;此时Android系统认为A进程至少和B进程同等重要。

       由于拥有Service活动组件的进程重要等级要高于拥有后台Activity的进程,如果一个Activity初始化时执行长时间的耗时操作(a long-running operation),最好启动一个Service,并把该耗时初始化操作放在Service中执行,而不是在Activity中简单地启动一个工作线程来执行耗时操作,特别是在该耗时操作可能拖垮Activity情况下。例如,一个Activity中需要上传图片到一个网站时,应该启动一个service用于执行该上传操作,以保证用户在离开该Activity页面时图片也能正常在后台上传。此时不论界面Activity发生任何操作,使用Service都能至少保证该操作拥有“服务进程”优先级。这也是BroadcastReceiver中尽量启动service而不是使用工作线程来处理耗时操作的原因。


二、线程Thread

       当一个应用程序被启动时,Android系统会创建一个线程用于执行该应用程序---主线程MainThread。这个线程非常重要,因为它负责给相应的用户界面部件分发事件(Event),其中包括界面绘制事件(drawing events)。你的应用程序同样需要在这个线程中与Android UI部件(所有来自android.widgetandroid.view包下的部件)进行交互,因此主线程也通常被称为UI线程。

       Android系统并没有为每一个组件实例创建单独的线程,同一进程的所有组件都是在该UI线程中被实例化,并且针对每个组件的系统调用/回调也都是在这个UI线程中分发。因此,类似onKeyDown()的系统回调或生命周期回调也都是运行在该UI线程中的。

       例如,当用户触摸屏幕上的一个按钮时,应用程序的主线程分发该触摸事件到对应的窗口。然后该按钮设置它的Pressed状态、并发出一个invalidate请求到事件队列(event queue)。UI线程取出请求通知部件应该重绘自己

       当应用程序需要密集操作来响应用户交互时,但线程模型将会性能很差,除非你特别恰当地实现你的应用。特别是如果所有事情都在主线程中处理,比如耗时操作:网络访问、数据库查询,将会阻塞你的整个主线程。当线程被阻塞,包括绘画在内的所有事件都无法分发。在用户角度的表现为假死。更糟糕时,若UI线程被阻塞超过几秒(Activity:5s,service:15s,BroadcastReceiver:10s),用户就会看到“应用程序未响应”的ANR提示框。此时用户就会选择退出你的应用,更不爽的话甚至卸载你的应用。

       此外,Android提供的UI工具包都不是线程安全的。即,你只能在UI线程中操纵UI,而不能再工作线程。因此,Android的单线程模型需要开发遵循如下两个规则:

  1. 不能阻塞你的UI线程;
  2. 不可以再工作线程访问Android UI工具包;

------工作线程

       如上所述,对于Android应用开发,保证应用UI线程的正常响应是至关重要的。如果需要执行的操作不是瞬时的,你需要保证它们在单独的后台/工作线程中执行。

       示例,如下代码是ImageView的onClick监听器中启动线程来执行网络下载bitmap病显示到该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();}

起初这似乎工作得很好因为它创建一个新线程处理网络操作。然而,它违反了单线程模型(single-threaded model)的第二条原则:不可以在非UI线程访问Android UI工具包,该示例在工作线程修改ImageView。这将会导致未定义/意外的结果,并且当异常发生后,定位将会很困难、耗时。

       为了解决这个问题,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();}

现在,代码实现终于是线程安全的了:在工作线程中处理网络操作,并在UI线程中操纵ImageView

        然而,随着操作复杂性的增加,这类代码维护起来将会很复杂、困难。为了解决UI线程与工作线程的复杂交互,你和可能想到使用Handler。但最好的解决方案,却是使用AsyncTask,它成功简化了需要与UI线程交互的工作线程执行。

----Using AsyncTask

       AsyncTask允许你在用户界面执行异步操作。它在工作线程中执行耗时操作,并将执行结果在UI线程中发布。很好的封装,不需要你自己处理线程、Handler相关。

       要使用它,你需要继承AsyncTask类,并实现其doInBackground() 回调(该回调在工作线程池中执行);而为了更新UI,你需要实现onPostExecute()回调(该回调接收来自doInBackground的处理结果、并在UI线程中执行操作),在该回调中你可以放心地更新UI。你可以再UI线程中执行AsyncTaskexecute()方法来启动它。

       例如,可以使用AsyncTask方式实现上面的示例。、

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);    }}

现在,线程彻底安全了。如要完全了解AsyncTask,你需要阅读其说明文档,如下可以让你快速了解AsyncTask是如何工作的。

  • 你可以自定义参数类型、工作进度值和task的最终结果;
  • 回调 doInBackground() 将会自动在工作线程中执行;
  • onPreExecute(),onPostExecute(),和onProgressUpdate() 都将在UI线程中执行;
  • 回调 doInBackground() 的返回值将被发送给onPostExecute();
  • doInBackground() 中你可以任意时刻执行publishProgress()以触发UI线程中的onProgressUpdate() 回调;
  • 你可以再任意时刻、任意线程中取消该Task任务;

注意:在使用工作线程时你很可能遇到的问题:Android运行时配置发生改变时Activity会异常重启(例如屏幕横竖屏切换),它会销毁你的工作线程。


-----线程安全方法
      某些情况下,你实现的方法可能会在多个线程中被访问,因此它需要被写成线程安全的。当方法被远程调用时(例如绑定的Service方法),该描述基本正确。

      当执行IBinder中实现的方法时,若调用源于IBinder属于同一个进程时,该方法将会在caller所在线程中执行;而若调用源于IBinder不属于同一个进程时,IBinder所属于

进程将会在其维护的线程池中选择一个线程来执行该方法,但不会是其UI线程;



0 0
原创粉丝点击