Overlay 资源覆盖
本文承接 PackageManager 实践,聚焦 Runtime Resource Overlay(RRO)如何从 overlay 服务操作变成目标包的资源路径。文章不把 overlay 简化为“同名资源替换”,而是区分 OverlayManagerService(OMS)的策略状态、idmap 生成、PMS 的 per-user OverlayPaths 以及 AssetManager 的最终消费。
1. 资源覆盖链
| 层 | owner | 结果 |
|---|---|---|
| overlay 策略 | OverlayManagerServiceImpl | enabled/disabled、priority、target |
| 映射文件 | IdmapManager/idmap daemon | overlay 到 target 的资源 ID 映射 |
| PMS 包状态 | PackageUserStateImpl | user-specific OverlayPaths |
| 资源消费 | framework/resource loader | 目标包加载 overlay 资源 |
2. OverlayInfo 状态
源码文件:frameworks/base/core/java/android/content/om/OverlayInfo.java,符号:状态常量
public static final int STATE_MISSING_TARGET = 0;
public static final int STATE_NO_IDMAP = 1;
public static final int STATE_DISABLED = 2;
public static final int STATE_ENABLED = 3;
@Deprecated
public static final int STATE_TARGET_IS_BEING_REPLACED = 4;
public static final int STATE_OVERLAY_IS_BEING_REPLACED = 5;
@Deprecated
public static final int STATE_ENABLED_IMMUTABLE = 6;
public static final int STATE_SYSTEM_UPDATE_UNINSTALL = 7;STATE_MISSING_TARGET、STATE_NO_IDMAP 和 STATE_DISABLED 都表示当前资源覆盖未生效,但原因不同:目标不存在、idmap 创建失败、或策略未启用。调试时不能把所有非 enabled 状态归为“overlay 被禁用”。
3. OMS Binder 入口
源码文件:frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java,符号:setEnabled
public boolean setEnabled(String packageName,
boolean enable, int userIdArg) {
if (packageName == null) {
return false;
}
OverlayIdentifier overlay =
new OverlayIdentifier(packageName);
int userId = handleIncomingUser(
userIdArg, "setEnabled");
enforceActor(overlay, "setEnabled", userId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
updateTargetPackagesLocked(
mImpl.setEnabled(
overlay, enable, userId,
constraints));
return true;
}
} catch (OperationFailedException e) {
return false;
} finally {
Binder.restoreCallingIdentity(ident);
}
}OMS 先解析目标 user,再做 actor 权限校验,最后在 OMS lock 内调用实现。setEnabled 的返回值只表示策略操作是否接受,不等于 PMS 已经完成资源路径更新。
4. idmap 与状态计算
源码文件:frameworks/base/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java,符号:setEnabled、updateOverlaysForUser
Set<UserPackage> setEnabled(
OverlayIdentifier overlay, boolean enable,
int userId, @Nullable OverlayConstraints constraints)
throws OperationFailedException {
OverlayInfo oldInfo = mSettings.getOverlayInfo(
overlay, userId);
boolean modified = mSettings.setEnabled(
overlay, userId, enable);
if (!modified) {
return Collections.emptySet();
}
return updateOverlaysForUser(userId);
}OMS 修改自己的持久化 settings,再重新计算受影响 target。实现返回的是需要更新的 UserPackage 集合,供上层通知 PMS 哪些目标包的 overlay paths 需要改变。
源码文件:frameworks/base/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java,符号:createIdmap
int idmapStatus = mIdmapManager.createIdmap(
targetPackageState, overlayPackageState,
overlayName, userId);
if (idmapStatus != IdmapManager.IDMAP_SUCCESS) {
return STATE_NO_IDMAP;
}
return enabled ? STATE_ENABLED : STATE_DISABLED;资源映射失败会进入 STATE_NO_IDMAP,即使 overlay 的 enabled setting 是 true,也不能进入有效覆盖路径。idmap 是状态计算的一部分,而不是启用后的异步装饰。
5. PMS 接收 paths
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:setEnabledOverlayPackages
private void setEnabledOverlayPackages(
int userId,
ArrayMap<String, OverlayPaths> pendingChanges,
Set<String> outUpdatedPackageNames,
Set<String> outInvalidPackageNames) {
synchronized (mOverlayPathsLock) {
Computer computer = snapshotComputer();
for (int i = 0; i < pendingChanges.size(); i++) {
String targetPackageName =
pendingChanges.keyAt(i);
OverlayPaths newPaths =
pendingChanges.valueAt(i);
PackageStateInternal state =
computer.getPackageStateInternal(
targetPackageName);
AndroidPackage target =
state == null ? null : state.getPkg();
if (target == null) {
outInvalidPackageNames.add(
targetPackageName);
continue;
}
if (canSetOverlayPaths(
state.getUserStateOrDefault(userId)
.getOverlayPaths(), newPaths)) {
outUpdatedPackageNames.add(
targetPackageName);
}
}PMS 使用当前 snapshot 验证 target package,并在 overlay paths 确实变化时加入更新集合。不存在 target 的 pending change 不会写入 user state。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:commit mutation
commitPackageStateMutation(null, mutator -> {
for (int i = 0; i < pendingChanges.size(); i++) {
String target = pendingChanges.keyAt(i);
if (!outUpdatedPackageNames
.contains(target)) {
continue;
}
mutator.forPackage(target)
.userState(userId)
.setOverlayPaths(
pendingChanges.valueAt(i));
}
});
}
invalidatePackageInfoCache(
PackageMetrics
.INVALIDATION_REASON_ENABLE_OVERLAY_PACKAGES);
}PMS 把 overlay paths 写入目标包指定 user 的 PackageUserState,然后使 package/application info cache 失效。资源消费者下一次取得新的 ApplicationInfo 时才看到更新后的 overlay paths。
6. 共享库依赖传播
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:setEnabledOverlayPackages 的 dependent 更新
for (String libName : targetPkg.getLibraryNames()) {
SharedLibraryInfo info = computer
.getSharedLibraryInfo(
libName,
SharedLibraryInfo.VERSION_UNDEFINED);
Pair<List<VersionedPackage>, ?> dependents =
computer.getPackagesUsingSharedLibrary(
info, 0, Process.SYSTEM_UID, userId);
for (VersionedPackage dependent :
dependents.first) {
mutator.forPackage(
dependent.getPackageName())
.userState(userId)
.setOverlayPathsForLibrary(
libName, newOverlayPaths);
}
}overlay 目标如果是 shared library,PMS 还要把 paths 传播给依赖该库的包。只更新目标包而不更新 dependents,会让同一 user 的资源解析上下文不一致。
7. System UI 路径
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:maybeUpdateSystemOverlays
if (targetPackageName.equals("android")) {
if (newOverlayPaths == null) {
mPlatformPackageOverlayPaths = null;
mPlatformPackageOverlayResourceDirs = null;
} else {
mPlatformPackageOverlayPaths =
newOverlayPaths.getOverlayPaths()
.toArray(new String[0]);
mPlatformPackageOverlayResourceDirs =
newOverlayPaths.getResourceDirs()
.toArray(new String[0]);
}
applyUpdatedSystemOverlayPaths();
}针对 android 平台包的 overlay,PMS 还同步更新系统 ApplicationInfo 的 overlay/resource dirs,确保 System UI 和 framework 资源使用最新路径。这个特殊缓存只在 system user 的 overlay 更新中维护。
8. 包事件与重算
源码文件:frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java,符号:package monitor
private void onPackageAdded(
@NonNull String packageName, int userId) {
traceBegin(TRACE_TAG_RRO,
"OMS#onPackageAdded " + packageName);
try {
var packageState =
mPackageManager.onPackageAdded(
packageName, userId);
updateTargetPackagesLocked(
mImpl.onPackageAdded(
packageName, userId));
} finally {
traceEnd(TRACE_TAG_RRO);
}
}overlay 包、target 包安装/更新/删除都会触发 OMS package monitor,重新计算 target overlays。RRO 的生效不仅由 setEnabled 触发,也由包生命周期事件触发。
9. 用户维度与持久化
Overlay 的 enabled state、priority 和 idmap 状态按 user 计算;PMS 接收的 OverlayPaths 也写入指定 user 的 PackageUserState。因此 user 0 启用 overlay 不代表 user 10 自动看到同一资源覆盖。
10. 失败定位
| 现象 | 应检查 |
|---|---|
| setEnabled 返回 true 但资源未变 | idmap 状态、PMS paths mutation、cache invalidation |
STATE_MISSING_TARGET | target package 是否安装/可见 |
STATE_NO_IDMAP | target/overlay resources、idmap daemon |
| 只影响一个用户 | userId 参数、OMS settings、PackageUserState |
| shared library 使用旧资源 | dependent overlay paths 是否传播 |
| System UI 未更新主题 | target android 特殊路径、applyUpdatedSystemOverlayPaths |
11. 阅读检查
复述这条链:OverlayManager client → OMS 权限/user/actor → OMSImpl 更新 enabled/priority → idmap → PMS setEnabledOverlayPackages → per-user OverlayPaths → cache invalidation → resource consumer。然后回答:启用设置成功是否等于 idmap 成功?PMS 是否保存 overlay 的全局唯一状态?更新 shared library overlay 时只改 library package 是否足够?答案分别是“不一定”“不是,路径按 user 写入”“不够,还要传播给依赖包”。
