本文首发于微信公众号——世界上有意思的事,搬运转载请注明出处,否则将追究版权责任。微信号:a1018998632,交流qq群:859640274
databinding是google去年发布的一个库,它支持在xml中写表达式使得viewModel中的数据能够绑定到view中,目前已经支持双向绑定,也就是说数据的改变能够反馈到界面上,界面的数据也能够主动传到viewModel中。虽然我已经用这个库已经有4个月了,但是它的内在机制我一直没有去探寻,所以本片博客就是来深究databinding的运行机制。
1.了解 DataBindingUtil和DataBinderMapper##
这是一个工具类主要就是帮助我们获取和生成View所对应的ViewDataBinding类。其中的方法分为以下几个种类
- 1.inflate(LayoutInflater inflater, int layoutId,ViewGroup parent, boolean attachToParent)
这个方法是将布局转化成一个ViewDataBinding,inflater和layoutId就不用说了。attachToParent表示是否将layoutId生成的View放入parent中。 - 2.bind(View root)
若我们已经得到了一个view并且其是由有databinding的xml文件生成的,那么我们就可以通过这个方法来获得ViewDataBinding,相当于上面一个方法的简化版。 - 3.findBinding(View view)
若一个view是一个使用过databinding的view的子view,那么我们就可以通过这个函数来寻找到其父view的ViewDataBinding。大家进入源代码中查看会发现,其内部只不过是通过while来不断的遍历当前view的父view来判断该父view是否绑定了ViewDataBinding。注意这个和前面的两个不同,这个方法的ViewDataBinding已经生成,本方法只不过是去找寻找而已 - 4.getBinding(View view)
这个方法和3一样也是去寻找已经生成的ViewDataBinding,不过这里的view就是已经使用过databing的view。 - 5.setContentView(Activity activity, int layoutId)
这个方法是将一个使用了databinding的view绑定到一个activity中去。其源码也很简单,先调用了该activity的setContentView(layoutId)。然后获取activity的decorView中的bodyView(我想了解activity绘制机制的同学应该了解这东西),将其作为该view的parent view。最终调用前面的bind()方法生成ViewDataBinding。 - 6.getDataBinder(DataBindingComponent bindingComponent, View view, int layoutId)
这个方法在自动生成的DataBinderMapper中,前面DataBindingUtil需要生成ViewDataBinding的方法最终都是调用了这个方法。这个方法通过layoutId找到需要生成的具体的ViewDataBinding类,然后调用ViewDataBinding的bind(view, bindingComponent)来获取ViewDataBinding实体。
2.了解ViewDataBinding
首先大家都知道每个使用了databiding的xml文件经过编译之后都会自动生成一个继承于ViewDataBinding的文件,这个类是该xml文件view的管理类。我们可以通过操作这个文件来对xml文件生成的view进行操作。我也介绍几个该类中主要的方法:
- 1.setVariable(int variableId, Object variable)
这个方法是设置我们在xml文件中绑定的viewModel用的,variableId是BR文件中的id,variable是我们要绑定的实体。除此之外,该类中还会生成对应字段的set方法,比如我xml文件中字段名为viewModel,那么其会生成一个setViewModel(MainActivityViewModel viewModel)方法。每一次设置这些字段都会调用父类的requestRebind()方法来异步绑定我们设置的数据。这个我们在后面会进行源码解析。 - 2.executeBindings()
这个方法就是为xml文件中的view设置我们绑定数据的具体方法了,该方法在父类中是抽象方法,具体实现每一个xml对应的类都是不同的。该方法是通过requestRebind()方法被异步在主线程中调用的,所以在实际运用过程中,无论你在什么线程中对被观察的字段赋值(也就是Observable化的字段),都不会报错。 - 3.invalidateAll()
这个方法在父类中也是抽象方法,具体实现就是强行调用requestRebind()来进行view的异步刷新,如果你有个字段没有Observable化,那么在重新赋值之后,可以调用这个方法进行view的刷新。 - 4.addOnRebindCallback(OnRebindCallback listener)
这个方法是设置绑定周期的监听器的方法,也就是说在整个绑定周期中,OnRebindCallback中的方法会被调用。比如说onPreBind(ViewDataBinding binding)会在executeBindings()调用之前被调用。onCanceled(T binding) 会在绑定取消的时候调用。 - 5.executePendingBindings()
这个方法在父类中实现,对executeBindings()之前和之后进行处理,比如说我们4中说的监听器的方法就是在这个类中调用的。 - 6.hasPendingBindings()
这个方法在子类中实现,返回一个boolean数值。其内部是判断是否有Observable化的字段数据被更新。
3.databinding的初始化绑定和数据动态更新机制解析
我们前面了解了databinding的几个重要类的方法,那么现在我们就能通过源码分析很容易的了解到其初始化和数据动态更新的机制了。
- 1.databinding的初始化
- 1.将一个绑定一个view有以下两种方法:第一种方法实际上就是用了第二个方法,所以我们重点解析第一个方法。
- 1.调用DataBindingUtil.setContentView()来设置Activity的视图。
- 2.调用DataBindingUtil.inflate()来设置fragment和listView的item或者RecycleView的item。
- 2.大家可以打开AS跟着我进入源码查看该方法到底是怎么调用的。
- 1.该方法调用了一个重载方法setContentView(activity, layoutId, sDefaultComponent)们,前两个参数不变,最后一个参数是静态实例子,会自动加载。
- 2.再点击进入
- 1.发现第一行代码就是我们不是使用databinding时候,activity初始化view的方式。
- 2.2,3行代码是获取我们xml文件生成的view的父view的父view,大家如果不清楚可以去了解一下activity界面的初始化过程。最后获取到的contentView是一个FrameLayout,其中有一个LinnearLayout这个View就是用来放置我们xml文件生成的view的。
- 3.最后则是调用了这个方法bindToAddedViews(bindingComponent, contentView, 0, layoutId)
- 3.再点击进入
- 1.1,2行我们会得到childrenAdded=1
- 2.进入第一个if之后4行我们获取到了真正的xml生成的view的父view了。
- 3.然后调用了bind(component, childView, layoutId)
- 4.进入之后我们会发现调用了sMapper.getDataBinder(bindingComponent, root, layoutId)。
- 1.sMapper这个实例是在我们编译完成之后生成的。大家可以去项目下面被编译的文件夹中招,我的目录是D:\AndroidStudio\project\TestRecycleView\app\build\intermediates\classes\debug\android\databinding。
- 2.然后大家就可以看见getDataBinder()其实就是一个switch方法,里面会根据layoutId调用对应的ViewDataBinding子类的inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot)。
- 3.进入之后会调用ViewDataBinding对应子类的构造函数。每个xml生成的ViewDataBinding子类的构造函数都是不同的,但是里面一定会调用mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds)方法来获取xml文件中所有使用了databinding的view。
- 5.进入mapBindings()之后先构造了一个Object[]用来存放view。
- 1.然后进入了重载函数,mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true),这个函数很复杂,我们只要知道该函数实例化了xml中所有使用了databinding的view,并将整个xml对应的view添加到了root中,也就是我们从activity中获取的view,此外注意如果xml文件中使用了layout标签,并且也使用了databinding,那么该标签会对应成为一个ViewDataBinding子类,并且将其对应的view添加到当前的view之中,如此。
- 2.返回Object[]给ViewDataBinding子类。
- 6.我们回到ViewDataBinding对应子类的构造函数中。
- 1.后面的几行代码会根据返回的Object[]来设置ViewDataBinding子类的成员view,注意如果有view设置了id,那么其访问权限是public,否则则是private。
- 2.倒数第二行会调用setRootTag(root),给root这个view设置tag,而这个tag就是ViewDataBinding,所以我们有时候可以通过绑定的view来获取ViewDataBinding。
- 3.最后一行会调用invalidateAll(),这个方法我们在前面说过,现在我们来具体讲讲。
- 7.invalidateAll()中先判断是否有字段发生了改变,然后会调用requestRebind()。进入这个方法。
- 8.requestRebind()
- 1.进入之后会先同步mPendingRebind这个变量,在当前的情况下其为false。
- 2.然后进入一个if选择项,当我们的sdk大于16(即android4.1以上)会进入第一个选项。我们就以第一个为例。mChoreographer.postFrameCallback(mFrameCallback)这个函数调用就是异步的开始,所以我们只要吧目光集中在mFrameCallback中就可以了。
- 9.mFrameCallback在ViewDataBinding的构造函数中初始化,注意不是其子类,其仅有一个doFrame(long frameTimeNanos)的方法。该方法调用了mRebindRunnable.run()。
- 10.mRebindRunnable是一个Runnable类,这时候已经运行在了主线程。
- 1.run()中先同步了一下mPendingRebind = false,我们结合前面可以知道,此时是禁止调用requestRebind()的。
- 2.然后是一个if选择,这一段我们不用关心。
- 3.最后一行我们看见了熟悉的函数executePendingBindings(),这个方法我们前面说过是处理executeBindings()前后的函数,我们继续进入。
- 11.进入了之后
- 1.会有一个变量mIsExecutingPendingBindings,此时该变量是false,所以我们跳过。
- 2.然后是hasPendingBindings(),我们前面解释过这个函数,所以这里返回的是true,也跳过。
- 3.然后跳过两个赋值,我们看见了mRebindCallbacks,这个回调就是我们前面说过的监听整个绑定流程的监听器的回调。在executeBindings()调用的前,取消,后。这三个阶段,分别会调用监听器的三个函数。
- 4.最后就是调用executeBindings()进行view的数据刷新了,这个方法的实现是在ViewDataBinding的子类中。大体上就是一些赋值之类和判断之类的操作。这个函数结束了,整个databinding的初始化流程就结束了。
- 12.这里还有一些额外的内容,有一些函数也会requestRebind(),比如说我们前面说的setVariable(),还有就是我们set自定义字段的时候,如我前面说的setViewModel()。
- 13.再加点额外的内容,可能有些同学不清楚如何进入一个方法。快捷键是ctrl-B,然后查找某个方法和字段在那些地方使用过也是这个快捷键。另外可能有些源码中的方法和字段使用ctrl-B查找不到使用过的地方,这时候就可以用ctrl-alt-F7,在全局查找。
- 1.将一个绑定一个view有以下两种方法:第一种方法实际上就是用了第二个方法,所以我们重点解析第一个方法。
- 2.databinding的Observable字段自动更新要实现字段的自动更新有两种办法,我比较亲睐于使用ObservableBoolean和ObservableField等封装类来实现,这样代码的侵入性不强。那么我们就来像刚刚一样一步步深入源码了解自动更新的机制。
- 1.我们调用某个Observable实例的set方法,进入查看。发现其调用了父类BaseObservable的notifyChange()。再进入
- 2.发现调用了mCallbacks.notifyCallbacks()方法,该回调是在databinding初始化时executeBindings()中的updateRegistration()中设置的。
- 3.我们再进入,发现notifyCallbacks()中非常复杂,所以我就直接跳到大家能看的明白的层次中。这个方法最终会进入到ViewDataBinding的内部类WeakPropertyListener的onPropertyChanged(Observable sender, int propertyId)方法中。
- 4.WeakPropertyListener是一个弱引用监听器
- 1.第一行先获取了Observabel实例所在的ViewDataBinding。
- 2.然后调用了ViewDataBinding的handleFieldChange(mListener.mLocalFieldId, sender, propertyId)
- 5.进入这个方法中大家可以看见熟悉的requestRebind(),接下来的流程就和我们前面讲的databinding的初始化流程的后半段一样了。
- 6.还有点额外的内容,就是我们前面说的只是某个字段的动态更新机制,但是其实map和List的机制也都差不多,我们可以在ViewDataBinding找到List对应的内部类WeakListListener和map对应的内部类WeakMapListener其实都是一样的。
以上就是这篇博客的全部内,databinding也用了几个月了,也写用它为基础基于MVVM写了两个商业项目+一个比赛的app,对MVVM架构下的app开发,还是颇有心得的,所以过一阵子我会给大家分享一篇MVVM+databinding+RxJava2+Retrofit2的项目架构博客。在此之前的会写一些项目架构中库的解析,让大家知其然更知其所以然,今天的这篇博客就是开篇。希望大家能多多关注!
不贩卖焦虑,也不标题党。分享一些这个世界上有意思的事情。题材包括且不限于:科幻、科学、科技、互联网、程序员、计算机编程。下面是我的微信公众号:世界上有意思的事,干货多多等你来看。