Skip to content

Compat变更

沿着 PendingIntent 显式可变性要求,追踪 Compat Change 从声明、规则判定到进程内存和运行时分支。

基于android-17.0.0_r1
Android源码阅读兼容性PendingIntent

Compat变更 ​

本文面向已经能读 Java 和 Binder 基础代码、但容易混淆 SDK_INT、targetSdkVersion 与 change ID 的读者。建议先读 Java调用链 了解进程边界,再读 搜索调用链 了解如何从声明追到消费者。

本文只研究一个真实问题:创建 PendingIntent 时没有指定 FLAG_IMMUTABLE 或 FLAG_MUTABLE,为什么有些应用抛出异常,有些应用仍能创建?答案要沿着 change 160794467 走完:PendingIntent 声明规则,CompatChange 按应用 target 判定,ProcessList 在启动进程时准备数组,AppCompatCallbacks 在应用进程内保存结果,最后 checkPendingIntent() 查询布尔值。

不展开 API 版本年表,也不把每个兼容变更列成清单。

1. 三种版本 ​

先把三个概念拆开:设备运行的系统版本、应用声明的目标版本、行为规则的 change ID。

源码文件:frameworks/base/core/java/android/os/Build.java

相关字段:Build.VERSION.SDK_INT、Build.VERSION.SDK_INT_FULL、Build.VERSION.DEVICE_INITIAL_SDK_INT、Build.VERSION.PREVIEW_SDK_INT

java
public static final int SDK_INT = SystemProperties.getInt(
        "ro.build.version.sdk", 0);
public static final int SDK_INT_FULL = parseFullVersion(
        SystemProperties.get("ro.build.version.sdk_full", ""));
public static final int DEVICE_INITIAL_SDK_INT = SystemProperties.getInt(
        "ro.product.first_api_level", 0);

这些字段描述设备提供的 platform 能力,不是每次兼容查询的直接结果。targetSdkVersion 来自 ApplicationInfo,change ID 则是平台为某个行为分支分配的稳定编号。

2. 规则声明 ​

2.1 Change 定义 ​

源码文件:frameworks/base/core/java/android/app/PendingIntent.java

相关符号:PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED

java
@ChangeId
@EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.R)
static final long PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED = 160794467L;

@EnabledAfter(R) 表示 target SDK 高于 R 时默认进入新行为,也就是 S 及以上。注解是规则元数据,不是直接抛异常的位置。

2.2 规则判定 ​

源码文件:frameworks/base/services/core/java/com/android/server/compat/CompatChange.java

相关函数:CompatChange.isEnabled

java
boolean isEnabled(ApplicationInfo app,
        AndroidBuildClassifier buildClassifier) {
    if (app == null) return defaultValue();
    if (app.packageName != null) {
        final Boolean enabled = mEvaluatedOverrides.get(app.packageName);
        if (enabled != null) return enabled;
    }
    if (getDisabled()) return false;
    if (getEnableSinceTargetSdk() != -1) {
        int compareSdk = Math.min(app.targetSdkVersion,
                buildClassifier.platformTargetSdk());
        return compareSdk >= getEnableSinceTargetSdk();
    }
    return true;
}

判定顺序是包级 override、explicit disabled、target 门槛、默认值。这里比较的是应用 target 与平台 target 的较小值,不是直接读 SDK_INT。

2.3 未知变更 ​

源码文件:frameworks/base/services/core/java/com/android/server/compat/CompatConfig.java

java
boolean isChangeEnabled(long changeId, ApplicationInfo app) {
    CompatChange change = mChanges.get(changeId);
    if (change == null) return true;
    return change.isEnabled(app, mAndroidBuildClassifier);
}

未知 change 默认返回 true,与已声明但因 target 门槛关闭的 change 是两条路径。

3. 进程下发 ​

3.1 差异数组 ​

源码文件:frameworks/base/services/core/java/com/android/server/compat/CompatConfig.java

相关函数:getDisabledChanges

java
long[] getDisabledChanges(ApplicationInfo app) {
    LongArray disabled = new LongArray();
    for (CompatChange change : mChanges.values()) {
        if (!change.isEnabled(app, mAndroidBuildClassifier)) {
            disabled.add(change.getId());
        }
    }
    long[] result = disabled.toArray();
    Arrays.sort(result);
    return result;
}

数组排序是应用侧二分查找的前提。

3.2 启动边界 ​

源码文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java

相关函数:startProcessLocked

java
app.setDisabledCompatChanges(null);
app.setEnabledCompatChanges(null);
app.setLoggableCompatChanges(null);
if (mPlatformCompat != null) {
    app.setDisabledCompatChanges(
            mPlatformCompat.getDisabledChanges(app.info));
    app.setEnabledCompatChanges(
            mPlatformCompat.getEnabledChanges(app.info));
    app.setLoggableCompatChanges(
            mPlatformCompat.getLoggableChanges(app.info));
}

ProcessRecord 持有的是本次进程启动快照,后续数组作为 Zygote 参数进入新进程。

3.3 应用安装 ​

源码文件:frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

相关函数:applicationInit

java
VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
VMRuntime.getRuntime().setDisabledCompatChanges(disabledCompatChanges);
if (disabledCompatChanges != null || enabledCompatChanges != null) {
    AppCompatCallbacks.install(disabledCompatChanges,
            enabledCompatChanges, null, false, targetSdkVersion);
}

源码文件:frameworks/base/core/java/android/app/ActivityThread.java

相关函数:handleBindApplication

java
AppCompatCallbacks.install(
        data.disabledCompatChanges,
        data.enabledCompatChanges,
        data.mLoggableCompatChanges,
        data.mLogChangeChecksToStatsD,
        data.appInfo.targetSdkVersion);

RuntimeInit 覆盖静态初始化期,ActivityThread 在 bindApplication 时安装完整数据。

4. 进程查询 ​

源码文件:frameworks/base/core/java/android/app/AppCompatCallbacks.java

相关函数:isChangeEnabledInternal

java
private boolean isChangeEnabledInternal(long changeId) {
    if (changeIdInChangeList(mDisabledChanges, changeId)) return false;
    if (mEnabledChanges != null
            && changeIdInChangeList(mEnabledChanges, changeId)) return true;
    if (mTargetSdkVersion != -1) {
        return CompatibilityRules.isChangeEnabled(
                changeId, mTargetSdkVersion);
    }
    return true;
}

这是进程内存查询,不会每次通过 Binder 访问 system_server。另一条服务代表查询通过如下 API:

源码文件:frameworks/base/core/java/android/app/compat/CompatChanges.java

java
public static boolean isChangeEnabled(long changeId,
        @NonNull String packageName, @NonNull UserHandle user) {
    validateChangeId(changeId);
    return QUERY_CACHE.query(ChangeIdStateQuery.byPackageName(
            changeId, packageName, user.getIdentifier()));
}
优先级状态来源结果
1disabled 数组false
2enabled 数组true
3预加载规则 + target按规则计算
4未知 changetrue

5. 行为消费 ​

源码文件:frameworks/base/core/java/android/app/PendingIntent.java

相关函数:checkPendingIntent

java
private static void checkPendingIntent(int flags, @NonNull Intent intent,
        @NonNull Context context, boolean isActivityResultType) {
    boolean immutable = (flags & FLAG_IMMUTABLE) != 0;
    boolean mutable = (flags & FLAG_MUTABLE) != 0;
    if (immutable && mutable) {
        throw new IllegalArgumentException("Cannot set both flags");
    }
    if (Compatibility.isChangeEnabled(
            PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED)
            && !immutable && !mutable) {
        throw new IllegalArgumentException(
                "requires an explicit mutability flag");
    }
}
flagschange结果
两个 flag 同时存在任意参数异常
两个 flag 都没有关闭继续旧流程
两个 flag 都没有开启抛出 IllegalArgumentException
只设置一个任意通过本项检查

6. 重启边界 ​

包 override 修改的是 system_server 中的状态。已运行进程仍持有旧 AppCompatCallbacks 数组,因此需要重启包进程才会重新经过 ProcessList、Zygote 和 ActivityThread 安装链。

7. 测试路径 ​

源码文件:frameworks/base/core/tests/coretests/src/android/app/compat/CompatChangesTest.java

相关测试:testEnabledChange、testDisabledChange

java
@Test
@EnableCompatChanges(CHANGE_ID)
public void testEnabledChange() {
    assertThat(CompatChanges.isChangeEnabled(CHANGE_ID)).isTrue();
    assertThat(CompatChanges.isChangeEnabled(
            CHANGE_ID, Process.myUid())).isTrue();
    assertThat(CompatChanges.isChangeEnabled(
            CHANGE_ID, getPackageName(),
            UserHandle.of(UserHandle.myUserId()))).isTrue();
}

这些测试检查当前进程、UID、package/user 三种查询入口,不检查 PendingIntent 异常文本。

源码文件:frameworks/base/core/tests/coretests/src/com/android/internal/os/ZygoteArgumentsTest.java

相关测试:testParseAndMergeCompatChanges、testParseAndMergeCompatChanges_sorted、testParseAndMergeCompatChanges_malformed、testParseAndMergeCompatChanges_dedup

这组测试给出多段参数、乱序 ID、空分隔符和重复 ID,检查合并、排序、忽略空值和去重。它约束参数运输,不判断某个 change 的业务门槛。

8. 搜索复现 ​

在 frameworks/base project 内执行:

bash
rg -n 'PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED|checkPendingIntent' \
  core/java/android/app/PendingIntent.java
rg -n 'getDisabledChanges|getEnabledChanges' \
  services/core/java/com/android/server/compat \
  services/core/java/com/android/server/am/ProcessList.java
rg -n 'AppCompatCallbacks\.install|isChangeEnabledInternal' \
  core/java/android/app core/java/com/android/internal/os
rg -n 'testEnabledChange|testDisabledChange|testParseAndMergeCompatChanges' \
  core/tests/coretests/src

按顺序回答:change 在哪里声明?谁按 target 计算?数组何时进入新进程?谁在应用内查询?缺少两个 flag 时哪一行抛异常?

9. 边界 ​

本文沿 Android 17 用户态源码讲清一个 Compat Change 从声明到应用行为的路径。没有覆盖所有 change 注解生成、厂商配置和所有 override 命令,也没有把一个 change 的结论外推到其他 change。