前语

手机运用的动态换肤功用现在现已运用的比较广泛了,也有许多文章分析了其间的原理,运用办法也迥然不同,基本都需求在运用内集成一个三方的结构,或独立开发一个换肤结构。此类换肤办法需求长期保护一套换肤结构,对原始运用存在一定的侵入性,开发的复杂度、作业量都会比较大。车载运用出于稳定性的考虑,关于引进第三方结构会比较克制,所以现在车载体系运用罕见有直接选用手机运用换肤计划的(当然也不是没有,博主也做过)。

那么车载运用要怎么在下降复杂度、作业量的前提下实现运用换肤呢?

其实也不难,因为 Android 体系自身现已供给了一套动态资源替换机制(Runtime Resource Overlay),灵敏运用动态资源替换就能够实现运用换肤的功用,那么本篇就来介绍一下这个 Runtime Resource Overlay 是怎么运用的。

Runtime Resource Overlay 简介

Runtime Resource Overlay(RRO)是一种在运转时更改方针运用所运用资源值的机制。例如,咱们能够将装置在体系镜像上的运用程序的资源值,动态修正为另一个资源包中界说的资源值,抵达修正运用程序更改其界面或行为的目的。

装置在不同分区上的 RRO 能够在运转时更改运用程序资源的值,而不需求在构建时运用对资源值进行硬编码。不过RRO只能够替换res目录下的资源,assert目录下的资源不属于资源结构无法动态替换。RRO机制现在首要运用场景是厂商定制体系级的主题切换功用。

RRO作业原理简略来说,便是将资源包中界说的资源映射到运用层界说的资源上。当运用尝试解析运用资源值时,体系转而会回来资源包中的资源值。

【车载 Android】应用换肤方案(一) - Runtime Resource Overlay

RRO 换肤实践

Demo 请访问github仓库:github.com/linxu-link/…

首要准备一个需求被换肤的运用,这里咱们随便新建一个APP为例,下面咱们将新建的Target App工程称为『方针运用』,方针是利用RRO机制替换方针运用中色彩值。

【车载 Android】应用换肤方案(一) - Runtime Resource Overlay

第 1 步:新建一个APP工程,装备 AndroidManifest.xml

首要咱们要新建一个独立的 app 工程,这个运用存在的意义便是放置『方针运用』的资源文件,下面咱们将其称为『资源包』。它虽然是一个运用工程,可是不包括逻辑代码,只能用于寄存资源文件。

留意到这一步,咱们实践上新建了两个app工程,一个是方针运用,一个是资源包。

  • 【必需设定】android:targetPackage 用于指明 RRO 想要替换的『方针运用』。
  • 【必需设定】android:hasCode有必要设定为 false。因为无法替换代码,因而 RRO 无法运用 DEX 文件。
  • 【非必需设定】android:targetName 用于指明 RRO 『方针运用』的可替换资源子集的称号。假如『方针运用』没有界说可替换资源集,此特点就不需求设定。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.target.overlay">
    <overlay android:targetPackage="com.android.target" />
    <application android:hasCode="false"/>
</manifest>

假如某个APK 的AndroidManifest.xml中包括 <overlay> 符号作为 <manifest> 符号的子项,该APK将被视为『资源包』 。

第 2 步:界说资源映射

Android 10或以下版别

在 Android 10 或更低版别中,体系是依据资源的称号进行资源替换,所以咱们只需求在资源包中将需求替换的资源界说好即可。

【车载 Android】应用换肤方案(一) - Runtime Resource Overlay

Android 11或以上版别

在 Android 11 或更高版别中,Google引荐在『资源包』的res/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.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:resourcesMap="@xml/overlays"/>
</manifest>

第 3 步:构建资源包

假如方针运用未做约束,构建的资源包即使与方针运用的签名不一致,也能够生效。怎么约束运用统一签名才能够生效的办法,请参阅 第5节-约束可替换资源

构建出的资源包,咱们在调试时,能够运用Android Studio直接装置,如下所示。

【车载 Android】应用换肤方案(一) - Runtime Resource Overlay

因为『资源包』(resource_ten)没有activity所以也无法发动,咱们只需看到data/app目录下又多了一个途径,即表明装置成功。『方针运用』(target)也是相同的装置办法。

正式发布时,『资源包』能够发布到vendor/overlay目录下,『方针运用』则能够依据需求能够灵敏放置。之后重启Android体系即可。

第 4 步:启用/停用 RRO

运用OverlayManager供给的API能够启用、停用RRO。OverlayManager在揭露的Android SDK中并没有供给,假如是运用Gradle构建工程,需求额定运用AOSP源码编译一个framework.jar并引进才能够运用,一起运用签名也需求运用Android体系签名,让运用成为体系级运用。

