转载自 【车载 Android】应用换肤方案(一) - Runtime Resource Overlay
Android从M开始加入了动态资源overlay机制 runtime resource overlay(RRO),这个是sony贡献的,实现机制如下图,就是在框架中建立一套资源ID映射表,通过这个映射表动态切换不同的主题。
RRO 换肤实践
首先准备一个需要被换肤的应用,这里我们随便新建一个APP为例,下面我们将新建的Target App工程称为『目标应用』,目标是利用RRO机制替换目标应用中颜色值。
第 1 步:创建资源包,配置 AndroidManifest.xml
首先我们要新建一个独立的 app 工程,这个应用存在的意义就是放置『目标应用』的资源文件,下面我们将其称为『资源包』。它虽然是一个应用工程,但是不包含逻辑代码,只能用于存放资源文件。
注意到这一步,我们实际上新建了两个app工程,一个是目标应用,一个是资源包。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.target.overlay.ten">
<overlay
android:targetName="ThemeResources"
android:priority="20"
android:targetPackage="com.android.target" />
<application android:hasCode="false"/>
</manifest>
如果某个APK 的AndroidManifest.xml中包含 <overlay>
标记作为 <manifest>
标记的子项,该APK将被视为『资源包』 。
- 【必需设定】android:targetPackage 用于指明 RRO 想要替换的『目标应用』。
- 【必需设定】android:hasCode必须设定为 false。由于无法替换代码,因此 RRO 无法使用 DEX 文件。
- 【非必需设定】android:targetName 用于指明 RRO 『目标应用』的可替换资源子集的名称。如果『目标应用』没有定义可替换资源集,此属性就不需要设定。
可替换资源集
在 Android 10 或更高版本中,『目标应用』可以使用
<overlayable>
标签公开一组允许 RRO 替换的资源,未被公开的资源,则不允许 RRO 替换。
<resources>
<overlayable name="ThemeResources">
<policy type="public">
<item type="color" name="purple_200" />
<item type="color" name="purple_500" />
</policy>
</overlayable>
</resources>
一个 APK 可以定义多个 标记,但每个标记必须在该软件包中具有唯一的名称。例如:
- 两个不同的资源包可以同时定义 。
- 一个 APK 不能具有两个 块。
然后需要在资源包的AndroidManifest.xml中使用targetName指定ThemeResources。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.target.overlay.ten">
<overlay
android:targetName="ThemeResources"
android:targetPackage="com.android.target" />
<application android:hasCode="false"/>
</manifest>
当目标应用定义 <overlayable>
标记时,资源包的<overlay>
需满足以下条件:
-
必须指定 targetName。
-
只能替换
<overlayable>
标记中列出的资源。 -
只能定位到一个
<overlayable>
名称。
限制策略
使用<policy>
标记可以在目标应用中对可替换资源施加限制。type
属性指定叠加层必须满足哪些政策的要求才能替换包含的资源。支持以下类型:
- public:任何叠加层均可替换相应资源。
- system:系统分区上的任何叠加层均可替换相应资源。
- vendor:vendor 分区上的任何叠加层均可替换相应资源。
- product:product 分区上的任何叠加层均可替换相应资源。
- signature:使用与目标 APK 相同的签名进行签名的任何叠加层均可替换相应资源。
<overlayable name="ThemeResources">
<policy type="vendor" >
<item type="string" name="foo" />
</policy>
<policy type="product|signature" >
<item type="string" name="bar" />
<item type="string" name="baz" />
</policy>
</overlayable>
如需指定多个政策,请使用竖线 (|) 作为分隔符。 如果指定了多个政策,叠加层只需满足一个政策的要求即可替换 <policy>
标记中列出的资源。
第 2 步:定义资源映射
- Android 10或以下版本
在 Android 10 或更低版本中,系统是根据资源的名称进行资源替换,所以我们只需要在资源包中将需要替换的资源定义好即可。
- Android 11或以上版本
在 Android 11 或更高版本中,Google推荐在『资源包』的res/xml 目录中创建一个文件overlays.xml
,枚举出应覆盖的『目标应用』的资源值及其替换值。
<?xml version="1.0" encoding="utf-8"?>
<overlay xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 更换颜色 -->
<item target="color/purple_200" value="#000000"/>
<item target="color/purple_500" value="#000000"/>
<item target="color/purple_700" value="#000000"/>
<item target="color/teal_200" value="#000000"/>
<item target="color/teal_700" value="#000000"/>
<item target="color/black" value="#000000"/>
<item target="color/white" value="#000000"/>
</overlay>
注意,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>
第 3 步:构建资源包
构建出的资源包,我们在调试时,可以使用Android Studio直接安装,如下所示。
- 运行目标应用和资源包
- 在data/app 目录上右键,选择 “Synchronize” 选项
以上两步之后data/app目录下会多出两个路径,分别代表目标应用和资源包的apk。
正式发布时,『资源包』可以发布到vendor/overlay目录下,『目标应用』则可以根据需要可以灵活放置。之后重启Android系统即可。
第 4 步:启用/停用 RRO
使用OverlayManager提供的API可以启用、停用RRO。OverlayManager在公开的Android SDK中并没有提供,如果是使用Gradle构建工程,需要额外使用AOSP源码编译一个framework.jar并引入才可以使用,同时应用签名也需要使用Android系统签名,让应用成为系统级应用。
OverlayManager manager = MainActivity.this.getSystemService(OverlayManager.class);
OverlayInfo overlayInfo = manager.getOverlayInfo("com.android.target.overlay.ten", UserHandle.CURRENT_OR_SELF);
manager.setEnabled("com.android.target.overlay.ten", !overlayInfo.isEnabled(), UserHandle.CURRENT_OR_SELF);
在启用或停用RRO后,配置更改事件会传播到目标应用,并且目标应用的 activity 会重新启动。
如果要在手机端测试,可以使用adb命令来开启、关闭RRO
adb shell cmd overlay enable [com.android.target.overlay.ten] // 开启
adb shell cmd overlay disable [com.android.target.overlay.ten] // 关闭