Android进阶 - 源码中的视图转换

android_7.1.png

摘要:

最近,笔者在研究Activity布局加载时,发现自己XML文件里的部分View在实际运行时被转换成了其他View,举个简单的例子,如下图所示:

hierarchy_viewer.png

笔者使用 Hierarchy Viewer 工具分析视图时发现:原activity_main.xml里的TextView被转换成了AppCompatTextView

笔者以此为问题起点,引出本篇文章的主要内容:

  1. 源码是怎样实现视图转换的?
  2. 这种视图转换有什么实际应用场景?

注: 本文源码分析使用的Android版本为 API 25 (Android 7.1)

正文:

一、源码是怎样实现视图转化的?

为帮助读者理解,笔者先简要概括一下源码实现视图转换的原理,再去详细分析源码。

在Android源码中有一个用作视图加工处理的工厂,我们可以称之为视图工厂。这个视图工厂和Android系统提供的“Inflater”做了绑定,我们从XML文件中加载的每一个View都会被这个视图工厂处理。

源码分析开始:

首先从Activity的onCreate()方法的第一行代码super.onCreate()方法开始分析,如下图所示:

main_activity.png

首先,AppCompatActivity(MainActivity的父类)的onCreate()的方法会被先调用,该方法的具体实现如下图所示:

appcompat_activity.png

对应视图转换功能,这里只分析getDelegate()delegate.installViewFactory()方法。

1. getDelegate()

介绍这个方法的原因在于这个方法使用很频繁,我们后续会经常用到。

getDelegate()用于获取AppCompatDelegate的实例,当没有实例时会先去创建,如下图所示:

get_delegate.png

注:AppCompatDelegate是一个抽象类,它有多个版本的实现类,用于兼容各个版本执行代理操作。

AppCompatDelegate.create()方法会根据手机或模拟器的Android版本创建不同的实例,创建过程如下图所示:

appcompat_delegate.png

可以看到笔者sdk 25版本创建的出的实例是AppCompatDelegateImplN对象

补充:
这些不同版本的实现类之间是有继承关系的,用来保证高版本兼容低版本的各种方法。

delegate.png

2. delegate.installViewFactory()

从方法名可以看出这个方法的作用:安装视图工厂

installViewFactory()方法的实现如下图所示:

view_factory.png
  • 可以看到这是 AppCompatDelegateImplV9 类中的一个方法。
  • 可以看到抽象类LayoutInflater的具体实现类是 PhoneLayoutInflate
  • LayoutInflaterCompat.setFactory()是整个方法的 核心

接下来,我们看一下LayoutInflaterCompat.setFactory()方法(如下图所示):

view_factory3.png

稍微解释一下,这个静态方法实际上就是把我们传入的的inflater对象和factory对象做了一个绑定,以后通过这个inflater加载进来的视图都会被factory加工处理。

那么这个LayoutInflaterFactory是什么呢?(参看下图)

layout_inflater_factory.png
  • 原来LayoutInflaterFactory是一个接口,里面有个抽象方法onCreateView(),从注释可以知道这个方法是用来解析XML文件的标签名重新创建View的。

接下来,我们回到AppCompatDelegateImplV9类,看一下LayoutInflaterFactory的具体实现:

layout_inflater_factory2.png

我们找到了LayoutInflaterFactory类抽象方法onCreateView()的具体实现(上图红色圈选)。

之后,继续跟进上图1087行代码createView(parent, name, context, attrs)方法,如下图所示:

layout_inflater_factory2_2.png

最后,跟进上图1029行代码AppCompatViewInlater类的createView()方法,如下图所示:

layout_inflater_factory3.png

TextView是怎样被转成AppCompatTextView的?看到这里基本上就真相大白了。

而且,不止TextView,其他系统控件也做了兼容转换

最后,再回到LayoutInflater.setFactory()这个核心方法说一个没有提及的问题,如下图所示:

view_factory_core.png

问题:我们通过setFactory(layoutInflater, this)方法只是把inflaterfactory做了个绑定,那onCreateView()这个方法是在哪?何时?被谁调用的呢?

其实,这个问题对于有经验的老司机来讲,大方向是能确定的:“一定是inflater在inflate视图的时候掉用了这个方法”。

在说明这个问题前,需要再说一下setFactory()的一个绑定细节:

setFactory(inflater, factory)在绑定视图工厂的过程中,会把这个“factory”赋值给inflater的mFactory属性和mFactory2属性,如下图(324行代码)所示:

inflater_result.png

剩下的就是“inflate的流程”了,整个流程如下图所示:

inflate.png

最后,可以看到inflater是通过mFactory2属性调用了“视图工厂”的onCreateView()方法。

至此,整个流程就缕通了。

二、这种视图转换有什么实际应用场景?

讲使用场景前,先回顾下installViewFactory()这个方法,如下图所示:

view_factory.png