framework.jar和体系签名文件,能够访问GitHub仓库获取github.com/linxu-link/…

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 会重新发动,如下所示:

【车载 Android】应用换肤方案(一) - Runtime Resource Overlay

也能够运用下面的adb指令来开启、封闭RRO。

adb shell cmd overlay enable [com.android.target.overlay.ten] // 开启
adb shell cmd overlay disable [com.android.target.overlay.ten] // 封闭

【车载 Android】应用换肤方案(一) - Runtime Resource Overlay

假如方针运用或RRO资源为正在查询的资源界说了多个装备,RRO将回来与设备装备最匹配的装备值。 如需承认最匹配的装备,请将叠加层资源装备调集并到方针资源装备集中,然后遵循常规资源流程履行操作,如需了解详情,请参阅 Android 怎么寻觅最匹配的资源。

以上便是 RRO 简略地运用办法,不过实践开发时咱们一定会遇到各式各样的问题,接下来咱们先来讲讲调试RRO时排查问题的一般过程。

RRO 故障排查

Android体系供给了丰富的指令用于排查RRO的各种过错与反常,假如你的 RRO 没有依照预期那样正常运转,能够依照下面列出的过程进行排查。

留意事项

1)在Android 12上『方针运用』的AndroidManifest.xml中不能有android:sharedUserId=""标签,不然启用RRO时『方针运用』的 activity 将不会重启,需求自行重启『方针运用』资源替换才能生效。

sharedUserId 标签实践上在Android 10今后现已被符号为过时,详细原因能够参阅官方文档:developer.android.com/guide/topic…

2)在 Android 10或更低的版别上运用OverlayManager的运用,需求声明android.permission.CHANGE_OVERLAY_PACKAGES权限,Android 11之后不用声明此权限。

第 1 步:列出 RRO

adb shell cmd overlay list --user current

【车载 Android】应用换肤方案(一) - Runtime Resource Overlay

在这一过程中,可查看当时体系一切 RRO 『资源包』,假如上述指令没有展示出你的『资源包』,则阐明该『资源包』未被正确装置。

包名前的符号表明 RRO 的状况,如下所示:

指标 仿制权状况
[ ] 已装置并处于待激活状况
[X] 装置并处于激活状况
已装置但包括过错

第 2 步:启用和停用 RRO

运用adb 指令启用(或禁用)RRO:

adb shell cmd overlay [enable/disable] --user current [资源包 package name]

能够将 current 替换为你需求的用户。例如,能够将其替换为 010,也能够保持原值不变。

有的时候咱们运用IOverlayManagerOverlayManager开启RRO或许并不会生效,这时就能够运用adb指令启用RRO,假如adb指令能够生效,那么基本能够以为是咱们关于OverlayManager的用法呈现了过错,有关OverlayManager的用法请参阅第五节 – RRO进阶

第 3 步:承认『资源包』状况

咱们需求保证『资源包』被overlayManager服务正确辨认,能够在shell中履行 dumpsys overlay,查看对应资源包的相关信息。

adb shell cmd overlay dump [资源包 package name]

【车载 Android】应用换肤方案(一) - Runtime Resource Overlay

  1. 查看装置了 RRO 的用户,即mUserId

  2. 查看mState的值:

    1. STATE_ENABLEDSTATE_ENABLED_IMMUTABLE :RRO 已启用并运用于方针运用。

    2. STATE_MISSING_TARGET :方针运用未装置。

    3. STATE_NO_IDMAP :AndroidManifest.xml 、 overlays.xml 或 overlayable.xml文件的设置办法有问题。运用adb logcat运转日志并查找关键字idmap以辨认过错,第4、5步会解释。

    4. STATE_UNKNOWN :OverlayManagerService 呈现反常

第 4 步:查看『资源包』的AndroidManifest.xml

  1. 查看targetNametargetPackageandroid:targetName应该与方针运用程序中界说的可掩盖组具有相同的值。只要在定位掩盖层时才需求这样做。android:targetPackage一直是必需的,应包括方针运用程序的包称号。

  2. 查看 RRO 是否是静态的。默许状况下,静态RRO在体系发动时被启用,而动态RRO,则默许不启用。

  3. 查看静态 RRO 的优先级(动态 RRO 优先级一直设置为Integer.MAX_VALUE ,它们的运用次序取决于它们的启用时间)。

  4. 多个 RRO 或许适用于同一方针。最终发动的运用具有更高的优先级。在 0 到 10 的范围内,10 最高,0 最低。

第 5 步:查看 overlays.xml

此项仅适用于 Android 11(及更高版别)

  1. 查看overlays.xml。承认计划替换的一切资源都在此文件中有界说。
<overlay>
    <item target="string/app_name" value="@string/overlaid_app_name" />
