Google 在 Android P 起开始在系统层面推动 Material Design 2,对话框的圆角由 2px 改为 4px,搜索框启用更大的圆角,默认全局采用 Product Sans 字体,逐渐放弃 Hamburger Menu 采用底栏,白色更加明亮(brighter),并将 MD1 中被逐渐淡化的边框重新启用起来(具体表现是 Android P 开始的 Quick Settings Panel 中的图标的开关样式由原来的涂色方式改成由其外面的形状的底色所决定,所有图标都被套上一个形状,以及 Google 账户登录界面的密码框新增一圈 outline)等一系列改观
这边我不想争辩 MD1 和 MD2 孰优孰劣,但是我这个怀旧族更喜欢 MD1 的风格,在一番搜索之后发现不需要重新编译即可变更系统 UI,就是使用由 Sony 提出,在 Android L 中合并并在 Android M 上完全支持的 RRO 主题引擎,当然还有别的主题引擎在本文之后会略微提到。
利用 Android RRO 主题引擎可以让我们在不修改代码的情况下修改样式,当然需要代码变更的部分还是需要变动代码或者使用 (Ed) Xposed 等技术。系统设置中一些色彩方案其实也是通过启用不同的 Overlay 来实现变色效果。
Android RRO 主题引擎工作原理是什么呢?Vendor(厂商) 可以编译一系列 Overlay APK,里面包含需要修改的样式以及目标应用的 APK 包名,将其安装至 overlay 目录下(不同系统的 overlay 目录似乎有点不一样),启用该 Overlay 之后便可使其生效。
而 Android RRO 加载样式时则会先查看是否当前应用有 Overlay 所附加,如果有则优先使用 Overlay 的样式并忽略应用本身对应的样式,最后才加载应用本身的样式,达到不变动代码修改系统样式的目的。
Android RRO 的局限性在于其仅能覆盖 res/ 目录下的文件的值(所以实际上不光可以改 UI,若别的值存放在 res/ 目录下的文件亦可以使用 RRO 修改),对于 assets/ 则无能为力,而 Substratum 的一种模式则支持替换 assets/。Android RRO 的优势在于其是原生包含于 Android 中,无需其他改动即可使用。
而往往将 Overlay APKs 存放于 overlay 目录下需要特权(往往是 root),因此 Android RRO 仅使用于 root 用户,如果手机预装了 Substratum 那么还有点救;同时请注意对于国产安卓(注意不是 Android)如 MIUI、EMUI 等亦无法保证 RRO 是否可以在这类 ROM 上工作
本文就是编译 Overlay 来达成修改 System UI 的外观的目的,本文以修改 System UI 对话框中的圆角半径(4px 修改为 2px)为例展开。
本文默认所有操作在 Ubuntu 18.04 下进行
建议安装 Java 8, Java 11 可能会有些问题
如果你已经准备好环境则可以跳过这一步
执行如下命令
sudo apt update
sudo apt install openjdk-8-jdk-headless unzip
接着去 https://developer.android.com/studio 上下载 Android SDK, 根据操作系统选择 Command Line Tools Only 下的 zip 文件,这里因为用的是 Ubuntu 所以下载 Linux 版,下载完后解压,定位到 tools 文件夹下,执行
mkdir ~/android-sdk
export ANDROID_HOME=~/android-sdk
./bin/sdkmanager --sdk_root=${ANDROID_HOME} --list
可以看到所有可用的 SDK,建议用最新的 Platform 版本,这里我用 31,执行
./bin/sdkmanager --sdk_root=${ANDROID_HOME} "platform-tools" "platforms;android-31" "build-tools;31.0.0"
即配置完成
因为需要知道需要修改的键名才能正常覆盖掉原本的值,所以需要通过各种方式查看到目标应用原本的样式文件获取对应的键名才能进行下一步操作
如果是开源项目,直接找到这个项目的 res 文件夹下一个个去翻 xml 文件就是了,如果不确定是哪个值可以丢进 Android Studio 让其模拟出样式界面便于定位;如果是闭源项目那么可能需要反编译现成的 APK,并分析反编译出的样式文件
而本例中则是对于 System UI 的分析,其位于 android_framework_base 下,小加搜索便能发现圆角半径是由三个值确定的,均在 core/res/res/values/config.xml 文件内,分别是 config_progressBarCornerRadius, config_progressBarCornerRadius, config_buttonCornerRadius 所控制的,其中 config_buttonCornerRadius 引用的是 @dimen/control_corner_material,其真正的值位于 dimens_material.xml 内,原内容如下
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 忽略别的一些内容 -->
<dimen name="control_corner_material">4dp</dimen>
</resources>
我们现在要做的就是把 dimens_material.xml 中的 control_corner_material 值改成 2dp,另外两个值也是如此操作
一旦知道这些数据之后便可以进行下一步操作了
Overlay 其实和普通的 APK 没什么差别,新建一个文件夹,在里面放好一个 AndroidManifest.xml,并写入如下内容
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="me.milkice.remove.miui.volte">
<application/>
<uses-sdk android:minSdkVersion="30"
android:targetSdkVersion="30"/>
<overlay android:priority="1" android:targetPackage="android"/>
</manifest>
其中 package 就是 APK 包名,可以任意指定
android:priority 是 Overlay 优先级,在多个资源存在的情况下系统会根据 priority 加载样式
android:targetPackage 是目标应用的包名,对于 System UI 包名就是 android
注意,如果你想让你的 Overlay 安装之后默认启用,请在 overlay 节点下新增属性 android:isStatic="true" 即可
完成上述步骤后新建一个 res 文件夹,然后根据你想要覆盖的文件新建同样的路径和同样的文件名,在本例中则是新建 values/dimens_material.xml 文件,内容如下
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="control_corner_material">2dp</dimen>
<dimen name="progress_bar_corner_material">0dp</dimen>
</resources>
只需要写入你想要修改的值就行了
同理,本例中还要新建 values/dimens.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="dialog_corner_radius">2dp</dimen>
</resources>
这样就好了,非常容易
aapt 应该已经在第一步的时候安装好了,到 Overlay 项目文件夹的根目录,执行
~/android-sdk/build-tools/31.0.0/aapt package -M AndroidManifest.xml -S res/ \
-I ~/android-sdk/platforms/android-31/android.jar \
-F overlay.apk.u
可能需要手动修改一下 aapt 和 android.jar 的路径
生成好 overlay.apk.u 之后对 apk 签名
jarsigner -keystore ~/.android/debug.keystore \
overlay.apk.u androiddebugkey
如果提示~/.android/debug.keystore 不存在
那就自己生成一个呗
keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"
签名的时候会要求输入密码,密码是 android,回车即可
最后用 zipalign 对齐一下 apk 文件
zipalign 4 overlay.apk.u overlay.apk
生成的 overlay.apk 就是可用的 Overlay 啦
首先需要摸明白 overlay 目录到底在哪里,有些机型是在 /system/vendor/overlay,更新的系统似乎是放在了 /system/product/overlay,找下就行了
其次是安装 overlay 的方式,overlay APK 不可直接安装,必须丢进上述的文件夹,可以直接硬塞进 overlay 文件夹内,或者写一个 Magisk 模块来实现 systemless 安装,这里不再赘述
安装完 overlay 重启后进入系统,发现一切都没有变化,是因为此时 overlay 模式处于禁用状态。开启 overlay 很简单,开启 ADB 后插进电脑上,执行 adb shell 进入 shell 后,执行 cmd overlay list 可以列出所有已安装的 overlay,此时应该能看到自己的 overlay 的包名,再执行 cmd overlay enable <你的应用包名> 就可以启用 Overlay 了
现在再去看看 System UI, 是不是有些变化了?
之前虽然有注意到一些 Overlay 相关 Magisk 模块(比如 Lawnchair 的 QuickSwitch)会在卸载 RRO 之后清空 /data/resource-cache 但是没有给予太多的关注
直到有用户报告在卸载 RRO 相关 Magisk 模块之后开机卡第二屏的情况才发现问题的严重性
需要卸载 Android RRO 的时候请一定要在重启之前删除 /data/resource-cache 下的所有文件,避免重启后翻车
//TODO
有些 App 的资源文件可能在打包编译的时候被混淆了,比如 Google Chrome 似乎在 68 之后开始会对资源混淆,即用 apktool 对 Chrome APK 文件反编译出的 xml 文件内的键名都是 “OBFUSCATED_xxx”,似乎上述的方法就不可用了
但是后面仔细观察一波发现,这些被混淆的资源大多都有 resource id,同时还有 public.xml 呈现 id 映射关系,那么是否可以根据 resource id 来修改对应的值呢?这块儿内容因为还没有测试,尚待填坑。
Substratum 相对 RRO 来说就有更多的可修改的空间,同时 Substratum 也提供了 模板 以及 多个选项 供开发者使用,之前我也曾经试着编译了一个 Substratum 模块,但是步骤明显比 RRO 要复杂一些,同时用到了 Android Studio(虽然不知道是不是必须要用 AS),并没有太多经验因而这里就一笔带过了