Android 进程和线程

这篇文章转载自:http://www.oschina.net/question/195301_32205html

进程和线程java

若是某个应用程序组件是第一次被启动,且这时应用程序也没有其余组件在运行,则Android系统会为应用程序建立一个包含单个线程的linux进程。默认状况下,同一个应用程序的全部组件都运行在同一个进程和线程里(叫作“main”主线程)。若是组件启动时,已经存在应用程序的进程了(由于应用程序的其它组件已经在运行了),则此组件会在已有的进程和线程中启动运行。不过,能够指定组件运行在其余进程里,也能够为任何进程建立额外的线程。linux

         本文讨论进程和线程是如何在Android应用程序中发挥做用的。android

 

进程数据库

默认状况下,同一个应用程序内的全部组件都是运行在同一个进程中的,大部分应用程序也不会去改变它。不过,若是须要指定某个特定组件所属的进程,则能够利用manifest 文件来达到目的。编程

manifest文件中的每种组件元素——<activity>、 <service>、 <receiver><provider>——都支持定义android:process属性,用于指定组件运行的进程。设置此属性便可实现每一个组件在各自的进程中运行,或者某几个组件共享一个进程而其它组件运行于独立的进程。设置此属性也可让不一样应用程序的组件运行在同一个进程中——实现多个应用程序共享同一个Linux用户ID、赋予一样的权限。缓存

<application>元素也支持android:process属性,用于指定全部组件的默认进程。安全

若是内存不足,可又有其它为用户提供更紧急服务的进程须要更多内存,Android可能会决定关闭一个进程。在此进程中运行着的应用程序组件也会所以被销毁。当须要再次工做时,会为这些组件从新建立一个进程。网络

在决定关闭哪一个进程的时候,Android系统会权衡它们相对用户的重要程度。好比,相对于一个拥有可见activity的进程,更有可能去关闭一个activity已经在屏幕上看不见的进程。也就是说,是否终止一个进程,取决于运行在此进程中组件的状态。终止进程的断定规则将在后续内容中讨论。多线程

 

进程的生命周期

Android系统试图尽量长时间地保持应用程序进程,但为了新建或者运行更加剧要的进程,老是须要清除过期进程来回收内存。为了决定保留或终止哪一个进程,根据进程内运行的组件及这些组件的状态,系统把每一个进程都划入一个“重要性层次结构”中。重要性最低的进程首先会被清除,而后是下一个最低的,依此类推,这都是回收系统资源所必需的。

重要性层次结构共有5级,如下列表按照重要程度列出了各种进程(第一类进程是最重要的,将最后一个被终止):

1. 前台进程

用户当前操做所必须的进程。知足如下任一条件时,进程被视做处于前台:

o     其中运行着正与用户交互的Activity(Activity对象的 onResume() 方法已被调用)。

o     其中运行着被正与用户交互的activity绑定的服务Service

o     其中运行着“前台”服务Service——服务以startForeground()方式被调用。

o     其中运行着正在执行生命周期回调方法(onCreate()onStart()onDestroy())的服务Service

o     其中运行着正在执行onReceive()方法的BroadcastReceiver

通常而言,任什么时候刻前台进程都是为数很少的,只有做为最后的策略——当内存不足以维持它们同时运行时——才会被终止。一般,设备这时候已经到了内存分页状态(memory paging state)的地步,终止一些前台进程是为了保证用户界面的及时响应。

 

2. 可见进程

没有前台组件、但仍会影响用户在屏幕上所见内容的进程。知足如下任一条件时,进程被认为是可见的:

o     其中运行着不在前台的Activity,但用户仍然可见到此activity(onPause()方法被调用了)。好比如下场合就可能发生这种状况:前台activity打开了一个对话框,而以前的activity还容许显示在后面。

o     其中运行着被可见(或前台)activity绑定的服务Service

可见进程被认为是很是重要的进程,除非没法维持全部前台进程同时运行了,它们是不会被终止的。

 

3. 服务进程

