13、OTA 包生成:OTA 机制简介、target_files 包、生成 OTA 升级包
各位同学,今天我们来聊聊 Android 系统更新中最接地气的一个话题——OTA 升级包是怎么生成的。
说实话,我刚入行那会儿,觉得 OTA 就是个「手机收到推送,点一下重启就完事」的东西。直到我第一次自己动手打一个 OTA 包,才发现这里面的门道比我想象的多得多。嗯,今天我就把这些经验掰开了揉碎了讲给你听。
13.1 OTA 机制到底是个啥?
OTA,全称 Over-The-Air,说白了就是「空中升级」。你不需要连电脑,不需要刷机工具,手机自己就能完成系统更新。
但你要知道,OTA 不是简单的「把新系统整个下载下来覆盖掉旧的」。那样做太粗暴了,而且流量消耗巨大。实际上,OTA 升级包只包含新旧版本之间的差异部分。你想想看,一个系统固件动辄几个 GB,如果每次升级都要全量下载,用户早就骂娘了。
我在项目中遇到过最典型的一个场景:某次系统版本从 12.0 升级到 12.1,改动其实就几个底层驱动和系统应用。如果打全量包,大小接近 3GB;但用增量 OTA,包体只有 80MB 左右。用户下载体验完全不是一个量级。
核心要点:OTA 机制的核心思想就是「只传差异,不传全量」。这背后依赖的是 Android 的 bsdiff / imgdiff 差分算法,以及一套完整的升级脚本引擎。
Android 的 OTA 升级分为两种:
- 增量 OTA(Incremental OTA):只包含从旧版本到新版本的差异数据。体积小,但要求设备当前版本必须匹配。
- 全量 OTA(Full OTA):包含完整的系统镜像。体积大,但兼容性好,任何版本都能直接升级。
我个人习惯是:日常小版本迭代用增量包,大版本跨版本升级或者首次出货用全量包。这样既节省带宽,又保证兼容性。
13.2 target_files 包——OTA 的「原材料」
要生成 OTA 包,你首先得有一个东西叫 target_files 包。这玩意儿是 Android 构建系统产出的一个中间产物,后缀名通常是 .zip,但你别把它当成普通压缩包。
target_files 包里装的是什么?我直接给你看它的目录结构:
target_files.zip
├── META/
│ ├── misc_info.txt # 构建元信息
│ ├── otacerts.zip # OTA 签名证书
│ └── filesystem_config.txt # 文件系统权限配置
├── SYSTEM/
│ ├── build.prop # 系统属性
│ ├── app/ # 预装应用
│ ├── framework/ # 框架文件
│ └── ... # 其他系统文件
├── VENDOR/
│ └── ...
├── BOOT/
│ ├── boot.img # 内核镜像
│ └── ...
├── RADIO/
│ └── ... # 基带固件
├── IMAGES/
│ ├── system.img # 系统镜像
│ ├── vendor.img # 厂商镜像
│ └── ...
└── OTA/
└── ... # OTA 相关脚本
你看,它把整个系统的所有组成部分都打包在一起了。为什么要搞这么个中间产物?
原因很简单:方便差分。你想想看,如果你只有最终的 system.img 镜像文件,你怎么做差分?镜像文件是二进制块,里面文件排列顺序一变,整个镜像的哈希值就变了,根本没法做细粒度的差分。
而 target_files 包保留了文件的原始结构,每个文件都是独立的。这样 OTA 工具就能逐文件对比,只打包那些真正发生变化的文件。
避坑指南:我曾经犯过一个低级错误——把 target_files 包当成普通 zip 解压修改后又重新打包。结果 OTA 生成工具报错说「文件签名不匹配」。后来我才知道,target_files 包里的文件是有哈希校验的,你改了文件就得重新签名。所以,永远不要手动修改 target_files 包,要用官方提供的工具。
13.3 生成 OTA 升级包——动手实操
好了,原材料有了,接下来就是炒菜环节。Android 提供了两个核心工具来生成 OTA 包:
ota_from_target_files:生成全量 OTA 包ota_from_target_files -i old.zip new.zip:生成增量 OTA 包
这两个工具都在 build/tools/releasetools/ 目录下。你编译完整个 Android 源码后,它们会自动生成。
我们来看一个实际的生产命令:
# 生成全量 OTA 包
./build/tools/releasetools/ota_from_target_files \
-k build/target/product/security/testkey \
target_files.zip \
full_ota_update.zip
# 生成增量 OTA 包
./build/tools/releasetools/ota_from_target_files \
-k build/target/product/security/testkey \
-i old_target_files.zip \
new_target_files.zip \
incremental_ota_update.zip
这里有几个参数你得注意:
-k:指定签名密钥。生产环境一定要用你的 release key,别用 testkey。-i:指定旧版本的 target_files 包,用于生成增量包。-v:打印详细日志,调试时很有用。
工具跑起来之后,你会看到一堆输出日志。它会做以下几件事:
- 解析两个 target_files 包的差异(如果是增量包)
- 生成升级脚本(updater-script)
- 打包差异文件并压缩
- 对整个包进行签名
整个过程大概需要几分钟到十几分钟,取决于你的机器性能和包的大小。
重要提醒:生成增量 OTA 包时,旧版本的 target_files 包必须和用户设备上当前版本完全一致。哪怕你只改了一个文件的权限,差分结果都会不同。我见过有人用「差不多」的旧包去生成增量包,结果刷到设备上直接变砖。嗯,这种事情发生一次就够了。
13.4 OTA 升级包的结构
生成的 OTA 包其实也是一个 zip 文件。我们把它解压看看里面有什么:
full_ota_update.zip
├── META-INF/
│ ├── MANIFEST.MF # 清单文件
│ ├── CERT.SF # 签名文件
│ ├── CERT.RSA # 证书文件
│ └── com/
│ └── google/
│ └── android/
│ ├── updater-binary # 升级程序二进制
│ └── updater-script # 升级脚本
├── boot.img # 内核镜像
├── system/
│ ├── build.prop
│ ├── app/
│ └── ...
├── vendor/
│ └── ...
└── radio/
└── ...
这里面最核心的是 updater-script 文件。它是一段用 Edify 语言写的脚本,告诉设备「先擦除哪个分区、再写入哪个文件、最后设置什么权限」。我截一段给你看看:
# 全量 OTA 脚本示例
assert(getprop("ro.product.device") == "my_device" ||
abort("This package is for \"my_device\" devices; this is a \"" +
getprop("ro.product.device") + "\"."););
show_progress(0.500000, 0);
format("ext4", "EMMC", "/system", "0", "/system");
mount("ext4", "EMMC", "/system", "/system");
package_extract_dir("system", "/system");
set_perm_recursive(0, 0, 0755, 0644, "/system");
show_progress(0.200000, 0);
# 写入 boot 分区
package_extract_file("boot.img", "/dev/block/bootdevice/by-name/boot");
show_progress(0.100000, 0);
# 设置 recovery 更新标志
set_progress(1.000000);
你看,脚本里先检查设备型号,然后格式化 system 分区,再把包里的文件解压进去,最后写 boot 分区。每一步都有进度显示,这就是你升级时看到的进度条。
我个人觉得,理解这个脚本的结构很重要。因为有时候 OTA 升级失败,问题就出在脚本逻辑上。比如分区大小不对、挂载点写错了,都会导致升级中断。
13.5 知识体系总览
说了这么多,我画了一张图帮你把整个 OTA 包生成的流程串起来:
从这张图你可以看到,整个流程从源码编译开始,产出 target_files 包,然后根据需求生成全量或增量 OTA 包,最后在设备端由 Recovery 模式执行升级。每一步都有明确的输入输出,环环相扣。
13.6 一些实战经验
最后,我分享几个我在实际项目中踩过的坑,希望能帮你少走弯路:
经验一:生成增量 OTA 包时,一定要确保旧版本的 target_files 包是从同一套源码编译出来的。我曾经从两个不同的分支拿包做差分,结果工具报了一堆「文件不存在」的错误。后来查了半天才发现,两个分支的文件列表根本不一样。
经验二:OTA 包的签名密钥一定要保管好。如果密钥丢了,你后续的所有 OTA 包都无法生成。我见过一个团队把密钥存在一个临时服务器上,服务器挂了之后,整个 OTA 发布流程瘫痪了两天。嗯,这种事情发生一次就够了。
经验三:测试 OTA 包时,不要只在模拟器上测。模拟器的分区结构和真机不一样。我建议你准备几台真机,专门用来做 OTA 回归测试。每次生成新包,先刷一台机器看看能不能正常开机。
好了,关于 OTA 包生成的核心内容就这些。你只要理解了 target_files 包的作用、全量和增量的区别、以及升级脚本的结构,后面遇到具体的 OTA 问题就能快速定位了。
公众号:蓝海资料掘金营,微信deep3321