Android启动流程概览:从按下电源到Launcher显示
说实话,做埋点不搞懂启动流程,就像修车不看发动机内部——早晚要翻车。我当年刚接触系统级埋点时,就因为在Boot阶段丢了一段关键日志,排查了整整三天……嗯,从那以后我养成了一个习惯:按下电源键的那一刻起,大脑就要跟着代码一起跑。
你为什么要关心这个流程?
启动埋点的核心难点在于:系统还没完全起来,埋点基础设施可能都没加载。你想想看,如果往一个还没初始化的文件系统里写日志,要么写不进去,要么进程被kernel杀掉。所以,搞清每个阶段能做什么、不能做什么,是必修课。
整个启动过程可以拆成几个关键阶段。我把它整理成下面这张图,方便你对照着看:
阶段拆解:每个环节都在干什么
| 阶段 | 主导者 | 关键动作 | 埋点约束 |
|---|---|---|---|
| 1. 按下电源 | 硬件 + Boot ROM | 加载SoC厂商的预置代码,校验签名 | 没有存储,只能通过UART输出 |
| 2. BootLoader | BootLoader(如uboot、ABL) | 初始化DDR、加载boot.img到内存 | 可以写日志到内存缓冲区,断电丢失 |
| 3. Linux Kernel | Linux内核 | 驱动加载、设备树解析、挂载根文件系统 | 可通过printk输出,但无法持久化 |
| 4. init进程 | init(pid=1) | 解析init.rc,启动核心守护进程 | 可以写文件到 /data 分区(挂载后) |
| 5. Zygote | Zygote进程 | 预加载Java framework类,监听socket | 可以调用 Android.util.Log |
| 6. SystemServer | SystemServer进程 | 启动AMS、WMS、PMS等系统服务 | 可全量使用埋点SDK(如果已集成) |
| 7. Launcher | Launcher应用 | 显示桌面图标,接收开机广播 | 标准的Android埋点环境 |
注意看,从第4阶段(init进程)开始,埋点才有了“写文件”的可能性。我之前有个项目需要在BootLoader阶段记录一次硬件自检结果,结果发现数据存在内存里,重启后就没了……后来改成通过misc分区缓存,才解决了问题。
埋点能从哪里下手?
不同阶段留“证据”的方法完全不同。我个人习惯在init.rc里加一个服务来做壳埋点,比如这样:
# 在 /system/etc/init/boot_log.rc 中添加
service boot_logger /system/bin/boot_logger
class core
user root
oneshot
# 在boot阶段就启动,记录关键节点时间戳
writepid /sys/class/.../boot_complete
然后在boot_logger的代码里往 /data/boot_trace 写文件。不过要注意 —— /data 分区在早期可能是加密的,需要等vold启动后才能正常写入。这个坑我踩过,日志全都丢在了/data/之前。后来我改用/proc/来中转,虽然不太优雅,但至少数据没丢。
一个常见的坑:init阶段的并发问题
init.rc里的服务是按类(class)顺序启动的。比如class core比class main先跑。但我遇到过一种情况:我们的埋点服务依赖了某个驱动节点,结果那个驱动还没加载完成,埋点服务就启动了,导致一直读不到数据。解决办法也很简单——用 on property:sys.boot_completed=1 来延迟启动埋点服务。嗯,这里要注意,sys.boot_completed 这个属性是SystemServer在启动完成后设置的,用它触发最保险。
总结一下本章的核心点
- 时机决定一切:清楚每个阶段能访问什么资源,埋点才能落地。
- init.rc是你最强大的武器:可以定义服务、设置属性、控制启动顺序。
- 善用/proc和/sys:在早期阶段,这两个虚拟文件系统是最可靠的通信渠道。
- 多留一条后路:如果主路径写失败,及时降级到日志缓冲区或内核日志。