DataBinding从基本使用到核心源码解析,看不懂的兄弟跟我一起去摆摊

286 篇文章 10 订阅

DataBinding是谷歌发布的jetpack库里的重要一员,通过使用DataBinding来实现MVVM架构,可以有效避免了MVP架构里新建文件过多的繁杂问题,并且每次数据源更新时不再需要开发人员来调用控件的set方法更新数据了,同时支持双向绑定也能让控件之间互相刷新,可以减少控件之间的监听,从而减缓陷入回调地狱的进度。今天笔者给大家分享一下DataBinding从入门基本使用,到深入源码的一整个过程。如果你从来没用过DataBinding,那么恭喜你看完就是jetpack大佬了。哈哈开个玩笑乐呵乐呵,jetpack深似海,普通人没有个三五年的研究是没办法精通的,这只不过是沧海一粟而已,不过希望看完能对大家有所帮助。

一、导包

二、基本用法

方式1、每次都手动设置数据


步骤一 首先定义好一个普通的JavaBean,这就是控件需要的数据源,不需要做任何的特殊处理。

步骤二 修改布局成databinding布局

进入activity的布局文件,鼠标放到根布局上,然后同时按住alt+enter打开系统提示框。由于我们之前在build.gradle中设置了打开dataBinding,所以当前会出现Convert to data binding layout选项,顾名思义就是转换成dataBinding支持的布局模式,选择该选项即可。

选择好了该模式以后,布局文件大概就会变成这样子。布局最外层给自动加上了layout便签,包裹了我们最初的根布局。其次,系统自动生成了<data></data>和之前的根布局同级。

接下来只需要在data标签里面配置好系统自动生成类的类名,以及<variable>属性即可。其中variable中name有两个作用,第一是使用该数据源,第二个是用来自动生成更新数据时调用的方法名,type指定好前面我们写好的那个bean即可。在控件上使用时只需要通过@{name.field}的方式设置在控件上即可,当然这里以TextView为例,ImageView需要特殊处理,后面会讲到。

经过以上的准备工作,接下来就要开始正式使用了。我这里打开了一个线程,然后每过一秒变化一下数据源,注意每次修改了数据源,最后都需要调用setTestVariable方法才能生效

运行效果如下:

效果符合预期,每过一秒就自动更新数据,而我们并没有对TextView进行setText操作

我们成功地实现了在数据源变化的时候自动设置到ui上面,也仅仅是实现了功能而已,但是这种方法非常地笨拙,每次数据源更新的时候还要给dataBinding重新设置数据源,操作很麻烦,那有没有只需要写一遍就可以初始化数据源,从而一劳永逸的办法呢?答案是有的,接下来,我们来看看方式2。

 

方式2、利用@Bindable注解以及官方的notifyPropertyChanged方法来实现


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,也是最常用的方式。

 

方式3、使用ObservableField的方式


修改Bean即可,将属性包装成ObservableField,该属性的类型改成泛型的形式给出,如下图

在使用时的方式也需要稍加修改,需要先获取到ObservableFiled属性,然后调用其set方法来设置新值。这种方法是不是比前面那2种要好不少呢,既不需要每次赋值的时候给DataBinding赋值,也不需要记得给Bean里每个字段的get方法加上@Bindable,以及set方法上手动加上调用notifyPropertyChanged方法,这种方式也是个人觉得最好用的方式。

三、高级用法

1、数据源是Map或者List时如何使用


如果数据源是存放在Map或者List时,DataBinding提供了相应封装好的类可以直接使用,Map -> ObservableMap,List -> ObservableList,不过这两个都是接口,实例化的时候用ObservableArrayMap和ObservableArrayList即可。

我们来简单尝试一下Map进行存值和使用吧,首先在Bean中添加一个数据源为ObservableMap类型。

这里还需要相应添加一个key,因为java的字符串是不支持单引号和双引号一起使用的,在xml中使用时需要。

接下来就是对map进行赋值了,这里需要注意,map和key需要同时设置,不然在xml中绑定了是拿不到值的。

2.在xml中进行运算符操作


在使用了DataBinding的xml中可以使用简单运算符操作,比如,我们可以用取到的数字源进行操作后再设置到控件上,这里举一个例子,我们在获取的值后面加一个单位。需要注意的是,在这里拼接字符串需要使用的并不是单引号,而是数字1左边的那个键。

除了上面用到的字符串拼接以外,还支持另外一些操作,具体见下图,注意并不是所有的符号都支持。

3.双向绑定


如果希望某个控件跟用户输入的值改变以后,其他和其使用同一属性的控件也跟着同步过去,那么就需要用到双向绑定,具体做法是将@{}改成@={}。如下图,在将EditText的值与数据源双向绑定了以后,当用户在输入框中输入内容以后,下面的TextView内容依然会发生变化。

如果没有使用双向绑定,那么效果是这样的,输入框的修改不会同步到文本框

而双向绑定以后是这样的,输入框不管是删除还是输入,都会把结果同步到文本框上

4、通过BindingAdapter来自定义Setter


有些控件的属性并不是直接显示在控件上的,而是需要经过处理甚至是第三方API的处理才能使用。又或者是想要覆盖系统自身的属性,也可以,总之就是这里定义的对属性的BindingAdapter注解处理方式会覆盖系统的方式。

比如想要将url设置到ImageView上,需要通过Picasso或者Glide来将url下载转成bitmap来设置,这个时候就需要特殊处理。

首先在Bean里新增一个String类型的url字段,然后在ImageView上自定义一个url属性,将其绑定到url字段上。也可以定义多个,这里多定义了一个error属性。

 

最后处理一下实际的将url设置到ImageView上的操作

5.设置数据到列表控件中


以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哈。

让我们来看一下最终的效果,符合预期。

上源码(划重点)

1、DataBinding初始化的时候都做了什么事情


我们在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方法,说明是预留给我们使用的功能,查看方法提示大概是可以用来和日志有关系?

2、setVariable数据更新流程是怎么样的

首先点进去看下,它是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来发送消息的方式。

3、BR是个什么类


和BindingImpl类一样,BR也是系统自动生成的类之一。生成的内容极其简单,以项目中所有Variable标签名以及使用到的Bean中字段名来命名生成好了相关静态属性,相当于是提供好Id用来方便地更新数据,我怀疑就是之前那个Keys数组生成的,因为值一模一样。

4、双向绑定为何不会死循环


我们知道,在双向绑定的时候,数据会自动刷新控件,同时控件内容有变化,那数据也会同步更新。那么问题来了,在控件内容变化的时候,刷新数据,刷新完数据控件内容又变化,控件内容变化又刷新数据。。。这样不是进入了死循环了吗?如果你真这么认为,那你也太小看谷歌工程师了,人家只用了一行代码便解决了这个问题:

看到没,在DataBinding的setText方法中做了处理,如果文本内容不变不算数据有更新,不会设置到控件上,相当于这一趟流程白跑了而已。

总结


本次我们从DataBinding的三种基本使用方法,延伸到一些常见的高级用法。然后从源码的角度剖析了DataBinding的主要流程,其中每一张源码图都是笔者debug一步一步截下来的,保证了流程不会有问题。其次最核心的setVariable()流程总结图也是笔者纵观整个流程,一个一个类的画出来的,笔者写完已头昏眼花,不过只要能对大家使用和理解DataBinding有所帮助的话,不管怎么样就都值了,牺牲自己成就他人,咱IT工程师不都是实在人么。关于标题,看不懂本文的就不劝你转行了,毕竟每行都不容易。不过兼职摆摊确实是不错,有想去摆摊的同学欢迎一起探讨哈!

粉丝裙:

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值