29、OTA升级实战:从AOSP编译到OTA包生成与升级
这一章,咱们来点真格的。
前面讲了那么多动态分区的原理、虚拟快照、COW机制,说实话,都是纸上谈兵。今天我们就从AOSP源码编译开始,一路走到生成OTA包,最后在设备上完成升级。我当年第一次完整跑通这个流程时,整整折腾了三天三夜。嗯,有些坑,我希望你能绕过去。
29.1 编译AOSP:你得先有个系统
OTA升级的前提是什么?你得有两个系统版本——一个旧的,一个新的。所以第一步,编译AOSP。
我个人习惯用 lunch 选择目标设备。比如我们模拟一个Pixel设备:
source build/envsetup.sh
lunch aosp_arm64-userdebug
make -j16
这里有个小细节:userdebug 和 eng 版本会开启更多调试功能,但如果你要做正式的OTA测试,我建议用 user 版本。为什么?因为 user 版本更接近真实用户环境,有些权限问题在 userdebug 下根本暴露不出来。
编译完成后,你会得到这些关键产物:
out/target/product/<device>/system.img—— 系统镜像out/target/product/<device>/vendor.img—— 厂商镜像out/target/product/<device>/super.img—— 超级分区镜像(动态分区)out/target/product/<device>/obj/PACKAGING/target_files_intermediates/—— target_files.zip
注意最后一个,target_files.zip 是生成OTA包的原材料。它里面包含了所有分区的原始数据、文件权限、SELinux上下文等信息。说白了,它就是你的系统快照。
out 目录备份一份。我当年就是忘了备份,改了点代码重新编译后,发现旧版本没了,想生成增量OTA包都生成不了。血的教训。
29.2 生成OTA包:增量 vs 全量
有了两个版本的 target_files.zip,我们就可以生成OTA包了。AOSP提供了 ota_from_target_files 这个Python脚本,路径在 build/tools/releasetools/ 下。
29.2.1 全量OTA包
全量包最简单,就是把整个系统打包成一个更新包。设备收到后,不管当前是什么版本,直接覆盖所有分区。
./build/tools/releasetools/ota_from_target_files \
-k build/target/product/security/testkey \
out/target/product/<device>/obj/PACKAGING/target_files_intermediates/<device>-target_files-1.0.zip \
ota_full_1.0.zip
全量包的优点是简单、可靠。缺点是包体积大。一个完整的super.img可能有4-6GB,压缩后也有1-2GB。用户下载起来很痛苦。
29.2.2 增量OTA包
增量包才是OTA的精髓。它只包含两个版本之间的差异数据。
./build/tools/releasetools/ota_from_target_files \
-i out/target/product/<device>/obj/PACKAGING/target_files_intermediates/<device>-target_files-1.0.zip \
-k build/target/product/security/testkey \
out/target/product/<device>/obj/PACKAGING/target_files_intermediates/<device>-target_files-2.0.zip \
ota_incremental_1.0_to_2.0.zip
这里 -i 参数指定了旧版本的target_files。脚本会逐文件对比新旧版本,只打包发生变化的部分。
你想想看,如果系统版本从1.0升级到1.1,可能只改了十几个文件。增量包可能只有几十MB。用户体验好太多了。
29.3 OTA包的结构:里面到底有什么?
生成的OTA包其实是一个ZIP文件。我们解开来看看:
unzip -l ota_incremental_1.0_to_2.0.zip
Archive: ota_incremental_1.0_to_2.0.zip
Length Date Time Name
--------- ---------- ----- ----
1234 2024-01-15 10:00 META-INF/com/android/metadata
56789 2024-01-15 10:00 META-INF/com/android/otacert
1234567 2024-01-15 10:00 payload.bin
789 2024-01-15 10:00 payload_properties.txt
456 2024-01-15 10:00 care_map.pb
我来解释一下每个文件的作用:
| 文件 | 作用 |
|---|---|
META-INF/com/android/metadata |
元数据,包含版本号、设备类型、时间戳等 |
META-INF/com/android/otacert |
OTA包的签名证书,用于验证包的真实性 |
payload.bin |
核心数据,包含所有分区的更新内容 |
payload_properties.txt |
payload的属性,如大小、哈希值、操作类型等 |
care_map.pb |
Protobuf格式的"关心映射",标记哪些块需要更新 |
这里重点说一下 payload.bin。它内部是一个流式格式,包含多个操作:
- SOURCE_COPY:从旧分区直接复制数据,不做修改
- SOURCE_BSDIFF:对旧数据应用BSDiff补丁
- BROTLI_BSDIFF:用Brotli压缩的BSDiff补丁(Android 10+)
- ZERO:写入全零数据
- DISCARD:丢弃数据(用于trim)
我曾经调试过一个奇怪的问题:增量包升级后,某个应用总是崩溃。后来发现是 care_map.pb 漏掉了一个块,导致那个块没有被更新,还是旧数据。嗯,从那以后我每次都会检查care_map的完整性。
29.4 升级流程:从下载到重启
OTA包生成好了,怎么升级?流程大致如下:
- 下载:系统下载OTA包,通常放在
/data/ota_package/下 - 校验:验证签名和哈希,防止篡改
- 重启到recovery:设置
bootloader_message,然后重启 - 应用更新:recovery系统解析payload.bin,逐个分区更新
- 重启到新系统:更新成功,重启进入新版本
在动态分区设备上,第4步会涉及 update_engine 和 snapshotctl 的配合。具体来说:
# update_engine 内部执行流程(简化版)
1. 读取 payload.bin 的操作列表
2. 对于每个分区操作:
a. 如果是 SOURCE_COPY:直接从旧分区读取数据,写入新分区槽位
b. 如果是 SOURCE_BSDIFF:读取旧数据,应用补丁,写入新分区槽位
3. 所有分区更新完成后,调用 snapshotctl 创建快照
4. 设置新槽位为 active
5. 重启
super 分区本身不会被更新。更新的是 super 内部的逻辑分区(system、vendor、product等)。super 分区的布局信息存储在 metadata 分区中,这个分区在OTA过程中会被更新。
29.5 实战中的坑:我踩过的那些雷
讲几个我实际项目中遇到的问题,希望能帮你省点时间。
坑一:签名不匹配
我曾经在生成OTA包时用了测试密钥,但设备上烧录的是正式密钥。结果升级时签名校验失败,直接卡在recovery界面。解决办法:确保生成OTA包和烧录系统时使用同一套密钥。
坑二:分区大小不足
动态分区的好处是灵活,但如果你在OTA中新增了一个分区,而 super 分区的剩余空间不够,升级就会失败。我建议在编译时预留10%-15%的额外空间。
坑三:AB切换失败
有一次升级完成后,设备反复重启。查了半天,发现是 boot_control HAL 实现有问题,导致槽位切换不成功。这个坑比较隐蔽,建议在升级完成后手动检查 /proc/bootconfig 确认当前槽位。
29.6 知识体系总览
下面这张图总结了OTA升级的完整流程和关键组件:
这张图把整个流程串起来了。从左到右是时间线,从上到下是层次关系。你可以看到,编译阶段产出target_files,然后生成OTA包,最后在设备上执行升级流程。底部是动态分区特有的细节——super分区本身不变,但内部的逻辑分区和metadata会被更新。
29.7 验证升级结果
升级完成后,怎么确认成功了?我一般做这几步检查:
# 1. 检查当前槽位
getprop ro.boot.slot_suffix
# 输出应该是 _a 或 _b
# 2. 检查系统版本
getprop ro.build.version.incremental
# 应该等于新版本的增量版本号
# 3. 检查动态分区状态
snapshotctl dump
# 查看是否有残留的快照
# 4. 检查分区挂载情况
mount | grep -E "system|vendor|product"
# 确认所有分区都正确挂载
如果一切正常,恭喜你,OTA升级成功了。
说实话,第一次完整跑通这个流程时,那种成就感是很强的。从源码编译到生成OTA包,再到设备上升级成功,整个链路都掌握在自己手里。嗯,这就是做系统底层开发的乐趣所在。
公众号:蓝海资料掘金营,微信deep3321