深入Android databinding的使用和原理分析

Android的databinding已经出来好久了,一直也没有用到项目中,这两天在郭霖的公众号上看到分析databinding的一篇文章,遂打算练习一下,使用之后发现非常方便,个人认为对于交互不多,展示性强的界面可以使用databinding。另外之前写过一篇android组件化开发的文章Android组件化开发实践, 在后续的开发中,发现butterknife从version8.2.0才开始支持用在library中,而且需要把R.xxx.xxx 更改为R2.xxx.xxx, 直接导致在组件化开发中使用坑非常深,因此使用databinding是一个不错的代替,虽然也有一些坑,记一次 Data Binding 在 library module 中遇到的大坑中有介绍。

基本用法

1. 依赖

在使用databinding的module的build.gradle中设置:

dataBinding { 
       enabled true
}

注意如果moduleA依赖moduleB,muduleB中使用了databinding,那么moduleA中也需要如上设置。

2. xml

使用databindind简单来讲就是帮助我们把数据data和视图view进行bind,我们需要在xml中定义这种绑定。修改后的xml分为两部分:数据定义和视图布局

<?xml version="1.0" encoding="utf-8"?>
//最外层用layout
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    //数据部分定义
    <data>
        <variable
            name="user"
            type="cn.easydone.componentizationapp.model.User" />

    </data>
    //布局部分定义
    <RelativeLayout
        android:id="@+id/activity_data_bind"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />

        <TextView
            android:id="@+id/age"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/name"
            android:text="@{String.valueOf(user.age)}" />
    </RelativeLayout>
</layout>

可以看到在数据部分我们定义了一个User类的变量user,User类定义在路径

cn.easydone.componentizationapp.model.User

下。之后在布局文件中就可以使用user变量进行text的设置了。但是user是什么时候进行设置呢:

3. 数据的绑定

在代码中要修改原来

setContentView(R.layout.activity_data_bind )
ActivityDataBindBinding bindBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_bind);
final User user = new User();
bindBinding.setUser(user);

可以看到通过binding把user set进去即可,使用很方便。其中ActivityDataBindBinding是根据xml的名字生成的,之后在databinding原理部分会进行介绍。

4. 数据的更新

databinding的功能不仅仅是完成data和view的映射,更重要是可以实现data和view的绑定,即实现数据视图同步,更新数据后,view的展示可以实时更新。
实现这种绑定有两种方法:

方法一:继承BaseObservable类

之前我们的User类仅仅是一个普通的POJO,现在继承BaseObservable:

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.user);
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
        notifyPropertyChanged(BR.user);
    }

这样在使用setXxx后就可以直接更新view了,其中BR类是databinding生成的代码,在原理部分进行探讨。

方法二:使用Observable变量

    public final ObservableField<String> name = new ObservableField<>();
    public final ObservableInt age = new ObservableInt();

个人比较喜欢这种方式,非常方便直观。

databinding分析

再来回顾一下databinding的功能:data view绑定,即实现数据和视图的映射和同步。

1. 映射

我们在xml中分别定义了数据变量和视图布局,在编译过程中,databinding会把xml拆分为两部分,其中数据部分的xml如下:

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<Layout layout="activity_data_bind" modulePackage="cn.easydone.componentizationapp" absoluteFilePath="/Users/xxx/Documents/workspace/ComponentizationApp/App/src/main/res/layout/activity_data_bind.xml" directory="layout" isMerge="false">
  <Variables declared="true" type="cn.easydone.componentizationapp.model.User" name="user">
    <location startLine="5" startOffset="8" endLine="7" endOffset="63" /></Variables>
  <Targets>
    <Target id="@+id/activity_data_bind" tag="layout/activity_data_bind_0" view="RelativeLayout">
      <Expressions/>
      <location startLine="11" startOffset="4" endLine="28" endOffset="20" /></Target>
    <Target id="@+id/name" tag="binding_1" view="TextView">
      <Expressions>
        <Expression text="user.name" attribute="android:text">
          <Location startLine="20" startOffset="12" endLine="20" endOffset="38" />
          <TwoWay>false</TwoWay>
          <ValueLocation startLine="20" startOffset="28" endLine="20" endOffset="36" /></Expression>
      </Expressions>
      <location startLine="16" startOffset="8" endLine="20" endOffset="41" /></Target>
    <Target id="@+id/age" tag="binding_2" view="TextView">
      <Expressions>
        <Expression text="String.valueOf(user.age)" attribute="android:text">
          <Location startLine="27" startOffset="12" endLine="27" endOffset="53" />
          <TwoWay>false</TwoWay>
          <ValueLocation startLine="27" startOffset="28" endLine="27" endOffset="51" /></Expression>
      </Expressions>
      <location startLine="22" startOffset="8" endLine="27" endOffset="56" /></Target>
  </Targets>
