5、AMS(ActivityManagerService)核心原理:Activity生命周期管理、任务栈、进程调度

AMS,也就是ActivityManagerService,是Android系统里最核心的服务之一。我经常跟团队里的新人说,搞懂了AMS,你就掌握了Android系统的半壁江山。它管着Activity的生死、任务的切换、进程的调度,说白了就是整个系统的“大管家”。

今天咱们就深入聊聊AMS的三个核心模块:Activity生命周期管理、任务栈机制、进程调度策略。我会结合我这些年踩过的坑,给你讲透。

5.1 Activity生命周期管理

Activity的生命周期,大家都很熟悉了。但AMS是怎么管理这些状态的?这才是关键。

每个Activity在AMS里对应一个ActivityRecord对象。AMS维护着一个全局的Activity栈,通过这个栈来管理所有Activity的状态切换。

核心要点:AMS通过ActivityRecord记录每个Activity的状态,通过ActivityStack管理栈结构,通过ActivityStackSupervisor协调多个栈。

生命周期回调的触发,其实是一个跨进程通信的过程。当用户按Home键时,AMS会通知当前Activity进入onPause,然后启动新的Activity。这个过程涉及多次Binder调用,我当年调试一个ANR问题时,就发现是onPause耗时太长,导致AMS卡住了。

5.1.1 生命周期状态机

AMS内部维护了一个状态机,Activity的状态变化都在这里流转。我画了一张图,你看完就明白了:

AMS Activity生命周期状态机 INITIALIZING RESUMED PAUSED STOPPED DESTROYED STOPPING 启动完成 按Home键 被覆盖 重新显示 内存不足 finish() 清理完成

嗯,这里要注意:状态切换不是瞬间完成的。AMS会先发送消息到主线程,等应用处理完再回调。这个异步过程,就是很多生命周期问题的根源。

5.1.2 生命周期与进程优先级

Activity的状态直接影响进程的优先级。AMS会根据当前Activity的状态,动态调整进程的oom_adj值。

Activity状态 进程优先级 oom_adj范围 被杀概率
RESUMED 前台进程 0-100 极低
PAUSED 可见进程 100-200
STOPPED 服务进程 200-300
DESTROYED 缓存进程 300-900

我曾经遇到过一个诡异的问题:应用切到后台后,过一会儿再回来就重启了。查了半天,发现是onStop里做了大量IO操作,导致进程被系统判定为“不响应”,直接给杀了。后来我把IO操作移到后台线程,问题就解决了。

避坑指南:onPause和onStop里不要做耗时操作。系统对这两个方法的执行时间有严格限制,超过5秒就会触发ANR。我建议只做状态保存和资源释放,其他事情交给onSaveInstanceState。

5.2 任务栈管理

任务栈,也就是Task,是AMS管理Activity的核心数据结构。每个Task包含一组ActivityRecord,按照启动顺序排列。

AMS维护着一个全局的Task栈列表。每个Task有自己的栈,ActivityStackSupervisor负责协调这些栈之间的关系。

5.2.1 启动模式与栈行为

Activity的启动模式,说白了就是告诉AMS:这个Activity应该怎么放进栈里。我整理了一个表格,把四种启动模式的行为说清楚:

启动模式 栈行为 典型场景
standard 每次启动都创建新实例,压入栈顶 普通页面
singleTop 如果栈顶已有实例,复用;否则创建新实例 通知栏点击
singleTask 如果栈中已有实例,清除其上所有Activity并复用 主页面
singleInstance 独占一个Task栈 来电界面

你想想看,如果每个Activity都无脑用standard模式,栈会越来越深。用户按返回键要按半天才能退出。我一般建议主页面用singleTask,子页面用standard,特殊场景用singleTop。

5.2.2 Intent Flag对栈的影响

除了启动模式,Intent Flag也能控制栈行为。常用的几个Flag:

  • FLAG_ACTIVITY_NEW_TASK:在新Task中启动Activity
  • FLAG_ACTIVITY_CLEAR_TOP:如果目标Activity已在栈中,清除其上所有Activity
  • FLAG_ACTIVITY_SINGLE_TOP:类似singleTop模式
  • FLAG_ACTIVITY_REORDER_TO_FRONT:把栈中已有的Activity移到栈顶

