专栏名称: 郭霖
Android技术分享平台,每天都有优质技术文章推送。你还可以向公众号投稿,将自己总结的技术心得分享给大家。
目录
相关文章推荐
stormzhang  ·  生活真的经不起考验 ·  17 小时前  
stormzhang  ·  被裁员就是员工的错了? ·  昨天  
鸿洋  ·  Android 复杂项目崩溃率收敛至0.01%实践 ·  3 天前  
stormzhang  ·  特斯拉画的饼,圆上了? ·  3 天前  
鸿洋  ·  PackageManagerService和 ... ·  1 周前  
51好读  ›  专栏  ›  郭霖

DataBinding实现原理探析

郭霖  · 公众号  · android  · 2016-11-02 08:00

正文

今日科技快讯

最近,优酷的创始人兼CEO古永锵,宣布不再担任优酷土豆的董事长兼CEO身份,视频行业的最后一位创业老兵也正式退出了这个行业的舞台。古永锵曾经是搜狐的首席运营官,2005年从搜狐辞职创办了优酷网,之后优酷发展迅速,成为了视频行业的老大。但视频行业的所有公司没有一家是盈利的,即时后来合并了土豆网之后,古永锵也没能扭转优酷亏损的局面。去年阿里巴巴收购优酷土豆,其实已经隐隐暗示了古永锵的出局,这个行业他已经拼不动了。

作者简介

本篇是 milter 的第三篇投稿,给大家分析了DataBinding的核心原理。希望对大家有所帮助。另外作者博客里原理分析的文章很多,感兴趣的朋友可以看看。

milter 的博客地址:

http://www.jianshu.com/users/511ba5d71aef

引言

关于 DataBinding 技术,网上教程可谓多矣,但大都没能摆脱简单拷贝、翻译修改官方文档的嫌疑,个别的翻译的还不准确,初学者很容易被误导。鉴于此,本文结合本人项目实践中的经验与思考,为广大Android 开发者提供一篇有观点、有思考的 DataBinding 讲解文章。

DataBinding解决什么问题

DataBinding 技术的出现,肯定是为了解决我们在开发中的一些痛点问题。所以,了解 DataBinding 要解决的问题,能够使我们更深刻地理解 DataBinding 技术的设计实现。

从开发角度看,简言之,DataBinding 主要解决了两个问题:

  • 需要多次使用 findViewById,损害了应用性能且令人厌烦

  • 更新UI数据需切换至UI线程,将数据分解映射到各个view比较麻烦

应该说,针对上述问题,都有第三方解决方案。第一个问题可以使用 Jake WhartonButterKnife;对于第二个问题,谷歌提供了 Loop-Handler 方案,你还可以使用 RxJavaEventBus 等方案,但它们只是解决了线程切换的问题,却没有解决将数据分解映射到各个view的问题,这正是 DataBinding 的魅力所在!

同时,DataBinding 的线程切换也是透明的,这是指,当你的 Activity 需要展示新的数据时,你可以在后台线程中获取数据,然后直接交给 DataBinding 就可以了,完全不需要关心线程切换的问题。

如何解决这些问题

总体思路

DataBinding 解决这些问题的思路非常简单。就是针对 每个Activity 的布局,在编译阶段,生成一个 ViewDataBinding 类的对象,该对象持有 Activity 要展示的数据和布局中的各个view的引用(这里已经解决了令人厌烦的findViewById问题)。同时该对象还有如下可喜的功能:

  • 将数据分解到各个view

  • 在UI线程上更新数据

  • 监控数据的变化,实时更新

有了这些功能,你会感觉到,你要展示的数据已经和展示它的布局紧紧绑定在了一起,这就是该技术叫做 DataBinding 的原因。

实现细节

下面,我们深入 DataBinding 的内部,看看它是如何实现以上所说的功能的。如何设置使用 DataBinding 在此就不赘述了,网上大把大把的资料。示范项目基本情况:

  • 项目名称为 DataBindingTest

  • 项目包名 com.like4hub.www.databindingtest

  • 项目只有一个主Activity,名称为MainActivity,其布局文件为activity_main.xml

  • 项目用到的图片资源有两个,如下:

