Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


title: Android 系统启动耗时优化与 bootanalyze chapter: '18.6' section: '18.6' status: ready-to-publish pipeline_stage: ready-to-publish task6_state: reviewed applicable_versions: Android 10 (API 29) - Android 17 (API 37) last_verified: '2026-08-14' last_verified_against: AOSP android-17.0.0_r1 (bootanalyze / bootio / init / bootstat / ZygoteInit / SystemServer / ActivityManagerService / DexOptHelper / ArtManagerLocal); Android Common Kernel android17-6.18-2026-06_r6; current source.android.com boot guidance; current Android Developers 16 KB page-size guidance confidence: high sources:

  • type: official path: https://source.android.com/docs/core/perf/boot-times
  • type: official path: https://source.android.com/docs/core/architecture/kernel/boot-time-opt
  • type: official path: https://source.android.com/docs/core/runtime/boot-image-profiles
  • type: official path: https://developer.android.com/guide/practices/page-sizes
  • type: official path: https://perfetto.dev/docs/getting-started/system-tracing
  • type: aosp path: https://android.googlesource.com/platform/system/extras/+/refs/tags/android-17.0.0_r1/boottime_tools/bootanalyze/README.md
  • type: aosp path: https://android.googlesource.com/platform/system/extras/+/refs/tags/android-17.0.0_r1/boottime_tools/bootanalyze/bootanalyze.py
  • type: aosp path: https://android.googlesource.com/platform/system/extras/+/refs/tags/android-17.0.0_r1/boottime_tools/bootanalyze/bootanalyze.sh
  • type: aosp path: https://android.googlesource.com/platform/system/extras/+/refs/tags/android-17.0.0_r1/boottime_tools/bootanalyze/config.yaml
  • type: aosp path: https://android.googlesource.com/platform/system/extras/+/refs/tags/android-17.0.0_r1/boottime_tools/bootio/README.md
  • type: aosp path: https://android.googlesource.com/platform/system/core/+/refs/tags/android-17.0.0_r1/init/README.md
  • type: aosp path: https://android.googlesource.com/platform/system/core/+/refs/tags/android-17.0.0_r1/init/init.cpp
  • type: aosp path: https://android.googlesource.com/platform/system/core/+/refs/tags/android-17.0.0_r1/init/service.cpp
  • type: aosp path: https://android.googlesource.com/platform/system/core/+/refs/tags/android-17.0.0_r1/rootdir/init.zygote64.rc
  • type: aosp path: https://android.googlesource.com/platform/system/core/+/refs/tags/android-17.0.0_r1/rootdir/init.zygote64_32.rc
  • type: aosp path: https://android.googlesource.com/platform/system/core/+/refs/tags/android-17.0.0_r1/bootstat/bootstat.cpp
  • type: aosp path: https://android.googlesource.com/platform/system/core/+/refs/tags/android-17.0.0_r1/bootstat/bootstat.rc
  • type: aosp path: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-17.0.0_r1/core/java/com/android/internal/os/ZygoteInit.java
  • type: aosp path: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-17.0.0_r1/services/java/com/android/server/SystemServer.java
  • type: aosp path: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-17.0.0_r1/services/core/java/com/android/server/SystemServiceManager.java
  • type: aosp path: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-17.0.0_r1/services/core/java/com/android/server/am/ActivityManagerService.java
  • type: aosp path: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-17.0.0_r1/services/core/java/com/android/server/am/ProcessList.java
  • type: aosp path: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-17.0.0_r1/services/core/java/com/android/server/pm/DexOptHelper.java
  • type: aosp path: https://android.googlesource.com/platform/art/+/refs/tags/android-17.0.0_r1/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
  • type: kernel path: https://android.googlesource.com/kernel/common/+/refs/tags/android17-6.18-2026-06_r6/drivers/base/dd.c tags:
  • aosp
  • boot
  • boot-time
  • perfetto
  • performance related_chapters:
  • '1.3'
  • '8.2'
  • '14.1'
  • '18.1'

Android 系统启动耗时优化与 bootanalyze

系统启动优化研究的是设备从上电到系统可用的整条路径。它横跨 bootloader、kernel、init、APEX、Zygote、system_server、SystemUI 和 Launcher,实验范围不同于单个 App 在进程尚未创建时的 cold launch(冷启动)。

