概述
上一篇文章讲到当我们通过OMS的setEnabled
方法去改变一个Overlay包的状态时会经过一系列的判断,如Overlay包是否存在,Overlay包的目标应用是否存在,目标应用的Idmap文件是否存在,Overlay包是否为静态等等,如果最终通过判断且Overlay包的状态确实发生了改变则会调onOverlaysChanged
方法去通知目标应用其资源包发生了改变,进而使Overlay能够生效。
boolean setEnabled(@NonNull final String packageName, final boolean enable,
final int userId) {
......
//此方法里面会计算Overlay包的状态,返回值作为判断是否更新Overlay的依据
modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0);
//如果需要更新Overlay,则回调onOverlaysChanged
if (modified) {
mListener.onOverlaysChanged(oi.targetPackageName, userId);
}
return true;
} catch (OverlayManagerSettings.BadKeyException e) {
return false;
}
}
onOverlaysChanged
mListener
定义在OMS内部:
private final class OverlayChangeListener
implements OverlayManagerServiceImpl.OverlayChangeListener {
@Override
public void onOverlaysChanged(@NonNull final String targetPackageName, final int userId) {
//持久化
schedulePersistSettings();
FgThread.getHandler().post(() -> {
updateAssets(userId, targetPackageName);
//发送OVERLAY_CHANGED的广播,不重要,省略
....
try {
......
} catch (RemoteException e) {
// Intentionally left empty.
}
});
}
}
OverlayManagerSettings.schedulePersistSettings
schedulePersistSettings
负责将Overlay的信息持久化保存到设备中,路径为:/data/system/overlays.xml
private void schedulePersistSettings() {
//避免重复调用
if (mPersistSettingsScheduled.getAndSet(true)) {
return;
}
IoThread.getHandler().post(() -> {
mPersistSettingsScheduled.set(false);
synchronized (mLock) {
FileOutputStream stream = null;
try {
stream = mSettingsFile.startWrite();
mSettings.persist(stream);
mSettingsFile.finishWrite(stream);
} catch (IOException | XmlPullParserException e) {
mSettingsFile.failWrite(stream);
Slog.e(TAG, "failed to persist overlay state", e);
}
}
});
}
持久化其实就是一个写xml的过程,具体实现在OverlayManagerSettings
中,OverlayManagerSettings
内部提供了一个工具类Serializer
来操作写入流:
void persist(@NonNull final OutputStream os) throws IOException, XmlPullParserException {
Serializer.persist(mItems, os);
}
public static void persist(@NonNull final ArrayList<SettingsItem> table,
@NonNull final OutputStream os) throws IOException, XmlPullParserException {
final FastXmlSerializer xml = new FastXmlSerializer();
xml.setOutput(os, "utf-8");
xml.startDocument(null, true);
xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
xml.startTag(null, TAG_OVERLAYS);
XmlUtils.writeIntAttribute(xml, ATTR_VERSION, CURRENT_VERSION);
final int n = table.size();
//遍历系统所有的Overlay所对应的SettingsItem
for (int i = 0; i < n; i++) {
final SettingsItem item = table.get(i);
persistRow(xml, item);
}
xml.endTag(null, TAG_OVERLAYS);
xml.endDocument();
}
上面代码很好理解,整个写入过程就是遍历mItems
(前面讲过,mItems存储了系统所有Overlay包所对应的SettingsItem),依次将每一个SettingsItem
的信息写入overlays.xml,文件内容如下:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<overlays version="3">
<item packageName="com.caic.car.rroresource" userId="0" targetPackageName="com.example.overlaydemo|com.android.systemui" baseCodePath="/product/overlay/RROResource/RROResource.apk" state="3" isEnabled="true" isStatic="false" priority="1" category="com.caic.car.theme.customization.rroresource" />
<item packageName="com.caic.car.rroresource" userId="10" targetPackageName="com.example.overlaydemo|com.android.systemui" baseCodePath="/product/overlay/RROResource/RROResource.apk" state="3" isEnabled="true" isStatic="false" priority="1" category="com.caic.car.theme.customization.rroresource" />
</overlays>
接下来方法updateAssets
是本篇的重点:
OverlayManagerService.updateAssets
private void updateAssets(final int userId, final String targetPackageName) {
//将String转换为集合
updateAssets(userId, Collections.singletonList(targetPackageName));
}
private void updateAssets(final int userId, List<String> targetPackageNames) {
//根据目标应用更新Overlay的路径
updateOverlayPaths(userId, targetPackageNames);
final IActivityManager am = ActivityManager.getService();
try {
//通知AMS处理目标应用的ApplicationInfo发生变化
am.scheduleApplicationInfoChanged(targetPackageNames, userId);
} catch (RemoteException e) {
// Intentionally left empty.
}
}
updateAssets
中首先调了updateOverlayPaths
的方法:
OverlayManagerService.updateOverlayPaths
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);
//如果目标应用集合中包含包名为"android"的应用(也就是framework-res)
final boolean updateFrameworkRes = targetPackageNames.contains("android");
if (updateFrameworkRes) {
//则会修改目标应用集合,赋值为系统已经安装的所有非Overlay的应用
targetPackageNames = pm.getTargetPackageNames(userId);
}
//用来保存目标应用与Overlay包的对应关系
final Map<String, List<String>> pendingChanges =
new ArrayMap<>(targetPackageNames.size());
synchronized (mLock) {
//获取包名为"android"(也就是framework-res)的Overlay包
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);
//这个list用来保存目标应用的Overlay包的集合
List<String> list = new ArrayList<>();
if (!"android".equals(targetPackageName)) {
//对于非framework-res的目标应用来说,除了自身的Overlay包以外还需要加入
//framework-res的Overlay包
list.addAll(frameworkOverlays);
}
//获取目标应用的Overlay包集合,加入list
list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
//以目标应用与Overlay集合存入Map
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);
}
//将每一个目标应用的Ovelray包信息更新到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);
}
}
这个方法的作用就是找到目标应用所对应的所有Overlay包,并将所有Overlay包的路径以(Key,Value)的形式保存到PMS中。
需要特殊处理的是包名为"android"的应用(framework-res),因为系统中的所有应用都可能使用了framework-res中的资源,所以一旦framework-res被Overlay,我们就需要将目标应用的集合改为整个系统中除开Overlay的所有应用了:
@Override
public List<String> getTargetPackageNames(int userId) {
List<String> targetPackages = new ArrayList<>();
synchronized (mPackages) {
for (PackageParser.Package p : mPackages.values()) {
//mOverlayTarget为空说明这不是一个Overlay应用
if (p.mOverlayTarget == null) {
targetPackages.add(p.packageName);
}
}
}
return targetPackages;
}
并且还需要将framework-res的Overlay包加入到其他所有应用的Overlay集合中,也就是说,如果framework-res被Overlay,那么系统中所有应用的Overlay list中都至少会包含framework-res的Overlay包。
接着我们来看看如何根据目标包名获取其Overlay集合:
OverlayManagerServiceImpl.getEnabledOverlayPackageNames
List<String> getEnabledOverlayPackageNames(@NonNull final String targetPackageName,
final int userId) {
//获取OverlayInfo集合
final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
userId);
final List<String> paths = new ArrayList<>(overlays.size());
final int n = overlays.size();
for (int i = 0; i < n; i++) {
final OverlayInfo oi = overlays.get(i);
if (oi.isEnabled()) {
//只获取状态为STATE_ENABLED的Overlay
paths.add(oi.packageName);
}
}
return paths;
}
OverlayManagerSettings.getOverlaysForTarget
List<OverlayInfo> getOverlaysForTarget(@NonNull final String targetPackageName,
final int userId) {
// Static RROs targeting "android" are loaded from AssetManager, and so they should be
// ignored in OverlayManagerService.
return selectWhereTarget(targetPackageName, userId)
.filter((i) -> !(i.isStatic() && "android".equals(i.getTargetPackageName())))
.map(SettingsItem::getOverlayInfo)
.collect(Collectors.toList());
}
OverlayManagerSettings
中对集合的操作都是使用了java8新增的Stream
,关于Stream
用法很简单,如果对js,python等脚本语言有过了解的应该一看就懂了,归根结底还是对集合的遍历,然后根据条件过滤。这里遍历的就是OverlayManagerSettings
内部的mItems
,即存储系统所有Overlay包对应SettingsItem
的集合,
selectWhereTarget
会从mItems
中筛选出匹配目标包名的Overlay应用集合,接着通过filter
进行过滤,过滤条件为:目标应用包名是"android"的静态Overlay,注释说的很清楚,目标应用包名是"android"的静态Overlay会在AssetManager
中加载,然后通过map
对得到的Overlay集合的每一个成员执行getOverlayInfo
方法,这里的作用就是将SettingsItem
转换为OverlayInfo
:
private OverlayInfo getOverlayInfo() {
if (mCache == null) {
mCache = new OverlayInfo(mPackageName, mTargetPackageName, mTargetOverlayableName,
mCategory, mBaseCodePath, mState, mUserId, mPriority, mIsStatic);
}
return mCache;
}
这里转换就很直接了,如果没缓存就new一个OverlayInfo
,所以最后就通过目标包名得到一个OverlayInfo
的集合,拿到OverlayInfo
集合后,状态为STATE_ENABLED
或者STATE_ENABLED_STATIC
才是目标应用有效的Overlay,关于Overlay包的状态更新在上篇文章分析过。
到此我们已经看完了系统是如何根据目标包名获取Overlay集合的,本质上就是遍历OverlayManagerSettings
内部的mItems
,然后根据条件进行过滤,最终返回的就是满足条件的集合。
再回到updateOverlayPaths
方法,目标应用与Overlay包以(key,value)的形式保存在了pendingChanges
中,一个目标应用可以对应多个Overlay,接着会将它们的对应关系更新到PMS:
PackageManagerService.setEnabledOverlayPackages
@Override
public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName,
@Nullable List<String> overlayPackageNames) {
synchronized (mPackages) {
//目标应用异常的情况,mPackages保存了PMS解析过的所有系统应用
if (targetPackageName == null || mPackages.get(targetPackageName) == null) {
Slog.e(TAG, "failed to find package " + targetPackageName);
return false;
}
ArrayList<String> overlayPaths = null;
if (overlayPackageNames != null && overlayPackageNames.size() > 0) {
final int N = overlayPackageNames.size();
overlayPaths = new ArrayList<>(N);
//遍历Overlay包的集合
for (int i = 0; i < N; i++) {
final String packageName = overlayPackageNames.get(i);
final PackageParser.Package pkg = mPackages.get(packageName);
//系统中并没有这个Overlay包,这是异常情况
if (pkg == null) {
Slog.e(TAG, "failed to find package " + packageName);
return false;
}
//保存Overlay包的安装路径
overlayPaths.add(pkg.baseCodePath);
}
}
//PackageSetting用来描述一个应用包的设置相关信息
final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
//将Overlay的路径更新到PackageSetting
ps.setOverlayPaths(overlayPaths, userId);
return true;
}
}
PackageSettingBase.setOverlayPaths
void setOverlayPaths(List<String> overlayPaths, int userId) {
modifyUserState(userId).overlayPaths = overlayPaths == null ? null :
overlayPaths.toArray(new String[overlayPaths.size()]);
}
PackageSettingBase.modifyUserState
private PackageUserState modifyUserState(int userId) {
PackageUserState state = mUserState.get(userId);
if (state == null) {
state = new PackageUserState();
mUserState.put(userId, state);
}
return state;
}
最终Overlay包的路径被保存到了目标应用对应的PackageUserState
的overlayPaths
中,记住这个位置,后面会有用。
到此updateOverlayPaths
方法已经分析完了,接着回到前面的updateAssets
方法:
private void updateAssets(final int userId, List<String> targetPackageNames) {
updateOverlayPaths(userId, targetPackageNames);
final IActivityManager am = ActivityManager.getService();
try {
am.scheduleApplicationInfoChanged(targetPackageNames, userId);
} catch (RemoteException e) {
// Intentionally left empty.
}
}
接下来要进入AMS了:
AMS.scheduleApplicationInfoChanged
@Override
public void scheduleApplicationInfoChanged(List<String> packageNames, int userId) {
enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
"scheduleApplicationInfoChanged()");
synchronized (this) {
final long origId = Binder.clearCallingIdentity();
try {
updateApplicationInfoLocked(packageNames, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
}
AMS.updateApplicationInfoLocked
void updateApplicationInfoLocked(@NonNull List<String> packagesToUpdate, int userId) {
final boolean updateFrameworkRes = packagesToUpdate.contains("android");
...
mProcessList.updateApplicationInfoLocked(packagesToUpdate, userId, updateFrameworkRes);
if (updateFrameworkRes) {
//如果被Overlay的目标应用中包含framework-res,则会回调onOverlayChanged,SystemUI很多主要控件
//都实现onOverlayChanged,以便在framework-res被Overlay之后能迅速改变界面
....
}
}
ProcessList.updateApplicationInfoLocked
@GuardedBy("mService")
void updateApplicationInfoLocked(List<String> packagesToUpdate, int userId,
boolean updateFrameworkRes) {
//遍历系统当前存活的所有进程
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
final ProcessRecord app = mLruProcesses.get(i);
if (app.thread == null) {
continue;
}
if (userId != UserHandle.USER_ALL && app.userId != userId) {
continue;
}
//packageCount表示同一进程的package数量,一般一个进程只有一个
final int packageCount = app.pkgList.size();
for (int j = 0; j < packageCount; j++) {
final String packageName = app.pkgList.keyAt(j);
//如果被Overlay的目标包含framework-res,或者当前进程中有被Overlay的目标package则会通知到APP进程
if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
try {
//获取目标package对应的ApplicationInfo
final ApplicationInfo ai = AppGlobals.getPackageManager()
.getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
if (ai != null) {
//通知APP进程
app.thread.scheduleApplicationInfoChanged(ai);
}
} catch (RemoteException e) {
Slog.w(TAG, String.format("Failed to update %s ApplicationInfo for %s",
packageName, app));
}
}
}
}
}
上面的方法会遍历系统中存活的所有进程,如果updateFrameworkRes
为true即被Overlay的目标包含framework-res则AMS会通知所有进程,它们的资源文件发生了改变,它们需要对这种情况进行处理。如果updateFrameworkRes
为false,则AMS只需要通知具体被Overlay的目标进程。
我们看通知APP进程调用的方法scheduleApplicationInfoChanged
,从名字能看出来这是APP的ApplicationInfo
发生了变化,那ApplicationInfo
的什么属性发生了变化呢?
接着来看getApplicationInfo
:
PackageManagerService.getApplicationInfo
@Override
public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) {
return getApplicationInfoInternal(packageName, flags, Binder.getCallingUid(), userId);
}
private ApplicationInfo getApplicationInfoInternal(String packageName, int flags,
int filterCallingUid, int userId) {
//省略非本篇重点
.....
synchronized (mPackages) {
...
PackageParser.Package p = mPackages.get(packageName);
if (p != null) {
PackageSetting ps = mSettings.mPackages.get(packageName);
//省略非本篇重点
....
ApplicationInfo ai = PackageParser.generateApplicationInfo(
p, flags, ps.readUserState(userId), userId);
if (ai != null) {
ai.packageName = resolveExternalPackageNameLPr(p);
}
return ai;
}
//省略非本篇重点
...
}
return null;
}
前面在看updateOverlayPaths
方法时分析过目标应用对应的Overlay包集合会被更新到PMS,最终Overlay包的路径是被保存到了目标应用对应的PackageUserState
的overlayPaths
属性中,这里构造ApplicationInfo
时调用了PackageParser.generateApplicationInfo
,传递的参数有一个是ps.readUserState
,这个参数实际上就是去拿到目标应用对应的PackageUserState
对象,有了PackageUserState
,也就有了前面更新到PMS的目标应用的Overlay路径,接着看:
PackageParser.generateApplicationInfo
@UnsupportedAppUsage
public static ApplicationInfo generateApplicationInfo(Package p, int flags,
PackageUserState state, int userId) {
//省略无关代码
.....
//ApplicationInfo的构造是根据已有的,再copy一份,然后修改一些属性
ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
//省略无关代码
...
updateApplicationInfo(ai, flags, state);
return ai;
}
PackageParser.updateApplicationInfo
private static void updateApplicationInfo(ApplicationInfo ai, int flags,
PackageUserState state) {
//省略无关属性更新
....
//目标应用对应的Overlay包路径最终被更新到了ApplicationInfo的resourceDirs属性中
ai.resourceDirs = state.overlayPaths;
...
}
我们只关注和Overlay包相关的信息更新,发现最终目标应用对应的Overlay包路径就被更新到了目标应用对应的ApplicationInfo
的resourceDirs
属性中,这个ApplicationInfo
被传递到目标应用所在的APP进程之后,APP进程就可以根据Overlay包来更新资源了,下一篇我们继续看APP进程的处理。