Earth Guardian

You are not LATE!You are not EARLY!

0%

进程和线程

Android 应用启动时,系统会为应用新建一个 Linux 的进程,默认情况下同一应用程序下的所有组件运行在相同的进程和线程(主线程,也是 UI 线程)中。
一个Android 应用就是一个 Linux 进程,每个应用在各自的进程中运行,互不干扰比较安全。一个应用对应一个主线程,就是通常所说的 UI 线程,Android 遵守的就是单线程模型,所以说 UI 操作不是线程安全的。

进程

概念

虽然默认整个应用在同一进程中,但是可以在清单文件 AndroidManifest.xml 中定义组件是否以新建进程的方式运行。元素:<application>, <activity>, <service>, <receiver>, <provider> ,均支持 android:process 属性,默认进程名为包名,如果设置了 android:process=":remote" 属性,则进程名为:包名:remote
可以通过设置此属性,使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。 此外还可以设置此属性,使不同应用的组件在相同的进程中运行,但前提是这些应用共享相同的 Linux 用户 ID 并使用相同的证书进行签署。

注意:android:process=":remote",是否带分号是有区别的:

  • 有分号
    创建一个专属于当前进程的进程,他们的 PID 不同,但是父进程 PPID 是一样的。
  • 没有分号
    创建全局进程,不同的应用程序共享该进程。如果不带冒号,需要使用标准的命名规范命名进程名,例如 com.xxx.xxx.xxx,而且该进程是全局共享的进程,即不同应用的组件都可以运行于该进程,会创建两个完全独立的进程。这可以突破应用程序的 24M(或 16M )内存限制。

如果内存不足,而其他为用户提供更紧急服务的进程又需要内存时,Android 可能会决定在某一时刻关闭某一进程。在被终止进程中运行的应用组件也会随之销毁。当这些组件需要再次运行时,系统将为它们重启进程。

低内存进程管理

Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存。为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时系统会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。 进程优先级顺序为:

Foreground process:前台进程