本文保留平台常用名:bootloader 是上电后校验并装载系统的引导程序,kernel 是 Linux 内核,init 是内核启动的第一个 Android 用户空间进程。Zygote 预加载框架类和资源,再派生 system_server 与应用进程;system_server 承载大部分 Java 系统服务。APEX 是可独立更新的模块化系统包。

平台源码锚点是 Android 17 / API 37 / android-17.0.0_r1,kernel 锚点是 android17-6.18-2026-06_r6。这两个固定 tag 用于复现结论,后续 tag 不会反向改变这里描述的代码。历史版本只用于解释机制演进;没有设备实测支撑的收益数字不作为结论。

先固定“启动完成”的含义

同一次启动可以有多个终点。终点选错,优化结果会在看板上变好,却没有改善用户等待。

终点观察方式能回答的问题不能替代的指标
kernel entrybootloader 日志、UART、硬件计时bootloader 已经把控制权交给 kernel上电到 kernel 的完整时间
second-stage initdmesg、init second stage startedkernel 和 first-stage init 的基线/data 挂载、Zygote、桌面可用
Zygote startro.boottime.zygote、init 日志;flag 开启时可用 ro.boottime.event.zygote-startframework 进程模型何时开始建立system_server 是否 ready
sys.boot_completed=1property、bootstatAMS 已进入 boot completion 收尾Launcher 是否已绘制且可操作
Launcher shownLauncher 自有事件、Surface/Window trace首个桌面窗口是否显示输入是否已被处理
first interactive自动化输入、画面检测、产品事件用户何时能完成第一个关键动作单纯的 property 时间

first-stage init 先完成早期挂载与 SELinux 切换所需工作,随后执行 second-stage init,由后者加载属性、解析更多 rc 并启动服务。UART 是启动早期常用的串口日志通道。

Android 17 的 ActivityManagerService.finishBooting() 会设置 sys.boot_completed,随后继续处理用户级 boot complete、用户 profile 启动和广播。这个 property 是稳定的平台边界,但它不等于“桌面已显示”,也不等于“触摸已有响应”。

这里的 AMS 是 ActivityManagerService,负责进程与 Android 用户生命周期等系统事务;property 是 Android 的系统键值属性。文中的“用户 profile”指工作资料等 Android 用户配置,不是 ART 的性能 Profile。

init 会用 ro.boottime.<service-name> 记录 service 第一次启动的 CLOCK_BOOTTIME 时间,所以主 Zygote 对应 ro.boottime.zygoteCLOCK_BOOTTIME 从内核启动起单调计时,并包含系统休眠时间。 ro.boottime.event.<event-name> 记录相应 event 的第一条 Action 命令开始执行的时间。Action 是 init 中一组 on <trigger> 条件及其 command;这个属性只有在功能开关 com.android.init.flags.enable_init_event_timestamp 开启时才生成。分析工具必须允许 event property 缺失,不能把缺值解释为 Zygote 没有启动。

实验报告应把起点和终点写进指标名。例如:

  • power_on_to_boot_completed_ms 包含 bootloader,但需要 bootloader 或外部硬件提供上电起点。
  • kernel_to_boot_completed_ms 从 kernel 时钟起算,适合 userspace 回归。
  • boot_completed_to_launcher_shown_ms 反映 property 之后的桌面尾部。
  • power_on_to_first_interaction_ms 接近用户体验,但依赖可靠的外部输入和画面判定。

冷启动样本也要分类

以下样本不能放进同一组分布:

类型额外工作建议标签
普通 cold boot完整经过 bootloader、kernel、挂载、服务和桌面启动normal
factory reset 后首启恢复出厂后的包扫描、初始化、向导和可能的 dexoptfirst_boot
OTA 后首启更新 checkpoint、APEX/分区切换、dexoptpost_ota
Boot Classpath APEX 变化后首启框架核心类路径变化触发 ART boot dexoptpost_bcp_apex
userspace reboot不经过完整 bootloader/kernel 路径userspace_reboot
加密状态或用户状态变化CE(凭据加密)与 DE(设备加密)存储的可用时点不同storage_state_*

checkpoint 是文件系统更新状态的可提交或可回退点,OTA 后首启会多出相关确认工作。

