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,这样就能根据这两个十六进制位来区分是哪个包里的资源。

你可能感兴趣的:(Android换肤之前人栽树后人撞树)