前言
Android的RRO(Runtime Resource Overlay)机制允许开发者在运行时替换或重写系统资源,例如布局、图标、字符串等。这个机制的目标是为了支持设备定制和主题化,特别是在不修改系统源代码的情况下。RRO通过在系统的资源上叠加一个额外的资源层,来实现个性化和品牌定制,而不需要修改原有的资源文件。通常,RRO被用于OEM厂商在其设备上定制UI和功能,或者在Android版本升级时保持兼容性。
RRO的工作原理是,通过在运行时将资源包(如APK文件)作为叠加层加载到系统中。这样,当应用请求某个资源时,Android首先检查叠加层中的资源,如果存在,则使用叠加层的版本,而不是默认的系统资源。RRO机制的优点是它减少了系统定制的复杂性,同时避免了对基础系统文件的修改。
这种机制最早是在Android 6.0(Marshmallow)中引入的,并且随着Android版本的升级得到了优化和增强。
一、RRO 换肤实践
1.1 创建目标应用
首先新建一个需要被换肤的应用工程RROApplication,该工程中只有一个简单的MainActivity,加载了一个activity_main.xml的布局文件,有三个TextView,引用了三个颜色资源,我们的目标是利用RRO机制替换这三个TextView的颜色。这个工程所产生的应用我们将其称为【目标应用】。
1.2 创建资源包应用
1.2.1 新建资源包应用工程
再次新建一个独立的app工程,这个应用存在的意义就是放置【目标应用】的资源文件,它虽然是一个应用工程,但是不包含任何逻辑和业务,就只是用于存放资源文件。这个工程所产生的应用我们将其称为【资源包】。
1.2.2 配置 AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.rro.overlay">
<overlay
android:targetName="ThemeResources"
android:priority="1"
android:targetPackage="com.example.rro" />
<application android:hasCode="false"/>
</manifest>
如果某个APK 的AndroidManifest.xml中包含 标记作为 标记的子项,该APK将被视为『资源包』 。
-【必需设定】android:targetPackage 用于指明 RRO 想要替换的『目标应用』。
-【必需设定】android:hasCode必须设定为 false。由于无法替换代码,因此 RRO 无法使用 DEX 文件。
-【非必需设定】android:targetName 用于指明 RRO 『目标应用』的可替换资源子集的名称。如果『目标应用』没有定义可替换资源集,此属性就不需要设定。
1.3 可替换资源集标签overlayable
1.3.1 目标应用使用overlayable标签标记允许被替换的资源
在 Android 10 或更高版本中,『目标应用』可以使用 标签公开一组允许 RRO 替换的资源,未被公开的资源,则不允许 RRO 替换。
<resources>
<overlayable name="ThemeResources">
<policy type="public">
<item type="color" name="purple_200" />
<item type="color" name="purple_500" />
<item type="string" name="main_content" />
</policy>
</overlayable>
</resources>
一个 APK 可以定义多个overlayable 标签,但每个标签必须在该软件包中具有唯一的名称
1.3.2 资源包指定目标应用中允许被替换的标签
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.rro.overlay">
<overlay
android:targetName="ThemeResources"
android:priority="1"
android:targetPackage="com.example.rro" />
<application android:hasCode="false"/>
</manifest>
当目标应用定义 标签的时候,资源包的需满足以下条件:
- 必须指定 targetName
- 只能替换 标记中列出的资源。
- 只能定位到一个 名称。
1.3.3 限制策略
使用标记可以在目标应用中对可替换资源施加限制。type属性指定叠加层必须满足哪些政策的要求才能替换包含的资源。支持以下类型:
- public:任何叠加层均可替换相应资源。
- system:系统分区上的任何叠加层均可替换相应资源。
- vendor:vendor 分区上的任何叠加层均可替换相应资源。
- product:product 分区上的任何叠加层均可替换相应资源。
- signature:使用与目标 APK 相同的签名进行签名的任何叠加层均可替换相应资源。
<overlayable name="ThemeResources">
<policy type="vendor" >
<item type="string" name="main_content" />
</policy>
<policy type="product|signature" >
<item type="color" name="purple_200" />
</policy>
</overlayable>
如需指定多个政策,请使用竖线 (|) 作为分隔符。 如果指定了多个政策,叠加层只需满足一个政策的要求即可替换 标记中列出的资源。
1.4 定义资源映射
1.4.1 Android 10或以下版本
在 Android 10 或更低版本中,系统是根据资源的名称进行资源替换,所以我们只需要在资源包中将需要替换的资源定义好即可。
1.4.2 Android 11或以上版本
在 Android 11 或更高版本中,Google推荐在『资源包』的res/xml 目录中创建一个文件overlays.xml,枚举出应覆盖的『目标应用』的资源值及其替换值。
注意,target标签中的color并没有带上@标记,他实际上仅仅是一个字符串而不是引用
然后在资源包的AndroidManifest.xml 中将android:resourcesMap 属性的值设置为资源映射文件。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.target.overlay.eleven">
<overlay
android:targetName="ThemeResources"
android:targetPackage="com.android.target"
android:resourcesMap="@xml/overlays"/>
<application
android:hasCode="false"/>
</manifest>
二、RRO换肤效果展示
2.1 构建安装包
在AndroidStudio中分别编译构建目标应用安装包和资源包应用安装包
2.2 安装目标应用
在我们安装完目标应用RROApplication-debug.apk之后,打开该应用,默认的显示效果如下所示。
2.3 安装资源包应用
在我们安装完资源包应用RROResource-debug.apk之后,重新打开目标应用,会发现目标应用并不会发生什么变化,这是为什么呢?其实这是因为资源包应用的overlay功能开关默认没有被开启所导致的。
通过执行adb shelldumpsys overlay com.example.rro.overlay这条指令,可以得到以下关键信息。
com.example.rro.overlay:0 {
mPackageName...........: com.example.rro.overlay
mOverlayName...........: null
mUserId................: 0
mTargetPackageName.....: com.example.rro
mTargetOverlayableName.: ThemeResources
mBaseCodePath..........: /data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk
mState.................: STATE_DISABLED
mIsEnabled.............: false//注释1
mIsMutable.............: true
mPriority..............: 2147483647
mCategory..............: null
mIsFabricated..........: false
}
IDMAP OF com.example.rro.overlay
Paths:
target path : /data/app/~~B-NqptfXHZZHKfqYzQu-tg==/com.example.rro-8BHPMbRD_6ZPxdM6ZkYu3Q==/base.apk
overlay path : /data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk
Debug info:
W overlay '/data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk' is not allowed to overlay resource 'color/purple_700' in target: target resource has no overlayable declaration
W overlay '/data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk' is not allowed to overlay resource 'color/teal_200' in target: target resource has no overlayable declaration
W overlay '/data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk' is not allowed to overlay resource 'color/teal_700' in target: target resource has no overlayable declaration
W overlay '/data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk' is not allowed to overlay resource 'color/black' in target: target resource has no overlayable declaration
W overlay '/data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk' is not allowed to overlay resource 'color/white' in target: target resource has no overlayable declaration
Mapping://注释2
0x7f05004a -> color 0xff000000 (color/purple_200)
0x7f05004b -> color 0xff000000 (color/purple_500)
0x7f0d001d -> string "RRO替换资源" (string/main_content)
在注释1处可以发现该资源包的overlay功能并没有被开启,因此目标应用对应的UI样式才没有发生变化。
在注释2处可以发现该资源包所要替换的具体资源条目信息。
2.4 为资源包应用开启overlay功能开关
执行cmd overlay enable com.example.rro.overlay可以为资源包开启overlay功能开关
然后再执行adb shelldumpsys overlay com.example.rro.overlay这条指令,可以得到以下关键信息。
dumpsys overlay com.example.rro.overlay
com.example.rro.overlay:0 {
mPackageName...........: com.example.rro.overlay
mOverlayName...........: null
mUserId................: 0
mTargetPackageName.....: com.example.rro
mTargetOverlayableName.: ThemeResources
mBaseCodePath..........: /data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk
mState.................: STATE_ENABLED
mIsEnabled.............: true//注释1
mIsMutable.............: true
mPriority..............: 2147483647
mCategory..............: null
mIsFabricated..........: false
}
IDMAP OF com.example.rro.overlay
Paths:
target path : /data/app/~~B-NqptfXHZZHKfqYzQu-tg==/com.example.rro-8BHPMbRD_6ZPxdM6ZkYu3Q==/base.apk
overlay path : /data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk
Debug info:
W overlay '/data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk' is not allowed to overlay resource 'color/purple_700' in target: target resource has no overlayable declaration
W overlay '/data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk' is not allowed to overlay resource 'color/teal_200' in target: target resource has no overlayable declaration
W overlay '/data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk' is not allowed to overlay resource 'color/teal_700' in target: target resource has no overlayable declaration
W overlay '/data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk' is not allowed to overlay resource 'color/black' in target: target resource has no overlayable declaration
W overlay '/data/app/~~dxlUScJmLwQ1fBFif8-PNw==/com.example.rro.overlay-1r-PLB3eA9jUEf00J1vPYw==/base.apk' is not allowed to overlay resource 'color/white' in target: target resource has no overlayable declaration
Mapping:
0x7f05004a -> color 0xff000000 (color/purple_200)
0x7f05004b -> color 0xff000000 (color/purple_500)
0x7f0d001d -> string "RRO替换资源" (string/main_content)
可以发现注释1处,该资源包的overlay功能开关已经被开启,此事目标应用的显示效果也会发生变化。
三、启用/停用RRO功能
3.1 使用adb指令
adb shell cmd overlay enable [com.example.rro.overlay] // 开启
adb shell cmd overlay disable [com.example.rro.overlay] // 关闭
使用以上指令可以为特定包名的资源包开启或者关闭overlay功能。
3.2 使用系统api
OverlayManager overlayManager = context.getSystemService(OverlayManager.class);
OverlayInfo overlayInfo = manager.getOverlayInfo("com.example.rro.overlay", UserHandle.CURRENT_OR_SELF);
if(overlayInfo != null){
//true为开启,false为关闭
overlayManager.setEnabled("com.example.rro.overlay", true, UserHandle.CURRENT_OR_SELF);
};
可以在系统源码中直接使用OverlayManager提供的API可以启用、停用RRO,但是OverlayManager在公开的Android SDK中并没有提供,如果是使用Gradle构建工程,需要额外使用AOSP源码编译一个framework.jar并引入才可以使用,同时应用签名也需要使用Android系统签名,让应用成为系统级应用。