4、Recovery分区:传统recovery模式、recovery-from-boot.patch机制、AB升级下的recovery变化

Recovery分区,说白了就是Android系统的一个「救急工具箱」。我刚开始接触Android底层时,觉得这玩意儿就是个刷机界面,后来踩过几次坑才明白——它其实是系统升级的最后一道防线。

今天咱们就把它掰开揉碎了讲清楚。从传统模式讲起,再到那个神奇的patch机制,最后聊聊AB分区时代Recovery的进化。

4.1 传统Recovery模式

传统Recovery模式,大家应该不陌生。手机进不去系统了,按音量键+电源键,就进入一个黑底蓝字的菜单界面。嗯,就是那个。

它的核心职责其实就三件事:

  • 应用OTA升级包——把下载好的升级包刷进去
  • 双清/恢复出厂——擦除data分区
  • adb sideload——通过电脑推送刷机包

它的启动流程是这样的:

Bootloader → 读取misc分区 → 判断启动模式
  ├── 正常模式 → 启动boot.img → 进入系统
  └── Recovery模式 → 启动recovery.img → 进入Recovery

这里有个关键点:misc分区。它就像一个「传令兵」,Bootloader通过读取它来决定下一步干什么。我在项目中遇到过一个问题——misc分区被意外写坏了,结果手机怎么按都进不去Recovery,最后只能拆机用烧录器重写。所以,千万别小看这个只有几KB的分区

传统Recovery的局限性:它和boot分区是独立的。如果boot分区坏了,系统起不来,但Recovery还能用。反过来,如果Recovery分区本身坏了...那就真成砖了。

4.2 recovery-from-boot.patch机制

这个机制,说实话,是Google早期的一个「神来之笔」。为什么要搞它?

你想想看,Recovery分区如果被破坏了怎么办?比如刷机刷到一半断电了,或者恶意软件把Recovery分区给抹了。传统方案下,这就得返厂了。

Google的解法很巧妙——把Recovery的「种子」藏在boot分区里

具体做法是这样的:

  1. 编译系统时,生成一个recovery-from-boot.p文件
  2. 这个文件其实是Recovery镜像和boot镜像的差分补丁
  3. 把它放在/system/etc/目录下
  4. 每次开机时,init进程会检查Recovery分区是否完整
  5. 如果不完整,就用boot分区+补丁文件,重新生成Recovery分区

看一段关键代码你就明白了:

// system/core/recovery/recovery-persist.cpp
static bool recovery_needs_patching() {
    // 检查recovery分区的头部签名
    // 如果签名不对,说明被破坏了
    if (verify_recovery_signature() != 0) {
        return true;  // 需要修复
    }
    return false;
}

static int apply_recovery_patch() {
    // 从boot分区读取原始数据
    // 用recovery-from-boot.p生成新的recovery镜像
    // 写入recovery分区
    return apply_patch("/dev/block/boot", 
                       "/dev/block/recovery",
                       "/system/etc/recovery-from-boot.p");
}

我曾经在一个项目里遇到过这种情况:客户反馈手机OTA升级后Recovery进不去了。排查了半天,发现是recovery-from-boot.p文件被某个安全软件给删了。嗯,从那以后我就在代码里加了个保护——把这个文件设为不可删除。

个人经验:这个机制在Android 7.0之前非常有用。但到了AB分区时代,它的重要性就降低了。为什么呢?往下看。

4.3 AB升级下的Recovery变化

AB分区,也叫无缝升级。Google从Android 7.0开始引入,彻底改变了升级的玩法。

在AB分区架构下,Recovery的角色发生了根本性变化:

对比项 传统模式 AB模式
升级方式 重启进入Recovery,离线升级 后台在线升级,下次重启生效
Recovery分区 独立分区,必须存在 可以合并到boot分区
recovery-from-boot.p 必须存在 可以去掉
升级失败恢复 依赖Recovery修复 回滚到旧slot即可

说白了,AB分区把Recovery的「救急」职责给分担了。为什么?

因为AB分区有两个slot——A和B。你平时用A,升级包在后台写到B。写完后重启,如果B起不来,Bootloader自动回滚到A。整个过程用户无感知。

这样一来,Recovery就不再是「必需品」了。Google甚至允许厂商把Recovery合并到boot分区里,做成一个recovery内核+ramdisk的形式:

// BoardConfig.mk 中的配置
// 传统方式:独立recovery分区
BOARD_RECOVERYIMAGE_PARTITION_SIZE := 0x1000000

// AB方式:recovery合并到boot
BOARD_USES_RECOVERY_AS_BOOT := true
TARGET_NO_RECOVERY := true

我参与过一个项目,客户坚持要保留独立Recovery分区,理由是「万一两个slot都坏了呢?」。说实话,这个概率极低,但既然客户要求,我们就保留了。不过我们把recovery-from-boot.p去掉了——因为AB分区下,boot和recovery是同一份镜像,不需要补丁修复。

注意:虽然AB分区下Recovery不再是必须的,但很多厂商仍然保留它。原因有两个:一是用户习惯(很多人还是想进Recovery双清),二是工厂生产时需要Recovery来刷写分区。所以,别急着删。

4.4 实际项目中的选择建议

讲了这么多,到底该怎么选?我个人的建议是这样的:

  • 新项目,用AB分区:用户体验好,升级失败能自动回滚。Recovery可以合并到boot,省一个分区。
  • 低端设备,内存小:可以考虑传统模式+recovery-from-boot.p。因为AB分区需要双倍分区空间,低端设备吃不消。
  • 特殊需求,比如医疗设备:建议保留独立Recovery分区。这类设备对稳定性要求极高,多一层保障总是好的。

最后说一句:Recovery分区虽然看起来不起眼,但它是系统升级的「压舱石」。设计得好,能省很多售后麻烦。设计得不好...嗯,我见过因为Recovery分区太小,导致大升级包刷不进去的案例,最后只能返厂。

所以,别小看它。