</Layout>

布局部分的xml如下:

<?xml version="1.0" encoding="utf-8"?>
                                                                  
    <RelativeLayout
        android:id="@+id/activity_data_bind"
        android:layout_width="match_parent"
        android:layout_height="match_parent" android:tag="layout/activity_data_bind_0" xmlns:android="http://schemas.android.com/apk/res/android">

        <TextView
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:tag="binding_1"     />

        <TextView
            android:id="@+id/age"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/name"
            android:tag="binding_2"                    />
    </RelativeLayout>

注意其中id和tag的设置。
之后根据两个xml在编译时生成 ActivityDataBindBinding和BR类,其中ActivityDataBindBinding的名字是根据xml的命名生成的,我们看一下其构造函数:

    public ActivityDataBindBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
        super(bindingComponent, root, 2);
        final Object[] bindings = mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds);
        this.activityDataBind = (android.widget.RelativeLayout) bindings[0];
        this.activityDataBind.setTag(null);
        this.age = (android.widget.TextView) bindings[2];
        this.age.setTag(null);
        this.name = (android.widget.TextView) bindings[1];
        this.name.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

构造函数中根据上文提到的tag对view变量进行了赋值,之后设置tag为null(至此tag的任务已经完成),这里就完成了findViewById的任务。

2. 绑定

数据绑定的完成是在ActivityDataBindBindingexecuteBindings方法中:

@Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        android.databinding.ObservableInt ageUser = null;
        android.databinding.ObservableField<java.lang.String> nameUser = null;
        java.lang.String nameUser1 = null;
        cn.easydone.componentizationapp.model.User user = mUser;
        int ageUser1 = 0;
        java.lang.String stringValueOfStringA = null;

        if ((dirtyFlags & 0xfL) != 0) {
            if ((dirtyFlags & 0xdL) != 0) {
                    if (user != null) {
                        // read user.age
                        ageUser = user.age;
                    }
                    updateRegistration(0, ageUser);
                    if (ageUser != null) {
                        // read user.age.get()
                        ageUser1 = ageUser.get();
                    }
                    // read String.valueOf(user.age.get())
                    stringValueOfStringA = java.lang.String.valueOf(ageUser1);
            }
            if ((dirtyFlags & 0xeL) != 0) {
                    if (user != null) {
                        // read user.name
                        nameUser = user.name;
                    }
                    updateRegistration(1, nameUser);
                    if (nameUser != null) {
                        // read user.name.get()
                        nameUser1 = nameUser.get();
                    }
            }
        }

        // batch finished
        if ((dirtyFlags & 0xdL) != 0) {
            // api target 1
            android.databinding.adapters.TextViewBindingAdapter.setText(this.age, stringValueOfStringA);
        }
        if ((dirtyFlags & 0xeL) != 0) {
            // api target 1
            android.databinding.adapters.TextViewBindingAdapter.setText(this.name, nameUser1);
        }
    }

可以看到把user 的name和age设置到了textview上,之后每次数据的更新,都会执行executeBindings方法进行视图View的更新。
我们来看一下BR类的作用:

public class BR {
        public static final int _all = 0;
        public static final int user = 1;
}

BR类非常简单,其中的常量是一种标识符,标识一个会发生变化的数据,当数据改变后,可以用该标识符通知 DataBinding,很快,**DataBinding **就会用新的数据去更新UI。

3. 实时更新

这里我们就文中例子进行分析,如有错误之处请进行指正。我们再来看一下定义的User类:

