博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
插件化实现Android多主题功能原理剖析
阅读量:5881 次
发布时间:2019-06-19

本文共 5541 字,大约阅读时间需要 18 分钟。

前言

之前我们总结过B站的皮肤框架MagicaSakura,也点出了其不足,,该框架只能完成普通的换色需求,没有QQ,网易云音乐类似的皮肤包的功能。

那么今天我们就带来,拥有皮肤加载功能的插件化换肤框架。框架的分装和使用具体可以看我的工程里面的代码。

这样做有两个好处:

  1. 皮肤可以不集成在apk中,减小apk体积
  2. 动态化增加皮肤,灵活性大,自由度很大

如何实现换肤功能

想当然的,在View创建的时候这是让我们应用能够完美的加载皮肤的最好方案。

那么我们知道,对于Activity来说,有一个可以复写的方法叫onCreateView

@Overridepublic View onCreateView(View parent, String name, Context context, AttributeSet attrs) {    return super.onCreateView(parent, name, context, attrs);}复制代码

我们的view的创建就是通过这个方法来的,我们甚至可以通过复写这个方法,实现view的替换,比如本来要的是TextView,我们直接给它替换成Button.而这个方法其实是实现的LayoutInflaterFactory接口。

关于LayoutInflaterFactory,我们可以看一下鸿神的文章

创建View

根据拿到的onCreateView里面的name,来反射创建View,这边用到了一个技巧:onCreateView中的name,对于系统的View,是没有'.'符号的,比如"TextView"我们拿到的直接是TextView,

但是自定义的View,我们拿到的是带有包名的全部名称,因此反射时,对于系统的View,我们需要加上系统的包名,自定义的View,则直接使用name。

也不用疑问为什么用反射,这样不是慢吗?

因为系统的LayoutInflater在createView的时候也是这么做的,这边的代码都是参考系统的实现的。

private static final String[] sClassPrefixList = {            "android.widget.",            "android.view.",            "android.webkit."    };    static View createViewFromTag(Context context, String name, AttributeSet attrs) {        if (name.equals("view")) {            name = attrs.getAttributeValue(null, "class");        }        try {            mConstructorArgs[0] = context;            mConstructorArgs[1] = attrs;            // 系统控件,没有".",因此去创建系统View            if (-1 == name.indexOf('.')) {                // 根据名称反射创建                for (int i = 0; i < sClassPrefixList.length; i++) {                    final View view = createView(context, name, sClassPrefixList[i]);                    if (view != null) {                        return view;                    }                }                return null;                // 有'.'的情况下是自定义View,V4与V7也会走            } else {                // 直接根据名称创建View                return createView(context, name, null);            }        } catch (Exception e) {            // We do not want to catch these, lets return null and let the actual LayoutInflater            // try            return null;        } finally {            // Don't retain references on context.            mConstructorArgs[0] = null;            mConstructorArgs[1] = null;        }    }    /** * 反射,使用View的两参数构造方法创建View * @param context * @param name * @param prefix * @return * @throws ClassNotFoundException * @throws InflateException */private static View createView(Context context, String name, String prefix)        throws ClassNotFoundException, InflateException {    Constructor
constructor = sConstructorMap.get(name); try { if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it Class
clazz = context.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); constructor = clazz.getConstructor(sConstructorSignature); sConstructorMap.put(name, constructor); } constructor.setAccessible(true); return constructor.newInstance(mConstructorArgs); } catch (Exception e) { // We do not want to catch these, lets return null and let the actual LayoutInflater // try return null; }}复制代码

判断View是否需要换肤

与创建View一样,根据拿到的onCreateView里面的AttributeSet attrs

拿到后,我们解析attrs

/** * 拿到attrName和value * 拿到的value是R.id */String attrName = attrs.getAttributeName(i);//属性名String attrValue = attrs.getAttributeValue(i);//属性值复制代码

根据属性名和属性值进行判断,有背景的属性,是否符合需要换肤的属性、

插件化资源注入

我们的皮肤包其实是APK,是我们写的另一个app,与正式App不同的是,其只有资源文件,且资源文件需要和主app同名。

1.通过 PackageManager拿皮肤包名

2.拿到皮肤包里面的Resource

但是因为我们想new Resources()时候,发现其第一个参数是AssetManager,但是AssetManager的构造方法在源码中被@hide了,我们没有方法拿到这个类,但是幸好其类还是能拿到的,我们直接反射获取。

我们拿资源的代码如下。

PackageManager mPm = context.getPackageManager();PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);skinPackageName = mInfo.packageName;/** * AssetManager assetManager = new AssetManager(); * 这个方法被@ hide了。。我们只能通过反射newInstance */AssetManager assetManager = AssetManager.class.newInstance();/** * addAssetPath同样被系统给hide了 */Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);addAssetPath.invoke(assetManager, skinPkgPath);Resources superRes = context.getResources();Resources skinResource = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());/** * 讲皮肤路径保存,并设置不是默认皮肤 */SkinConfig.saveSkinPath(context, params[0]);skinPath = skinPkgPath;isDefaultSkin = false;/** * 到此,我们拿到了外置皮肤包的资源 */return skinResource;复制代码

