换肤框架Android-Skin-Support解析

知识点汇总:

一:换肤框架Android-Skin-Support项目概述

二:实现换肤功能背景知识点学习

三:对需要换肤的界面和控件实现

四:实现XML布局解析监听

五:自定义Resources对象,创建皮肤包加载对象

六:动态映射技术实现皮肤包资源的查找

七:换肤项目Android-skin-support总结

八:换肤实现思路汇总

九:扩展阅读


一:换肤框架Android-Skin-Support项目概述

下面是官方介绍:

换肤的原理概述:

步骤一:封装需要换肤的界面元素

       可以自定义界面中需要换肤的元素,这些元素包括:各种不同的控件及其相关的属性、界面状态栏、界面主题等,在定义这些换肤元素时,需要实现特定的换肤接口。

步骤二:对界面中所有需要换肤元素的监听

       监听界面中需要换肤的界面相关元素及相关界面,当需要进行皮肤切换的时候,可以获取到所有界面需要换肤的元素,并对其做统一处理。

步骤三:加载皮肤包中的资源文件

       应用默认是加载系统资源路径和应用打包时res路径下的资源,需要通过自定义Resources对象,实现对外部皮肤包资源的加载。

步骤四:查找皮肤包中对应的资源

       要实现默认资源和皮肤包资源的一一对应,这样才能自定义不同的资源在实现换肤后的效果,这时需要通过动态映射技术实现资源的查找,并一一对应找出相关资源。


二:实现换肤功能背景知识点学习

知识点一:Android资源加载机制

       在我们项目中,我们一般通过接口getResources().getString(R.string.app_name)等类似接口访问项目中的资源,当我们进去查看源码时,会发现最终去获取资源的代码是通过AssetManager对象实现的,大体逻辑如下:

       既然是通过AssetManager对象获取应用和系统的资源,那我们就要知道AssetManager对象是如何初始化的,并最终在Resource对象中使用,使应用中可以加载系统和应用的相关资源,请看下面相关系统代码:

解析:创建Resources需要一个AssetManager对象。在开发应用程序时,使用Resources.getAssets()获取的就是这里创建的AssetManager对象,AssetManager其实并不只是访问res/assets目录下的资源,而是可以访问res目录下的所有资源,AssetManager在初始化的时候会被赋予两个路径,一个是应用程序资源路径 /data/app/xxx.apk,一个是Framework资源路径/system/framework/framework-res.apk(系统资源会被打包到此apk中),所以应用程序使用本地Resources既可访问应用程序资源,又可访问系统资源。

        我们通过对Android资源加载机制的了解,现在加入要加载外部的资源时,是否可以自定义一个Resource对象,并通过这个自定义Resource对象拿到相关的资源呢?

备注:Android资源加载机制的深入了解,可以查看本文末尾的相关扩展阅读。


知识点二:应用的布局解析实现

       当我们需要绘制界面布局时,我们通常会在xml布局中设置不同的控件和布局,在Activity中会通过界面xml文件获取相关的控件和控件的属性,从而实现对不同控件对象的展示,通过setContentView函数,我们了解到Activity是通过一个AppCompatDelegate对象实现对xml布局的解析,下面我们看看AppCompatActivity类的onCreate初始化代码:    

         除了AppCompatDelegate对象的初始化代码,我们看到调用了一个函数:installViewFactory,我们继续看看,代码实现在AppCompatDelegateImpl类中:

         通过对setFactory2的第二个参数的深入了解,我们看到了在解析界面相关控件时,发现系统代码AppCompatViewInflater的createView函数实现,代码如下:

解析:上面代码中,通过switch判断出不同控件时,可以返回该控件二次开发后的子控件,这样假如我们需要对TextView进行换肤,并实现了TextView类的换肤子类的话,是否也可以通过上面思路实现呢?

备注:对该知识点的深入了解,请查看:https://blog.csdn.net/duolaimila/article/details/64907012


三:对需要换肤的界面和控件实现

解析:在实现界面换肤时,界面可以实现换肤的元素包括控件、状态栏、主题背景等,那么我们如何根据项目需求,把需要换肤的控件与控件属性、状态栏等进行定制实现呢,下面实现换肤的控件和界面代码:

