Android OTA 流程介绍

1. 构建 OTA 包

1.1 构建完整更新

完整更新是指 OTA 软件包将对设备的整个最终状态(system 分区、boot 分区和 recovery 分区)进行更新。不管设备的当前状态如何,只要设备能够接收和应用软件包,软件包就会安装 build。例如,以下命令使用发布工具为 sstar_pioneer5 设备构建 target-files.zip 归档文件。

source build/envsetup.sh && lunch sstar_pioneer5-userdebug
mkdir dist_output
make dist DIST_DIR=dist_output

make dist 会构建完整的 OTA 软件包(在指定的 dist_output 中)。生成的 .zip 文件包含为 sstar_pioneer5 设备构建 OTA 软件包所需的所有内容。还可以通过 ota_from_target_files 构建为 Python 二进制文件,并调用它来构建完整软件包或增量软件包。

ota_from_target_files dist_output/sstar_pioneer5-target_files-eng.payne.chen.zip full_ota_update.zip

其中 payne.chen 为编译服务器用户名称,后面均以此为例。

ota_from_target_files 路径在 $PATH 中设置(source 之后即可以正常使用),并且生成的 Python 二进制文件位于当前目录中。

full_ota_update.zip 现已准备就绪,可以发送到测试设备(所有内容均使用测试密钥进行签名)。

1.2 构建增量更新

增量更新是指 OTA 软件包将对设备上的现有数据应用二进制补丁。采用增量更新的软件包通常较小,因为它们无需包含未更改的文件。此外,由于更改的文件通常与之前的版本非常相似,因此软件包中只需包含针对两个文件之间的不同之处进行的编码。

只有当设备具有构建相应软件包所使用的源 build 时才能在设备上安装增量更新软件包。如需构建增量更新,需要上一个 build(要更新的那个 build)中的 target_files.zip 文件以及新 build 中的 target_files.zip 文件。例如,以下命令可使用发布工具为 sstar_pioneer5 设备构建增量更新。

ota_from_target_files -i PREVIOUS-sstar_pioneer5-target_files-eng.payne.chen.zip dist_output/sstar_pioneer5-target_files-eng.payne.chen.zip incremental_ota_update.zip

此 build 与上一个 build 非常相似,而且增量更新软件包(incremental_ota_update.zip)比对应的完整更新软件包小得多。

仅当设备运行的上一个 build 与相应增量更新软件包的起点 build 完全一样时,才能向其分发该增量更新软件包。必须刷写 PREVIOUS-sstar_pioneer5-target_files-eng.payne.chen.zip 或 PREVIOUS-sstar_pioneer5-img-eng.payne.chen.zip 中的映像(均使用 make dist 构建,将使用 fastboot update 进行刷写),而不是 PRODUCT_OUT 目录下的映像(使用 make 进行构建,将使用 fastboot flashall 进行刷写)。尝试在具有其他某个 build 的设备上安装增量更新软件包会导致安装错误。如果安装失败,设备同样会保持正常运行状态(运行旧系统);软件包在触及它要更新的所有文件之前,会验证其上一个状态,因此设备不会滞留在半升级状态。

1.3 SigmaStar OTA 升级包构建

SigmaStar 封装并简化了 OTA 包升级流程,将上诉相关的指令封装到了统一的编译脚本 sstar_make.sh 当中,以下将介绍如何通过 sstar_make.sh 脚本编译 OTA 升级包。

1.3.1 构建全量 OTA 升级包

构建全量 OTA 包可以通过如下指令获取:

./sstar_make.sh --ota_full

生成的全量 OTA 包位于 $(OUT)/full_ota_update.zip$(OUT) 为编译生成镜像路径,例如:/home/payne.chen/Android/sstar-android12/out/target/product/pioneer5_sdp。 编译全量包同时会在 $(OUT) 目录下生成 target_files,这个会记录当前版本的全量信息,后续生成增量 OTA 包会需要用到这个,target_files 每次编译增量或者全量 OTA 包都会生成,生成文件夹名称带编译日期,例如:ota_sstar_pioneer5_sdp_20230413_121058。

1.3.2 构建增量 OTA 升级包

OTA 增量包是由两个完整的版本 target_files 做比较提取出增量得到的,所以当修改的 AOSP 的代码后要制作增量包需要在之前需要升级的版本做过一次构建 OTA 全量或者增量包并生成 target_file 文件。

假设在之前版本已经编译过一次 OTA 全量包并在 $(OUT) 目录生成了 ota_sstar_pioneer5_sdp_20230413_121058 的旧的 target-files.zip 并且当前设备也是运行在该版本上,

在修改了代码后通过以下指令得到了一个基于旧版本更新到新版本的 OTA 增量包:

./sstar_make.sh --ota $(OUT)/ota_sstar_pioneer5_sdp_20230413_121058/sstar_pioneer5_sdp-target_files-eng.payne.chen.zip