用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程:

  • 托管用户正在交互的 Activity (已调用 ActivityonResume() 方法)
  • 托管某个 Service ,后者绑定到用户正在交互的 Activity
  • 托管正在“前台”运行的 Service (服务已调用 startForeground()
  • 托管正执行一个生命周期回调的 Service(onCreate() 、onStart()、onDestroy())
  • 托管正执行其 onReceive() 方法的 BroadcastReceiver

通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应.

Visible prcess:可见进程

没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。如果一个进程满足以下任一条件,即视为可见进程:

  • 托管不在前台、但仍对用户可见的 Activity (已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况
  • 托管绑定到可见(或前台)ActivityService

可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

Service process:服务进程

正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

Background process:后台进程

包含目前对用户不可见的 Activity 的进程(已调用 ActivityonStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。通常会有很多后台进程在运行,因此它们会保存在LRU(最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。

Empty process:空进程

不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

一个进程的优先级是可以变化的。

此外一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。例如,如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 B 同样重要。
由于运行服务的进程其级别高于托管后台 Activity 的进程,因此启动长时间运行操作的 Activity 最好为该操作启动服务,而不是简单地创建工作线程,当操作有可能比 Activity 更加持久时尤要如此。例如,正在将图片上传到网站的 Activity 应该启动服务来执行上传,这样一来,即使用户退出 Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论 Activity 发生什么情况,该操作至少具备“服务进程”优先级。同理,广播接收器也应使用服务,而不是简单地将耗时冗长的操作放入线程中。

线程

主线程( UI 线程)

应用启动时,系统会为应用创建一个名为“主线程”的执行线程。此线程非常重要,因为它负责将事件分派给相应的用户界面小部件,其中包括绘图事件。此外,它也是应用与 Android UI 工具包组件(来自 android.widgetandroid.view 软件包的组件)进行交互的线程。因此,主线程有时也称为UI 线程。响应系统回调的方法(例如,报告用户操作的 onKeyDown() 或生命周期回调方法)始终在进程的 UI 线程中运行。
此外 Android UI 工具包并非线程安全工具包,因此只能通过 UI 线程操纵用户界面。 因此 Android 的单线程模式必须遵守两条规则:

  • 不要阻塞 UI 线程
  • 不要在 UI 线程之外访问 Android UI 工具包

工作线程

根据单线程模式,要保证应用 UI 的响应能力,关键是不能阻塞 UI 线程。 如果执行的操作不能很快完成,则应确保它们在单独的线程(“后台”或“工作”线程)中运行。
Android 提供了几种途径来从其他线程访问 UI 线程:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)

为了避免代码变得复杂且难以维护,可以在工作线程中使用 Handler 处理来自 UI 线程的消息。

示例分析

  • PID 进程 ID
  • TID 线程 ID

可以通过 adb shell top 或者 adb shell ps 来查看应用的进程及线程 ID

以下两个例子分别是: ActivityService 属于同一进程或不同进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Service 设置新建进程
// Activity 进程和 UI 主线程都是 18585
07-31 09:51:28.002 18585-18585/? D/:XMT:ShowService:: onClick: button.onClick = Start Service
// 启动 Service ,新进程和线程都是 18688
07-31 09:51:28.015 18688-18688/? D/:XMT:StartService:: onCreate:
07-31 09:51:28.016 18688-18688/? D/:XMT:StartService:: onStartCommand: flags = 0, startId = 1
// Service 中新建线程 31645 执行耗时操作
07-31 09:51:28.020 18688-31645/? D/:XMT:StartService:: run:
07-31 09:51:31.020 18688-31645/? D/:XMT:StartService:: run: stopSelfResult.mStartId = 1
// Service 完成生命周期销毁(实际通过 top 命令查看并未销毁,下次启动 Service 还是 18688)
07-31 09:51:31.026 18688-18688/? D/:XMT:StartService:: onDestroy:
// 回到 Activity
07-31 09:51:32.854 18585-18585/? D/:XMT:ShowService:: onClick: button.onClick = Stop Service

// 默认 Service 和 Activity 等属于同一个进程和主线程 31813
07-31 09:52:29.439 31813-31813/? D/:XMT:ShowService:: onClick: button.onClick = Start Service
07-31 09:52:29.463 31813-31813/? D/:XMT:StartService:: onCreate:
07-31 09:52:29.464 31813-31813/? D/:XMT:StartService:: onStartCommand: flags = 0, startId = 1
// Service 中新建线程 31868 执行耗时操作
07-31 09:52:29.467 31813-31868/? D/:XMT:StartService:: run:
07-31 09:52:32.468 31813-31868/? D/:XMT:StartService:: run: stopSelfResult.mStartId = 1
07-31 09:52:32.471 31813-31813/? D/:XMT:StartService:: onDestroy:

区别和联系

  • 单位

线程进程的一个组成部分,一个进程可以包含一个或多个线程

  • 占用资源
    计算机内部的软硬件资源的分配与线程无关,进程是系统所有资源分配时候的一个基本单位,拥有一个完整的虚拟空间地址,线程是不会分配资源的。系统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享进程的资源。
  • CPU 调度

线程进程的一个实体,是 CPU 调度和分配的基本单位,即执行运算的最小单位。其本身不拥有系统资源,只含有程序计数器、寄存器和栈等一些运行时必不可少的基本资源。所以在多核系统中开多线程才能最大化利用 CPU

新开进程还是线程,如何选择?考虑的因素是什么?
根据上面的分析可以得出,如果是仅仅考虑后台处理,并发执行等可以只需要开线程就能满足;如果是需要考虑低内存管理进程优先级,应用能分配更多的源等可以新建进程。

参考文档

  1. https://developer.android.com/guide/components/processes-and-threads.html#Threads
  2. http://www.cnblogs.com/yw123/p/3614734.html
  3. http://blog.csdn.net/u011895534/article/details/46873479
  4. http://www.cnblogs.com/android-blogs/p/5632549.html
  5. http://blog.csdn.net/u014297278/article/details/46762407