5、升级流程源码分析:Recovery.cpp主循环、install_package函数详解、applypatch与bspatch调用链
各位同学,今天我们深入Recovery模式的源码核心。说实话,这部分代码我前前后后啃了不下五遍,每次都有新发现。你想想看,整个Android系统升级的“心脏”就在这里跳动——从Recovery.cpp的主循环,到install_package的调度,再到applypatch和bspatch的底层打补丁,这是一条完整的执行链路。
5.1 Recovery.cpp主循环:升级的“调度中心”
Recovery.cpp的main函数,说白了就是一个超级状态机。我刚开始看的时候,觉得这代码怎么这么绕?后来才明白,它要处理的事情太多了:用户按键、日志输出、进度显示、异常恢复……
核心逻辑其实就一个while循环:
// 简化后的主循环结构
int main(int argc, char **argv) {
// 1. 初始化各种子系统
ui_init(); // 初始化UI
load_volume_table(); // 加载分区表
// 2. 解析启动参数
int ret = parse_boot_options(argc, argv);
// 3. 进入主循环
while (true) {
// 检查是否有命令要执行
if (check_for_command()) {
// 执行升级命令
install_package(package_path);
}
// 处理用户输入(比如音量键选择菜单)
int key = ui_wait_key();
handle_menu_key(key);
}
}
嗯,这里要注意:主循环并不是一直在跑,它会在ui_wait_key()处阻塞等待。只有检测到升级命令或者用户按键时,才会继续往下走。
关键点:Recovery主循环的设计哲学是“事件驱动”。它不会主动轮询,而是等待外部事件(命令文件、按键)来触发下一步动作。这种设计在嵌入式系统中非常常见,省电又高效。
5.2 install_package函数详解:升级的“项目经理”
install_package是整个升级流程的“项目经理”。它不干具体的活,但知道每一步该找谁。我曾在项目中遇到过一个诡异的问题:升级到一半卡住了,最后发现是install_package里一个路径拼接的bug。
来看它的核心流程:
int install_package(const char* path) {
// 1. 验证包完整性
if (!verify_package(path)) {
return INSTALL_CORRUPT;
}
// 2. 读取升级包类型
// OTA包有两种:BLOCK-based 和 FILE-based
if (is_block_ota) {
// 块级OTA,直接写分区
return install_block_ota(path);
} else {
// 文件级OTA,需要applypatch
return install_file_ota(path);
}
// 3. 更新系统状态
finish_recovery();
return INSTALL_SUCCESS;
}
个人经验:我曾经在调试一个分区大小不匹配的问题时,发现verify_package里对签名校验特别严格。如果你的升级包签名过期了,哪怕内容完全正确,也会返回INSTALL_CORRUPT。所以,记得检查你的签名证书有效期!
5.3 applypatch与bspatch调用链:打补丁的“工匠”
applypatch是文件级OTA的核心。它不像块级OTA那样直接写分区,而是“精雕细琢”——只修改有变化的部分。bspatch则是它最得力的工具,负责执行二进制差分合并。
调用链是这样的:
// applypatch 的核心逻辑
int applypatch(const char* source_file,
const char* target_file,
const char* patch_file) {
// 1. 读取补丁文件头
// 补丁文件里包含了目标文件的SHA1、大小等信息
PatchHeader header;
read_patch_header(patch_file, &header);
// 2. 校验源文件
// 确保源文件是我们期望的那个版本
if (!verify_source(source_file, header)) {
return -1;
}
// 3. 调用bspatch执行差分合并
// 这是最核心的一步
int ret = bspatch(source_file, target_file, patch_file);
// 4. 校验目标文件
if (!verify_target(target_file, header)) {
return -1;
}
return 0;
}
bspatch的算法其实不复杂,但效率极高。它读取补丁文件中的控制块,决定是从源文件拷贝数据,还是从补丁文件中插入新数据:
// bspatch 核心逻辑(极度简化)
int bspatch(const char* old_file,
const char* new_file,
const char* patch_file) {
// 打开源文件和补丁文件
FILE* old_fp = fopen(old_file, "rb");
FILE* patch_fp = fopen(patch_file, "rb");
FILE* new_fp = fopen(new_file, "wb");
// 读取控制块
// 控制块告诉我们要拷贝多少、插入多少
while (read_control_block(&ctrl)) {
// 从源文件拷贝 ctrl.copy_len 字节
copy_from_old(old_fp, new_fp, ctrl.copy_len);
// 从补丁文件插入 ctrl.insert_len 字节
insert_from_patch(patch_fp, new_fp, ctrl.insert_len);
// 跳过源文件中的 ctrl.skip_len 字节
skip_in_old(old_fp, ctrl.skip_len);
}
return 0;
}
避坑指南:我曾经遇到过一个非常隐蔽的问题——bspatch在合并大文件(超过2GB)时,内部使用的off_t类型在32位系统上会溢出。后来我加了个#define _FILE_OFFSET_BITS 64才解决。如果你也在做类似开发,记得检查文件大小和类型定义。
5.4 整体调用链流程图
为了让你更直观地理解整个流程,我画了一张图:
5.5 关键数据结构
在applypatch和bspatch之间,有几个关键的数据结构你需要了解:
| 数据结构 | 所在文件 | 作用 |
|---|---|---|
| PatchHeader | applypatch.h | 存储补丁文件的元信息,包括目标文件大小、SHA1哈希等 |
| ControlBlock | bspatch.cpp | 控制块,包含copy_len、insert_len、skip_len三个字段 |
| Value | install.h | 升级包中的键值对,用于传递参数和配置 |
我的习惯:每次分析这类调用链时,我都会先画一张流程图,把每个函数的输入输出标清楚。这样调试时,一旦某个环节出问题,我能立刻定位到是哪个函数“掉链子”了。
好了,关于Recovery升级流程的源码分析就到这里。从主循环的事件驱动,到install_package的分发调度,再到applypatch和bspatch的精细打补丁——这条链路环环相扣,缺一不可。理解了这个流程,你就能在遇到升级失败时,快速判断是哪个环节出了问题。
公众号:蓝海资料掘金营,微信deep3321