一、换肤的两种方式
- 内置换肤(静态):在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
网友评论