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:打印详细日志,调试时很有用。

工具跑起来之后,你会看到一堆输出日志。它会做以下几件事:

  1. 解析两个 target_files 包的差异(如果是增量包)
  2. 生成升级脚本(updater-script)
  3. 打包差异文件并压缩
  4. 对整个包进行签名

整个过程大概需要几分钟到十几分钟,取决于你的机器性能和包的大小。

重要提醒:生成增量 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 包生成的流程串起来:

OTA 包生成流程总览 Android 源码编译 make -j8 target_files 包 包含所有原始文件 全量 OTA 包 ota_from_target_files 增量 OTA 包 ota_from_target_files -i OTA 升级包内部结构 META-INF/ 签名 & 证书 updater-script 升级脚本 system/ 系统文件 boot.img 内核镜像 vendor/ 厂商分区 设备端 Recovery 模式执行升级 解析 updater-script → 分区操作 → 重启

从这张图你可以看到,整个流程从源码编译开始,产出 target_files 包,然后根据需求生成全量或增量 OTA 包,最后在设备端由 Recovery 模式执行升级。每一步都有明确的输入输出,环环相扣。

13.6 一些实战经验

最后,我分享几个我在实际项目中踩过的坑,希望能帮你少走弯路:

经验一:生成增量 OTA 包时,一定要确保旧版本的 target_files 包是从同一套源码编译出来的。我曾经从两个不同的分支拿包做差分,结果工具报了一堆「文件不存在」的错误。后来查了半天才发现,两个分支的文件列表根本不一样。

经验二:OTA 包的签名密钥一定要保管好。如果密钥丢了,你后续的所有 OTA 包都无法生成。我见过一个团队把密钥存在一个临时服务器上,服务器挂了之后,整个 OTA 发布流程瘫痪了两天。嗯,这种事情发生一次就够了。

经验三:测试 OTA 包时,不要只在模拟器上测。模拟器的分区结构和真机不一样。我建议你准备几台真机,专门用来做 OTA 回归测试。每次生成新包,先刷一台机器看看能不能正常开机。

好了,关于 OTA 包生成的核心内容就这些。你只要理解了 target_files 包的作用、全量和增量的区别、以及升级脚本的结构,后面遇到具体的 OTA 问题就能快速定位了。


公众号:蓝海资料掘金营,微信deep3321