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
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
@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
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
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
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
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
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
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
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
public static boolean isChangeEnabled(long changeId,
@NonNull String packageName, @NonNull UserHandle user) {
validateChangeId(changeId);
return QUERY_CACHE.query(ChangeIdStateQuery.byPackageName(
changeId, packageName, user.getIdentifier()));
}| 优先级 | 状态来源 | 结果 |
|---|---|---|
| 1 | disabled 数组 | false |
| 2 | enabled 数组 | true |
| 3 | 预加载规则 + target | 按规则计算 |
| 4 | 未知 change | true |
5. 行为消费
源码文件:frameworks/base/core/java/android/app/PendingIntent.java
相关函数:checkPendingIntent
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");
}
}| flags | change | 结果 |
|---|---|---|
| 两个 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
@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 内执行:
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。