Android 17 的 DexOptHelper.performPackageDexOptUpgradeIfNeeded() 只在 first boot、device upgrade 或 Boot Classpath APEX 发生变化时调用 ArtManagerLocal.onBoot()。源码注释明确说明这个调用会阻塞,耗时可能达到 30 秒以上;普通启动会直接返回。 版本看板若不区分这些类型,P90(90% 样本不超过的值)很容易被少量升级样本主导。

一套实用的证据层级

系统启动问题适合按“统计 → 分段 → trace → 源码”逐层缩小:

  1. bootstat(持久化启动事件的系统工具)或产品指标确认回归是否稳定,观察 P50(中位数)、P90 和离群点。
  2. bootanalyze 把 logcat/dmesg 中的稳定事件转成阶段时间。
  3. bootio 找启动窗口里的进程 I/O 体量。
  4. Perfetto/ftrace 把慢阶段细分到线程调度、CPU 频率、Binder、锁、page fault、block I/O 和 ext4。
  5. 回到 init.rc、driver probe、Zygote preload 或 SystemServer trace slice 核对依赖。

dmesg 是内核日志,logcat 主要承载 Android 用户空间日志。Perfetto/ftrace 记录带时间轴的系统事件;trace slice 是带起止时间的区间事件。Binder 是 Android 的进程间通信机制,page fault 是访问尚未映射到进程页表的内存页,block I/O 是面向存储块设备的读写。

单份 trace 适合解释一次慢启动,不能单独证明版本回归。阶段统计没有原始 trace 时只能提示方向,也不足以指导 rc、kernel 或 profile 修改。

bootanalyze:它读什么,输出什么

Android 17 的工具位于:

system/extras/boottime_tools/bootanalyze/

目录中的角色如下:

文件作用
bootanalyze.py采集并解析 logcat、dmesg、timing event 和 shutdown event
config.yaml定义单点事件、带耗时事件、关机事件和时钟修正规则
bootanalyze.sh设置循环、重启方式、bootchart、Automotive 和登录流程
README.md快速说明,但有文档漂移

README 与 r1 脚本存在漂移

README.md 仍写 Python 2.7,bootanalyze.py 的 shebang(脚本首行声明的解释器)已是 #!/usr/bin/python3。README 还展示了 stop_event,r1 脚本没有读取这个字段。 脚本默认等待 BootCompleteLauncherStart 两个事件;启用额外选项时,还会等待 FsStat、CarWatchdog、LoginEndLauncherShown

这带来两个工程约束:

  • 产品定制 config.yaml 时,要保留脚本使用的事件键,或同步修改脚本里的停止事件列表。
  • Launcher 类名或日志格式发生变化时,默认 LauncherStart 正则可能一直匹配不到,采集会等到超时。

eventstimings 的含义不同

events 记录一个事件第一次出现的时间点。timings 从一条日志中提取子阶段名和耗时,正则必须提供 nametime 命名捕获组,也就是用名称标识的正则匹配结果。

下面的配置用于解析 SystemServer 的 took to complete 日志:

timings:
  system_server: 'SystemServerTiming(Async)?\s*:\s*(?P<name>\S+) took to complete:\s(?P<time>[0-9]+)ms'

匹配后,工具会把 StartPackageManagerService 一类子阶段及其毫秒值分别记录。正则要先在目标版本的原始 logcat 上回放,避免日志 tag 变化后静默丢点。

多轮采集

下面的命令使用 r1 wrapper 做十次回归采样:

ANDROID_BUILD_TOP="$PWD" \
CONFIG_YMAL="$PWD/system/extras/boottime_tools/bootanalyze/config.yaml" \
LOOPS=10 \
RESULTS_DIR="$PWD/out/boot-analyze" \
system/extras/boottime_tools/bootanalyze/bootanalyze.sh -a

CONFIG_YMAL 是源码里已经固化的拼写,不能改成 CONFIG_YAML。工具需要可执行 su 的测试设备,适合允许调试和 root 的 userdebug/eng 构建及实验室环境。 r1 wrapper 会无条件执行 touch /data/bootchart/enabled-b 只控制后续 bootchart 处理。bootchart 是按进程展示 CPU 与 I/O 活动的启动时间线;做低扰动基线时应评估开启它的影响,或直接调用 bootanalyze.py 并自行管理采集开关。

