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();
        }
    }

总结一下,留着以后用。

相关推荐
游戏开发爱好者81 小时前
日常开发与测试的 App 测试方法、查看设备状态、实时日志、应用数据
android·ios·小程序·https·uni-app·iphone·webview
王码码20352 小时前
Flutter for OpenHarmony 实战之基础组件:第三十一篇 Chip 系列组件 — 灵活的标签化交互
android·flutter·交互·harmonyos
黑码哥2 小时前
ViewHolder设计模式深度剖析:iOS开发者掌握Android列表性能优化的实战指南
android·ios·性能优化·跨平台开发·viewholder
亓才孓2 小时前
[JDBC]元数据
android
独行soc2 小时前
2026年渗透测试面试题总结-17(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
金融RPA机器人丨实在智能2 小时前
Android Studio开发App项目进入AI深水区:实在智能Agent引领无代码交互革命
android·人工智能·ai·android studio
科技块儿2 小时前
利用IP查询在智慧城市交通信号系统中的应用探索
android·tcp/ip·智慧城市
独行soc3 小时前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
王码码20353 小时前
Flutter for OpenHarmony 实战之基础组件:第二十七篇 BottomSheet — 动态底部弹窗与底部栏菜单
android·flutter·harmonyos
2501_915106323 小时前
app 上架过程,安装包准备、证书与描述文件管理、安装测试、上传
android·ios·小程序·https·uni-app·iphone·webview