http://www.cnblogs.com/xitang/archive/2011/09/24/2189460.html

Processes and Threads

译者署名: 呆呆大虾

译者微博: http://weibo.com/popapa

版本:Android 3.2 r1

原文

http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html

快速查看

·           默认情况下,每个应用程序运行在各自的进程中,应用程序中的所有组件也都运行在其中。

·           activity中所有运行缓慢的、阻塞的操作都应该运行在新建的线程中,以免减缓用户界面运行速度。

在本文中

进程

进程的生命周期

线程

工作线程

线程安全的方法

进程间通讯

 

进程和线程

如果某个应用程序组件是第一次被启动,且这时应用程序也没有其他组件在运行,则Android系统会为应用程序创建一个包含单个线程的linux进程。默认情况下,同一个应用程序的所有组件都运行在同一个进程和线程里(叫做“main”主线程)。如果组件启动时,已经存在应用程序的进程了(因为应用程序的其它组件已经在运行了),则此组件会在已有的进程和线程中启动运行。不过,可以指定组件运行在其他进程里,也可以为任何进程创建额外的线程。

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

进程

默认情况下,同一个应用程序内的所有组件都是运行在同一个进程中的,大部分应用程序也不会去改变它。不过,如果需要指定某个特定组件所属的进程,则可以利用manifest 文件来达到目的。

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

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

如果内存不足,可又有其它为用户提供更紧急服务的进程需要更多内存,Android可能会决定关闭一个进程。在此进程中运行着的应用程序组件也会因此被销毁。当需要再次工作时,会为这些组件重新创建一个进程。

在决定关闭哪个进程的时候,Android系统会权衡它们相对用户的重要程度。比如,相对于一个拥有可见activity的进程,更有可能去关闭一个activity已经在屏幕上看不见的进程。也就是说,是否终止一个进程,取决于运行在此进程中组件的状态。终止进程的判定规则将在后续内容中讨论。(注: 一个进程的关闭级别,按照该进程中最强的级别来定义。 如该进程中有activity和service,那么该进程的级别为service。)

进程的生命周期

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也是使用服务而非线程来处理耗时任务的。

(个人理解:启动一个service和在activity中启动一个线程是差不多的。为什么要在service中启动线程呢? 是因为service的关闭比较苛刻,这样才能保证持续运行。而activity是容易被关闭的。)

线程

应用程序启动时,系统会为它创建一个名为“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[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的参考文档。以下是关于其工作方式的概述:

·       可以用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开发指南。

Processes and Threads的更多相关文章

  1. Processes vs Threads

    A process is an executing instance of an application. What does that mean? Well, for example, when y ...

  2. Google Android官方文档进程与线程(Processes and Threads)翻译

    android的多线程在开发中已经有使用过了,想再系统地学习一下,找到了android的官方文档,介绍进程与线程的介绍,试着翻译一下. 原文地址:http://developer.android.co ...

  3. /usr/bin/uwsgi --http :8888 --wsgi-file wsgi.py --master --processes 4 --threads 2

    /usr/bin/uwsgi --http :8888 --wsgi-file wsgi.py --master --processes 4 --threads 2 root 18756 0.0 0. ...

  4. Android Processes and Threads

    Processes and Threads When an application component starts and the application does not have any oth ...

  5. Windbg Processes and Threads(进程和线程)窗口的使用

    在 WinDbg 中,进程和线程窗口中显示有关系统. 进程和线程正在调试的信息. 此窗口还可选择新的系统. 进程和线程处于活动状态. 如何打开进程和线程窗口 通过菜单View--->Proces ...

  6. Processes and Threads (转)

    http://www.cnblogs.com/xitang/archive/2011/09/24/2189460.html 原文 http://developer.android.com/guide/ ...

  7. [原]Threads vs Processes in Linux 分析

    Linux中thread (light-weighted process) 跟process在實作上幾乎一樣. 最大的差異來自於,thread 會分享 virtual memory address s ...

  8. 《深入理解Linux内核》阅读笔记 --- Chapter 3 Processes

    Process Switching 1.The set of data that must be loaded into the registers before the process resume ...

  9. Android 性能优化(16)线程优化:Creating a Manager for Multiple Threads 如何创建一个线程池管理类

    Creating a Manager for Multiple Threads 1.You should also read Processes and Threads The previous le ...

随机推荐

  1. OA系统出现窗口拦截的解决办法

    我们使用oa时候,有时候会出现“你打开了窗口拦截功能”.如图 出现窗口被拦截主要有三种情况,分别是IE浏览器本身拦截功能.第三方插件(如百度工具栏.搜搜工具栏.谷歌工具栏等).第三方浏览器拦截功能(如 ...

  2. j2ee网站项目首页如何直接使用action

    之前做过一些网站项目,大多数都是首页就是登录,直接进入首页的不多,也就没有注意到,今天刚好注意到了就来记一下.也算是一个小技巧 <welcome-file>index.jsp</we ...

  3. hdu_5707_Combine String("巴卡斯杯" 中国大学生程序设计竞赛 - 女生专场)

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=5707 题意:给你三个字符串 a,b,c,问你 c能否拆成a,b,a,b串的每一个字符在c中不能变 题解 ...

  4. spark第二篇--基本原理

    ==是什么 == 目标Scope(解决什么问题) 在大规模的特定数据集上的迭代运算或重复查询检索 官方定义 aMapReduce-like cluster computing framework de ...

  5. 第一天学习oc用xcode做的一个加减乘除 圆的面积计算

    #import <Foundation/Foundation.h>  //这是oc的框架 @interface jisuan : NSObject  //申明一个jisuan这样的类 并继 ...

  6. 【实验室笔记】太阳能板清洁器DEMO

    <太阳能板清洁器DEMO>2015年的毕昇杯比赛作品,用时两天,整体设计思路很简单: [机械结构]: 清洁器主体采用角钢搭建,用钢锯切割好以后,上螺丝,走线用的尼龙扎带捆绑: 清洗滚轮采用 ...

  7. SSH自动断开连接的原因

    方法一: 用putty/SecureCRT连续3分钟左右没有输入, 就自动断开, 然后必须重新登陆, 很麻烦. 在网上查了很多资料, 发现原因有多种, 环境变量TMOUT引起,ClientAliveC ...

  8. 微信支付WxpayAPI_php_v3(二)支付功能开发

    这里我没有开发openid和acessToken的获取,需要的请参考文档获取. 在阅读本教程之前请熟读微信支付的开发者文档. 直接开始[统一下单],在实际开发的项目中一般都有mvc分层的开发思想. 根 ...

  9. iperf linux版本移植到android (使用工具链方式不是使用Android.mk)

    由于很多程序是用makefile编译linux应用程序的,如果移植到android就要重新写Android.mk,对于不熟悉这个的人来说,特别麻烦,所以这里介绍只修改makefile就能移植到andr ...

  10. Drupal7的theme函数执行顺序

    theme('name') 执行顺序: 1.当前主题_name(),这个函数一般在主题的template.php文件中 2.所在模块_name() 3.theme_name() 4.name.tpl. ...