Android换肤之前人栽树后人撞树

最近没什么难点技术好写,有难点的技术没研究透也不敢写,我估摸着太久不更新也不太好,得显得我还存在着对技术的热爱。
这不正好上头派了个换肤的功能,那换肤不外乎是那些思路,用框架也行,自己写也不难,主要不想用别人写的,不然出BUG就不好整了。

1. 最基本的思路

那要换肤,不外乎就是if-else,判断某个参数,根据该参数来if-else,但这样写太Low了嘛,每次要加需要换肤的地方,都这样写,那就很难看。
我们可以封装一个类,需要换肤的时候传入原ResId,返回特定皮肤的ResId。
我的思路就是做一个规范,相应渠道的皮肤图片的名称就是原图名_渠道名,这样以后扩展不同的渠道只需要导入相应的资源就行,不用改代码。
比如说原图的名是 home_top_logo.png , 那渠道1的图片名就要用 home_top_logo_channelone.png, 那渠道2的图片名就要用 home_top_logo_channeltwo.png,大概是这么一个意思。

    String channelName = 渠道名

    public int getChannelRid(Context context, int res){
        String resName = context.getResources().getResourceEntryName(res);
        String suffix = "";
        if (channelName != null){
            suffix = "_"+ channelName.toLowerCase();
        }else { return res; }
        Resources resources = context.getResources();
        return resources.getIdentifier(resName+suffix, "drawable", context.getPackageName());
    }

这样就能传入ResId,返回对应渠道的ResId

2. 代码要优美

在这个思路的基础上,如果我们做离线换肤,那就需要提前把渠道图片放到包中。如果渠道多的话,就会导致你的drawable文件夹里:
home_top_logo.png
home_top_logo_channelone.png
home_top_logo_channeltwo.png
......
home_top_logo_channelsix.png
当资源多的情况下,我们就会看得很累,这时候我们正常的思路是什么,没错,就是分文件夹
但是res资源里面怎么分文件夹呢,这就需要用到Gradle来实现,如果你还不熟悉Gradle,那一定要去学。我们的思路就是开发时这些资源分文件夹,这样就很方便增删改查,打包时再一起打到包里就行。
这时候就要用到sourceSets了,简单的写

    sourceSets{
        main{
            res.srcDirs = ['src/main/res', 'src/main/reschone', 'src/main/reschtwo']
        }
    }

3. 插件包实现

按照之前的做法也只能实现离线换肤,如果要实现在线,还得需要用到插件化。插件化是什么,之前也写过简单的文章介绍。(写文章的好处就是忘记了能马上回忆起来)
https://www.jianshu.com/p/d813e3df1cb9
如果我们的这个插件包只是用来放资源,那就挺简单,无需去考虑平常插件化场景中的生命周期这类问题,只需要防止资源冲突就行。

我们把资源放到另外一个项目中,然后打成apk文件放到设备里,再在当前应用去引用apk的资源,这个就是主要的原理。

        String resName = context.getResources().getResourceEntryName(res);
        String suffix = "";
        if (channelName != null){
            suffix = "_"+ channelName.toLowerCase();
        }

        int rid = -1;
        try {
            if (pluginResources == null) {
                AssetManager assetManager = AssetManager.class.newInstance();
                AssetManager.class.getDeclaredMethod("addAssetPath", String.class)
                        .invoke(assetManager, path + suffix + ".apk");
                pluginResources = new Resources(assetManager, context.getResources().getDisplayMetrics(),
                        context.getResources().getConfiguration());
            }

            rid = pluginResources.getIdentifier(resName+suffix,
                    "drawable", 插件包名);
        }catch (Exception e){
            e.printStackTrace();
        }

正常我们这样就能获得到插件中资源的ResId了。but我们要获取资源必须要用这个pluginResources,不能用宿主context的Resources,因为不是同个表中的,会找不到。

最后说说解决资源冲突的方法。(这里不扩展去讲,因为这种技巧懂的都懂,不懂的我就说个思路,要自己去操作才能学会)
(1)可以去网上抄代码改AAPT (小心有些人的代码是不生效的)
(2)可以反编译去改再回编译,反正是你自己的包。
但是一般不会冲突

4. 前人栽树后人撞树

那么问题就来了,我这代码是接以前的人写的,他那封装好那个方法就是返回ResId,然后在几层之后的最外层再调用setXXXResource()

如果我们直接返回插件的ResId肯定是莫得了,找不到文件滴。这时候我们就需要一个标识来判断这个ID是宿主的ID还是插件的ID。
正常我们资源文件的ID格式都是固定的 PPTTNNNN格式,但是前面两个一般我们不改都是7f开头。
如果我们做了防冲突的操作,一般会改PP,这样就能根据这两个十六进制位来区分是哪个包里的资源。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,997评论 2 59
  • 前置知识 需要了解setContentView的具体流程 需要了解LayoutInflater的inflate过程...
    _柚子啊阅读 798评论 0 0
  • 前言: 换肤,目前包括静态换肤和动态换肤静态换肤 这种换肤的方式,也就是我们所说的内置换肤,就是在APP内部放置多...
    若无初见阅读 796评论 0 4
  • 今天再给大家带来一篇干货。 Android的主题换肤 ,可插件化提供皮肤包,无需Activity的重启直接实现无缝...
    _SOLID阅读 100,031评论 147 1,120
  • 概述 本文主要分享类似于酷狗音乐动态换肤效果的实现。 动态换肤的思路: 收集换肤控件以及对应的换肤属性 加载插件皮...
    昊空_6f4f阅读 655评论 0 6