一、Android Overlay机制
developer.aliyun.com/article/126…
Android Overlay是一种资源替换机制,它能在不重新打包apk的情况下,实现资源文件的替换(res目录非assert目录),Overlay又分为静态Overlay(Static Resource Overlay)与运行时Overlay(Runtime Resource Overlay)。
RRO工作原理简单来说,就是将资源包中定义的资源映射到应用层定义的资源上。当应用尝试解析应用资源值时,系统转而会返回资源包中的资源值。
配置步骤
-
创建一个只包含res的项目(资源包)
-
配置AndroidManifest.xml
package
:资源包包名,可以任意命名,一般皮肤主题或者overlay为后缀
android:hasCode="false"
:必须设定为 false。由于无法替换代码,因此 RRO 无法使用 DEX 文件
android:isStatic="false"
:必须设定为 false才能在多套资源之间实现动态切换
android:priority
:设置资源包的优先级,值越大,优先级越高,用于存在多个overlay.apk情况下的判断
android:resourcesMap
:Android 11及以上新增配置,用于指定资源映射关系
android:targetPackage
:目标apk包名
-
定义资源映射
在 Android 10 或更低版本中,系统是根据资源的名称进行资源替换,所以我们只需要在资源包中将需要替换的资源定义好即可,注意资源名需要一致。
在 Android 11 或更高版本中,Google推荐在资源包的res/xml
目录中创建一个文件,枚举出应覆盖的目标应用的资源值及其替换值。
注意,target标签中的color并没有带上@标记,它实际上仅仅是一个字符串而不是引用,系统通过该字符串在目标应用中查找对应资源类型和资源名。
然后在资源包的AndroidManifest.xml 中将android:resourcesMap
属性的值设置为资源映射文件。
-
限制可替换资源
在目标应用中创建res/values/overlayable.xml
文件,该文件中指定了能被覆盖的资源
- 一个应用可以有多个
overlayable
标记,但不能重名,比如上面的"OverlayableResource_1"和"OverlayableResource_2";多个overlayable
可以定义相同的可覆盖资源; - 当应用定义
<overlayable>
标记时,资源包只能覆盖列出的资源; - 资源包必须在AndroidManifest中指定
android:targetName
,指定目标应用中的overlayable
,只能指定一个overlayable
-
限制策略
使用 <policy>
标记可对可叠加资源施加限制。type
属性指定叠加层必须满足哪些策略的要求才能替换包含的资源。受支持的类型如下:
public
:任何叠加层均可替换相应资源。system
:系统分区上的任何叠加层均可替换相应资源。vendor
:vendor 分区上的任何叠加层均可替换相应资源。product
:product 分区上的任何叠加层均可替换相应资源。signature
:使用与目标 APK 相同的签名进行签名的任何叠加层均可替换相应资源。
如需指定多个策略,请使用竖线 |
作为分隔符。 如果指定了多个策略,资源包只需满足一个政策的要求即可替换 <policy>
标记中列出的资源。
-
构建资源包
使用Android Studio直接安装或者只用adb install [RRO apk]
。正式发布时,资源包可以发布到vendor/overlay
目录下,目标应用则可以根据需要可以灵活放置。之后重启Android系统即可。
调试资源包
使用代码动态切换
注意事项
- 使用
OverlayManager
API 启用和停用可变叠加层(使用Context#getSystemService(Context.OVERLAY_SERVICE)
检索 API 接口)。叠加层只能由其目标软件包或具有android.permission.CHANGE_OVERLAY_PACKAGES
权限的软件包启用。在您启用或停用叠加层后,配置更改事件会传播到目标软件包,并且目标 activity 会重新启动。 - 在Android 12上目标应用的AndroidManifest.xml中不能有
android:sharedUserId=""
标签,否则启用RRO时目标应用的 activity 将不会重启,需要自行重启目标应用资源替换才能生效。
sharedUserId 标签实际上在Android 10以后已经被标记为过时,具体原因可以参考官方文档:developer.android.com/guide/topic…
- 在 Android 10或更低的版本上使用
OverlayManager
的应用,需要声明android.permission.CHANGE_OVERLAY_PACKAGES
权限,Android 11之后不必声明此权限。 - RRO问题排查
二、第三方换肤方案
Android-skin-support 、 Android-Skin-Loader
Android中我们使用Resource获取res
目录下的资源,比如
换肤无非就是替换这些资源,以getString
为例,最终会走到getText
Resource
通过AssetManager
获取资源,AssetManager
提供访问原始资源文件的接口,比如res、assets目录,一般我们就是通过Resource
间接调用这些接口。也就是说,如果想实现换肤, 核心 就是换 AssetManager
。
经此替换,getColor()
、getString()
、getDrawable()
等接口拿到的就是皮肤包的资源。
三方换肤的两种思路
-
完整版:
自定义LayoutInflater.Factory2
,在onCreateView()
中将sdk中的view替换成自定义支持换肤的view,
将资源Resource对象注入到自定义view中,对原方法(比如setBackground()
、setTextColor()
)进行拦截
-
简约版:
不对view进行替换,仅对需要支持换肤的属性进行替换,
在创建view的时候解析view支持换肤的属性并缓存,在切换皮肤的时候通过view的对应方法(比如setBackground()
、setTextColor()
)实现动态切换资源。
三、Android里的View是怎么解析的?
以AppCompatActivity为例,它通过AppCompatDelegate
代理来处理setContentView()
和所有的生命周期事件,
重点看看LayoutInflater
是如何把布局xml中的节点解析成View的,
通过自定义 Factory2
拦截系统View的创建过程,替换成自定义支持换肤的View, Android-skin-support 就是这么做的。