API Guides/App Components/Process and Thread翻译

原文链接: Processes and Threads

进程和线程

当一个应用程序没有任何组件运行时,启动该应用的一个组件,Android 系统就会为该应用启动一个新的Linux进程,执行一个线程应用。默认,同一个应用的所有组建都运行在同一个进程和线程(该线程被称为“主线程”)里面。假如启动一个组件,该应用已经存在一个进程(可能是该应用的其他组件所启动的进程),那么该组件就会在该进程里面运行,使用同样的线程应用。但是,你可以在应用中为不同的组件安排不同的进程,你也可以为任何进程创建额外的线程。

该文档将会讨论Android内进程和线程的工作原理。

进程


默认,同一个应用的所有组件都运行在同一个进程里面,很多应用不能修改该实现。但是,如果你想控制或者改变某个组件的进程,可以在AndroidManifest.xml文件里面修改。

在AndroidManifest.xml文件里面,对于每一个组建---- <activity> <service> <receiver> , 以及  <provider>  都提供了 android:process 属性,该属性指定了该组件所运行的进程。
你也可以设置该属性使得该组件运行在自己的进程中,或者,一些组件运行在同一个进程中,另一些却不。你也可以通过设置该属性,使同一个进程内运行不同的应用组件,即应用程序共享相同的Linux用户ID和相同的数字证书签证。

<application>元素也支持 android:process 属性,通过设置默认值,适用与所有应用的组件。

当内存不足,系统需要运行其他进程时,Android可能会杀死某个进程。运行在该进程的所有组件都会被销毁。当重新运行这些组件时,进程会被重新开启。

Android系统根据进程对于用户的重要性来决定该杀死哪个进程。比如,相对于那些包含可见activities的进程来说,系统会更容易杀死那些不可见的进程。是否杀死某个进程,依赖于该进程中组件的状态。该杀死哪个进程的规则在后续会讨论。

进程生命周期


Android系统会尽可能长时间保持某个应用进程,但是,也会通过删除旧的进程回收内存用于新的或者更重要的进程。要决定到底那个进程保持还是被杀死,系统会将每个进程放入到一个“重要性层次列表”里面,这个列表是基于该进程中所运行的组件以及这些组件的状态。那些带有最低性能标志的进程首先会被淘汰掉,然后淘汰倒数第二个,一次类推,回复所需的系统资源。

有五个级别的重要性层次,下面列表里面列出了不同类型进程的重要性顺序(第一个类型最终要,最后被杀死):

1. 前端进程


改类型进程代表用户正在操作。假如某个进程符合以下任何条件中的一个,便认为是前端进程:
 (1)它承载一个用户正在交互的Activity.
   (2)  它承载一个 Service,该Service绑定用户正在交互的Activity.
   (3)  它承载一个 Service, 该Service正在运行在前方------该服务被调用 startForeground() .
   (4)  它承载一个 Service,该Service执行其生命周期的回调( onCreate() onStart() , or  onDestroy())。
 (5) 它承载了一个 BroadcastReceiver ,该 执行了它的   onReceive()  方法。

一般情况下,只有一小部分前端进程存在与任何时候,如果内存很低,设备不能继续运行,他们会被最后杀死。通常,这时,设备达到一个内存页面的状态,为了保持用户界面的响应,杀死那些前端进程是必须的。

2. 可视进程


改进程不包含任何前端控件,但是依然存在与屏幕上面。若符合一下任一条件,则被认为属于可视进程:
(1) 它承载了一个 Activity  , 该活动不属于前端控件,但是一直可被用户可视(它的 onPause()  方法被调用)。比如,如果前端activity开启一个dialog,它运行前端activity在它背后可视。
(2) 它承载一个 Service   ,该服务绑定一个可视的(或前端)的activity.

可视进程非常重要,一般不会被杀死,除非要所有的前端进程都运行。

3. 服务进程


该进程运行一个服务,该服务使用   startService()  方法被启动,该进程也不属于上述两个级别更高的类型。该服务进程也不直接绑定用户所能看到的视图。它只做那些用户关心的事情(比如说,后台播放音乐,或者网络下载数据)。系统将会一直保持它的运行,除非没有足够的内存来保存所有前台或可视进程。

4. 后台进程


该进程承载一个当前不被可视的activity(这个activity的 onStop() 方法被调用)。这些进程对用户体验没有直接影响,系统可在任何时候杀死它们,为上述三种类型的进程回收内存。通常,有许多后台进程在运行,它们被保存到LRU(最近最少使用)列表里面,以确保用户最近所看到的进程被最后杀死。如果一个Activity 正确的实现了它们的生周周期方法,并保存它的当前状态,在杀死进程时并不会影响用户体验,因为,大概你用户返回到该Activity时候,它会回复所有保存的可视数据。有关保存和回复状态信息,请参照   Activities文档。


5. 空进程


该进程不包含任何活动的控件,唯一保持该控件活动的原因就是缓存,用来提高下一次控件开启时间。系统经常会杀死这些进程,以平衡系统资源在进程缓存和底层内核缓存之间的平衡。

Android系统根据进程中所有控件的最高级别的给进程分类,比如,一个进程承载一个服务和一个可视的activity,这个进程就会被看作可视进程。而不是服务进程。

另外,一个进程的级别可能会上升,若有其他进程依赖它。如果一个进程服务与另一个进程,那么它不会比另一个进程的级别低。比如,在进程A中,一个content provide正在服务一个在进程B里面的客户端,或者在进程A里面的一个服务,被绑定到进程B里面的一个控件,那么进程A至少和B一样的级别。

