
手机刚开机,桌面出现后点开一个从未运行过的 App。开发者熟悉的 Application.onCreate(),其实已经是这段故事的后半程:在它之前,系统得完成启动,找到桌面,确定目标 Activity,再为 App 准备进程。
一句话结论:开机启动解决"Android 能不能运行应用",应用冷启动解决"这次点击由哪个进程、哪个 Activity 接住",两段流程在 Launcher 处接上,最终以应用自己的首帧显示告一段落。
本文参考了 liu_xi_xin 于 2021 年 7 月 26 日发表的《Android系统启动流程总结概要》。原文从 Bootloader 写到 Launcher;这里沿着同一问题继续追到应用首帧,并按较新的 AOSP 职责重新组织。源码链接以 AOSP android17-release 分支为阅读入口;Bootloader、内核与厂商服务的细节会因设备而异。文中的流程图表示关键责任和先后关系,省略了失败重试、权限分支、多用户、预启动进程等路径。
1. 先把两段启动接起来
下面这张图回答"谁把谁带起来"。箭头表示关键启动或调度关系,不代表所有节点都在同一个进程里。
#mermaid-svg-h8XnoybT6ZtLyEBt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-h8XnoybT6ZtLyEBt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-h8XnoybT6ZtLyEBt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-h8XnoybT6ZtLyEBt .error-icon{fill:#552222;}#mermaid-svg-h8XnoybT6ZtLyEBt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-h8XnoybT6ZtLyEBt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-h8XnoybT6ZtLyEBt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-h8XnoybT6ZtLyEBt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-h8XnoybT6ZtLyEBt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-h8XnoybT6ZtLyEBt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-h8XnoybT6ZtLyEBt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-h8XnoybT6ZtLyEBt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-h8XnoybT6ZtLyEBt .marker.cross{stroke:#333333;}#mermaid-svg-h8XnoybT6ZtLyEBt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-h8XnoybT6ZtLyEBt p{margin:0;}#mermaid-svg-h8XnoybT6ZtLyEBt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-h8XnoybT6ZtLyEBt .cluster-label text{fill:#333;}#mermaid-svg-h8XnoybT6ZtLyEBt .cluster-label span{color:#333;}#mermaid-svg-h8XnoybT6ZtLyEBt .cluster-label span p{background-color:transparent;}#mermaid-svg-h8XnoybT6ZtLyEBt .label text,#mermaid-svg-h8XnoybT6ZtLyEBt span{fill:#333;color:#333;}#mermaid-svg-h8XnoybT6ZtLyEBt .node rect,#mermaid-svg-h8XnoybT6ZtLyEBt .node circle,#mermaid-svg-h8XnoybT6ZtLyEBt .node ellipse,#mermaid-svg-h8XnoybT6ZtLyEBt .node polygon,#mermaid-svg-h8XnoybT6ZtLyEBt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-h8XnoybT6ZtLyEBt .rough-node .label text,#mermaid-svg-h8XnoybT6ZtLyEBt .node .label text,#mermaid-svg-h8XnoybT6ZtLyEBt .image-shape .label,#mermaid-svg-h8XnoybT6ZtLyEBt .icon-shape .label{text-anchor:middle;}#mermaid-svg-h8XnoybT6ZtLyEBt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-h8XnoybT6ZtLyEBt .rough-node .label,#mermaid-svg-h8XnoybT6ZtLyEBt .node .label,#mermaid-svg-h8XnoybT6ZtLyEBt .image-shape .label,#mermaid-svg-h8XnoybT6ZtLyEBt .icon-shape .label{text-align:center;}#mermaid-svg-h8XnoybT6ZtLyEBt .node.clickable{cursor:pointer;}#mermaid-svg-h8XnoybT6ZtLyEBt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-h8XnoybT6ZtLyEBt .arrowheadPath{fill:#333333;}#mermaid-svg-h8XnoybT6ZtLyEBt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-h8XnoybT6ZtLyEBt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-h8XnoybT6ZtLyEBt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h8XnoybT6ZtLyEBt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-h8XnoybT6ZtLyEBt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h8XnoybT6ZtLyEBt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-h8XnoybT6ZtLyEBt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-h8XnoybT6ZtLyEBt .cluster text{fill:#333;}#mermaid-svg-h8XnoybT6ZtLyEBt .cluster span{color:#333;}#mermaid-svg-h8XnoybT6ZtLyEBt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-h8XnoybT6ZtLyEBt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-h8XnoybT6ZtLyEBt rect.text{fill:none;stroke-width:0;}#mermaid-svg-h8XnoybT6ZtLyEBt .icon-shape,#mermaid-svg-h8XnoybT6ZtLyEBt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h8XnoybT6ZtLyEBt .icon-shape p,#mermaid-svg-h8XnoybT6ZtLyEBt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-h8XnoybT6ZtLyEBt .icon-shape .label rect,#mermaid-svg-h8XnoybT6ZtLyEBt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h8XnoybT6ZtLyEBt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-h8XnoybT6ZtLyEBt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-h8XnoybT6ZtLyEBt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Bootloader
Linux 内核
init:用户空间入口
servicemanager:Binder 服务目录
Zygote:应用进程孵化器
其他 native 服务
system_server
AMS、ATMS、PMS、WMS 等系统服务
启动 HOME Activity
Launcher 桌面
点击图标:请求启动 Activity
系统决定任务与目标进程
App 进程与首个 Activity
应用首帧
这里有两个容易被总览图掩盖的事实。第一,servicemanager 和 Zygote 都由 init 的服务配置启动,前者不是 Zygote 的子步骤。第二,system_server 管理许多 Java 系统服务,但不能把所有名为"Service"的组件都画成它的子对象;例如有些硬件相关服务运行在独立的 native 进程。图中的"系统服务"是职责集合,不是一个进程列表。init 启动说明 · servicemanager 源码目录
接下来分两次看这张图:先看到 Launcher 为什么能出现,再看点击图标后发生了什么。
2. 开机:从 Bootloader 到 init
Bootloader 和内核分别交付什么
Bootloader 是设备厂商实现的启动程序。它完成必要的硬件初始化、启动镜像校验与装载,把执行权交给 Linux 内核。这里不把它细化为一套所有手机通用的函数调用链,因为启动分区、Verified Boot 和设备树的处理依赖设备实现。Android Bootloader 官方概览
内核启动后准备内存管理、调度、驱动和根文件系统等基础能力,然后运行 Android 用户空间的入口 init。参考文章中"内核寻找 init.rc"的表述适合记忆阶段,却容易让人以为 rc 文件由内核解析:解析 rc 配置并启动 Android 服务的是 init,不是内核。
init 为什么不只是读一个 init.rc
现代 AOSP 将早期 init 分为 first stage、SELinux setup、second stage。第一阶段先准备必要的挂载和设备节点,使后续系统代码可用;SELinux 阶段装载安全策略;第二阶段再进入 rc 动作和服务管理。各设备还会引入自己的 rc 文件,所以"系统只有一个 init.rc"也不是准确的源码阅读模型。AOSP init README
rc 文件主要描述两类东西:何时执行的动作,以及如何启动的服务。读到 service zygote ... 时,意思是让 init 管理这个进程及其重启行为,不是 Java 世界里某个 Service 组件。以 64 位配置为例,init.zygote64.rc 指向 app_process64,并带有 --zygote、--start-system-server 等参数。init.zygote64.rc
有了 init,Android 才能把相互独立的基础进程逐步带起来。下面最值得分清的是 servicemanager 与 Zygote:一个帮助进程找到服务 ,一个帮助系统创建进程。
3. servicemanager、Zygote 与 system_server
servicemanager 管什么
Framework Binder 的 servicemanager 充当服务名到 Binder 对象的查询入口。服务把自己注册进去,客户端按名字取得句柄,再通过 Binder 调用真正的服务实现。它不负责创建 App 进程,也不会执行 Activity 生命周期。当前源码入口在 frameworks/native/cmds/servicemanager/main.cpp;其中初始化 Binder、成为 context manager、进入线程池的结构,比旧文章里单独记 binder_open()、binder_loop() 更适合作为现代源码定位点。servicemanager/main.cpp
这里说的是 Framework Binder 的服务管理器。Android 还有面向其他 Binder 域的服务管理器,本文不展开。这个边界能避免把"所有系统服务都注册到同一个目录"当成规则。
Zygote 怎样把 Java 世界准备好
init 启动 app_process 后,native 入口创建 Android Runtime,准备虚拟机和 JNI 环境,随后进入 ZygoteInit.main()。Zygote 预加载常用类和资源,创建接收进程启动请求的 socket,并创建 system_server。以后需要普通应用进程时,系统进程通过这条通道请求 Zygote;是否走 USAP 等优化路径取决于设备与系统配置,不能把每次启动都简化为同一条直接 fork() 调用。app_main.cpp · ZygoteInit.java
为什么不直接让 system_server 自己创建 App 进程?关键不在类名,而在进程边界:Zygote 维护适合派生进程的预初始化运行时;system_server 负责决策并发出请求。新 App 进程和 system_server 都来自 Zygote 这一侧,App 不是 system_server 的子进程。
system_server 怎样让桌面出现
Zygote 创建 system_server 后,它的 Java 入口是 SystemServer.main()。SystemServer 建立系统上下文与 SystemServiceManager,再按启动阶段初始化大量服务。对本文重要的几个名字是:PackageManagerService(包与组件信息)、ActivityManagerService(进程等管理)、ActivityTaskManagerService(Activity 与任务)以及 WindowManagerService(窗口管理)。它们相互协作,不能用一句"AMS 启动一切"替代。SystemServer.java
如果准备跟源码,先抓住几个入口,再沿方法往下走;表中只列主干,不是可直接执行的代码:
| 要追的问题 | 从哪里开始看 | 读到什么为止 |
|---|---|---|
init 怎样管理 Zygote |
init.zygote64.rc 的 service zygote |
app_process 被启动 |
| Zygote 怎样进入 Java 层 | app_main.cpp 的 main()、AndroidRuntime.start() |
ZygoteInit.main() |
system_server 从哪里来 |
ZygoteInit.forkSystemServer() |
SystemServer.main() |
| 系统服务怎样启动 | SystemServer.run() |
startBootstrapServices()、startCoreServices()、startOtherServices() 等阶段 |
系统准备好后,会启动 HOME 类型的 Activity;常见手机上它是 Launcher,首次设置流程或厂商桌面则可能改变实际组件。AOSP 中可以从 ActivityManagerService.systemReady() 一侧追到启动 Home 的请求,再看 ATMS 如何选出目标。Launcher 出现在屏幕上,才给用户提供了接下来的"点图标"入口。ActivityManagerService.java
到这里,系统开机 这段链路已经完成了本文需要的准备;此后点开一个 App,并不会从 Bootloader、init 再走一遍。
4. 点击图标:系统怎样决定启动哪个 Activity
桌面图标一般指向带有 MAIN action 与 LAUNCHER category 的入口 Activity。Launcher3 的点击路径会准备启动 Intent,并调用启动 Activity 的接口;它负责发起请求,不负责在自己的进程里构造目标 App 的 Activity 对象。Launcher3 Launcher.java
这次 Binder 请求进入系统侧后,大致有三道决策:目标是谁、放进哪个任务、目标进程是否可用 。ActivityTaskManagerService 与 ActivityStarter 处理 Activity/Task 侧的规则,包括 Intent 解析结果、启动标志、已有任务和启动限制;需要创建进程时,再交由 AMS/ProcessList 一侧处理。旧资料把这一整段统称 AMS,理解历史文章时可以这么读,定位新源码则要分开。ActivityTaskManagerService.java · ActivityStarter.java
"点击图标一定新建 Activity"并不成立:已有任务和启动模式可能让系统复用实例或把已有任务带到前台。本文选择目标进程不存在、需要创建首个 Activity这条冷启动路径,才能把 Zygote 和应用初始化串起来。
5. 冷启动:从进程创建到 ActivityThread
先看一次请求跨越了哪些进程。图中 bindApplication 和 Activity 启动事务只表示主干;系统实际还要处理进程状态、配置、失败回滚等工作。
用户 窗口与图形系统 App 进程 Zygote AMS 与 ProcessList ATMS Launcher 用户 窗口与图形系统 App 进程 Zygote AMS 与 ProcessList ATMS Launcher #mermaid-svg-09ahCQkSFZMMOw85{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-09ahCQkSFZMMOw85 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-09ahCQkSFZMMOw85 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-09ahCQkSFZMMOw85 .error-icon{fill:#552222;}#mermaid-svg-09ahCQkSFZMMOw85 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-09ahCQkSFZMMOw85 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-09ahCQkSFZMMOw85 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-09ahCQkSFZMMOw85 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-09ahCQkSFZMMOw85 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-09ahCQkSFZMMOw85 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-09ahCQkSFZMMOw85 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-09ahCQkSFZMMOw85 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-09ahCQkSFZMMOw85 .marker.cross{stroke:#333333;}#mermaid-svg-09ahCQkSFZMMOw85 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-09ahCQkSFZMMOw85 p{margin:0;}#mermaid-svg-09ahCQkSFZMMOw85 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-09ahCQkSFZMMOw85 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-09ahCQkSFZMMOw85 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-09ahCQkSFZMMOw85 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-09ahCQkSFZMMOw85 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-09ahCQkSFZMMOw85 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-09ahCQkSFZMMOw85 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-09ahCQkSFZMMOw85 .sequenceNumber{fill:white;}#mermaid-svg-09ahCQkSFZMMOw85 #sequencenumber{fill:#333;}#mermaid-svg-09ahCQkSFZMMOw85 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-09ahCQkSFZMMOw85 .messageText{fill:#333;stroke:none;}#mermaid-svg-09ahCQkSFZMMOw85 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-09ahCQkSFZMMOw85 .labelText,#mermaid-svg-09ahCQkSFZMMOw85 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-09ahCQkSFZMMOw85 .loopText,#mermaid-svg-09ahCQkSFZMMOw85 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-09ahCQkSFZMMOw85 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-09ahCQkSFZMMOw85 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-09ahCQkSFZMMOw85 .noteText,#mermaid-svg-09ahCQkSFZMMOw85 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-09ahCQkSFZMMOw85 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-09ahCQkSFZMMOw85 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-09ahCQkSFZMMOw85 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-09ahCQkSFZMMOw85 .actorPopupMenu{position:absolute;}#mermaid-svg-09ahCQkSFZMMOw85 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-09ahCQkSFZMMOw85 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-09ahCQkSFZMMOw85 .actor-man circle,#mermaid-svg-09ahCQkSFZMMOw85 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-09ahCQkSFZMMOw85 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 请求启动入口 Activity 1 选择 Activity 与 Task 2 目标进程不存在 3 请求创建应用进程 4 新进程进入运行时入口 5 ActivityThread 向 AMS 调用 attachApplication 6 bindApplication 7 创建 Application 与 Provider 8 Application.onCreate 9 发送 Activity 启动事务 10 创建并恢复 Activity 11 应用窗口提交首帧 12 显示应用画面 13
最后两步画的是显示路径:App 不会把图像发送给 Launcher,画面还要经过窗口、图形缓冲与合成系统。用一张图表达主线时,尤其要守住这个边界。
ProcessList 负责组织进程启动参数,通过 Process.start() 等路径向 Zygote 发起请求;Zygote 派生出的子进程最终进入 ActivityThread.main()。这个名字看似一个普通 Thread,实际是应用主线程的 framework 入口:它准备主线程 Looper,创建 ActivityThread,向 AMS 报到,然后进入消息循环。它并不等于某个 Activity,也不是开发者自己创建的线程。ProcessList.java · ActivityThread.java
App 进程向系统报到后,系统通过 bindApplication 交付进程名、应用信息、Provider 信息等绑定数据。至此才进入开发者最熟悉的一段:应用类和组件开始构造。
6. Application、Provider、Activity:回调到底怎么排
正常的单进程冷启动里,顺序可记为:
#mermaid-svg-KnuD0OM0lk60BLtM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KnuD0OM0lk60BLtM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KnuD0OM0lk60BLtM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KnuD0OM0lk60BLtM .error-icon{fill:#552222;}#mermaid-svg-KnuD0OM0lk60BLtM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KnuD0OM0lk60BLtM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KnuD0OM0lk60BLtM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KnuD0OM0lk60BLtM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KnuD0OM0lk60BLtM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KnuD0OM0lk60BLtM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KnuD0OM0lk60BLtM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KnuD0OM0lk60BLtM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KnuD0OM0lk60BLtM .marker.cross{stroke:#333333;}#mermaid-svg-KnuD0OM0lk60BLtM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KnuD0OM0lk60BLtM p{margin:0;}#mermaid-svg-KnuD0OM0lk60BLtM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KnuD0OM0lk60BLtM .cluster-label text{fill:#333;}#mermaid-svg-KnuD0OM0lk60BLtM .cluster-label span{color:#333;}#mermaid-svg-KnuD0OM0lk60BLtM .cluster-label span p{background-color:transparent;}#mermaid-svg-KnuD0OM0lk60BLtM .label text,#mermaid-svg-KnuD0OM0lk60BLtM span{fill:#333;color:#333;}#mermaid-svg-KnuD0OM0lk60BLtM .node rect,#mermaid-svg-KnuD0OM0lk60BLtM .node circle,#mermaid-svg-KnuD0OM0lk60BLtM .node ellipse,#mermaid-svg-KnuD0OM0lk60BLtM .node polygon,#mermaid-svg-KnuD0OM0lk60BLtM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KnuD0OM0lk60BLtM .rough-node .label text,#mermaid-svg-KnuD0OM0lk60BLtM .node .label text,#mermaid-svg-KnuD0OM0lk60BLtM .image-shape .label,#mermaid-svg-KnuD0OM0lk60BLtM .icon-shape .label{text-anchor:middle;}#mermaid-svg-KnuD0OM0lk60BLtM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KnuD0OM0lk60BLtM .rough-node .label,#mermaid-svg-KnuD0OM0lk60BLtM .node .label,#mermaid-svg-KnuD0OM0lk60BLtM .image-shape .label,#mermaid-svg-KnuD0OM0lk60BLtM .icon-shape .label{text-align:center;}#mermaid-svg-KnuD0OM0lk60BLtM .node.clickable{cursor:pointer;}#mermaid-svg-KnuD0OM0lk60BLtM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KnuD0OM0lk60BLtM .arrowheadPath{fill:#333333;}#mermaid-svg-KnuD0OM0lk60BLtM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KnuD0OM0lk60BLtM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KnuD0OM0lk60BLtM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KnuD0OM0lk60BLtM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KnuD0OM0lk60BLtM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KnuD0OM0lk60BLtM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KnuD0OM0lk60BLtM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KnuD0OM0lk60BLtM .cluster text{fill:#333;}#mermaid-svg-KnuD0OM0lk60BLtM .cluster span{color:#333;}#mermaid-svg-KnuD0OM0lk60BLtM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KnuD0OM0lk60BLtM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KnuD0OM0lk60BLtM rect.text{fill:none;stroke-width:0;}#mermaid-svg-KnuD0OM0lk60BLtM .icon-shape,#mermaid-svg-KnuD0OM0lk60BLtM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KnuD0OM0lk60BLtM .icon-shape p,#mermaid-svg-KnuD0OM0lk60BLtM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KnuD0OM0lk60BLtM .icon-shape .label rect,#mermaid-svg-KnuD0OM0lk60BLtM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KnuD0OM0lk60BLtM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KnuD0OM0lk60BLtM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KnuD0OM0lk60BLtM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ActivityThread.main
处理 bindApplication
构造 Application 并 attachBaseContext
安装本进程 Provider 并回调 onCreate
Application.onCreate
处理 Activity 启动事务
Activity.onCreate、onStart、onResume
这里最容易答反的是 Provider 与 Application。Application 对象已经创建并完成 attachBaseContext(),系统才安装本进程需要的 ContentProvider;Provider 的 onCreate() 通常先于 Application.onCreate()。所以 Provider 初始化代码若依赖 Application.onCreate() 中设置的全局状态,就可能在冷启动时出错。没有声明相应 Provider、Provider 放在别的进程,或特殊的备份/测试场景,都需要另看实际路径。ActivityThread.handleBindApplication
首个 Activity 的创建由系统发来的 client transaction 驱动。阅读新源码可从 LaunchActivityItem.execute() 追到 ActivityThread.handleLaunchActivity(),再看实例化、attach 和 Instrumentation 如何触发生命周期。这里的 onCreate()、onStart()、onResume() 是应用组件回调 ;它们不会替代前面的进程创建和绑定。LaunchActivityItem.java
把时间线停在 onResume() 仍然太早:Activity 可以已经恢复,但应用内容还没画到屏幕上。
7. 从窗口到首帧:用户什么时候真正看见应用
Activity 构建界面后,窗口需要附着到窗口管理系统。ViewRootImpl 持有 View 树与窗口之间的重要连接,安排 traversal,再完成测量、布局与绘制;渲染结果经过图形缓冲与系统合成后,用户才看到应用画面。若页面在 onCreate() 主线程里做了大量 I/O,或者首次布局绘制很重,生命周期日志看起来"启动完成",首帧仍会晚。ViewRootImpl.java
Android 12 及以后常见的系统启动画面也要单独看。它能让用户尽早得到视觉反馈,但显示启动画面不等于应用自己的首帧已经画完。讨论启动速度时,至少区分两个时间点:
| 时间点 | 说明 | 常见观察方式 |
|---|---|---|
| TTID,首次显示 | 应用窗口首次绘制完成,覆盖进程、组件与初次 UI 工作 | Logcat 中的 Displayed、启动指标 |
| TTFD,完全显示 | 应用报告关键内容已可用,通常包含异步加载 | 适时调用 reportFullyDrawn() |
TTFD 不是系统自动猜出的"用户可用"时间;应用要在真正准备好后报告。过早调用 reportFullyDrawn() 会让这个数字失去诊断价值。官方文档同时解释了 Displayed 日志、TTID 与 TTFD 的测量边界。Android 官方启动耗时说明
8. 沿链路查慢点,而不是只盯 Application.onCreate()
开机耗时与一次 App 冷启动耗时是两个不同问题,先确认当前排查的是哪一个。应用启动又分冷、温、热三种:冷启动需要应用进程;温启动可能复用进程但重建 Activity;热启动主要把现有 Activity 带回前台。实际路径还受任务状态和进程存活影响,不能只凭"第二次点击"给它分类。Android 官方启动类型说明
排查一次明确的冷启动,可以按下面的责任表找证据:
| 看到的现象 | 优先检查的阶段 | 下一步证据 |
|---|---|---|
| 点击后很久才有 App 进程 | Activity/任务决策与进程创建 | system_server、Zygote 相关 trace slice |
| 进程已出现,Activity 还没创建 | 进程绑定、Provider、Application |
主线程 trace、Provider 与 Application 初始化 |
onResume() 已到,页面仍空白 |
窗口附着、布局、绘制 | ViewRootImpl/Choreographer 与首帧 trace |
| 首帧很快,但内容迟迟不可用 | 异步数据与实际页面完成时间 | TTFD、reportFullyDrawn() 的调用时机 |
最小的本地复现命令如下;把包名和 Activity 换成自己的,连续多次测量时还要固定设备状态,并注意 -S 会先停止目标应用:
bash
adb shell am start -S -W -n com.example.app/.MainActivity
adb logcat -d | grep 'Displayed com.example.app'
第一条用于发起一次停止应用后的启动并等待结果,第二条找系统记录的首次显示日志。Windows PowerShell 可把第二条的 grep 改为 Select-String。am start -W 的等待值、Logcat Displayed 与 Perfetto 中的具体 slice 不是同一把尺子;需要定位卡在哪个阶段时,应看完整时间线,而非只比较一个毫秒数。官方命令示例与 Displayed 解释 · 启动 trace 分析指南
读源码也按这张责任表走:进程没出现,先看 ATMS/AMS/ProcessList;进程已出现但业务代码未运行,看 ActivityThread 与 Provider;生命周期已完成但画面慢,看窗口和绘制。这样定位,比背一串类名更有用。
9. 回头看那张总览图
开机一侧,Bootloader 交给内核,内核进入 init;init 启动包括 servicemanager、Zygote 在内的基础进程,Zygote 带起 system_server,系统服务再让 HOME Activity 出现。点击图标后,Launcher 发请求,系统决定目标与任务;冷启动时创建 App 进程,再由 ActivityThread 绑定应用、创建组件,直到应用首帧显示。
最该记住的不是箭头数量,而是每次跨过的边界:启动服务不等于调用服务,创建进程不等于创建 Activity,onResume() 不等于首帧可见。 下一次启动慢,把现场放回这三个边界,就知道先去查哪一段。