1 简介和简单使用
1.1 简介
DataBinding是Google推出的一款数据和视图绑定库,可以省去findViewById
和setText
,能大量减少业务逻辑和布局之间的繁琐代码。支持双向绑定,也就是当数据发生变化的时候,不用setText
,UI就会自动更新。而UI上的内容发生变化的时候,对应的数据也会同步修改。DataBinding多用于MVVM架构。
DataBinding使用APT技术,自动生成辅助代码,底层实现最终也是通过setText
和TextWatcher
来实双向绑定。
优点:使用简单,支持双向绑定,适合MVVM架构,适合数据变化频繁的APP。
缺点:在View过于复杂的时候性能会比较低,不适合低端手机运行。
Google官方文档:https://developer.android.google.cn/topic/libraries/data-binding
1.2 在Java项目中使用
开启DataBinding
方式1
android {
...
dataBinding {
enabled = true
}
}
方式2
android {
...
dataBinding.enabled=true
}
创建数据实体,继承BaseObservable
,实现get/set
方法,然后编译。会通过APT生成一个BR
类。然后在get
方法上用@Bindable
标注,在set
方法内调用notifyPropertyChanged()
,notifyPropertyChanged()
方法就是用来通知属性数据变化的。
public class Book extends BaseObservable {
private String name;
private String author;
public Book(String name, String author) {
this.name = name;
this.author = author;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
notifyPropertyChanged(BR.author);
}
}
将xml修改为DataBinding类型的xml,用<layout>标签包裹。<data>标签下用来定义数据源。然后在TextView中使用@{book.name}
的形式,即可获取数据。
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="book"
type="cn.zhangmushui.databindinguseforjava.Book" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{book.name}" />
<TextView
android:id="@+id/author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{book.author}" />
<Button
android:id="@+id/btn_change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="修改数据" />
</LinearLayout>
</layout>
在Activity中,建立数据和View的绑定关系。这样,修改book对象中的数据的时候,UI就会自动同步更新。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//替代setContentView,ActivityMainBinding是APT生成的代码,对应activity_main布局
final ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
Book book = new Book("沉默的大多数", "王小波");
//建立绑定关系
binding.setBook(book);
//修改数据,无需调用setText,UI就能更新
binding.btnChange.setOnClickListener(v -> {
book.setName("平凡的世界");
book.setAuthor("路遥");
});
}
}
1.3 在Kotlin项目中使用
开启DataBinding
方式1
android {
...
dataBinding {
enabled = true
}
}
方式2
android {
...
dataBinding.enabled=true
}
创建数据实体,与Java不同的是,Kotlin的数据实体自动带有set/get
方法,没法使用@Bindable
标注和notifyPropertyChanged()
,所以Kotlin中采用以下方式。
class Book {
val name: ObservableField<String> by lazy { ObservableField<String>() }
val author: ObservableField<String> by lazy { ObservableField<String>() }
}
同样修改xml文件。这里需要注意,@{book.name}
是单项绑定,也就是ViewModel修改了数据,UI会同步,反之不会同步。@={book.name}
是双向绑定,任何一方有了改动,另一方都会同步。
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="book"
type="cn.zhangmushui.databindinguseforkt.Book" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:id="@+id/et_name_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="书名1"
android:text="@{book.name}" />
<EditText
android:id="@+id/et_author_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="作者1"
android:text="@{book.author}" />
<EditText
android:id="@+id/et_name_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:hint="书名2"
android:text="@={book.name}" />
<EditText
android:id="@+id/et_author_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="作者2"
android:text="@={book.author}" />
</LinearLayout>
</layout>
在Activity中进行绑定。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding =
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
val book = Book()
book.name.set("沉默的大多数")
book.author.set("王小波")
binding.book=book
}
}
运行后发现,修改前两个输入框的数据,后两输入框的数据不会变化,这说明前两个是单向绑定。而修改后两个输入框的数据,前两个会跟着变化,说明后两个是双向绑定。
1.4 业务架构
2 源码分析
以下源码分析以上边Kotlin示例进行。
2.1 布局转换
当原始的xml文件转换为DataBinding的xml文件之后,会被DataBinding拆分生成为两个xml文件。
第一个是build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_main-layout.xml
。
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="databindinguseforkt/src/main/res/layout/activity_main.xml"
isBindingData="true" isMerge="false"
layout="activity_main" modulePackage="cn.zhangmushui.databindinguseforkt" rootNodeType="android.widget.LinearLayout">
<Variables name="book" declared="true" type="cn.zhangmushui.databindinguseforkt.Book">
<location endLine="8" endOffset="60" startLine="6" startOffset="8" />
</Variables>
<Targets>
<Target tag="layout/activity_main_0" view="LinearLayout">
<Expressions />
<location endLine="46" endOffset="18" startLine="11" startOffset="4" />
</Target>
<Target id="@+id/et_name_1" tag="binding_1" view="EditText">
<Expressions>
<Expression attribute="android:text" text="book.name">
<Location endLine="22" endOffset="38" startLine="22" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="22" endOffset="36" startLine="22" startOffset="28" />
</Expression>
</Expressions>
<location endLine="22" endOffset="41" startLine="17" startOffset="8" />
</Target>
<Target id="@+id/et_author_1" tag="binding_2" view="EditText">
<Expressions>
<Expression attribute="android:text" text="book.author">
<Location endLine="29" endOffset="40" startLine="29" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="29" endOffset="38" startLine="29" startOffset="28" />
</Expression>
</Expressions>
<location endLine="29" endOffset="43" startLine="24" startOffset="8" />
</Target>
<Target id="@+id/et_name_2" tag="binding_3" view="EditText">
<Expressions>
<Expression attribute="android:text" text="book.name">
<Location endLine="35" endOffset="39" startLine="35" startOffset="12" />
<TwoWay>true</TwoWay>
<ValueLocation endLine="35" endOffset="37" startLine="35" startOffset="29" />
</Expression>
</Expressions>
<location endLine="37" endOffset="32" startLine="31" startOffset="8" />
</Target>
<Target id="@+id/et_author_2" tag="binding_4" view="EditText">
<Expressions>
<Expression attribute="android:text" text="book.author">
<Location endLine="43" endOffset="41" startLine="43" startOffset="12" />
<TwoWay>true</TwoWay>
<ValueLocation endLine="43" endOffset="39" startLine="43" startOffset="29" />
</Expression>
</Expressions>
<location endLine="44" endOffset="32" startLine="39" startOffset="8" />
</Target>
</Targets>
</Layout>
里边定义了数据对象和多个<Target>,每个Target对应着布局文件中的一个控件,一个控件id对应一个tag属性。
在第二个拆分出来的build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
中,每个控件会添加一个tag属性。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" android:tag="layout/activity_main_0" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools">
<EditText
android:id="@+id/et_name_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="书名1"
android:tag="binding_1" />
<EditText
android:id="@+id/et_author_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="作者1"
android:tag="binding_2" />
<EditText
android:id="@+id/et_name_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="binding_3"
android:layout_marginTop="30dp"
android:hint="书名2" />
<EditText
android:id="@+id/et_author_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="binding_4"
android:hint="作者2" />
</LinearLayout>
2.2 源码分析
首先进入DataBindingUtil.setContentView
,可以看到这里返回的类型是ViewDataBinding
。
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
继续往下走,调用了bindToAddedViews
方法。
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);
}
然后都走了bind
方法。
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);
}
}
然后调用了抽象类DataBinderMapper
的getDataBinder
方法。
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}
public abstract class DataBinderMapper {
... int layoutId);
public abstract ViewDataBinding getDataBinder(DataBindingComponent bindingComponent,
View[] view, int layoutId);
...
}
这里DataBinderMapper
里的抽象方法的真正实现是在APT生成的build/generated/source/kapt/debug/包名/DataBinderMapperImpl.java
中,而不是DataBinding
的aar中的DataBinderMapperImpl.java
。
public class DataBinderMapperImpl extends DataBinderMapper {
...
@Override
public 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_ACTIVITYMAIN: {
//这里去找到activity_main-layout.xml中tag为layout/activity_main_0的布局
if ("layout/activity_main_0".equals(tag)) {
return new ActivityMainBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
}
}
}
return null;
}
}
然后实例化了build/generated/source/kapt/debug/包名/databinding/ActivityMainBindingImpl.java
。
public class ActivityMainBindingImpl extends ActivityMainBinding {
...
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds));
}
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 2
, (android.widget.EditText) bindings[2]
, (android.widget.EditText) bindings[4]
, (android.widget.EditText) bindings[1]
, (android.widget.EditText) bindings[3]
);
this.etAuthor1.setTag(null);
this.etAuthor2.setTag(null);
this.etName1.setTag(null);
this.etName2.setTag(null);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
...
}
由于业务层调用public static <T extends ViewDataBinding> T setContentView()
传入的泛型是ViewDataBinding
,所以ActivityMainBindingImpl
里的super
最终是到了ViewDataBinding
中。ViewDataBinding
首先执行了一个static
代码块。static
中是一个监听,当view附着于屏幕的时候,就会触发监听。
static {
if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
ROOT_REATTACHED_LISTENER = null;
} else {
ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
@TargetApi(VERSION_CODES.KITKAT)
@Override
public void onViewAttachedToWindow(View v) {
// execute the pending bindings.
final ViewDataBinding binding = getBinding(v);
binding.mRebindRunnable.run();
v.removeOnAttachStateChangeListener(this);
}
@Override
public void onViewDetachedFromWindow(View v) {
}
};
}
}
监听中执行了了一个mRebindRunnable
,调用了executePendingBindings
方法。
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
mPendingRebind = false;
}
processReferenceQueue();
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
// Nested so that we don't get a lint warning in IntelliJ
if (!mRoot.isAttachedToWindow()) {
// Don't execute the pending bindings until the View
// is attached again.
mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
return;
}
}
executePendingBindings();
}
};
在ActivityMainBindingImpl
构造方法中,调用了invalidateAll()
。
@Override
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x8L;
}
requestRebind();
}
invalidateAll
中调用了requestRebind
,requestRebind
是ActivityMainBindingImpl
父类ViewDataBinding
中的方法。
protected void requestRebind() {
if (mContainingBinding != null) {
mContainingBinding.requestRebind();
} else {
final LifecycleOwner owner = this.mLifecycleOwner;
if (owner != null) {
Lifecycle.State state = owner.getLifecycle().getCurrentState();
if (!state.isAtLeast(Lifecycle.State.STARTED)) {
return; // wait until lifecycle owner is started
}
}
synchronized (this) {
if (mPendingRebind) {
return;
}
mPendingRebind = true;
}
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
在requestRebind
中,执行了mRebindRunnable
。mRebindRunnable
中的run
方法执行了两个内容:如果mRoot
已经和屏幕进行了绑定,那么直接执行executePendingBindings
方法;如果没有绑定,先添加监听,什么时候绑定了,什么时候执行。
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
mPendingRebind = false;
}
processReferenceQueue();
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
// Nested so that we don't get a lint warning in IntelliJ
if (!mRoot.isAttachedToWindow()) {
// Don't execute the pending bindings until the View
// is attached again.
mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
return;
}
}
executePendingBindings();
}
};
在executePendingBindings
中,执行了executeBindingsInternal
。
public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
在executeBindingsInternal
方法中,会进入executeBindings()
方法。
private void executeBindingsInternal() {
if (mIsExecutingPendingBindings) {
requestRebind();
return;
}
if (!hasPendingBindings()) {
return;
}
mIsExecutingPendingBindings = true;
mRebindHalted = false;
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBIND, null);
// The onRebindListeners will change mPendingHalted
if (mRebindHalted) {
mRebindCallbacks.notifyCallbacks(this, HALTED, null);
}
}
if (!mRebindHalted) {
executeBindings();
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
}
}
mIsExecutingPendingBindings = false;
}
protected abstract void executeBindings();
executeBindings
的具体实现在APT生成的ActivityMainBindingImpl
中。
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
androidx.databinding.ObservableField<java.lang.String> bookName = null;
java.lang.String bookNameGet = null;
java.lang.String bookAuthorGet = null;
cn.zhangmushui.databindinguseforkt.Book book = mBook;
androidx.databinding.ObservableField<java.lang.String> bookAuthor = null;
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xdL) != 0) {
if (book != null) {
// read book.name
bookName = book.getName();
}
updateRegistration(0, bookName);
if (bookName != null) {
// read book.name.get()
bookNameGet = bookName.get();
}
}
if ((dirtyFlags & 0xeL) != 0) {
if (book != null) {
// read book.author
bookAuthor = book.getAuthor();
}
updateRegistration(1, bookAuthor);
if (bookAuthor != null) {
// read book.author.get()
bookAuthorGet = bookAuthor.get();
}
}
}
// batch finished
if ((dirtyFlags & 0xeL) != 0) {
// api target 1
//单项绑定,最终也是调用了setText去给TextView设置数据
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etAuthor1, bookAuthorGet);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etAuthor2, bookAuthorGet);
}
if ((dirtyFlags & 0x8L) != 0) {
// api target 1
//双向绑定,使用TextWatcher监听EditText数据变化,去修改Model
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etAuthor2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etAuthor2androidTextAttrChanged);
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etName2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etName2androidTextAttrChanged);
}
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etName1, bookNameGet);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etName2, bookNameGet);
}
}
至此,整个流程结束。可以看到DataBindding框架是使用APT去生成辅助工具类,最终也是将setText
和TextWatcher
包装起来,在框架内部
进行调用。这样就简化了业务层的代码。
3 流程图
关注木水小站 (zhangmushui.cn)和微信公众号【木水Code】,及时获取更多最新技术干货。