avatar_pure.jpg

avatar_sexy.jpg

  • 项目中要展示的数据是User, 其代码如下:


有了以上的准备工作,我们可以开始了。首先创建如下一个布局:


我们看到,使用 DataBinding 需要遵照一定的模板去写布局文件,这个模板如下:


我们的 Activity onCreate() 方法是这样的:


然后运行我们的程序,结果如下:

那么问题来了,DataBinding究竟在背后做了什么?下面,我们就分步骤进行讲解。

DataBinding在背后做什么

对布局文件进行预处理

首先,DataBinding 会对根元素为 的布局文件进行预处理(本例中即activity_main.xml),处理后,原布局文件会变成这个样子:


我们看到,根元素 LinearLayout 和那些在属性中使用了 binding表达式 的view都被设置了 Tag,而原有的 标签data标签 以及里面的 variable标签,还有各个view中的 binding表达式 都不见了!!

DataBinding 将它们藏在哪儿了呢?答案是:DataBinding把最初布局文件中的以及各个view中的 binding表达式 内容抽取出来,生成了一个名为 activtiy_main-layout.xml 文件,该文件 主要内容 如下:


通过给原有布局文件中的view设置Tag和在生成的文件中(本例中即 activtiy_main-layout.xml)使用Tag,使得抽取出来的内容能够与其原先所在的位置对应起来。如下图所示:

这里有几点需要注意:

  • LinearLayout 设置的Tag 是 以layout开头的,表示它是根布局。

  • 最初布局文件标签中的内容几乎原封不动的挪到了新生成的文件中。

生成ActivityMainBinding与BR类

现在,DataBinding 将会依据上面两个xml文件(即 activtiy_main.xml 和 activtiy_main-layout.xml)生成两个类,一个类是 ActivityMainBinding,它继承自 ViewDataBinding,里面包含如下 fields


观察这些fields,我们可以发现:

  • 对应每个 variable标签ActivityMainBinding 都有一个相应的变量,在本例中就是上面的 mUser 变量。

  • 对应每一个有id的View,都会有一个以其id为名的 public final变量,其类型正是该View的类型(如button,firstname)。

  • 对应每一个没有id但是处理中添加了Tag 的View,都会有一个 private final的变量 与其对应,名字没有什么特殊的含义(如mboundView0,mboundView3)。

生成的BR类的内容非常简单,如下:

package com.like4hub.www.databindingtest;
public class BR {       public static final int _all = 0;       public static final int user = 1; }

其中的 _all变量 是默认生成的,user变量 是对应 ActivityMainBinding中的 mUser变量的。举例来讲,假如我们有一个 ActivityMainBinding类 的实例对象 amb,我们可以调用 amb.setVariable(BR.user, userInstance),该调用将会把 userInstance 赋值给 amb的mUser变量。下面是 setVariable 方法的代码:

那么,DataBinding 是否仅仅只给 标签 中的每一个 variable 生成对应的BR常量,答案是:NO

如果你在 User类 中的 getAvatar 方法上添加 @Bindable注解,并且让 User类 继承 BaserObservable 那么,DataBinding 生成的 BR类 中将会是这样:

public class BR {        
    public static final int _all = 0;    
    public static final int avatar = 1;      
    public static final int user = 2;
}

实际上,BR中的常量是一种标识符,它对应一个会发生变化的数据,当数据改变后,你可以用该标识符通知 DataBinding,很快,DataBinding 就会用新的数据去更新UI。

那么,DataBinding 如何知道哪些数据会变化呢?目前,我们可以确定,中的每一个 variable 是会变化的,所以 DataBinding 会为它们生成 BR标识符。用 @Bindable 注解 的类中的 getXXX 方法(该类父类为 BaseObservable 或者实现 Observable接口)对应一个会变化的数据,DataBinding 也会为它们生成 BR标识符。实际上,还有第三种,暂且按下不表。

生成ActivityMainBinding实例并绑定

在这一步中,主要有三个过程:

