Android JetPack深入分析DataBinding源码

前言

数据绑定库是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。

DataBinding支持双向绑定,数据变化的时候界面跟着变化,界面变化也同步给数据;

DataBinding在MVVM模式中使用比较多,双向绑定机制实现了View和Model的同步更新。

简单使用

DataBinding一般配合LiveDataViewModel一起使用,这里就简单使用下,便于后续源码分析;

  • build.gradle配置
    buildFeatures {dataBinding true}
  • 定义数据源
data class User(@Bindable var username: String = "", @Bindable var pwd: String = "") : BaseObservable()
  • 定义数据源绑定的布局文件activity_data_binding.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"><data><!--data标签内定义数据源User--><variablename="User"type="com.xixu.jetpack.User" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/tvName"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{User.username}"android:textSize="16sp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/tvPwd"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:text="@{User.pwd}"android:textSize="16sp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toBottomOf="@+id/tvName" /></androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  • 定义Activity使用
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val dataBinding = DataBindingUtil.setContentView<ActivityDataBindingBinding>(this,R.layout.activity_data_binding)lifecycleScope.launch{delay(2000)dataBinding.user = User("XiXu", "123456")}}
}

界面效果:延迟2s后,分别将XiXu123456数据绑定到tvNametvPwd控件上;

源码分析

DataBinding是如何实现将数据绑定到具体视图上的呢?

DataBinding为我们生成了哪些布局文件

首先,DataBinding会使用APT(Annotation Processing Too,注解解析器),在编译器为我们生成如下布局文件
由于使用DataBinding,布局文件中引入了layout标签,我们先看下布局文件变化;

1.build/intermediates/incremental/packageDebugResources/stripped.dir/layout/activity_data_binding.xml,为每个控件都新增了tag属性;
activity_data_binding
2.build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_data_binding-layout.xml
定义了多组Target与布局文件中的tag标签控件对应,并标注了每个tag对应控件的view类型,Expressions标签中定义了控件属性对应绑定数据,其中TwoWay标签表示是否是双向绑定;
activity_data_binding-layout

小结

使用DataBinding会在编译期生成辅助布局文件,为每个控件新增tag标签,并记录控件类型id属性等信息;

DataBindingUtil.setContentView()做了什么

    public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,int layoutId) {return setContentView(activity, layoutId, sDefaultComponent);}public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,int layoutId, @Nullable DataBindingComponent bindingComponent) {activity.setContentView(layoutId);View decorView = activity.getWindow().getDecorView();ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);return bindToAddedViews(bindingComponent, contentView, 0, layoutId);}

调用setContentView方法主要做了如下几件事:

  1. 调用activity对应的setContentView方法绑定布局;
  2. 获取activity对应的R.id.content控件,我们知道即FrameLayout控件;
  3. 调用bindToAddedViews绑定布局中的控件;

这里我们重点看下bindToAddedViews是如何实现布局控件绑定的;

   private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,ViewGroup parent, int startChildren, int layoutId) {final int endChildren = parent.getChildCount();final int childrenAdded = endChildren - startChildren;if (childrenAdded == 1) {final View childView = parent.getChildAt(endChildren - 1);return bind(component, childView, layoutId);} else {final View[] children = new View[childrenAdded];for (int i = 0; i < childrenAdded; i++) {children[i] = parent.getChildAt(i + startChildren);}return bind(component, children, layoutId);}}

这里parent即指FrameLayout,因此parent.getChildCount()获取的便是根布局个数,上述例子对应的为1,即ConstraintLayout;最终会调用bind(component, childView, layoutId)方法如下:

    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,int layoutId) {return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);}

其中sMapper初始化代码如下:

    private static DataBinderMapper sMapper = new DataBinderMapperImpl();public class DataBinderMapperImpl extends MergedDataBinderMapper {DataBinderMapperImpl() {addMapper(new com.crystal.maniu.DataBinderMapperImpl());}
}

因此sMapper.getDataBinder最终调用的为DataBinderMapperImpl【注意包名,与androidx.databinding区分】.getDataBinder方法:

	### DataBinderMapperImplpublic class DataBinderMapperImpl extends DataBinderMapper {private static final int LAYOUT_ACTIVITYDATABINDING = 1;private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);static {INTERNAL_LAYOUT_ID_LOOKUP.put(com.crystal.maniu.R.layout.activity_data_binding, LAYOUT_ACTIVITYDATABINDING);}@Overridepublic ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);if(localizedLayoutId > 0) {final Object tag = view.getTag();if(tag == null) {throw new RuntimeException("view must have a tag");}switch(localizedLayoutId) {case  LAYOUT_ACTIVITYDATABINDING: {if ("layout/activity_data_binding_0".equals(tag)) {return new ActivityDataBindingBindingImpl(component, view);}throw new IllegalArgumentException("The tag for activity_data_binding is invalid. Received: " + tag);}}}return null;}}

可见getDataBinder方法其实就是直接new了一个ActivityDataBindingBindingImpl【APT生成的类】对象,并把根布局ConstraintLayout入参;我们看看ActivityDataBindingBindingImpl构造方法里做了什么;

    public ActivityDataBindingBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));}private ActivityDataBindingBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {super(bindingComponent, root, 1, (android.widget.TextView) bindings[1], (android.widget.TextView) bindings[2]);this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];this.mboundView0.setTag(null);this.tvName.setTag(null);this.tvPwd.setTag(null);setRootTag(root);// listenersinvalidateAll();}

其中mapBindings方法会对根据tag对布局文件进行xml解析得到控件数组 Object[],super会调用父类ActivityDataBindingBinding【APT生成的类】的构造方法,完成View的初始化工作 invalidateAll()会进行数据的初始化绑定,具体如何绑定我们下一步在分析!

小结

调用DataBindingUtil.setContentView方法会调用setContentView方法并完成控件的初始化工作,从而代替FindViewById的工作;

DataBinding如何实现数据更新

我们再看下调用dataBinding.user = User("XiXu", "123456")是如何实现将数据更新到控件上去的,具体实现交给ActivityDataBindingBindingImpl.setUser方法;

  public void setUser(@Nullable com.xixu.jetpack.User User) {updateRegistration(0, User);this.mUser = User;synchronized(this) {mDirtyFlags |= 0x1L;}notifyPropertyChanged(BR.User);super.requestRebind();}

其中BR文件如下,定义一系列常量用于区分更新字段:

public class BR {public static final int User = 1;public static final int _all = 0;public static final int pwd = 2;public static final int username = 3;
}

可以看到调用setUser主要做了如下几件事:

  1. 调用updateRegistration(0, User)方法;User作为被观察者,会先判断User是否为null,如果为null解除注册,否则会将User绑定到WeakListener虚引用对象上,并包装成WeakPropertyListener对象,用于后续处理数据更新操作;

  2. 调用notifyPropertyChanged(BR.User)方法;经过层层处理会执行WeakPropertyListener.onPropertyChanged方法如下:

    ### WeakPropertyListener.onPropertyChangedpublic void onPropertyChanged(Observable sender, int propertyId) {//1.先判断绑定的ViewDataBinding是否为nullViewDataBinding binder = mListener.getBinder();if (binder == null) {return;}//2.obj即为传入的User对象,判断和之前绑定的是否一致Observable obj = mListener.getTarget();if (obj != sender) {return; }//3.调用ViewDataBinding.onFieldChange方法;binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);}

