Android源码学习之Overlay
在 Android Framework 开发中,Overlay 主要用于修改和替换系统或应用的资源,而无需直接修改源码,与源码解耦。Overlay 机制可以分为 两种类型:
静态 Overlay(Static Resource Overlay, SRO)
- 在 编译时 覆盖资源。
- 适用于系统级资源修改(如默认壁纸、配置文件等)。
- 需要在
PRODUCT_PACKAGE_OVERLAYS
中指定 Overlay 目录。
动态 Overlay(Runtime Resource Overlay, RRO)
- 运行时 动态替换资源。
- 适用于应用级资源修改(如主题更换、颜色调整等)。
- 通过 Manifest 声明
overlay
,可在 运行时启用/禁用。
静态 Overlay(Static Resource Overlay, SRO)
系统在编译的时候会生成两个 APK,一个Overlay,一个是原来的 APK。在运行时,系统会解析这个应用是否有Overlay,如果有的话,会先去Overlay中查找,而不是向原来的 APK 中查找资源(比如 res 目录下的:文字、图片、配置等资源)。静态Overlay要求有源码的情况才能使用,一般用在修改系统源码应用属性时使用。
在系统源码中,静态 Overlay 通常以独立的模块(APK 或资源包)形式存在,放置在 vendor/overlay
或 product/overlay
目录下。
示例(定制壁纸)
打开 android/frameworks/base/core/res
目录,其中当前目录下就是一个完整的 APK 源码文件,其中系统的权限、弹框字符、配置参数等资源都会从这个应用读取。打开该目录下的 Android.pb
文件可以看到,该应用编译后的应用名叫 framework-res
:
android_app {
name: "framework-res",
....
}
可以在 android/frameworks/base/core/res/res/drawable-nodpi/
等其他资源文件夹下找到 default_wallpaper.png 系统默认壁纸图片。如果直接在这些目录下替换图片的话,就和源码耦合度太高了,不推荐。为了解耦,就可以使用 SRO 进行编译覆盖。
创建 overlay 文件目录
首先在源码根目录创建 vendor 文件夹,然后创建 overlay 文件夹,接着 overlay 下的文件目录要和源码中的文件目录保持一致,创建一个 frameworks/base/core/res/res/drawable-nodpi/
目录结构,将包含 default_wallpaper.png 图片的目录都创建出来。例如:res
目录下有 drawable-sw600dp-hdpi
、drawable-sw720dp-nodpi
等文件夹包含默认壁纸,就需要将这些文件夹都创建出来,每个文件夹都放一份要替换的默认壁纸图片(注意文件类型要保持一致,如果默认是 png,你替换的也要是 png 格式)。至此资源文件创建完成。
添加到编译文件
资源文件创建完成了,需要让系统去知道你有这个文件夹,那么就需要在 .mk
文件中去把这个文件夹添加上。在上篇文章中 vendor
目录下的 vendor_product.mk
中增加如下代码:
# overlay
PRODUCT_PACKAGE_OVERLAYS := \
vendor/overlay
这样,overlay 文件夹就被 MK 文件知道了,编译的时候系统就知道它是 overlay 的文件了。编译过程中就会把相应的目录资源进行覆盖操作,这样在开机的时候就会展示我们定制的壁纸了 😃
同样,如果想要修改系统默认的导航方式,则需要创建 values/config.xml
文件,因为系统导航的默认配置在 android/frameworks/base/core/res/res/values/config.xml
文件中:
<resources>
<!-- Controls the navigation bar interaction mode:
0: 3 button mode (back, home, overview buttons)
1: 2 button mode (back, home buttons + swipe up for overview)
2: gestures only for back, home and overview -->
<integer name="config_navBarInteractionMode">0</integer>
</resources>
效果如下:
默认壁纸:
替换壁纸:
最终效果:
动态 Overlay(Runtime Resource Overlay, RRO)
用在只有应用包的情况下使用,应用是以 APK 包的形式直接放到系统源码中的,不会再进行重新编译了。要覆盖应用里面的内容,就只能使用 RRO,动态去修改应用中的资源内容。
RRO 的工作原理是将叠加层软件包中定义的资源映射到目标软件包中定义的资源。当应用尝试解析目标软件包中资源的值时,系统转而会返回目标资源映射到的叠加层资源的值。
设置清单文件
如果某个软件包包含 <overlay>
标记作为 <manifest>
标记的子项,该软件包将被视为 RRO 软件包。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.dynamictheme.overlay">
<application android:hasCode="false" />
<overlay
android:targetPackage="com.example.myapp"
android:isStatic="true"
android:priority="1"/>
</manifest>
- 必要
android:targetPackage
属性的值用于指明 RRO 想要叠加(覆盖)的软件包的名称。 - 可选
android:targetName
属性的值用于指明 RRO 想要叠加的目标软件包的可叠加资源子集的名称。如果目标未定义可叠加资源集,此属性就不会显示。 android:isStatic
,当此布尔值属性的值设置为 true 时,叠加层会默认处于启用状态并且不可变,这会导致叠加层无法停用。android:priority
,当多个静态叠加层以相同的资源值为替换目标时,此数字属性的值(仅影响静态叠加层)将配置叠加层的优先级。数值越大表示优先级越高。
APK 包
假设现在有一个已经打包好的 APK 包名为 com.example.myapp
,其首页标题显示使用 res/values/strings.xml
中的文字资源:
<string name="hello_word">Hello word!</string>
即首页标题显示 my app。
在 vendor 目录下创建 apps/MyApplication/
和 apps/MyApplicationOverlay/
文件目录,将上面写好的 AndroidManifest.xml
文件放到 apps/MyApplicationOverlay/
目录下,将编译好的 APK 和其对应的 Android.mk
文件放到 apps/MyApplication/
目录下,如图:
接着在 apps/MyApplicationOverlay/
目录下,创建与原 APK 对应的文件资源目录 res/values/strings.xml
,然后修改文字资源:
<resources>
<string name="hello_word">Hello word Overlay!</string>
</resources>
现在 overlay 包已经制作好了,需要把 MyApplicationOverlay
这个应用参与到编译中去,就需要在 apps/MyApplicationOverlay/
目录下创建对应的 Android.mk
文件,来告诉源码该如何进行编译:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# 资源目录
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
# 模块标签
LOCAL_MODULE_TAGS := optional
# Java 源文件(通常不需要)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# SDK 版本
LOCAL_SDK_VERSION := current
# 模块名称
LOCAL_PACKAGE_NAME := MyApplicationOverlay
# 输出路径
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/overlay
# 签名方式
LOCAL_CERTIFICATE := shared
# AAPT 标志
LOCAL_AAPT_FLAGS := --auto-add-overlay
# 构建规则
include $(BUILD_PACKAGE)
然后编辑 product.mk
,将 MyApplication
和 MyApplicationOverlay
文件夹添加到编译环境中去:
注意:product.mk
已经在 vendor_product.mk
中被引用,而 vendor_product.mk
又在系统build/target/product/sdk_phone_x86_64.mk
文件中引用,所以 product.mk
一定会被执行。
PRODUCT_PACKAGES+=\
MyApplication
MyApplicationOverlay
然后单编push或者整编验证即可!