☰
Android 13 关机流程分析:从上层触发到内核断电全链路
2026/10/17 2:49:37 网站建设 项目流程

Android 13 关机流程分析

这段时间在追一个关机相关的疑难问题,把 Android 13 的关机流程从上层到内核完整梳理了一遍。说实话,这个流程平时不会有人主动去读,但一旦遇到“关机卡死”“重启不进系统”“关机动画黑屏”这类问题,没有全局视角就很被动。这篇把我梳理的结论和排查思路完整写出来,包括入口、核心线程、系统服务联动、底层参数传递,以及几个常见的坑,给同样需要跟这个流程的兄弟做个参考。

1. 关机流程的整体骨架与三条触发路径

先说结论:Android 13 的关机流程不是一个“单线程”的线性过程,而是分层的状态机协作。整个流程涉及 SystemUI、SystemServer 里的多个服务、Native 层、内核,任何一层出问题,表现出来的都是“关不掉”或者“关不干净”。

从触发入口看,日常会碰到的路径有三条:

  1. 用户长按电源键,弹出电源菜单,点击“关机”或“重启”。这是最常见的一条,入口在 SystemUI。
  2. adb reboot或adb shell reboot命令触发。这个走的是 Shell 命令通道,最终同样汇聚到 SystemServer 的 ShutdownThread。
  3. 系统检测到关键错误(比如 Watchdog 超时、电池温度过高触发保护),由系统主动调用PowerManagerService.shutdown()或reboot()。这条路径不带 UI 提示,直接执行。

三条路径最终会汇合到一个核心类:ShutdownThread。它位于 framework 层,包名com.android.server.power,是整个关机流程的“发动机”。无论从哪条路进来,最后都会走到ShutdownThread.shutdown()或ShutdownThread.reboot()这两个静态方法。

在研究流程前,建议先在frameworks/base/services/core/java/com/android/server/power/ShutdownThread.java这个文件里把整体骨架读一遍,这个类不算长,但信息密度很高。我建议按“入口方法 → 状态推进 → 广播通知 → 退出”的顺序去读,不要上来就抠细节。

// 入口之一:PowerManagerService 中调用 public void shutdown(final String reason, boolean confirm) { // confirm 表示是否需要 UI 确认 ShutdownThread.shutdown(mContext, reason, confirm); }

这里有一个关键点很多人会忽略:confirm这个布尔值。当 confirm 为 true 时,ShutdownThread 不会立即执行关机,而是会先弹出关机确认对话框,等用户确认后才真正开始。如果是在 AOSP 默认的 power menu 里,SystemUI 已经做过一次确认了,传给 PMS 的 confirm 一般是 false。但如果你在做差分,改了 SystemUI 的电源菜单,这里很容易出现“关机确认框重复弹两次”或者“点了关机没反应”的现象。

另外要区分shutdown和reboot的差异。reboot 支持传入reason,这个 reason 会作为ro.boot.bootreason的一部分传给内核,也会被BootReceiver记录下来写入persist.sys.boot.reason,而 shutdown 的 reason 相对简单。业界在分析“重启原因”时,经常就是根据这个参数来判断是用户手动重启、OTA 重启还是 watchdog 触发,所以排查类的项目里这两个入口要分开看。

2. ShutdownThread 核心:状态推进与动作编排

ShutdownThread 里的核心方法是run(),它决定了整个关机动作的执行顺序。Android 13 的代码里,run() 的逻辑大体分这几个阶段:

  1. 检查并设置关机原因,写入系统属性。
  2. 发出ACTION_SHUTDOWN广播,通知所有注册了这个广播的组件开始收尾。
  3. 依次调用各关键服务(InputManager、WindowManager、MountService 等)执行预关闭动作。
  4. 逐步降低屏幕亮度、关闭 UI 相关组件。
  5. 调用底层接口真正断电或重启。

把源码简化一下,run() 的开头是这样的:

public void run() { ... // 阶段1:检查重启原因并设置属性 if (mReboot) { ... rebootViaActions(mRebootReason); } else { shutdownViaActions(); } ... }

这里我建议先看shutdownViaActions()和rebootViaActions()这两个私有方法。Android 13 里这两个方法是整个关机动作编排的核心,里面有大量对“顺序”的依赖。比如必须先发ACTION_SHUTDOWN广播,让所有 App 有机会保存数据,然后才能关闭 MountService,否则正在写存储的应用会直接丢数据。

2.1 广播通知阶段:为什么 ACTION_SHUTDOWN 那么重要

系统在真正断电前,会先发送一个全局有序广播,Action 是Intent.ACTION_SHUTDOWN。这是一个 ordered broadcast,意味着每个接收者执行完后才会轮到下一个,系统会等待所有接收者完成。这一步的主要目的是让应用有机会做数据保存、释放资源、停止后台任务。

Intent intent = new Intent(Intent.ACTION_SHUTDOWN); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); mContext.sendOrderedBroadcastAsLooper(intent, null, mShutdownListener, ...);

在实际分析中,要注意这个广播是阻塞式的。如果某个应用在onReceive里做了耗时操作(比如同步数据到云端),关机流程会一直等它,通常系统会有一个超时控制,但超时时间比较长,体感就是“卡在关机动画很久”。

从 Android 12 开始,系统对广播接收器的后台执行限制更严格了,但ACTION_SHUTDOWN是系统级广播,不受Background Execution Limits限制,依然可以启动组件。这一点在兼容性测试中很关键,如果你们的 App 在关机时没有收到广播或者收到后被杀,需要重点检查是否在清单里显式声明了android:exported和权限保护。

2.2 reboot 与 shutdown 的分叉点

在rebootViaActions()中,代码会根据重启原因决定走不同的子流程。

private void rebootViaActions(String reason) { ... if (reason.equals(PowerManager.REBOOT_RECOVERY)) { // 重启到 Recovery } else if (reason.equals(PowerManager.REBOOT_BOOTLOADER)) { // 重启到 Bootloader } else { // 普通重启 } }

调试时可以通过adb shell reboot recovery或adb shell reboot bootloader来验证这条分支。注意,这里的字符串是固定的,如果自己接三方工具或车机方案,传参不一致会导致重启到错误分区。

另外还必须说一个常见问题——reboot.override属性。这个属性在 OTA 相关流程中经常出现:

adb shell setprop reboot.override recovery adb shell reboot

设置了reboot.override之后,即使调用的 reboot 不带 reason,底层也会强制按 override 指定的模式重启。分析时如果发现“明明是普通重启,却进了 recovery”,大概率就是这个属性被某个 OTA 或系统更新组件设置后没清掉。每次重启后系统应该清掉它,但有些定制系统在异常重启时不会清理。

3. 系统服务的联动与底层参数传递

ShutdownThread 只是编排者,真正干活的是各个系统服务。Android 13 中,关机流程涉及的核心服务有这么几个:

  • PowerManagerService:负责电源状态管理、重启原因的设置。
  • InputManagerService:在关机时屏蔽触摸事件,防止用户误触。
  • WindowManagerService:关闭所有窗口、使其不可见。
  • MountService:卸载所有已挂载的存储设备,避免数据损坏。
  • BatteryService:和BatteryLight配合,在关机充电时显示电池状态。
  • AlarmManagerService:在重启后设置 RTC 闹钟。

其中最容易出问题的就是 MountService 这一步。它会尝试对所有已挂载的卷执行 unmount,如果有应用正占着文件句柄,unmount 会失败或超时。Android 13 里对 timeout 的机制做了一些调整,但问题依旧存在。

3.1 PMS 层 shutdown 与 reboot 的三种实现

PowerManagerService里和关机直接相关的方法有:

  1. shutdown():进入关机流程,不入 recovery,不重启。
  2. reboot():执行重启,可带 reason。
  3. rebootSafeMode():进入安全模式,它内部也是调用 reboot,但会设置一个特殊 reason。

在实际代码里,它们的调用关系大致是:

public void reboot(String reason) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.REBOOT, "reboot"); ... ShutdownThread.reboot(mContext, reason, false); }

这里要注意权限检查。REBOOT权限是 signature 级别,普通应用无法调用。如果是做系统级 App 或车机方案,需要在 Manifest 里声明android:permission="android.permission.REBOOT"才能调通。

3.2 JNI 层与内核的交接

当 Java 层的所有服务都完成收尾后,ShutdownThread 会调用PowerManagerService.lowLevelShutdown()或lowLevelReboot(),这两个方法通过 JNI 进入 Native 层,最终调用reboot()系统调用。