时间修正

启动早期的 logcat wall clock(日期时间)可能被校时。默认配置通过 time_correction_key: correction 匹配 Updating system time,再修正校时点之前的 logcat 时间。 dmesg 使用从内核启动起算的时间,两类时钟不能直接相减;产品新增事件时,应确认它来自哪个时钟域。

bootio:先找 I/O 责任进程

bootio 通过 kernel taskstats 接口读取进程级统计。它依赖以下 kernel 配置:

  • CONFIG_TASKSTATS=y
  • CONFIG_TASK_DELAY_ACCT=y
  • CONFIG_TASK_XACCT=y
  • CONFIG_TASK_IO_ACCOUNTING=y

它记录进程维度的启动 I/O,适合回答“谁读写得多”,不提供文件级调用链。下面的命令设置 120 秒窗口、采五次并在结束后清理控制文件:

adb shell 'echo "120 5" > /data/misc/bootio/start'
adb reboot
adb shell bootio -p
adb shell rm /data/misc/bootio/start

/data/misc/bootio/start 不会自动删除。若只看总字节数,仍无法区分顺序读、随机读、page cache(内核文件页缓存)命中或 block queue(块设备请求队列)等待;定位文件和等待原因还要接 ftrace/Perfetto。

Perfetto:解释等待发生在哪里

bootanalyze 给出阶段,Perfetto 负责解释阶段内部的并发和等待。启动 trace 至少应覆盖:

  • sched_switchsched_wakeup、CPU frequency/idle;
  • binder_driver 和主要 framework atrace category;
  • block I/O、ext4、page fault;
  • init、Zygote、SystemServer 的 atrace slice;
  • 目标 HAL、SurfaceFlinger、SystemUI 和 Launcher 的自定义 slice。

启动采集要在重启前安装 trace 配置,并确认 trace session 已经开始。普通的“设备起来后再执行 perfetto”会漏掉 kernel、first-stage init、APEX bootstrap 和 Zygote 前半段。 采集配置、buffer 大小和 data source(事件来源)也要作为实验元数据保存,因为丢事件和 trace 开销都会改变结论。

Android 17 的 SystemServer.run() 显式调用 Producer.init(..., 4 * 1024),源码注释把它定义为 4 MiB 的 Perfetto producer shared-memory buffer。 producer 是向 Perfetto 写入事件的一端;这块内存只服务于 system_server producer,不能当成整份 trace 的全局 buffer,也不能换算成“追踪精度提升多少”或“固定增加多少 CPU 开销”。

init 的串行队列与进程并发

init 的 Action 队列按 rc 文件解析顺序入队。service 定义可由 init 启动和监管的进程。队列中的 Action 依次执行,每个 Action 内的 command 也依次执行。 Android 17 init.cpp 主循环每次调用一次 ActionManager::ExecuteOneCommand(),然后回到事件循环处理 property、子进程和控制消息。

这不表示启动期间只能运行一个进程:

  • startclass_start 依次 fork/exec service,即创建子进程并载入目标程序;启动后的 service 彼此可以并发运行。
  • execexec_start 会让 Action 队列等待进程结束。
  • exec_background 启动进程后不阻塞后续 command。
  • waitwait_for_prop 和同步的文件检查会把等待留在 init 关键路径。

下面的 rc 片段展示如何把独立工作放到 service 中,再用 property 表达依赖完成:

service vendor_prepare_cache /vendor/bin/prepare_cache
    class main
    user system
    group system
    disabled
    oneshot

on post-fs-data
    start vendor_prepare_cache

on property:vendor.prepare_cache.ready=1
    start vendor_consumer

这种写法允许 vendor_prepare_cache 与其他 service 并发。安全性取决于 vendor_consumer 是否只在 ready property 之后启动,以及失败路径是否能超时、降级或阻止错误状态继续传播。

class_start 的边界

Android 17 do_class_start() 遍历 ServiceList,对属于目标 class 的 service 调用 StartIfNotDisabled()。它有三点容易写错:

  • disabled service 不随 class 自动启动。
  • 单个 service 启动失败会记日志,遍历仍会继续。
  • class 只提供分组,不表达 service 之间的依赖图。

