Skip to content

应用更新流程

追踪普通 APK 替换安装的签名、版本、数据保留、冻结和提交路径。

基于android-17.0.0_r1
AndroidPackageManagerService应用更新签名校验源码阅读

应用更新流程 ​

本文承接 安装全景、安装前检查、降级安装 和 系统应用更新。主题限定为普通 APK 的 replace install:新包如何确认自己可以接管旧包的数据,何时冻结旧进程,旧 PackageSetting 如何被新对象替换,以及更新失败时哪些状态仍保持不变。系统包的 disabled 记录和 OTA 恢复不在本文重复展开。

1. 更新对象 ​

1.1 数据 owner ​

更新判断不是“目录中存在同名 APK”,而是 Settings.getPackageLPr(packageName) 返回已有 PackageSetting。它拥有 version、signing details、appId、per-user 状态和 code path;AndroidPackage 是当前可运行对象,可能因 DELETE_KEEP_DATA 而为空,但设置仍保留。

1.2 replace 标志 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java

java
if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
    final String renamedPackage =
            mPm.mSettings.getRenamedPackageName(pkgName);
    if (renamedPackage != null) {
        pkgName = renamedPackage;
    }
    synchronized (mPm.mLock) {
        ps = mPm.mSettings.getPackageLPr(pkgName);
        if (ps != null) {
            replace = true;
        }
    }
}

replace 是本次 request 的事实状态,后续签名、版本和删除旧包都围绕它展开。renamed package 先归一化,避免把历史重命名记录当成新安装。

2. 签名校验 ​

2.1 调用方 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java

java
final boolean compareCompat =
        ReconcilePackageUtils.isCompatSignatureUpdateNeeded(
                mPm.getSettingsVersionForPackage(parsedPackage));
final boolean compareRecover =
        ReconcilePackageUtils.isRecoverSignatureUpdateNeeded(
                mPm.getSettingsVersionForPackage(parsedPackage));
final boolean compatMatch =
        PackageManagerServiceUtils.verifySignatures(
                signatureCheckPs, signatureCheckSus, null,
                parsedPackage.getSigningDetails(),
                compareCompat, compareRecover, isRollback);

InstallPackageHelper 根据 settings 版本决定是否尝试旧签名格式兼容和证书恢复,然后把已有包、shared UID 和新包 SigningDetails 交给工具函数。普通 replace 的 isRollback 为 false;Rollback 专题会额外放宽历史签名能力。

2.2 能力与 lineage ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java

java
boolean match = parsedSignatures.checkCapability(
        pkgSetting.getSigningDetails(),
        SigningDetails.CertCapabilities.INSTALLED_DATA)
        || pkgSetting.getSigningDetails().checkCapability(
                parsedSignatures,
                SigningDetails.CertCapabilities.ROLLBACK);

if (!match && compareCompat) {
    match = matchSignaturesCompat(packageName,
            pkgSetting.getSignatures(), parsedSignatures);
    compatMatch = match;
}
if (!match && compareRecover) {
    match = matchSignaturesRecover(packageName,
            pkgSetting.getSigningDetails(), parsedSignatures,
            SigningDetails.CertCapabilities.INSTALLED_DATA)
            || matchSignaturesRecover(packageName, parsedSignatures,
            pkgSetting.getSigningDetails(),
            SigningDetails.CertCapabilities.ROLLBACK);
}
if (!match) {
    throw new PackageManagerException(
            INSTALL_FAILED_UPDATE_INCOMPATIBLE,
            "Existing package " + packageName
                    + " signatures do not match newer version; ignoring!");
}

签名不是简单 byte equality:签名 lineage 中的 capability 决定新证书能否接管已安装数据;兼容/恢复分支只在 settings 版本要求时启用。失败抛出 INSTALL_FAILED_UPDATE_INCOMPATIBLE,发生在 commit 前,因此旧包仍是当前运行对象。

2.3 shared UID ​

若包属于 shared UID,verifySignatures 还会用 canJoinSharedUserId 对照 shared user 的 signing lineage;不匹配返回 INSTALL_FAILED_SHARED_USER_INCOMPATIBLE。这条检查防止更新包以不兼容证书进入已有 UID,从而获得同 UID 其他包的数据和权限。

3. 版本与数据 ​

3.1 版本检查 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java

java
final Pair<Integer, String> versionCheck = verifyReplacingVersionCode(
        pkgLite, request.getRequiredInstalledVersionCode(), installFlags);
if (versionCheck.first != PackageManager.INSTALL_SUCCEEDED) {
    throw new PrepareFailure(versionCheck.first, versionCheck.second);
}

普通更新默认禁止降级;requiredInstalledVersionCode 还可要求当前版本必须精确匹配,避免并发更新覆盖了调用方预期的旧版本。长版本号、base revision 和 split revision 的比较细节见 降级安装。

3.2 数据路径一致性 ​

更新不会在 prepare 阶段销毁旧数据。旧 PackageSetting 的 CE/DE inode、appId 和 per-user 状态会被传给新设置;commit 后 AppDataHelper 为新 package 准备同一用户的数据目录。若 Manifest 改变 PROPERTY_NO_APP_DATA_STORAGE,checkNoAppStorageIsConsistent 会拒绝不一致的更新,避免包在“有数据/无数据存储”之间悄悄切换。

3.3 update owner ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java

java
final String oldUpdateOwner = pkgAlreadyExists
        ? oldPkgSetting.getInstallSource().mUpdateOwnerPackageName : null;