此进程运行着由startService()方法启动的服务,它不会升级为上述两级别。尽管服务进程不直接和用户所见内容关联,但他们一般在执行一些用户关心的操做(好比在后台播放音乐或从网络下载数据)。所以,除非内存不足以维持全部前台、可见进程同时运行,系统会保持服务进程的运行。

 

4. 后台进程

包含目前用户不可见activity(Activity对象的onStop()方法已被调用)的进程。这些进程对用户体验没有直接的影响,系统可能在任意时间终止它们,以回收内存供前台进程、可见进程及服务进程使用。一般会有不少后台进程在运行,因此它们被保存在一个LRU(最近最少使用)列表中,以确保最近被用户使用的activity最后一个被终止。若是一个activity正确实现了生命周期方法,并保存了当前的状态,则终止此类进程不会对用户体验产生可见的影响。由于在用户返回时,activity会恢复全部可见的状态。关于保存和恢复状态的详细信息,请参阅Activities文档。

 

5. 空进程

不含任何活动应用程序组件的进程。保留这种进程的惟一目的就是用做缓存,以改善下次在此进程中运行组件的启动时间。为了在进程缓存和内核缓存间平衡系统总体资源,系统常常会终止这种进程。

 

依据进程中目前活跃组件的重要程度,Android会给进程评估一个尽量高的级别。例如:若是一个进程中运行着一个服务和一个用户可见的activity,则此进程会被评定为可见进程,而不是服务进程。

此外,一个进程的级别可能会因为其它进程的依赖而被提升——为其它进程提供服务的进程级别永远不会低于使用此服务的进程。好比:若是A进程中的content provider为进程B中的客户端提供服务,或进程A中的服务被进程B中的组件所调用,则A进程至少被视为与进程B一样重要。

由于运行服务的进程级别是高于后台activity进程的,因此,若是activity须要启动一个长时间运行的操做,则为其启动一个服务service会比简单地建立一个工做线程更好些——尤为是在此操做时间比activity自己存在时间还要长久的状况下。好比,一个activity要把图片上传至Web网站,就应该建立一个服务来执行之,即便用户离开了此activity,上传仍是会在后台继续运行。不论activity发生什么状况,使用服务能够保证操做至少拥有“服务进程”的优先级。同理,上一篇中的广播接收器broadcast receiver也是使用服务而非线程来处理耗时任务的。

 

 

线程

应用程序启动时,系统会为它建立一个名为“main”的主线程。主线程很是重要,由于它负责把事件分发给相应的用户界面widget——包括屏幕绘图事件。它也是应用程序与Android UI组件包(来自android.widgetandroid.view包)进行交互的线程。所以,主线程有时也被叫作UI线程。

系统并不会为每一个组件的实例都建立单独的线程。运行于同一个进程中的全部组件都是在UI线程中实例化的,对每一个组件的系统调用也都是由UI线程分发的。所以,对系统回调进行响应的方法(好比报告用户操做的onKeyDown()或生命周期回调方法)老是运行在UI线程中。

举个例子,当用户触摸屏幕上的按钮时,应用程序的UI线程把触摸事件分发给widget,widget先把本身置为按下状态,再发送一个显示区域已失效(invalidate)的请求到事件队列中。UI线程从队列中取出此请求,并通知widget重绘本身。

若是应用程序在与用户交互的同时须要执行繁重的任务,单线程模式可能会致使运行性能很低下,除非应用程序的执行时机恰好很合适。若是UI线程须要处理每一件事情,那些耗时很长的操做——诸如访问网络或查询数据库等——将会阻塞整个UI(线程)。一旦线程被阻塞,全部事件都不能被分发,包括屏幕绘图事件。从用户的角度看来,应用程序看上去像是挂起了。更糟糕的是,若是UI线程被阻塞超过必定时间(目前大约是5秒钟),用户就会被提示那个可恶的“应用程序没有响应(ANR)对话框。若是引发用户不满,他可能就会决定退出并删除这个应用程序。

此外,Andoid的UI组件包并不是线程安全的。所以不容许从工做线程中操做UI——只能从UI线程中操做用户界面。因而,Andoid的单线程模式必须遵照两个规则:

1.         不要阻塞UI线程。

2.         不要在UI线程以外访问Andoid的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(); 

}

