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 整体调用链流程图

为了让你更直观地理解整个流程,我画了一张图:

Recovery升级流程调用链 Recovery.cpp 主循环 install_package() 块级OTA 文件级OTA install_block_ota() install_file_ota() applypatch() bspatch() 图例 主循环/调度层 包处理层 具体安装层 补丁应用层

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