如何设计一个安全可靠的 OTA 升级流程(含校验、断点续传)?

解读

面试官问的是“设计”,不是“背诵”。他要的是一条从云端到终端、从下载到重启、从异常到回滚的完整链路,且必须兼顾国内合规(工信部 24 小时可回退要求、个人信息保护、网络安全审查)、国内网络环境(弱网、运营商劫持、CDN 回源失败)、国内硬件生态(A/B 无缝升级普及率不足、部分厂商仍用 recovery 卡刷)。因此,回答必须体现:

  1. 安全:完整性、真实性、机密性、抗重放、防回滚
  2. 可靠:断点续传、断电续升、降级保护、可灰度、可回滚
  3. 合规:用户明示同意、日志留存、失败上报、24h 回退通道
  4. 性能:差分包的 bsdiff/bspatch 压缩、下载流量省 70% 以上、升级时间 <20 min
  5. 可运维:云端灰度、设备分群、实时熔断、后台看板

知识点

  1. 国内 OTA 合规:工信部《移动智能终端应用软件预置和分发管理暂行规定》、TC260 个人信息安全规范、Android 13 隐私沙盒、targetSdk 升级强制要求
  2. 安全机制:
    • 签名:APK 采用 v3 方案,整包采用 avb2.0 签名校验,recovery 公钥硬编码在 boot 镜像,防篡改
    • 加密:HTTPS + TLS1.3 + 证书固定(SHA-256 Pin),防止 CDN 缓存投毒;差分包可再 AES-256-GCM 加密,密钥通过 TEE 派生
    • 防回滚:avb rollback_index 写入 RPMB,防回滚熔断;同时云端记录 last_update_version,设备端二次校验
    • 抗重放:升级令牌 nonce + timestamp + ECDSA 签名,一次有效
  3. 可靠传输:
    • 断点续传:HTTP Range + 自定义分块校验(SHA-256 per 4 MB),本地 sqlite 记录已下载区间;支持多 CDN 切换,失败 3 次自动降级到备用域名
    • 断电续升:升级包写入后立刻 sync + fsync,metadata 写进 /misc 分区;recovery 阶段先校验 metadata 完整性再继续
  4. 差分策略:
    • 源版本→目标版本采用 bsdiff 生成差分包,再经过 lzma2 二次压缩;对 system、vendor、product 分区分别做 imgdiff,保证块对齐
    • 升级前校验源版本 system.img hash,防止第三方 ROM 刷机导致差分失败
  5. 升级流程:
    • 预检:电量 >30% 或插电、存储剩余 >2×包体、用户前台明示同意(符合国标 GB/T 35273)
    • 灰度:按设备型号+地区+系统版本三维哈希取模,先 1% → 5% → 20% → 100%,每阶段观察 24h 崩溃率 <0.3% 才继续
    • 下载:WorkManager 约束网络=非计费、充电时触发;前台通知渠道 IMPORTANCE_LOW,用户可随时暂停
    • 校验:下载完先验签(RSA-4096 + SHA-256),再验哈希(SHA-256 manifest),最后写 RPMB rollback_index
    • 安装:A/B 无缝升级走 update_engine,非 A/B 走 recovery,均先备份 boot 与 dtbo 到 /cache/backup,支持 24h 回退
    • 回滚:升级失败或用户投诉,云端推送“回退指令”,设备重启进入 recovery 应用备份镜像,回退后上报结果
  6. 监控与熔断:
    • 实时日志:升级各阶段埋点(download_percent、verify_time、install_error_code)通过 MQTT 上报,延迟 <5s
    • 熔断规则:任意阶段错误率 >5% 或 ANR/Crash 上升 >0.5% 即自动暂停该版本灰度
  7. 性能优化:
    • 后台下载限速 2 MB/s,前台不限;支持 HTTP/2 多路复用,减少握手 RTT
    • 升级重启后首次开机做 dex2oat 后台编译,优先级 idle-only,防止卡顿
  8. 异常场景:
    • 用户关机:下次开机 BootReceiver 重新触发 WorkManager,继续断点下载
    • 存储不足:提前计算所需空间,弹窗引导清理;支持外置 SD 卡缓存,但校验仍在内部存储
    • root 设备:检测到 su 文件直接拒绝升级,防止绕过签名校验

答案

“我会把 OTA 拆成云端、管道、终端三段来设计。
第一段云端:

  1. 构建系统打出全量包与差分包,使用 avbtool 签名并生成 rollback_index;
  2. 灰度平台按‘型号+地区+版本’三维哈希取模,先 1% 试水;
  3. 监控看板实时收集 crash 率,>0.3% 自动熔断。

第二段管道:

  1. 下载域名用双 CDN + 主源站,HTTPS 证书固定,TLS1.3;
  2. 断点续传采用 HTTP Range + 4 MB 分块 SHA-256 校验,本地 sqlite 记录区间;
  3. 差分包二次 AES-256-GCM 加密,密钥由 TEE 派生,防缓存投毒。

第三段终端:

  1. 预检:电量、存储、root、解锁状态一票否决;
  2. 下载:WorkManager 约束非计费网络 + 充电,前台通知可暂停;
  3. 校验:下载完先 RSA 验签,再 SHA-256 验整包,最后把 rollback_index 写 RPMB;
  4. 安装:A/B 设备走 update_engine,非 A/B 走 recovery,均先备份 boot/dtbo 到 /cache/backup;
  5. 回滚:升级失败或用户 24h 内投诉,云端下发回退指令,recovery 把备份写回;
  6. 埋点:各阶段错误码实时上报,错误率 >5% 自动停灰度。

这样兼顾了国内合规、弱网、劫持、断电、用户回退诉求,整条链路可灰度、可熔断、可回滚,安全可靠。”

拓展思考

  1. 如果以后做“零停机”升级,可否把 update_engine 搬到用户空间,利用 dm-snapshot 实现秒级切换?需要解决 snapshot cow 设备空间膨胀与 io 性能回退问题。
  2. 国内厂商开始推“虚拟 A/B”方案,把快照数据放 /data 分区,减少存储占用,但 /data 加密后如何在内核阶段挂载?需结合 metadata 加密密钥与 Keymaster 早期启动。
  3. 未来工信部可能要求“升级前用户二次指纹确认”,如何把 TEE 的 auth_token 与 OTA 令牌绑定,防止恶意程序代点“同意”?
  4. 折叠屏设备有主副两片存储,升级包是否拆成两片并行刷写?需设计双通道校验与双备份回滚,复杂度翻倍。