做Android开发这些年,天天跟四大组件打交道,最后都会绕到一个叫ActivityManagerService(AMS)的地方。startActivity、startService、registerReceiver,看起来是各自为战,实际全归它管。谁该启动、谁该暂停、内存吃紧先杀谁、任务栈怎么恢复,都是它说了算。我真正下决心啃AMS的启动过程,是因为遇到一台开机黑屏的设备,顺着system_server日志一路追到AMS,才明白整个开机链路里这一环有多关键。这篇东西就按我当时的排查思路,把AMS从无到有的启动过程完整拆开,讲清楚它什么时候创建、创建时做了什么、怎么一步步进入ready状态,顺便把踩过的坑和排查心得一起放出来,给正在啃framework的朋友做个参考。
1. AMS到底是什么,它凭什么这么重要
1.1 Android系统里的“大内总管”
如果把Android框架比作一家餐厅,AMS就是那个大内总管。Activity是包厢里的客人,Service是后厨的团队,ContentProvider是仓库管理员,BroadcastReceiver是传菜员。表面上各干各的,但谁进哪个包厢、后厨团队要不要扩编、仓库数据怎么分发、传菜员什么时候待命,全部由AMS统一调度。
具体来说,AMS至少管这几摊事:组件调度,所有Activity和Service的启动、停止、绑定都由它分配;进程管理,每个App进程的优先级(adj)由它打分,低分进程在内存不足时最先被回收;任务栈管理,记录最近任务、返回栈、Activity启动模式,这决定了你的返回键到底回到了哪一页;系统级权限检查和多用户管理,虽然细节拆给了别的模块,但入口基本都在AMS。
1.2 弄懂AMS启动过程能解决什么实际问题
很多人觉得“AMS启动过程”是面试题,背背流程就够了。但我在实际调试中发现,不搞懂这一块,遇到下面这些问题基本抓瞎:
开机黑屏停在logo,桌面进程一直起不来,你只知道“系统没起来”,说不清是AMS没ready,还是launcher被卡在pid 0;system_server反复崩溃软重启,日志里只有一堆“System is rebooting”,不知道是AMS初始化失败还是后面某个服务把主线程弄死了;设备从开机到可用要两分钟,但不确定是哪一项服务拖了后腿;权限异常导致某些系统应用闪退,你可能压根没想到是AMS携带的权限数据在启动阶段就没配好。
这些问题的共同点是:现象在应用层,根因全在SystemServer那几十个服务的启动时序。AMS又是其中最核心的一个,所以把它的启动链路搞明白,排错能力会直接上一个台阶。
1.3 启动链路一句话时间线
先把AMS在整个系统启动里的位置用一张表记住,后面所有内容都是围绕这张表展开的:
| 阶段 | 干了什么 | AMS在哪出现 |
|---|---|---|
| BootROM/BootLoader | 加载引导程序,初始化基础硬件 | 还不存在 |
| Kernel | 初始化驱动、内存、调度器 | 还不存在 |
| init进程 | 解析init.rc,启动属性服务、Zygote | 不存在 |
| Zygote | 预加载Java框架类,fork出SystemServer | Zygote内存中已有framework但AMS未实例化 |
| SystemServer.main | 启动系统服务,按序构建AMS、PMS、WMS等 | AMS在此阶段创建并注册 |
| AMS.systemReady | 启动持久进程、恢复任务栈、发BOOT_COMPLETED | AMS正式进入“可用”状态 |
这张表也解释了为什么AMS启动过程值得单独研究:它不是一个“启动一下就完事”的服务,而是分阶段从实例化走到ready,链路很长,任何一步出问题都会以各种诡异现象暴露在开机过程中。
2. 从按下电源键到AMS准备就绪:完整启动链路
2.1 init、Zygote与SystemServer:一次接力赛
开机流程本质是一场接力赛。内核跑起来之后,用户空间的第一个进程是init(PID 1),它负责解析init.rc配置文件,把分区挂载好,启动关键的native服务,然后再把Zygote拉起来。
Zygote是Android Java世界的“母进程”,它的工作方式很巧妙:进程启动前先fork自己,子进程里再走Java层初始化。由于fork会完整复制父进程的内存,Zygote在启动时提前把常用Java类和资源加载进内存,这样后续每个App进程fork时就不用重新加载framework的类了,启动速度和内存开销都好看很多。
SystemServer就是Zygote fork出来的第一个进程,也是整个framework服务的“家”。它内部会创建SystemServiceManager,然后通过这个管理器逐个启动几十个系统服务。AMS就住在SystemServer里,跟着它一起出生。
2.2 Zygote如何“生”出SystemServer
在AOSP源码里,ZygoteInit的main方法会判断一个startSystemServer参数,如果为true就调用forkSystemServer,子进程拿到一个Runnable跑起来,最终进入SystemServer.main。这里有个容易忽略的细节:Zygote forkSystemServer时传进去的类名是com.android.server.SystemServer,参数里还带着“--zygote”这样的标记,目的是让SystemServer初始化时知道自己不是普通App。
SystemServer.main做的事情很简单,new一个SystemServer对象,然后调用run()。真正的工作全在run()里面:
public static void main(String[] args) { new SystemServer().run(); } private void run() { // 创建系统上下文 createSystemContext(); // 创建SystemServiceManager mSystemServiceManager = new SystemServiceManager(mSystemContext); LocalServices.addService(SystemServiceManager.class, mSystemServiceManager); // 按批次启动服务 startBootstrapServices(); startCoreServices(); startOtherServices(); }这种三段式启动顺序是刻意设计的。startBootstrapServices是“引导批”,只启动那些系统运行必需、且后续服务依赖的服务;startCoreServices是“核心批”,启动一些独立性强的基础服务;startOtherServices是“其他批”,把剩下的服务全部拉起来,并在最后统一做“systemReady”的通知。
2.3 AMS在SystemServer中的出场顺序
AMS属于“引导批”里第二顺位的服务,第一顺位是Installer服务,它负责安装和删除应用时的native层操作,必须先就位。紧接着就是AMS。
为什么AMS要这么早启动?因为后面很多服务,比如PackageManagerService(PMS)、WindowManagerService(WMS),都需要AMS帮忙处理进程、任务、权限相关的事情。PMS扫描完应用后要通知AMS更新权限和包信息,WMS也要和AMS协同管理Activity窗口。如果AMS放在最后才启动,整条链路会被卡住。
但这里有个先后关系的细节:AMS先构造,PMS后构造,两者之间还要交换数据。所以AMS的setSystemProcess阶段不会依赖PMS,而是等PMS在startBootstrapServices里跑完扫描后,再通过AMS的回调接口把权限信息补齐。这种“先建对象,后补依赖”的设计在framework里非常常见,目的就是尽量缩短启动串行时间。
3. 源码视角:AMS启动的核心代码路径
3.1 构造阶段:new ActivityManagerService里搭班子
在startBootstrapServices里看到的第一句关键代码大概是这样的:
mActivityManagerService = mSystemServiceManager.startService( ActivityManagerService.Lifecycle.class).getService(); mActivityManagerService.setSystemProcess();AMS的构造过程有不少门道。它不是一个空壳对象,而是会创建一条属于自己的消息线程mHandlerThread(前台优先级,允许IO),因为AMS要处理大量binder调用和调度消息,如果都堆在system_server主线程上,主线程一旦被某个慢操作卡住,整个系统就跟着卡死。独立线程让AMS既能保持响应,又不会拖累主线程处理DisplayManager、WMS这些同样高优先级的服务。
构造函数里还会初始化一堆内部组件:ProcessList负责进程列表和adj值更新;ActiveServices负责Service生命周期;BroadcastQueue负责广播排队;ActivityTaskManagerService负责Activity栈管理(新版Android把Activity栈逻辑基本拆到了这个模块,AMS作为入口统一调度);ActivityManagerConstants则用来读系统配置,比如最近任务数量上限、App启动超时时间等。
用生活化的说法,构造阶段就是“开张前先把店面、员工、账本、存货都准备好”,这一步没做扎实,后面的一切都是空中楼阁。
3.2 从Lifecycle到onStart:SystemServiceManager的启动机制
SystemServiceManager.startService的入参是一个Class对象,框架会通过反射创建实例,然后把服务加入内部列表,再调用onStart方法。AMS内部默认实现了Lifecycle类来做适配:
public static final class Lifecycle extends SystemService { private final ActivityManagerService mService; public Lifecycle(Context context) { super(context); mService = new ActivityManagerService(context); } @Override public void onStart() { mService.start(); } public ActivityManagerService getService() { return mService; } }这段代码最大的价值是“早期化”:真正的AMS实例在Lifecycle构造期间就已经创建完成,而不是等onStart才创建。这样后续服务在startService返回后就能立刻拿到AMS对象,不用等什么异步回调。start()里面做的事情主要是确认系统上下文、注册一些本地服务,并启动AMS内部的消息处理线程,为后续setSystemProcess和systemReady打基础。
3.3 setSystemProcess与installSystemProviders:注册与备料
setSystemProcess这个方法是AMS启动里“对外亮相”的一步,核心动作是把自己注册成系统级binder服务:
ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);这一步之后,任何App进程都可以通过ServiceManager查询到“activity”服务,再通过binder IPC调用到AMS。同时setSystemProcess还会做几件容易被忽略的事:加载系统权限数据,判断哪些权限是系统级预授权;把system_server这个进程本身录入进程管理列表,让它持有SYSTEM_ADJ的高优先级;把AMS内部的一些实现接口注册进LocalServices,方便系统内部模块没必要走binder就去直接调用。
installSystemProviders是紧接着的“备料”工作。AMS所在的system_server进程要先安装好系统级的ContentProvider,比如SettingsProvider,后面系统和应用才能通过Settings读取各种配置。AMS会调用mSystemThread.installSystemProviders,把系统包扫描出来的Provider列表逐个装好。这一步如果失败,往往表现为开机后设置无法读取、各种系统服务初始化拿不到配置值,异常日志又五花八门,很容易让人误判成数据库问题。
3.4 systemReady:万事俱备的信号
AMS从构造到setSystemProcess,只是把服务本身拉起来了,真正进入“系统可用”状态还要等systemReady。这个方法的调用发生在startOtherServices的末尾,它接收一个Runnable回调,回调内部会执行一系列“所有服务都准备好了才开始”的操作。
核心逻辑可以概括成四件事:恢复持久化数据和最近任务,启动persistent进程——也就是常驻系统进程,比如SystemUI、Telephony等;发送LOCKED_BOOT_COMPLETED和BOOT_COMPLETED广播,通知系统应用和普通应用开机完成;启动桌面应用Launcher,让用户看到可以操作的界面;通知WindowManagerService做暂停/恢复窗口的整理,结束开机动画。
我建议你在阅读时重点盯一下systemReady里“goingCallback.run()”的位置,它保证所有服务都处于可工作状态后才触发开机广播。这也是为什么有时候logcat里已经能看到BOOT_COMPLETED,桌面还是黑屏——因为Launcher的启动请求是在AMS内部另一个线程里分发的,回调顺序稍有异常,就会出现“系统广播已发出但UI还没起来”的中间态。
4. 启动过程容易被坑的地方与排查技巧
4.1 开机黑屏:先分清楚死在“哪一层”
开机黑屏是我见过最多、也最容易误判的问题。同样一个黑屏,可能是屏驱动没起来,可能是SurfaceFlinger没起来,可能是SystemServer没起来,也可能是桌面进程没起来。层与层之间表现很像,但排查路径完全不同。
我的第一板斧永远是看logcat里的SystemServer、ActivityManager、SystemUI这几个tag。如果开机动画正常播放完才黑屏,那屏幕驱动和SurfaceFlinger基本没问题,重点看AMS有没有完成systemReady,以及Launcher进程有没有被正常创建和attach。如果logcat里能看到Launcher的pid和adj,但窗口一直没绘制,就要再往ActivityTaskManager和WMS方向查。
有个容易被坑的点:开机黑屏时不一定是“异常”,合理范围内的黑屏时间不等于故障。所以排查前先抓正常设备的基线,对比AMS从构造到systemReady的耗时,再判断是不是真的回退了。
4.2 AMS启动变慢:用日志时间戳锁定位点
系统开机慢,很多人上来就怀疑AMS里某个操作太慢,但慢在哪里完全靠猜。其实可以用日志时间戳做一次粗定位。
先用过滤命令把关键日志抓下来:
adb logcat -b all -s SystemServer ActivityManager ActivityTaskManager BootAnimation重点观察几个标志性时间点:SystemServer.run()开始时间、AMS构造完成时间、systemReady调用时间、BOOT_COMPLETED发出时间。每个时间点之间如果有明显的等待间隙,再用adb shell dumpsys activity查看AMS内部消息队列的情况,看看是不是有长任务卡住了主线程。
我遇到过一种情况:AMS构造完成后,setSystemProcess阶段要等PMS扫描完整个/vendor/app目录,结果某个预装应用安装包损坏,解析APK时反复抛异常,导致AMS的systemReady迟迟没被调用。这类问题只看AMS本身的代码是看不出名堂的,必须把PMS和AMS的日志对照起来看,明白“AMS先构造、PMS后扫描、再通过回调补齐权限”的时序,才能快速定位到“等PMS”这个节点上。
4.3 权限与配置问题导致AMS异常
AMS启动阶段对权限非常敏感。setSystemProcess时它会加载系统中的权限数据,如果某个权限文件损坏或者内核SELinux策略没放行,AMS可能直接抛出SecurityException导致system_server崩溃。
还有一个容易被忽略的配置项:平台签名。系统应用签名不对,AMS在扫描时会对某些系统组件做权限校验,签名不一致会直接拒绝加载,表现是开机后部分系统应用一直“未安装”或者闪退。这种问题在自编译固件里很常见,排查时用adb shell settings get global看设备标识和签名状态,再对比构建时使用的platform密钥,基本能锁定方向。
另外内存配置也要提一下。AMS启动时会读取框架的low memory相关常量,如果设备内存很小但系统配置里还是采用了高内存阈值的参数,进程管理就会激进,导致开机后桌面被误杀,循环重启开不进去。
4.4 三板斧排查:dumpsys、ps与ANR trace
真到排查现场,我一般固定用三招,按顺序来。
第一招是adb shell dumpsys activity,看AMS全局状态。注意这里有大量输出,优先看Processes部分,确认system_server和桌面进程是否存在、adj多少、是否处于“cached”或“receiving”等状态。如果桌面进程没有出现在列表里,说明Launcher没被启动;如果出现了但处于低优先级,说明是被进程管理清掉了。
第二招是adb shell ps -A | grep system_server,确认system_server还活着、CPU占用是否异常。如果一个设备的system_server反复重启,ps会看到pid在跳动,这时再看logcat里有没有“System server died”之类的信息。
第三招是抓ANR或重启前的trace。在userdebug或root设备上执行adb shell kill -SIGQUIT <pid>,让system_server在/data/anr目录下输出当前线程栈,重点看主线程和AMS消息线程在等哪把锁。很多时候AMS卡住不是AMS自身的问题,而是其他服务持有锁一直不释放,通过栈回溯能直接看到等待链。
这三招看完,大部分AMS启动相关的问题都能缩小到某个具体模块,而不是在应用层瞎猜。
5. 一点实操心得:把AMS吃透的建议
5.1 源码阅读顺序推荐
如果准备从源码层面把AMS启动啃下来,我的路径建议是这样的:从SystemServer.java的run()入口开始,先明白服务启动的批次划分;接着读SystemServiceManager.startService,理解Lifecycle的创建机制;再进入ActivityManagerService的构造函数、start()、setSystemProcess()、installSystemProviders()、systemReady(),按这个顺序读,思路最顺。
遇到Activity栈相关逻辑不用死磕细节,Android 10以后Activity栈管理已经拆到ActivityTaskManagerService里,先把AMS的主干搞清楚,再按需要补ActivityTaskManager和ProcessList的专项知识即可。
源码版本的选择上,我建议从Android 10到Android 13之间选一个你手头设备能跑起来的版本,不要单纯追求最新。新版代码里新增了很多可配置开关和模块化拆解,主干逻辑虽然没变,但细节量对于新手来说容易淹没主线。
5.2 调试工具与设备准备
准备一台能root或者userdebug的测试设备非常重要。没有root权限,开机黑屏这种问题你很难从system_server进程层面入手。模拟器也可以,但模拟器的内核、驱动和真机差异较大,遇到SELinux策略或vendor相关的问题,模拟器上复现不了。
AOSP源码加Android Studio这个组合是我用的比较多的方案。源码可以方便地本地检索、断点调试,Android Studio用来查看模块结构和单步跟踪Java层代码。搭建环境的过程确实比较折腾,但如果你的目标是深入framework,这一步省不掉。
平时我会把这几条命令记在手机里:adb logcat -b all -s SystemServer ActivityManager ActivityTaskManager,adb shell dumpsys activity processes,adb shell ps -A | grep system_server。排查的时候直接能用,比现查快很多。
5.3 记录“启动时间线”基线的好习惯
最后分享一个帮过我好几次的习惯:每次调试系统启动相关问题之前,先抓一台正常设备的完整启动日志,按我前面说的时间节点整理成基线数据。比如从SystemServer.run()到AMS构造完成是1.2秒,从AMS构造完成到systemReady是3.8秒,从systemReady到BOOT_COMPLETED是0.6秒。之后改动任何系统服务、预装应用或权限配置,都拿这套基线做对比。
有一次设备开机从15秒回退到25秒,所有人都怀疑是某个自定义服务的问题,我对了一下基线,发现卡点根本不在自定义服务里,而是PMS扫描阶段多耗了8秒,最后查出来是某个预装APK没有对齐导致解析超时。没有基线数据,这个问题可能要浪费一个下午去猜。启动过程这种长链路,时间戳对比是最直接有效的排查手段,希望你也养成这个习惯。