可以看到LayoutInflaterCompat.setFactory()这个方法在执行前进行了一个 if 判断,如果layoutInflater之前没有设置过factory才会执行绑定操作,如果绑定过只会简单的打印个Log日志。这意味着我们可以在installViewFactory()方法调用之前安装自己的视图工厂

场景一:

假如出现这样一种需求,某Android程序员自定义了一个功能很强的TextView - SuperTextView,想要进行全局替换(把所有Activity里每个用到TextView的地方全部替换成SuperTextView),用正常程序员的思维一般是跑到每个XML文件里一个一个查找替换。看完源码的视图转换机制我们可以给出另一种做法:

为Inflater安装自定义视图工厂,如下图所示:

super_text_view.png

视图转换效果如下:

view_change.png

补充下笔者代码的两个注意点:

  1. 笔者代码中使用的new SuperTextView(context, attrs)两个参数的构造方法,其中第二个参数attrs(属性集)是从XML文件里解析出来的,这意味着“转换后的View”要继承自XML中“被转换的View”(要保证属性兼容)
  2. 笔者这种代码写法最低兼容至API 11(Android 3.1)

场景二:

统一的字体替换需求,如果产品给了我们一种新的字体格式,需要我们进行全局替换。

方案一:可以用自定义TextView实现,使用场景一的方法进行替换。

方案二:继续使用原生控件,额外设置属性,代码和效果图如下:

change_ttf.png

注:

比较细心的朋友会发现我们的标题栏字体“My Application”字体没有发生改变,不是说这种设置是全局的吗?

答:笔者有个地方一直没有点透,文章从开始到现在一直在围绕一个点展开,那就是“inflater”“factory”,也就是只有在XML解析的时候才会用到inflaterfactory如果在代码中new一个View是不会走inflate流程的,也不会有视图加工的过程。而Toolbar的标题是在代码中new出来的,如下图所示:

toolbar_source.png

最后,笔者插入一点关于inflater的剧情,有兴趣的朋友可以看下:

inflater剧情:

我们常常使用LayoutInflater.from(Context context)获取inflater的实例,但实际上这个inflater是通过获取系统服务的方式获取的(如下图所示:)

inflater.png

稍微研究过getSystemService()源码的朋友可能都会认为,通过获取系统服务的方式获取的实例对象一定是单例。但笔者在这里强调的一下inflater不是,因为我们通常情况下获取到的inflater是“系统服务生成的inflater”的克隆体

简单看一下原因:

我们调用的LayoutInflater.from(Context context)方法最终会走到ContextThemeWrap类的getSystemService()方法中,如下图所示:

inflater2.png

当我们第一次掉用这个方法,因为当前mInflater还没有实例引用,会继续调用LayoutInflater.from(Context context)方法并传入另一个Context对象继续获取inflater的实例,之后调用cloneInContext(Context context)进行克隆

继续Debug跟进上图167行代码LayoutInflater.from(getBaseContext())方法,如下图所示:

inflater3.png
  • 可以看到我们传入的getBaseContext()是一个 ContextImpl 类的实例。
  • 可以看到获取和最后返回的LayoutInflater实例是 PhoneLayoutInflater@4553

接下来,会调用cloneInContext(Context context)这个克隆方法,如下图所示:

inflater4.png

原来所谓的克隆,就是根据传入的context参数又new了一个PhoneLayoutInflater对象

接下来,我们看下最终生成的inflater实例,如下图所示:


inflater5.png
  • 可以看到mInflater最后引用的实例对象是PhoneLayoutInflater@4564,而前一步通过服务获取的实例对象是PhoneLayoutInflater@4553,这显然不是同一个对象。
  • 还可以注意到最后生成的PhoneLayoutInflater@4564对象的mFactory属性值为null,也就是说此时的inflater还没有绑定LayoutInflaterFactory接口对象。

最后,笔者用一个简单的实验图做一个总结:

inflater6.png

在我们的应用程序中一般系统会生成两个inflater的实例:一个是通过ContextImpl从服务中获取的实例对象;另一个是我们通过"克隆方法"new出来的实例对象。我们"克隆"出来的inflater对象安装了视图工厂(Factory),原始服务中的却没有。

题外话:

分析完视图转换的过程后,发现整个过程是其实是和inflater息息相关的,也就是说受影响的是XML的解析过程,我们在代码里创建视图(比如new TextView)时,是不会有影响的(最后还是TextView)。

这让我想起一件事,和我一起工作的同事喜欢纯代码写布局,不喜欢用XML(应该是IOS写习惯了ㄟ( ▔, ▔ )ㄏ),当时感觉这同事太非主流了,就说道了他两句。

他是这样怼我的“写XML最后不是还会转成代码吗?用代码写布局和XML写布局有什么区别吗?”

当时的我:“......”

不说了,马上开怼,怼回来~O(∩_∩)O~

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

推荐阅读更多精彩内容