如何实现应用内自动检查更新并下载新版本?

解读

国内 Android 应用无法依赖 Google Play 的静默更新,必须在 App 内部完成“检查-下载-安装”闭环。面试官想考察的是:

  1. 对国内合规分发渠道(应用宝、华为 AppGallery、小米商店、OPK、CDN 直链等)差异的理解;
  2. 对 Android 7.0+ FileProvider、8.0+ 安装未知来源权限、9.0+ 明文流量限制、10+ 分区存储、12+ 悬浮窗/通知权限、13+ 通知权限、14+ 前台服务类型声明的适配经验;
  3. 对后台下载省电、省流量、断点续传、完整性校验、异常回退、用户隐私合规(工信部 164 号文、26 条)的落地思路;
  4. 对增量更新(bsdiff/patch)与热修复(Tinker、Sophix)边界的认知。

一句话:既要保证业务闭环,又要通过国内各大应用市场隐私合规扫描,还不能被系统杀后台。

知识点

  1. 版本检查协议:自建后台返回 json(versionCode、minSdk、forceUpdate、deltaPatchUrl、hash)、带 RSA 签名防篡改。
  2. 网络层:Retrofit + Kotlin Coroutines,下载用 OkHttp + ProgressResponseBody 暴露进度;支持断点续传 Range & If-Range。
  3. 存储:Android 10 以前 /external/Android/data/pkg/files/apk;Android 10+ 使用 MediaStore.DOWNLOADS 或 Context.getExternalFilesDir(),无需权限;若 targetSdk=34 需在 manifest 声明 android:requestLegacyExternalStorage=false。
  4. 安装未知来源权限:Android 8.0+ 需动态请求 REQUEST_INSTALL_PACKAGES,用户拒绝后引导到设置页;低于 8.0 在 manifest 静态声明。
  5. FileProvider:7.0+ 必须通过 content:// 分享,路径配置 <external-files-path name="apk" path="apk/" />,避免 file:// 暴露。
  6. 前台服务:Android 9.0+ 下载需 startForegroundService(),并立即调用 startForeground() 展示进度通知;Android 14 需指定 android:foregroundServiceType="dataSync"。
  7. 完整性校验:下载完先比对 SHA-256,再比对 V2/V3 签名公钥指纹,防止 CDN 劫持或运营商插包。
  8. 强制更新:使用 WorkManager 设置 EXPEDITED 加急任务,保证即使应用被杀死也能弹通知;非强制更新用普通 CoroutineWorker。
  9. 增量更新:服务端生成 bsdiff 差分包,客户端用 native 库 bspatch 合并,合并后校验全量 hash,失败回退全量包;差分包大小需 <30% 才划算。
  10. 合规与隐私:下载前弹窗告知“版本更新”目的,获取用户明确同意;后台接口返回的更新信息不得携带 IMSI、MAC、OAID 等个人数据;首次启动隐私政策里需声明“自更新”功能。
  11. 异常回退:下载过程监听电量、网络类型(移动数据时弹“是否继续”)、存储空间不足;失败 3 次后提示前往浏览器下载官网包。
  12. 安装后自启动:Android 12+ 禁止后台启动 Activity,需在用户点击通知后跳转至系统安装器,安装完成后监听 PACKAGE_REPLACED 广播,重启应用用 Activity.recreate() 即可,无需自启动权限。

答案

  1. 协议层
    客户端启动时携带当前 versionCode、channel、abi、osVersion 请求后台 /update/check;后台返回: { "versionCode": 23456, "force": false, "fullUrl": "https://cdn.xxx.com/app-release-v23456.apk", "deltaUrl": "https://cdn.xxx.com/app-v23000-to-v23456.patch", "size": 18234567, "hash": "sha256:abcd...", "signature": "rsa256:xxxx..." } 客户端用内置公钥验签,失败直接丢弃。

  2. 弹窗策略
    非强制更新:WorkManager 延迟 24 h 后再次检查;强制更新:立即弹全屏 Dialog,不可取消。

  3. 下载实现
    创建 DownloadWorker : CoroutineWorker(),在 doWork() 中:

    • 获取 StorageManager 判断剩余空间 > size*1.2;
    • 构建 OkHttpClient,addNetworkInterceptor(ProgressInterceptor{ progress -> updateNotification(progress) });
    • 若本地已存在同名临时文件,header("Range", "bytes=${file.length()}-");
    • 写入 Context.getExternalFilesDir(null)/apk/update.apk,下载完 renameTo(update.apk);
    • 计算 SHA-256 与 json.hash 比对,不一致则返回 Result.failure()。
  4. 安装流程

    • Android 8.0+ 先检查 packageManager.canRequestPackageInstalls(),false 则 startActivity(Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, uri));
    • 通过 FileProvider.getUriForFile() 生成 content:// uri;
    • Intent(Intent.ACTION_VIEW).setDataAndType(uri, "application/vnd.android.package-archive").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    • startActivity(intent);
    • 注册广播接收器 ACTION_PACKAGE_REPLACED,过滤自身包名,收到后发送本地广播重启主 Activity。
  5. 失败与回退

    • 下载中断:WorkManager 默认重试 3 次,指数退避;
    • 校验失败:删除文件,回退到全量包重新下载;
    • 用户拒绝安装:记录 SP 拒绝时间,3 天内不再弹强制更新;
    • 存储空间不足:提示清理缓存,同时把下载任务改为 BLOCKED 状态,等待空间充足再次调度。
  6. 合规细节

    • 下载通知渠道 ID 为 "update",重要性 IMPORTANCE_LOW,避免骚扰;
    • 移动数据下载前弹窗确认,记录用户选择;
    • 隐私政策中单独章节描述“更新模块”收集的数据(仅版本号、渠道号),并在首次弹窗提供《更新与隐私说明》小字链接。

一句话总结:用 WorkManager 做调度,OkHttp 做断点续传,FileProvider 解决 7.0+ 文件暴露,8.0+ 动态申请安装权限,下载完强校验再安装,全程通知保活,失败可回退官网包,隐私合规一步到位。

拓展思考

  1. 如果应用需要上架华为、小米、OPPO 等商店,但商店要求“禁止自更新”,如何灰度?
    思路:在 AndroidManifest 里用 <meta-data android:name="enable_inapp_update" android:value="${enableUpdate}" />,Gradle 根据 buildVariant 动态注入;商店包关闭开关,自有渠道包打开,同时后台根据 channel 下发不同策略,避免被下架。

  2. 如何做到“无感知热升级”而不走安装器?
    采用 Tinker/Sophix 合成差分 dex/so,下次启动加载补丁;但补丁无法改 AndroidManifest 且受 Google Play 政策限制,国内合规需向工信部报备“热修复能力”,并确保补丁内容不触碰安全红线。

  3. 下载过程如何防劫持?
    除了 HTTPS 证书强校验(OkHttp certificatePinner),可预埋双证书(主+备),同时启用 APK Signature Scheme V3 的轮替机制,保证即使旧证书泄漏也能通过新证书安装;CDN 回源增加边缘签名,客户端二次验签。

  4. 折叠屏/大屏交互差异:更新弹窗在展开态使用双栏 Dialog,保证按钮不超出拇指热区;下载进度通知利用 12+ 的进度指示器模板,可直接在通知栏取消或暂停。

  5. 未来趋势:Google 在推“App Update API”的灵活/强制模式,但国内不可用;可提前封装统一接口,当设备带有 GMS 时优先走 Google 模式,无 GMS 时回落到自研方案,一套代码兼容海内外。