ViewDataBinding.onFieldChange代码如下:

    protected void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {if (mInLiveDataRegisterObserver || mInStateFlowRegisterObserver) {return;}boolean result = onFieldChange(mLocalFieldId, object, fieldId);if (result) {//内部会进行Lifecycle生命周期判断,从而实现更新逻辑与生命周期绑定requestRebind();}}protected void requestRebind() {if (mContainingBinding != null) {//设置包含绑定mContainingBinding.requestRebind();} else {//这里会进行组件活跃判断,如果不是STARTED、RESUMED状态,不执行更新数据操作final LifecycleOwner owner = this.mLifecycleOwner;if (owner != null) {Lifecycle.State state = owner.getLifecycle().getCurrentState();if (!state.isAtLeast(Lifecycle.State.STARTED)) {return;}}synchronized (this) {if (mPendingRebind) {return;}mPendingRebind = true;}if (USE_CHOREOGRAPHER) {//SDK_INT >= 16 使用编舞者处理,最终还是调用mRebindRunnable.run方法mChoreographer.postFrameCallback(mFrameCallback);} else {mUIThreadHandler.post(mRebindRunnable);}}}

其中onFieldChange调用的是ActivityDataBindingBindingImpl.onFieldChange方法根据mLocalFieldId以及fieldId匹配BR文件中的常量,判断能否更新;

如果返回true,则调用requestRebind()方法,最终又会回到 ActivityDataBindingBindingImpl.executeBindings方法

  protected void executeBindings() {long dirtyFlags = 0;synchronized(this) {dirtyFlags = mDirtyFlags;mDirtyFlags = 0;}com.xixu.jetpack.User user = mUser;java.lang.String userUsername = null;java.lang.String userPwd = null;if ((dirtyFlags & 0xfL) != 0) {if ((dirtyFlags & 0xbL) != 0) {if (user != null) {// read User.usernameuserUsername = user.getUsername();}}if ((dirtyFlags & 0xdL) != 0) {if (user != null) {// read User.pwduserPwd = user.getPwd();}}}// batch finishedif ((dirtyFlags & 0xbL) != 0) {// api target 1androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvName, userUsername);}if ((dirtyFlags & 0xdL) != 0) {// api target 1androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvPwd, userPwd);}}

这里采用了取巧的方式处理,使用位运算操作计算要更新哪些控件,最终调用TextViewBindingAdapter.setText完成数据更新;

更新数据流程图更新数据流程图

总结

DataBinding通过APT技术于编译期生成相关辅助类,当调用DataBindingUtil.setContentView方法时帮助我们完成布局控件绑定工作,减少FindViewById操作,当调用数据更新操作时,数据作为被观察者,会绑定到WeakListener虚引用对象上,更新过程中会使用Lifecycle进行生命周期判断,最终通过调用androidx.databinding.adapters包下的辅助工具类完成控件更新操作;

结语

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/19359.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】1-Visual C++ 2010 的简单使用和第一个 C 语言程序

1. Visual C++ 2010 的简单使用 1.1 面板介绍 1.2 新建C语言项目 打开 Visual C++ 2010,点击 New Project 根据下面的图示进行操作,其中需要注意 ③:这里输入的为项目名(建议和我的命名保持一致) ④:这里是项目存放的位置,可以自己选择,最好不要有中文路径(只要是使…

selenium 浏览器托管

selenium 浏览器托管&#xff0c;是启动一个浏览器&#xff0c;调试代码&#xff0c;可以运行当前调试代码&#xff0c;不用从启动浏览器开始从头执行 在谷歌浏览器chrome.exe 目录中打开cmd 输入下面目录&#xff0c;启动器浏览器 chrome.exe --remote-debugging-port9222 -…

使用docker部署rancher并导入k8s集群

前言&#xff1a;鉴于我已经部署了k8s集群&#xff0c;那就在部署rancher一台用于管理k8s&#xff0c;这是一台单独的虚拟环境&#xff0c;之前在k8s的master节点上进行部署并未成功&#xff0c;有可能端口冲突了&#xff0c;这个问题我并没有深究&#xff0c;如果非要通过修改…

数据结构05:树与二叉树[C++][并查集]

图源&#xff1a;文心一言 Chat GPT生成&#xff0c;代码的核心思想与王道咸鱼老师的视频虽然类似&#xff0c;但是在具体实现上毕竟还是略有差别~~因此&#xff0c;如果对考研方向的并查集代码感兴趣&#xff0c;可以查看—— 王道咸鱼老师的视频&#xff1a;{5.5_2_并查集_…

【Elasticsearch】文档操作

目录 3.文档操作 3.1.新增文档 3.2.查询文档 3.3.删除文档 3.4.修改文档 3.4.1.全量修改 3.4.2.增量修改 3.5.总结 3.文档操作 3.1.新增文档 语法&#xff1a; POST /索引库名/_doc/文档id {"字段1": "值1","字段2": "值2"…

遥感云大数据在灾害、水体与湿地领域典型案例实践及GPT模型应用

​ ​ ​ ​ 第一部分 基础实践 一 平台及基础开发平台 GEE平台及典型应用案例介绍&#xff1b; GEE开发环境及常用数据资源介绍&#xff1b; ChatGPT、文心一言等GPT模型介绍 JavaScript基础简介&#xff1b; GEE遥感云重要概念与典型数据分析流程&#xff1b; …

Hightopo 使用心得(4)- 3D 场景 Graph3dView 与 Obj 模型

在前一篇文章《Hightopo 使用心得&#xff08;3&#xff09;- 吸附与锚点》中&#xff0c;我们在结尾处提到过 HT 的 3D 场景。这里我们通过代码建立一个 3D 场景并添加一个 Obj 模型来介绍一下 HT for Web 在 3D 场景和模型加载方面的使用。 这是我们最终实现的效果&#xff…

QML Canvas 几何变换(平移/旋转/缩放)

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 几何变换是 Canvas 提供的一项强大的图形处理能力,主要涉及平移、旋转、缩放。通过运用这些功能,我们有了更大的创作自由度,可以很容易的在 QML 中实现一些出色的游戏效果、动态图表、交互式数据可视化等…

装机——固态硬盘的选择

文章目录 问题描述知识学习硬盘分类PCIe接口SATA接口M.2接口 通道PCI-E通道SATA通道SAS通道FC通道 通信协议IDE协议AHCI协议NVMe协议 硬盘参数表主控存储颗粒SLCMLCTLCQLC失败的颗粒&#xff08;需要购买原装厂商的存储颗粒&#xff09; 问题解决问题总结 问题描述 女朋友笔记本…

2023-7-10-第十五式命令模式

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f4a5;&#x1f4a5;&#x1f4a5;欢迎来到&#x1f91e;汤姆&#x1f91e;的csdn博文&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f49f;&#x1f49f;喜欢的朋友可以关注一下&#xf…

【洛谷】P1342 请柬(正反建图+dijkstra)

1&#xff1a;思考&#xff1a; 从1到所用顶点简单&#xff08;单源最短路径。&#xff09;&#xff0c;重点在怎么解决所用点到1&#xff08;单终点最短路径&#xff09; 答案&#xff1a;反向建图使&#xff08;单终点最短路径→单源最短路径。&#xff09; 复杂度&#xf…

openGauss学习笔记-07 openGauss 语法

文章目录 openGauss学习笔记-07 openGauss 语法7.1 帮助7.2 SQL语句格式7.3 SQL语法ABORTALTER AUDIT POLICYALTER DATA SOURCEALTER DATABASEALTER DEFAULT PRIVILEGESALTER DIRECTORYALTER EXTENSIONALTER FOREIGN TABLEALTER FUNCTIONALTER GROUPALTER INDEXALTER LARGE OBJ…