运行时资源叠加层 (RRO) 是一个软件包,可在运行时更改目标软件包的资源值。例如,安装在系统映像上的应用可能会根据资源值更改其行为。安装在不同分区中的 RRO 可能会在运行时更改应用的资源值,而不是在构建时硬编码资源值。您可以启用或停用 RRO。您可以通过编程方式设置启用/停用状态,以切换 RRO 更改资源值的功能。
动态调试:
adb root
adb install <path-to-overlay.apk-file>
adb shell cmd overlay list --user current
com.sll.helloworld
[ ] com.ehome.helloworldoverlay
如果新安装的 RRO 旁边显示了 ---,则表示未找到目标 APK(请核对您的 AndroidManifest.xml 上的 targetPackage 声明)或 RRO 中定义的某些资源与目标中的资源都不匹配。
以下命令需要在root用户下执行
启用:adb shell cmd overlay enable --user current <rro-package-name>
停用:adb shell cmd overlay disable --user current <rro-package-name>
代码实现切换:
val om: OverlayManager = getSystemService(Context.OVERLAY_SERVICE) as OverlayManager
om.setEnabled("com.ehome.myapplication.overlay", true, UserHandle.CURRENT)
叠加资源的定义
叠加层的工作原理是将叠加层软件包中定义的资源映射到目标软件包中定义的资源。当应用尝试解析目标软件包中资源的值时,系统转而会返回目标资源映射到的叠加层资源的值。所以RRO可以在不改变源APK的前提下,进行资源替换。
实现步骤
创建Overlay工程:例:HelloWorldOverlay
修改AndroidManifest.xml文件
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ehome.helloworldoverlay">
<overlay
android:isStatic="false"
android:priority="1"
android:resourcesMap="@xml/overlays"
android:targetPackage="com.sll.helloworld" />
<application android:hasCode="false" />
</manifest>
android:targetPackage 属性的值用于指明 RRO 想要叠加的软件包的名称,此例为com.sll.helloworld
android:resourcesMap 指定的映射map
android:priority 优先级,如果只有一个叠加软件包,那么默认为1即可,当有多个叠加包时,优先使用优先级较高的叠加包
android:isStatic 设置该属性,才能RRO。但是如果想在多套主题之间实现动态切换,这个属性不能设置为true(设置为true之后应该是默认生效,但是个人测试是无效的)。
android:hasCode 必须被设置为false
编写资源映射Map
<overlay>
<!-- mipmap替换,target为目标APK的资源名称(此资源在目标APK中),value为你想要替换的资源(此资源在叠加包中) -->
<item target="mipmap/ic_bg1" value="@mipmap/ic_bg1" />
<!-- string字符串替换 -->
<item target="string/overlay_target" value="@string/overlay_dst" />
<!-- layout文件替换,注意替换layout文件时,也需要将其中的控件ID进行替换,否则会出现在目标APK中无法找到控件 -->
<item target="layout/activity_main" value="@layout/activity_main" />
<item target="id/btn_get_provider_value" value="@id/btn_get_provider_value" />
</overlay>
实现原理:
正常情况下,一个APK文件其实就是一个资源包,里面有源代码也有资源文件,而我们通过ID索引就可以拿到相对应的资源,这些资源映射关系都记录在APK文件的resources.arsc中。
所有的getString() getDrawable() getColor() 等等,最终都会走到AssetManaget#getResourceValue() 方法中
boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
Objects.requireNonNull(outValue, "outValue");
synchronized (this) {
ensureValidLocked();
// 最终所有方法都会从这里去获取相对于的资源,此为Native方法
final int cookie = nativeGetResourceValue(
mObject, resId, (short) densityDpi, outValue, resolveRefs);
if (cookie <= 0) {
return false;
}
// Convert the changing configurations flags populated by native code.
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
outValue.string = getPooledStringForCookie(cookie, outValue.data);
}
return true;
}
}
综上所述,所有的资源获取都是通过AssetManager来完成的
参考:
https://source.android.com/devices/architecture/rros?hl=zh-cn
https://source.android.com/devices/automotive/hmi/car_ui/appendix?hl=zh-cn