ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RescueParty源码解析:Android 11系统崩溃自愈与分级救援

RescueParty源码解析:Android 11系统崩溃自愈与分级救援 做 Android Framework 这些年要论“平时没人关心关键时刻却能救命”的机制RescueParty 绝对排得上号。它本质上是 Android 系统内置的一道自愈保险丝当 system_server 反复崩溃、设备陷入 boot loop 或者开机后不断重启时RescueParty 会根据异常事件的数量与频率逐步升级处置等级从重置系统设置一路走到恢复出厂设置目的只有一个——把设备从“坏状态”里拉回来。这篇文章就以 Android 11AOSP的实现为主线从设计动机、触发链路、源码关键节点到日志排查与实战避坑一次性讲清楚。适合做系统稳定性、Framework 开发、定制 ROM 的同学也适合想搞懂“为什么有时候重启后设置被还原甚至数据被清掉”的发烧友参考。1. RescueParty 是什么被低估的系统自愈机制1.1 为什么 Android 需要一把“保险丝”Android 系统里有一个核心进程叫 system_server所有上层服务——ActivityManager、WindowManager、PackageManager、SettingsProvider——都跑在它里面。它一旦崩溃Android 就不是“某个 App 闪退”那么简单而是整个系统 UI 直接没了、桌面起不来、系统服务全部失效。麻烦的地方在于触发 system_server 崩溃的原因太多了一个三方应用申请了设备管理员权限后乱改 Settings 全局项、某个 OTA 升级后旧的配置项不兼容、甚至某个 Settings 数据库里的脏数据在启动时被读出来都能让 system_server 在开机的早期反复挂掉。如果系统没有自动处理机制用户遇到这种情况就只能进 Recovery 手动双清门槛极高。Google 很早就意识到这个问题所以从 Android 7 左右开始引入了 RescueParty 的原型并在后续版本里不断强化。它在系统里的地位你可以理解成机房里的 UPS 或者家里的漏电保护器平时没人注意它可一旦发生异常它能自动做出一系列动作把系统从“濒死”状态拖回“可用”状态。Android 11 里的 RescueParty 已经相当成熟分工明确、链路完整很值得拆开看。1.2 分级救援的设计思路能局部修复就绝不清数据RescueParty 最核心的设计思想我总结成八个字由轻到重分级自愈。它不会一上来就给你恢复出厂设置那样太粗暴用户在不知情的情况下丢了照片和聊天记录换成谁也受不了。它更像个分级诊疗的医生普通感冒先开药吃药不行再打针打针还不行才考虑手术。在 Android 11 中救援等级大致分为四层官方源码里对应着不同的常量Level 1重置“受信任的默认设置”。这里的受信任主要指系统自身所改动的设置项相当于把所有系统侧的异常改动拨回原点。Level 2重置所有“不受信任的默认设置”。主要是三方应用通过 DevicePolicy 或其他通道写进去的设置把它们也恢复默认。Level 3重置全部用户可配置的设置项与偏好让整个 Settings 体系回到出厂状态。Level 4工厂级数据清除直接擦除用户数据分区等同于恢复出厂设置。这四个等级的推进不是“立刻执行最高级”而是每执行完一级系统会尝试继续引导如果还是崩下一个等级才会跟上。也就是说每一次升级都意味着前面更温和的手段已经失效必须付出更高代价来换取可用性。这个设计思路解决了几个关键问题第一最大程度保护用户数据不是一崩就清空第二给了系统自愈的机会不需要用户干预第三即便最终走到工厂重置用户也能从日志里看到“你是因为等级升级到哪一步才被清的”可追溯、可解释。这套逻辑放在 Android 11 里已经很成熟值得每个做系统稳定性的人细品。2. Android 11 中 RescueParty 的触发链路与状态机2.1 三类“救命事件”boot、system_server crash、native rebootRescueParty 判断“系统是否处于坏状态”靠的并不是猜而是对三类事件的持续观察与记录。第一类是 boot 事件——每次系统完成一次引导也就是开机流程成功走到某个阶段框架层会记录一笔。它代表设备至少有一次“能开机”的底子是后续判断异常的基础参照。第二类是 system_server crash——也就是 system_server 进程真正挂掉。这个状态在 Framework 里特别好捕捉因为 Java 层所有的未捕获异常最终都会汇聚到 RuntimeInit 的 UncaughtHandler系统服务器在启动阶段还会额外包一层专门的崩溃处理逻辑。一旦捕捉到就会调用RescueParty.noteSystemServerCrash()把这次崩溃记录在案。第三类是 native reboot——底层因为 watchdog 超时、HAL 反复出错等原因触发了重启。这类事件往往意味着问题不在 Java 层而是已经沉到 Native 层单靠重置设置救不回来但对 RescueParty 来说它依然是一个重要的“系统不稳定”信号。这三类事件不是简单地“加一”而是会被写入持久化存储也就是 Settings 数据库里的某些隐藏全局项。设备重启之后计数器不会清零这样 RescueParty 才能跨重启地判断“你到底是不是一直在崩溃”。我在看 AOSP 源码时最感慨的一点就是这个统计过程做得非常克制。它不会因为一次 system_server 崩溃就立刻动手而是像积累证据一样把每一次异常都记录在案等证据攒够了再行动。这种“先记录、后判定”的思路其实是所有高可靠系统都应该具备的素养。2.2 阈值判定与时间窗口防止“误诊”如果只看绝对次数系统很容易误判。比如用户手动重启了三次每次都碰巧遇到一次偶发崩溃总不能因为这样就把人家数据清了。所以 Android 11 里 RescueParty 的判定逻辑采用了“滑动窗口 去抖”的思路。简单说它不会只问“你崩溃了多少次”还会问“这些崩溃是不是集中在很短的时间段里”。只有当一个时间窗口内异常事件数量达到某个阈值才会被认定为“系统进入了持续不稳定的坏状态”从而触发救援流程。窗口和阈值的具体数值在 AOSP 里是通过常量定义的Google 在不同版本里也做过调整比如设置 30 分钟的观察窗口、在窗口内累积数条异常事件才触发一级救援。时间窗口的存在本质上是给“偶发抖动”留了缓冲。我刚开始读这块代码时也觉得这个判定有点绕后来拿现实生活一对比就通了这就像一个人偶尔熬夜一次你总不能让他直接住院但如果连续一周每天都熬夜到凌晨三四点那你确实该怀疑他身体撑不撑得住。RescueParty 的窗口机制干的就是这个“持续观察”的活儿。另外需要注意触发救援之后事件计数会被处理掉一部分相当于系统做了一次“结算”。这样做的目的很明显——防止刚执行完 Level 1 救援系统还没完全起来又被同一批历史事件立刻推去执行 Level 2、Level 3。每次救援都给系统一个“重新开始”的机会而不是被旧账快速推上绝路。2.3 从 Level 1 到 Level 4 的升级逻辑RescueParty 的状态机并不复杂但每一步都很关键。它执行完一级救援后不会自己主动决定要不要继续升级而是把判断权交给下一次启动结果如果下一次系统成功进入稳定状态救援流程就到此为止一切恢复正常如果下一次启动还是崩异常事件再次累积那么下一轮判定时RescueParty 就会把救援等级往上抬一级执行更重的处置。Level 1 的重置受信任设置是代价最小的动作。它主要处理系统侧自己改出来的异常状态比如某些系统应用写坏的共享偏好、某些全局配置项异常。用户感知上最多就是桌面布局、默认铃声这些被还原个人文件完全不受影响。Level 2 与 Level 3 会把范围扩大到应用侧与用户偏好侧逐步清掉非系统写入的设置项。到了这一步用户会明显感觉“很多东西被重置了”比如应用权限被打回默认、Wi-Fi 列表没了、个性化配置丢了但照片、下载文件这类数据仍然还在。Level 4 恢复出厂设置是最后手段直接走 RecoverySystem 的重置流程擦除整个用户数据分区。这个动作执行前RescueParty 会留下非常清晰的日志痕迹方便事后确认“是救援模式干的不是系统自己抽风”。这里有一个很有 Android 风格的细节如果 Level 4 都已经执行完设备依然无法正常启动RescueParty 会停止继续救援并打印明确的“无法救援”类日志。原因很好理解——数据都清空了还起不来说明问题已经超出软件设置层面再清十遍也是白搭可能涉及内核、驱动或者硬件本身继续清数据只会无限循环用户更痛苦。这个“止损”设计我特别欣赏。3. 关键源码与配置参数解析3.1 核心类与调用链路RescueParty 的核心代码集中在 AOSP 的frameworks/base/services/core/java/com/android/server/RescueParty.java以及frameworks/base/services/java/com/android/server/SystemServer.java这两个文件里。前者是救援动作的执行逻辑后者是系统启动生命周期中负责“喂事件”的入口。在 SystemServer 的启动流程里你能看到几个关键调用分布在不同的启动阶段RescueParty.noteBoot(...)在系统完成引导的关键节点被调用记录一次 boot 成功。RescueParty.noteSystemServerCrash(...)在 system_server 的崩溃处理路径中被调用记一次崩溃。RescueParty.noteNativeReboot(...)在 native 重启事件上报时被调用记一次底层重启。RescueParty.onSystemServerStarted(...)则是在系统服务启动基本成型后让 RescueParty 检查是否需要执行救援动作的核心入口。这些调用点不是随便放的它们都踩在系统生命周期里最关键的“判定位置”。比如onSystemServerStarted必须在 SettingsProvider 等基础服务都可用之后才能执行否则连“重置设置”都无从谈起。我在阅读源码时发现Android 11 里这套流程的时序设计已经相当讲究避免了“救援逻辑本身因为依赖没准备好而二次引发崩溃”的尴尬。真正的救援动作集中在executeRescueLevel()方法里。它内部会根据当前的 level 值分发到不同的处置分支调用 SettingsProvider 的 resetSettings 接口去重置对应范围的设置项或者走 RecoverySystem 去触发恢复出厂。你如果读过 SettingsProvider 的代码就会知道它支持多种 reset 模式RescueParty 正是利用这些模式精准控制“重置哪些范围”的粒度。3.2 关键属性、设置项与日志开发者和发烧友最容易接触到的是几个开关与状态字段。RescueParty 在 Android 11 里把运行状态记录在 Settings.Global 域中键名以rescue_party_开头比如rescue_party_state、rescue_party_count、rescue_party_start_millis等。你可以在设备上用下面的命令直接查看adb shell settings list global | grep rescue这条命令能把当前设备的救援状态和事件计数全部捞出来。看到rescue_party_state的值不为 0或者rescue_party_count在快速上涨基本就可以断定这台设备正在被 RescueParty 盯着。还有一个系统属性非常重要adb shell getprop persist.sys.disable_rescue这个属性如果被设置为 1RescueParty 会被整体关闭所有救援动作都不会执行。对开发者来说调试阶段确实方便但注意user 版本千万别随便关因为你永远不知道下一次崩溃会什么时候来。日志方面RescueParty 的 TAG 就叫RescueParty抓日志时直接过滤这个 TAG 就能看到几乎全部关键动作adb logcat -s RescueParty比如执行某级救援时日志里会打印类似Executing level 1或者对应等级的动作描述。这也成了我们在稳定性测试中判断“系统是否自行救了命”的黄金依据。3.3 Android 11 与旧版本的行为差异RescueParty 不是 Android 11 才有的东西但它在这个版本里的变化非常明显我挑几个我认为最值得讲的点。第一个差异是统计维度变细了。早期版本的救援判断相对粗糙基本围绕“system_server 崩没崩、崩了几次”来展开Android 11 开始把 boot 和 native reboot 也纳入观察范围异常事件的“口径”丰富了很多。这样做的好处是一些并非 Java 层崩溃、而是底层反复重启的场景也能被救援机制覆盖到。第二个差异是引入了对 DeviceConfig 的联动处理。Android 10 之后Google 把很多系统功能开关迁移到了 DeviceConfig 这套动态参数系统里也就是说黑名单、白名单、首选项这类东西不再全躺在 Settings 里。RescueParty 要是只重置 Settings 而不管 DeviceConfig很多“功能开关异常”的问题根本救不回来。Android 11 里救援逻辑专门处理了这部分是我认为最有价值的一次升级。第三个差异是防递归机制更完善。早期版本最怕遇到的坑是救援本身触发了新一轮崩溃然后系统又认为自己“需要救援”最后陷入无限恢复出厂。Android 11 在这方面加了不少限制比如执行救援后要进入冷静期、要重新累积事件才会再次触发、达到 Factory Reset 仍无效就停手止损整个状态机更健壮。第四个差异是日志与痕迹更可追溯。Android 11 把 RescueParty 的关键事件也接入了 DropBox 等系统诊断通道开发者后续翻查历史记录时能更完整地还原“设备到底经历了什么”。这对我们做稳定性复盘来说帮助特别大。4. 实操观察、模拟与规避4.1 如何判断设备是否触发过救援模式实际开发中我们经常要回答一个问题这台测试机到底有没有被 RescueParty 动过手最直接的办法还是先看日志。adb logcat -d -s RescueParty如果输出里能看到类似Executing level 1、RescueParty: reset settings这样的关键字那就不用怀疑救援动作肯定执行过。再配合查看 DropBox 里有没有对应的异常记录adb shell dumpsys dropbox --print | grep -i rescueDropBox 里的记录更详细能看到事件发生的时间戳、救援等级、关联的异常类型对还原现场非常有价值。接着用settings list global | grep rescue把计数拉出来。正常情况下rescue_party_count应该是一条稳定的小数字如果它频繁变化、持续增长说明系统正处于频繁崩溃、不断被救援的恶性循环里。这时候别急着修 RescueParty先找到让 system_server 反复崩溃的根因才是正事。还有一种情况用户反馈“重启后桌面布局变了、铃声变回默认”但日志已经被刷掉。此时去查rescue_party_state和 DropBox往往能找到蛛丝马迹。我们线上收到过不少类似工单最后排查下来多半是系统设置被写脏RescueParty 执行了 Level 1 或 Level 2 救援把设置拨回默认用户数据其实没有丢——虚惊一场。4.2 在测试设备上模拟一次救援触发RescueParty 这类机制光看代码不够最好能真机观察一次完整流程。当然不建议拿主力机试建议用模拟器或者专门用来折腾的测试机系统最好用 AOSP 编译产物方便从源头把日志打开。一个相对可控的模拟方法是直接修改 Settings.Global 里的计数器字段把事件数量抬高到触发阈值附近然后重启设备观察 RescueParty 是否按预期执行救援。比如adb shell settings put global rescue_party_count 5 adb shell settings put global rescue_party_start_millis 0 adb reboot重启后立刻抓日志adb logcat -s RescueParty如果看到救援等级被触发就说明机制生效了。测试完记得把这些字段恢复原样避免干扰后续测试。需要说明的是不同版本对计数器的具体字段名和阈值定义可能有差异动手前先settings list global | grep rescue确认一下当前系统的字段名更稳妥。如果你想更逼真地模拟“system_server 反复崩溃”的场景可以写一个小的测试 App 或者用adb shell am crash一类的调试命令去向 system_server 投递崩溃信号但这招风险更大稍有不慎会让系统彻底起不来。我的建议是先改计数器看完整流程真需要压力测试再考虑用崩溃注入而且务必在专属测试机上操作。4.3 开发者与厂商如何避免误伤与保护数据RescueParty 是一把双刃剑用得好是保命机制用不好就是数据杀手。站在开发者和 ROM 定制方的角度我总结了几条深入的防护思路。第一非必要不要全局关闭 RescueParty。persist.sys.disable_rescue虽然能关但代价是整个系统的自愈能力归零。与其一刀切不如把精力放在排查自己系统为什么频繁触发救援上根因解决掉才是正道。第二注意自研服务与 system_server 的耦合方式。有些厂商会把大量业务逻辑直接塞进 system_server 或者用系统权限跑在系统进程里一旦这部分代码有 bug崩溃信号会直接算到 system_server 头上从而把 RescueParty 引过来。尽量把可独立运行的服务拆出去减少对核心进程的干扰。第三测试脚本别乱写 Settings。我见过不少稳定性测试脚本为了模拟边界情况故意往 Settings.Global 里塞脏数据结果测着测着测试机自己被 RescueParty 恢复了出厂。这种问题不是设备坏是测试脚本“污染”了系统状态诱导了救援机制。脚本设计时应该避开这些全局配置项或者在测试完立即恢复干净。第四user 版固件要多做救援机制本身的功能测试。不要只在 debug 版里验证过“能恢复出厂就行了”要重点测“用户正常使用数据时误触发救援”的概率毕竟数据无价一次误清就可能引发严重的用户投诉。5. 常见问题与排查技巧5.1 常见问题速查表很多同学第一次接触 RescueParty都是在“莫名其妙丢数据”之后。我把实际开发中和社区里常见的问题整理成一张速查表方便大家按图索骥症状可能原因建议处置重启后桌面布局、铃声等设置恢复默认但照片和 App 数据还在触发救援 Level 1 或 Level 2重置了部分系统与设置项检查 Settings 全局配置是否有脏数据排查 system_server 崩溃日志大量应用权限被打回默认Wi-Fi 列表消失触发救援 Level 3用户偏好被重置查看 RescueParty 日志确认等级检查是否有应用管理策略异常所有数据被清空恢复出厂触发救援 Level 4 Factory Reset重点排查 rescue 日志与 DropBox 记录确认根因系统无限清理数据、反复重启Factory Reset 后仍无法启动进入止损状态问题已经超出设置层面检查内核、驱动、分区损坏频繁出现救援但找不到明显原因测试脚本或三方应用持续写入非法设置项查看事件计数字段锁定异常写入来源设备变砖连救援都救不回引导分区损坏或硬件故障不依赖 RescueParty直接走底层刷机或返修这张表是给最常见场景用的现场排查时一定要结合日志做最终判断不要只靠症状猜。5.2 实战排查三板斧干这行时间久了我总结出一套 RescueParty 相关的排查三板斧遇到相关工单基本都能快速定位。第一板斧先看计数再找崩溃源。拿到设备第一件事settings list global | grep rescue和adb logcat -d -b crash同时拉一遍。前者告诉你救援有没有触发、触发了几次后者告诉你 system_server 到底为什么崩。很多时候崩溃日志里直接写着异常堆栈根本不需要在救援机制上绕圈子。第二板斧区分“自身问题”与“诱导信号”。救援机制看得出来如果崩溃是因为某个三方应用写了一个非法 DeviceConfig 值那 ResueParty 只是被诱导了真正的凶手是那个应用或配置管理的 bug。这种情况下你修复救援机制本身是没用的要回到写配置的那条链路上去堵漏。第三板斧保留现场再动手。如果问题还在复现先别急着让系统自行救援在关键节点把日志留存下来。你可以临时设置persist.sys.disable_rescue为 1防止系统在你抓日志的过程中突然恢复出厂等拿到完整崩溃现场、确认根因后再把救援开关打开。这一招在做系统稳定性测试时尤其好用。最后再分享一个我踩过几次坑之后的经验在分析 RescueParty 相关问题时别只盯日志关键字要结合“事件发生的时间线”去看。比如设备是先出现 native reboot再出现的 system_server crash最后才触发救援那大概率根因在底层驱动或 HAL 层而不是 Java 层配置问题。时间线能帮你快速锁定问题层次少走很多弯路。以上这套方法在实际的 Android 11 系统稳定性测试里帮我定位了不少疑难问题。RescueParty 不是个热闹的功能但真正理解它之后你会对整个 Android 系统的稳定策略有更深一层的认识。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进