Android RRO换肤
RRO全称是Runtime Resource Overlay,如名称所说它是可以在运行时动态完成换肤的一个机制。
使用方式
首先需要为target应用创建一个皮肤资源包(overlay apk),这个apk是没有任何代码的,AndroidManifest配置如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.resourceapp">
<application android:hasCode="false" />
<overlay
android:targetPackage="com.example.target"
android:resourcesMap="@xml/target_resource"
android:category="type"
android:priority = 1
/>
</manifest>
添加overlay标签:
- targetPackage:表示换肤目标应用的包名
- resourcesMap: 换肤资源Map
- category:表示该皮肤类型
- priority:表示overlay包的优先级,一个target应用可能存在多个overlay包,所以需要优先级来排序,值越小优先级越高
target_resource类容如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<overlay xmlns:android="http://schemas.android.com/apk/res/android">
<item target="color/black" value="#FFFFFFFF"/>
<item target="color/white" value="#FF000000"/>
<item target="color/colorPrimary" value="#FFFFFFFF"/>
<item target="color/line" value="#142B2E33"/>
<item target="drawable/bg_theme_default" value="@drawable/bg_theme_default"/>
<item target="string/common_sub_title" value="@string/common_sub_title" />
</overlay>
这里存放的就是皮肤资源与目标应用的map。
- target:表示目标应用里使用到的资源名称(名称一定要和目标应用相同,这样才会建立转储关系)
- value:皮肤资源
接着就是编译资源包-安装。
让皮肤资源包生效,两种方式:
1.可以通过adb调试。
(1)、adb shell dumpsys overlay可以查询设备中overlay包的详细信息
可以看到当前状态是false。
(2)、adb shell cmd overlay enable com.example.resourceapp使皮肤资源包生效。此时target app也会完成换肤。
2.通过OverlayManager。
java
val overlayManager = context.getSystemService(OverlayManager::class.java)
overlayManager?.setEnabled(packageName, true, UserHandle.getUserHandleForUid(0))
需要注意的是该api只对系统应用开放。
换肤大致原理
1.资源替换
举个例子当我们给Text设置文字颜色为<color name="black">#FF000000</color>
,对应R文件中的ID为0x7f060021,系统资源管理框架使用此ID查找资源时并不会立即去应用的资源包中找。
先会判断当前应用是否有overlay,如果有就会去读取当前应用和overlay应用生成的Idmap文件,这个文件记录了这两个应用之间同名资源的ID映射关系。(此文件是在overlay apk安装时由OverlayManagerService解析生成)
比如overlay应用中定义的black颜色资源ID为0x7f060010,系统会将0x7f060021替换成0x7f060010,然后拿着0x7f060010去overlay资源包找到对应的资源值,此时target应用已经完成了资源替换。
2.刷新应用
接下来就需要刷新应用页面了,OverlayManagerService会自动通知应用的activity重走生命周期达到刷新目的,不过这个重走生命周期是会保留窗口直至activity完成,所以换肤过程是无缝衔接的并不会有闪屏出现。
除了重走生命周期这种刷新方式,还可以选择修改OverlayManagerService,让它只回调应用的configurationChanged,让应用自己在configurationChanged方法中去重新set界面的资源,这样也可以完成刷新,不会使Activity重走生命周期。
实现系统整体换肤方案
所谓系统整体换肤即让系统各应用(SystemUI、Launcher、Settings等等)同时完成换肤即可。 Note:对于SystemUI这种没有Activity的应用就可以通过OverlayManagerService回调configurationChanged的方式达到刷新效果。
当overlay apk安装成功后OverlayManagerService会解析生成文件data/system/overlays.xml,这个文件存放了overlay apk包信息
xml
</overlays>
<item packageName="com.android.theme.color.orchid" userId="0" targetPackageName="android" baseCodePath="/product/overlay/AccentColorOrchid/AccentColorOrchidOverlay.apk" state="2" isEnabled="false" isStatic="false" priority="2147483647" category="android.theme.customization.accent_color" />
<item packageName="com.android.theme.color.purple" userId="0" targetPackageName="android" baseCodePath="/product/overlay/AccentColorPurple/AccentColorPurpleOverlay.apk" state="2" isEnabled="false" isStatic="false" priority="2147483647" category="android.theme.customization.accent_color" />
......
<item packageName="com.zlingsmart.resourceapp" userId="0" targetPackageName="com.example.settings" baseCodePath="/data/app/~~w0YvN3bMjOZbRQHJ7axruw==/com.zlingsmart.resourceapp-HQ5IxRQ4Cu9WQ7tBHlS6wA==/base.apk" state="3" isEnabled="true" isStatic="false" priority="2147483647" category="day" />
</overlays>
- packageName:overlay apk的包名
- targetPackageName:目标应用包名
- baseCodePath:overlay apk存储位置
- state:overlay apk当前状态
- isStatic:是否是静态overlay
- priority:overlay优先级
- category:overlay包类别
我们可以通过OverlayManager.getAllOverlays
方法获取所有的overlay包信息。 Note:该方法原生OverlayManagerService中已实现,但OverlayManager没有提供该方法,需要修改OverlayManager。
接下来的工作就简单了,比如我们要实现切换黑夜主题:
- 1.overlay应用Manifest中配置category为night,打包黑夜资源皮肤apk安装;
- 2.OverlayManager.getAllOverlays获取所有overlay皮肤包信息;
- 3.过滤category,OverlayManagerService enable所有对应的资源
这样就完成了整体换肤,同理可以做多套皮肤:黑夜、白天、科技、简约等等。