public static void lowLevelReboot(String reason) { if (reason == null) { reason = ""; } nativeReboot(reason); }

JNI 对应的实现在frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp:

static void nativeReboot(JNIEnv* env, jclass clazz, jstring reason) { const char* reasonChars = env->GetStringUTFChars(reason, NULL); reboot(ANDROID_RB_RESTART2, reasonChars); }

这个reboot()最终会通过syscall触发内核的kernel_restart,整个 Android 用户空间到此基本退出。

在内核侧,reboot系统调用会被kernel/reboot.c处理。如果带 reason,内核会把它保存到restart_msg,供 bootloader 读取。这一步直接决定重启后进入哪个分区,所以车机或嵌入式方案里,如果 bootloader 没正确读取restart_msg,就会出现“重启后进了 fastboot”“重启后起不来”的问题。

4. 关键日志与常见问题排查

关机流程的排查,本质上就是看日志和属性。我一般会先抓这几类:

  1. logcat -b system -b events:看 SystemServer 的关机日志。
  2. logcat -b crash:看关机阶段有没有 crash。
  3. dmesg:看内核日志,确认最后一步是否执行。
  4. getprop | grep sys.boot:看启动阶段的属性。

4.1 常见问题速查表

现象可能原因排查方法
点击关机无反应SystemUI 电源菜单未回调 PMS,或 confirm 参数异常抓 SystemUI 日志,看点击事件是否调用了PowerManager.shutdown()
关机动画卡死某个应用在ACTION_SHUTDOWN广播回调中耗时太长抓 events 日志,看 ACTION_SHUTDOWN 的发送与接收时间戳
重启后进入 recovery存在残留的reboot.override属性adb shell getprop reboot.override查看并清除
重启后 bootloader 模式异常底层restart_msg传递不正确抓 dmesg,确认reboot系统调用参数
存储数据损坏MountService 卸载超时,卷未正常 unmount检查 MountService 日志,看 Vold 状态
关机过程 ANR后台应用在主线程执行了磁盘 IO抓 ANR trace,确认是哪个应用阻塞

4.2 一个典型的关机 ANR 案例

我之前遇到一个案子,现象是关机动画播放到一半卡住,大约 20 秒后系统强制杀掉了所有后台进程,最终才完成关机。抓日志后发现,某个第三方应用在ACTION_SHUTDOWN广播回调里做了数据库同步,而且是在onReceive主线程里直接开的网络请求,导致广播超时。

这类问题的排查思路是:

  1. 抓取logcat -b events,查看am_broadcast_discard或am_anr相关日志。
  2. 如果看到Action: android.intent.action.ACTION_SHUTDOWN对应的进程一直没返回,就基本锁定。
  3. 再用kill -3 <pid>抓 ANR trace,看堆栈是否卡在网络库或 IO。
  4. 最后跟应用团队确认,将所有耗时操作移到goAsync()或后台线程。

Android 13 上这个问题的超时控制相比早期版本更严格了,特别是对 BroadcastReceiver 的onReceive返回时间有要求。建议所有做系统应用的人,在ACTION_SHUTDOWN回调里只做轻量操作,重的活得放到goAsync()里,并且设一个自己的超时保护。

4.3 另一个坑:ShutdownThread 卡在“等待 MountService”

同样常见的是 ShutdownThread 等待 MountService 完成卸载,但 vold 因为某个进程占用文件系统迟迟无法 unmount。这种情况下,logcat里会看到反复打印:

ShutdownThread: Waiting for MountService ...

解决方法分两层:

  • 应用层:排查哪些进程持有FileDescriptor,一般是有 Daemon 进程或媒体扫描进程。
  • 系统层:如果确认是某个系统组件导致,可以调 ShutdownThread 里的超时时间。但这是治标不治本,根本上还是要保证卸载流程通畅。

还有一个容易忽略的是 Watchdog。ShutdownThread 在执行关机动作期间,Watchdog 可能认为系统无响应而主动重启系统,造成“关机中”又起了的现象。在排查时要同时关注 Watchdog 的日志,特别是有没有打印WATCHDOG KILLING SYSTEM PROCESS。这种情况在低内存设备上更容易出现,因为关机会占用大量资源。

5. 实操复盘:一次完整的关机日志采集与问题定位

下面用我最近一次实操来演示,怎么一步步把关机流程理清楚。