解析:在需要换肤的控件和界面中,通过实现接口SkinCompatSupportable,接口包含唯一函数applySkin(),这样在触发换肤功能时,就会触发相关控件和界面的换肤回调接口applySkin(),在换肤控件中,我们看到ImageViewHelper类中设置了该控件需要实现换肤的控件属性,并获取该属性的id值,具体代码就不展示了,而实现换肤的Activity界面中,可以通过applySkin()函数,设置界面中状态栏随着皮肤包的变化而变化等其他功能。


四:实现XML布局解析监听

        通过前面对相关背景知识的学习,了解到系统是如何监听和解析我们自定义界面的xml布局文件的,下面我们就看看该项目是如何实现对需要换肤界面和控件进行监听的,下面看看在框架进行初始化时,执行的部分初始化代码:

解析:通过registerActivityLifecycleCallbacks接口,可以监听到每个界面的生命周期,通过观察者模式,把创建的界面保存在ArrayList<SkinObserver> observers数组中,SkinCompatManager.getInstance().addObserver(observer)函数实现对观察者数组的统一添加,而实现xml布局解析监听,是通过installLayoutFactory函数实现,具体代码实现如下:

解析:通过上面对应用的布局解析实现的解析,是否有熟悉的感觉,我们可以看到,对xml布局控件的解析,主要是在SkinCompatDelegate中实现的,而对SkinCompatDelegate代码的进一步查看,我们看到一个类似AppCompatViewInflater的换肤相关类SkinCompatViewInflater,代码如下所示:

解析:通过类似应用解析xml布局控件,对界面中需要换肤的控件进行界面,最后存放在数组List<WeakReference<SkinCompatSupportable>> mSkinHelpers = new CopyOnWriteArrayList<>()中。


五:自定义Resources对象,创建皮肤包加载对象

         在项目的初始化阶段,会通过链式调用的方式(建造者模式),配置不同皮肤库的加载策略,而在加载外部的皮肤包,而在加载外部皮肤库时,需要创建自定义Resources对象,代码如下:

解析:通过前面Android资源加载机制,我们了解到应用本身也是通过自定义Resources对象实现对系统资源和应用本身的资源进行加载的,底层是通过AssetManager对象实现对资源的加载的,那么现在我们需要加载外部皮肤资源,我们也顺着系统的资源加载实现思路,在该开源项目中,通过自定义Resources对象,并通过外部皮肤资源的路径,实现对外部资源的加载实现,后续在需要切换皮肤时,就通过该自定义Resources对象来查找皮肤包资源,从而实现应用的换肤。


六:动态映射技术实现皮肤包资源的查找    

        根据前面对换肤控件的包装实现,在切换皮肤时,各个界面控件都会触发接口函数:applySkin(),从而实现控件的换肤操作,看看皮肤切换的调用代码和查找到皮肤包中的资源的是实现代码的:

解析:通过原皮肤包的资源id作为参数,找到外部皮肤包的资源,上面会通过判断当前项目或界面是否有设置相关的主题颜色或图片资源,还通过加载策略,对特定皮肤资源的获取,如果上面条件都不符合,那么就执行常规换肤的资源的获取函数getTargetResId,下面我们看看具体代码。

解析:看到上面的代码,我们可能会有疑问,为什么需要通过应用资源的id,找到皮肤包资源的id,原因是所有的资源文件都包含三个基本数据:资源Id、资源名字、资源类型,我们在生成外部的皮肤包时,相同的替换资源一般资源名字和资源类型是相同的,但是资源id并不相同(AAPT插件控制),所以我们需要通过动态映射技术,找出原资源包中的资源id与皮肤包中对应的资源id,从而通过查找到的皮肤包的资源id,找到皮肤包的资源。


七:换肤项目Android-skin-support总结

代码解析:

一:界面可换肤的控件和属性的监听实现

1.1、通过接口:SkinLayoutInflater,实现不同控件库的导入,并通过SkinCompatManager链式调用导入列表中。

1.2、需要实现换肤的控件,无论是自定义控件还是系统控件,都需要实现接口:SkinCompatSupportable,通过接口可以

设置控件中需要换肤的属性id。

1.3、使用ActivityLifeCyclerCallback监听每个界面的生命周期,并在每个Activity创建时,执行函数installLayoutFactory(activity);,

通过此函数的设置,可以监听到界面中所有控件的创建,并保存每个界面可以换肤的控件,保存在数组List<WeakReference<SkinCompatSupportable>> mSkinHelpers中。(实现接口SkinCompatSupportable,

并添加进SkinLayoutInflater接口实现类里面的控件)

