概述
上一篇文章讲了RRO的简单用法,本篇文章来看看支撑RRO的系统服务OverlayManagerService
的实现细节,OverlayManagerService
是Android8.0引入了一个系统服务来配合RRO,大致结构如下:
* <pre>
* Android framework
* | ^
* . . . | . . . . | . . . .
* . | | .
* . AIDL, broadcasts .
* . intents | .
* . | | . . . . . . . . . . . .
* . v | . .
* . OverlayManagerService . OverlayManagerTests .
* . \ . / .
* . (1) \ . / (3) .
* . . . . . . . . . . \ . . . / . . . . . . . . .
* . \ / .
* . (2) \ / .
* . OverlayManagerServiceImpl .
* . | | .
* . | | .
* . OverlayManagerSettings IdmapManager .
* . .
* . . . . . . . . . . . . . . . . . . . . . .
* </pre>
上面是从OverlayManagerService
的注释中copy来的,它很好的描述了OverlayManagerService
的架构:
- 首先
OverlayManagerService
运行在system_server
进程,client
端通过AIDL接口进行访问。 - 其次
OverlayManagerService
的具体逻辑放在OverlayManagerServiceImpl
中实现,OverlayManagerSettings
和IdmapManager
用来辅助OverlayManagerServiceImpl
完成一些逻辑。
那我们这篇文章就来分析分析RRO的这个架构,RRO系列文章都是基于AndroidQ代码分析。
OverlayManagerService初始化
OverlayManagerService
开机时在SystemServer
中启动:
private void startBootstrapServices() {
......
// Manages Overlay packages
traceBeginAndSlog("StartOverlayManagerService");
mSystemServiceManager.startService(new OverlayManagerService(mSystemContext, installer));
traceEnd();
......
}
从启动时机可以看出这个服务其实是非常重要的,它属于系统的启动引导服务,OverlayManagerService
的onStart
方法是个空实现,它的所有初始化都放在了构造方法中:
public OverlayManagerService(@NonNull final Context context,
@NonNull final Installer installer) {
super(context);
try {
traceBegin(TRACE_TAG_RRO, "OMS#OverlayManagerService");
//创建overlays.xml,全路径为/data/system/overlays.xml,这个xml文件主要用于保存Overlay包的相关信息
mSettingsFile = new AtomicFile(
new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays");
mPackageManager = new PackageManagerHelper();
mUserManager = UserManagerService.getInstance();
//Idmap文件的操作管理类,涉及Idmap文件的创建,删除等
IdmapManager im = new IdmapManager(installer, mPackageManager);
//这个类用来描述Overlay包相关状态的数据结构
mSettings = new OverlayManagerSettings();
//OverlayManagerService的具体实现
mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
getDefaultOverlayPackages(), new OverlayChangeListener());
//注册对Overlay包以及目标包的状态监听,被卸载,被覆盖等
final IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(ACTION_PACKAGE_ADDED);
packageFilter.addAction(ACTION_PACKAGE_CHANGED);
packageFilter.addAction(ACTION_PACKAGE_REMOVED);
packageFilter.addDataScheme("package");
getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL,
packageFilter, null, null);
//多用户相关的广播
final IntentFilter userFilter = new IntentFilter();
userFilter.addAction(ACTION_USER_ADDED);
userFilter.addAction(ACTION_USER_REMOVED);
getContext().registerReceiverAsUser(new UserReceiver(), UserHandle.ALL,
userFilter, null, null);
//读取保存在/data/system/overlays.xml中的overlay包的状态数据
restoreSettings();
initIfNeeded();
onSwitchUser(UserHandle.USER_SYSTEM);
//将OverlayManagerService内部的Binder服务注册到ServiceManager以便Client端使用
publishBinderService(Context.OVERLAY_SERVICE, mService);
//让OverlayManagerService只能被系统进程访问,不具备系统签名的三方应用是无法使用RRO的
publishLocalService(OverlayManagerService.class, this);
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
OverlayManagerService
构造方法中初始化了一些重要的东西:
首先创建了文件/data/system/overlays.xml
用于保存Overlay包的状态信息:
<overlays version="3">
<item packageName="com.caic.car.rroresource" userId="0" targetPackageName="com.example.overlaydemo" baseCodePath="/product/overlay/RROResource/RROResource.apk" state="2" 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" baseCodePath="/product/overlay/RROResource/RROResource.apk" state="3" isEnabled="true" isStatic="false" priority="1" category="com.caic.car.theme.customization.rroresource" />
</overlays>
这个文件中一个个item对应的就是OverlayManagerSettings
中的SettingsItem
,即对Overlay包的描述信息。
然后创建了IdmapManager
,OverlayManagerSettings
,OverlayManagerServiceImpl
,这三个类是RRO框架的重要组成部分,IdmapManager
用于生成Idmap文件,Idmap文件用来描述Target应用与Overlay应用之间的资源映射关系,通过命令adb shell idmap2 dump --idmap-path xxx
可以查看其中的具体映射详情:
target apk path : /system/app/OverlayDemo/OverlayDemo.apk
overlay apk path : /product/overlay/RROResource/RROResource.apk
0x7f030000 -> 0x7f010001 drawable/ic_launcher_background
0x7f030002 -> 0x7f010003 drawable/test1
0x7f030003 -> 0x7f010004 drawable/test2
OverlayManagerSettings
主要用来管理系统中所有的Overlay包,它内部通过SettingsItem
来描述一个Overlay包的信息,更准确的说是通过SettingsItem
内部的OverlayInfo
来描述的,外部可以通过set和get来设置和获取Overlay包的状态。
OverlayManagerServiceImpl
这个类就是OverlayManagerService
的具体实现逻辑了,比如Overlay包的状态设置,信息获取,状态更新,Idmap创建与删除等,OverlayManagerServiceImpl
中的很大部分逻辑是交给OverlayManagerSettings
和IdmapManager
去做的。
紧接着OMS构造方法中注册了几个对RRO影响非常大的操作的广播,分为两类,一类是包的状态更新(不管是Target包还是Overlay包,安装卸载都需要更新很多状态),二类是多用户相关的广播(RRO这种明显改变UI界面的操作必须考虑多用户,用户的增删都需要更新Target与Overlay的状态)。
剩下的操作也比较简单,restoreSettings
用来读取/data/system/overlays.xml
中保存的Overlay状态,拿到这些状态值之后会构造一个SettingsItem
,publishBinderService
用来将OMS内部的Binder服务注册到ServiceManager
以便Client端使用,publishLocalService
使得此服务只能系统进程访问,RRO这个功能普通的三方应用是无法使用的,只有具有系统签名的应用才能使用。
OMS的构造方法比较简单,接着我们根据上一篇文章的例子来看看Overlay包是如何生效的,上一篇文章说道Overlay包编译好之后push进设备,使用命令adb shell dumpsys overlay
可以查看Overlay的状态:
com.caic.car.rroresource:0 {
mPackageName...........: com.caic.car.rroresource
mUserId................: 0
mTargetPackageName.....: com.example.overlaydemo
mTargetOverlayableName.: null
mBaseCodePath..........: /product/overlay/RROResource/RROResource.apk
------------------------------
targetPackageName:com.example.overlaydemo,mState = :STATE_DISABLED
------------------------------
mIsEnabled.............: false
mIsStatic..............: false
mPriority..............: 1
mCategory..............: com.caic.car.theme.customization.rroresource
}
当前我们这个包状态为STATE_DISABLED
,我们可以通过命令adb shell cmd overlay enable --user 0 com.caic.car.rroresource
使Overlay生效,这个命令会通过OverlayManagerShellCommand
的onCommand
为入口,调到OMS的setEnabled
方法中,流程很简单不贴出来了。
我们直接来看OMS的setEnabled
方法:
OverlayManagerService.setEnabled
@Override
public boolean setEnabled(@Nullable final String packageName, final boolean enable,
int userId) throws RemoteException {
try {
.....
try {
synchronized (mLock) {
return mImpl.setEnabled(packageName, enable, userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
具体实现在OverlayManagerServiceImpl
中:
OverlayManagerServiceImpl.setEnabled
boolean setEnabled(@NonNull final String packageName, final boolean enable,
final int userId) {
//获取overlay的PackageInfo,这里的packageName就是com.caic.car.rroresource
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
if (overlayPackage == null) {
return false;
}
//如果Overlay是静态的,则不允许动态enable或者disable
if (overlayPackage.isStaticOverlayPackage()) {
return false;
}
try {
//获取描述Overlay信息的OverlayInfo
final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
//判断此次设置的状态和上一次状态是否一致,不一致则保存到SettingsItem并返回true,一致则返回false
boolean modified = mSettings.setEnabled(packageName, userId, enable);
//此方法里面会计算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;
}
}
这个方法首先会判断要Enable的Overlay是否为静态的,如果是静态的Overlay是不允许Enable或者Disable的,Android也提供了一种静态Overlay的方式,一般设备厂商可以在Vendor目录定义特定于项目的资源文件,在出厂是自动Overlay原生的资源,最常见的就是开关机动画了。
所以会有这个判断,它的值是怎么来的呢?做Overlay包时,在其AndroidManifest.xml
的overlay标签中个属性isStatic
,就是这个值,在PKMS解析Overlay包时会将isStatic
的值存到PackageInfo
中:
pkg.mOverlayIsStatic = sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic,
false);
接着来看getOverlayInfo
:
OverlayManagerSettings.getOverlayInfo
@NonNull OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId)
throws BadKeyException {
final int idx = select(packageName, userId);
if (idx < 0) {
throw new BadKeyException(packageName, userId);
}
return mItems.get(idx).getOverlayInfo();
}
select
是其内部封装的一个对mItems
的集合操作方法,mItems
是OverlayManagerSettings
内部用来存储系统所有Overlay包对应的SettingsItem
的ArrayList,select
的操作就是从中找到匹配packageName
和userId
的目标,mItems
是啥时候保存SettingsItem
的呢?
来看看OverlayManagerSettings
的init
方法:
private final ArrayList<SettingsItem> mItems = new ArrayList<>();
void init(@NonNull final String packageName, final int userId,
@NonNull final String targetPackageName, @Nullable final String targetOverlayableName,
@NonNull final String baseCodePath, boolean isStatic, int priority,
@Nullable String overlayCategory) {
remove(packageName, userId);
final SettingsItem item =
new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
baseCodePath, isStatic, priority, overlayCategory);
if (isStatic) {
// All static overlays are always enabled.
item.setEnabled(true);
int i;
for (i = mItems.size() - 1; i >= 0; i--) {
SettingsItem parentItem = mItems.get(i);
if (parentItem.mIsStatic && parentItem.mPriority <= priority) {
break;
}
}
int pos = i + 1;
if (pos == mItems.size()) {
mItems.add(item);
} else {
mItems.add(pos, item);
}
} else {
mItems.add(item);
}
}
这个方法里面会根据Overlay包的信息构造SettingsItem
,然后对静态和动态的Overlay包分别存储,动态Overlay包从mItems
头部开始依次存入,静态的Overlay包从mItems
尾部开始添加并且会根据优先级来排序,大的在前小的在后。
再来看看init
方法调用的地方,在OverlayManagerServiceImpl
中有三处调用,最常见的调用来自onOverlayPackageAdded
,很好理解。
接着我们继续看SettingsItem
的getOverlayInfo
方法:
SettingsItem.getOverlayInfo
private OverlayInfo getOverlayInfo() {
if (mCache == null) {
mCache = new OverlayInfo(mPackageName, mTargetPackageName, mTargetOverlayableName,
mCategory, mBaseCodePath, mState, mUserId, mPriority, mIsStatic);
}
return mCache;
}
可以看到这里就很直接的创建了一个OverlayInfo
,这个类才是真正用来描述一个Overlay包的全部信息的数据结构。
回到OverlayManagerServiceImpl.setEnabled
中,OverlayInfo
拿到之后我们重点来看updateState
方法:
OverlayManagerServiceImpl.updateState
private boolean updateState(@NonNull final String targetPackageName,
@NonNull final String overlayPackageName, final int userId, final int flags)
throws OverlayManagerSettings.BadKeyException {
final PackageInfo targetPackage = mPackageManager.getPackageInfo(targetPackageName, userId);
final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName,
userId);
//目标应用包名为"android"的framework-res.apk的静态Overlay,不会为其创建Idmap,
//因为在native层已经创建了
if (targetPackage != null && overlayPackage != null
&& !("android".equals(targetPackageName)
&& overlayPackage.isStaticOverlayPackage())) {
//创建Idmap
mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
}
boolean modified = false;
if (overlayPackage != null) {
//将Overlay包的安装目录和Category属性的值保存到SettingsItem
modified |= mSettings.setBaseCodePath(overlayPackageName, userId,
overlayPackage.applicationInfo.getBaseCodePath());
modified |= mSettings.setCategory(overlayPackageName, userId,
overlayPackage.overlayCategory);
}
//当前状态
final @OverlayInfo.State int currentState = mSettings.getState(overlayPackageName, userId);
//计算新的状态
final @OverlayInfo.State int newState = calculateNewState(targetPackage, overlayPackage,
userId, flags);
if (currentState != newState) {
if (DEBUG) {
Slog.d(TAG, String.format("%s:%d: %s -> %s",
overlayPackageName, userId,
OverlayInfo.stateToString(currentState),
OverlayInfo.stateToString(newState)));
}
//如果两个状态不等,则将新状态保存至SettingsItem
modified |= mSettings.setState(overlayPackageName, userId, newState);
}
return modified;
}
这个方法中最重要的两点就是创建Idmap文件和计算Overlay的状态了,先来看Idmap的创建:
IdmapManager.createIdmap
boolean createIdmap(@NonNull final PackageInfo targetPackage,
@NonNull final PackageInfo overlayPackage, int userId) {
final int sharedGid = UserHandle.getSharedAppGid(targetPackage.applicationInfo.uid);
final String targetPath = targetPackage.applicationInfo.getBaseCodePath();
final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath();
try {
//FEATURE_FLAG_IDMAP2默认为true
if (FEATURE_FLAG_IDMAP2) {
//计算Overlay包的安全策略
int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId);
//是否强制执行Overlay的检查策略,Q版本及之后默认为true
boolean enforce = enforceOverlayable(overlayPackage);
//判断是否有必要重新创建Idmap
if (mIdmap2Service.verifyIdmap(overlayPath, policies, enforce, userId)) {
return true;
}
//创建Idmap
return mIdmap2Service.createIdmap(targetPath, overlayPath, policies,
enforce, userId) != null;
} else {
mInstaller.idmap(targetPath, overlayPath, sharedGid);
return true;
}
} catch (Exception e) {
return false;
}
}
可以看到和Idmap文件相关的操作具体由mIdmap2Service
来实现的,mIdmap2Service
是名为IDMAP_SERVICE
(“idmap”)的Binder服务端,其实现在native层:
IBinder binder = ServiceManager.getService(IDMAP_SERVICE);
mIdmap2Service = IIdmap2.Stub.asInterface(binder);
Idmap2Service::createIdmap
Status Idmap2Service::createIdmap(const std::string& target_apk_path,
const std::string& overlay_apk_path, int32_t fulfilled_policies,
bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
std::unique_ptr<std::string>* _aidl_return) {
....
const PolicyBitmask policy_bitmask = ConvertAidlArgToPolicyBitmask(fulfilled_policies);
//Idmap文件的生成路径:/data/resource-cache目录下,文件名为Overlay包路径拼接而成
//例如:/data/resource-cache/product@overlay@RROResource@RROResource.apk@idmap
const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
....
const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
if (!target_apk) {
return error("failed to load apk " + target_apk_path);
}
//这里会加载Overlay包的resource.arsc
const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
if (!overlay_apk) {
return error("failed to load apk " + overlay_apk_path);
}
const auto idmap = Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path,
*overlay_apk, policy_bitmask, enforce_overlayable);
......
return ok();
}
Idmap的创建比较复杂,想要完全搞清楚原理与细节需要apk编译打包相关知识,并且需要对resource.arsc文件结构有一定了解,这部分也很复杂所以我们这里不会详细去看Idmap实现原理,只需要知道最关键的部分就行了:
Idmap::FromApkAssets
Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const std::string& target_apk_path,
const ApkAssets& target_apk_assets,
const std::string& overlay_apk_path,
const ApkAssets& overlay_apk_assets,
const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable) {
//省略一大堆条件判断
......
// 查找目标应用和Overlay应用中都存在的资源
MatchingResources matching_resources;
const auto end = overlay_pkg->end();
for (auto iter = overlay_pkg->begin(); iter != end; ++iter) {
const ResourceId overlay_resid = *iter;
Result<std::string> name = utils::ResToTypeEntryName(overlay_asset_manager, overlay_resid);
if (!name) {
continue;
}
// prepend "<package>:" to turn name into "<package>:<type>/<name>"
const std::string full_name =
base::StringPrintf("%s:%s", target_pkg->GetPackageName().c_str(), name->c_str());
const ResourceId target_resid = NameToResid(target_asset_manager, full_name);
if (target_resid == 0) {
continue;
}
if (enforce_overlayable) {
Result<Unit> success =
CheckOverlayable(*target_pkg, *overlay_info, fulfilled_policies, target_resid);
if (!success) {
LOG(WARNING) << "overlay \"" << overlay_apk_path
<< "\" is not allowed to overlay resource \"" << full_name
<< "\": " << success.GetErrorMessage();
continue;
}
}
//将目标应用和Overlay应用中都存在的资源保存下来
matching_resources.Add(target_resid, overlay_resid);
}
if (matching_resources.Map().empty()) {
return Error("overlay \"%s\" does not successfully overlay any resource",
overlay_apk_path.c_str());
}
//创建Idmap文件,走到这里基本上就会创建成功了
.....
idmap->data_.push_back(std::move(data));
return {std::move(idmap)};
}
上述方法就是Idmap创建的核心代码了,省略了一堆和编译打包相关的错误,这种错误一般不太会遇到,留下来的代码也比较简单,目的就一个,找出目标应用和Overlay应用中都存在的资源文件(资源名称相同的),这一对资源会组成std::pair
,以资源类型为Key存入一个Map中,在后面的创建Idmap时直接遍历,为每一对std::pair
创建映射。
但是在创建std::pair
之前需要经过两种情况的判断,这两种情况就是我们开发者需要关心的了,看到留下来的代码的注释了吗,这两种情况就是目标应用与Overlay应用的签名比对和目标应用与Overlay应用的同名资源查找,所以我们在使用RRO时如果发现Overlay包的状态为STATE_NO_IDMAP
,多半都是这两种情况二选一,请多注意。
好了继续回到OverlayManagerServiceImpl.updateState
,Idmap的创建我们已经看了,接着来看看Overlay包的状态计算:
OverlayManagerServiceImpl.calculateNewState
private @OverlayInfo.State int calculateNewState(@Nullable final PackageInfo targetPackage,
@Nullable final PackageInfo overlayPackage, final int userId, final int flags)
throws OverlayManagerSettings.BadKeyException {
//目标应用正在升级
if ((flags & FLAG_TARGET_IS_BEING_REPLACED) != 0) {
return STATE_TARGET_IS_BEING_REPLACED;
}
//Overlay应用正在升级
if ((flags & FLAG_OVERLAY_IS_BEING_REPLACED) != 0) {
return STATE_OVERLAY_IS_BEING_REPLACED;
}
// Overlay为空
if (DEBUG && overlayPackage == null) {
throw new IllegalArgumentException("null overlay package not compatible with no flags");
}
//目标为空
if (targetPackage == null) {
return STATE_MISSING_TARGET;
}
//没有为目标应用生成Idmap文件
if (!mIdmapManager.specifiedIdmapExists(targetPackage.applicationInfo.getBaseCodePath() + overlayPackage.applicationInfo.getBaseCodePath(), userId)) {
return STATE_NO_IDMAP;
}
//Overlay包是静态的
if (overlayPackage.isStaticOverlayPackage()) {
return STATE_ENABLED_STATIC;
}
//如果上面判断都通过了,则直接返回Overlay包是否Enable
final boolean enabled = mSettings.getEnabled(overlayPackage.packageName, userId);
return enabled ? STATE_ENABLED : STATE_DISABLED;
}
Overlay包的状态值全部定义在OverlayInfo
中,计算过程相对简单,无需多说。
如果Overlay包最终经过updateState
之后得到modified
返回值为true,则表明需要告知目标应用Overlay包状态发生改变,即会调用onOverlaysChanged
,这个方法要做的事情是非常多的,主要会让Overlay包生效完成目标应用的换肤,我们下一篇文章再分析。