最近,优酷的创始人兼CEO古永锵,宣布不再担任优酷土豆的董事长兼CEO身份,视频行业的最后一位创业老兵也正式退出了这个行业的舞台。古永锵曾经是搜狐的首席运营官,2005年从搜狐辞职创办了优酷网,之后优酷发展迅速,成为了视频行业的老大。但视频行业的所有公司没有一家是盈利的,即时后来合并了土豆网之后,古永锵也没能扭转优酷亏损的局面。去年阿里巴巴收购优酷土豆,其实已经隐隐暗示了古永锵的出局,这个行业他已经拼不动了。
本篇是 milter 的第三篇投稿,给大家分析了DataBinding的核心原理。希望对大家有所帮助。另外作者博客里原理分析的文章很多,感兴趣的朋友可以看看。
milter 的博客地址:
http://www.jianshu.com/users/511ba5d71aef
关于 DataBinding 技术,网上教程可谓多矣,但大都没能摆脱简单拷贝、翻译修改官方文档的嫌疑,个别的翻译的还不准确,初学者很容易被误导。鉴于此,本文结合本人项目实践中的经验与思考,为广大Android 开发者提供一篇有观点、有思考的 DataBinding 讲解文章。
DataBinding 技术的出现,肯定是为了解决我们在开发中的一些痛点问题。所以,了解 DataBinding 要解决的问题,能够使我们更深刻地理解 DataBinding 技术的设计实现。
从开发角度看,简言之,DataBinding 主要解决了两个问题:
应该说,针对上述问题,都有第三方解决方案。第一个问题可以使用 Jake Wharton 的 ButterKnife;对于第二个问题,谷歌提供了 Loop-Handler 方案,你还可以使用 RxJava,EventBus 等方案,但它们只是解决了线程切换的问题,却没有解决将数据分解映射到各个view的问题,这正是 DataBinding 的魅力所在!
同时,DataBinding 的线程切换也是透明的,这是指,当你的 Activity 需要展示新的数据时,你可以在后台线程中获取数据,然后直接交给 DataBinding 就可以了,完全不需要关心线程切换的问题。
总体思路
DataBinding 解决这些问题的思路非常简单。就是针对 每个Activity 的布局,在编译阶段,生成一个 ViewDataBinding 类的对象,该对象持有 Activity 要展示的数据和布局中的各个view的引用(这里已经解决了令人厌烦的findViewById问题)。同时该对象还有如下可喜的功能:
将数据分解到各个view
在UI线程上更新数据
监控数据的变化,实时更新
有了这些功能,你会感觉到,你要展示的数据已经和展示它的布局紧紧绑定在了一起,这就是该技术叫做 DataBinding 的原因。
实现细节
下面,我们深入 DataBinding 的内部,看看它是如何实现以上所说的功能的。如何设置使用 DataBinding 在此就不赘述了,网上大把大把的资料。示范项目基本情况:
avatar_pure.jpg
avatar_sexy.jpg
有了以上的准备工作,我们可以开始了。首先创建如下一个布局:
我们看到,使用 DataBinding 需要遵照一定的模板去写布局文件,这个模板如下:
我们的 Activity onCreate() 方法是这样的:
然后运行我们的程序,结果如下:
那么问题来了,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,使得抽取出来的内容能够与其原先所在的位置对应起来。如下图所示:
这里有几点需要注意:
生成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.firstname 和 user.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方法中已经看到它们的身影了),最后一个是事件监听绑定(这个很简单)。其实,掌握了这些核心原理,剩下的内容你可以很轻松地掌握。
每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都会有好心情。
如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。
欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号: