Android 使用Overlay现实主题切换

最近项目上,想做一个主题切换的功能,整理了一下发布出来,主要使用的是IOverlayManager,大体思路如下:

1、想切换的应用,各自做overlay apk(简称皮肤包)

2、将overlay apk push 到vendor/overlay目录下(如果没有这个目录,push到system/app里面也可以),这个主要目的是为了让overlay 找到这个overlay apk。

3、重启设备

4、点击切换主题

5、各个应用重走生命周期,然后显示出来新的资源

这个主题切换的原理,我自己感觉,就是将overlay apk里面的资源替换到原来apk里面,然后重新加载。

一、app准备,制作overlay apk皮肤包

1、AS内新建项目 文件命名方式:xxxx.Red/xxxx.Young/xxxx.Gold

例如:LocalSetting 新建替换资源的模块名可命名为 LocalSetting.Red /LocalSetting.Young/LocalSetting.Gold,其实就和新建的app 项目一样。

仅保留AndroidManifest.xml 和res目录下仅保留需要替换的资源信息(values下的colors.xml , strings.xml , dimens.xml themes.xml等 和 drawables/xml目录下的资源, 不支持替换布局文件),想替换那个文件,就在对应的目录下用相同的资源名字放进去。

下图是launcher的主题app目录。

AndroidManifest.xml仅包含以下信息即可,其中targetPackage为要替换资源的app包名。

java 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.launcher.young">

    <overlay
        android:priority="9"
        android:targetPackage="com.test.launcher" />
</manifest>

2、生成overlay apk;

overlay apk 中 AndroidManifest.xml 包名命名方式:xxxx.red/xxxx.young/xxxx.gold,如上示例

eg: package="com.neusoft.localsetting.red"

替换 红色(酷炫)主题的apk名为localsetting_red.apk

替换 金色(酷睿)主题的apk名为localsetting_gold.apk

替换 年轻色(酷潮)主题的apk名为 localsetting_young.apk
注意:其中red、gold、young是需要打合定义的,目的是为了在切换主题时,获取对应主题的包名

3、每套主题如果对应2套UI(日夜模式)大家正常在对应的主题中建-night文件夹即可。

本地测试:将生成的overlay apk签名 push 到vendor/overlay/red/xxxx.apk目录下; 然后调用切换主题即可。

二、实现主题切换流程

主题切换流程:

主要使用原生方法IOverlayManager,调用setEnabledExclusive方法切换主题。

1、开机遍历获取/vendor/overlay下的主题包

使用如下代码可以把不同主题的包名方法一个list中,等后续切换时直接使用。
注意:这个包名是之前和app打合的,必须所有的app都要按照这个要求规则命名apk。

java 复制代码
private List<String> mRed1List = new ArrayList<String>(); //存放红色主题包名list
private List<String> mYoungList = new ArrayList<String>(); //存放young主题包名list
private IOverlayManager mOverlayManager = IOverlayManager.Stub.asInterface(ServiceManager.getService("overlay"));


    public void getThemeOverlayList() {
        IOverlayManager mOverlayManager = IOverlayManager.Stub.asInterface(ServiceManager.getService("overlay"));
        mRed1List.clear();
        mYoungList.clear();
        try {
            mOverlayInfoMap = mOverlayManager.getAllOverlays(0);
            for (List<OverlayInfo> overlayInfos : mOverlayInfoMap.values()) {
                for (int j = 0; j < overlayInfos.size(); j++) {
                    String overlayPackageName = overlayInfos.get(j).packageName;
                    if (overlayPackageName.contains("red")) {
                        mRed1List.add(overlayPackageName);
                    } else if (overlayPackageName.contains("young")) {
                        mYoungList.add(overlayPackageName);
                    }
                }
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

2、切换时遍历list,调用原生方法mOverlayManager.setEnabledExclusive切换主题。

复制代码
mOverlayManager.setEnabledExclusive(mYoungList.get(i), true, 0);方法是把对应的主题包资源替换进去。
复制代码
mOverlayManager.setEnabled(mRed1List.get(i), false, 0);方法是把主题包禁用,从而显示显示源代码里面的资源。
java 复制代码
    private void changeThemeColor(int themeChange) {
        List<Boolean> changeThemeResult = new ArrayList<>();
        List<String> changeResultFail = new ArrayList<>();
        try {
            if (themeChange == SettingConfig.THEME_RED) {
                if (mRed1List.size() != 0) {
                    for (int i = 0; i < mRed1List.size(); i++) {
                        boolean isSuccess = mOverlayManager.setEnabledExclusive(mRed1List.get(i), true, 0);
                        if (!isSuccess) {
                            changeThemeResult.add(isSuccess);
                            changeResultFail.add(mRed1List.get(i));
                        }
                    }
                    Log.d(TAG, "---mRed1List.get(i):" + mRed1List.toString());
                }
            } else if (themeChange == SettingConfig.THEME_YOUNG) {
                if (mYoungList.size() != 0) {
                    for (int i = 0; i < mYoungList.size(); i++) {
                        boolean isSuccess = mOverlayManager.setEnabledExclusive(mYoungList.get(i), true, 0);
                        if (!isSuccess) {
                            changeThemeResult.add(isSuccess);
                            changeResultFail.add(mYoungList.get(i));
                        }
                    }
                    Log.d(TAG, "---mYoungList.get(i):" + mYoungList.toString());
                }
            } else if (themeChange == SettingConfig.THEME_GOLD) {
                if (mRed1List.size() != 0) {
                    for (int i = 0; i < mRed1List.size(); i++) {
                        boolean isSuccess = mOverlayManager.setEnabled(mRed1List.get(i), false, 0);
                        if (!isSuccess) {
                            changeThemeResult.add(isSuccess);
                            changeResultFail.add(mRed1List.get(i));
                        }
                    }
                    Log.d(TAG, "---mRed1List.get(i):" + mRed1List.toString());
                }
                if (mYoungList.size() != 0) {
                    for (int i = 0; i < mYoungList.size(); i++) {
                        boolean isSuccess = mOverlayManager.setEnabled(mYoungList.get(i), false, 0);
                        if (!isSuccess) {
                            changeThemeResult.add(isSuccess);
                            changeResultFail.add(mYoungList.get(i));
                        }
                    }
                    Log.d(TAG, "---mYoungList.get(i):" + mYoungList.toString());
                }
            }

            if (changeThemeResult != null && changeThemeResult.size() > 0
                    && changeResultFail != null && changeResultFail.size() > 0) {
                Log.d(TAG, "---changeThemeResult:" + changeThemeResult.toString());
                Log.d(TAG, "---changeResultFail:" + changeResultFail.toString());
            } else {
                Log.d(TAG, "---changeThemeResult   无");
                Log.d(TAG, "---changeResultFail    无");
            }

        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

总结一下,留着以后用。

相关推荐
TeleostNaCl5 分钟前
Android | 启用 TextView 跑马灯效果的方法
android·经验分享·android runtime
TheNextByte11 小时前
Android USB文件传输无法使用?5种解决方法
android
quanyechacsdn2 小时前
Android Studio创建库文件用jitpack构建后使用implementation方式引用
android·ide·kotlin·android studio·implementation·android 库文件·使用jitpack
程序员陆业聪3 小时前
聊聊2026年Android开发会是什么样
android
编程大师哥3 小时前
Android分层
android
极客小云5 小时前
【深入理解 Android 中的 build.gradle 文件】
android·安卓·安全架构·安全性测试
Juskey iii5 小时前
Android Studio Electric Eel | 2022.1.1 Patch 2 版本下载
android·ide·android studio
Android技术之家5 小时前
2025年度Android行业总结:AI驱动生态重构,跨端融合开启新篇
android·人工智能·重构
洞见前行5 小时前
Android第二代加固技术原理详解(附源码)
android
风清云淡_A5 小时前
【JetCompose】入门教程实战基础案例01之显隐动画
android