如何动态的从皮肤包中获取资源

我们以从皮肤包里面获取color来举例

业务端是通过资源的id来获取color的,资源的id也就是一个在编译时就生成的int型。 而皮肤包的也是编译时生成的,因此两个id是不一样的,我们只能通过资源的id先拿到在我们应用里的该id的名字,再通过名字去资源包里面拿资源。

public int getColor(int resId) {    int originColor = ContextCompat.getColor(context, resId);    /**     * 如果皮肤资源包不存在,直接加载     */    if (mResources == null || isDefaultSkin) {        return originColor;    }    /**     * 每个皮肤包里面的id是不一样的,只能通过名字来拿,id值是不一样的。     * 1. 获取默认资源的名称     * 2. 根据名称从全局mResources里面获取值     * 3. 若获取到了,则获取颜色返回,若获取不到,老老实实使用原来的     */    String resName = context.getResources().getResourceEntryName(resId);    int trueResId = mResources.getIdentifier(resName, "color", skinPackageName);    int trueColor;    if (trueResId == 0) {        trueColor = originColor;    } else {        trueColor = mResources.getColor(trueResId);    }    return trueColor;}复制代码

实际使用

上面都是我们插件化加载的需要了解的知识,真的进行框架使用的时候,使用了自定义属性,根据自定义属性判断是否需要换肤。

使用观察者模式,所有需要换肤的view都会存放在Activity一个集合中,在皮肤管理器通知皮肤更新时,主动更新视图状态。

说了这么多了,框架的分装和使用具体可以看我的工程里面的代码。

效果如图:

代码见:

欢迎star

APK下载


本文作者:Anderson/Jerey_Jobs

博客地址 :

简书地址 :
github地址 :

转载地址:http://lgpix.baihongyu.com/

你可能感兴趣的文章
CentOS6.4+Apache+Mariadb+PHP搭建WordPress
查看>>
【代发】初识项目虚拟团队--作者Dylan Gao
查看>>
解决“HTTP/1.1 405 Method not allowed”问题,让静态文件响应POST请求
查看>>
支持IPV6的瑞士×××之nc命令------强大的网络命令工具
查看>>
类的实例化
查看>>
Python学习笔记__12.9章 urlib
查看>>
Ansible自动化运维亲身经验之谈
查看>>
python 高阶函数:map、reduce
查看>>
css图片整合
查看>>
使用阿里云ACM简化你的Spring Cloud微服务环境配置管理
查看>>
修改默认view
查看>>
去中心化、中介化的区块链技术
查看>>
对话DeepMind创始人:建立通用人工智能
查看>>
linux中awk工具的使用
查看>>
我在51CTO微职位学软考——我是mata宇我为自己代言
查看>>
python
查看>>
SIEM在PCI DSS和安全等级保护合规性中的作用
查看>>
奥马冰箱:用设计创新突破行业“天花板”
查看>>
Oracle Database 11g SQL开发指南
查看>>
ACM一些小的注意事项 持续更新ing
查看>>