乍看起来,这段代码彷佛能运行得很好,由于建立了一个新的线程来处理访问网络的操做。但是它违反了单线程模式的第二条规则:不要在UI线程以外访问Andoid的UI组件包——以上例子在工做线程里而不是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(); 

}

以上代码的执行如今是线程安全的了:网络相关的操做在单独的线程里完成,而ImageView是在UI线程里操纵的。

不过,随着操做变得愈来愈复杂,这类代码也会变得很复杂很难维护。为了用工做线程完成更加复杂的交互处理,能够考虑在工做线程中用Handler来处理UI线程分发过来的消息。固然,最好的解决方案也许就是继承使用异步任务类AsyncTask,此类简化了一些工做线程和UI交互的操做。

 

使用异步任务

异步任务AsyncTask 容许以异步的方式对用户界面进行操做。它先阻塞工做线程,再在UI线程中呈现结果,在此过程当中不须要对线程和handler进行人工干预。

要使用异步任务,必须继承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> { 

    /** 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[]); 

    } 

     

    /** 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); 

    } 

}

如今UI是安全的,代码也获得简化,由于任务分解成了工做线程内完成的部分和UI线程内完成的部分。

要全面理解这个类的使用,须阅读AsyncTask的参考文档。如下是关于其工做方式的概述:

·       能够用generics来指定参数、进度值和任务最终值的类型。

·       工做线程中的doInBackground()方法会自动执行。

·       onPreExecute()onPostExecute()onProgressUpdate()方法都在UI线程中调用。

·       doInBackground()的返回值会传给onPostExecute()

·       在doInBackground()内的任什么时候刻,均可以调用publishProgress()来执行UI线程中的onProgressUpdate()

·       能够在任什么时候刻、任何线程内取消任务。

注意:在使用工做线程时,可能遇到的另外一个问题是因为运行配置的改变(好比用户改变了屏幕方向)致使activity意外重启,这可能会销毁该工做线程。要了解如何在这种状况下维持任务执行、以及如何在activity被销毁时正确地取消任务,请参见Shelves例程的源代码。

 

线程安全的方法

在某些场合,方法可能会从不止一个线程中被调用,所以这些方法必须是写成线程安全的。

对于能被远程调用的方法——好比绑定服务(bound service)中的方法,这是理所固然的。若是对IBinder所实现方法的调用发起于IBinder所在进程的内部,那么这个方法是执行在调用者的线程中的。可是,若是调用发起于其余进程,那么这个方法将运行于线程池中选出的某个线程中(而不是运行于进程的UI线程中),该线程池由系统维护且位于IBinder所在的进程中。例如,即便一个服务的onBind()方法是从服务所在进程的UI线程中调用的,实现了onBind()的方法对象(好比,实现了RPC方法的一个子类)仍会从线程池中的线程被调用。由于一个服务能够有不止一个客户端,因此同时能够有多个线程池与同一个IBinder方法相关联。所以IBinder方法必须实现为线程安全的。

相似地,内容提供者(content provider)也能接收来自其它进程的数据请求。尽管ContentResolver类、ContentProvider类隐藏了进程间通信管理的细节,ContentProvider中响应请求的方法——query()insert()delete()update()getType()方法——是从ContentProvider所在进程的线程池中调用的,而不是进程的UI线程。由于这些方法可能会从不少线程同时调用,它们也必须实现为线程安全的。

 

 

进程间通信

Android利用远程过程调用(remote procedure call,RPC)提供了一种进程间通讯(IPC)机制,经过这种机制,被activity或其余应用程序组件调用的方法将(在其余进程中)被远程执行,而全部的结果将被返回给调用者。这就要求把方法调用及其数据分解到操做系统能够理解的程度,并将其从本地的进程和地址空间传输至远程的进程和地址空间,而后在远程进程中从新组装并执行这个调用。执行后的返回值将被反向传输回来。Android提供了执行IPC事务所需的所有代码,所以只要把注意力放在定义和实现RPC编程接口上便可。

要执行IPC,应用程序必须用bindService()绑定到服务上。详情请参阅服务Services开发指南。

相关文章
相关标签/搜索