深入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秒的背后

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容