作为Google官方发布的支持库,DataBinding实现了UI组件和数据源的双向绑定,同时在Jetpack组件中,也将DataBinding放在了Architecture类型之中。对于DataBinding的基础使用请先翻阅前两篇文章的详细阐述。本文所用代码也是建立在之前工程基础之上。
初始化分析
按照官方文档所说,Databinding在编译期会生成代码,利用的技术是Apt(annotation-processing-tool)。在运行完工程后,可以看到build文件夹下生成多个文件夹和文件,看到了这里,就可以明白其核心原理肯定跟注解处理器有关系,其实所有通过APT生成代码的框架(比如ButterKnife,dagger2,hit等),大多数情况下其核心逻辑的实现都在生成代码中,可以说其完全就是通过注解处理器产生的,因此需要我们重点翻阅的都是生成的代码。在我们按照规则写完布局文件后,会生成相应的.java文件,文件名为xml文件名加上Binding后缀。原工程则生成是的ActivityMainBinding.java文件,这点不难理解。
从XML开始
让我们关注build目录下的intermediates目录,你会发现,相较于其他没有使用Data Binding的工程,这里多了几个目录:
不难猜出,这是DataBinding特有的目录风格。我们看到最后一个文件夹,展开会发现:
这里的activity_main-layout.xml像极了我们activity所使用布局。点开会发现里面充满了各种标签。(如果一开始里面代码只有一行,则可以使用快捷键 Windows : ctrl+Alt + L调整代码格式)
先列出原布局文件:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.example.dbjavatest.bean.DataBean"/>
<variable
name="dataInfoBean"
type="DataBean" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="26sp"
android:textColor="@color/purple_200"
android:text="@{dataInfoBean.dataInfo}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="50dp"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入数据"
android:text="@={dataInfoBean.dataInfo}"
app:layout_constraintTop_toBottomOf="@+id/tv_data"
android:textSize="25sp"
android:layout_marginTop="30dp"
android:paddingLeft="20dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
可以看出这里面总共三个控件,对应activity_main-layout.xml中有三个< target>节点。
节点里view属性名称则刚好可以是我们布局中所用控件,这里就是我们在布局中声明的控件无疑。在这些< target>信息中,还可看到所赋予的tag信息(binding_1、binding_2)。而在Expression标签中,text对应属性值里有“dataInfoBean.dataInfo”这就刚好对应上数据来源。当然,仅仅靠这个文本,是不可能生成一个能绘制出来的效果。这时还要观察另外一个文件。
在intermediates/incremental目录下,相应的有效文件夹为mergeDebugResources\stripped.dir\layout 下,对应的文件名即为布局文件名:
点开后你会发现,相较于原始的activity_main.xml布局文件,< layout>和< data>标签都消失了:
且内部控件都多了一个属性,android:tag属性。此tag属性所赋id则跟build/intermediates/data_binding_layout_info_type_package/debug/out/activity_main-layout.xml中的标签里的tag相对应。不难猜出,其xml具体显示流程与这俩文件紧密相关。
接下来就要去看整体的流程了。
初始化流程
要分析整体流程还得从MainActivity入手,DataBindingUtil.setContentView(this, R.layout.activity_main);就是完成XML布局的初始化操作:
点进去查看其源码:
可以发现这里通过获取Window的decorView,来把传进来的layoutId(即我们的布局)通过bindToAddedViews()绑定到屏幕视图上。这里的contentView和其id.content是工程创建时Activity就自带的默认FrameLayout,对这个不了解的要去了解下屏幕渲染机制,在这里不需要纠结。
继续观察bindToAddedViews()内部对于绑定的过程实现:
可看到最终都是调用的bind():
这其中有个变量,sMapper。我们看到声明地方:
可以看到此变量是由一个生成类new出来,且此类并不是原先就存在,只有在编译过程结束后才生成出来的文件:
而在此类中最重要的就是getDataBinder(),在此方法中,可以看到拿标签流程:
通过判断是activity_main.xml的id来返回一个new ActivityMainBindingImpl(component, view);对象。我们点进去观察此对象源码:
可以看出,对应取控件id等操作来源得看mapBindings内部实现:
此方法后续逻辑基本上都是如此,在这方法体内将所有view存进一个数组,然后在ActivityMainBindingImpl中通过bindings[1]去获取view实例:
然后通过invalidateAll()更新所有UI。
以上不难看出这些流程就是XML布局的初始化所有流程。在初始化结束后,xml的控件信息就存在了DataBinding对象里,就可以通过DataBinding对象拿到具体对应控件对象实例了。
ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
viewDataBinding.tvData.setText("拿到对象实例了");
这也正是为什么DataBinding可以不用findViewById了的原因。其实不难想到,随着页面的UI复杂度的增加,dataBinding对内存的消耗也会越大,个人感觉这是DataBinding的缺点。(后面有时间,我会去对比DataBinding与常规架构所构建的APP在内存和整体性能上的区别)