14、App 进程管理:AMS对进程的创建、销毁、保活策略
进程管理这块,说白了就是AMS怎么管着App的“生老病死”。
我刚开始啃这部分源码时,觉得不就是fork个进程、杀个进程嘛,有啥难的?后来真踩了坑才发现,这里面的门道多着呢。尤其是保活策略,简直是国内Android开发的“兵家必争之地”。
今天咱们就聊聊,AMS到底是怎么创建进程、销毁进程,以及那些所谓的“保活黑科技”在系统层面到底是怎么一回事。
14.1 进程的创建:从startActivity到Zygote
你点一个图标,App就启动了。这背后AMS干了啥?
我画了个流程图,你看一眼就明白了:
嗯,这里有个关键点:AMS并不直接创建进程。它通过Socket向Zygote进程发送一个“给我生个孩子”的请求。Zygote收到后,会fork自身,然后在子进程中加载App的APK、初始化ActivityThread。
核心源码路径:
- AMS.startProcessLocked() → Process.start() → ZygoteProcess.openZygoteSocket()
- Zygote侧:ZygoteConnection.runOnce() → Zygote.forkAndSpecialize()
我个人习惯在分析启动流程时,重点关注ProcessList里的几个阈值。比如前台进程最多能有多少个、缓存进程何时开始清理。这些参数直接影响启动速度。
14.2 进程的销毁:LMK与OOM_ADJ
进程销毁,说白了就是“杀进程”。但AMS不是乱杀的,它有一套精密的评分体系——OOM_ADJ。
每个进程都有一个adj值,范围从-17到15。值越大,越容易被杀。我整理了一张表:
| adj值 | 进程类型 | 典型场景 |
|---|---|---|
| -17 | 系统核心进程 | system_server |
| 0 | 前台进程 | 用户正在交互的Activity |
| 1 | 可见进程 | Activity onPause但可见 |
| 2 | 服务进程 | 前台Service |
| 5 | 后台进程 | Activity onStop |
| 6 | 内容提供者 | ContentProvider |
| 7 | 空进程 | 无组件运行 |
| 15 | 缓存进程 | 最近最少使用的进程 |
我曾经在项目里遇到过一个问题:某个App后台播放音乐,但总是被杀。查了半天,发现是它的Service没有调用startForeground(),导致adj值被系统判定为2(后台服务),而不是0(前台服务)。
避坑指南:不要试图通过降低adj值来保活。Android 8.0以后,系统对后台进程的限制越来越严。你就算把adj改成-12,系统也会在下一轮检查中把你纠正回来。
销毁的触发时机主要有两个:
- LMK(Low Memory Killer):当系统内存低于阈值时,内核直接根据adj值杀进程。这是最狠的,连AMS都拦不住。
- AMS主动清理:比如
trimApplications()、killBackgroundProcesses()。这些是AMS在内存紧张时主动调用的。
14.3 保活策略:系统视角的真相
说到保活,很多开发者会想到“双进程守护”、“1像素Activity”、“前台Service”这些招数。但站在系统架构师的角度看,这些其实都是“钻空子”。
Android系统对保活的态度是:你越是想活,我越要杀你。尤其是国内厂商,各家ROM都加了“省电管理”、“自启动管理”,第三方App的保活成功率越来越低。
我总结了几种常见的保活策略,以及系统层面的应对:
| 保活策略 | 原理 | 系统应对(Android 8.0+) |
|---|---|---|
| 前台Service | 通过通知栏提升进程优先级 | 必须显示通知,且用户可关闭 |
| 双进程守护 | A进程被杀,B进程拉起A | 限制后台启动,禁止互相拉起 |
| JobScheduler | 利用系统任务调度周期性唤醒 | 受省电模式限制,间隔变长 |
| 1像素Activity | 启动透明Activity提升adj | 后台启动Activity被限制 |
| 系统白名单 | 申请加入厂商白名单 | 需要用户手动授权,且有限额 |
我的建议:与其花精力研究保活,不如把业务逻辑设计成“可恢复”的。比如即时通讯App,断线后重连、消息同步做好,用户根本感知不到进程被杀了。这才是正道。
你想想看,为什么微信、支付宝这些国民App能一直活着?不是它们技术多牛,而是它们被加入了系统级白名单。普通App想复制这条路,基本没戏。
14.4 进程优先级动态调整
AMS不是静态地给每个进程一个adj值就完事了。它会根据进程的运行状态,动态调整adj值。
举个例子:
- 你的App在前台时,adj=0,系统打死也不会杀你。
- 按Home键回到桌面,Activity onStop,adj变成5。
- 过了一段时间,系统内存紧张,adj可能变成7甚至15。
- 如果用户再切回来,adj立刻回到0。
这个调整过程在ProcessList.updateOomAdjLocked()里完成。我读这部分代码时,印象最深的是它有一个computeOomAdjLocked()方法,里面密密麻麻全是if-else,判断进程里有哪些组件在运行、有没有绑定服务、有没有前台通知……
关键代码片段(伪代码):
// frameworks/base/services/core/java/com/android/server/am/ProcessList.java
int computeOomAdjLocked(ProcessRecord app) {
if (app.foregroundActivities) {
return ADJ_FOREGROUND; // 0
}
if (app.foregroundServices) {
return ADJ_FOREGROUND_SERVICE; // 1
}
if (app.hasShownUi && !app.activities.isEmpty()) {
return ADJ_VISIBLE; // 2
}
// ... 更多判断
return ADJ_CACHED; // 15
}
嗯,这里要注意:adj值只是建议,不是最终判决。LMK在杀进程时,还会考虑进程的rss(实际物理内存占用)。一个adj=5但占用了2GB内存的进程,比adj=7但只占10MB的进程更容易被杀。
14.5 进程生命周期管理的最佳实践
讲了这么多原理,最后给点实际建议。我在做系统优化时,总结了几条原则:
- 不要对抗系统。Android的进程管理机制是为了保证用户体验和系统稳定性。你非要保活,最后倒霉的是用户手机的电量和内存。
- 合理使用前台Service。如果确实需要长时间运行(比如音乐播放、导航),一定要调用
startForeground(),并且给用户一个清晰的通知。 - 利用WorkManager。对于后台任务,用WorkManager替代自己维护线程。系统会帮你做省电优化。
- 做好状态保存。在
onSaveInstanceState()里保存关键数据。进程被杀后恢复时,用户无感。 - 监控进程状态。通过
ActivityManager.getProcessMemoryInfo()等API,了解自己进程的内存占用,及时释放资源。
我记得有一次,一个合作方的App在后台疯狂拉活,导致用户手机发热严重。最后用户直接卸载了。你看,保活保到最后,反而把自己保死了。
说白了,进程管理这件事,系统比你聪明。你只需要做好自己的事,剩下的交给AMS就好。