需要注意的是 sstar_pioneer5_sdp-target_files-eng.payne.chen.zip 文件对于每个编译服务器名字不一样,前缀 sstar_pioneer5_sdp-target_files 都是一样的,只需要通过前缀就能够找到这个文件。

最终生成的增量 OTA 包位于 $(OUT)/incremental_ota_update.zip

2. 通过 OTA 升级包进行系统升级

2.1 recovery 模式手动升级

所有在 recovery 模式下手动升级都需要主动进入 recovery 模式,可以通过 adb 如下命令进入 recovery:

adb reboot recovery

或者直接在终端(串口)中直接输入如下指令进入:

reboot recovery

当然还有其他方式进入,这里不一一介绍。

2.1.1 sideload 升级

进入 recovery 之后使用遥控器或者其他输入设备选择 recovery 界面的如下选项:

Apply update from ADB

然后使用 adb 输入如下命令进行 OTA 包安装:

adb sideload $(path_to_file)/full_ota_update.zip (adb sideload $(path_to_file)/incremental_ota_update.zip)

path_to_file 为 OTA 升级包的路径。

2.1.2 u 盘或者 SD 卡升级

进入 recovery 之后使用遥控器或者其他输入设备选择 recovery 界面的如下选项:

Apply update from SD card

此时必须保证已经有 u 盘或者 SD 卡处于插入状态(如果两个都处于插入状态则会默认只查找 SD 卡),同时保证 u 盘和 SD 卡的格式为 VFAT 格式,否则会出现挂载不上问题。如果正常挂载的话会出现挂载之后的文件列表,选择相应的文件之后点击确定便可以正常进行升级。

需要注意 u 盘必须格式化为 FAT32 格式,否则无法在 recovery 挂载成功。

2.2 bootloader 手动升级

可以 bootloader 通过 BCB 写 misc 分区主动触发升级流程,具体指令如下:

bcb load 0 misc
bcb set command boot-recovery
bcb set recovery recovery:--update_package=/sdcard/full_ota_update.zip
bcb store

其中 /sdcard/full_ota_update.zip 为 OTA 升级包存放的路径。

这里支持 OTA 升级包存放在 u 盘,SD 卡,data 以及 cache 分区。需要注意的是,因为 u 盘在 recovery 下的挂载路径为 udisk,因此如果放在 u 盘,则需要以 udisk 作为路径开头,例如 /udisk/full_ota_update.zip。同时 SD 卡在 recovery 下的挂载路径为 sdcard,因此如果放在 SD 卡,则需要以 sdcard 作为路径开头,例如 /sdcard/full_ota_update.zip。以此类推,放在 data 以及 cache 分区的话则需要以分区挂载路径开头,例如 /data/full_ota_update.zip/cache/full_ota_update.zip。如果放在 cache 分区的话需要保证 cache 分区大小足够大能够放下 OTA 升级包。

2.3 normal 模式升级

2.3.1 APK 自动升级

这个模式升级是最常规的 OTA 升级方式,APK 一般需要通过调用如下类并传入 OTA 包的路径触发设备重启进入 recovery 并自动升级。

      @RequiresPermission(android.Manifest.permission.RECOVERY)
      public static void installPackage(Context context, File packageFile)
              throws IOException {
          installPackage(context, packageFile, false);
      }

这里 OTA 包同样支持放在 u 盘,SD 卡,data 以及 cache 分区,但是由于 u 盘和 SD 卡在 normal 模式和 recovery 模式下挂载的点不一样,因此这里不能直接传入 OTA 包在 normal 模式下路径,而是要按照上节所说的传入相应的以 sdcard 或者 udisk 开头的路径,而如果放在 data 或者 cache 分区的话,由于这两个分区在 recovery 和 normal 下挂载路径一致,因此可以直接传入 normal 下的相应路径,例如 data/ota_package/full_ota_update.zip

2.3.2 指令手动升级

在 normal 模式下还可以通过 adb 或者串口指令来手动触发 ota 升级,具体指令如下:

adb root
adb shell "echo "--update_package=/sdcard/full_ota_update.zip" > /cache/recovery/command"
adb reboot recovery

其中 /sdcard/full_ota_update.zip 为 OTA 升级包存放的路径。

这里支持 OTA 升级包存放在 u 盘,SD 卡以及 cache 分区。需要注意的是,因为 u 盘在 recovery 下的挂载路径为 udisk,因此如果放在 u 盘,则需要以 udisk 作为路径开头,例如 /udisk/full_ota_update.zip。同时 SD 卡在 recovery 下的挂载路径为 sdcard,因此如果放在 SD 卡,则需要以 sdcard 作为路径开头,例如 /sdcard/full_ota_update.zip。以此类推,放在 cache 分区的话则需要以分区挂载路径开头,例如 /cache/full_ota_update.zip。如果放在 cache 分区的话需要保证 cache 分区大小足够大能够放下 OTA 升级包。

此外,如果 ota 包放在 data 分区,因为 data 分区加密,所有指令有所差异,具体如下:

adb root
adb push full_ota_update.zip /data/ota_package/full_ota_update.zip
adb shell uncrypt /data/ota_package/full_ota_update.zip /cache/recovery/block.map
adb shell "echo "--update_package=@/cache/recovery/block.map" > /cache/recovery/command"
adb reboot recovery

3. OTA 升级分区支持

3.1 SigmaStar OTA 升级支持分区

分区 AB 设备 non-AB 设备
system.img 支持 支持
vendor.img 支持 支持
odm.img 支持 支持
vbmeta.img 支持 支持
boot.img 支持 支持
vendor_boot.img 支持 支持
supper.img 支持 支持
cache.img / 不支持
misc.img 不支持 不支持
partition-able.img 不支持 不支持
userdata.img 不支持 不支持
recovery.img 支持 不支持
dtbo.img 支持 支持
factory.img 不支持 不支持
logo.img 支持 支持
bootloader.img 支持 支持
env.img 支持 支持
riscv.img 支持 支持

3.2 客制化 OTA 升级分区

由于 recovery 下升级分区主要是依赖 fstab 才能知道分区对应的块设备,而在正常模式下很多分区都是没有挂载的,例如 dtbo,vendor_boot 等原生分区和 env 等客制化分区。因此需要在 recovery 下使用和正常模式不同的 fstab,Android recovery 模式下的分区表由 TARGET_RECOVERY_FSTAB 变量指定。

TARGET_RECOVERY_FSTAB := $(LOCAL_PATH)/fstab.sstar.recovery

而在 recovery 下的 fstab 必须对所有需要升级的分区进行指定,如果没有文件系统则挂载成裸分区形式,例如:

/dev/block/by-name/env                 /env   emmc     defaults      defaults

因此如果要客制化分区需要在 recovery 的 fstab 下添加对应配置。

3.2.1 AB 设备客制化 OTA 升级分区

AB 设备客制化 OTA 升级分区前提为客制化的分区支持 AB,如果分区本身不支持 AB 分区的话,则无法进行升级。如果分区支持 AB,可以通过在 BoardConfig-common.mk 中的如下变量中添加需要升级的分区:

AB_OTA_PARTITIONS :=\
    custom

其中 custom 为编译出客制化分区的名字。

同时必须保证客制化的分区的编译伪目标被如下变量引用:

INSTALLED_RADIOIMAGE_TARGET += $(SSTAR_INSTALLED_CUSTOMIMAGE_TARGET)
BOARD_PACK_RADIOIMAGES += custom.img

SSTAR_INSTALLED_CUSTOMIMAGE_TARGET 为 custom.img 分区的编译伪目标。该操作的意义在于将客制化的分区打包进 target file 中。

3.2.2 non-AB 设备客制化 OTA 升级分区

non-AB 设备客制化 OTA 升级分区主要是通过 releasetools.py 脚本实现,同样在保证分区会被 INSTALLED_RADIOIMAGE_TARGET 以及 BOARD_PACK_RADIOIMAGES 正确引用之后通过客制化修改 releasetools.py 脚本来实现升级客制化的分区,脚本位置如下:

/device/sigmastar/common/releasetools/releasetools.py

以 env 分区为例添加如下打包命令:

def InstallEnv(env_img, input_zip, info):
  common.ZipWriteStr(info.output_zip, "env.img", env_img)
  info.script.Print("Update env image.")
  info.script.WriteRawImage("/env", "env.img")

# 打包到全量包
def FullOTA_InstallEnd(info):
  ...
  try:
    env_img = info.input_zip.read("IMAGES/env.img")
    print "wirte env now..."
    InstallEnv(env_img, info.input_zip, info)
  except KeyError:
    print "warning: no env.img in input target_files; not flashing env"

# 打包到增量包
def IncrementalOTA_InstallEnd(info):
  ...
  # Check env image change or not
  try:
    target_env = info.target_zip.read("IMAGES/env.img")
  except KeyError:
    target_env = None

  try:
    source_env = info.source_zip.read("IMAGES/env.img")
  except KeyError:
    source_env = None

  if (target_env != None) and (target_env != source_env):
    print "write env now..."
    InstallEnv(target_env, info.target_zip, info)
  else:
    print "env unchange, skipping"

需要注意的是,如果分区的挂载方式为 ext4 的话,默认分区打包时一定不能选择压缩镜像的模式,例如 logo 分区在分区表规划的大小为 4M,则编译出来必须也是 4M,而不能是压缩过的大小,否则 OTA 升级过程中会出现升级之后的分区异常或者为空。

3.3 升级 IPL_PRE 分区

正常而言,IPL_PRE 分区在 OTA 流程中不会被升级,因为该分区理论上不应该被升级。如果因为特殊需求要在 OTA 流程中升级该分区则可以通过在设备端的 device_common.mk 文件中开启以下宏并重新编译 OTA 升级包即可以对该分区进行升级(ab 以及非 ab 设备都支持)。

#SSTAR_OTA_UPDATE_IPL_PRE := true