final boolean isUpdate = pkgAlreadyExists && oldPkgSetting.getInstalled(userId);
final boolean requestOwnership =
        (request.getInstallFlags()
                & PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0;
final boolean sameOwner = TextUtils.equals(
        oldUpdateOwner, installSource.mInstallerPackageName);

if (!isUpdate || !requestOwnership || !sameOwner) {
    installSource = installSource.setUpdateOwnerPackageName(null);
}
pkgSetting.setInstallSource(installSource);

更新 owner 是安装来源状态的一部分,不等于签名 owner。源码会考虑当前用户是否已安装、installer 是否相同、是否请求 ownership 以及 denylist;installer 变化时可清除 owner。它影响后续谁能继续管理更新,不改变 APK 的签名校验结果。

4. 冻结与提交 ​

4.1 冻结旧包 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java

java
final PackageFreezer freezer = freezePackageForInstall(
        packageName, UserHandle.USER_ALL,
        installRequest.getInstallFlags(),
        "installPackageLI",
        ApplicationExitInfo.REASON_PACKAGE_UPDATED,
        installRequest);
installRequest.setFreezer(freezer);

冻结发生在 reconcile 之后、commit 之前。它保护旧进程不在新旧 code path 切换期间继续启动;InstallRequest 持有 freezer,后续成功或失败都由收尾逻辑关闭。

4.2 替换与注册 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java

java
if (installRequest.isInstallReplace()) {
    final AndroidPackage oldPackage = mPm.mPackages.get(packageName);
    mDeletePackageHelper.executeDeletePackage(
            reconciledPkg.mDeletePackageAction, packageName,
            true, allUsers, false,
            installRequest.isKeepArtProfile());
}

final AndroidPackage pkg = commitReconciledScanResultLocked(
        reconciledPkg, allUsers);
updateSettingsLI(pkg, allUsers, installRequest);

普通 APK replace 先通过 DeletePackageHelper 移除旧运行对象和旧设置关联,再把 reconcile 后的新扫描结果写入 mPackages、Settings、Resolver 和 PermissionManager。系统包更新在这里走 disabled system 分支,不能把两者的删除语义混为一谈。

4.3 提交后状态 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java

java
final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
if (ps != null) {
    installRequest.setNewUsers(ps.queryInstalledUsers(allUsers, true));
    ps.setUpdateAvailable(false);
}
if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
    mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
    mPm.updateInstantAppInstallerLocked(packageName);
}

新包提交后,request 记录实际 installed users,Settings 更新 sequence number,后续广播和回调使用这些用户集合区分 update 与 first install。更新成功不代表 dexopt、app data 或广播已经全部完成,它们属于 post-commit 阶段。

5. 失败与恢复 ​

5.1 前置失败 ​

签名、版本、shared UID、权限声明或 shared library 检查失败时,流程不会进入 commitPackagesLocked。staging code path 和预先创建的 App ID 由失败收尾清理,旧包和用户数据保持原状。

5.2 commit 后异常 ​

Android 17 源码在 commitReconciledScanResultLocked 注释中明确提醒:commit 中仍可能发生不可预测的 I/O 异常,导致中间状态不一致。因此“原子更新”是设计目标而非所有外部副作用的绝对事务;排查 commit 异常要检查 Settings、Resolver、App ID 和 code path 是否已经部分改变。

5.3 进程与依赖消费者 ​

更新共享库 host 时,源码会杀死依赖客户端(除特定 static library 新版本场景)。普通应用更新则由 freezer/ActivityManager 协作停止旧进程。更新完成后,Resolver、PermissionManager、AppsFilter 和 PackageInstaller 广播分别消费新状态;任一消费者缓存未刷新都可能表现为“版本已更新但仍解析旧组件”。

6. 验证方法 ​

6.1 正常更新 ​

用同一签名、递增 versionCode 的 v1/v2 APK 更新,检查 pm path、dumpsys package 的 version、user 状态和 installer 信息。关键断言是 code path 切换到新版本、用户数据仍可访问、旧进程被停止或重启;这不证明所有 runtime permission 都自动变化。

6.2 签名失败 ​

使用不同签名但同包名的 APK,预期 INSTALL_FAILED_UPDATE_INCOMPATIBLE,且 mPackages 仍指向旧 APK。若 shared UID 场景使用不兼容 lineage,预期 INSTALL_FAILED_SHARED_USER_INCOMPATIBLE。

6.3 并发版本 ​

设置 requiredInstalledVersionCode 为旧版本,同时让另一个更新先完成,再提交当前 session。预期返回 INSTALL_FAILED_WRONG_INSTALLED_VERSION,证明 required version 是并发保护条件,不是普通 versionCode 降级检查。

bash
adb shell pm path <package.name>
adb shell dumpsys package <package.name> | grep -E 'versionCode|codePath|installerPackageName'

7. 源码路线 ​

  1. InstallPackageHelper.preparePackage:替换对象和旧设置来源。
  2. PackageManagerServiceUtils.verifySignatures:签名 capability、兼容和 shared UID。
  3. verifyReplacingVersionCode:required version 与降级边界。
  4. commitPackagesLocked:旧包删除和新包提交顺序。
  5. commitReconciledScanResultLocked:update owner、冻结检查和消费者注册。
  6. deleteInstalledPackageLIF/RemovePackageHelper:旧状态、数据和 code path 的处理。

普通应用更新是一条“先证明可替换,再冻结并提交”的路径:Settings 提供旧包事实,签名 lineage 保护数据接管,版本检查防止意外倒退,DeletePackageHelper 清理旧运行对象,InstallPackageHelper 最终把新对象注册给所有消费者。理解这些 owner 和屏障,才能区分更新失败、状态提交异常与应用自身数据兼容问题。