
做Android开发这些年我遇到不少刚转Framework方向的朋友问我同一个问题手机按下电源键之后屏幕点亮之前系统到底干了哪些事这个问题看起来基础但很少有人能答完整。今天我用一篇长文把Android系统启动流程从头到尾拆一遍从芯片里固化在硬件上的Boot ROM到Bootloader、Linux内核、init进程、Zygote、SystemServer再到最后的Launcher桌面和开机广播整个过程涉及哪些关键进程、哪些系统服务、每段启动日志大概长什么样、出了问题怎么排查我都会结合源码和实际调机经验讲清楚。不管你是做应用开发想往Framework方向进阶还是做系统定制、设备移植、车机系统这篇文章都值得收藏慢慢看。1. 启动流程全景从按下电源键到看见桌面的每一阶段1.1 它是什么解决什么问题Android系统启动流程指的是设备从硬件上电开始到操作系统完成初始化、系统服务全部就绪、用户看到完整桌面并可以进行交互的整个过程。它不是单纯指桌面Launcher的启动而是把底层固件、内核、用户空间进程、Java虚拟机、系统服务、应用层全部串联起来的完整生命周期。把时间线拉长看这段旅程通常会经过五个大的阶段Boot ROM引导、Bootloader加载、Linux内核启动、init用户空间初始化、Zygote与SystemServer系统服务启动。后面还会接上Launcher启动和开机广播触发。为什么值得花时间研究它我说几个最实际的理由优化开机速度的时候你需要知道时间到底花在哪个阶段是内核初始化慢还是PMS扫描APK慢还是某个厂商自研服务拖了后腿。设备无限重启、卡在开机动画、开机后系统服务疯狂崩溃你要学会从日志里快速判断是哪一环出了问题。做系统定制、ROM移植、车机或者智能硬件的时候几乎绕不开init.rc修改、服务裁剪、启动项规划这些事。就算是只做应用开发理解了Zygote和AMS的工作方式你会明白为什么进程启动那么慢、为什么冷启动和热启动表现不一样很多ANR和启动性能问题也能看得更透。这篇文章适合三种人一是想从应用开发转向系统开发的人二是已经在做系统框架开发但希望把脉络理得更完整的人三是负责设备启动优化和问题排查的工程师。基础概念我会讲源码位置和底层细节也会给尽量避免“懂的人觉得浅、不懂的人看不懂”的尴尬。1.2 五阶段速览整体脉络先打个底先站在高处把整个流程扫一遍让心里有个坐标图后面每一段再往深处展开。我用一个表格把这几个阶段的关键内容列出来。启动阶段核心执行者关键动作阶段产出第一阶段Boot ROM固化在芯片中的引导代码初始化最基础硬件找到并校验BootloaderBootloader被加载到内存第二阶段Bootloader初始化存储、显示等外设读取分区表决定启动哪个槽位或进入fastbootLinux内核镜像与ramdisk被加载第三阶段Linux Kernel设备树解析、驱动初始化、挂载根文件系统用户空间第一个进程init被启动第四阶段init进程PID 1解析init.rc挂载分区创建关键目录/节点启动Zygote等基础服务Zygote进程、系统属性服务就绪第五阶段Zygote / SystemServerZygote孵化出SystemServerSystemServer启动AMS、WMS、PMS等核心服务系统服务就绪Launcher被拉起整个启动链条里有一个贯穿始终的主线那就是进程的父子关系。内核启动完以后第一个用户空间进程是init它的PID是1可以说是所有进程的祖先。init负责拉起ZygoteZygote再去孵化出SystemServer和其他应用进程。SystemServer内部又依次启动各个系统服务最后由ActivityManagerServiceAMS负责启动桌面Launcher。这样说可能有点抽象我用一个开餐厅的类比帮你加深印象。Boot ROM相当于餐厅门口的门卫先确认你人没问题才放你进门Bootloader是值班经理负责把后厨大门打开确认设备状态Linux内核相当于把水、电、煤气这些基础设施接通init进程是餐厅的店长按照SOP清单逐一确认每个岗位到位然后打电话让主厨到场Zygote是那位主厨他能快速“复制”出很多个帮厨每个新应用就是一个被复制的帮厨SystemServer是后厨的总协调人负责让传菜员、收银员、前台全部就位最后Launcher就是餐厅开门迎客你看到桌面一切正式开始。2. 底层奠基Boot ROM、Bootloader与Linux内核2.1 Boot ROM芯片里的第一段固件很多人以为手机按下电源键后第一段执行代码是Bootloader其实不是。严格来说CPU一上电首先执行的是固化在SoC内部的一段微代码这就是Boot ROM。这段代码出厂时写死在芯片里用户改不了它的存在意义是完成最最底层的初始化工作比如看门狗、时钟、最小存储控制器然后把Bootloader从存储介质里加载到RAM中执行。为什么要存在Boot ROM不能直接让Bootloader自己启动因为Bootloader通常存放在eMMC或UFS这样的非易失性存储中而CPU刚上电时内存控制器都还没配置好根本没法直接从存储芯片里执行大段复杂代码。所以需要一段极小的、在SoC内部SRAM里就能运行的固件做“引子”把后续代码一步步搬进来。这个过程跟DELL电脑里的BIOS引导差不多停留在“最基础硬件自检”的层次。Boot ROM还有一个很重要的职责就是安全校验。目前主流的芯片方案都会在Boot ROM阶段做一次性可编程熔丝或根密钥校验Bootloader的签名合法才让过否则就直接进入紧急下载模式甚至断电。虽然这属于安全范畴但理解这一点有助于后面排查“为什么设备刷了某个Bootloader后开不了机”这类问题——多半是签名或者校验策略不对被Boot ROM卡住了。2.2 Bootloader普通启动还是fastbootBootloader被加载到RAM后接下来就是它的主舞台。它要做的事情大概有这些初始化显示器、触摸、存储、USB等硬件读取分区表信息确认启动模式然后加载Linux内核镜像和设备树到内存在必要时还会加载一个ramdisk作为临时根文件系统最后把控制权交给内核。Android生态里有一个相当特殊的模式叫fastboot。平时我们说的“按音量键加电源键进fastboot模式”本质上是Bootloader的一个最小功能模式它没有加载完整Linux内核只是提供了通过USB接口刷写分区的能力。对这个模式最简单的理解就是它是一个“BIOS刷机界面”允许你用fastboot flash boot boot.img这样的命令把镜像直接写入对应分区。从Android高版本开始绝大多数设备都启用了A/B分区甚至虚拟A/B系统分成了slot A和slot B两个槽位。Bootloader会根据引导标志、系统更新状态等条件决定从哪个槽位加载系统。这就是为什么OTA升级后如果新系统启动失败设备可以自动回滚到旧槽位不会变砖。这个机制也是排查启动失败时一个非常重要的参考点。2.3 Linux内核设备树、驱动与根文件系统Bootloader把内核镜像加载进内存之后跳转到内核入口点Linux内核开始启动。Android使用的虽然是Linux内核但和标准Linux发行版有一个显著差别Android引入了设备树Device Tree机制用来描述硬件配置把主板型号、内存地址范围、外设寄存器等信息统一交给内核解析。没有设备树的时候内核里要写死大量的板级文件维护起来说是灾难也不为过。内核启动阶段大致顺序是处理启动参数、初始化内存管理、启动调度器、注册驱动模型、解析设备树并枚举硬件设备、初始化各种驱动子系统、挂载根文件系统。Android早期还会有ramdisk阶段也就是先用一个内存中的临时根文件系统加载必要的驱动和脚本再切换到真正的system分区根文件系统。到了Android 10之后因为system分区不再单独挂载而是和vendor等分区合并进system-as-root启动链路比原来简化了但核心思路依旧没变。想观察这一阶段的运行情况最直接的办法是抓内核日志。在电脑上执行adb reboot后迅速运行adb shell dmesg或者在板子上连接串口能看到内核打印的完整启动日志里面有大量的驱动初始化和硬件枚举信息。对启动优化来说内核日志主要看的时间点是Kernel command line到Freeing unused kernel memory之间的耗时这一段如果超过几百毫秒通常意味着有些驱动初始化逻辑或者板级配置过于冗余。2.4 HAL的软硬件解耦思想在看内核启动时很容易碰到一个概念叫HALHardware Abstraction Layer硬件抽象层。它在Android里的角色简单说就是一个“翻译官”让上层Framework不需要关心底层驱动怎么实现只需要调用HAL提供的一套标准接口。比如相机、音频、传感器、WiFi都有各自的HAL接口定义。Android 8.0引入Treble架构之后HAL的接口全面转向了HIDL或AIDLvendor分区的实现可以和system框架分开独立升级。这样带来的好处非常明显手机厂商升级系统版本时不用重新适配所有硬件驱动只要保证HAL接口兼容就行。从启动流程角度看这带来的变化是内核启动后init会分别挂载和启动来自不同分区system、vendor、product、odm等的服务和库分区之间彼此隔离整体容错能力更强了。我在实际排查启动问题时碰到过好几次“内核启动正常但系统服务起不来”的情况最后都定位到HAL接口不匹配或vendor库冲突。所以提醒一句如果你在移植系统或者OTA升级后遇到不明原因的启动异常除了看应用层日志一定要去翻HAL层的启动报错特别是vendor进程是否有crash。这往往才是真正的病根。3. 一号进程init如何拉起整个Android用户空间3.1 init的职责Linux内核完成各项初始化之后最终要做的一件事就是在根文件系统上执行第一个用户空间程序。在Android里这个程序就是/init也就是init进程它的PID固定为1。注意它不是Android独有的但Android赋予了init一些非常特殊的任务。init进程的主要工作可以概括为四个方向。第一挂载各种关键文件系统比如/proc、/sysfs、/dev、/dev/pts等并创建必要的目录给整个用户空间搭建好基础设施。第二负责解析并执行init.rc中的动作和服务定义比如启动属性服务、启动Zygote、启动SurfaceFlinger、启动vold等。第三作为“守护进程之母”它还是服务管理器负责在某个服务异常退出后按配置决定是否重新拉起Android里很多系统服务能在crash后自动恢复靠的就是init这一层。第四处理系统属性property的读写提供一个全局的KV存储系统供所有进程查询和配置系统状态。这里插一个技术细节init进程本身是静态编译的不依赖任何动态库也不依赖Bionic libc以外的其他库。为什么这样设计因为init是所有用户空间进程的起点在它启动时文件系统、动态链接器加载等环节都还没有完全就绪它必须尽量减少外部依赖保证自身稳定可靠。这也是为什么在手机开机过程中init进程几乎不可能丢失一旦它没了整个Android系统就会彻底死掉。3.2 init.rc整个启动流程的“剧本”要理解Android具体启动哪些服务、以什么顺序启动必须先学会看init.rc。init.rc是一个使用Android Init Language编写的启动脚本它主要分为三个要素import、on trigger、service。一个最简化的init.rc结构是这样的import /init.environ.rc import /system/etc/init/hw/init.usb.rc on early-init start ueventd on init sysclktz 0 mkdir /dev/socket 0750 root shell symlink /sys/kernel/debug /d mount tmpfs tmpfs /tmp mode0755 on property:vold.decrypttrigger_restart_framework class_start main service zygote /system/bin/app_process -Z --zygote --start-system-server class main socket adbd stream 660 root system onrestart restart zygoteon trigger表示当满足某个触发条件时执行下面这些命令。触发条件可以是early-init、init、late-init这些固定阶段也可以是property:属性名值这样的属性变化事件。service则定义了一个守护进程的启动方式包括路径、参数、服务类别、所属组、socket等。启动顺序方面init会先执行所有early-init阶段的动作然后依次进入init、late-init。在late-init触发器里通常执行trigger boot和trigger main进而启动main类的所有服务。Zygote就是典型的class main服务SurfaceFlinger、vold、netd等也都在这一波启动里。我建议每个做系统开发的人都养成阅读device/厂商/型号/init.rc的习惯因为设备厂商经常会在原生init.rc之上追加自定义服务尤其是一些底层daemon和mqtt、日志上传类的进程。搞明白它们的class和触发时机能让你在分析开机耗时时更快找到“哪个厂商服务又拖慢了启动速度”。3.3 属性系统Android世界的“环境变量”属性系统是init进程提供的另一个核心能力可以把它想象成Android系统的全局环境变量。它维护着一个键值对存储任何进程都能通过property_get和property_set读取和修改这些值。常见的sys.boot_completed、ro.build.version.sdk、persist.sys.timezone都是属性。实现机制上init进程持有一个属性服务其他进程要设置属性时先通过socket发送请求给init由init统一处理并更新共享内存区域再通过共享内存把变化广播给所有已经注册监听的进程。这样设计的好处是权限集中控制不是谁想改就能随便改系统关键属性。和启动流程最紧密的一个属性是sys.boot_completed。它是SystemServer执行完之后由ActivityManagerService设置的值为1时代表系统已经完成启动上层应用可以正常使用。开机动画、开机广播、各种启动服务都会监听这个标志作为准备就绪的信号。3.4 ueventd与设备节点的创建很多人容易忽略init还有个“马甲”进程叫ueventd它的作用是监听内核的uevent事件并在 /dev 目录下创建设备节点、设置正确的权限。这个阶段看似不起眼但一旦ueventd出问题最直接的后果就是很多硬件设备文件不存在比如 /dev/dri/card0 缺失导致显示异常或者 /dev/ttyACM0 缺失导致串口不可用。在init启动过程中ueventd会被显式拉起并且它和init是同一个二进制文件只是入口不同。系统里看到的是一个init进程和一个ueventd进程实际上它们来自同一个可执行文件通过启动参数区分。这算是一个很小的冷知识但在排查设备节点问题时还是会用到。4. Zygote应用孵化器如何工作4.1 Zygote的诞生过程现在进入Android特有的核心环节Zygote进程。在init.rc中会定义类似下面这样的service配置service zygote /system/bin/app_process -Z --zygote --start-system-server class main priority -20 user root group root readproc reserved_disk socket zygote stream 660 root system onrestart restart zygote注意启动参数里的--start-system-server它的作用非常关键告诉Zygote在初始化完成后直接fork出SystemServer进程。这正好解释了一个常见的疑问——SystemServer是哪来的它并不是init直接启动的而是Zygote这个“应用孵化器”的第一个孵化成果。Zygote初始化时最主要的事情分为三步创建用于IPC的Java层Socket接口、预加载通用类与资源、启动SystemServer、然后进入一个无限循环等待请求。它就像职业介绍所坐在那里等各个应用进程上门请求“帮我fork一个”收到请求后自己fork出一个子进程子进程再继续读取参数并按需加载应用代码。4.2 为什么用fork而不是直接启动应用要理解Zygote的高效先记住一个Linux知识fork会创建一个与父进程几乎完全一样的子进程。在写时复制Copy-on-Write的机制下子进程会共享父进程的内存页只有在真正写入时才复制。所以Zygote fork出来的应用进程天然就继承了Zygote已经preload好的那些类、资源、字符串、主题等。应用启动时就不需要重新加载这些基础库直接使用即可。这也是为什么Android应用的进程启动比传统Java程序的JVM启动快得多。传统Java程序从零到可运行需要创建虚拟机、加载类、解析字节码而Android应用进程本身就是从Zygote fork出来的它带着一整块已经“热好”的Java环境。我在做启动优化时经常碰到“冷启动慢”的问题很多情况下就是应用进程创建后还需要重新加载大量SharedPreferences、Bitmap或者做大量序列化。从系统层面来看如果Zygote里预加载的资源太多反而会导致内存压力过大和首次启动变慢所以Android源码里有一份preloaded-classes列表用来控制哪些类需要预加载。改这个列表是高级优化手段没掌握好尺度时很容易把整个系统搞得不稳定不建议轻易动。4.3 预加载的资源池Zygote预加载的具体内容主要包括三大块Framework相关的基础类比如Activity、Service、ContextImpl等系统资源比如主题、颜色、尺寸、字符串等还有一些全局常用对象如Locale、SharedPreferences相关的初始化对象。你可以简单理解为Zygote是Android Framework级的一个“完整蓝本”。Zygote的入口是ZygoteInit.main关键流程大致如下// frameworks/base/core/java/com/android/internal/os/ZygoteInit.java public static void main(String[] argv) { // 1. 注册Zygote Socket ZygoteServer zygoteServer new ZygoteServer(); // 2. 预加载类和资源 preload(bootTimingsTraceLog); // 3. 启动SystemServer if (startSystemServer) { Runnable r forkSystemServer(abiList, zygoteSocketName, zygoteServer); // r 是子进程执行的入口即SystemServer.main } // 4. 进入循环等待孵化新进程的请求 caller zygoteServer.runSelectLoop(abiList); }从代码可以看出Zygote在完成系统资源预加载后就立刻fork出SystemServer然后再进入runSelectLoop监听Socket请求。Android应用的“一个新进程诞生”几乎全部要经过这个套路应用端通过AMS发送请求AMS再通过Socket把fork请求发给Zygote。5. SystemServer系统服务的大总管5.1 system_server的启动流程SystemServer是Zygote fork出的第一个Java进程所有系统核心服务都在它内部运行。它在Zygote进程里被fork出来之后执行的入口是SystemServer.main()。这个入口做的事可以分成两大部分先初始化系统的上下文Context包括创建SystemServer专用的Looper和ContextImpl接着按阶段启动上百个系统服务。源码里的SystemServer.java有非常清晰的三个阶段划分startBootstrapServices()启动引导服务包括ActivityManagerService、PowerManagerService、PackageManagerService、DisplayManagerService等最核心的服务。这些服务是其他服务的前置依赖。startCoreServices()启动核心服务比如BatteryService、UsageStatsService、WebViewUpdateService、BinderCallsStatsService等。startOtherServices()启动其余服务包括WindowManagerService、InputManagerService、NotificationManagerService、LocationManagerService等一大批与用户功能直接相关的服务。在启动服务的顺序上有一个非常重要的细节服务之间有依赖关系有些必须等到前面的服务完全ready才能继续启动比如AMS要先创建系统目录、初始化ActivityThreadWMS要依赖DisplayManagerService把显示设备初始化好。这也是为什么厂商在系统服务阶段做性能优化时最常做的一件事就是把非核心服务延后、把核心服务精简而不是一股脑地全部塞在同一时间启动。5.2 AMS、WMS、PMS各是干什么的说到SystemServer绕不过去的就是三大服务ActivityManagerService、WindowManagerService、PackageManagerService。想真正理解启动流程这三个服务必须重点讲。ActivityManagerService简称AMS负责管理所有Activity、Service、BroadcastReceiver以及应用进程的调度和生命周期。通俗地说它是系统里的“总经理”决定谁在什么时候应该出现在前台、谁能占用CPU资源、谁的进程该被杀掉。开机后AMS还会负责启动Launcher后面会细聊。WindowManagerService简称WMS是“视觉主管”。它管理所有应用窗口的创建、布局、输入事件派发以及系统级窗口状态栏、导航栏。它和AMS配合非常紧密AMS决定应用要不要显示WMS决定怎么把窗口画到屏幕上。两者有一个很经典的双向锁依赖一旦处理不好很容易产生死锁或ANR。PackageManagerService简称PMS负责扫描和解析APK管理应用安装、卸载、权限、组件查询等。开机过程中PMS会扫描/system/app、/system/priv-app、/data/app等目录下的所有APK解析它们的Manifest信息并建立包名到组件信息的映射表。这个扫描过程在预装应用很多的机器上耗时非常可观是开机优化中重点关注的对象之一。从启动日志上看这几个服务的启动节点通常出现在SystemServer.startOtherServices前后。我曾经在一台预装了几百个应用的测试机上看到过PMS扫描耗时能占到整个开机过程的将近一半后来通过精简预装、开启扫描缓存、延迟后台扫描等方案把开机时间压缩了接近20%。5.3 systemReady与开机动画的切换SystemServer并不是把服务都start出来就算完它还有一个很重要的收尾环节叫systemReady。当所有服务启动到一定程度后AMS会调用ActivityManagerService.systemReady()通知系统“核心服务已经就绪可以开始启动应用了”。在systemReady之前系统显示的一直是开机动画。开机动画进程是bootanim它本身也是由init拉起的一个服务负责在启动期间循环播放一段动画。当SystemServer确认系统已经准备好就会通过属性或Binder调用通知bootanim退出随后桌面Launcher开始出现。这里有个常见的现象开机动画加载完但桌面迟迟不出来或者桌面出来了又闪回动画。遇到这种情况大方向就是两个一是AMS/systemReady环节卡住了重点关注AMS和ActivityStackSupervisor的逻辑二是Launcher自身启动崩溃或依赖的某个服务还没ready经常会伴随大量Timeout超时日志。5.4 WatchdogSystemServer里的“安全用”Android系统为了保证可靠性在SystemServer里启动了一个Watchdog线程专门检测系统关键锁是否长时间被持有、应用进程是否长时间没有响应。一旦发现核心线程卡死超过一定时间一般是60秒Watchdog就会强制重启SystemServer最终表现为设备重启或者系统UI重启。和启动流程相关的部分在于如果某个自定义系统服务在启动阶段持锁阻塞Watchdog会在启动过程中就触发。排查思路主要是抓取Android的watchdog日志里面会明确标注阻塞的线程、持有的锁、等待的线程栈。配合Java和Native的栈dump定位这类问题通常不会太难。我在实际项目里遇到过一次厂商服务持锁导致系统反复重启的案例当时卡了一个下午最后是看traces.txt才发现某个自定义服务在SystemServer启动早期就去拿了一个被长时间占用的锁直接触发了Watchdog。解决问题本身不复杂但找问题的过程非常依赖对整个启动流程的理解。6. 从系统到应用Launcher启动与开机完成标志6.1 AMS如何拉起LauncherSystemServer完成各项服务启动并进入systemReady后最后的“压轴任务”就是启动桌面。启动桌面并不是通过简单执行一个可执行文件完成的而是由AMS按照一定的规则去拉一个带CATEGORY_HOME意图的组件。Launcher实际上就是一个普通的Activity但它声明了android.intent.action.MAIN和android.intent.category.HOME所以系统知道它是“桌面”。在AMS内部这一步会走到startHomeActivityLocked或者类似逻辑不同版本实现位置有差异它开启一个从包管理查询到组件信息、再走常规Activity启动流程的过程。所以如果你做了多桌面比如智能电视、车机上常见的定制桌面只要安装了多个声明为HOME的应用AMS会弹出一个选择框或者在系统配置里指定默认桌面包名。这个阶段对启动体验的体感影响特别大。我调试过的很多设备系统服务全部启动正常但如果Launcher启动时要做大量数据库迁移、网络预连接、动画预热用户就会觉得“开机好慢”。这类问题的优化方向一般是让Launcher自己做启动任务划分不在首帧显示前做耗时操作把组件初始化改为懒加载。6.2 sys.boot_completed与BOOT_COMPLETED前面讲过sys.boot_completed属性这里再把它和广播的关系说清楚。这个属性不是谁设置完就马上广播的它是在AMS完成系统就绪和首屏显示后由系统进程设置随后才会发送BOOT_COMPLETED广播通知第三方应用“系统已经启动完成”。从开发者的视角来看BOOT_COMPLETED是应用常驻后台类App最喜欢监听的开机广播。但要特别注意从Android 12API 31开始如果你动态注册了BOOT_COMPLETED而应用没有处于已启动或前台状态可能收不到广播建议改用清单文件静态注册或在触发逻辑上做兜底。系统在发送BOOT_COMPLETED时并不会保证所有系统服务都已经完全可交互所以建议在广播处理中做容错比如延迟初始化、捕获服务连接异常。另外由于开机广播是所有应用都会收到的一个集中式广播如果个别应用处理过慢会拖慢整个开机收尾过程。排查开机耗时的时候不要漏掉这一块你可以通过adb logcat -b events | grep BOOT_COMPLETED来查看广播的发送和接收时间线。6.3 开机广播的优先级注意点优先级这个点容易被忽略但遇到问题时会非常头疼。Android系统里确实允许给BroadcastReceiver设置android:priority在同一个Intent的多个接收者之间按照优先级排序。但在实际使用中依赖开机广播优先级来做业务逻辑并不可靠因为高版本系统对隐式广播的限制越来越严格而且不同厂商的系统也可能对广播队列做特殊处理。我的建议很简单不要把关键业务的安全感押在开机广播的接收顺序上。正确的做法是收到BOOT_COMPLETED之后先做一个短延时或加一个前置条件检查确认依赖的系统能力就绪后再执行真正的初始化工作。宁可让初始化稍晚几十毫秒到几百毫秒也不要因为过早执行而踩到空指针或者服务未注册的崩溃。7. 实操如何分析一次完整的启动过程7.1 抓取完整的开机日志分析启动流程第一件事是把日志拿到手。Android的日志其实可以分为几类我们平时用的adb logcat默认抓的是Java和Native层的系统日志也就是SystemServer和应用打印的信息。完整启动分析时还需要关注内核日志和事件日志。推荐的抓取方式# 重启设备并在重启完成后立即拉取日志 adb reboot adb wait-for-device adb logcat -b all -v threadtime boot_all.log adb shell dmesg boot_kernel.log内核日志里有Boot ROM、Bootloader、驱动初始化的痕迹虽然很多厂商会关闭底层串口日志但在开发板或Userdebug版本上仍然能拿到。事件日志用-b events抓取里面有非常重要的时间点比如开机动画启动、boot_progress_start、boot_progress_ams_ready、boot_progress_enable_screen等。拿到的日志怎么看建议先拉出着几个关键节点的时间差下面的命令能快速列出它们adb shell dmesg | grep -i boot_progress adb logcat -b events -d | grep -E boot_progress|boot_completed如果能看到boot_progress_start到boot_progress_ams_ready之间的时间明显偏大那么问题多半出在SystemServer阶段可能需要配合Java栈进一步排查。7.2 常见的启动分析工具除了手工看日志还有几个工具建议学会用。第一个是systrace现在更多推荐Perfetto它可以记录整个系统在一段时间内的CPU调度、内核事件、进程名、线程调用栈对分析启动耗时和卡顿非常有用。启动阶段采集方式大致是这样# 在设备端开启perfetto跟踪并保存到文件这里只做示意 perfetto -o /data/misc/perfetto-traces/boot.trace -s 4MB -c /data/local/tmp/trace_config.pbtx第二个是bootchart它是一个专门统计开机过程中各进程CPU消耗、IO等待的工具。Android构建版本中通常已经包含了bootchart功能用户态开启后会在/data/bootchart下生成数据再用pybootchartgui把数据渲染成图。它可以直观告诉你开机过程中哪些进程在抢CPU、哪些进程IO等待严重特别适合做T0启动优化。第三个是开发者选项里的“启动耗时”页面。Android 11及以上系统在开发者选项里能看到系统的启动时间统计它会整体展示从开机到桌面画出来分别花了多少时间。这个数值虽然没有perfetto那么细但胜在方便适合做前后对比。7.3 把启动耗时量化到具体阶段做启动优化的第一步永远是先把耗时“量化”。没有数据一切优化都是拍脑袋。我一般会把整个启动过程拆成几个大块来量从上电到内核启动完成主要靠内核日志时间戳。从init启动到Zygote启动主要靠logcat里的init阶段日志和dmesg。从Zygote到SystemServer各服务ready主要看boot_progress_ams_ready、boot_progress_enable_screen等事件。从Launcher首帧显示到sys.boot_completed置位主要看窗口绘制日志和BOOT_COMPLETED广播时间。把这几个时间点首尾相减通常很快就能定位瓶颈所在。拿我之前调的一台设备来说总开机时间20秒结果有7秒花在PMS扫描APK上3秒花在SurfaceFlinger等待显示设备初始化上剩下大部分时间都耗在了厂商自定义服务串行启动上。找到这些数据之后优化方向就变得非常明确。8. Android版本演进对启动流程的影响8.1 Android 12及以上的启动优化很多人会好奇Android高版本对启动流程做了哪些改动是不是从12开始启动方式都变了。其实底层逻辑没有变依然是init、Zygote、SystemServer、AMS启动Launcher这条主链路但Google在启动性能上投入了大量优化。比如Android 12在SystemServer启动时做了一些原生化改造部分启动路径上的接口从Java层下沉到了Native层减少了Java反射和跨进程Binder调用的开销。同时引入了启动关键路径上的线程优化避免无谓的锁竞争让系统服务的启动速度更快。此外启动动画也做了重构引入了更平滑的启动过渡体验让冷启动期间用户感觉更“跟手”。我自己的体感是同一台设备从Android 11升级到Android 12后在CPU不变的前提下SystemServer阶段整体启动时间确实有所缩短。但厂商预装服务和第三方App仍然是最大的开机耗时来源单纯依赖系统版本升级带来的收益有时并不明显。8.2 APEX与动态分区带来的底层变化Android 10引入了动态分区system、vendor、product等分区不再使用固定大小而是在一个超级分区内动态分配。这对启动流程的影响主要是init启动时需要先处理动态分区映射Bootloader和内核的引导参数也会相应调整。如果设备分区表损坏或者init找不到动态分区配置就会直接卡在内核阶段或init早期阶段。Android 10还引入了APEX机制可以理解为系统组件的“可单独升级单元”。它允许系统核心组件比如Conscrypt、MediaCodec等以类似APK的方式单独更新不用依赖整个系统OTA。对启动流程来说init阶段会比以前多做一步APEX加载需要解压和挂载对应APEX文件。如果这个过程出错可能出现system_server启动后部分功能异常但因为APEX是叠加在system之上的普通logcat有时看不太出来需要专门查apexd和apex相关的错误日志。8.3 车机与嵌入式的特殊考量最后提一下车机Android和嵌入式设备。现在因为Android在车载领域Android Automotive OS的扩展启动流程在某些场景下会被拆得更加细致比如要求快速显示后视摄像头画面、快速启动导航、快速响应语音唤醒这些都需要对启动过程做深度定制。常见做法是让极早期阶段的SurfaceFlinger提前启动显示特定画面或者把部分核心服务拆分为“首屏优先启动”和“后台延迟启动”两类。如果你在车载或工控领域做系统一定要对开机启动的每一阶段做指标监控不要等到整机联调时才发现启动时间超了。我在做这类项目时通常从第一天就把启动log采集脚本挂上每个版本都会对比各阶段耗时的变化一旦发现某个阶段突变就能第一时间锁定是新加的某个服务或进程引起的回归。9. 常见启动问题排查与避坑指南9.1 无限重启Bootloop无限重启是最典型的启动问题现象是设备反复在开机动画、Logo、甚至黑屏之间循环无法进入桌面。按原因分大致有几类init或底层服务崩溃init作为PID 1如果退出内核会直接panic重启。SystemServer中的关键服务反复崩溃比如AMS、WMS、PMS无法完成初始化。SELinux策略错误系统分区或vendor分区里的服务在启动时被SELinux拒绝执行访问反复启动失败。OTA升级失败或分区数据损坏A/B槽位切换时新系统无法启动设备自动回滚旧槽位但如果两个槽位都有问题就会一直循环。排查时优先抓取adb logcat -b crash -b system -b kernel需要设备能进入系统或至少能抓到日志重点看有没有Fatal signal、Watchdog、SELinux denials等信息。如果连日志都抓不到就进fastboot重新刷写boot、system、vendor镜像从干净环境再试。9.2 卡在开机动画卡在开机动画说明Linux内核和init阶段已经正常问题多半出在SystemServer启动过程中或者SurfaceFlinger/开机动画与SystemServer之间的交接环节。比较常见的情况是某个系统服务启动时执行了耗时很长的阻塞操作比如访问不可用的硬件、等待某个HAL进程超时。Watchdog已经触发说明SystemServer里的主线程或关键线程长时间没有响应。PMS扫描APK过程中遇到损坏的包反复解析失败极端情况下会导致启动阶段长时间阻塞。经验上先看logcat -b events里有没有boot_progress_ams_ready这个标志如果一直没出现说明AMS都还没准备好如果AMS已经ready但动画还在可能Launcher启动环节出问题。再结合dumpsys window看当前焦点窗口是否是Launcher很多时候能快速定位到是桌面App还是系统窗口的问题。9.3 开机慢开机慢的优化和前两类排查不太一样更偏向性能优化。核心思想是先量化再拆解最后逐项消除冗余。我通常会把问题拆解成这四步用perfetto或bootchart记录启动全过程拿到各阶段耗时占比。找出CPU占用最高、阻塞最久、IO等待最严重的进程重点看厂商自定义服务和预装应用。对启动阶段的冗余操作做裁剪比如延迟预装应用的初始化、禁用不必要的开机自启、把串行服务改成并行启动。复测对比确认优化没有引入稳定性风险。值得注意的是开机慢的问题往往不是单一原因而是“叠加态”。即使每个服务只慢200毫秒几十个服务累加起来就很可观。所以不要指望只优化一个点就能彻底解决需要持续监控慢慢磨。9.4 排错速查表我把常见的启动问题、现象和优先排查方向整理成一张速查表方便大家在实际工作中查阅。现象可能原因优先排查方向设备完全黑屏无反应Boot ROM/Bootloader异常或硬件供电问题串口日志、充电电流、fastboot是否能进入卡在Logo或厂商开机画面Bootloader或内核阶段失败内核日志、设备树配置、分区表反复自动重启SystemServer崩溃/Watchdog触发/init重启crash日志、watchdog日志、SELinux log卡在开机动画很久SystemServer服务阻塞/PMS扫描异常/Launcher未启动boot_progress_ams_ready、dumpsys window开机后桌面白屏或闪退Launcher崩溃/权限异常/SurfaceFlinger异常logcat crash日志、Launcher相关trace开机很慢但能正常使用预装应用和后台自启过多/PMS扫描耗时bootchart、perfetto、BOOT_COMPLETED广播耗时9.5 独家避坑技巧最后分享几个我自己在项目里踩过坑之后总结出的经验希望对你有帮助。第一改完init.rc之后不要只验证“能开机”一定要验证“服务状态正确”。我曾经把某个服务放到了错误的class里导致它在zygote还没启动时就先跑起来结果初始化失败系统启动后功能异常但整个启动过程看起来很正常。排查这类问题最有效的方式是启动完成后查看每个关键服务对应的socket是否已建立、属性标志是否按预期设置。第二SELinux相关的启动问题非常容易误诊。有时候是能开机但某个vendor服务访问不到某个节点导致启动后半段的功能缺失。建议在排查启动问题时打开SELinux的permissive模式做对比测试。如果permissive模式正常而enforcing模式异常基本可以确定是策略问题。第三使用adb reboot做启动测试时建议先在设置里关闭“运行中的应用”自动恢复功能或者使用adb shell settings put global always_finish_activities 0确认状态。否则上一次的Activity栈可能会影响开机后的Launcher启动测试结果会不干净。第四抓日志一定要趁早。如果等到桌面都出来了再去抓日志很多启动早期的内核日志、服务启动日志已经被覆盖了。我的习惯是启动测试前提前设置logcat的缓冲区大小并在开机完成后第一时间拉取所有log必要时保留一份开机全过程录像对比画面时间点。这些看起来很小的细节能在关键时刻帮你省下半天排查时间。