1.4、上面是控件的换肤监听,加入需要监听界面的状态栏换肤监听,项目中各个Activity实现SkinCompatSupportable接口,从而使的在皮肤切换时界面会触发函数接口函数:applySkin,可以通过该函数设置不同皮肤的状态栏变化。

1.5、在实现换肤功能时,项目中实现了可以换肤的有:控件图片和颜色相关的换肤、状态栏的换肤、界面背景窗口的换肤。

二:皮肤包的加载和获取实现

2.1、项目中可以通过接口:SkinLoaderStrategy,实现不同外部皮肤包的载入,Sdcard、Assets等方式实现。

2.2、这里的重点是要如何通过各个换肤控件的属性id值,获取皮肤包里面id对应的资源,这是需要调用SkinCompatManager的函数getSkinResources实现,原理是自定义区别于系统的Resources对象,实现自有路径资源的加载。

三:换肤时界面的刷新实现

3.1、由于我们上面已经通过List<WeakReference<SkinCompatSupportable>> mSkinHelpers保存了每个界面的换肤控件的实现,并且每个Activity界面也实现了接口SkinCompatSupportable,这样在刷新界面时,通过调通当前界面的所有换肤控件和activity的SkinCompatSupportable接口实现对界面的外部皮肤库的资源,从而实现界面的换肤,这里注意,应用中的其他界面的换肤需要回到前面界面时,在onResume函数中重新刷新最新的皮肤。


项目总结:

        在了解换肤的实现原理时,我们需要先了解:Android资源加载机制剖析 和 应用的布局解析实现,这也是为什么需要去了解,去深入剖析系统源码的原因,只有通过了解系统源码的设计思想,才可以真正的了解作者在实现当前项目时的思路和实现逻辑,才能理解该项目从无到有的过程,所以当我们决定去查看该项目源码时,应该先去了解上面的背景知识。

八:换肤实现思路汇总

方案一:Andrid-Skin-Loader主题切换方案

1、初始化加载APK主题包

2、通用Factory遍历View,保存相关View与属性的信息

3、最后切换主题的时候,重新调用SkinManager来获取相关主题包的资源属性

备注:当前的换肤方案的也是基于此方案思路上优化实现的。


方案二:热更新资源替换方案

   我们知道,系统级的替换主题都是替换的资源ID,那我们思考一下,能不能有一种主题切换方式,我们加载的主题APK资源包,就能替换当前APP默认的资源,我们不用手写代码去填写覆盖?也能及时更新资源?按照热更新的例子,资源是可以替换的,具体原理我们可以查看相关热修复框架的实现,大致原理图如下:

    主要原理依靠反射,通过AssertManager的addAssetPath函数,加入外部的资源路径,然后将Resources的mAssets的字段设为前面的AssertManager,这样在通过getResources去获取资源的时候就可以获取到我们外部的资源了。


方案三:AOP方案切换主题

    做一个钩子,那么我们获取到APK资源的 Resources,替换掉其相关的resource应该就可以达到这样的效果。包括布局XML的,新出来的等等,代理View.OnClick... Window.Callback 相关函数,钩子...

View.AccessibilityDelegate

Android AOP三剑客:APT, AspectJ 和 Javassist

APT应用:Dagger,butterKnife,组件化方案等

AspectJ:主要用于性能监控,日志埋点等

Javassist:热更新(可以在编译后,打包Dex之前干事情,可以突破一下限制)

九:扩展阅读

1、https://github.com/ximsfei/Android-skin-support(项目源码)

2、https://blog.csdn.net/duolaimila/article/details/64907012(android LayoutInflaterCompat解析)

3、https://juejin.cn/post/6844903902060478477(Android 换肤那些事儿, Resource包装流 ?AssetManager替换流?)

4、http://www.360doc.com/content/18/0808/11/31784658_776582382.shtml(Android-skin-support 换肤原理全面解析)

5、https://zhuanlan.zhihu.com/p/147683628(Android 主题换肤技术方案分析)

6、https://blog.csdn.net/weixin_27303545/article/details/117288743(android换肤的实现方案,Android换肤技术总结)

7、https://www.jianshu.com/p/1d0bfbdaab17(Android资源加载机制详解)

8、https://segmentfault.com/a/1190000014016431(Android 资源加载机制剖析)

9、https://www.cnblogs.com/yyangblog/p/6252490.html(Android热更新开源项目Tinker源码解析系列之二:资源文件热更新)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容