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
(已调用Activity
的onResume()
方法) - 托管某个
Service
,后者绑定到用户正在交互的Activity
- 托管正在“前台”运行的
Service
(服务已调用startForeground()
) - 托管正执行一个生命周期回调的
Service(onCreate() 、onStart()、onDestroy())
- 托管正执行其
onReceive()
方法的BroadcastReceiver
通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应.
Visible prcess
:可见进程
没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。如果一个进程满足以下任一条件,即视为可见进程:
- 托管不在前台、但仍对用户可见的
Activity
(已调用其onPause()
方法)。例如,如果前台Activity
启动了一个对话框,允许在其后显示上一Activity
,则有可能会发生这种情况 - 托管绑定到可见(或前台)
Activity
的Service
可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。
Service process
:服务进程
正在运行已使用 startService()
方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。
Background process
:后台进程
包含目前对用户不可见的 Activity
的进程(已调用 Activity
的 onStop()
方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。通常会有很多后台进程在运行,因此它们会保存在LRU
(最近最少使用)列表中,以确保包含用户最近查看的 Activity
的进程最后一个被终止。如果某个 Activity
正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity
时,Activity
会恢复其所有可见状态。
Empty process
:空进程
不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
一个进程的优先级是可以变化的。
此外一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。例如,如果进程 A
中的内容提供程序为进程 B
中的客户端提供服务,或者如果进程 A
中的服务绑定到进程 B
中的组件,则进程 A
始终被视为至少与进程 B
同样重要。
由于运行服务的进程其级别高于托管后台 Activity
的进程,因此启动长时间运行操作的 Activity
最好为该操作启动服务,而不是简单地创建工作线程,当操作有可能比 Activity
更加持久时尤要如此。例如,正在将图片上传到网站的 Activity
应该启动服务来执行上传,这样一来,即使用户退出 Activity
,仍可在后台继续执行上传操作。使用服务可以保证,无论 Activity
发生什么情况,该操作至少具备“服务进程”优先级。同理,广播接收器也应使用服务,而不是简单地将耗时冗长的操作放入线程中。
线程
主线程( UI
线程)
应用启动时,系统会为应用创建一个名为“主线程”的执行线程。此线程非常重要,因为它负责将事件分派给相应的用户界面小部件,其中包括绘图事件。此外,它也是应用与 Android UI
工具包组件(来自 android.widget
和 android.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
。
以下两个例子分别是: Activity
和 Service
属于同一进程或不同进程:
1 | // Service 设置新建进程 |
区别和联系
- 单位
线程是进程的一个组成部分,一个进程可以包含一个或多个线程。
- 占用资源
计算机内部的软硬件资源的分配与线程无关,进程是系统所有资源分配时候的一个基本单位,拥有一个完整的虚拟空间地址,线程是不会分配资源的。系统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享进程的资源。 CPU
调度
线程是进程的一个实体,是 CPU
调度和分配的基本单位,即执行运算的最小单位。其本身不拥有系统资源,只含有程序计数器、寄存器和栈等一些运行时必不可少的基本资源。所以在多核系统中开多线程才能最大化利用 CPU
。
新开进程还是线程,如何选择?考虑的因素是什么?
根据上面的分析可以得出,如果是仅仅考虑后台处理,并发执行等可以只需要开线程就能满足;如果是需要考虑低内存管理进程优先级,应用能分配更多的源等可以新建进程。