Android 平台 App 进程优先级

我们都知道 Android 手机上可以安装很多 App,每一个 App 至少会有一个进程。创建进程是件麻烦而且耗资源的事情,Android 为了让 App 启动的时候能更快,会把那些暂时不使用的 App 进程缓存起来,但是内存是有限的,总不能让所有的进程都放在内存里边,所以 Android 有一个淘汰机制:根据 App 的运行状态设置一个进程优先级(oom_adj),然后根据内存的紧张程度,把那些优先级低(oom_adj 值大)的进程 kill 掉,以保证其他进程有足够的内存使用。

进程

  1. Zygote 进程 这是 Android 框架的主要进程,所有的 App 进程以及系统服务进程 SystemServer 都是由 Zygote 进程 Fork 出来的。

  2. App 的主进程 每一个 App 的运行都在一个独立的进程中,进程名就是 App 的 packagename,这些进程都是从 Zygote 进程 Fork 出来的,并受 AMS(ActivityManagerService)管理。

  3. App 的辅助进程 可以允许 App 有多个进程,在 AndroidManifest.xml 里配置 android:process 属性,就可以开启多进程,这些进程名都是 packagename:name 这种形式,以区分属于哪个 App。这些进程也都是从 Zygote 进程 Fork 出来的,并受 AMS 管理。

  4. 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常量名说明
16UNKNOWN_ADJ预留的最低级别,一般对于缓存的进程才有可能设置成这个级别
15CACHED_APP_MAX_ADJ缓存进程、空进程,内存不足时优先被 kill
9CACHED_APP_MIN_ADJ缓存进程,也就是空进程
8SERVICE_B_ADJ不活跃的进程(old and decrepit services)
7PREVIOUS_APP_ADJ切换进程,即用户上一个使用的 App
6HOME_APP_ADJ与 Home 交互的进程
5SERVICE_ADJ有 Service 的进程
4HEAVY_WEIGHT_APP_ADJ高权重进程
3BACKUP_APP_ADJ正在备份的进程
2PERCEPTIBLE_APP_ADJ可感知的进程,比如后台播放音乐
1VISIBLE_APP_ADJ可见进程
0FOREGROUND_APP_ADJ前台进程
-11PERSISTENT_SERVICE_ADJ重要进程,系统或持久进程绑定的
-12PERSISTENT_PROC_ADJ核心进程,比如 telephony
-16SYSTEM_ADJ系统进程
-17NATIVE_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,是一个驱动。

关键点:

  1. 向系统注册 lowmem_shrinker 回调,当系统空闲内存不足时调用,去 kill 进程。
  2. 把”空闲内存低于多少就 kill 那个级别的进程”定义为策略。
  3. 杀的策略由 application 层根据内存状况指定,写入文件传递给驱动。
  4. 根据策略计算出 min_score_adj
  5. 每个 App 进程都有 oom_adj 值,根据 oom_adj 值计算出 oom_score_adj(得分值),oom_adj 越大,oom_score_adj 越高,高于 min_score_adj 的被列入死亡名单。
  6. 在死亡名单里选一个占内存最大的进程 kill 掉。

所以当内存不足时,进程优先级低(oom_adj 越大)且占内存大的 App 进程会被优先 kill 掉。

策略由两个文件传递给驱动,各 ROM 可能不一致,但都只能分 6 个级别:

查看传给 lowmemorykiller 驱动的策略文件
查看传给 lowmemorykiller 驱动的策略文件

  • minfree:列举 6 个内存阈值,例如:32768,61440,73728,129024,147456,184320
  • adj:列举 6 个 oom_adj 级别,例如:0,1,2,3,9,15

两个值相互对应,空闲内存低于某个阈值时就 kill 对应级别的进程。

ProcessList.java 里定义的策略参数
ProcessList.java 里定义的策略参数

当然有些 ROM 也会把这个参数定义在 init.rc 里边,开机后再写入 minfreeadj 文件传递给驱动。

trimApplications 机制

仅仅在低内存情况下才 kill 进程吗?明显不是,对于 App Crash、退出、系统清理、卸载这些情况也需要处理。

trimApplications 是一个方法,定义在 ActivityManagerService 里边,位于:

${android-sdk-path}/sources/android-23/com/android/server/am/ActivityManagerService.java

关键点:

  1. package 已被卸载的无用进程会被 kill。
  2. persistent 的 App 会被优先照顾,进程优先级设置为 PERSISTENT_PROC_ADJ = -12
  3. Activity 仅在主进程跑的 App 会被认为是高权重进程,HEAVY_WEIGHT_APP_ADJ = 4
  4. 只有前台进程才是 FOREGROUND_APP_ADJ = 0,前台进程不会被杀。
  5. 每当 Activity、Service 等生命周期发生变化时都会引起进程 oom_adj 的调整。
  6. 进程里边没有任何 Activity 的存在,优先被杀。
  7. 空进程最容易被杀。

如何提高后台 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)。

  • 发送/接收广播,别让自己变成空进程。

很明显这会消耗更多的电量,也有点流氓,有些需求也许本就不该做。