美文网首页Android轮子
动态换肤框架1-基础换肤

动态换肤框架1-基础换肤

作者: Laughing_G | 来源:发表于2019-10-04 15:27 被阅读0次

    一、换肤的两种方式

    1. 内置换肤(静态):在Apk包中存在多种资源(图片、颜色值)用于换肤时候切换。缺点是自由度低,apk文件大,一般用于没有其他需求的日间/夜间模式app;
      2.动态换肤:通过运行时动态加载皮肤包。

    网易云音乐就是采用动态换肤的方式。找一台root的手机或是模拟器,下载运行网易云app,先在“个性换肤”下载一款皮肤,然后执行adb shell->su->cd data/data/com.netease.cloudmusic/files/theme,在theme这个目录我们会发现有个.skin411结尾的文件,这个就是皮肤包:


    image.png

    解压之后发现其本身就是个apk类型的文件!

    二、动态换肤的两个流程

    1.采集需要换肤的控件;
    2.加载皮肤包,开始换肤;

    问题一:如何采集目标控件?

    2.1setContentView做了什么?

    image.png

    可以看到是通过LayoutInlater布局加载器传入resId来加载,接下来再看看inflate里面做了什么事:


    image.png

    得到XmlResourceParser解析类之后,再继续看inflate方法:

    image.png

    再来看createViewFromTag干了什么事:


    image.png

    代码看到这里我们可以想到一个思路,就是想办法让Factory不为null,然后就能加载我们自己的view了,我们再来看看LayoutInflate内部是不是有setFactory这样的方法,很惊喜的方法有这个方法并且还没有被@hide注解,也就是Google工程师又给我们留了一道后门!

    2.2如何是setFactory设置自己的Factory?

    这里用到了ActivityLifecycleCallbacks,这个类可以监听整个app的所有页面的生命周期方法,我们可以用一个类是实现ActivityLifecycleCallbacks,然后在重写的onActivityCreated方法内部用反射的方式,去重写设置自己的setFactory2方法:

    @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    
            //获得Activity的布局加载器
            LayoutInflater layoutInflater = LayoutInflater.from(activity);
            try {
                /**
                 * 为什么要先将mFactorySet变量设置为false呢?
                 * 源码中在执行setFactory之前会先判断mFactorySet这个变量,如果为true会跑异常
                 */
                Field field = LayoutInflater.class.getDeclaredField("mFactorySet");
                field.setAccessible(true);
                field.setBoolean(layoutInflater, false);
            } catch (Exception e) {
                e.printStackTrace();
            }
            SkinLayoutFactory skinLayoutFactory = new SkinLayoutFactory();
            LayoutInflaterCompat.setFactory2(layoutInflater, skinLayoutFactory);
            mLayoutFactory.put(activity, skinLayoutFactory);
            SkinManager.getInstance().addObserver(skinLayoutFactory);
        }
    

    SkinLayoutFactory就是我们自己的Factory。在SkinLayoutFactory的onCreateView方法内,仿照系统的源码,我们自己去查找对应xml的所有的view,当然要经过一层过滤,就是通过属性SkinAttribute过滤那些我们需要换肤的View:


    image.png

    SkinAttribute方法我们定义了两个bean类:1.SkinPair; 2.SkinView:
    SkinPair:


    SkinPair类介绍
    SkinView
    SkinView类介绍

    2.3代码中的核心调用入口SkinManager.loadSkin(String skinPath)

    通过这个核心入口,传入换肤apk的路径,还是用反射的方法,根据路径生成换肤的skinResource对象,然后调用SkinResource的applySkin,传入skinResource,就能查询到换肤apk中的view 的属性和ID。
    当然执行了loadSkin方法之后需要通知SkinLayoutFactory去开启换肤的工作,这里用到了jdk的utils包给的观察者和被观察者模式,去动态的发起换肤的通知:


    被观察者
    观察者

    以上就是动态换肤的基本实现思想,另外自定义View、状态栏的换肤将在下一篇继续完善。
    Demo地址:
    仿网易云动态换肤Demo

    相关文章

      网友评论

        本文标题:动态换肤框架1-基础换肤

        本文链接:https://www.haomeiwen.com/subject/emguyctx.html