
做Android系统定制或者遇到开机异常的时候很多人其实对“从按下电源键到桌面出现”这中间发生了什么并没有特别完整的认知。网上能搜到的资料要么是片段式的“init做了什么”要么是过于底层的Linux内核讲解很少有人把整条链路拉通来讲。这篇文章就是想把整个Android系统启动流程彻底拆开从硬件上电一直讲到你看到Launcher图标把每一段涉及的核心进程、关键机制、以及实战排障时最常用的判断手段都讲清楚。无论你是刚接触Framework开发的新人还是正在被开机优化、性能问题折磨的老手这篇文章都值得收藏。1. 整体链路概览一次开机三段接力Android的启动流程本质上是一系列程序的“接力传递”。每一棒程序负责完成特定的初始化任务然后把执行权交给下一棒直到整个系统可用。整个过程可以粗暴地分为三大段底层引导、内核启动、用户空间启动。底层引导是从硬件复位开始到Bootloader把Linux内核加载进内存为止。这一阶段和Android本身的关系不大主要由芯片厂商高通、联发科等的固化代码控制。内核启动是Linux内核接管硬件完成基础初始化挂载根文件系统最后启动第一个用户空间进程。这一阶段的标志就是PID为1的init进程出现。用户空间启动是整个流程最复杂、也最值得Framework工程师关注的部分。init进程会拉起关键守护进程启动ZygoteZygote再fork出SystemServerSystemServer注册一系列核心服务最终由服务拉起桌面。为了方便理解我把整条链路的各个阶段、核心事件、以及判断“走到了哪一步”的方法整理成了下面这张表阶段核心事件关键标志/文件排查命令/手段BootROM固化代码执行初始化最小硬件环境厂商私有串口日志Bootloader初始化DDR加载boot分区AVB校验boot分区、misc分区fastboot、串口日志Linux内核初始化驱动挂载根文件系统kmsg、/procadb shell dmesginit进程解析init.rc挂载分区启动守护进程/init、/system/etc/init/*.rcadb shell ps -A 看initZygote预加载Java类创建SystemServerzygote进程、app_processadb shell ps -A 找zygoteSystemServer注册AMS、PMS、WMS等上百个系统服务system_server进程adb shell dumpsysLauncher启动桌面发送开机广播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上非常重要的机制AVBAndroid Verified Boot。AVB负责对启动镜像做哈希校验确保引导分区的完整性和来源可信。如果校验不通过设备通常会进入错误状态或直接停在bootloader界面。定制系统时如果关闭了AVB或没有正确处理vbmeta签名就很容易卡在这一步。Bootloader完成后会把一段描述硬件信息的结构体device tree blobDTB传给内核然后跳到内核入口地址。注意这里的“boot.img”是一个打包镜像里面包含了Linux内核本身、设备树、以及一个叫ramdisk/initramfs的临时根文件系统。2.3 Linux内核启动内核是整个系统真正的“管家”但它的启动初期做的事情也是高度流程化的。入口是start_kernel函数它负责初始化调度器、内存管理、中断、定时器然后启动一个名为kernel_init的内核线程。kernel_init线程最核心的工作有两件一是加载各类驱动程序built-in驱动以及从initramfs加载的模块二是尝试挂载根文件系统。在Android设备上根文件系统要么是initramfsramdisk要么是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.zygotezygote服务状态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与SystemServerJava世界的“孵化器”和“大管家”init进程把基础环境搭好之后会启动一个名为Zygote的进程。这是Android系统从Native世界进入Java世界的关键节点也是所有应用程序进程的父进程。理解Zygote的设计思路是看懂整个Android进程模型的核心。4.1 Zygote的启动方式与设计初衷Zygote在init.rc中有对应的service条目它的可执行文件是/system/bin/app_process。简单来说Zygote是一个预加载了大量Java类库和资源文件的虚拟机进程。它的启动过程大致是启动ART虚拟机预加载常用类、系统资源、主题等启动一个名为ZygoteServer的Socket服务端监听来自系统的zygote连接请求等待AMSActivityManagerService通过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的中枢它承载了系统最重要的服务ActivityManagerServiceAMS、PackageManagerServicePMS、WindowManagerServiceWMS、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、Crashadb 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模拟器或真机上跑一跑亲手抓一次完整日志比任何文档都管用。