上一篇说到了View的加载过程,而且对TextView进行了拦截,让页面上全部的TextView内容都变成了咱们想要的helloworld,此次咱们一块儿来研究一下网易云音乐的动态换肤技术。说到换肤,有不少人选择了更改样式的方式,可是这种方式样式是固定的,必须提早内置好打包到apk中,因此致使apk包增大,而且灵活性不够强。今天咱们要作的是打造一个动态下发+全属性修改+流畅无闪烁的换肤方案。要实现该方案主要有3个难点,分别是:如何批量修改View的背景、如何标识须要换肤的控件、如何加载插件皮肤资源到当前app中,接下来咱们一个一个难题来攻破。app
1.如何批量修改View的背景?字体
一个一个控件设置确定是不现实的,这样会产生不少的垃圾代码,灵活性也不够强,因此必须想办法批量处理。这里能够经过view的hook技术,经过自定义Factory的方式来控制app中view的背景设置,从而实现不闪烁换肤的效果。既然咱们拦截了系统生成view的过程,那么首先咱们必需要本身生成view,否则页面就要空白了,生成view的具体代码以下:插件
这里调用mDelegate.createView是考虑到AppCompatActivity自己对一些控件作了一些兼容性问题,若是能在里面找到就直接使用就行了,若是没有找到的话,咱们就要本身定义生成View的方式了。系统生成view时是使用反射的方式,这里咱们也来模仿一下。须要用到反射的话就必须知道控件的包名,可是有些控件已经自带包名,好比V7下面的控件,这个时候咱们经过判断这里是否包含"."来判断出来,而后直接把整个路径传进去就行了。包名的话主要是3种,以下:cdn
具体生成的View方式和系统的如出一辙,都是使用到反射来生成,这里再贴一下代码:xml
2.如何找到须要换肤的控件?对象
这里用的最多的方法是给控件设置一个属性来标记,判断哪一个控件包含自定义的该属性,给包含的进行背景图片或颜色的替换。咱们首先在values目录下新建一个attr.xml,而后在里面定义了一个叫作skinable的boolean属性,而后当该属性为true时就加入该控件到一个map中,准备进行换肤。其中attrs是前面初始化view时传过来的,表明该view的全部属性。判断出该控件须要换肤时,遍历出该控件全部属性加入到一个属性map中备用,具体代码以下:blog
3.如何实现资源的替换?图片
咱们平时获取项目中的资源通常经过context.getResource().getXXX来获取,其实Resource只是一个皮包类,本质仍是经过AssetManager来获取资源文件的,因此咱们只须要获取到外部插件包的AssetManager对象便可,这里能够经过反射来获取到,而且调用addAssetPath方法来加载外部资源包。最后咱们也将外部生成的AssetManager进行包一层Resource,为的是保留resid和资源的对应关系,这样才能实现资源的替换。资源
获取到了外部插件的Resource对象接下来就好办了,直接将当前app中控件的属性设置成外部的资源就大功告成了。这里attrsMap是咱们自定义生成View时保存的某个View的全部属性,找出其中须要更换的属性,好比background、drawable什么的。get
问题解答
1.若是同一控件不光要换背景,同时还要换字体怎么办?
答:这里能够再自定义一个属性表明字体须要替换
2.皮肤插件该怎么生成呢?
答:直接将当前项目更换一下资源文件,而后打包成apk,最后改个名字就行了,最好不要以apk结尾,以防用户去安装
3.现有app中的资源id是怎么和插件中的资源id进行对应的呢?
答:现有app中的Resource配置和插件Resource的配置是同一个,因此能够经过当前资源id=外部资源id的特殊性,根据当前资源id直接找到外部资源进行加载