</overlay>
  1. 有必要保证:
  • 方针运用程序中存在称号为app_namestring资源。

  • 称号为overlaid_app_namestring资源存在于资源包中。

  1. 假如方针运用有一个overlayable.xml文件,请保证该文件中包括app_name 。保证在AndroidManifest.xml文件中运用正确的targetName

假如要替换layout文件,请保证一切 ID 和运用命名空间特点都包括 overlays.xmloverlayable.xml中。例如:

<overlay>
<item target="layout/car_ui_base_layout_toolbar" value="@layout/car_ui_base_layout_toolbar" />
<item target="id/car_ui_toolbar_background" value="@id/car_ui_toolbar_background" />
<item target="attr/layout_constraintTop_toBottomOf" value="@attr/layout_constraintTop_toBottomOf" />
</overlay>

overlayable标签的运用办法,请持续阅览第5节-约束可替换资源

第 6 步:转储 idmap

进入到这一步之前,调试RRO时的一切问题都应该现已处理了,接下来转储 RRO 的idmap以了解资源是怎么被解析的,以及它解析出的资源值为何与预期不同的原因。

1)在设备上找到idmap的途径。

adb shell
su
ls data/resource-cache

【车载 Android】应用换肤方案(一) - Runtime Resource Overlay

2)要转储该文件的内容。

idmap2 dump --idmap-path [path to your RRO idmap file]// 根目录履行要带上绝对途径 data/resource-cache

输出类似于以下内容,会显现 RRO 中的id映射到方针中的id,以及替换资源的称号。

【车载 Android】应用换肤方案(一) - Runtime Resource Overlay

RRO 进阶

常用 API

在第三节中,咱们运用OverlayManager的 API 来启用、停用 RRO,可是实践上OverlayManager供给的 API 功用太少,实践开发中咱们引荐运用功用更加丰富的IOverlayManager

IOverlayManager manger = IOverlayManager.Stub.asInterface(ServiceManager.getService(Context.OVERLAY_SERVICE));

IOverlayManager 供给了以下功用

  • getAllOverlays
Map<String, List<OverlayInfo>> allOverlays = overlayManager.getAllOverlays(userId);

回来有关指定用户的一切已装置资源包的OverlayInfo。假如没有为该用户装置资源包,则回来空映射(即从不回来null)。

回来的映射是方针运用包名-资源包列表的映射。给定方针包的每个列表都按优先级次序排序,列表末尾具有最高优先级的掩盖层。

能够经过 UserHandle.myUserId() 的办法获取当时运用的userId。

  • getDefaultOverlayPackages
String[] overlayPackages = overlayManager.getDefaultOverlayPackages();

回来默许资源包的列表,假如没有,则回来空数组。

  • getOverlayInfo
OverlayInfo overlayInfo = overlayManager.getOverlayInfo(packageName, userId);

回来有关具有指定用户的给定包称号的OverlayInfo

  • getOverlayInfosForTarget
List<OverlayInfo> overlayInfosForTarget = overlayManager.getOverlayInfosForTarget(targetPackageName, userId);

回来指定用户的给定方针包的一切OverlayInfo。回来的列表依据列表末尾具有最高优先级的掩盖优先级排序。

  • setEnabled
boolean enabled = overlayManager.setEnabled(packageName, enbale, userId);

启用或禁用RRO。该办法一直能够禁用RRO,但因为技术和安全原因,或许不总是能够启用RRO,例如:没有装置相关的方针运用。

一般setEnable办法咱们总是用于禁用RRO,而发动RRO会运用setEnabledExclusive或setEnabledExclusiveInCategory

  • setEnabledExclusive
boolean enable = overlayManager.setEnabledExclusive(packageName, enable, userId);

恳求启用RRO,并禁用具有相同方针运用的任何其他资源包。

  • setEnabledExclusiveInCategory
boolean enabled = overlayManager.setEnabledExclusiveInCategory(packageName, userId);

恳求启用RRO,并禁用具有相同方针包和类别的任何其他资源包。


以下内容出自官方文档:source.android.com/docs/core/r…

约束可替换资源

在 Android 10 或更高版别中,『方针运用』能够运用<overlayable> 标签揭露一组允许 RRO 替换的资源,未被揭露的资源,则不允许 RRO 替换。

在以下的示例 res/values/overlayable.xml 文件中,string/foointeger/bar 是用于为设备的外观设置主题布景的资源,为了替换这些资源,『资源包』有必要依据称号明承认位到可替换资源的调集。

<resources>
    <overlayable name="ThemeResources">
        <policy type="public">
            <item type="color" name="purple_200" />
            <item type="color" name="purple_500" />
        </policy>
    </overlayable>
</resources>

一个 APK 能够界说多个 <overlayable> 符号,但每个符号有必要在该软件包中具有唯一的称号。例如:

  • 两个不同的资源包能够一起界说 <overlayable name="purple_200">
  • 一个 APK 不能具有两个 <overlayable name="purple_200"> 块。

