8、ActivityManagerService(AMS):Activity栈管理、启动模式、进程优先级与OOM调整

说实话,AMS 是 Android 框架里最绕的一块。我当年刚啃源码的时候,光 Activity 栈就画废了好几张纸。但你别怕,这东西说白了就是一套「任务调度系统」——谁该显示、谁该隐藏、谁该被杀掉,全由 AMS 说了算。

今天我们就把它拆开揉碎,从栈管理到启动模式,再到进程优先级和 OOM 调整,一条线串下来。

8.1 Activity 栈:谁在上面谁就是老大

Activity 栈,也叫 Task。每个 Task 就是一个栈结构,后进先出。你打开一个 App,点进一个页面,再点进另一个页面——这些 Activity 就一层层压进栈里。

按返回键的时候,栈顶的 Activity 弹出,下面的那个就露出来了。嗯,就这么简单。

但实际项目里,栈的管理远不止这么简单。我遇到过一个问题:用户从通知栏点进来,结果按返回键直接回到桌面,而不是回到 App 之前的页面。这就是栈没处理好。

核心概念:AMS 维护了一个全局的 Activity 栈列表。每个 Task 有自己的栈,每个栈里有多个 Activity Record。AMS 通过栈顶 Activity 来决定当前谁在显示。

8.2 启动模式:四种模式,各有各的坑

启动模式是 AMS 栈管理的核心策略。Android 提供了四种启动模式,我一个个说。

8.2.1 standard

默认模式。每次启动都会创建一个新的 Activity 实例,压入当前 Task 的栈顶。说白了就是「来一个加一个」,不管栈里有没有相同的实例。

这种模式最容易造成栈里一堆重复的 Activity。我记得有个项目,用户连续点了五次按钮,结果按返回键要按五次才能退出——这就是 standard 的典型问题。

8.2.2 singleTop

如果栈顶已经是这个 Activity 的实例,就不创建新的,而是复用栈顶的,调用 onNewIntent()。如果不在栈顶,还是得创建新的。

这个模式适合推送通知的场景。用户点通知,如果目标页面已经在栈顶,直接刷新内容就行,不用重新创建。

8.2.3 singleTask

这个就厉害了。AMS 会在整个系统里找有没有这个 Activity 所在的 Task。如果有,就把这个 Task 拉到前台,并且把目标 Activity 之上的所有 Activity 都清掉。如果没有,就新建一个 Task。

我建议把 App 的主页设置成 singleTask。这样用户从任何地方回到主页,栈里都是干干净净的,不会出现一堆页面堆在上面。

8.2.4 singleInstance

这个更特殊。它单独占一个 Task,而且这个 Task 里只能有它一个 Activity。任何其他 Activity 都不能跟它共用一个 Task。

说实话,这个模式我用的不多。只有在做系统级的「悬浮窗」或者「来电界面」时才会用到。因为它太独立了,容易造成用户困惑——按返回键直接跳到桌面,而不是回到之前的 App。

避坑指南:我曾经在 singleTask 和 singleInstance 之间搞混过。记住一句话:singleTask 是「清掉上面的,复用自己」,singleInstance 是「我自己一个栈,谁也别进来」。

8.3 启动模式与 Intent Flag 的配合

除了在 Manifest 里设置 launchMode,你还可以在代码里通过 Intent Flag 来临时改变启动行为。这两者配合起来,才能做到精细控制。

Flag 作用 常用场景
FLAG_ACTIVITY_NEW_TASK 在新 Task 中启动 Activity 从 Service 或 BroadcastReceiver 启动 Activity
FLAG_ACTIVITY_CLEAR_TOP 如果目标 Activity 已在栈中,清掉它上面的所有 Activity 回到主页时清空中间页面
FLAG_ACTIVITY_SINGLE_TOP 如果目标 Activity 在栈顶,复用不重建 同 singleTop 模式
FLAG_ACTIVITY_CLEAR_TASK 启动前先清空目标 Task 中的所有 Activity 重新开始一个新的任务栈

我个人习惯是:Manifest 里设置默认模式,代码里用 Flag 做临时调整。比如主页用 singleTask,但用户从推送通知进来时,加上 FLAG_ACTIVITY_CLEAR_TOP,确保栈是干净的。

8.4 进程优先级:谁该活,谁该死

AMS 不光管 Activity 的显示,还管进程的生死。当系统内存不足时,AMS 会根据进程优先级来决定先杀谁。

Android 的进程优先级从高到低分为五档:

  1. 前台进程:用户正在交互的 Activity,或者绑定了前台 Service。优先级最高,几乎不会被杀。
  2. 可见进程:Activity 可见但不在前台(比如被对话框部分遮挡)。优先级次之。
  3. 服务进程:正在运行 Service,但没有前台界面。比如后台下载、音乐播放。
  4. 后台进程:Activity 被暂停了,但进程还在。这类进程是 OOM 杀的主要目标。
  5. 空进程:没有任何组件在运行,纯粹为了缓存。优先级最低,最先被杀。
注意:后台进程并不是「一进入后台就被杀」。AMS 有一个 LRU 列表,越久没被使用的后台进程越容易被杀。所以你的 App 如果频繁被杀,不一定是内存不够,可能是用户很久没切回来了。

8.5 OOM 调整:LMK 是怎么工作的

OOM 调整,说白了就是 Low Memory Killer(LMK)的工作。LMK 是内核层的一个机制,它会根据 AMS 提供的 oom_adj 值来决定杀谁。

oom_adj 的取值范围是 -17 到 15。值越小,优先级越高,越不容易被杀。前台进程的 oom_adj 通常是 0,后台进程可能是 8 到 15。

LMK 的工作流程是这样的:

  • 系统内存低于某个阈值时,LMK 开始扫描进程的 oom_adj
  • 找到 oom_adj 最大的进程(也就是优先级最低的)
  • 发送 SIGKILL 信号,杀掉这个进程
  • 如果内存还是不够,继续杀下一个

我记得有一次,客户的 App 在后台总是被杀。查了半天,发现是 Service 里做了大量内存分配,导致 oom_adj 被系统调高了。解决方案是把 Service 改成前台 Service,oom_adj 就降下来了。

关键点:oom_adj 不是固定的。AMS 会根据进程的状态动态调整。比如一个后台 Activity 突然开始播放音乐,AMS 会把它提升到服务进程的优先级。

8.6 知识体系总览

下面这张图是我自己整理的 AMS 核心逻辑。你看一眼,基本就能把今天的内容串起来。

AMS 核心逻辑总览 ActivityManagerService Activity 栈管理 Task 栈结构 启动模式控制 Intent Flag 配合 进程优先级管理 前台/可见/服务/后台/空 LRU 列表维护 oom_adj 动态调整 OOM 调整 LMK 内核机制 内存阈值触发 SIGKILL 杀进程 三者关系:栈管理决定谁显示,优先级决定谁存活,OOM 决定谁先死 AMS 是总调度,协调这三块逻辑

你看,AMS 其实就干三件事:管栈、管优先级、管 OOM。这三件事是联动的。栈里的 Activity 状态变了,优先级就跟着变;优先级变了,oom_adj 就跟着变;oom_adj 变了,LMK 杀进程的顺序就变了。

嗯,今天的内容就到这。你回去可以自己画一遍这个图,画完基本就通了。

公众号:蓝海资料掘金营,微信deep3321