29、AMS 源码导读:关键类源码分析
说实话,啃AMS源码这件事,我当年也觉得很痛苦。代码量巨大,类之间关系复杂,稍不留神就迷失在调用链里。但后来我发现一个窍门——抓住三个核心类,就能把整个Activity管理的骨架理清楚。
哪三个?ActivityManagerService、ActivityStack、ActivityRecord。今天我们就来拆开看看,它们各自扮演什么角色,又是怎么配合的。
ActivityManagerService:系统的大脑
AMS是整个Activity管理的总控中心。它负责什么?说白了,所有跟Activity生命周期、进程调度、任务栈管理相关的事情,最终都要经过它。
我习惯把AMS想象成一个操作系统里的“大管家”。你启动一个App,它要检查权限、创建进程、通知Launcher动画、还要管理栈。事情多到爆炸。
来看一段核心代码,这是AMS处理startActivity的入口:
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions,
UserHandle.getCallingUserId());
}
嗯,这里只是转调。真正干活的是startActivityAsUser,它会经过一系列权限检查、进程判断,最后交给ActivityStackSupervisor去处理。
关键点:AMS本身不直接操作Activity栈,它通过ActivityStackSupervisor来协调各个ActivityStack。这种分层设计,让AMS可以专注于“决策”,而把“执行”下放。
我在项目中遇到过一个问题:App启动时偶现ANR,查了半天发现是AMS主线程被一个广播阻塞了。你看,AMS虽然是大脑,但它也是单线程处理消息的,任何耗时操作都会卡住整个系统。
ActivityStack:栈的管理者
ActivityStack,顾名思义,就是管理Activity栈的。但别小看它,它不只是个“栈容器”。
每个ActivityStack对应一种“栈类型”。比如:
- HOME_STACK:桌面Launcher所在的栈
- FULLSCREEN_STACK:全屏应用栈
- FREEFORM_STACK:自由窗口模式栈
每个栈里维护着一组TaskRecord,而每个TaskRecord里又包含多个ActivityRecord。这个层级关系,我画了张图:
你看,这个层级关系非常清晰。AMS通过Supervisor管理多个Stack,每个Stack管理多个Task,每个Task管理多个ActivityRecord。
ActivityStack里有个重要的方法——resumeTopActivityInnerLocked。这个方法负责把栈顶的Activity恢复出来。我当年调试一个分屏bug时,就是在这里加了日志,才发现栈顶Activity的状态判断出了问题。
// frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
// 找到栈顶的ActivityRecord
ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
if (next == null) {
// 栈空了,启动桌面
return resumeHomeActivityLocked(prev, "noMoreActivities");
}
// ... 省略大量状态判断和生命周期调用
}
个人经验:调试栈相关问题时,重点关注topRunningActivityLocked和resumeTopActivityInnerLocked这两个方法。它们几乎涵盖了所有“栈顶切换”的场景。
ActivityRecord:每个Activity的“身份证”
ActivityRecord是什么?它不是一个Activity实例,而是AMS用来描述一个Activity的元数据对象。每个Activity在AMS里都对应一个ActivityRecord。
它里面记录了什么?我列一下关键字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| intent | Intent | 启动该Activity的Intent |
| state | ActivityState | 当前生命周期状态(INITIAL、RESUMED、PAUSED等) |
| app | ProcessRecord | 所属进程 |
| task | TaskRecord | 所属任务栈 |
| launchMode | int | 启动模式(standard、singleTop等) |
| visible | boolean | 是否可见 |
为什么说它是“身份证”?因为AMS判断两个Activity是否相同,就是通过比较ActivityRecord的intent和launchMode。比如singleTop模式下,如果栈顶已经有一个相同Intent的ActivityRecord,就不会新建,而是复用。
来看一段ActivityRecord的构造方法:
// frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java
ActivityRecord(ActivityManagerService _service, ProcessRecord _caller,
int _launchedFromPid, int _launchedFromUid, String _launchedFromPackage,
Intent _intent, String _resolvedType, ActivityInfo aInfo,
Configuration _configuration, ActivityRecord _resultTo,
String _resultWho, int _reqCode, boolean _componentSpecified,
boolean _rootVoiceInteraction, ActivityStackSupervisor supervisor,
ActivityOptions options, ActivityRecord sourceRecord) {
service = _service;
intent = _intent;
info = aInfo;
// ... 初始化一堆字段
state = ActivityState.INITIALIZING;
}
嗯,这里有个细节:state初始值是INITIALIZING。之后AMS会通过scheduleLaunchActivity把它变成INITIAL,再通过生命周期回调一步步推进。
避坑指南:我曾经遇到一个诡异的问题——App切到后台再切回来,界面卡死了。查到最后发现是ActivityRecord的state在某个异常路径下被设置成了STOPPING,但对应的Activity线程根本没收到stop信号。结果AMS一直等,等到超时。所以,ActivityRecord的状态机必须严格同步,任何一步出错都会导致后续流程紊乱。
三个类如何协作?
我举个例子,你就能明白它们是怎么配合的了。
假设你点击桌面图标启动一个App:
- Launcher进程通过Binder调用AMS的
startActivity - AMS检查权限、解析Intent,创建
ActivityRecord - AMS通过
ActivityStackSupervisor找到合适的ActivityStack - ActivityStack把ActivityRecord压入栈顶,调用
resumeTopActivityInnerLocked - AMS通知Zygote进程fork出App进程
- App进程创建Activity实例,绑定到ActivityRecord上
- ActivityRecord的state一路从INITIALIZING走到RESUMED
整个过程,AMS是决策者,ActivityStack是栈管理者,ActivityRecord是状态记录者。三者缺一不可。
总结一下:读AMS源码,别一头扎进细节里。先理清这三个类的关系,你就掌握了Activity管理的“骨架”。剩下的,都是在这个骨架上添砖加瓦。
我个人习惯是,每读一个类,先画它的状态机或类关系图。画着画着,很多疑惑就自然解开了。你也不妨试试。