然后需求在资源包的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> 符号时,定位到该运用的叠加层需满意以下条件:

  • 有必要指定 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> 符号中列出的资源。

装备叠加层

Android 支撑运用不同的机制以装备叠加层的可变性、默许状况和优先级,详细取决于不同的 Android 版别。

  • 搭载 Android 11 或更高版别的设备能够运用 OverlayConfig 文件 (config.xml) 代替清单特点。关于叠加层,引荐运用叠加层文件。
  • 一切设备都能够运用清单特点(android:isStaticandroid:priority)装备静态 RRO。

留意:只要定位到 android 软件包的不可变叠加层才会影响经过 Resources.getSystem() 检索的资源值。

运用 OverlayConfig

在 Android 11 或更高版别中,能够运用OverlayConfig装备叠加层的可变性、默许状况和优先级。如需装备叠加层,请创建或修正坐落 partition/overlay/config/config.xml 的文件,其间partition是需装备的叠加层的分区。

为了进行装备,叠加层有必要坐落装备叠加层的分区的overlay/目录中。以下代码显现了一个示例 product/overlay/config/config.xml

<config>
    <merge path="OEM-common-rros-config.xml" />
    <overlay package="com.oem.overlay.device" mutable="false" enabled="true" />
    <overlay package="com.oem.green.theme" enabled="true" />
</config>"

<overlay> 符号需求 package 特点,该特点指示正在装备哪一个叠加层软件包。可选 enabled 特点操控是否默许启用叠加层(默许为 false)。可选 mutable 特点操控叠加层是否可变,并且在运转时是否可经过编程办法更改其启用状况(默许为 true)。装备文件中未列出的叠加层是可变的,默许处于停用状况。

叠加层优先级

运用多个叠加层替换同一资源时,有必要按恰当叠加层次序进行替换。对叠加层而言,装备越低,优先级越高。叠加层在不同分区的优先级次序(从最低优先级到最高优先级)如下:

  • system
  • vendor
  • oem
  • odm
  • product
  • system_ext

留意:当 OverlayManagerService 启用未装备的叠加层时,体系不会界说叠加层的次序。

运用 <merge> 符号可将坐落指定位置的其他装备文件合并到装备文件中。该符号的 path 特点表明要合并的文件相关于目录(包括叠加层装备文件)的途径。

装备静态 RRO(SRO)

静态RRO是在体系发动后自动生效,不需求再运用OverlayManager启用。

Android 10 或以下版别

运用以下清单特点装备『资源包』的不可变性和优先级。

  • android:isStatic。当此布尔值特点的值设置为 true 时,叠加层会默许处于启用状况并且不可变,这会导致叠加层无法停用。
  • android:priority。当多个静态叠加层以相同的资源值为替换方针时,此数字特点的值(仅影响静态叠加层)将装备叠加层的优先级。数值越大表明优先级越高。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:isStatic="true"
                   android:priority="5"/>
</manifest>

Android 11 或以上版别

在 Android 11 或更高版别中,假如装备文件坐落 partition/overlay/config/config.xml 中,叠加层会运用该文件进行装备,并且 android:isStaticandroid:priority 不会影响坐落分区中的叠加层。在任何分区中界说叠加层装备文件都会强制履行叠加层分区优先级。

此外,Android 11 或更高版别移除了运用静态叠加层影响软件包装置期间读取的资源值的功用。如需了解运用静态叠加层以更改布尔值(其用于装备组件启用状况)的常见用例,请运用 <component-override> SystemConfig 符号(Android 11 的新变化)。

总结

以上便是有关Runtime Resource Overlay的一切介绍了,能够看出RRO本质上是一套动态资源替换机制,运用换肤仅仅它功用的一小部分而已。总结一下,RRO有以下两个首要的长处:

  • 功用强大,能够替换多种资源类型;

  • 对方针运用无任何侵入,不需求集成换肤结构,省去了保护结构的成本;

可是RRO也有一个缺点,那便是更换资源时,方针运用的activity会发生重启,当下Android APP开发遍及不喜欢考虑处理activity重启的状况,这时就有或许发生bug需求处理。

总得来说,运用RRO进行换肤是一套利大于弊的计划,博主之前的公司现已在实践的项目中落地并量产。可是RRO并不是万能的,实践开发中也有或许呈现一些意想不到的状况,这时候就需求咱们再引进其它的换肤计划,今后再持续介绍运用的其它换肤计划与原理。

好了,感谢你的阅览,希望对你有所帮助。

参阅资料:

source.android.com/docs/device…

source.android.com/docs/core/r…

source.android.com/docs/device…

www.jianshu.com/p/398f1beb1…