我们知道在Android的低版本中,源生是不支持动态overlay的,也就是说,所有的overlay都是静态的,对于一个target package而言,它只要有overlay package,那么它肯定会被优先级最高的那个overlay package所覆盖,我们不能动态地去enable或者disable一个overlay package。不过,到了Android Oreo(不过我们的代码分析还是结合Android 10),Android引入了OverlayManagerService(后面简称OMS),正式支持了动态overlay。
OverlayManagerService的作用
OverlayManagerService提供了一些API,我们通过这些API可以随时指定哪个overlay包生效,我们也可以随时查询一个包的状态,要详细了解OMS的功能,我们可以先看一下,相关接口的定义:
//frameworks/base/core/java/android/content/om/IOverlayManager.aidl
interface IOverlayManager {
//获取overlay相关的信息,这是一组接口,只介绍其中两个
/**
* 返回指定user下,系统中已经安装的所有overlay package的信息,如果没有overlay package,则返回空。
* @param userId 指定的userId.这个是我们手机概念中的userId,而非linux的uid
* @return 返回的map中,key是一个一个的target包的包名,value是一个List,表示这个target包中所有
* overlay包的信息,list中的元素是OverlayInfo,它里面记录着Overlay相关的信息
*/
Map getAllOverlays(in int userId);
/**
* 返回指定user下,某一个target pacakge的所有overlay package,如果没有,则返回空
* @param targetPackageName 指定的target package
* @param userId 指定的userId.这个是我们手机概念中的userId,而非linux的uid
* @return 返回一个List,它里面的元素是OverlayInfo
*/
List getOverlayInfosForTarget(in String targetPackageName, in int userId);
/**动态enable或者disable overlay package的相关接口,也是一组,只介绍两个*/
/**
* 对某个用户,enable或者disable一个指定的overlay package
* @param packageName 我们要enable或者disable的overlay package的包名
* @param enable true or false enable or disable
* @userId 指定的userId
* @return 操作成功返回true,否则返回false
*/
boolean setEnabled(in String packageName, in boolean enable, in int userId);
/**
* 对某个用户,enable或者disable一个指定的overlay package,同时对其它overlay package做反向操作
* @param packageName 我们要enable或者disable的overlay package的包名
* @param enable true or false enable or disable
* @userId 指定的userId
* @return 操作成功返回true,否则返回false
*/
boolean setEnabledExclusive(in String packageName, in boolean enable, in int userId);
//设置overlay package的优先级,也是一组接口,只介绍两个
/**
* 对于指定的用户,把一个overlay package的优先级设置为最高
* @param packageName 要设置的overlay包的包名
* @param userId 指定的userId
* @return 操作成功则返回true,否则返回false
*/
boolean setHighestPriority(in String packageName, in int userId);
/**
* 对于指定的用户,把一个overlay package的优先级设置为最低
* @param packageName 要设置的overlay包的包名
* @param userId 指定的userId
* @return 操作成功则返回true,否则返回false
*/
boolean setLowestPriority(in String packageName, in int userId);
}
我们看到,这个接口中的方法主要分为三类:
-
获取overlay包的信息。比如我们通过这些方法得到:一个target包有哪些overlay 包;
某个overlay包的target包是哪个等等。 -
设置一个overlay包的优先级。可以把优先级设定成最高,或者最低,也可以设置得比某个包高。
-
动态enable或者disable一个overlay包。
这里我们需要说明的是,disable一个overlay package和设置一个overlay package 都可能影响最终overlay的效果。但是一旦一个overlay package的被disable了,不论它的优先级有多高,它都不会生效;但是把一个overlay package的优先级设置到最低,它仍有生效的机会,比如对应的target package的overlay package只有它一个或者其它overlay package 都被disable了。
另外,并不是所有的overlay pacakge都是可以被enable或者disable的。在Android 8之前,所有的overlay package 默认都是 enable 并且不能修改为disable的(因为Android 8之前还没有OMS)。在Android 8及以后的版本中,如果一个overlay package的AndroidManifest.xml中的overlay标签中有这个属性android:isStatic=“true” ,那么这个overlay package就不可以被enable或者disable,也就是说setEnabled组的那些方法都会失败。我们说这样的overlay包是一个静态的overlay包,否则我们就说这样的overlay包是一个动态的overlay包,我们可以动态enable或者disable它。其实Android还有一种静态overlay机制,它是指,oem厂商可以把自己定制的东西放到一个overlay目录下,这样在编译ROM的时候,编译系统会用这个目录下的文件来代替源生对应的文件来参与编译,这样就实现了oem厂商的代码和AOSP代码的隔离,注意和我们这里说的静态overlay package的区别,两者不是一个东西。
OverlayManagerService的架构
这个图是OverlayManagerService.java中的原图,它非常简明地描述了OMS的架构,OMS涉及到的核心类也就OverlayManagerService
、OverlayManagerServiceImpl
、OverlayManagerSettings
、IdmapManager
四个,再加上OverlayManagerTests
(严格来说这只是个测试类,说不上是OMS的核心模块),它们被分成了3个模块,图中已经用(1)(2)(3)标出来了:
- 模块(1),Android Framework等上层模块,它们是OMS相关服务的正式使用者。需要说明的是,
OverlayManagerService
类只是一个空壳,它只不过是OMS相关服务在system_server中的一个接口类,实现了IOverlayManager.aidl中定义的接口。这个接口可以通过aidl为应用层提供服务。需要说明的是OMS除了被动被应用层调用外,也会监听一些系统广播,比如包信息变化的广播。当一个包被卸载了的时候,如果它有overlay package,OMS会删除OverlayManagerSettings
中这个包的overlay设置信息,还会发一个action=Intent.ACTION_OVERLAY_CHANGED
的广播,以通知感兴趣的组件这个包的overlay信息发生了改变;同样,当我们安装一个包的时候,如果系统中已经存在这个包的overlay包,那OMS会添加OverlayManagerSettings
中overlay 包的设置信息,同时让overlay包生效,让overlay包生效的逻辑主要也在OverlayManagerService
这个类中。 - 模块(2),这部分才是OMS相关服务的真正实现者,
OverlayManagerService
类只是空壳,OverlayManagerServiceImpl
类才是真正干活的。这个模块主要的工作就2个:更新OverlayManagerSettings
中overlay包的信息以及调用IdmapManager
动态生成idmap文件。我们知道,要想支持动态enable或者disable一个overlay包,我们就必须记录每一个overlay包的状态信息,并且还得把这些信息持久化,否则一重启机器,overlay包变了,那就太不合理了。所以,这个任务就落在了OverlayManagerSettings
这个类身上,它负责管理这些信息,不仅仅会在内存中记录这些信息,还会把这些信息持久化到/data/system/overlays.xml这个文件中。IdmapManager
则是负责idmap文件的生成,在Android 10中由于引入了idmap2,所以它还会决定到底使用idmap这个bin还是service_manager中对应的idmap服务来生成,关于idmap相关的我们后面会详说。 - 模块(3),就是一个测试模块,在测试的时候,会直接和模块(2)交互;在实际的使用中,会被模块(1)取代。
OverlayManagerSettings
OverlayManagerSettings
这个类主要负责overlay包信息的维护,包括内存中的信息,以及持久化信息,并且保持两者同步。在说它之前,我先看一下持久化信息,也就是/data/system/overlays.xml:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<overlays version="3">
<item packageName="com.android.systemui.auto_generated_rro_vendor__"
userId="0"
targetPackageName="com.android.systemui"
baseCodePath="/vendor/overlay/SystemUI__auto_generated_rro_vendor.apk"
state="6"
isEnabled="true"
isStatic="true"
priority="0" />
......
<item packageName="com.android.theme.icon_pack.filled.systemui"
userId="0"
targetPackageName="com.android.systemui"
baseCodePath="/product/overlay/IconPackFilledSystemUI/IconPackFilledSystemUIOverlay.apk"
state="2"
isEnabled="false"
isStatic="false"
priority="1"
category="android.theme.customization.icon_pack.systemui" />
......
</overlays>
我们可以看到,每一个item,表示一个overlay信息,对应于OverlayManagerSettings.SettingItem
类的一个实例。它描述了一个overlay的基本信息,比如overlay包是什么,target包是什么,这个overlay包的状态、优先级等等,我们结合SettingItem
详说:
//frameworks/base/services/core/java/com/android/server/om/OverlayManagerSettings.java
private static final class SettingsItem {
/**
* overlay包要作用于系统的哪个用户
*/
private final int mUserId;
/**
* overlay包的包名
*/
private final String mPackageName;
/**
* target包的包名
*/
private final String mTargetPackageName;
/**
* overlay包的目标overlayable,关于overlayable
* 我们后面会详说,这个是Android 10 引入的,跟overlay策略有关。
* 这里可以先不用太在意
*/
private final String mTargetOverlayableName;
/**
* overlay包的路径
*/
private String mBaseCodePath;
/**
* overlay包的状态,这里的状态包括是否有target包(考虑target包被卸载的时候)、
* 是否已经生成了idmap文件、是否正在更新等
*/
private @OverlayInfo.State int mState;
/**
* overlay包是否被enable了
*/
private boolean mIsEnabled;
/**
* 用来将一个SettingItem转化为一个OverlayInfo
* 为什么要有这么个转换呢?
* 我的理解是SettingItem是private的,它是不对外公开的,
* 这个当然是为了安全,防止应用直接接触SettingItem,进而有意
* 或无意篡改/data/system/overlays.xml,也就是系统的overlay信息
* 但OverlayInfo是公开的,应用对它的修改不会同步到SettingItem,进而
* 影响/data/system/overlays.xml
*/
private OverlayInfo mCache;
/**
* overlay包是否是Static的,一个overlay包如果是static的,那么它是不能
* 被动态enable或者disable的
*/
private boolean mIsStatic;
/**
* overlay包的优先级
*/
private int mPriority;
/**
* overlay包的类别,目前要么是空,要么是CATEGORY_THEME,
* Google用它来改变Android系统的主题,不过这个和国内
* 手机厂商普遍的主题市场啥的没关系。
*/
private String mCategory;
}
当我们动态enable或者disable一个overlay包的时候,OMS会通过OverlayManagerSettings
的接口,修改里面的信息,并且会把这些信息持久化到/data/system/overlays.xml;当我们开机的时候,OMS会通过OverlayManagerSettings
的接口,去读取/data/system/overlays.xml里的数据,构造出一个一个的SettingItem
,我们就可以去查询overlay信息了。这两个过程的实现,是OverlayManagerSettings
通过persist
和restore
两个方法实现的,这两个方法则是FastXmlSerializer
来将我们的SettingItem
转化为xml形式的数据,最终写入xml文件或者将xml文件中的数据解析出来,构造出一个一个的SettingItem
,我们就不详说了,感兴趣的话,可以查看frameworks/base/services/core/java/com/android/server/om/OverlayManagerSettings.java
OverlayManagerServiceImpl
OMS中相关服务的实现主要分为两部分,第一部分在OverlayManagerServiceImpl
中,它主要负责维护overlay包的状态信息,生成idmap文件,我们以动态enable一个overlay package为例来走一下大概流程:
OMS给应用端的接口封装在OverlayManager.java
中,应用可以通过Context.getSystemService(Context.OVERLAY_SERVICE)
方法得到其实例。
//frameworks/base/core/java/android/content/om/OverlayManager.java
/**
* Updates OverlayManager state; gets information
* about installed overlay packages.
* @hide
*/
@SystemApi
@SystemService(Context.OVERLAY_SERVICE)
public class OverlayManager {
/**
* Binder代理,其实现为system_server进程中的OMS
*/
private final IOverlayManager mService;
private final Context mContext;
/*...省略部分代码...*/
/**
* 动态enable或者disable一个overlay package
* @param packageName 要操作的overlay package的包名
* @param enable true or false
* @user 这个操作针对的是哪个用户
*/
@SystemApi
@RequiresPermission(anyOf = {
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.INTERACT_ACROSS_USERS_FULL"
})
public void setEnabled(@NonNull final String packageName, final boolean enable,
@NonNull UserHandle user) {
try {
if (!mService.setEnabled(packageName, enable, user.getIdentifier())) {
throw new IllegalStateException("setEnabled failed");
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return;
}
/*...省略部分代码...*/
}
这个逻辑非常简单,就是OverlayManager
通过mService
这个binder代理,去访问OMS完成具体的操作。不过这里需要说明的是,虽然IOverlayManager.aidl
里定义了很多接口方法,但是OverlayManager
所提供的却非常少,所以如果我们想要访问更多的接口方法,建议使用如下方式来访问:
IOverlayManager mService = ServiceManager.getService(Context.OVERLAY_SERVICE);
我们直接使用mService
这个代理,就可以访问IOverlayManager.aidl
里定义的所有接口方法了。另外,我们也看到了,OverlayManager
是隐藏的系统API,一般应用是不能直接访问的。setEnabled
方法在OMS中的实现如下:
//frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java
@Override
public boolean setEnabled(@Nullable final String packageName, final boolean enable,
int userId) throws RemoteException {
try {
/**trace、权限检查、合法性检查相关的代码,略去**/
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
//调用了mImpl的对应方法,mImpl是OverlayManagerServiceImpl类的实例
return mImpl.setEnabled(packageName, enable, userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
这个逻辑也很简单,就是调用OMS实现类,不过我们看到在调用实现类对象的对应方法前后,有final long ident = Binder.clearCallingIdentity();
和Binder.restoreCallingIdentity(ident);
这两行代码。这种现象在Android Framework的代码中是非常常见的,为什么要有这两行代码呢?我们假设这个setEnable
方法是从某个应用通过binder调到OMS的,也就是说,这个setEnable
方法,是在binder线程中执行的binder方法。那么我们通过Binder.getCallingUid()
方法(这个方法会在OMS去获取包信息,调用PMS的时候,被PMS用来获取请求方的uid)得到到肯定是这个应用的uid,那这时候使用这个uid有可能就通不过PMS的权限检查。所以,要通过final long ident = Binder.clearCallingIdentity();
这行代码,把Binder里面的mCallingPid
和mCallingUid
设置为当前进程,也就是system_server的pid和uid,这样就不会有问题了(这样也是合理的,毕竟这时候已经是OMS代替应用去访问PMS了,而不是应用进程去访问的)。当然,访问结束后还要记得恢复,这就是Binder.restoreCallingIdentity(ident);
接着,我们看OverlayManagerServiceImpl
的实现:
//framework/base/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
boolean setEnabled(@NonNull final String packageName, final boolean enable,
final int userId) {
//通过PM拿到overlay包的包信息
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
if (overlayPackage == null) {
return false;
}
/**
* 如果这个overlay包是static的,也就是说AndroidManifest.xml的overlay标签中
* 设置了android:isStatic="true",那么这个overlay包是不能被动态enable或者disable
* 的,直接return false,操作失败
*/
if (overlayPackage.isStaticOverlayPackage()) {
return false;
}
try {
//获取这个包的overlay信息,mSettings是OverlayManagerSettings的实例
final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
/**
* 设置mSettings中对应SettingItem的mIsEnabled字段
* 如果要设置的值和里面的值一样,或者这个overlay包是static的
* 则返回false,否则modified为true
*/
boolean modified = mSettings.setEnabled(packageName, userId, enable);
//更新overlay包的状态
modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0);
/**
* 如果这个overlay包的信息有变动,则通知OverlayManagerService
* OverlayManagerService会负责让这个变动生效
*/
if (modified) {
mListener.onOverlaysChanged(oi.targetPackageName, userId);
}
return true;
} catch (OverlayManagerSettings.BadKeyException e) {
return false;
}
}
这个方法的核心是两个操作:更改OverlayManagerSettings
中的overlay信息以及让这个改动生效。先看信息的改动:
//framework/base/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
/**
* 只有当真正有信息改动的时候才return true
*/
private boolean updateState(@NonNull final String targetPackageName,
@NonNull final String overlayPackageName, final int userId, final int flags)
throws OverlayManagerSettings.BadKeyException {
//拿到target package和overlay package的包信息
final PackageInfo targetPackage = mPackageManager.getPackageInfo(targetPackageName, userId);
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName,
userId);
/**
* 生成idmap文件,这里要说明的是,为什么不对framework-res.apk的静态overlay包生成idmap
* 文件。因为它会在native层生成,我们之前的文章中有介绍,这里不再多说
*/
if (targetPackage != null && overlayPackage != null
&& !("android".equals(targetPackageName)
&& overlayPackage.isStaticOverlayPackage())) {
mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
}
boolean modified = false;
//将包信息更新到OverlayManagerSettings里面去
if (overlayPackage != null) {
modified |= mSettings.setBaseCodePath(overlayPackageName, userId,
overlayPackage.applicationInfo.getBaseCodePath());
modified |= mSettings.setCategory(overlayPackageName, userId,
overlayPackage.overlayCategory);
}
//获OverlayManagerSettings里的状态
final @OverlayInfo.State int currentState = mSettings.getState(overlayPackageName, userId);
//计算新的状态
final @OverlayInfo.State int newState = calculateNewState(targetPackage, overlayPackage,
userId, flags);
//状态不一样的话,则更新之
if (currentState != newState) {
modified |= mSettings.setState(overlayPackageName, userId, newState);
}
return modified;
}
//计算overlay包的状态
private @OverlayInfo.State int calculateNewState(@Nullable final PackageInfo targetPackage,
@Nullable final PackageInfo overlayPackage, final int userId, final int flags)
throws OverlayManagerSettings.BadKeyException {/* flags = 0 */
if ((flags & FLAG_TARGET_IS_BEING_REPLACED) != 0) {
return STATE_TARGET_IS_BEING_REPLACED;
}
if ((flags & FLAG_OVERLAY_IS_BEING_REPLACED) != 0) {
return STATE_OVERLAY_IS_BEING_REPLACED;
}
// assert expectation on overlay package: can only be null if the flags are used
if (DEBUG && overlayPackage == null) {
throw new IllegalArgumentException("null overlay package not compatible with no flags");
}
// target pacakge 不存在
if (targetPackage == null) {
return STATE_MISSING_TARGET;
}
//idmap文件没有生成
if (!mIdmapManager.idmapExists(overlayPackage, userId)) {
return STATE_NO_IDMAP;
}
/**
* 一般情况下如果一个overlay package 是static的
* 那么它也应该是enabled,否则它将永远是disable的
* 这是没有意义的
*/
if (overlayPackage.isSytaticOverlayPackage()) {
return STATE_ENABLED_STATIC;
}
//以上检查都没问题,才会看它是否是enabled,这个状态的优先级是最低的
final boolean enabled = mSettings.getEnabled(overlayPackage.packageName, userId);
return enabled ? STATE_ENABLED : STATE_DISABLED;
}
对于我们的setEnabled
流程而言,信息的改动其实就是修改OverlayManagerSettings
中对应item的信息,然后计算并更新它的状态。需要说明的是,当overlay包或者target包改变的时候(比如overlay包或者target包被删除、改变、增加等),都会触发updateState
方法,所以它的实现相对复杂些。另外,在这个过程中还涉及到了idmap文件的生成和更新,关于idmap我们后面会详说。
然后就是让这个变动生效了,我们有了overlay包和target包,还有idmap文件,这个时候从原理上来说,我们已经万事俱备了。但是到目前为止,我们操作的都是设置状态信息和生成idmap文件,target包所对应的进程(如果这个包已经在运行了)的AssetManager2还没有重新加载新的overlay包,也就是说,target包所对应的进程的显示效果还是以前的老效果,怎么让它生效呢?答案就在mListener.onOverlaysChanged(oi.targetPackageName, userId);
这行代码,mListener
是OverlayManagerService
在构造OverlayManagerServiceImpl
的时候传过来的,它里面的逻辑我们下篇再说。