把 service 移到更早 class 前,应核对分区挂载、SELinux domain(安全策略中的进程域)、设备节点、APEX 激活、Binder service 和 HAL 依赖。HAL 是 framework 访问硬件实现的抽象接口。仅凭“没有显式 wait”不能证明 service 可以提前启动。

event trigger 与 property trigger

on boot && property:x=y 只在 boot event 发生时检查组合条件。如果 boot 已经过去,property 随后才变成 y,这条 Action 不会补跑。只依赖 property 的 on property:x=y 会在属性变成目标值时触发;boot event 的末条 command 执行完以后,init 还会对全部 property trigger 做一次检查,执行当时已经满足条件的 Action。

持久化 property 还有额外顺序边界:当 ro.property_service.async_persist_writes=true 时,写入持久化存储的 setprop 与普通 setprop 触发顺序没有定义。修改 rc 时要用状态机或显式 property 表达依赖,不能依赖日志里一次偶然的顺序。

APEX rc 与 updatable service

Android 17 会处理 /apex/*/etc/*rc,并按 SDK 后缀选择适用的版本化 rc。标记为 updatable 的 service 如果在 APEX 激活完成前收到启动请求,init 会把启动延后;没有 updatable 标记的 service 不能由 APEX 中的定义覆盖。

启动回归不能只检查 /system/etc/init/hw/init.rc。vendor、odm、APEX 和硬件专用 rc 都可能新增 Action、service 或 property 等待。

kernel:以 android17-6.18-2026-06_r6 为准

kernel 启动耗时通常集中在镜像加载/解压、module load、driver probe、firmware、存储和设备依赖。module 是可动态加载的内核代码;driver probe 是驱动发现设备后执行初始化并绑定设备的回调。 Android 官方 kernel boot-time 指南给出的优化方向,在 6.18 锚点上仍要逐驱动验证。

选择性异步 probe

android17-6.18-2026-06_r6drivers/base/dd.c 会根据 driver 的 probe_typedriver_async_probe= 选择同步或异步 attach。Android 模块还可以通过 <module>.async_probe=1 启用异步 probe。

适合评估的对象通常有:

  • 慢速 I2C/SPI 总线设备;
  • probe 中加载 firmware 的设备;
  • 大量硬件初始化且不阻塞首屏的设备。

异步 probe 不能全量开启。consumer(依赖方)发现 supplier(提供时钟、电源等资源的一方)未就绪时,必须正确返回 -EPROBE_DEFER,让内核稍后重试。 显示、存储、clock、regulator(稳压器)、thermal(温控)等依赖若表达错误,耗时会从 module load 转成更晚的同步等待或功能故障。

官方文档给出的异步 probe 示例收益是 100–500 ms,移动非必要 module 到 second-stage init 的示例收益是 500–1000 ms。这些数字取决于硬件和 driver,只能用来说明量级,不能直接写进产品收益预期。

first-stage 与 second-stage module

正常启动的 first stage 只应保留完成根文件系统和早期启动所需的 module。recovery/fastbootd 需要的 USB、显示等 module 可以保留在 ramdisk,却不必在 normal boot 的 first stage 全部加载。

相关构建变量包括:

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD
  • BOARD_VENDOR_KERNEL_MODULES_LOAD

移动 module 后,要同时验证 normal boot、recovery、fastbootd、OTA 和 crash recovery。second-stage 后台加载还要提供 ready property 或可靠的设备节点等待,不能让 HAL 在 driver 未就绪时无限阻塞。

CPUfreq/devfreq 的启动顺序

CPUfreq 控制 CPU 频率,devfreq 用同类机制控制 DRAM、互连等设备频率。它们过晚上线,会让早期串行工作长期运行在 bootloader 留下的低频状态。 提前 probe 前要确认 clock、regulator 和 thermal supplier 已就绪。频率上升带来的功耗和温升也要纳入回归,不能只保留 boot time。

Zygote:预加载成本放在哪个阶段

Android 17 r1 的 primary 64-bit Zygote 由 init.zygote64.rc 启动,命令行没有 --enable-lazy-preload,因此在 fork system_server 前执行完整 preload。

primary Zygote 负责设备主 ABI(应用二进制接口,例如 arm64-v8a)并派生 system_server;双 Zygote 设备还会启动面向另一 ABI 的 secondary Zygote。preload 是在派生子进程前预先装入常用类、资源和本地库,让后续进程共享已经初始化的内存页。

完整 preload 包括:

  • classes 与 non-boot classloader(非 Boot Classpath 的类加载器)cache;
  • framework resources;
  • app-process HAL 和 graphics driver;
  • androidjnigraphics 等 shared library;
  • text/font cache 与 compatibility rules;
  • flag 开启时的 HttpEngine.preload()
  • WebView Zygote 准备和 JCA provider warm-up;JCA 是 Java Cryptography Architecture,provider 是其密码算法实现方。

preload 会增加 boot 阶段的 CPU、I/O 和 page fault,同时让 fork 后进程复用更多已初始化状态。删减预加载项时要同时测系统 boot、首个 App、首个 WebView、PSS(按共享页比例折算的进程内存)和连续启动。

Boot image profiles 文档把 boot classpath Profile、system_server Profile 和 Zygote 预加载类清单放在同一套设备调优流程中。 这些数据应来自真实 CUJ(critical user journey,关键用户路径)并随系统镜像发布;它们不同于应用安装时的 Baseline Profile。删类或扩充清单都应由目标设备采样与回归结果驱动。

lazy preload 不是 Android 17 新能力

--enable-lazy-preload 在较早 Android 版本已经存在。Android 17 的 64/32 配置里,init.zygote64_32.rc 仍给 32-bit secondary Zygote 传这个参数;primary Zygote 不传。

lazy 模式跳过启动期 preload,收到首次 preload 请求时由 ZygoteConnection 调用 ZygoteInit.lazyPreload(),后者仍执行同一套完整 preload()。它改变的是支付时间,不会自动把 preload 拆成增量任务。

回归时应分别记录:

  • primary Zygote 的 ZygotePreload
  • secondary Zygote 的 ZygoteInitTiming_lazy
  • 首个 32-bit 进程请求前后的延迟;
  • 双 Zygote 设备与纯 64-bit 设备的配置差异。

SystemServer:从 trace slice 看同步点

Android 17 SystemServer.run() 的主线结构是:

  1. 初始化 SystemServer 进程环境和 Perfetto producer;
  2. 启动 SystemServerInitThreadPool
  3. SystemConfig::getInstance 尽早提交到线程池;
  4. 加载 android_servers
  5. 创建 system context 和 SystemServiceManager
  6. 依次进入 bootstrap、core、other、APEX service 分组;
  7. 进入各个 boot phase,直到 AMS 完成 boot。

主线程上的 startService() 调用顺序仍然重要,但某些 service 会把工作提交到线程池。判断某个 slice 是否阻塞关键路径时,要同时看主线程是否等待 future(异步任务的结果句柄)、Binder reply、锁或 property,不能按 slice 宽度直接推断全部为 CPU 执行。

SystemConfig 早期并发

r1 无条件调用 startSystemConfigInit(),方法把 SystemConfig::getInstance 提交给 SystemServerInitThreadPool。后续 consumer 第一次取 SystemConfig 时,如果后台工作尚未完成,仍可能在那里等待。

优化方向应围绕“提交是否足够早、consumer 在哪里 join、配置扫描是否变重”展开。源码仅证明并发结构,不能推出固定的毫秒收益。

ART Mainline 的早期初始化

startBootstrapServices() 很早就调用 ArtModuleServiceInitializer.setArtModuleServiceManager(...)。class linking 会解析类及其依赖,GC(garbage collection)负责回收 Java 堆内存。 源码注释说明 service-art.jar 的 class linking 和 GC 互斥;把首次引用放在 PackageManager 大量分配之前,可避开后面的 GC 竞争。

这段初始化和特殊启动中的 boot dexopt 要分开:

  • ArtModuleServiceInitializer 是早期注册与 class-linking 时点。
  • UpdatePackagesIfNeeded 在 later startOtherServices 中运行。
  • first boot、OTA 或 Boot Classpath APEX 变化时,ArtManagerLocal.onBoot() 才执行阻塞式包 dexopt。
  • 普通 cold boot 不走这轮 boot dexopt。

APEX system service 必须位于分组末尾

startApexServices() 遍历 ApexManager.getApexSystemServices(),启动 APEX 声明的 system service,然后调用 SystemServiceManager.sealStartedServices(),禁止后续再注册新的 system service。 源码注释要求 APEX service 是启动 service 的末组,避免 platform service 反向依赖可独立更新的 APEX service。

这是一条架构约束,不是“APEX 并行挂载带来固定收益”的证据。bootanalyze 默认配置只提供 apexd_activatedapexd_bootstrapping_doneapexd_ready 三个 apexd 日志事件;挂载 namespace(进程可见的挂载视图)、单个 APEX 校验和单个 service 初始化仍要靠更细的 trace 或自定义日志。

Boot phase 不是任务并行模型

Android 17 的 SystemServer 会触发 PHASE_WAIT_FOR_DEFAULT_DISPLAYPHASE_WAIT_FOR_SENSOR_SERVICEPHASE_SYSTEM_SERVICES_READYPHASE_ACTIVITY_MANAGER_READYPHASE_THIRD_PARTY_APPS_CAN_START 等阶段。 SystemServiceManager.startBootPhase() 按已启动 service 调用 onBootPhase();阶段号是通知各服务进入某个生命周期边界的编号,不是自动并行的任务调度器。

平台新增 service 时,应记录:

  • constructor/start 的 trace slice;
  • 各 boot phase callback;
  • 首次 Binder 发布和 ready 事件;
  • 失败时的降级路径;
  • 是否阻塞默认显示、PMS、AMS、SystemUI 或 Launcher。

finishBooting() 内还有哪些工作

Android 17 的 AMS 只有在 boot animation 完成后才继续 finishBooting();若动画尚未完成,会记录待处理状态并返回。进入收尾后,顺序包含:

  1. 通知 Zygote 与 VMRuntime boot completed;
  2. 提交存储 checkpoint,失败时请求重启;
  3. 触发 PHASE_BOOT_COMPLETED
  4. 启动此前 hold 的进程;
  5. 设置 sys.boot_completed=1dev.bootcomplete=1
  6. 向 lmkd 发送 LMK_START_MONITORING
  7. 进入用户级 boot completion,随后调度用户 profile 启动。

lmkd 是 low memory killer daemon,在内存压力下协助选择并结束进程;PSI 是内核提供的资源压力指标。

因此,以下说法都不够严谨:

  • “boot animation 停止就是 sys.boot_completed”:两者有顺序关系,还要看 AMS 收尾。
  • “property 置 1 后没有启动工作”:用户回调、广播和应用进程仍可能继续占用 CPU/I/O。
  • “LMKD PSI 在 Android 17 全程关闭”:framework 只证明 boot complete 后显式发送 start-monitoring 命令,早期是否已经监控还受 lmkd 配置影响。

bootstat:适合版本看板的持久化事件

bootstat 把事件名和相对时间持久化。这里的相对时间通常是 uptime,即从本次系统启动起经过的时间。Android 17 的 bootstat.rc 在第一次 sys.boot_completed=1 时执行:

  • --record_boot_complete
  • --record_boot_reason
  • --record_time_since_factory_reset
  • -l

RecordBootComplete() 还会收集一组明确列出的 ro.boottime.init.*ro.boottime.event.* 字段,以及 ro.boot.boottime 中的 bootloader 分段。 event 字段只有在 init 的 event timestamp flag 开启并成功写入 property 时才有值。bootloader 没有提供 ro.boot.boottime 时,bootstat 无法补出上电到 kernel 的缺失时间。

下面的命令用于记录自定义事件并检查本机事件:

adb shell bootstat -r vendor_display_ready
adb shell bootstat -p
adb shell bootstat -l

-r 记录执行命令时的系统 uptime。r1 的 -l 实现会把已支持的事件映射到 bootstats atoms;atom 是 statsd 使用的结构化事件类型。README 中“EventLog/Tron histogram”的描述已经落后于当前实现。 自定义事件仍可持久化和打印,但没有 kBootEventToAtomInfo 映射时不会作为受支持 atom 写出。

I/O、page fault 与文件布局

启动 I/O 要分三层:

层级典型现象证据调整方向
文件APK/JAR/APEX/配置被过早扫描file access、bootio、page cache延迟非关键读取、减少重复扫描
block/fsqueue 等待、读放大、verity、fsckblock/ext4/f2fs traceI/O scheduler、read ahead、文件布局
memory mappingmajor fault、mmap 抖动、解压page fault + sched + file map热点布局、预加载清单、压缩策略

读放大指底层读取量超过上层真正需要的数据量;verity 是对只读分区做完整性校验的机制;fsck 是文件系统一致性检查。mmap 把文件映射进进程地址空间,major fault 则需要从存储读取缺失页。

“减少读取字节”也可能把成本推到 Launcher 显示之后。评估时至少保留两个窗口:

  • kernel entry 到 sys.boot_completed
  • sys.boot_completed 到 first interactive。

OTA 后 checkpoint、pre-reboot dexopt artifact 提交、APEX 切换和首次包扫描要单独标记。pre-reboot dexopt artifact 是重启前为更新后的系统预先生成、待更新生效时提交的 ART 编译产物。它们属于升级成本,不能用普通 cold boot 的目标去裁剪。

16 KB page size 是设备级基线变化

page size 是内核管理虚拟内存和文件映射的基本页粒度。Android Developers 的 16 KB page size 文档给出一组初始测试:system boot time 平均改善 8%,约 950 ms,同时说明不同设备结果会变化。这个数字是官方测试样本,不是 Android 17 所有设备的保证。

比较 4 KB 与 16 KB 时,page table、mmap、page fault、ELF 对齐、文件系统和设备内存都发生了变化。应把 page size 写入样本维度,并使用同一硬件、同一 build 配置和同一启动类型做 A/B。不能把跨设备差异归到某个 SystemServer service。

从慢阶段回到处理动作

慢阶段优先确认常见处理
bootloader镜像读取、解压、UART、boot reason减少日志、选择合适压缩、拆分硬件阶段
kernel / coldbootmodule load、probe、firmware、supplier移动非必要 module、选择性 async probe
first-stage init存储、dm-verity、first-stage module缩小 ramdisk 关键集合
second-stage initexec、property wait、service class后台 service、显式 ready、修正依赖
Zygoteclasses/resources/library preload调整清单并回归首个 App 与共享内存
PMS / ART包扫描、first boot/OTA dexopt分类启动类型、检查 compiler filter 与 artifact
SystemServerservice start、boot phase、Binder/锁trace 到具体 callback 和等待对象
SystemUI / Launcher进程创建、首帧、资源竞争单独测 shown 与 first interactive

每次改动只回答一个假设。例如,“I2C touch probe 阻塞 module load”要用 probe 时间证明;改成 async 后再验证触摸在 Launcher 首帧前 ready、recovery 可用、没有 defer storm。 defer storm 指大量 probe 反复返回 -EPROBE_DEFER 并重试,消耗启动期 CPU 和时间。把多项 kernel、rc 和 framework 调整合在一个版本里,会让收益与回归都无法归因。

实验与回归门禁

一组可复现的 boot 实验至少记录:

  • build fingerprint、AOSP tag、kernel tag、vendor image;
  • page size、文件系统、存储型号和加密状态;
  • normal/first boot/post-OTA/userspace reboot;
  • 电池、充电、温度和关机静置时间;
  • bootchart、Perfetto、UART 等采集开关;
  • sys.boot_completed、Launcher shown、first interactive;
  • P50、P90、样本数和剔除规则。

安全与稳定性门禁包含:

  • verified boot、SELinux、KeyMint/Gatekeeper 和 checkpoint 顺序不变;
  • recovery、fastbootd、OTA、加密解锁都能完成;
  • display、touch、radio、audio 等产品关键硬件按场景 ready;
  • service 失败有超时和降级,不制造无限 property wait;
  • boot 变快后,首屏 jank(掉帧或卡顿)、首个 App、功耗和内存没有回归。

KeyMint 管理硬件支持的密钥与密码操作,Gatekeeper 负责锁屏凭据验证;两者都不能为了缩短首屏时间随意后移。

关闭日志和 trace 可以减少测试机上的开销,但量产配置与可观测性要分别评估。没有可回放证据的优化,一旦在后续版本回归,定位成本通常高于省下的少量启动时间。

与其他章节的边界

参考资料