Android OTA使用及原理浅析
OTA(over the air)通过无线网络下载、删除更新等操作,完成业务操作;在Android系统方面,使用OTA可以解决系统升级,而其差分包会增量更新系统,具有占比流量小,适用于Android端;
制作OTA升级包
OTA包分区全量包和差分包;全量包包含编译整个系统后的所有内容,差分包则是在两个全量包基础上,对比产生的差分包;假如你有两个全量包Full A和Full B,此时设备上的系统是A版本,你之多的差分包C = Full B - Full A,则可以使用OTA技术将C作为升级包更新,而不需要更新整个B;
制作全量包
在源码根目录下,执行:
make otapackage -jX
X为线程数;成功后,会在out/target/product//obj/PACKAGING/target_files_intermediates/目录下生成zip的OTA全量包,表示制作成功
修改源码后,再次执行上述代码,会在上述目录在生成一个全量包;
制作差分包
将先后两个OTA包,差分对比生成差分包,并签上名即可部署到服务器或者本地,进行OTA升级;这里签名用到了源码生成的签名文件,如果更换签名会导致OTA升级校验verify无法通过,从而失败!
/build/tools/releasetools/ota_from_target_files -v --block -p out/host/linux-x86 -k build/target/product/security/testkey -i old.zip new.zip update.zip
上述命令实质是执行ota_from_target_files.py脚本,检验全量包,对比old.zip和new.zip产生差分包update.zip最后在签名,签名文件在testkey下面
差分包制作原理
原理实质就在这个ota_from_target_files.py脚本中,会对比两个zip的各个Image镜像文件,把不同的文件写入到update文件中;
差分包文件内容
- metadata
升级包中的元数据,主要描述当前差分包的版本升级信息、时间戳、需要缓存大小
ota-required-cache=53972992
ota-type=BLOCK
post-build=qcom/msm8953_32/msm8953_32:8.1.0/OPM1.171019.026/SIM8950LB01_10:userdebug/test-keys
post-build-incremental=SIM8950LB01_10
post-timestamp=1607567016
pre-build=qcom/msm8953_32/msm8953_32:8.1.0/OPM1.171019.026/SIM8950LB01_A9:userdebug/test-keys
pre-build-incremental=SIM8950LB01_A9
pre-device=msm8953_32
- system.new.dat
此次增量更新,system镜像的数据;vendor也有一个,因为我改动了vendor下的chromatic图像优化文件,所以这里也有 - system.patch.data
升级包中,system镜像需要的patch补丁数据 - system.transfer.list
升级命令列表,也就是升级过程中,会执行此list里面的命令,这些命令在制作时就确定了 - otacert
签名信息 - update-binary和update-script
二进制升级文件binary,用于解析update-script中的升级脚本,而script脚本又会调用每个镜像的list命令列表完成ota升级
OTA升级实践
上面我们制作好包后,就可以升级了;但是要切记 版本A8和A9制作的的A89差分包只能用于A8版本升到A9,不能用于其他版本
OTA升级都需要依赖Recovery模式,由Recovery帮我们完成升级
手动升级
把升级包update.zip推送到系统中或者网络下载下来,假如保存在/data/media/0/update.zip;则我们需要在/cache/recovery/下创建command命令,然后重启bootloader进入recovery模式:
adb push update.zip /data/media/0/
echo "--update_package=/data/media/0/update.zip" > command
adb push command /cache/recovery/
adb reboot recovery
这样就完成升级,升级后再/cache/recovery/会产生升级日志last_log,如果升级失败可用于分析其过程
Android API升级
API升级其实就是将上面手动升级弄成自动升级一样,我们需要指定升级包在哪里,然后重启进入recovery模式,如下代码:
try {
File file = new File("/data/media/0/update.zip");
RecoverySystem.installPackage(this, file);
} catch (IOException e) {
e.printStackTrace();
}
分析源码:
public static void installPackage(Context context, File packageFile, boolean processed)
throws IOException {
String filename = packageFile.getCanonicalPath();
.....
配置update_package指令
final String filenameArg = "--update_package=" + filename + "\n";
final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
final String securityArg = "--security\n";
String command = filenameArg + localeArg;
if (securityUpdate) {
command += securityArg;
}
RecoverySystem rs = (RecoverySystem) context.getSystemService(
Context.RECOVERY_SERVICE);
写入指令
if (!rs.setupBcb(command)) {
throw new IOException("Setup BCB failed");
}
Having set up the BCB (bootloader control block), go ahead and reboot
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
String reason = PowerManager.REBOOT_RECOVERY_UPDATE;
.....
重启系统进入recovery模式
pm.reboot(reason);
}
升级过程原理分析
上面两个升级过程实质都是告诉系统,升级包在哪里,而没有打开、解析包内容等;而这些操作都是进入Recovery模式后,由Recovery任务来完成;
重启进入Recovery模式后,会发现/cache/recovery下有command指令,就会分析其指令并执行,其分析过程如下代码:
int arg;
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
switch (arg) {
case 's': send_intent = optarg; break;
case 'u': update_package = optarg; break;
case 'w': wipe_data = wipe_cache = 1; break;
case 'c': wipe_cache = 1; break;
case 't': show_text = 1; break;
case 'x': just_exit = true; break;
case 'l': locale = optarg; break;
case 'g': {
if (stage == NULL || *stage == '\0') {
char buffer[20] = "1/";
strncat(buffer, optarg, sizeof(buffer)-3);
stage = strdup(buffer);
}
break;
}
case 'p': shutdown_after = true; break;
case 'r': reason = optarg; break;
case '?':
LOGE("Invalid command argument\n");
continue;
}
看到没?Recovery可以做很多事情,同理,你可以往/cache/recovery里面写入wipe_data来擦除数据区;
升级过程大致可以分为:
- 校验文件签名
- 打开升级文件
- 执行升级脚本try_update_binary
最后一个步骤中,会创建一个子进程,子进程中执行update中的binary脚本,通过管道与父进程通信;最后完成升级,重启,清除command文件,否则第二次还会出现升级