我记得有一次,一个同事用FLAG_ACTIVITY_NEW_TASK启动了一个Activity,结果发现按返回键直接回到桌面了。原因就是新Task和原Task没有关联,返回时直接退出了整个Task。后来我建议他用FLAG_ACTIVITY_CLEAR_TOP配合使用,问题就解决了。

注意:FLAG_ACTIVITY_NEW_TASK和singleTask模式配合使用时,要小心TaskAffinity的设置。如果两个Activity的TaskAffinity不同,它们会被分配到不同的Task中,导致返回行为不符合预期。

5.3 进程调度

AMS的进程调度,核心就是根据进程的重要性,决定什么时候该杀谁。这个机制叫Low Memory Killer(LMK),它和AMS紧密配合。

5.3.1 进程优先级体系

AMS把进程分为5个等级,每个等级对应不同的oom_adj值:

  1. 前台进程:正在和用户交互的Activity所在进程
  2. 可见进程:Activity可见但不处于前台
  3. 服务进程:正在运行Service的进程
  4. 后台进程:Activity已停止但进程还在
  5. 空进程:没有任何组件的进程

LMK会根据内存压力,从最低优先级的进程开始杀。我调试过一个案例:应用在后台播放音乐,结果被杀了。查日志发现是Service被系统判定为“可杀”,因为Service的优先级比可见进程低。后来我把Service绑定到前台通知,优先级就上去了。

5.3.2 AMS与LMK的协作

AMS和LMK的协作,是通过一个叫“proc”的数据结构完成的。AMS会更新每个进程的oom_adj值,LMK根据这个值决定杀谁。

// AMS更新进程优先级的核心逻辑
void updateOomAdjLocked(ProcessRecord app) {
    // 根据Activity状态计算优先级
    if (app.activities.size() > 0) {
        ActivityRecord r = app.activities.get(0);
        if (r.state == RESUMED) {
            app.setOomAdj(ADJ_FOREGROUND);  // 前台进程
        } else if (r.state == PAUSED) {
            app.setOomAdj(ADJ_VISIBLE);     // 可见进程
        } else {
            app.setOomAdj(ADJ_BACKGROUND);  // 后台进程
        }
    }
    // 通知LMK更新
    mProcessList.updateLruProcessLocked(app);
}

这段代码看起来简单,但实际逻辑要复杂得多。AMS还要考虑Service、BroadcastReceiver、ContentProvider等因素。我见过一个极端情况:一个进程同时有前台Activity和后台Service,AMS会取最高优先级,所以Service不会被杀。

核心要点:进程的优先级是动态变化的。AMS会根据当前所有组件的状态,取最高优先级作为进程的最终优先级。所以,如果你的应用需要保活,最好的办法是让用户看到你的界面。

5.3.3 进程冻结与解冻

Android 11引入了冻结机制。当应用进入后台一段时间后,AMS会冻结它的进程,停止所有线程的执行。这比直接杀进程更友好,因为恢复时更快。

冻结的触发条件:

  • 应用进入后台超过一定时间(默认5分钟)
  • 没有前台Service或通知
  • 没有正在执行的JobScheduler任务

解冻的触发条件:

  • 用户再次打开应用
  • 有高优先级的广播需要处理
  • 系统内存充足时自动解冻

我曾经遇到一个bug:应用被冻结后,AlarmManager设置的定时任务不执行了。查了源码才发现,冻结状态下AlarmManager的闹钟会被延迟。解决方案是用WorkManager替代AlarmManager,因为WorkManager有专门的唤醒机制。

避坑指南:如果你的应用需要在后台执行定时任务,不要依赖AlarmManager。建议使用WorkManager,它兼容冻结机制。我踩过这个坑,后来全部迁移到WorkManager,问题就解决了。

5.4 总结

AMS的核心原理,说白了就是三件事:管好Activity的生死、管好任务的栈结构、管好进程的优先级。这三件事相互关联,一个Activity的状态变化,会影响它所在Task的结构,也会影响它所在进程的优先级。

我做了这么多年Android开发,最大的体会是:理解AMS,就能理解Android系统的设计哲学。它把复杂的状态管理抽象成简单的栈结构,把进程调度和组件生命周期紧密绑定。这种设计思路,值得我们每个开发者学习。

嗯,今天就聊到这里。如果你在实际开发中遇到AMS相关的问题,欢迎来公众号找我交流。


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