1、AOSP启动流程概述:启动阶段划分

做埋点之前,你得先知道系统是怎么活过来的。AOSP的启动就像一场接力赛,每个阶段跑完一棒,把控制权交给下一棒。我个人习惯把这六个阶段记成一句话:Bootload开机 → Kernel拉起内核 → Init启动用户态 → Zygote孵化Java世界 → SystemServer加载服务 → Launcher呈现桌面。下面我们逐个拆开看。

你想想看,从按下电源键到看到桌面,手机里到底发生了什么?这中间任何一个环节慢了,用户都能感知到「怎么还没点亮?」「怎么黑屏这么久?」。我曾经在优化开机时间时,发现某款设备的Bootloader阶段就卡了800ms,原因居然是引导分区读了个没用的日志文件。嗯,埋点就是要精确抓住这些坑。

AOSP启动阶段流程总览 BootLoader 加载引导程序 Kernel Linux内核启动 Init 用户态init进程 Zygote 孵化Java虚拟机 SystemServer 系统服务启动 Launcher 桌面就绪 💡 关键埋点时机 • 每个阶段起始/结束 • 阶段间交接等待 • 异常/超时节点 典型时间占比 Bootloader: ~5% Kernel: ~15% Init+Zygote: ~30% SystemServer: ~40% Launcher: ~10%

1. Bootloader 阶段

按下电源键,ROM里的一段固化代码开始跑。Bootloader负责初始化硬件、检测启动模式(正常启动、recovery、fastboot等),然后把内核镜像加载到内存并跳转。这个阶段通常很短,但容易踩坑——比如我遇到过某款低端机,bootloader里塞了个全量校验,导致每次开机多耗1.2秒。埋点只需要打一个时间戳(进入bootloader和离开bootloader),就能算出这一段耗时。

记录点:bootloader_start → bootloader_end。差值就是引导耗时。

2. Kernel 阶段

Linux内核接管硬件。这里做的事情很重:初始化内存管理、调度器、驱动加载(显示、存储、网络等),最后挂载根文件系统并执行第一个用户态进程——init。Kernel阶段的埋点比较特殊,内核空间不能用Java打的log,得靠printk或者tracepoint。我习惯在start_kernel函数入口和kernel_init完成处加时间戳,配合dmesg来解析。

// 内核埋点示例(伪代码)
trace_printk("KERNEL_INIT_START\n");
// ... 一系列初始化 ...
trace_printk("KERNEL_INIT_DONE\n");
用 tracepoint 比 printk 更轻量,我推荐在 /sys/kernel/debug/tracing 里动态开启。

3. Init 阶段

init进程(PID=1)启动后,它读取/init.rc脚本,按顺序执行一系列动作:挂载分区、设置属性、启动关键守护进程(如ueventd、logd、servicemanager等)。这个阶段是AOSP用户态的第一个里程碑。埋点可以打在init.rc每个section的开头和结尾,但别打在service启动里,那太细了。我的做法是用property_set("sys.init_completed", "1")当信号,然后查系统属性变化时间来算。

注意:Init阶段如果卡在某个service上(比如selinux策略加载),整个开机就会卡死。我曾经碰到一个客户,因为服务依赖循环导致init花了8秒——这就是埋点没覆盖到依赖关系。

4. Zygote 阶段

Init会启动zygote进程,它是Java世界的始祖。Zygote加载Java虚拟机,预加载常用类库和资源(比如系统字体、图标、主题),然后监听socket等待创建新应用进程。这个阶段有两个关键时间点:zygote进程开始执行Java代码(ZygoteInit.main),以及zygote完全就绪(开始监听socket)。我个人习惯在ZygoteInit.java里加埋点:

// frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
// ... 预加载逻辑 ...
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

说白了,zygote预热的时间直接决定了「冷启动」第一个应用的速度。如果你想优化开机时间,这里往往是最大的优化空间——把不必要的预加载类移走,我就这么干过。

5. SystemServer 阶段

Zygote fork出system_server进程,它是Android系统的「大管家」。这个阶段启动的Java服务多得吓人:ActivityManagerService、PackageManagerService、WindowManagerService……光核心服务就20多个,而且很多服务之间有互相依赖。埋点要细化到每个服务的onStart和systemReady回调。我建议用表格记录每个服务的耗时:

服务名称 启动顺序 典型耗时(ms) 埋点标签
Installer 1 ~80 installer_start/end
ActivityManagerService 3 ~200 ams_ready
PackageManagerService 5 ~1200 pms_scan_end
WindowManagerService 7 ~150 wms_init

对,PackageManagerService经常是耗时大户,因为它要扫描所有已安装应用的APK。我见过一台预装200个应用的设备,PMS扫描用了3秒多。

6. Launcher 阶段

SystemServer启动完成后,会发送一个ACTION_BOOT_COMPLETED广播,但用户真正能交互是在Launcher(桌面)显示出来之后。Launcher进程由zygote孵化,它加载主界面、绘制图标并开始响应触摸。这个阶段的埋点要盯住Activity.onCreate和onResume的时间差。我觉得最直观的指标是「冷启动到Launcher第一帧显示」的时长,可以用Choreographer来获取第一帧的时间。

其实整个启动过程还有一个隐藏的「暗时间」:从Launcher显示到用户能流畅滑动,这段时间往往被忽略。建议在埋点中增加「Launcher idle时间」,即第一帧后CPU空闲超过200ms的时刻。

好了,这六个阶段就是AOSP启动的主干。每个阶段的关键事件我都标出来了,你可以对照着先把手动日志打一遍。下一步我们就会深入每个阶段,讲具体的埋点API和实战技巧。先消化这张图,有问题随时问我。