前言
最近在做launcher时有一个需求是通过overlay机制实现替换主题,中间遇到了不少问题,这里记录一下自己以后能用到,也希望能帮助有需要的同学。我使用的是android11系统,参考官方文档
https://source.android.google.cn/devices/architecture/rros?hl=zh-cn
概述
android overlay是一种资源的动态替换机制,它的工作原理是将叠加层软件包中定义的资源映射到目标软件包中定义的资源。当应用尝试解析目标软件包中资源的值时,系统转而会返回目标资源映射到的叠加层资源的值。它也分为静态的(SRO , Static Resource Overlay)和动态的(RRO , Runtime Resource Overlay)两种,静态的需要在源码编译阶段完成,比如SystemUI等系统应用,而RRO是通过安装Overlay的apk应用实现替换的。
创建overlay应用
首先我创建一个普通的应用用来进行测试,包名是com.ts.overtest,主界面就两个按钮切换主题,这里切换的其实就是页面的背景色color/background,定义在color.xml中, 期望替换这个颜色值实现主题的替换。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/background"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button1"
android:text="切换红色主题"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button2"
android:text="切换绿色主题"/>
</LinearLayout>
还要申请权限
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES"
tools:ignore="ProtectedPermissions" />
接下来创建两个module作为Overlay,为了方便区分,我这里创建的两个module包名分别为com.ts.overtest.overlaygreen和com.ts.overtest.overlayred,下面以红色主题为例。首先是AndroidManifest.xml文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:sharedUserId="android.uid.system"
package="com.ts.overtest.overlaygreen">
<overlay android:targetPackage="com.ts.overtest" android:priority="2" android:isStatic="true"/>
<application
android:hasCode="false"/>
</manifest>
这里面最重要的是overlay标签,下面说明一下它的各个属性
- targetPackage 要替换的应用的包名
- priority 优先级,优先级越高的默认先使用
- isStatic 表示该overlay应用默认处于active状态,如果我们使用的是SRO,将该Overlay应用的apk push到/vendor/overlay目录下后重启,该主题就默认生效了。
接下来是application标签,由于我们只是进行资源的替换,所以应用中没有代码,hasCode 表示我们的应用中没有代码。
下面是我的build.grade文件,没有什么特别注意的,只是minSdk需要改到26以上,由于没有代码,dependencies也不需要了。
plugins {
id 'com.android.application'
}
android {
compileSdk 32
signingConfigs {
debug {
storeFile file('/home/yxd0000/AndroidStudioProjects/系统应用签名/原生android11签名/platform.keystore')
storePassword 'android'
keyAlias 'platform'
keyPassword 'android'
}
}
defaultConfig {
applicationId "com.ts.overtest.overlayred"
minSdk 26
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
还需要该一下color中background这个值,将其改为红色。
SRO
编译上面的Overlay应用生成apk文件,将其push到/system/vendor/overlay目录下,但是一般的模拟器都是只读的,所以一般都不能push,需要先root和remount这就需要我们有源码或者开发版的模拟器了。
重启模拟器就会发现已经替换成功了,背景色变为了红色。
RRO
运行时替换要求就没有SRO那么高了,编译生成overlayred安装包后直接安装即可。安装好之后我们就编写代码来通过接受广播的方式替换主题了。
首先定义了一些常量
public class Constants {
public static String THEME = "com.ts.overTest.overlay";
public final static String OVERLAY_RED = "com.ts.overtest.overlayred";
public final static String OVERLAY_GREEN = "com.ts.overtest.overlaygreen";
public final static String CURRENT_THEME = "current_theme";
public final static String THEME_RED = "theme.red";
public final static String THEME_GREEN = "theme.green";
public static final String THEME_DEFAULT = "theme.default";
public static final String KEY_THEME_TYPE = "android.car.THEME_TYPE";
}
接下来注册一个广播接收器,收到广播后切换主题
public class ThemeChangeReceiver extends BroadcastReceiver {
private static final String TAG = ThemeChangeReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "收到主题切换广播");
String themeType = intent.getStringExtra(Constants.THEME);
if (themeType == null || themeType.length() == 0){
Log.d(TAG, "没有指明主题的类型");
return;
}
ThemeChangeManager.changeTheme(themeType);
}
}
上面代码中的ThemeChangeManager这个类是我们用来管理切换的,看一下其中的切换为红色主题的方法,它是怎么切换的
public static synchronized void changeToRedTheme() {
try {
IBinder binder = ServiceManager.getService("overlay");
IOverlayManager mOverlayManager = IOverlayManager.Stub.asInterface(binder);
mOverlayManager.setEnabledExclusive(Constants.OVERLAY_RED, true, 0);
Log.d(TAG, "切换红色主题成功");
Settings.System.putString(MyApplication.context.getContentResolver(),Constants.KEY_THEME_TYPE, Constants.THEME_DEFAULT);
} catch (Exception e) {
e.printStackTrace();
}
}
ServiceManager和IOverlayManager这两个类都是不提供给开发者使用的,所以我们需要有frameworks.jar这个包,其次我使用了Settings.System,这是系统数据库,要系统应用才有权限,这一步不是必须的,可以不要,只是为了方便而已。通过OverlayManager这个服务我们还可以查看当前应用所拥有的overlay包的信息
OverlayInfo overlayThemeGreen = mOverlayManager.getOverlayInfo(Constants.OVERLAY_GREEN, 0);
OverlayInfo overlayThemeRed = mOverlayManager.getOverlayInfo(Constants.OVERLAY_RED, 0);
拿到OverlayInfo后我们就可以查看它的激活状态,包名等等这些信息了。
除此之外,我们还可以通过adb查看和启动overlay
adb shell cmd overlay list --user current
我的com.ts.overtest这个应用中有两个overlay,签名的[ x ]表示其正在被使用状态。
adb shell cmd overlay enable packageName
总结
- SRO ,一般在android源码编译阶段一起编译,或者push到/vendor/overlay目录下,重启生效。
- RRO, 直接安装,通过代码控制,更加灵活。
demo位置
https://gitee.com/yangxudongll/android-overlay
|