15、ActivityManagerService 数据结构:mActivities、mRecentTasks、mProcessList等核心数据结构
各位同学,今天我们来聊聊AMS里那些“藏”在代码深处的数据结构。说实话,我刚开始啃AMS源码时,最头疼的就是这些集合——mActivities、mRecentTasks、mProcessList……它们就像AMS的“五脏六腑”,搞不懂它们,你根本没法理解Activity是怎么被管理的。
我个人习惯把AMS比作一个“大管家”,它手里握着三本账本:一本记着当前所有Activity的状态(mActivities),一本记着最近打开过的任务(mRecentTasks),还有一本记着所有正在运行的进程(mProcessList)。这三本账本相互关联,缺一不可。
15.1 mActivities:Activity的“实时监控表”
mActivities是一个ActivityRecord的集合,说白了就是AMS用来跟踪所有已启动Activity的“台账”。它是个ArrayList,里面存的是ActivityRecord对象。每个ActivityRecord代表一个Activity实例——注意,是实例,不是类。
核心要点:mActivities里存的ActivityRecord,是“正在运行”或“已暂停”的Activity。那些已经被销毁的,会从列表里移除。
我在项目中遇到过一个问题:某个App频繁启动Activity,结果mActivities越来越大,最后导致OOM。排查后发现是某个三方SDK在后台不断启动透明Activity,而且没有及时finish。嗯,这里要注意——mActivities不是无限制增长的,AMS有保护机制,但如果你代码写得“太野”,还是可能出问题。
// 源码位置:ActivityManagerService.java
final ArrayList<ActivityRecord> mActivities = new ArrayList<>();
// 添加ActivityRecord
void addActivityRecord(ActivityRecord r) {
mActivities.add(r);
}
// 移除ActivityRecord
void removeActivityRecord(ActivityRecord r) {
mActivities.remove(r);
}
你想想看,AMS每次要查找某个Activity时,都得遍历这个列表。如果列表里有几千个ActivityRecord,那性能肯定扛不住。所以Android系统对Activity的启动数量是有限制的——默认情况下,一个App最多能启动的Activity数量是500个左右(不同厂商可能有差异)。
15.2 mRecentTasks:最近任务的“历史档案”
mRecentTasks存储的是最近打开过的任务(Task)。每个Task对应一个“任务栈”,也就是我们按“最近任务”键时看到的那一摞卡片。它是一个ArrayList<TaskRecord>,按最近使用时间排序——最近用的排在最前面。
我曾经调试过一个Bug:用户按“最近任务”键时,某个App的卡片不显示。查了半天,发现是那个App的TaskRecord被从mRecentTasks里移除了。为什么?因为AMS在清理最近任务时,会把某些“不合法”的任务剔除——比如没有Activity的任务,或者被标记为“excludeFromRecents”的任务。
避坑指南:如果你在AndroidManifest里给Activity设置了android:excludeFromRecents="true",那这个Activity所属的Task就不会出现在最近任务列表里。我曾经有个项目,用户反馈“为什么我的App在最近任务里消失了”,结果就是某个Activity加了这个属性,导致整个Task被“雪藏”。
// 源码位置:ActivityManagerService.java
final ArrayList<TaskRecord> mRecentTasks = new ArrayList<>();
// 添加最近任务
void addRecentTask(TaskRecord task) {
mRecentTasks.add(task);
// 如果超过最大数量,移除最旧的
if (mRecentTasks.size() > MAX_RECENT_TASKS) {
mRecentTasks.remove(0);
}
}
mRecentTasks的大小是有限制的。不同设备不一样,手机一般是20-30个,平板可能更多。为什么?因为最近任务列表不能无限长,否则用户翻起来也累,系统内存也扛不住。
15.3 mProcessList:进程的“花名册”
mProcessList是AMS用来管理所有运行中进程的数据结构。它是一个ProcessList对象,内部维护了多个列表——比如mLruProcesses(按LRU排序的进程列表)、mPidsSelfLocked(按PID索引的进程列表)等。
说白了,mProcessList就是AMS的“进程花名册”。每个进程对应一个ProcessRecord对象,里面记录了进程的PID、UID、启动时间、运行状态、绑定的Activity等信息。
核心要点:mProcessList不仅记录进程的存在,还记录进程的“优先级”。AMS根据这个优先级来决定——当内存不足时,先杀谁,后杀谁。
我记得有一次线上问题:某个App在后台频繁被杀死。查日志发现,它的进程在mLruProcesses里排在很后面——也就是“最近最少使用”的进程。为什么?因为这个App在后台做了大量计算,占用了CPU,但用户很久没打开它了。AMS觉得它“不活跃”,就优先把它杀了。
// 源码位置:ProcessList.java
final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<>();
final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<>();
// 更新进程的LRU位置
void updateLruProcessLocked(ProcessRecord app, boolean activityChange, ProcessRecord client) {
mLruProcesses.remove(app);
mLruProcesses.add(app);
// 如果进程有Activity,放到更靠前的位置
if (activityChange && app.hasActivities()) {
// 调整位置...
}
}
15.4 三者的关系:一张图看懂
这三个数据结构不是孤立的。它们之间通过引用相互关联——一个ActivityRecord会引用它所属的TaskRecord,TaskRecord会引用它包含的ActivityRecord列表,ProcessRecord会引用它运行的所有ActivityRecord。嗯,这里要理清楚。
15.5 实战中的“坑”与经验
我做了这么多年Android Framework,跟这三个数据结构打过无数次交道。说几个实战中容易踩的坑:
- mActivities泄漏:如果Activity的onDestroy没有被调用(比如因为某些异常),ActivityRecord会一直留在mActivities里。我曾经遇到一个App,因为某个Handler持有Activity的引用,导致Activity无法被GC,mActivities里堆积了几百个ActivityRecord。
- mRecentTasks混乱:如果你在代码里频繁调用
moveTaskToBack或者finishAffinity,可能会导致mRecentTasks里的TaskRecord状态不一致。我建议——尽量少用这些“骚操作”,让系统自己管理任务栈。 - mProcessList的LRU排序:AMS根据mLruProcesses的顺序来决定杀进程的优先级。如果你的App在后台做了大量工作(比如定位、音乐播放),记得使用前台Service来提高进程优先级,否则很容易被系统“干掉”。
警告:不要尝试直接修改AMS的这些数据结构!它们是系统内部状态,任何非法修改都可能导致系统崩溃或死锁。我曾经见过有人通过反射修改mActivities,结果系统直接panic——嗯,那场面,相当壮观。
好了,关于AMS的这三个核心数据结构,我就讲到这里。记住:mActivities管Activity实例,mRecentTasks管任务栈,mProcessList管进程。三者通过引用相互关联,构成了AMS的“骨架”。搞懂它们,你就掌握了AMS的一半。
公众号:蓝海资料掘金营,微信deep3321