Skip to content

调试与发布签名

解析签名切换、testOnly、debuggable 与数据保留边界。

AndroidPMS签名

调试与发布签名 ​

调试签名和发布签名的核心差异,不是 keystore 文件名或证书主体,而是 APK 的 SigningDetails 是否仍被 PackageManager 视为同一个包的可信更新者。Android 17 对签名切换没有“开发模式自动放行”的通用后门:普通更新仍要满足 lineage capability;debuggable 和 testOnly 只影响特定安装策略。

1. 三种状态 ​

状态来源影响
signerAPK v1/v2/v3/v4 验证身份、更新、shared UID、签名权限
FLAG_DEBUGGABLEmanifest/application调试器、部分测试策略
FLAG_TEST_ONLYmanifest/application安装命令是否要求 -t

调试构建通常同时使用 debug key、debuggable=true 和 testOnly=true,但三者不是同一个字段。换一把 key 不会自动设置 FLAG_DEBUGGABLE;把 debuggable 设为 true 也不会让新证书通过更新签名检查。

2. 安装时收集签名 ​

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

java
if (request.getSigningDetails() != SigningDetails.UNKNOWN) {
    parsedPackage.setSigningDetails(request.getSigningDetails());
} else {
    final ParseTypeImpl input =
            ParseTypeImpl.forDefaultParsing();
    final ParseResult<SigningDetails> result =
            ParsingPackageUtils.getSigningDetails(
                    input, parsedPackage,
                    false /* skipVerify */);
    if (result.isError()) {
        throw new PrepareFailure(
                "Failed collect during installPackageLI",
                result.getException());
    }
    parsedPackage.setSigningDetails(result.getResult());
}

安装请求如果已经携带签名详情就复用,否则从 APK 解析;skipVerify=false 表示不是只读证书。签名详情写入 parsedPackage,随后由更新、shared UID 和权限路径消费。

3. 更新签名门禁 ​

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

java
SigningDetails parsedPkgSigningDetails =
        parsedPackage.getSigningDetails();
SigningDetails oldPkgSigningDetails =
        oldPackageState.getSigningDetails();
if (!parsedPkgSigningDetails.checkCapability(
        oldPkgSigningDetails,
        SigningDetails.CertCapabilities.INSTALLED_DATA)
        && !oldPkgSigningDetails.checkCapability(
                parsedPkgSigningDetails,
                SigningDetails.CertCapabilities.ROLLBACK)) {
    if (!isRollback || !oldPkgSigningDetails.hasAncestorOrSelf(
            parsedPkgSigningDetails)) {
        throw new PrepareFailure(
                INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                "New package has a different signature: "
                        + pkgName11);
    }
}

新发布签名替换调试签名时,若没有 v3 lineage,INSTALLED_DATA 和 ROLLBACK 都失败,安装返回 INSTALL_FAILED_UPDATE_INCOMPATIBLE。卸载旧包再安装可以获得新 appId 状态,但会失去原应用的数据连续性;这不是签名检查被绕过,而是更新关系被切断。

4. 回滚方向 ​

普通更新要求“新 signer 信任旧 signer 的已安装数据”;回滚要求“旧 signer 允许回到新 signer”,因此代码反向调用 old.checkCapability(new, ROLLBACK)。此外,rollback 场景还要求旧 signer 的 lineage 包含待回滚 signer,防止任意历史 APK 被当作回滚包。

5. debuggable 作用 ​

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

java
// On debuggable platform builds, downgrades are permitted even for
// non-debuggable packages to make testing easier.
if (Build.IS_DEBUGGABLE) {
    // 允许测试环境使用更宽松的降级策略
}

Android 17 将平台是否可调试用于部分 downgrade 策略;它不改变 verifySignatures 的 signer capability 门禁。即使设备是 userdebug,换成另一把 debug key 也不能直接覆盖已安装的 release-signed 包。

6. testOnly 限制 ​

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

java
if (parsedPackage.isTestOnly()
        && (installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) {
    throw new PrepareFailure(
            INSTALL_FAILED_TEST_ONLY,
            "Failed to install test-only apk. Did you forget to add -t?");
}

testOnly 检查的是安装 flag INSTALL_ALLOW_TEST,通常对应 adb install -t。它解决的是“测试 APK 是否允许安装”,不是“证书是否可信”;加 -t 不会让不同签名的更新通过。

7. shared UID ​

java
boolean capabilityGranted =
        packageSigningDetails.checkCapability(
                sharedUserSigningDetails,
                SigningDetails.CertCapabilities.SHARED_USER_ID)
        || sharedUserSigningDetails.checkCapability(
                packageSigningDetails,
                SigningDetails.CertCapabilities.SHARED_USER_ID);

调试版和发布版若声明相同 sharedUserId,必须满足 shared UID signer/capability 关系。即使包名相同,签名切换也不能直接继续使用 UID 1000 或其他 shared UID;失败通常表现为 shared user incompatible,而非普通 update incompatible。

8. 数据迁移的边界 ​

签名相同或 lineage 允许 INSTALLED_DATA 时,更新沿用原 appId 和数据目录。签名切换失败时,三个可行路径含义不同:

  1. 使用 v3 key rotation 发布,保留 lineage 和需要的 capability,继续更新。
  2. 卸载旧包后重新安装,获得新签名身份,但数据通常不再自动关联。
  3. 在测试设备清除应用数据后安装新包;这是环境清理,不是 PMS 签名豁免。

FLAG_DEBUGGABLE 不会把旧数据迁移给不兼容 signer;数据连续性由 SigningDetails.checkCapability(INSTALLED_DATA) 决定。

9. 排查清单 ​

  1. 先用 SigningDetails 确认当前 signer、scheme version 和 past lineage。
  2. 更新失败看 INSTALLED_DATA,回滚看 ROLLBACK,shared UID 看 SHARED_USER_ID。
  3. INSTALL_FAILED_TEST_ONLY 检查 -t,不要把它和签名错误混淆。
  4. userdebug/debuggable 设备仍执行签名兼容检查。
  5. release → debug 或 debug → release 直接覆盖失败时,确认是否真的存在 v3 rotation lineage。
  6. 签名切换后数据丢失,区分“更新被拒绝”和“卸载后重装导致的新 appId/数据目录”。

10. 源码阅读路线 ​

  1. InstallPackageHelper 的签名收集代码。
  2. verifySignatures 与更新/回滚 capability 判断。
  3. SigningDetails.checkCapability 和 hasAncestorOrSelf。
  4. PackageManagerServiceUtils.canJoinSharedUserId。
  5. InstallPackageHelper 的 testOnly 和 downgrade 分支。
  6. PackageSetting 的 getSigningDetails/setSigningDetails,确认安装成功后签名如何进入持久状态。

调试签名与发布签名的真正分界在 signer 身份和数据连续性:debuggable 影响调试/测试策略,testOnly 影响安装入口,只有 SigningDetails lineage capability 才决定新 APK 能否替换旧 APK 并继续使用原数据。