1. AOSP启动流程概述:从电源键到Launcher,到底经历了什么?
做Android系统优化这么多年,我经常被问到一个问题:“手机从按下电源键到桌面出来,背后到底跑了多少东西?”
这个问题,说白了就是AOSP启动流程。今天咱们从头捋一遍,把每个关键阶段都拆开看看。我个人习惯先把全貌画出来,再慢慢抠细节——所以先上一张总览图。
整个流程,从按下电源键到桌面可见,可以拆成7个关键阶段。嗯,这里要注意:不同厂商可能会有些定制,但大框架跑不了。我当年在项目里排查启动慢的问题,就是拿着这张图一个阶段一个阶段打桩测时间。
1.1 BootROM —— 最底层的守门员
按下电源键后,CPU首先执行片上ROM里的固化代码。这部分叫BootROM,也叫一级引导(BL1)。它做的事情很纯粹:初始化最基本的硬件(时钟、电压、内存控制器),然后校验并加载下一级镜像(通常是Bootloader)。
为什么这块容易出问题?我曾经遇到一个案子:某款芯片的BootROM里加了安全校验逻辑,结果校验算法跑一次要800ms,直接拖慢了整机启动120ms。后来跟芯片厂商扯了好久才改成预计算。
1.2 Bootloader —— 说它是跳板一点不过分
Bootloader(通常叫U-boot或lk)接过控制权后,继续初始化更多外设(USB、显示、存储等),然后把内核镜像从分区读到内存里,最后跳转过去。这里有两个点值得注意:
- 预加载内核:很多平台会在Bootloader里直接把内核解压到内存,减少后续等待。
- 参数传递:通过
tag或device tree把硬件信息传给内核。
我自己习惯在Bootloader的最后一个函数里加一个高精度时间戳,方便和内核日志对齐。
// Bootloader 最后一步示例(伪代码)
printk("ABL: Jumping to kernel at 0x%x\n", kernel_addr);
timestamp_save("bootloader_exit");
jump_to_kernel(kernel_addr);
1.3 Kernel启动 —— 从汇编到C,再到init
内核启动过程大致分两步:
- 解压缩 + 汇编初始化:设置页表、MMU、中断向量。
- start_kernel():进入C世界,初始化调度器、内存管理、驱动模型等。
内核启动的最后一步是挂载根文件系统并执行 /init 进程。这个 /init 默认是用户态的 init 可执行文件(在AOSP里就是 /system/bin/init)。
1.4 init进程 —— 一切用户空间的起点
init进程是用户空间第一个进程(PID=1)。它主要干三件事:
- 解析
init.rc及各种.rc文件(定义服务、动作、触发器)。 - 启动核心守护进程:ueventd、servicemanager、logd 等。
- 最终触发
class_start core和class_start main,启动Zygote。
rc文件的解析效率很有讲究。我之前见过一个项目在 init.rc 里写了上百个 service 定义,结果解析阶段就花了500ms。后来合并了重复声明,降到120ms。你想想看,这个阶段埋点其实很简单——在 init.cpp 的 main() 函数入口和调用 LoadBootScripts() 前后加log就行了。
// init 主函数简化流程
int main(int argc, char** argv) {
// 埋点:init 入口
trace_begin("init_start");
...
LoadBootScripts(prop); // 解析 rc 文件
...
StartPropertyService(); // 启动属性服务
trace_end("init_start");
while (true) {
// 处理 action 和 service
}
}
1.5 Zygote进程 —— Android的灵魂
Zygote是init fork出来的第一个Java进程。它预加载了所有Framework层常用的类(约2000+个类)和资源(主题、字体等),然后进入孵化模式,等待socket连接。一旦有请求就fork自身,并加载对应的Activity。
为什么Zygote要预加载?说白了,这样后续App启动就不用重复加载了,直接复用进程。我做过统计,预加载阶段耗时一般在1.5s~2.5s之间,这个阶段是启动优化的重中之重。
main() 中,在 preloadClasses() 和 preloadResources() 前后加时间戳。我曾通过埋点发现某个第三方主题包导致预加载多花了800ms,后来从预加载列表里移除就正常了。
1.6 SystemServer —— 大管家的诞生
Zygote fork出SystemServer进程后,SystemServer会启动一系列核心服务。最关键的有:
| 服务名称 | 作用 | 埋点关注点 |
|---|---|---|
| ActivityManagerService (AMS) | 管理Activity栈、进程生命周期 | 启动耗时,关键通知点 |
| WindowManagerService (WMS) | 管理窗口、屏幕旋转 | 首帧绘制时间 |
| PackageManagerService (PMS) | 扫描安装包、管理权限 | 扫描耗时、APK数量影响 |
| PowerManagerService | 电源管理、唤醒锁 | 亮屏信号时机 |
这些服务启动顺序是写死的。AMS跑到某个阶段后会发出 BOOT_COMPLETED 广播,但桌面不一定在此时显示——要等WMS给出第一帧。
1.7 Launcher界面显示 —— 收官之战
最终,SystemServer启动完毕后会通过AMS启动Launcher应用(桌面)。Launcher activity走到onResume后,WMS开始合成第一帧画面。这时候用户才看到桌面图标——从按下电源键到这一刻,才算真正的“启动完成”。
我一般把“Launcher首帧绘制时间”作为启动优化核心指标。你可以在 ActivityThread.java 的 handleResumeActivity() 里埋一个点,配合 Choreographer 的回调拿到真实帧数。
好了,这一章就是全景扫描。后面我们会深入到每个阶段里,把埋点代码、工具、数据分析挨个讲透。我自己每次做新平台启动优化,都会重新画这张图——相信我,自己动手画一遍,理解深很多。