美文网首页DataBinding移动开发进阶程序员
深入Android databinding的使用和原理分析

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

作者: wutongke | 来源:发表于2016-11-13 14:29 被阅读2923次

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

    相关文章

      网友评论

      • 亲爱的Joe:错误太多了,这是简书,请认真点。
      • 吉凶以情迁:看了原理了那么应该知道为毛无法自动生成了吧,今天as抽风了,任何方法不报错也不生成。无语了
      • da27c260cc85:重复ID怎么解决呢?
      • AArman:很不过 可以转载?
        AArman:@wutongke 恩
        wutongke:@五菱老司机 标明转载地址就好

      本文标题:深入Android databinding的使用和原理分析

      本文链接:https://www.haomeiwen.com/subject/hzccpttx.html