第一步 就是 Inflate 处理后的布局文件,由于现在 activity_main.xml文件 与 普通的layout文件一样。现在 DataBindingUtil 将会 Inflate  activity_main.xml文件,得到一个ViewGroup变量root。

第二步 就是生成 ActivityMainBinding实例对象DataBindingUtil 会将这个 变量root 传递给 ActivityMainBinding的构造方法,生成一个 ActivityMainBinding的实例,就是我们在 onCreate方法 中获取的 binding对象。下面看看 ActivityMainBinding 的构造过程,它的构造方法签名如下:

public ActivityMainBinding(android.databinding.DataBindingComponent bindingComponent, View root)

其中第二个参数就是刚刚生成的ViewGroup root。你可能想知道第一个参数 bindingComponent 哪来的,简单一句话,是从 DataBindingUtil getDefaultComponent 调用中得来的。如果你之前学习过DataBinding,并且使用过BindingAdapter的话,你应该会比较熟悉它,这里不展开讲。

好,让我们继续构造我们的 ActivityMainBinding对象。在构造方法中,ActivityMainBinding会首先遍历root,根据各个View的Tag或者id,初始化自己的fields,就是下面这些:


至此,Tag 们的历史使命完成了,ActivityMainBinding将会把之前加到各个View上的Tags清空。

最后,构造方法调用 invalidateAll 引发数据绑定。

第三步 就是进行数据绑定。

在这一步中,ActivityMainBinding 将会计算 各个view上的 binding表达式,然后赋值给view相应的属性。绑定的主要代码如下(省略部分细节):


下面我们来分析上面的数据绑定过程。首先,针对两个binding表达式 user.firstnameuser.lastname ,ActivityMainBinding 生成了两个临时变量,即:

java.lang.String firstNameUser = null;

java.lang.String lastNameUser = null;

从中我们可以看出这两个变量的命名的规律。这两个变量就代表了两个binding表达式的值,为它们赋值的过程实际上就是binding表达式求值的过程。

ActivityMainBinding 通过调用 mUser 的 getFirstName getLastName 方法为上面两个变量赋值。

请思考,ActivityMainBinding是怎么知道调用mUser的getXXX方法为binding表达式求值的?

这个问题可以分成两步:

首先,在构建ActivityMainBinding类时,会对activtiy_main-layout.xml中的数据进行分析,我们再次贴出该文件的内容,以便继续:

DataBinding 发现,有一个 variable 名为 user,所以它为 ActivityMainBinding 生成了一个 mUser变量,DataBinding 进一步检查该文件发现,两个binding表达式 user.firstName user.lastName 圆点前面的字符串也是 user,由此知道,这两个表达式的值来自 mUser

接着,DataBinding 再次进行分析,两个binding表达式圆点后的字符串分别是 firstName lastName,所以 DataBinding 决定调用 mUser getFirstName getLastName方法。

请注意,让User类中包含这两个方法是我们开发者的责任。求出值之后就是设置了,比较简单。

在这里我们可以清楚地看到,binding表达式 user.firstName 和 user.lastName 并不是对应着User类中的两个fields,它们实际对应的是User类的两个get方法。

至此,你可以大胆猜测一下,如果我们给User类添加一个如下方法:

public String getAlias(){
   return "Alias"; }

但是我们并不给它添加一个String 类型的alias field,我们是否可以在binding表达式中这样写:@{user.alias}

答案是:YES YOU CAN!

进一步你可以理解,上文中,我们为什么要将 @Bindable注解 加到一个get方法上面而不是一个field上面了。

最后,由于 ImagView 中的 binding表达式 本身就是一个值,我们不需要再求值了,直接赋值就是。本文这样做,仅仅是为了说明,DataBinding 为View添加tag的规则是该View的属性中有没有使用binding表达式。

好了,至此,我们分析DataBinding工作的核心原理,还有三个内容没有涉及,一个是数据更新(仅略提了一下),另一个是BindingAdapter(其实在executeBindings方法中已经看到它们的身影了),最后一个是事件监听绑定(这个很简单)。其实,掌握了这些核心原理,剩下的内容你可以很轻松地掌握。

更多

每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都会有好心情。

如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。

欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号: