Android轮子

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

2019-10-04  本文已影响0人  Laughing_G

一、换肤的两种方式

  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

上一篇 下一篇

猜你喜欢

热点阅读