目录
- 一、DataBinding简介
- 二、findViewById 和 DataBinding 原理及优缺点
- 1. findViewById的优缺点
- 2. DataBinding的优缺点
- 三、Android mvvm 之 databinding 原理
- 1. 简介和三个主要的实体
- Data
- View
- ViewDataBinding
- 2.三个功能
- 2.1. rebind 行为
- 2.2 observe data 行为
- 2.3 observe view 行为
- 四、代码实例
一、DataBinding简介
DataBinding
是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM
模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter
层替换成了 ViewModel
层。DataBinding
能够省去我们一直以来的 findViewById()
步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常
二、findViewById 和 DataBinding 原理及优缺点
1. findViewById的优缺点
findViewById的原理:Android中的View结构是一个树形结构,findViewById
就是自树的根节点,依次遍历其子节点,知道找到目标的id。
- findViewById的优点
- 兼容性好,下面各种替代方案,都有其适用的场景,但是findViewById适用所有的场景,当你不知道用哪种方案的时候,那就用findViewById吧,肯定没有错。
- 非常灵活,适合动态加载layout文件。比如一个Activity,需要在不同的业务中,加载两个不同的layout文件,但是两个layout文件只有部分间距不同,其他各个元素都是相同的。这个时候,用findViewId就可以完美适用。
- findViewById的缺点
- 性能略差不好,findViewById是基于树形结构的查找,理论上会带来性能的额外开销,但是实际项目中,因为控件的个数也不会非常非常多,所以可以忽略不计。;
Fragment
中使用容易犯错。从原理图可以看到,在Activity中调用findViewById,实际上是调用Window中的findViewById,但是Fragment中并没有单独的Window,Fragment中调用findViewById的效果和Activity中调用的效果一模一样。所以如果一个Activity中有多个Fragment,Fragment中的控件名称又有重复的,那直接findViewById会出错的;- 增加代码的耦合度,findViewById随时实地都可以调用,在子view中,在Activity中等等,如果滥用起来,会让代码耦合的一塌糊涂,后面查找bug起来,非常麻烦,因为不知道View的属性在哪个类中被改变了;
- 容易引发空指针,一个大型项目中,控件的id经常会重复,xml中删除了一个控件,但是对应的Activity中没有删除这个控件的相关引用,编译时并不会报错,但是运营室时会报出空指针;
- 代码可读性不好,findViewById往往在Activity的
onCreate
方法中被引用,我们不能方便的将xml中的控件与代码中的控件结合起来,特别是如果xml的命名与代码中的命名又不规范,代码阅读起来简直就是噩梦。
2. DataBinding的优缺点
databinding
会根据动态生成一个ActivityMainBinding
文件,在执行ActivityMainBinding.inflate
的时候,会自动生成控件的引用(mapBindings
方法),这里对布局tree执行一趟遍历查找就可以生成所有的引用
-
DataBinding的优点
- 规避了控件空指针错误,如果有引用错误,则会在编译阶段发现;
- 效率比findViewById要高,一趟遍历可以生成所有控件的引用,而findViewById是每次执行时都需要遍历一遍
- 代码的可读性要高,得益于Android Studio的强大功能,我们很轻易的将java(kotlin)代码中的控件引用与xml的定义结合起来
-
DataBinding的缺点
- 灵活性不高,如果需要动态选取引用的布局文件,binding就无法适用了。
- 改造成本较大,布局文件只有加上layout标签才可以使用。
原文链接
参考博客
三、Android mvvm 之 databinding 原理
原文
1. 简介和三个主要的实体
Data Binding 的结构[Top]
作为在 Android 开发中体现 MVVM 架构思想的 Data Binding,其核心是 观察者模式 的特定实现。首先,它有三个主要的实体:
Data
与 View 相关的数据,它可以是 View 的可观察者对象;
View
展示给用户的视图,如果有交互功能且能更新数据,它可以是 Data 的可观察者对象;
ViewDataBinding
连接 Data 和 View 的中介,当 Data 或 View 作为可观察者对象时,它充当可观察者对象的代理。假如当我们写了一个名为 demo.xml 的 Data Binding 的 layout 文件后,编译工具会生成一个相应的类——DemoBinding,它的原型就是 ViewDataBinding。我们通常通过 DataBindingUtil.inflate(inflater, R.layout.demo, container, false) 来实例化的 DemoBinding 对象,即 ViewDataBinding。
2.三个功能
其次,它主要提供了以下三个方面的功能:
将特定的 View 与特定的 Data 进行绑定,便于模块化;
View 自动感知和响应 Data 的变化,使得处理数据的业务层不必关心 View 的状态,便于解耦;
Data 也可以自动同步带有交互功能的 View 对数据的修改,使得 UI 层的交互不必担心数据是否能同步 View 状态的问题,仍然便于解耦。
基于这三个功能,Data Binding 的结构也可以划分为三个行为模式,以下一一介绍:
2.1. rebind 行为
首先,Data 往往是一个数据的集合,数据绑定的第一步就是要将整个 Data 集合绑定到 View,比如初始化和数据的整体更新,如下图所示:
可以观察到,rebind 的过程就是一个简单的赋值操作,将 View 的值设置为 Data,只不过由 ViewDataBinding
这个代理来完成这个工作。图中的 _all 参数表示将 View 的所有需要更新的节点都设置为 Data 的所有对应的成员值。当我们在 layout 文件中进行如下设置时,ViewDataBinding
将代理完成 View 中所有数据绑定节点的 data rebind
操作:
2.2 observe data 行为
有时候,我们并不需要每次更新整个 Data 集合,而只需要更新集合中的某一个成员。我们希望看到的结果是,当 Data.element_i 发生变化
的时候,View.child_i 更新就可以了,而不需要将 View 的所有视图节点都重新渲染一遍。要做到这一点,我们必须要让 View 可以观察 Data 的行为。换句话说,Data 是一个可观察者对象——这是 Data Binding 中另一个魅力所在,其行为模式如下:
我们可以将任何数据作为一个 Observable,然后将 ViewDataBinding 作为 View 的代理观察者,订阅 Data 的成员变化,一旦 Data 成员变化,便通知所有观察者对象——即 ViewDataBinding,然后 ViewDataBinding 再将 View 的相应节点的值设置为 Data 相应成员的新值——即图中的 _member 参数。
这里有三个关键部分:
BaseObservable
:可观察者基类(实际的祖先基类是一个 Observable 接口),实现改接口后,ViewDataBinding 就会在每次 rebind 的时候去订阅 Data 的变化;
@Bindable 标注
:声明该成员是可被观察的,以及在 layout 中可以以 http://Data.xxx(标注的方法名如果为 getXXX)的方式进行访问;
notifyPropertyChanged 方法
:BaseObservable 用于通知具体成员发送变化的方法,只要该方法被调用,ViewDataBinding 就会检索出是哪一个 element 的变化,并只对 View 相应的节点进行更新。
2.3 observe view 行为
在开发中,根据业务需求,我们一般能遇到两种类型的 View:
一种是只用于展示的 View,它只展示 UI 状态,而不反馈状态,我们称之为 单工View;
另一种除了展示以外,还会反馈状态给监听者,我们称之为 双工View。
在 Android 的 UI事件流中,因为所有的 View 都是可以反馈状态的,所以准确来说,所有的 View 其实都是双工的。我们在这里区分单工和双工是针对业务需求的,比如:我们很多的视图只需要它们展示就可以了,不需要监听它们的状态变化,那么我们将其归为单工View。
费尽心思进行这样的划分,是因为,单工View 只需要有 observe data 行为就可以了;而双工View 往往就需要 observe view 行为。具体来说,在反馈状态时需要更新 Data 的双工 View,我们需要进行 observe view 行为。
因为双工View 会更新 Data,所以为了保证数据的一致性,Data 需要观察双工View 的状态变化。要做到这一点,这样的双工View 必须是一个可观察者对象。得益于 UI事件流的实现,双工View天然是可观察的(只要能反馈状态,就意味着能被观察)。在自定义的双工View中,可以间接引用 ViewDataBinding,这样 ViewDataBinding 就可以代理 Data 订阅 View 的状态变化:
四、代码实例
启用 DataBinding 的方法是在对应 Model :app
的 build.gradle
文件里加入以下代码,同步后就能引入对 DataBinding
的支持
android {dataBinding {enabled = true}
}
1、在布局文件中绑定指定的变量打开布局文件,选中布局文件的第一行,按住 Alt + 回车键,点击 “Convert to data binding layout”,就可以生成 DataBinding 需要的布局规则
和原始布局的区别在于多出了一个 layout 标签将原布局包裹了起来,data 标签用于声明要用到的变量以及变量类型,要实现 MVVM 的 ViewModel 就需要把数据(Person)与 UI(View)进行绑定,data 标签的作用就像一个桥梁搭建了 View 和 Person 之间的通道
Person类
public class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
在 data 标签里声明要使用到的变量名、类的全路径(如果 Person类型要多处用到,也可以直接将之 import 进来,就不用每次都指明整个包名路径)
<data><variablename="per1"type="com.example.databindingpro.Person" /><variablename="work"type="String" /></data>
如果存在 import 的类名相同的情况,可以使用 alias 指定别名
<data><import type="com.leavesc.databinding_demo.model.User" /><importalias="TempUser"type="com.leavesc.databinding_demo.model2.User" /><variablename="userInfo"type="User" /><variablename="tempUserInfo"type="TempUser" /></data>
这里声明了一个 Person
类型的变量per1
,我们要做的就是使这个变量与两个 TextView 控件挂钩,通过设置per1
的变量值同时使 TextView
显示相应的文本 完整的布局代码如下所示
<?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><variablename="per1"type="com.example.databindingpro.Person" /><variablename="work"type="String" /></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{per1.name}"android:textSize="30dp"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{String.valueOf(per1.age)}"android:textSize="30dp"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{work}"android:textSize="30dp"/></LinearLayout></LinearLayout>
</layout>
通过 @{per1.name
}使 TextView 引用到相关的变量,DataBinding
会将之映射到相应的 getter
方法 之后可以在 Activity 中通过 DataBindingUtil
设置布局文件,省略原先 Activity 的 setContentView()
方法,并为变量per1
赋值
import androidx.databinding.DataBindingUtil;
import android.app.Activity;
import android.os.Bundle;
import com.example.databindingpro.databinding.ActivityMainBinding;
public class MainActivity extends Activity {private ActivityMainBinding mActivityMainBinding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//setContentView(R.layout.activity_main);mActivityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);com.example.databindingpro.Person person = new com.example.databindingpro.Person("张三",23);mActivityMainBinding.setPer1(person);//给布局文件name="per1"传入数据,类型为Person对象mActivityMainBinding.setWork("Teacher");//给布局文件name="work"传入数据,类型为String字符串}
}
效果: