Recovery模式详解:分区结构、内核启动与UI系统

各位Android开发者,今天我们来聊聊Recovery模式。说实话,这个模式平时不太起眼,但一旦系统出问题,它就是最后的救命稻草。我在做OTA升级时,有几次差点把设备刷成砖,全靠Recovery模式救回来。嗯,咱们今天就把Recovery的底裤扒干净。

一、Recovery分区结构

Recovery分区,说白了就是一个独立的小系统。它和Android主系统是分开的,互不干扰。你想想看,如果主系统挂了,Recoery还能正常工作,这样才能执行恢复操作。

分区布局大概是这样的:

+------------------+
|  Recovery分区     |
+------------------+
|  bootloader       |  <- 引导加载程序
+------------------+
|  kernel           |  <- Linux内核
+------------------+
|  ramdisk          |  <- 根文件系统
|  ├── /sbin        |     <- 核心工具(adbd, recovery等)
|  ├── /etc         |     <- 配置文件
|  ├── /res         |     <- UI资源文件
|  └── /system      |     <- 挂载点
+------------------+
|  misc分区         |  <- 存储bootloader消息
+------------------+

这里有个关键点:Recovery的ramdisk是独立的,不依赖/system分区。我遇到过一台设备,/system分区被误删了,但Recoery还能正常启动,就是因为ramdisk里自带了所有必要工具。

核心要点:Recovery分区 = 内核 + 独立ramdisk。它不依赖主系统,这是它能救砖的根本原因。

二、Recovery内核启动流程

启动流程其实不复杂,我拆成几步来讲:

  1. Bootloader加载Recovery内核——用户按下组合键(比如音量上+电源),bootloader检测到后,从Recovery分区加载内核到内存。
  2. 内核初始化——内核启动,挂载ramdisk作为根文件系统。
  3. 执行init进程——ramdisk里的init进程启动,读取init.rc配置文件。
  4. 启动recovery服务——init启动recovery守护进程,这是核心。
  5. 读取misc分区——recovery检查misc分区,看有没有升级指令。
  6. 执行操作——根据指令执行升级、清数据、备份等操作。

我个人习惯把启动流程画成图,这样更直观:

Recovery内核启动流程 ① Bootloader加载内核 ② 内核初始化 ③ 执行init进程 ④ 启动recovery服务 ⑤ 读取misc分区指令

这里有个细节:misc分区是bootloader和recovery之间的通信桥梁。比如OTA升级时,系统会把"boot-recovery"和升级包路径写入misc分区,然后重启进入Recovery。Recovery读到这些信息,就知道该干什么了。

避坑指南:我曾经遇到过misc分区被写坏的情况,导致Recovery启动后不知道要干什么,直接卡在菜单界面。后来我加了个超时机制——如果misc分区数据无效,就显示菜单让用户手动选择。

三、Recovery模式下的UI系统

Recovery的UI系统,说白了就是一个轻量级的图形界面。它不像Android主系统那样用SurfaceFlinger,而是直接操作帧缓冲(framebuffer)。

UI系统的架构是这样的:

recovery UI 组件
├── ScreenRecoveryUI    <- 主UI类
│   ├── draw_background()  <- 绘制背景
│   ├── draw_text()        <- 绘制文字
│   ├── draw_icon()        <- 绘制图标
│   └── update_screen()    <- 刷新屏幕
├── 资源文件
│   ├── /res/images/       <- 图标(.png格式)
│   └── /res/fonts/        <- 字体文件
└── 输入处理
    ├── 音量键选择
    └── 电源键确认

我刚开始研究Recovery UI时,觉得它太简陋了。后来才明白,简单就是稳定。Recovery环境里没有GPU加速,没有硬件合成器,全靠CPU往帧缓冲里写像素。你想想看,如果UI系统太复杂,万一出问题,连恢复操作都做不了。

UI的绘制流程大致是:

  1. 初始化帧缓冲设备(/dev/graphics/fb0)
  2. 加载资源文件(图标、字体)到内存
  3. 绘制背景和菜单项
  4. 等待用户输入(按键事件)
  5. 根据输入更新界面

关键代码片段:Recovery UI的帧缓冲初始化

// 帧缓冲初始化示例
int fb_fd = open("/dev/graphics/fb0", O_RDWR);
if (fb_fd < 0) {
    // 嗯,这里要处理错误
    return -1;
}

struct fb_var_screeninfo vinfo;
ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo);

// 映射帧缓冲到用户空间
void* fb_mem = mmap(NULL, screensize, PROT_READ | PROT_WRITE,
                    MAP_SHARED, fb_fd, 0);
// 之后直接往fb_mem写像素数据

这里有个坑:不同设备的帧缓冲格式可能不同。有的用RGB565,有的用RGBA8888。我遇到过一台设备,帧缓冲是BGR格式,直接写RGB数据导致颜色完全错乱。后来我加了个格式检测逻辑,才解决这个问题。

帧缓冲格式 每像素位数 常见设备
RGB565 16位 低端设备
RGBA8888 32位 中高端设备
BGR565 16位 部分MTK设备

注意事项:Recovery UI不支持多点触控,只响应物理按键。如果你在Recovery模式下点屏幕没反应,别奇怪——它压根就没实现触摸驱动。

最后说一句,Recovery UI虽然简单,但它是用户和系统之间的最后一道桥梁。我建议你在定制Recovery时,一定要保留基本的文字提示。比如升级进度、错误信息这些,哪怕界面再丑,也比黑屏强。我曾经见过一个定制Recovery,把文字提示全删了,只留几个图标。结果升级失败时,用户看到个红色感叹号,完全不知道发生了什么。

好了,Recovery模式的核心内容就这些。分区结构、启动流程、UI系统,这三块搞懂了,你就能自己定制Recovery了。


公众号:蓝海资料掘金营,微信deep3321