5.1 准备阶段:开启相关日志开关

关机过程太快,日志转瞬即逝,所以第一步是预先打开日志缓冲和必要的调试选项。

# 设置日志缓冲足够大,避免旧日志被覆盖 adb shell logcat -G 16M # 开启 ShutdownThread 的 verbose 日志 adb shell setprop persist.sys.shutdown.debug true # 预先启动 logcat 到本地文件 adb shell "logcat -b all -v threadtime > /data/local/tmp/shutdown_log.txt &

这里有个细节:persist.sys.shutdown.debug不是 AOSP 标准属性,是我在定制系统里加的调试开关。在 AOSP 上你可以用setprop sys.powerctl相关属性,或者直接在 ShutdownThread 里加Log.v,效果类似。

5.2 触发关机并抓日志

# 触发关机,注意这里用 adb shell reboot 会直接重启,而不是关机 adb shell reboot

如果是抓关机流程,建议用adb shell reboot而不是adb shell shutdown,因为重启后logcat缓冲会保留重启前的日志,更有对比价值。

重启完成后,第一时间把日志拉出来:

adb pull /data/local/tmp/shutdown_log.txt

拿到日志后,重点搜索以下关键字:

  • ShutdownThread:核心流程,看它走到了哪一步。
  • ACTION_SHUTDOWN:广播的发送和结束时间。
  • MountService:存储卸载的状态。
  • vold:底层卷管理的日志。
  • boot_progress:启动阶段的进度,辅助判断上次关机是否干净。

5.3 解读典型日志顺序

一段正常的 ShutdownThread 日志大概是这样的:

ShutdownThread: begin shutdown ShutdownThread: Sending ACTION_SHUTDOWN broadcast ShutdownThread: Waiting for broadcast... ShutdownThread: Shutting down activity manager ShutdownThread: Shutting down mount service ShutdownThread: Shutdown complete, rebooting!

如果有Sending ACTION_SHUTDOWN broadcast之后长时间没有Waiting for broadcast...输出,说明卡在广播接收器上。这时要看am_broadcast_delivered对应的时间戳,找到耗时最长的 receiver。

真实项目中我见过一个更“隐晦”的情况:广播正常发完了,但ShutdownThread卡在Shutting down activity manager,这是因为 AMS 在等待某些进程的stopLocked操作,而那个进程的主线程被一个死锁卡住了。这种问题靠 logcat 不够,通常要配合debuggerd和dumpsys activity才能定位。

5.4 重启后的属性清理检查

重启完成后,务必要检查一次系统属性:

adb shell getprop sys.boot.reason adb shell getprop reboot.override

正常情况下,reboot.override应该是空,sys.boot.reason的值类似reboot、shutdown、recovery或watchdog。如果sys.boot.reason显示的值和预期不符,说明上层传递的 reason 有问题,需要回溯 ShutdownThread 的入参。

这里分享一个排查技巧:把sys.boot.reason纳入日常 CI 检查,很多“偶发重启”其实在重启原因里早就有线索了,只是没人去看这个属性。批量抓设备上的sys.boot.reason,能和常见异常模式快速对上号。

6. 从 Android 13 出发的几点总结与后续思考

这次完整走了一遍 Android 13 的关机流程,我的整体感受是:关机流程虽然历史悠久,但每一代 Android 都在往“更安全、更可追踪”的方向调。Android 13 里对广播超时、服务调用顺序、属性记录的细节都比以前更严格,这也意味着排查时必须更依赖日志而不是猜。

几个我认为值得记住的点:

  1. 关机流程的入口很多,但最终都汇聚到ShutdownThread,分析时从它入手最靠谱。
  2. 排查看日志,日志看时间戳,时间戳是理解关机流程顺序的钥匙。
  3. 如果做定制系统,可以从ShutdownThread里加自定义的上报点,把每个阶段的耗时记下来,这样线上问题才有据可查。

最后说一个小建议:如果你还没有完整读过ShutdownThread.java,建议抽半天时间读一遍,然后在模拟器上连续关机、重启几次,把日志对照源码看一遍。这个流程读懂了,以后遇到电源管理相关的疑难问题,你至少知道从哪一层开始查。

我后续还打算继续分析 Android 14 的关机流程差异,以及 AOSP 与主流定制系统在关机时序上的不同实现。到时候再整理一篇对比出来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询