做Android系统定制或者遇到开机异常的时候,很多人其实对“从按下电源键到桌面出现”这中间发生了什么,并没有特别完整的认知。网上能搜到的资料,要么是片段式的“init做了什么”,要么是过于底层的Linux内核讲解,很少有人把整条链路拉通来讲。这篇文章就是想把整个Android系统启动流程彻底拆开,从硬件上电一直讲到你看到Launcher图标,把每一段涉及的核心进程、关键机制、以及实战排障时最常用的判断手段都讲清楚。无论你是刚接触Framework开发的新人,还是正在被开机优化、性能问题折磨的老手,这篇文章都值得收藏。
1. 整体链路概览:一次开机,三段接力
Android的启动流程,本质上是一系列程序的“接力传递”。每一棒程序负责完成特定的初始化任务,然后把执行权交给下一棒,直到整个系统可用。整个过程可以粗暴地分为三大段:底层引导、内核启动、用户空间启动。
底层引导是从硬件复位开始,到Bootloader把Linux内核加载进内存为止。这一阶段和Android本身的关系不大,主要由芯片厂商(高通、联发科等)的固化代码控制。
内核启动是Linux内核接管硬件,完成基础初始化,挂载根文件系统,最后启动第一个用户空间进程。这一阶段的标志就是PID为1的init进程出现。
用户空间启动是整个流程最复杂、也最值得Framework工程师关注的部分。init进程会拉起关键守护进程,启动Zygote,Zygote再fork出SystemServer,SystemServer注册一系列核心服务,最终由服务拉起桌面。
为了方便理解,我把整条链路的各个阶段、核心事件、以及判断“走到了哪一步”的方法整理成了下面这张表:
| 阶段 | 核心事件 | 关键标志/文件 | 排查命令/手段 |
|---|---|---|---|
| BootROM | 固化代码执行,初始化最小硬件环境 | 厂商私有 | 串口日志 |
| Bootloader | 初始化DDR,加载boot分区,AVB校验 | boot分区、misc分区 | fastboot、串口日志 |
| Linux内核 | 初始化驱动,挂载根文件系统 | kmsg、/proc | adb shell dmesg |
| init进程 | 解析init.rc,挂载分区,启动守护进程 | /init、/system/etc/init/*.rc | adb shell ps -A 看init |
| Zygote | 预加载Java类,创建SystemServer | zygote进程、app_process | adb shell ps -A 找zygote |
| SystemServer | 注册AMS、PMS、WMS等上百个系统服务 | system_server进程 | adb shell dumpsys |
| Launcher | 启动桌面,发送开机广播 | sys.boot_completed属性 | adb shell getprop sys.boot_completed |
从这张表也能看出来,排障的核心思路是:先定位卡在哪个阶段,再针对性地看日志。启动流程本身是有严格顺序的,搞清楚顺序,就成功了一半。
2. 底层阶段:BootROM到内核启动的接力赛
很多人写启动流程文章,会从init进程讲起,刻意忽略底层。但我强烈建议大家把底层流程也过一遍,因为很多“开不了机”的硬件故障,根本走不到init阶段。先清楚底层是怎么工作的,才能尽快排除干扰。
2.1 BootROM阶段
手机硬件上电之后,CPU的第一段指令并不是来自Flash,而是来自芯片内部固化的BootROM代码。BootROM的作用非常有限:初始化最小的时钟、最小内存(通常是芯片内部的SRAM),然后根据芯片厂商预设的启动模式去读取后续代码。
所谓“启动模式”,简单来说就是BootROM决定从哪个物理介质读取引导程序。对手机来说,通常是UFS或eMMC,但也有可能进入特殊的下载模式(比如高通的EDL模式、联发科的BROM模式),用于刷机或量产烧录。这也是为什么有些砖机能通过工具恢复,因为BootROM还没坏,下载模式还进得去。
到了这一步,Android系统代码尚未参与,全是芯片厂商的私有实现。对你排查实际问题而言,这一阶段只需要记住一件事:如果设备完全无响应,优先检查BootROM是否还在运行,方法是看有没有USB枚举设备或串口日志输出。
2.2 Bootloader阶段
BootROM加载的下一段程序叫Bootloader。在Android生态里,大家接触最多的就是高通的ABL或类似实现。Bootloader负责的事情比BootROM多得多:
- 初始化DDR内存控制器,让大内存可用;
- 初始化存储驱动(UFS/eMMC控制器);
- 读取并校验启动分区(boot、dtbo、vbmeta等);
- 支持fastboot协议,供开发调试;
- 最终跳转到Linux内核入口。
这一阶段还有一个在Android上非常重要的机制:AVB(Android Verified Boot)。AVB负责对启动镜像做哈希校验,确保引导分区的完整性和来源可信。如果校验不通过,设备通常会进入错误状态或直接停在bootloader界面。定制系统时,如果关闭了AVB或没有正确处理vbmeta签名,就很容易卡在这一步。
Bootloader完成后,会把一段描述硬件信息的结构体(device tree blob,DTB)传给内核,然后跳到内核入口地址。注意,这里的“boot.img”是一个打包镜像,里面包含了Linux内核本身、设备树、以及一个叫ramdisk/initramfs的临时根文件系统。
2.3 Linux内核启动
内核是整个系统真正的“管家”,但它的启动初期做的事情也是高度流程化的。入口是start_kernel函数,它负责初始化调度器、内存管理、中断、定时器,然后启动一个名为kernel_init的内核线程。
kernel_init线程最核心的工作有两件:一是加载各类驱动程序(built-in驱动以及从initramfs加载的模块),二是尝试挂载根文件系统。在Android设备上,根文件系统要么是initramfs(ramdisk),要么是system分区的镜像,不同版本略有差别。
提示:Android的initramfs里,其实只有
/init可执行文件、一些配置文件以及必要的工具。这个/init就是用户空间的第一个进程init. 内核做的事情是把它从ramdisk解压出来,加载到内存执行。
内核把控制权交给init进程之后,Linux内核的“启动流程”就结束了,接下来是Android的世界。判断内核有没有正常启动,最直接的手段是看kmsg日志。在支持adb的设备上,即使系统尚未完全启动,只要内核起来且USB驱动加载了,就能执行adb shell dmesg看到内核日志。如果dmesg最后几行停在了奇怪的地方,那大概率是某个驱动或者根文件系统挂载出了问题。
3. init进程:Android用户空间的第一个“管理员”
Linux内核启动完成后,会执行根目录下的/init程序,这就是用户空间的第一个进程,也是所有用户空间进程的祖先。init进程出现异常,整台设备基本就没救了,所以在定制Android的时候,init相关代码一定要非常谨慎。
3.1 init进程到底做了什么
Android的init进程远不止传统Linux init那种“拉起几个服务”的职责,它要处理的事情非常多:
- 挂载
/proc、/sys、/dev等基础文件系统; - 创建设备节点,设置文件权限;
- 解析并执行
.rc脚本,启动系统服务,比如servicemanager、surfaceflinger、zygote; - 维护属性服务(property service),提供全局键值对的读写能力;
- 处理内核uEvent,动态创建/移除设备节点;
- 在用户态触发SELinux初始化并设置安全上下文。
init启动时的日志,可以通过logcat -b all或dmesg -t看到一部分,但更直接的方法是看/sys/fs/pstore/下的内核日志,以及init自己打出的日志。如果设备能进fastboot或recovery,也可以用串口方式抓到完整输出。
init进程的另一个重要工作是解析init.rc。它是Android使用的一种简单脚本格式,最早是AOSP原生版本,后来各家厂商都会在device/目录下追加自己的*.rc文件。整个启动过程被拆成多个阶段(phase),用on触发器来执行,比如on early-init、on init、on zygote-start。了解阶段划分,对理解“某个服务何时被启动”非常有帮助。
3.2 属性系统:与内核共享的全局字典
init进程维护了一套全局属性系统,用getprop和setprop命令就能读写。这套机制的实际载体是内核共享内存映射,所有进程都能通过property_get/property_set访问。它解决的是进程间的简单信息共享问题——不需要启动Binder、不需要复杂IPC,只要写一个键值对,其他进程就能立刻读到。
对所有Android开发者来说,有几个属性值得特别关注:
| 属性名 | 含义 |
|---|---|
| ro.build.version.release | 系统版本号 |
| ro.product.model | 机型 |
| sys.boot_completed | 开机是否完成,1为完成 |
| init.svc.zygote | zygote服务状态 |
| dev.bootcomplete | 传统的“启动完成”标记,部分版本使用 |
排障时,通过adb shell getprop sys.boot_completed能快速判断开机流程是否走到了最后。如果这个值为空,说明启动还没完成;如果为1,但桌面没出来,那大概率是Launcher层面的问题,而不是底层启动卡住了。
3.3 SELinux:安全策略如何影响启动
SELinux在Android里是强制开启的,它的核心作用是限制进程能访问的资源。init进程在启动早期就要完成SELinux初始化,给每个由init启动的进程分配安全上下文。如果SELinux策略配置有误,就会出现“服务启动失败”或“进程无法访问某个设备节点”的问题。
这类问题在定制系统时特别常见。比如你给新的硬件节点加了一个device.te策略漏写了allow规则,init在创建设备节点时就会被kernel拒绝,对应的功能也就无法使用。排障时可以查看adb shell dmesg | grep avc,找到被拒绝的avc: denied日志,再根据日志补充对应策略。
4. Zygote与SystemServer:Java世界的“孵化器”和“大管家”
init进程把基础环境搭好之后,会启动一个名为Zygote的进程。这是Android系统从Native世界进入Java世界的关键节点,也是所有应用程序进程的父进程。理解Zygote的设计思路,是看懂整个Android进程模型的核心。
4.1 Zygote的启动方式与设计初衷
Zygote在init.rc中有对应的service条目,它的可执行文件是/system/bin/app_process。简单来说,Zygote是一个预加载了大量Java类库和资源文件的虚拟机进程。它的启动过程大致是:
- 启动ART虚拟机;
- 预加载常用类、系统资源、主题等;
- 启动一个名为
ZygoteServer的Socket服务端,监听来自系统的zygote连接请求; - 等待AMS(ActivityManagerService)通过Socket发送“fork应用进程”的请求;
- 一旦收到请求,Zygote调用
fork()创建新的进程,并在新进程中初始化Android运行时环境,最后进入所请求的ActivityThread.main()。
Zygote的核心设计思想是:通过fork一个已经预加载好Java环境的进程,比每次重新启动一个ART虚拟机要快得多。这就像你提前准备好一桌菜,客人来了直接开餐,而不是等客人来了再洗菜切菜。
这里有两个细节值得深入理解:
- Zygote与上层通信为什么不用Binder,而是用Socket?因为Binder机制本身是作为一个系统服务(servicemanager)存在的,而Zygote在启动早期Binder环境还不可用。Socket是Linux传统IPC方式,足够简单可靠,所以被用在了这里。
- Zygote是所有应用进程的父进程,这也意味着如果Zygote崩了,所有应用进程都会跟着全部消失,整个系统会进入重启流程。所以Zygote启动异常在日志中通常非常显眼。
4.2 SystemServer的启动流程与职责
Zygote启动后的第一个fork是在自己进程内完成的:它通过forkSystemServer创建了system_server进程。SystemServer是整个Android Framework的中枢,它承载了系统最重要的服务:ActivityManagerService(AMS)、PackageManagerService(PMS)、WindowManagerService(WMS)、PowerManagerService等等。
SystemServer启动分为几个阶段:
SystemServer.main()入口,初始化主线程Looper并调用run();createSystemContext(),创建系统上下文,加载framework资源;startBootstrapServices(),启动引导阶段服务,包括AMS、PMS、LightsService等;startCoreServices(),启动电池、网络统计、用户管理、最近任务等核心服务;startOtherServices(),启动其余服务,包括WMS、IME、NotificationManager、AudioService、蓝牙、Wi-Fi等。
整个SystemServer启动过程是相对耗时的,特别是在低端机或者存储性能差的老设备上,光启动PMS扫描所有已安装应用就可能花费非常长的时间。这也是Android 11之后PMS引入“应用扫描与编译”阶段优化的原因。
SystemServer启动完成后,会通知AMS执行systemReady(),随后AMS会通知ActivityTaskManager去启动Launcher应用,也就是桌面。这一步完成之后,用户在屏幕上看到桌面图标,才算是“开机完成”。
4.3 系统服务注册与Binder调用链
SystemServer里启动的每一个服务,在启动时都会向servicemanager进程注册自己的Binder服务句柄。系统的Binder通信机制是Android整个IPC的基础,它允许不同进程通过跨进程调用访问其他服务的功能。
例如,一个应用要读取Wi-Fi状态,它不会直接访问硬件,而是通过Binder调用到系统服务进程里的WifiService。这套机制的好处是:服务集中管理、权限可控、崩溃隔离。
排查系统服务是否正常,最常用的命令是adb shell dumpsys,它可以列出所有已注册的Binder服务。如果你想精确定位某个服务,可以跑adb shell service list | grep wifi看是否有对应服务存在。如果服务不存在,说明SystemServer可能在启动该服务时崩溃,或者init没有把对应服务拉起来。
5. 应用层启动:从开机广播到桌面图标
系统服务的启动不等于整个系统已经“就绪”。当SystemServer完成启动,AMS正式进入systemReady之后,会发生一系列与应用层密切相关的事件:启动Launcher、发送开机广播、加载自启应用等。这些事件的执行顺序和完成状态,直接影响用户的实际体验。
5.1 Launcher是如何被启动的
AMS的systemReady方法内部会调用ActivityTaskManagerInternal.startHomeActivityLocked,去查找并启动“类别为HOME的Activity”。这个Activity通常是系统预装的Launcher应用,也就是用户看到的桌面。
这里有个有趣的细节:Launcher本身也是一个普通的Android应用,通过普通应用进程的方式运行。但它有一个特别之处,即它的Intent filter声明了CATEGORY_HOME,使得系统能把它识别为桌面。如果你在设备上安装了多个桌面应用,系统会弹出选择框让你选择默认桌面;如果你卸载了系统Launcher又没装新的桌面,设备显示效果就会是所有应用都没了入口,只剩下系统UI和状态栏。
5.2 BOOT_COMPLETED广播的发送时机
Launcher启动完成,大家经常说的“开机完成”才算真正标志性事件。但系统里还有一类常见需求——应用要在开机后自动启动某些功能,比如消息推送、自启服务。这类应用依赖的时机就是ACTION_BOOT_COMPLETED广播。
这个广播不是Init阶段发的,也不是SystemServer启动时发的,而是AMS在收到系统“启动完成”信号后,通过Binder广播给所有注册了该权限和接收器的应用。也就是sys.boot_completed属性被置为1的时间点附近。
对做开机优化的开发者来说,这个时间点相当重要。因为BOOT_COMPLETED广播发送时,系统服务已经就绪,应用可以安全地使用所有框架服务;但与此同时,如果大量应用都在这一瞬间启动服务、进行网络请求,必然会导致CPU和存储I/O资源瞬间爆炸,拖慢开机体验。这也是很多厂商在系统级做“自启管理”和“后台清理”的原因。
5.3 开机动画与“启动完成”的切换
大多数Android设备在开机时会播放一段开机动画,这个动画由bootanim服务负责。它的生命周期贯穿了整个启动流程:init阶段或SystemServer早期启动动画服务,动画一直播放到“启动完成”为止。
具体切换逻辑由SurfaceFlinger的bootFinished()触发。当AMS收到全部系统服务就绪并且Launcher进程启动后,会调用WindowManagerService的enableScreen,最终让SurfaceFlinger发出停止动画的信号。这一套流程如果出了问题,设备会一直卡在开机动画,但底层系统其实已经起来了。
遇到这种情况,我一般会先看adb shell dumpsys window policy | grep screen,或者看sys.boot_completed的值,判断是动画没有收到停止指令,还是系统实际没有完成启动。
6. 实战排障:如何判断卡在启动流程的哪一步
这部分是大家真正关心的重点。启动问题千奇百怪,但万变不离其宗,核心思路就是“先定位阶段,再分析具体日志”。我把自己在项目中常用的排障手段整理了一下,希望能帮到正在加班调试的你。
6.1 快速定位启动阶段
设备开不了机时,第一件事是判断它到底卡在哪一步。不要急着看logcat,先看设备指示灯、屏幕背光、USB枚举,再按下面的顺序检查:
- 确认BootROM是否运行:插上USB线,看电脑侧有没有USB设备枚举。如果完全没有枚举,很可能是硬件故障或者BootROM崩溃。
- 确认Bootloader是否运行:尝试进入fastboot模式(通常是音量下键+电源)。能进fastboot,说明Bootloader正常,问题大概率在内核或之后。
- 确认内核是否启动:执行
fastboot boot <boot.img>尝试临时启动内核,或者看dmesg。有内核日志就说明内核起来了。 - 确认init是否运行:
adb shell ps -A | grep init,如果存在init进程,说明用户空间已接管。 - 确认Zygote是否运行:
adb shell ps -A | grep zygote,如果Zygote不在,基本可以确定是app_process启动失败或SELinux问题。 - 确认SystemServer是否运行:
adb shell ps -A | grep system_server,如果system_server不在,或反复重启,基本就是Framework崩溃或死锁。 - 确认系统是否完成启动:
adb shell getprop sys.boot_completed,值为1代表系统完成。
提示:如果adb无法连接,但设备看起来在运行,优先检查USB调试是否打开、RSA授权是否通过。部分工程设备可以进入recovery模式抓取
/sys/fs/pstore下的日志。
6.2 启动阶段常见问题速查表
我把过去几年遇到的高频启动问题整理成了一个表格,供大家快速对照:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 完全没有反应,无USB枚举 | 硬件异常、BootROM损坏 | 换主板、进入EDL模式重刷 |
| 卡在Logo,无任何系统日志 | Bootloader或内核崩溃 | 看串口日志,检查AVB校验 |
| 有内核日志,但init没跑起来 | 根文件系统损坏、init权限异常 | 检查boot.img、initramfs是否正常 |
| init起来了,Zygote反复重启 | SELinux拒绝、framework资源加载失败 | 抓logcat,看avc日志 |
| 卡在开机动画,但系统其实活着 | SurfaceFlinger启动动画未收到停止信号 | 检查WindowManager/AMS是否进入ready状态 |
| 开机广播发出但延迟极大 | 自启应用过多、存储I/O瓶颈 | 优化自启动白名单,检查PMS扫描耗时 |
| 系统启动后重启 | SystemServer中某个服务崩溃 | 查看dropbox、tombstone、logcat崩溃堆栈 |
这类问题常见于定制ROM或移植AOSP的过程中,遇到之后不要慌,逻辑链路清楚了,按部就班地看日志就行。
6.3 抓取和分析启动日志的最佳姿势
日志抓取的姿势决定了能拿到多少有效信息。我强烈建议在每个开发阶段都提前配置好日志能力,而不是等出现问题时再去补。
在可调试状态下:
adb logcat -b all抓取全部缓冲区的日志,包括Main、System、Events、Crash;adb shell dmesg查看内核日志;adb shell getprop导出所有属性值,用于检查启动状态;adb shell dumpsys抓取服务状态;adb shell ls -l /sys/fs/pstore/,如果有console-ramoops之类的文件,抓下来看内核尾部日志,这是重启前最后的关键记录。
在完全无法启动或adb不可用的状态下:
- 使用串口工具抓取UART日志,这个需要硬件级支持,但对嵌入式开发来说是必备手段;
- 进入recovery模式后尝试通过
adb sideload或adb shell获取日志; - 如果设备没有root也没有串口,通常只能靠厂商售后或工程版本固件来抓取。
6.4 一步一步复现“开机流程”的小实验
如果你手头正好有调试机或模拟器,可以做一个非常直观的小实验来验证整套启动链路:冷启动设备,然后在adb shell里依次执行以下命令,对照启动阶段观察输出:
# 查看内核日志,确认Linux内核和init启动信息 adb shell dmesg | tail -n 50 # 查看所有进程,确认init、zygote、system_server是否出现 adb shell ps -A | grep -E "init|zygote|system_server" # 查看系统属性,确认启动进度 adb shell getprop sys.boot_completed adb shell getprop init.svc.zygote # 查看开机广播是否有应用注册 adb shell dumpsys activity broadcasts | grep -A 5 BOOT_COMPLETED # 查看当前前台Activity,确认是不是Launcher adb shell dumpsys activity activities | grep -E "mResumedActivity|mFocusedApp"这套组合拳打下来,能非常直观地看到整个启动过程在代码层面的表现。对初学者来说,亲手跑一遍这套命令,比看十篇文章都更能理解启动流程的每一步。
7. 开机优化的思路与实操建议
启动流程讲到最后,必须落到一个实战场景上:开机速度优化。尤其在企业定制、车机、IoT设备上,开机速度往往是产品体验的核心指标。系统级优化的方法很多,但核心原则就一条:减少串行操作、加快关键路径。
减少自启动应用数量。每多一个注册了BOOT_COMPLETED的应用,开机时就多一次进程启动、一次Binder调用,甚至多一次网络访问。对于非必要的应用,在系统定制时直接冻结或阻止它们接收开机广播。
优化PMS扫描耗时。应用越多包扫描越慢。可以考虑启动时只扫描必要分区,或者将部分应用的扫描延后到系统空闲时进行;能够有效减少开机时延的应用,不放在启动阶段就做全量扫描。
延迟非关键服务启动。SystemServer里有一堆服务,但不是所有服务都得在开机的瞬间全部初始化完毕。技术方案上可以把不阻塞用户操作的服务延后到系统空闲阶段,比如放在开机后几秒再通过addIdleHandler触发初始化。
合理使用sys.boot_completed。应用场景是,把你的开机自启动任务放到系统完成启动后再执行,避免和开机广播抢占资源。
这类优化的每一点改动都不算大,但综合起来效果非常明显。比如我经手的一个车机项目,通过上面这些手段,把从亮屏到车控主页可用的时间从25秒降到了18秒左右。优化启动流程的核心就是:它是一串接力赛,任何一棒慢了,后续所有步骤都得等;而你要做的,就是要在不破坏功能的前提下压缩每一棒的时间。
我个人做启动流程调试这些年,最大的体会是:Android的启动流程虽然复杂,但好在它是一条清晰的线性链路。只要你能准确定位当前卡在哪一段,90%的问题都可以通过日志推出来,剩下的极小部分,才是真正的硬件疑难杂症。多去qemu模拟器或真机上跑一跑,亲手抓一次完整日志,比任何文档都管用。