public class User  {
    public final ObservableField<String> name = new ObservableField<>();
    public final ObservableInt age = new ObservableInt();
}

首先看一下ObservableField类

    /**
     * Set the stored value.
     */
    public void set(T value) {
        if (value != mValue) {
            mValue = value;
            notifyChange();
        }
    }

可以看到当进行值的设置时,进行了notifychange,我们看一下这里是通知谁进行更新,跟踪代码,可以发现ActivityDataBindBinding的父类ViewDataBinding中的

        @Override
        public void onPropertyChanged(Observable sender, int propertyId) {
            ViewDataBinding binder = mListener.getBinder();
            if (binder == null) {
                return;
            }
            Observable obj = mListener.getTarget();
            if (obj != sender) {
                return; // notification from the wrong object?
            }
            binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
        }

被调用,之后调用了handleFieldChangerequestRebind -> requestRebind -> executePendingBindings -> executeBindings,可以看到最终调用了executeBindings,联系上文,最终在这里完成数据的更新。
这是根据代码调用关系完成分析的,我们正向进行分析datadinding的过程:
ViewDataBinding是ActivityDataBindBinding的父类,其初始化时监听attachState的变化:

    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) {
                }
            };
        }
    }

当AttachedToWindow时,执行了mRebindRunnable:

@Override
        public void run() {
            synchronized (this) {
                mPendingRebind = false;
            }
            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();
        }

mRebindRunnable实现了Runnable接口,在其run方法,执行了executePendingBindings, executePendingBindings方法中会执行ActivityDataBindBinding方法中的executeBindings,executeBindings方法是根据xml生成的,User 类中我们定义了:

public final ObservableInt age = new ObservableInt();

对应的executeBindings 方法中定义

android.databinding.ObservableInt ageUser = null;

并执行

updateRegistration(0, ageUser);

updateRegistration 调用ViewDataBinding中的

updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);

ViewDataBinding中有一个回调数组mLocalFieldObservers和一个回调处理CREATE_PROPERTY_LISTENER,updateRegistration方法中即把ObservableInt的回调绑定到了CREATE_PROPERTY_LISTENER中,这样当产生set操作时,就会回调CREATE_PROPERTY_LISTENERCREATE_PROPERTY_LISTENER实际create一个WeakPropertyListener变量:

private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
            implements ObservableReference<Observable> {
        final WeakListener<Observable> mListener;

        public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
            mListener = new WeakListener<Observable>(binder, localFieldId, this);
        }

        @Override
        public WeakListener<Observable> getListener() {
            return mListener;
        }

        @Override
        public void addListener(Observable target) {
            target.addOnPropertyChangedCallback(this);
        }

        @Override
        public void removeListener(Observable target) {
            target.removeOnPropertyChangedCallback(this);
        }

        @Override
        public void onPropertyChanged(Observable sender, int propertyId) {
            ViewDataBinding binder = mListener.getBinder();
            if (binder == null) {
                return;
            }
            Observable obj = mListener.getTarget();
            if (obj != sender) {
                return; // notification from the wrong object?
            }
            binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
        }
    }

可以看到在WeakPropertyListener的方法onPropertyChanged中进行了处理,连接之前我们分析的handleFieldChangerequestRebind -> requestRebind -> executePendingBindings -> executeBindings调用链,至此完成了对于User类中变量的监听并处理。
简单的讲,就是解析xml之后,把User类中的两个ObservableField加入到了ViewDataBinding的一个数组中,并设置了回调,当ObservableField进行数据set操作时,可以实时回调,执行executeBindings方法,对视图进行更新。

总结

databinding主要涉及到了ViewDataBinding, BaseObservable以及ViewDataBinding的内部静态类WeakPropertyListener等几个类,想分析等同学可以查看一下。
另外很重要的datadingding中对于xml的处理,还有binding类和BR类的生成,还需要继续探索。

参考

https://github.com/LyndonChin/MasteringAndroidDataBinding
http://www.jianshu.com/p/de4d50b88437

推荐阅读:

寻找卓越的(Android)软件工程师

想在Android中使用java8?你可能不再需要retrolambda了

你不知道一些神奇Android Api

Android增量编译3~5秒的背后

推荐阅读更多精彩内容