Android 平台 App 进程优先级
我们都知道 Android 手机上可以安装很多 App,每一个 App 至少会有一个进程。创建进程是件麻烦而且耗资源的事情,Android 为了让 App 启动的时候能更快,会把那些暂时不使用的 App 进程缓存起来,但是内存是有限的,总不能让所有的进程都放在内存里边,所以 Android 有一个淘汰机制:根据 App 的运行状态设置一个进程优先级(oom_adj),然后根据内存的紧张程度,把那些优先级低(oom_adj 值大)的进程 kill 掉,以保证其他进程有足够的内存使用。
进程
-
Zygote 进程 这是 Android 框架的主要进程,所有的 App 进程以及系统服务进程 SystemServer 都是由 Zygote 进程 Fork 出来的。
-
App 的主进程 每一个 App 的运行都在一个独立的进程中,进程名就是 App 的 packagename,这些进程都是从 Zygote 进程 Fork 出来的,并受 AMS(ActivityManagerService)管理。
-
App 的辅助进程 可以允许 App 有多个进程,在 AndroidManifest.xml 里配置
android:process属性,就可以开启多进程,这些进程名都是packagename:name这种形式,以区分属于哪个 App。这些进程也都是从 Zygote 进程 Fork 出来的,并受 AMS 管理。 -
Native 进程 Android 除了使用 Java,还有 NDK,可以使用 C/C++ 去开发,这里也可以 Fork 出进程,我一般称之为 Native 进程。Native 进程可以不受 AMS 管理,自由度很大,本文暂不讲。
优先级
App 进程的优先级在 com.android.server.am.ProcessList 类里边定义,这个类在 Android 的 API 里边找不到,要看实现可以去 Android SDK 里找,位于:
${android-sdk-path}/sources/android-23/com/android/server/am/ProcessList.java
主要有这么几个优先级(oom_adj 值):
| oom_adj | 常量名 | 说明 |
|---|---|---|
| 16 | UNKNOWN_ADJ | 预留的最低级别,一般对于缓存的进程才有可能设置成这个级别 |
| 15 | CACHED_APP_MAX_ADJ | 缓存进程、空进程,内存不足时优先被 kill |
| 9 | CACHED_APP_MIN_ADJ | 缓存进程,也就是空进程 |
| 8 | SERVICE_B_ADJ | 不活跃的进程(old and decrepit services) |
| 7 | PREVIOUS_APP_ADJ | 切换进程,即用户上一个使用的 App |
| 6 | HOME_APP_ADJ | 与 Home 交互的进程 |
| 5 | SERVICE_ADJ | 有 Service 的进程 |
| 4 | HEAVY_WEIGHT_APP_ADJ | 高权重进程 |
| 3 | BACKUP_APP_ADJ | 正在备份的进程 |
| 2 | PERCEPTIBLE_APP_ADJ | 可感知的进程,比如后台播放音乐 |
| 1 | VISIBLE_APP_ADJ | 可见进程 |
| 0 | FOREGROUND_APP_ADJ | 前台进程 |
| -11 | PERSISTENT_SERVICE_ADJ | 重要进程,系统或持久进程绑定的 |
| -12 | PERSISTENT_PROC_ADJ | 核心进程,比如 telephony |
| -16 | SYSTEM_ADJ | 系统进程 |
| -17 | NATIVE_ADJ | 系统起的 Native 进程,不受 AMS 管理 |
在 Android-18 及以下,空进程不叫
CACHED_APP_MIN_ADJ,叫HIDDEN_APP_MIN_ADJ,名字不一样但值相同。
如何查看 App 进程优先级
对于受 AMS 管理的 App 进程,都会有一个 oom_adj 文件记录着优先级的值:
cat /proc/${pid}/oom_adj
lowmemorykiller 机制
这个机制在 goldfish(即 Android 的 Linux kernel)层面实现,具体代码见 lowmemorykiller.c,是一个驱动。
关键点:
- 向系统注册
lowmem_shrinker回调,当系统空闲内存不足时调用,去 kill 进程。 - 把”空闲内存低于多少就 kill 那个级别的进程”定义为策略。
- 杀的策略由 application 层根据内存状况指定,写入文件传递给驱动。
- 根据策略计算出
min_score_adj。 - 每个 App 进程都有
oom_adj值,根据oom_adj值计算出oom_score_adj(得分值),oom_adj越大,oom_score_adj越高,高于min_score_adj的被列入死亡名单。 - 在死亡名单里选一个占内存最大的进程 kill 掉。
所以当内存不足时,进程优先级低(oom_adj 越大)且占内存大的 App 进程会被优先 kill 掉。
策略由两个文件传递给驱动,各 ROM 可能不一致,但都只能分 6 个级别:

minfree:列举 6 个内存阈值,例如:32768,61440,73728,129024,147456,184320adj:列举 6 个 oom_adj 级别,例如:0,1,2,3,9,15
两个值相互对应,空闲内存低于某个阈值时就 kill 对应级别的进程。

当然有些 ROM 也会把这个参数定义在 init.rc 里边,开机后再写入 minfree 和 adj 文件传递给驱动。
trimApplications 机制
仅仅在低内存情况下才 kill 进程吗?明显不是,对于 App Crash、退出、系统清理、卸载这些情况也需要处理。
trimApplications 是一个方法,定义在 ActivityManagerService 里边,位于:
${android-sdk-path}/sources/android-23/com/android/server/am/ActivityManagerService.java
关键点:
- package 已被卸载的无用进程会被 kill。
persistent的 App 会被优先照顾,进程优先级设置为PERSISTENT_PROC_ADJ = -12。- Activity 仅在主进程跑的 App 会被认为是高权重进程,
HEAVY_WEIGHT_APP_ADJ = 4。 - 只有前台进程才是
FOREGROUND_APP_ADJ = 0,前台进程不会被杀。 - 每当 Activity、Service 等生命周期发生变化时都会引起进程
oom_adj的调整。 - 进程里边没有任何 Activity 的存在,优先被杀。
- 空进程最容易被杀。
如何提高后台 App 进程的优先级
Android 框架的思想是很好的,对于空的进程、没事干的进程直接 kill 掉,对用户体验不会有影响。但往往 App 都会有推送功能,而 GCM(Google Cloud Messaging)在国内又不能用,所以很多情况下我们也会希望 App 在后台尽量不要被杀。
几个方法:
-
在 AndroidManifest.xml 中配置 persistent 属性
<application android:name="App" android:persistent="true" android:label="@string/dialerIconLabel" android:icon="@drawable/ic_launcher_phone"> -
重载 back 按键事件,让 Activity 在后台运行,不要 Destroy。
-
开 Service 并设置前台运行方式。
-
与 NotificationManager 交互,让进程变成可感知进程(
PERCEPTIBLE_APP_ADJ = 2)。 -
发送/接收广播,别让自己变成空进程。
很明显这会消耗更多的电量,也有点流氓,有些需求也许本就不该做。