7. update_engine源码分析:Action与ActionProcessor设计模式、Omaha协议与Payload生成、动态分区与快照机制
各位好,今天我们来啃一块硬骨头——update_engine的源码。说实话,我第一次看这个模块时也被绕晕过。但一旦理清了它的设计思路,你会发现Google这帮人确实有两下子。
update_engine是整个Android OTA的核心引擎。它负责下载、校验、写入更新包。我当年在做一个定制ROM项目时,就因为没搞懂ActionProcessor的调度逻辑,导致更新流程卡死——嗯,从那以后我老老实实把源码啃了一遍。
7.1 Action与ActionProcessor设计模式
update_engine的架构核心是Action和ActionProcessor。说白了,这就是一个责任链模式的变体。每个Action负责一个独立步骤,ActionProcessor负责串联它们。
核心思想:每个Action只做一件事,做完就通知Processor,Processor再启动下一个Action。
我们来看一下典型的Action链:
// 典型的OTA更新Action链
InstallPlanAction → DownloadAction → FilesystemVerifierAction → PostinstallRunnerAction
// 每个Action继承自Action<T>模板类
class DownloadAction : public Action<DownloadAction> {
public:
void PerformAction() override {
// 执行下载逻辑
// 完成后调用 processor_->ActionComplete(this, Error::None());
}
void TerminateAction() override {
// 清理资源
}
};
我个人习惯把ActionProcessor看作一个导演,Action就是演员。导演喊"Action!",演员开始演,演完喊"Cut!",导演再喊下一个演员上场。
避坑指南:我曾经在自定义Action时忘记调用ActionComplete(),结果整个更新流程卡死在那里。记住:每个Action执行完毕后,必须显式通知Processor,否则链会断掉。
ActionProcessor的核心代码其实很简洁:
void ActionProcessor::StartProcessing() {
if (actions_.empty()) {
CompleteProcessing(); // 没有Action了,结束
return;
}
// 取出第一个Action并执行
current_action_ = actions_.front();
actions_.pop_front();
current_action_->PerformAction();
}
void ActionProcessor::ActionComplete(Action<T>* action, Error error) {
if (error != Error::None()) {
// 出错处理
return;
}
// 继续下一个Action
StartProcessing();
}
你想想看,这种设计有什么好处?解耦。每个Action不需要知道前后是谁,只需要知道自己该干什么。我在项目中经常利用这个特性,在测试时替换某个Action为Mock版本,非常方便。
7.2 Omaha协议与Payload生成
Omaha协议是Google内部使用的更新协议,Chrome和Android都在用。说白了,它定义了一套客户端-服务器的交互规范。
协议的核心流程是这样的:
- 客户端发送请求(包含当前版本、硬件信息等)
- 服务器返回响应(包含更新URL、版本号、哈希值等)
- 客户端根据响应下载Payload
Payload生成是另一个关键环节。update_engine使用delta生成器来创建增量包。我记得第一次接触delta update时,觉得这玩意儿太神奇了——只传输变化的部分,能省下80%的流量。
// Payload生成的核心逻辑(简化版)
class DeltaArchiveGenerator {
public:
bool GeneratePayload(const std::string& old_image,
const std::string& new_image,
const std::string& output_path) {
// 1. 对比新旧镜像,找出差异块
std::vector<BlockDiff> diffs = ComputeBlockDiffs(old_image, new_image);
// 2. 生成操作列表
std::vector<InstallOperation> operations;
for (const auto& diff : diffs) {
InstallOperation op;
op.set_type(diff.type); // REPLACE, MOVE, SOURCE_COPY等
op.set_src_offset(diff.src_offset);
op.set_dst_offset(diff.dst_offset);
op.set_data(diff.data);
operations.push_back(op);
}
// 3. 写入Payload文件
return WritePayload(operations, output_path);
}
};
注意:Payload的校验非常重要。每个操作都有对应的哈希值,客户端会逐条校验。我曾经遇到过因为Payload签名错误导致整批设备变砖的情况——从那以后,我每次生成Payload都会手动校验签名。
Omaha协议中还有一个重要概念——安装计划(InstallPlan)。它描述了整个更新过程需要执行的操作序列:
| 字段 | 说明 | 示例值 |
|---|---|---|
| download_url | Payload下载地址 | https://update.example.com/payload.bin |
| hash_checksum | Payload的SHA256哈希 | a1b2c3d4e5f6... |
| metadata_size | 元数据大小 | 1024 |
| is_delta | 是否为增量更新 | true |
7.3 动态分区与快照机制
动态分区是Android 10引入的重大变化。它允许系统在运行时调整分区大小,不再需要固定的分区表。说白了,就是按需分配。
快照机制则是动态分区的安全保障。更新前先拍个快照,如果更新失败,可以回滚到快照状态。
我们来看一下动态分区的核心数据结构:
// 动态分区布局
struct LpMetadata {
struct LpMetadataHeader header; // 元数据头
struct LpMetadataPartition partitions[MAX_PARTITIONS]; // 分区列表
struct LpMetadataExtent extents[MAX_EXTENTS]; // 物理区域映射
struct LpMetadataSlot slots[MAX_SLOTS]; // 槽位信息
};
// 分区描述
struct LpMetadataPartition {
char name[36]; // 分区名,如"system_a"
uint32_t attributes; // 属性标志
uint32_t first_extent_index; // 第一个extent的索引
uint32_t num_extents; // extent数量
};
快照机制的核心是Copy-on-Write(COW)。更新时,系统会创建一个COW文件,记录所有被修改的块。如果更新成功,COW文件被合并;如果失败,COW文件被丢弃,系统恢复到更新前的状态。
关键点:快照机制保证了更新的原子性。要么全部成功,要么全部回滚。不会出现半更新状态。
我画了一张图来展示整个流程:
快照机制在代码中的实现:
// 快照管理器核心接口
class SnapshotManager {
public:
// 创建快照
bool CreateSnapshot(const std::string& partition_name) {
// 1. 创建COW文件
// 2. 记录当前分区状态
// 3. 返回快照ID
}
// 回滚到快照
bool RollbackSnapshot(const std::string& snapshot_id) {
// 1. 读取COW文件
// 2. 恢复原始数据
// 3. 删除COW文件
}
// 合并快照
bool MergeSnapshot(const std::string& snapshot_id) {
// 1. 将COW中的修改写入分区
// 2. 删除COW文件
// 3. 更新元数据
}
};
实战经验:我在调试动态分区时,发现一个常见问题——COW文件空间不足。如果更新包太大,COW文件可能撑爆userdata分区。建议在生成Payload时,预留足够的COW空间,一般建议是更新包大小的1.5倍。
最后,我想强调一点:动态分区+快照机制是Android OTA从"蛮力更新"走向"智能更新"的关键。它让系统更新变得像数据库事务一样可靠——要么全部成功,要么回滚到原点。
嗯,今天的内容就到这里。这些知识点我在实际项目中反复验证过,希望能帮到正在研究update_engine的你。