DataBinding是谷歌发布的jetpack库里的重要一员,经过使用DataBinding来实现MVVM架构,能够有效避免了MVP架构里新建文件过多的繁杂问题,而且每次数据源更新时再也不须要开发人员来调用控件的set方法更新数据了,同时支持双向绑定也能让控件之间互相刷新,能够减小控件之间的监听,从而减缓陷入回调地狱的进度。今天笔者给你们分享一下DataBinding从入门基本使用,到深刻源码的一整个过程。若是你历来没用过DataBinding,那么恭喜你看完就是jetpack大佬了。哈哈开个玩笑乐呵乐呵,jetpack深似海,普通人没有个三五年的研究是没办法精通的,这只不过是沧海一粟而已,不过但愿看完能对你们有所帮助。java
步骤一 首先定义好一个普通的JavaBean,这就是控件须要的数据源,不须要作任何的特殊处理。数据库
步骤二 修改布局成databinding布局数组
进入activity的布局文件,鼠标放到根布局上,而后同时按住alt+enter打开系统提示框。因为咱们以前在build.gradle中设置了打开dataBinding,因此当前会出现Convert to data binding layout选项,顾名思义就是转换成dataBinding支持的布局模式,选择该选项便可。安全
选择好了该模式之后,布局文件大概就会变成这样子。布局最外层给自动加上了layout便签,包裹了咱们最初的根布局。其次,系统自动生成了<data></data>和以前的根布局同级。bash
接下来只须要在data标签里面配置好系统自动生成类的类名,以及<variable>属性便可。其中variable中name有两个做用,第一是使用该数据源,第二个是用来自动生成更新数据时调用的方法名,type指定好前面咱们写好的那个bean便可。在控件上使用时只须要经过@{name.field}的方式设置在控件上便可,固然这里以TextView为例,ImageView须要特殊处理,后面会讲到。架构
通过以上的准备工做,接下来就要开始正式使用了。我这里打开了一个线程,而后每过一秒变化一下数据源,注意每次修改了数据源,最后都须要调用setTestVariable方法才能生效app
运行效果以下:异步
效果符合预期,每过一秒就自动更新数据,而咱们并无对TextView进行setText操做ide
咱们成功地实现了在数据源变化的时候自动设置到ui上面,也仅仅是实现了功能而已,可是这种方法很是地笨拙,每次数据源更新的时候还要给dataBinding从新设置数据源,操做很麻烦,那有没有只须要写一遍就能够初始化数据源,从而一劳永逸的办法呢?答案是有的,接下来,咱们来看看方式2。布局
xml文件不须要变,按照方式1的写法就能够了,只须要在Bean的字段的get方法上面加上@Bindable注解,而后在字段的set方法里调用一下notifyPropertyChanged方法便可,须要注意的是,必需要继承BaseObservable才会有该方法。其中@Bindable是告诉DataBinding在能够调用getName方法来获取name的值,notifyPropertyChanged方法是告知DataBinding当前数据已修改,快去调用get方法获取最新数据吧!因此不用从新设置数据源的缘由你们也都看出来了,其实就是在给Bean设值的时候通知了DataBinding,而后DataBind自动去更新了。
接下来在使用时,就不须要从新给DataBinding设置数据源了
当时这种方式也有缺陷,就是Bean里面每一个字段的set和get方法都须要进行修改,加上@Bindable和notifyPropertyChanged方法,不然将会自动更新失败。只要是须要人工修改的地方,那么在实际业务中不可避免地可能出现问题。那么有没有容错性更强的方式呢,固然是有的,下面说下方式3,也是最经常使用的方式。
修改Bean便可,将属性包装成ObservableField,该属性的类型改为泛型的形式给出,以下图
在使用时的方式也须要稍加修改,须要先获取到ObservableFiled属性,而后调用其set方法来设置新值。这种方法是否是比前面那2种要好很多呢,既不须要每次赋值的时候给DataBinding赋值,也不须要记得给Bean里每一个字段的get方法加上@Bindable,以及set方法上手动加上调用notifyPropertyChanged方法,这种方式也是我的以为最好用的方式。
若是数据源是存放在Map或者List时,DataBinding提供了相应封装好的类能够直接使用,Map -> ObservableMap,List -> ObservableList,不过这两个都是接口,实例化的时候用ObservableArrayMap和ObservableArrayList便可。
咱们来简单尝试一下Map进行存值和使用吧,首先在Bean中添加一个数据源为ObservableMap类型。
这里还须要相应添加一个key,由于java的字符串是不支持单引号和双引号一块儿使用的,在xml中使用时须要。
接下来就是对map进行赋值了,这里须要注意,map和key须要同时设置,否则在xml中绑定了是拿不到值的。
在使用了DataBinding的xml中可使用简单运算符操做,好比,咱们能够用取到的数字源进行操做后再设置到控件上,这里举一个例子,咱们在获取的值后面加一个单位。须要注意的是,在这里拼接字符串须要使用的并非单引号,而是数字1左边的那个键。
除了上面用到的字符串拼接之外,还支持另一些操做,具体见下图,注意并非全部的符号都支持。
若是但愿某个控件跟用户输入的值改变之后,其余和其使用同一属性的控件也跟着同步过去,那么就须要用到双向绑定,具体作法是将@{}改为@={}。以下图,在将EditText的值与数据源双向绑定了之后,当用户在输入框中输入内容之后,下面的TextView内容依然会发生变化。
若是没有使用双向绑定,那么效果是这样的,输入框的修改不会同步到文本框
而双向绑定之后是这样的,输入框无论是删除仍是输入,都会把结果同步到文本框上
有些控件的属性并非直接显示在控件上的,而是须要通过处理甚至是第三方API的处理才能使用。又或者是想要覆盖系统自身的属性,也能够,总之就是这里定义的对属性的BindingAdapter注解处理方式会覆盖系统的方式。
好比想要将url设置到ImageView上,须要经过Picasso或者Glide来将url下载转成bitmap来设置,这个时候就须要特殊处理。
首先在Bean里新增一个String类型的url字段,而后在ImageView上自定义一个url属性,将其绑定到url字段上。也能够定义多个,这里多定义了一个error属性。
最后处理一下实际的将url设置到ImageView上的操做
以RV为例,接下来讲一下如何将数据设置到列表控件中,这也是很是常见的操做了。因为列表控件是有Adapter的,至关于设置数据时多传了一层。
这是一个并无使用DataBinding的简单RecyclerView示例,里面有3种不一样布局,固然这里代码写得很不规范,由于这个不是重点。
运行之后大体的效果以下,为了区分三种不一样的Item,我将每一个Item显示的TextView个数进行了区分,方便你们看懂这是不一样的item。
接下来咱们要作让它绑定到DataBinding,实现当数据源变化时不调用nofigyDataSetChanged方法来实现Item数据发生变化。
使用步骤参照以前基本使用来便可,第一步新建一个Bean做为DataBinding获取数据库的地方。第二步将那3个item都变成layout包裹的DataBinding布局。
第三步是将当前布局交给DataBinding进行托管
第四步是须要修改一个ViewHolder的实现,因为以前是将View托管到了ViewDataBinding中,因此须要View的时候能够从托管平台DataBinding来获取。这里保存下binding是为了在onBindViewHolder中绑定数据用,要记住如今须要数据都是从ViewDataBiding中获取了,而不能直接从values数组里去获取,否则数据修改了是刷新不了的。这里实现一个接口,是为了偷懒,在onBindViewHolder中统一处理。
最后在onBindViewHolder中调用系统的setVariable方法对DataBinding进行赋值,是否是节省了不少代码哈哈。
最后,验证一下数据修改时,会自动刷新到rv上吧,我这里开了个线程,每秒修改一下rv上的数据源,注意我只是修改了数据源并无notifyDataSetChanged哈。
让咱们来看一下最终的效果,符合预期。
咱们在Activity中使用DataBinding的时候,须要进行初始化,代码大体是这么写的:
DataBindingUtil.setContentView(this, R.layout.activity_main)复制代码
咱们点进去看下都作了什么操做,能够看到很简单,首先调用了Activity的setContentView方法,这个是不使用DataBinding的时候也须要调用的,其次是调用了bindToAddedViews方法,调用的时候将最外层的Framlayout容器以及布局的LayoutId给传了进去。
在bindToAddedViews方法中,通过一个抽象类的中转,最终是调用到了DataBinderMapperImpl类,该类的具体代码以下:
ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
switch(layoutId){
case LAYOUT_AAA:
return new AaaBindingImpl(component, view);
case LAYOUT_BBB:
return new BbbBindindImpl(component, view)
}
}复制代码
这段代码十分地简单,就是初始化了相应的DataBinding类,没什么好说的,还有就是保存好了全部的布局文件id、Variable名、到一个map中,一直findUseage你会发现最终是在DataBindingUtil类中的convertByIdToString方法使用,该方法是暴露给开发人员的public方法,说明是预留给咱们使用的功能,查看方法提示大概是能够用来和日志有关系?
首先点进去看下,它是ViewDataBinding该抽象类中的抽象方法。ViewDataBinding类是系统生成的抽象类
接下来咱们看看它的实现类有哪些,这很明显是每一个DataBinding的布局文件生成了一个,命名是以布局文件的首字母大写而后驼峰的方式,最后拼上了BindingImpl,固然这个名字是能够自定义的,注意看第二个实现类名不同,这是由于咱们在布局文件中
<data class = "DataBindingTest">自定义了其名字。
接下来咱们随便点进去一个类吧,查看一下其setVariable方法
发现是调用了setItem1Variable方法,这个方法固然也是自动生成的,是根据variable的name属性生成的。咱们能够看到这里首先将数据传给了根据name生成的属性,这就是为了保存起来给后面使用的。
接下来看看从调用notifyPropertyChanged()到最终数据通知到View上的整个流程吧,otifyPropertyChanged方法一直点下去会执行到如下代码:
在这里mNotifier是在setVariable的时候初始化的,具体贴一个堆栈就一目了然了
在这里callback也是在CallbackRegistry类中添加的,具体也是执行setVariable方法的时候,同时也贴一个堆栈就一目了然了。固然这也是最少要执行一次setVariable的缘由之一,若是不执行连回调都没有初始化固然没办法通知。
明白了mNotifier和callback这两个关键对象的初始化时机,接下来再看看具体是怎么通知的吧。前面看到在notifyPropertyChanged()方法执行时会调用到
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);复制代码
执行这行代码就至关于执行到了
继续往下面看
发送了一个handler,那handler接收之后作了什么操做呢?
handler执行到的方法是mRebindRunnable.run()方法。
接下来咱们看看这个Runnable的具体实现,其定义就在当前类里,具体以下:
里面核心的方法是这个executePendingBindings(),而后是走到了executeBindings()方法
executeBindings()是一个抽象方法,接下来咱们看下它的实现,正是系统给咱们生成的BindingImpl类,是否是颇有亲切感呢!
接下来就简单了,咱们看下实现类里究竟作了什么呢?
其实就是调用了一下DataBinding里封装好的TextView.setText()方法而已,至此就能通了。
最后来总结一下大体流程,为了让你们更好地看懂,我这里手动画了2个图,分别是callback注册流程,以及数据刷新流程
callback注册流程:
数据刷新流程:
那么看完有同窗就要问了,为何不直接更新呢,为啥要经过Handler绕这么大个圈子?我我的以为可能跟实际作起来要考虑的因素不少有关,包括当前线程未知等缘由,下面也列举一下我找到的相关因素代码吧,不得不佩服谷歌工程师代码的健壮性真的是强大。不过要是安卓源码出问题,那没人能评估出损失,严谨一点固然是好。
部分健壮性处理代码以下:
1.须要保证当前View已经被加载才能调用
2.须要兼容API<16的状况
3.须要保证DataBinding已经在UI线程初始化,若是是子线程中则没有更新UI的能力
4.须要保证异步安全,因此在requestBind()时进行了加锁
综合各方面来看,因为各类限制比较复杂,并且还存在可能须要切换线程的状况,因此这里使用了Handler来发送消息的方式。
和BindingImpl类同样,BR也是系统自动生成的类之一。生成的内容极其简单,以项目中全部Variable标签名以及使用到的Bean中字段名来命名生成好了相关静态属性,至关因而提供好Id用来方便地更新数据,我怀疑就是以前那个Keys数组生成的,由于值如出一辙。
咱们知道,在双向绑定的时候,数据会自动刷新控件,同时控件内容有变化,那数据也会同步更新。那么问题来了,在控件内容变化的时候,刷新数据,刷新完数据控件内容又变化,控件内容变化又刷新数据。。。这样不是进入了死循环了吗?若是你真这么认为,那你也过小看谷歌工程师了,人家只用了一行代码便解决了这个问题:
看到没,在DataBinding的setText方法中作了处理,若是文本内容不变不算数据有更新,不会设置到控件上,至关于这一趟流程白跑了而已。
本次咱们从DataBinding的三种基本使用方法,延伸到一些常见的高级用法。而后从源码的角度剖析了DataBinding的主要流程,其中每一张源码图都是笔者debug一步一步截下来的,保证了流程不会有问题。其次最核心的setVariable()流程总结图也是笔者纵观整个流程,一个一个类的画出来的,笔者写完已头昏眼花,不过只要能对你们使用和理解DataBinding有所帮助的话,无论怎么样就都值了,牺牲本身成就他人,咱IT工程师不都是实在人么。关于标题,看不懂本文的就不劝你转行了,毕竟每行都不容易。不过兼职摆摊确实是不错,有想去摆摊的同窗欢迎一块儿探讨哈!