在前一篇,也就是Android资源管理框架-------之OverlayManagerService简介及其数据的维护更新(二)中我们简单分析了OMS的架构,以及我们动态地去enable一个overlay package时OMS的主要操作流程:生成idmap文件,更新OverlayManagerSettings
中的数据,然后就是在OverlayManagerServiceImpl
的setEnabled
方法中通过mListener.onOverlaysChanged(oi.targetPackageName, userId);
这一句来让我们的overlay包生效了。其中mListener
是OverlayManagerService
的内部类OverlayChangeListener
的实例:
//frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java
private final class OverlayChangeListener
implements OverlayManagerServiceImpl.OverlayChangeListener {
@Override
public void onOverlaysChanged(@NonNull final String targetPackageName, final int userId) {
//持久化OverlayManagerSettings中overlay相关的数据
schedulePersistSettings();
//FgThread中让overlay package 生效
FgThread.getHandler().post(() -> {
//overlay package 生效的核心实现在里面
updateAssets(userId, targetPackageName);
//发送overlay package 改变的广播
final Intent intent = new Intent(Intent.ACTION_OVERLAY_CHANGED,
Uri.fromParts("package", targetPackageName, null));
//该flag不会拉起未启动的应用
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
try {
ActivityManager.getService().broadcastIntent(null, intent, null, null, 0,
null, null, null, android.app.AppOpsManager.OP_NONE, null, false, false,
userId);
} catch (RemoteException e) {
// Intentionally left empty.
}
});
}
}
我们看到onOverlaysChanged
主要是做了两件事:
- 持久化OverlayManagerSettings中overlay相关的数据到
/data/system/overlays.xml
中 - 通过
updateAssets
让overlay包生效
至于发送overlay包改变的广播给感兴趣的组件不是核心,也没啥好说的,我们这里就不详细介绍了。不过,onOverlaysChanged
方法是在system_server的binder线程中执行的,而让overlay package生效显然是一个前台操作,放到FgThread
里比较合适。我们先看持久化:
//frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java
private void schedulePersistSettings() {
/**
* mPersistSettingsScheduled是个AtomicBoolean,先获取它之前的值,再将它的值
* 设置为true.表示已经让IOThread去干活了,但是它还未开工.当然,如果它之前就是true,
* 那就说明前面已经有人让它去持久化了,并且它还没开工,那么我们就直接return就好了,
* 等它开工了,我们的改动自然也会被持久化
*/
if (mPersistSettingsScheduled.getAndSet(true)) {
return;
}
//持久化,IO操作当然要放到IoThread
IoThread.getHandler().post(() -> {
//表示已经开工了^_^
mPersistSettingsScheduled.set(false);
synchronized (mLock) {
FileOutputStream stream = null;
try {
//拿到IOStream,/data/system/overlays.xml
stream = mSettingsFile.startWrite();
//mSettings是OverlayManagerSettings的实例,上一篇已经提过
mSettings.persist(stream);
//关闭io流
mSettingsFile.finishWrite(stream);
} catch (IOException | XmlPullParserException e) {
mSettingsFile.failWrite(stream);
Slog.e(TAG, "failed to persist overlay state", e);
}
}
});
}
持久化的逻辑非常简单,主要就是让IOThread
去干活,把内存中一个一个的SettingItem
输出到/data/system/overlays.xml
这个文件中。不过,mPersistSettingsScheduled
的使用,可以避免频繁的不必要的持久化,提高效率,这种用法在AOSP的代码中非常常见,比如BroadcastQueue
中对BroadcastRecord
的处理也用到了类似的思想。然后,我们看看updateAssets
方法是如何让overlay package生效的:
//frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java
private void updateAssets(final int userId, final String targetPackageName) {
//包了一层,看样子Android还是想支持一下子让多个target包生效的
updateAssets(userId, Collections.singletonList(targetPackageName));
}
private void updateAssets(final int userId, List<String> targetPackageNames) {
//把overlay信息更新到PMS里,后面AMS用到的时候,会去PMS里取
updateOverlayPaths(userId, targetPackageNames);
final IActivityManager am = ActivityManager.getService();
try {
//交给AMS啦,不过还是在system_server的FgThread中执行
am.scheduleApplicationInfoChanged(targetPackageNames, userId);
} catch (RemoteException e) {
// Intentionally left empty.
}
}
updateAssets
方法主要也是两件事儿:把OverlayManagerSettings
里的信息更新到PackageManagerService
里面去(这时候我们可以简单认为OverlayManagerSettings
、/data/system/overlays.xml
、PMS里的信息是一致的,都是最新的信息),后面会用到;再就是让ActivityManagerService
通知到target package所在的进程,你的overlay包发生了改变哦!先看updateOverlayPaths
:
//frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java
private void updateOverlayPaths(int userId, List<String> targetPackageNames) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames);
if (DEBUG) {
Slog.d(TAG, "Updating overlay assets");
}
final PackageManagerInternal pm =
LocalServices.getService(PackageManagerInternal.class);
/**
* 如果我们的target包中有android,也就是说我们要动态enable framework-res.apk的
* overlay package。由于所有的应用都有可能使用使用里面的资源,所以这个时候我们可以认为:
* 所有的target package都会被这个framework-res.apk的overlay package所覆盖,
* 所以下面会对targetPackageNames做调整。
*/
final boolean updateFrameworkRes = targetPackageNames.contains("android");
if (updateFrameworkRes) {
targetPackageNames = pm.getTargetPackageNames(userId);
}
/**
* 调整后的结果放在pendingChanges里
* 它的key是target package
* value是这个target package的所有overlay package
*/
final Map<String, List<String>> pendingChanges =
new ArrayMap<>(targetPackageNames.size());
synchronized (mLock) {
//拿到framework-res.apk的所有overlay package,
//mImpl是OverlayManagerServiceImpl的实例
final List<String> frameworkOverlays =
mImpl.getEnabledOverlayPackageNames("android", userId);
final int n = targetPackageNames.size();
for (int i = 0; i < n; i++) {
final String targetPackageName = targetPackageNames.get(i);
//存放target package的所有overlay package
List<String> list = new ArrayList<>();
/**
* 对于非framework-res.apk的所有target package而言
* framework-res.apk的所有overlay package,也是这个
* package的overlay package
*/
if (!"android".equals(targetPackageName)) {
list.addAll(frameworkOverlays);
}
/**
* 再去OverlayManagerSettings中拿到这个package真正的
* overlay package,也添加到list中。这个时候list中存放的
* 才是这个package完整的所有的overlay package
*
* mImpl.getEnabledOverlayPackageNames会从OverlayManagerSettings
* 中拿到overlay package.因为OverlayManagerServiceImpl本身并不存储
* overlay信息,它只是overlay信息的搬运工,哈哈
*/
list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
pendingChanges.put(targetPackageName, list);
}
}
final int n = targetPackageNames.size();
for (int i = 0; i < n; i++) {
final String targetPackageName = targetPackageNames.get(i);
if (DEBUG) {
Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
+ TextUtils.join(",", pendingChanges.get(targetPackageName))
+ "] userId=" + userId);
}
//更新到PMS当中去
if (!pm.setEnabledOverlayPackages(
userId, targetPackageName, pendingChanges.get(targetPackageName))) {
Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
targetPackageName, userId));
}
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
我们看到updateOverlayPaths
并不是简单地直接把OverlayManagerSettings
中的overlay信息给到PMS,而是在给到之前对OverlayManagerSettings
中的overlay信息做了校正,添加了framework-res.apk
的所有overlay package。这也是为什么我们前面说,经过updateOverlayPaths
后,我也只是可以简单地以为OverlayManagerSettings
、/data/system/overlays.xml
、PMS里的overlay信息是一致的。毕竟,这个时候PMS里的overlay信息比着OverlayManagerSettings
、/data/system/overlays.xml
多了framework-res.apk
的overlay package。说完了updateOverlayPaths
的处理,我们看看AMS的处理流程:
//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@Override
public void scheduleApplicationInfoChanged(List<String> packageNames, int userId) {
//权限检查
enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
"scheduleApplicationInfoChanged()");
synchronized (this) {
/**
* 这时的calling uid pid还是调用setEnable方法的应用的,所以
* 要换成system_server的
*/
final long origId = Binder.clearCallingIdentity();
try {
updateApplicationInfoLocked(packageNames, userId);
} finally {
//完了calling uid pid换回来
Binder.restoreCallingIdentity(origId);
}
}
}
void updateApplicationInfoLocked(@NonNull List<String> packagesToUpdate, int userId) {
/*...省略部分代码...*/
mProcessList.updateApplicationInfoLocked(packagesToUpdate, userId, updateFrameworkRes);
/*...省略部分代码...*/
}
mProcessList
是ProcessList
类的实例:
//frameworksbase/services/core/java/com/android/server/am/ProcessList.java
void updateApplicationInfoLocked(List<String> packagesToUpdate, int userId,
boolean updateFrameworkRes) {
/**
* packagesToUpdate是我们的target包
* updateFrameworkRes是我们是否enable了framework-res.apk的overlay包
*/
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
final ProcessRecord app = mLruProcesses.get(i);
//异常检查
if (app.thread == null) {
continue;
}
//user不对应的也跳过
if (userId != UserHandle.USER_ALL && app.userId != userId) {
continue;
}
final int packageCount = app.pkgList.size();
for (int j = 0; j < packageCount; j++) {
final String packageName = app.pkgList.keyAt(j);
/**
* 对与要生效的target package,通过IApplicationThread这个应用端的binder接口(也就是app.thread),
* 告知target process,你的overlay 包状态有变化,你得处理一下.
*
* 需要说明的是,如果我们enable了一个framework-res.apk的overlay包,由于所有应用都有可能使用里面的
* 资源,所以这个时候要通知所有的应用进程.
*/
if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
try {
/**
* 我们前面已经把overlay包状态改变了的信息更新到了PMS,
* 这里获取到的已经是更新过了的ApplicationInfo了
*/
final ApplicationInfo ai = AppGlobals.getPackageManager()
.getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
if (ai != null) {
app.thread.scheduleApplicationInfoChanged(ai);
}
} catch (RemoteException e) {
Slog.w(TAG, String.format("Failed to update %s ApplicationInfo for %s",
packageName, app));
}
}
}
}
}
mProcessList
也只是找到target process,然后把这个信息通知到应用进程,然后看应用进程的处理:
//frameworks/base/core/java/android/app/ActivityThread.java
public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai);
sendMessage(H.APPLICATION_INFO_CHANGED, ai);
}
//这个就是H.APPLICATION_INFO_CHANGED的消息处理方法
public void handleApplicationInfoChanged(@NonNull final ApplicationInfo ai) {
// Updates triggered by package installation go through a package update
// receiver. Here we try to capture ApplicationInfo changes that are
// caused by other sources, such as overlays. That means we want to be as conservative
// about code changes as possible. Take the diff of the old ApplicationInfo and the new
// to see if anything needs to change.
//target process所属的APK
LoadedApk apk;
/**
* resApk是干啥的,往下看
*/
LoadedApk resApk;
// Update all affected loaded packages with new package information
synchronized (mResourcesManager) {
WeakReference<LoadedApk> ref = mPackages.get(ai.packageName);
//拿到 target process中我们的target package信息
apk = ref != null ? ref.get() : null;
/**
* resApk表示我们的target process加载的其它资源包,比如我们通过
* public Context createPackageContext(String packageName, int flags)
* 方法访问别的APK的时候,就会为这个APK创建LoadedApk对象,并且如果这个APK中含有
* dex字节码就会把它的LoadedApk对象放到mPackages中,如果这个APK中没有字节码,只有资源,
* 那么就会把它放到mResourcePackages中去
*/
ref = mResourcePackages.get(ai.packageName);
resApk = ref != null ? ref.get() : null;
}
final String[] oldResDirs = new String[2];
if (apk != null) {
oldResDirs[0] = apk.getResDir();
final ArrayList<String> oldPaths = new ArrayList<>();
/**
* 虽然我们前面更新了system_server中OverlayManagerSettings、
* PMS的overlay信息,以及/data/system/voerlays.xml中的overlay
* 信息,但是应用进程也就是apk.getApplicationInfo()获取到的ApplicationInfo
* 仍然是老的。
*/
LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths);
/**
* 更新LoadedApk对象中的Application信息,ai这个对象中存储的ApplicationInfo
* 中包含的overlay信息是从PMS那里拿到的,也就是最新的。
*/
apk.updateApplicationInfo(ai, oldPaths);
}
if (resApk != null) {
oldResDirs[1] = resApk.getResDir();
final ArrayList<String> oldPaths = new ArrayList<>();
LoadedApk.makePaths(this, resApk.getApplicationInfo(), oldPaths);
resApk.updateApplicationInfo(ai, oldPaths);
}
synchronized (mResourcesManager) {
// Update all affected Resources objects to use new ResourcesImpl
/**
* 这里会更新ResourceImpl,也就是说,会创建新的AssetManager,加载resource.arsc等等
*/
mResourcesManager.applyNewResourceDirsLocked(ai, oldResDirs);
}
/**
* ApplicationPackageManager跑在应用进程中,它有一些方法,可以获取别的APK
* 中的Drawable、String等资源,同时它还会把获取到的这些资源缓存起来,这样后面
* 再次访问的时候就不用去获取了,直接返回。如果缓存里都是别的资源包里的资源,那这个时候
* 可以不用清理缓存,但是如果缓存里有我们target pacakge自身的,那这个时候,由于
* overlay package改变,缓存里的数据有可能已经过时了,所以要通过下面的这个方法去
* 清理它里面的缓存,这个我们就不详细介绍了。
*/
ApplicationPackageManager.configurationChanged();
// Trigger a regular Configuration change event, only with a different assetsSeq number
// so that we actually call through to all components.
// TODO(adamlesinski): Change this to make use of ActivityManager's upcoming ability to
// store configurations per-process.
Configuration newConfig = new Configuration();
newConfig.assetsSeq = (mConfiguration != null ? mConfiguration.assetsSeq : 0) + 1;
handleConfigurationChanged(newConfig, null);
// 重启所有的Activity!!!
relaunchAllActivities(true /* preserveWindows */);
}
熟悉的配方,熟悉的味道,又是发Message在ActivityThread中,scheduleXXX开头的方法都是这个模式 因为这些方法是通过binder接口调用过来的,运行在binder线程中, 而是要运行在应用的主线程中的,所以才会发消息了。另外,我们看到应用进程这边的处理也比较简单,主要就是:用新的ApplicationInfo(主要是ApplicationInfo.resourceDirs,也就是overlay包的路径)创建新的ResourcesImpl
(其实也就是重新创建了AsssetManager对象),然后重启所有的Activity,这样新的Activity起来后,自然就会利用我们新创建的ResourcesImpl
里面的资源重新绘制界面,这样我们的Overlay包就可以作用到target pacakge上了。
总结一下,enable一个overlay package的大体流程:
- enable一个overlay package后OMS模块会生成idmap文件,这个文件我们就不详细介绍了,感兴趣的同学点这里:Android资源管理中的Runtime Resources Overlay-------之PMS、installd和idmap的处理(二)。
- 然后OMS会更新overlay信息,信息在
OverlayManagerSettings
中。 - 再然后的两步其实是并行的:持久化
OverlayManagerSettings
的信息到/data/system/overlays.xml
,这个在IoThread
中执行;修正OverlayManagerSettings
中的overlay信息,然后把它同步到PMS中。 - 后面的流程就交给AMS了,AMS根据target包的包名,找到target process对应的
ProcessRecord
,从PMS那里拿到修正后的overlay信息(封装在ApplicationInfo
中啦)。 - AMS通过ProcessRecord中的
IApplicationThread
接口通知target process,你的overlay信息变了,你得处理一下。变成啥样子了?新的样子会封装在ApplicationInfo
参数中。 - 应用进程收到这个binder请求后,会更新自己的
LoadedApk
对象,重新创建AssetManager
对象。当然,这个过程中就会加载新的overlay包,这个时候,新的Resources
对象、AssetManager
对象中,新的overlay包已经生效了。 - 但是,我们看到的
Activity
、View
还是老的,怎么办呢?重启所有的Activity
,这样我们就新的Activity
起来后,自然就会用新的Resources
对象来绘制自己的内容了,我们看到的就是新的界面了!
另外,前面我们在updateOverlayPaths
的时候,通过pm.setEnabledOverlayPackages
方法,会把overlay信息更新到PMS中,PMS中存储overlay信息的是PackageSetting
对象,但是当我们在AMS中获取overlay信息时,却是通过AppGlobals.getPackageManager().getApplicationInfo
方法获取的,那么PMS是怎么把PackageSetting
对象中的overlay信息转移到ApplicationInfo
对象中的呢:
//frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName,
@Nullable List<String> overlayPackageNames) {
/**
* mPackages中存放的是从已经安装的应用的AndroidManifest.xml中解析而来的包信息,
* 包含overlay packages
* 访问mPackages必须同步,否则把mPackages弄乱系统就gg了
*/
synchronized (mPackages) {
//异常情况检测
if (targetPackageName == null || mPackages.get(targetPackageName) == null) {
Slog.e(TAG, "failed to find package " + targetPackageName);
return false;
}
//获取overlay包的路径
ArrayList<String> overlayPaths = null;
if (overlayPackageNames != null && overlayPackageNames.size() > 0) {
final int N = overlayPackageNames.size();
overlayPaths = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
final String packageName = overlayPackageNames.get(i);
//确保overlay包存在
final PackageParser.Package pkg = mPackages.get(packageName);
if (pkg == null) {
Slog.e(TAG, "failed to find package " + packageName);
return false;
}
//拿到overlay包的路径
overlayPaths.add(pkg.baseCodePath);
}
}
/**
* overlay包的路径信息更新到PackageSetting中,
* PackageSetting表示一个package的设置信息,比如这个package是disable
* 还是enable状态、包的路径、abi信息、versioncode、签名信息等等,
* 当然还包括它的overlay包路径
*/
final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
ps.setOverlayPaths(overlayPaths, userId);
return true;
}
}
我们在AMS中通过getApplicationInfo
方法获取ApplicationInfo
的时候:
//frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) {
/**
* packageName 我们的target pacakge的包名
*
* flags STOCK_PM_FLAGS,这个flags表示如果我们要获取的package是个资源共享库,
* 那么也同样会返回它的ApplicationInfo,如果没有这个flag,这种情况下则会返回null
*
* Binder.getCallingUid(),其实就是通过binder接口调用OMS的setEnabled方法的应用的uid
*
* userId,则是Android多用户中的user的ID,单用户的情况下是0
*/
return getApplicationInfoInternal(packageName, flags, Binder.getCallingUid(), userId);
}
private ApplicationInfo getApplicationInfoInternal(String packageName, int flags,
int filterCallingUid, int userId) {
// .......省略非关键代码
// 根据包名拿到PackageSetting对象,这里面的overlay信息是我们已经更新过了的
PackageSetting ps = mSettings.mPackages.get(packageName);
//ps.readUserState(userId),返回的是一个PackageUserState对象,overlay包的路径就存在里面
ApplicationInfo ai = PackageParser.generateApplicationInfo(
p, flags, ps.readUserState(userId), userId);
// .......省略非关键代码
return ai;
}
看PackageParser
的处理:
public static ApplicationInfo generateApplicationInfo(Package p, int flags,
PackageUserState state) {
/**
* 默认是调用者所属的user,这个UserHandle.getCallingUserId()是Android层面的userId,
* 而非linux层面的uid
*/
return generateApplicationInfo(p, flags, state, UserHandle.getCallingUserId());
}
public static ApplicationInfo generateApplicationInfo(Package p, int flags,
PackageUserState state, int userId) {
// .......省略非关键代码
ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
// .......省略非关键代码
//state是我们传过来的PackageUserState,overlay包的路径就在里面
updateApplicationInfo(ai, flags, state);
return ai;
}
private static void updateApplicationInfo(ApplicationInfo ai, int flags,
PackageUserState state) {
// .......省略非关键代码
// key code !!!
ai.resourceDirs = state.overlayPaths;
ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes;
}
Android 11 发布后,简单看了一下,资源管理这块儿,Google又有改动,没错还是关于RRO的,增加了一些接口,非常友好。没错就是Resource Loader框架我们抽时间分析一下。