因此一个进程运行一个服务的级别高于一个进程运行一个后台activites,一个activity若初始化为一个长时间操作,可以开启一个 service  代替,而不是简单的创建一个工作线程------特别是这个操作会超出这个activity的生命周期。比如,一个activity上传一副图片到web site,应该开启一个服务来操作上传,即使用户离开了该activity,该操作也可以在后台运行。使用服务保证了该操作至少拥有"服务进程"的优先级,不必在乎activity的运行情况。同样原因,broadcase receivers应该采用服务机制,而不是简单的在一个线程里面做耗时的操作。


线程


当一个应用被启动,系统会为它创建一个执行线程,叫做“主线程”。该线程非常重要,因为它主要负责调度用户事件到相应的交互的组件里面,包括重绘事件。该线程包括应用所交互的控件,这些控件出自Android UI工具包(包括,   android.widgetandroid.view包)。因此,主线程也被称为“UI线程”。

系统不会为每个组建的实例创建单独的线程。所有组件都运行在同一个进程内,并被实例化。系统都是从该线程调用每个组件。因此,那些与系统回调相关连的方法(如,   onKeyDown()显示用户的操作,或者一个生命在后期的回调方法)都运行在该线程的UI进程里面。

例如,当用户触摸屏幕上的一个按钮,你的应用UI进程将该触摸事件调度到对应的控件上面,该控件反过来设置它的按下状态,将一个无效请求发送到事件队列里面。UI线程从队列里面取出请求,并通知该控件重绘。

当你的应用运行量大的任务来响应用户交互时,这种单一的线程模式的性能很差,除非你能很准确的实现你的应用。具体来说,如果在UI线程里面,那些执行时间长的动作,比如链接网络,查询数据库,等都会阻塞整个UI。当线程被阻塞,没有事件可以被调度,包括重绘。从用户的角度来讲,应用被挂起。更糟的是,假如UI进程被阻塞超过几秒种(目前约5秒),用户会看到 application not responding“应用程序无相应”(ANR)对话框。用户可能会退出你的程序,或者直接下载。

此外,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();
}

乍看,它看起来没有问题,因为它创建了一个新的线程来处理网络操作。但是,他违反了上面的第二条规则:不能在UI线程以外执行UI工具包。这个例子在工作线程而不是UI线程修改了 ImageView.这种情况能会产生不确定或者意外的行为,很难跟踪排查。

为了修复该问题,Android提供了好几种方式从其他线程访问UI线程。下面是这些方法的列表:


比如,你可以使用 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> {
    /** 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);
    }
}


现在UI线程是安全的,代码简单,因为它将任务分成两部分,一部分在工作线程内执行,另一部分在UI线程里面执行。

你应该阅读 AsyncTask文档,对该类有个全面的了解,下面是它工作原理的简单概述:

  (1)你必须使用泛型指定参数的类型, 进度值,最后结果。
  (2) doInBackground()方法在一个工作进程中自动执行。
  (3) onPreExecute() onPostExecute() , 和  onProgressUpdate()方法都在UI线程里面调用。
  (4) doInBackground() 返回值被送到  onPostExecute()
  (5)你可以随时在 publishProgress()方法里面调用 doInBackground()  来执行在UI 线程里调用的   onProgressUpdate()
  (6) 你可以随时在任意的线程里面取消该任务。
 
注意:使用工作进程时,可能会遇到另外一个问题,因为运行的配置,可能会重启你的activity(比如,当用户改变屏幕的方向时),它可能会销毁掉你的线程。 为了搞清楚怎么让你的任务在重启中不被销毁, 以及在activity被销毁时,如何正确取消任务,请参考 例子 Shelves源码。

AsyncTask 的讲解延伸阅读: 点击打开链接


线程安全


在很多情况下,你实现的方法会被多个线程调用,因此,线程必须安全。

在远程调用方法中,这点显得有为重要,比如在一个   bound service 里面的方法。一个方法被调用,该方法在IBinder里面被实现,该方法和IBinder运行在同一个线程里面。该方法在被调用的线程里面被执行。然而,当调用者在另一个线程里面运行,该方法也会在一个线程里面被执行。比如,一个服务进程的 onBind()  方法将会被该服务进程的UI所调用,被实现的对象里面的 onBind()  返回值将会被线程池里面的线程所调用。因为一个服务可能会有多个客户端,多个线程池会在同一个时间会运行同一个 IBinder方法。 IBinder方法必须是线性安全的。

简单的说,一个 content provider 能够接受多个线程的数据请求。但是, ContentResolver  和  ContentProvider    类都隐藏了线程交换间通信管理细节, ContentProvider  提供的与这些请求相关连的方法-----   query() insert() delete() , update() , 和  getType()都会被线程池所调用,在content provider 的进程里面,而不是UI线程里面。因为i这些方法可能被很多线程在同一时间调用,因此必须线程安全。


进程间通信


Android 通过远程调用(RPCs)实现了一种进程间通行机制。在这里面,某个方法被一个Activity或者某个应用所调用,但是在远程执行(在另一个进程里面),将结果返回给调用者。该种机制将方法调用和它的数据分解为系统可理解的水平,将它从本地进程以及地址空间传送到远程进程以及地址空间,然后重新组织,重新呼叫。将结果按相反方向返回。Android提供了所有操作IPC事务的代码,用户只需定义以及事项RPC接口即可。

为了演示IPC,你的应用必须使用 bindService()绑定一个服务。 为了获取更多信息,请参照 Services开发文档。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值