12、OTA包生成:ota_from_target_files脚本、payload.bin结构
说到OTA升级,很多人只关心升级包怎么刷进去。但作为一个搞过几年系统升级的工程师,我得说——OTA包是怎么生成的,才是整个链路里最容易被忽视、却又最容易出问题的一环。
我记得刚接手Android系统升级那会儿,第一次跑ota_from_target_files脚本,看着终端里刷刷刷的输出,心里还挺美。结果生成的包刷到设备上,直接卡在验证阶段。后来一查,是payload.bin里的元信息写错了。嗯,从那以后,我再也不敢小看这个脚本了。
12.1 ota_from_target_files:幕后总指挥
这个脚本,说白了就是Google提供的一个Python工具。它的输入是target_files.zip——也就是你编译出来的那个完整系统包。输出呢,就是我们最终要用的OTA升级包。
你可能会问:为什么不直接用编译产物?非要绕一圈?
原因很简单:target_files.zip里包含了编译时的完整元数据。比如每个分区的文件列表、权限、selinux上下文、甚至文件哈希。这些信息,是生成增量包和全量包的基础。
核心命令示例:
# 生成全量OTA包
ota_from_target_files \
--block \
--output_metadata_path payload_metadata.txt \
target_files.zip \
ota_full.zip
# 生成增量OTA包
ota_from_target_files \
--block \
--incremental_from previous_target_files.zip \
target_files.zip \
ota_incremental.zip
我个人习惯在生成增量包时,加上--verify参数。这会让脚本在生成完成后,自动做一次模拟升级校验。虽然多花几分钟,但能提前发现很多低级错误。
12.2 payload.bin:升级包的心脏
OTA包解压后,你会看到一个payload.bin文件。这个文件,就是升级引擎真正要解析和执行的东西。
它的结构其实不复杂,但设计得很精巧。我画了一张图,帮你快速理解:
从上图你能看到,payload.bin主要分三块:
- 头部:固定格式,包含魔数、版本号、Manifest长度。我遇到过有人手动改版本号导致升级失败的情况——千万别这么干。
- Manifest:用Protobuf序列化的元数据。这里面记录了所有分区的升级操作。说白了,就是一张“施工图纸”。
- 操作数据:真正的二进制数据块。每个操作对应一段数据,可能是新文件内容,也可能是差分补丁。
12.3 Manifest 的详细结构
Manifest里到底写了什么?我直接贴一段解析后的内容给你看:
partitions {
partition_name: "system"
estimate_size: 2147483648
operations {
type: REPLACE
data_offset: 4096
data_length: 1048576
dst_extents {
start_block: 0
num_blocks: 2048
}
}
operations {
type: SOURCE_COPY
src_extents {
start_block: 2048
num_blocks: 1024
}
dst_extents {
start_block: 2048
num_blocks: 1024
}
}
hash: "a1b2c3d4e5f6..."
}
这里有几个关键点:
- REPLACE操作:直接用新数据覆盖目标区域。全量包基本都用这个。
- SOURCE_COPY操作:从源分区拷贝数据到目标分区。增量包里很常见。
- ZERO操作:把目标区域全部写0。用于擦除数据。
避坑指南:
我曾经在生成增量包时,发现某个分区总是校验失败。排查了半天,最后发现是Manifest里记录的源分区哈希和实际设备上的不一致。原因是编译时用了不同的签名密钥。所以——生成增量包时,一定要确保前后两个target_files.zip来自同一套编译环境。
12.4 操作类型详解
除了上面提到的三种,还有几种操作类型值得注意:
| 操作类型 | 说明 | 典型场景 |
|---|---|---|
| REPLACE | 直接写入新数据 | 全量包、新增文件 |
| SOURCE_COPY | 从源分区拷贝 | 未修改的文件 |
| ZERO | 写入全0 | 擦除分区 |
| BROTLI_BSDIFF | Brotli压缩的差分补丁 | 大文件增量更新 |
| PUFFDIFF | 另一种差分算法 | 特定场景优化 |
你想想看,为什么要有这么多种操作类型?
说白了,就是为了平衡升级包大小和升级速度。REPLACE最快,但包最大。BROTLI_BSDIFF包最小,但解压和打补丁需要更多CPU时间。实际项目中,我一般会针对不同分区选择不同策略——比如system分区用差分,vendor分区用全量替换。
12.5 生成过程中的关键细节
ota_from_target_files脚本在生成payload.bin时,会做几件重要的事:
- 解析target_files.zip:读取每个分区的文件列表、权限、哈希。
- 对比新旧版本:如果是增量包,逐文件对比差异。
- 生成操作列表:根据差异类型,选择合适的操作。
- 序列化Manifest:用Protobuf编码,写入payload.bin头部之后。
- 写入数据块:按操作顺序,依次写入实际数据。
注意:
生成增量包时,脚本会计算每个文件的哈希值。如果源设备和目标设备的文件哈希不一致,升级就会失败。我遇到过一种情况:设备上某个文件被root工具修改过,哈希对不上,导致整个升级流程卡住。所以——OTA升级前,最好做一次分区完整性校验。
12.6 实战:手动解析payload.bin
有时候,我们需要排查升级包的问题。手动解析payload.bin是个好习惯。我常用的方法是:
# 使用update_engine_client的调试模式
update_engine_client --payload file:///path/to/payload.bin \
--manifest_only \
--output /tmp/manifest.txt
# 或者直接用protobuf工具
protoc --decode \
chromeos_update_engine.DeltaArchiveManifest \
--proto_path /path/to/update_engine/proto \
< payload.bin > manifest.txt
解析出来的Manifest文件,你可以直接查看每个分区的操作列表。如果发现某个分区有大量REPLACE操作,说明这个分区几乎全部重写了——那升级包自然会很大。
嗯,说到这里,我想起一个案例。有次我发现生成的增量包异常大,一查Manifest,发现vendor分区里有个日志文件每次编译都会变。后来在编译脚本里把这个文件排除掉,增量包直接缩小了60%。
所以我的建议是:生成OTA包后,先解析Manifest看看操作分布。这能帮你快速判断升级包是否合理。
总结一下:
ota_from_target_files脚本是OTA包生成的总指挥,payload.bin是升级引擎真正要吃的“粮食”。理解它们的结构和生成逻辑,能让你在遇到升级问题时,快速定位根因。别等到设备变砖了才去翻代码——提前把这些搞明白,能省很多事。
公众号:蓝海资料掘金营,微信deep3321