JetPack 之 DataBinding

DataBinding 是一个基于 APT 技术的MVVM框架,DataBinding 使用了观察者模式来完成双向通知,通过 APT 技术在编译时扫描 XML 布局中的标记生成一个继承自 ViewDataBinding 的子类并写入相关绑定代码,保存在build目录下,不同版本的 android studio 具体路径不一,可以通过切换成android工程目录模式后在java(generated)目录下查找,保存类名为布局名+BindingImpl(如上面的例子layout_user.xml 对应 LayoutUserBindingImpl)。并建立相关绑定事件ID(在BR资源中存放),通过ID可以主动发起相关更新事件通知。ViewDataBinding 内部维护了观察者注册与通知并且持有 所有 View 对象以及绑定注册的model对象。

DataBinding 原理浅析

基本用法

1.在build.gradel中配置DataBinding

android {
    ...
    dataBinding{
        enabled true
    }
}

2.对View的布局文件进行配置,使用layout作为跟布局标签。使用data标签进行DataBinding的绑定。控件中可以使用@{}进行绑定,@{}为单向绑定@={}为双向绑定

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <!--DataBinding的配置文件-->
    <data>
        <!--name属性相当于声明了一个全局属性、type指定name对应的具体的数据类或是MVVM中VM-->
        <variable
                name="userInfo"
                type="com.scjd.framemodel_databinding.UserInfo"/>
    </data>
    <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"
                  android:padding="50dp"
                  android:orientation="vertical">
        <EditText android:layout_width="match_parent" android:layout_height="wrap_content"
                  android:text="@={userInfo.userName}"/>
        <EditText android:layout_width="match_parent" android:layout_height="wrap_content"
                  android:text="@{userInfo.passWord}"/>
        <View android:layout_width="wrap_content" android:layout_height="wrap_content"/>
    </LinearLayout>
</layout>

3.声明一个数据类,使用ObservableField声明属性并通过范型指定数据类型。

public class UserInfo {
    public ObservableField<String> userName = new ObservableField<>();
    public ObservableField<String> passWord = new ObservableField<>();
}

4.在Activity中进行绑定

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        var binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        var userInfo = UserInfo()
        userInfo.userName.set("xixi")
        userInfo.passWord.set("123")
        binding.userInfo = userInfo
    }
}

DataBinding绑定流程

DataBinding使用了apt技术,我们build项目时DataBinding会生成多个文件,我们可以在build文件中查看。

  • DataBinding将原有的activity_main.xml文件进行了拆分,分别是activity_mian.xmlactivity_main-layout.xml

build => intermediates => data_binding_layout_info_type_package => debug => packageDebugResources => out
可以看到生成的activity_main-layout.xml

build => intermediates => package_res => debug => layout =>
可以看到生成的activity_main.xml

找不到位置的也没关系,
首先DataBinding将初始layout文件的根元素LinearLayout 和那些在属性中使用了binding表达式 的view都被设置了 Tag,这就是activity_main.xml中的内容:

...
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:tag="layout/activity_main_0" />
...

然后DataBinding把最初布局文件中的<data>以及各个view中的 binding表达式 内容抽取出来,生成了 activtiy_main-layout.xml文件,其内容类似如下代码:

<Layout layout="activity_main" absoluteFilePath="/home/framgia/android/app/src/main/res/layout/activity_main.xml"
    directory="layout" isMerge="false" modulePackage="com.harpacrista">
    <Variables declared="true" name="viewModel" type="com.example.main.MainViewModel">
        <location endLine="8" endOffset="51" startLine="6" startOffset="8" />
</Variables>
<Imports name="View" type="android.view.View">
        <location endLine="10" endOffset="42" startLine="10" startOffset="8" />
</Imports>
xml
 <Targets>
        <Target tag="layout/activity_main_0" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text=" viewModel.text ">
                    <Location endLine="16" endOffset="41" startLine="16" startOffset="8" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="16" endOffset="39" startLine="16" startOffset="24" />
                </Expression>
            </Expressions>
            <location endLine="16" endOffset="44" startLine="14" startOffset="4" />
        </Target>
    </Targets>
</Layout>

<Layout>标签的里面,毫无疑问,有<Variables>标签和<Imports>标签。它们也包含位置location信息。<Target>标签告诉ViewModel应该映射到哪个View,绑定类型为单向还是双向,View的位置及View值的位置。
我们有一个<Target>标签的列表。在布局文件中我们可以有多个View/ViewGroup,但只有包含data-binding表达式的View/ViewGroup才会出现在这里。<Target>标签里面的<Expressions>标签表示Viewdata-binding表达式。

接着,DataBinding还生成了ActivityMainBinding.javaActivityMainBindingImpl.java

build => generated => ap_generated_sources => debug => out
=> activity的包名.databinding :
可以看到生成的DataBinding文件 ActivityMainBindingImpl

build => data_binding_base_class_source_out => debug => dataBindingGenBaseClassesDebug => out => activity的包名.databinding :
可以看到生成的DataBinding文件ActivityMainBinding

回到使用代码中生成ActivityMainBinding的那行代码:

var binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)

查看DataBindingUtil.setContentView:

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

方法中通过activity的setContentView加载布局,并通过window找到id为contentViewGroup,它是一个FrameLayout用于加载我们添加的布局文件。接下来就是bindToAddedViews方法:

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

parent中的子View就是我们布局文件中的根布局LinearLayout,所以走的是if中的代码

    private static DataBinderMapper sMapper = new DataBinderMapperImpl();
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
    }

我们还有一种通过DataBindingUtil.inflate();来生成ViewDataBinding类的方式。其内部依然是调用到了bind()方法中。

在bind()方法中又调用了DataBinderMapperImpl中的getDataBinder方法。

#DataBinderMapperImpl.getDataBinder


通过一系列的条件判断之后返回了一个ActivityMainBindingImpl对象,接下来我们看它的构造方法。

    private ActivityLoginBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 7
            , (android.widget.LinearLayout) bindings[13]
            , (androidx.appcompat.widget.AppCompatCheckBox) bindings[9]
            , (androidx.appcompat.widget.AppCompatEditText) bindings[5]
            , (androidx.appcompat.widget.AppCompatImageView) bindings[6]
            );
        this.checkBox.setTag(null);
        this.etPassword.setTag(null);
        this.ivSwichPasswrod.setTag(null);
        this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView1 = (androidx.appcompat.widget.AppCompatImageView) bindings[1];
        this.mboundView1.setTag(null);
        this.mboundView10 = (androidx.appcompat.widget.AppCompatTextView) bindings[10];
        this.mboundView10.setTag(null);
        this.mboundView11 = (androidx.appcompat.widget.AppCompatTextView) bindings[11];
        this.mboundView11.setTag(null);
        this.mboundView12 = (androidx.appcompat.widget.AppCompatTextView) bindings[12];
        this.mboundView12.setTag(null);
        this.mboundView2 = (androidx.appcompat.widget.AppCompatEditText) bindings[2];
        this.mboundView2.setTag(null);
        this.mboundView3 = (androidx.appcompat.widget.AppCompatImageView) bindings[3];
        this.mboundView3.setTag(null);
        this.mboundView4 = (androidx.appcompat.widget.AppCompatImageView) bindings[4];
        this.mboundView4.setTag(null);
        this.mboundView7 = (androidx.appcompat.widget.AppCompatTextView) bindings[7];
        this.mboundView7.setTag(null);
        this.mboundView8 = (androidx.appcompat.widget.AppCompatButton) bindings[8];
        this.mboundView8.setTag(null);
        setRootTag(root);
        // focus
        invalidateAll();
    }

我这里查看的是登录页面,但是原理是一样的。
它就是将布局中的含有databinding赋值的tag控件一一存入bindings的Object的数组中并返回。注意focus处的代码:

 @Override
    public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x100L;
        }
        requestRebind();
    }

#ViewDataBinding.java
 /**
     * @hide
     */
    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);
            }
        }
    }

最终会执行mRebindRunnable的run():


 private final Runnable mRebindRunnable = new Runnable() {
        @Override
        public void run() {
            ... 重点看这一句
            executePendingBindings();
        }
    };

public void executePendingBindings() {
        if (mContainingBinding == null) {
            executeBindingsInternal();
        } else {
            mContainingBinding.executePendingBindings();
        }
    }

public void executeBindingsInternal(){
  ...
  executeBindings();
  ...
}

最重要的是executeBindings

java
@Override
    protected void executeBindings() {
       long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        com.example.main.MainViewModel viewModel = mViewModel;
        androidx.databinding.ObservableField<java.lang.String> viewModelLoginErrorTip = null;
        java.lang.String viewModelLoginErrorTipGet = null;

        int isVisibleViewModelVI = 0;
        boolean isVisibleViewModel = false;
        java.lang.String textViewModel = null;
        android.databinding.ObservableBoolean isVisibleViewModel1 = null;
        android.databinding.ObservableField<java.lang.String> textViewModel1 = null;

        if ((dirtyFlags & 0xfL) != 0) {
              // focus--1
              if ((dirtyFlags & 0x181L) != 0) {
                    if (viewModel != null) {
                        // read viewModel.loginErrorTip
                        viewModelLoginErrorTip = viewModel.loginErrorTip;
                    }
                    updateRegistration(0, viewModelLoginErrorTip);
                    if (viewModelLoginErrorTip != null) {
                        // read viewModel.loginErrorTip.get()
                        viewModelLoginErrorTipGet = viewModelLoginErrorTip.get();
                    }
            }

            if ((dirtyFlags & 0xbL) != 0) {

                    if (viewModel != null) {
                        // read viewModel.isVisible
                        isVisibleViewModel1 = viewModel.isVisible;
                    }
                    updateRegistration(1, isVisibleViewModel1);


                    if (isVisibleViewModel1 != null) {
                        // read viewModel.isVisible.get()
                        isVisibleViewModel = isVisibleViewModel1.get();
                    }
                    if((dirtyFlags & 0xbL) != 0) {
                        if (isVisibleViewModel) {
                            dirtyFlags |= 0x20L;
                        } else {
                            dirtyFlags |= 0x10L;
                        }}
                    isVisibleViewModelVI = (isVisibleViewModel) ? (android.view.View.VISIBLE) : (android.view.View.INVISIBLE);
            }
            if ((dirtyFlags & 0xdL) != 0) {

                    if (viewModel != null) {
                        // read viewModel.text
                        textViewModel1 = viewModel.text;
                    }
                    updateRegistration(2, textViewModel1);


                    if (textViewModel1 != null) {
                        // read viewModel.text.get()
                        textViewModel = textViewModel1.get();
                    }
            }
        }

       if ((dirtyFlags & 0x181L) != 0) {
            // api target 1
            // focus --2
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView7, viewModelLoginErrorTipGet);
        }
        // batch finished
        if ((dirtyFlags & 0xbL) != 0) {
            // api target 1

            this.mboundView2.setVisibility(isVisibleViewModelVI);
        }
        if ((dirtyFlags & 0xdL) != 0) {
            // api target 1   android.databinding.adapters.TextViewBindingAdapter.setText(this.tvTest, textViewModel);
        }
    }

可以看到正是在这个方法中将viewModel中的ObsrvableField类型中的值设置在了对应的控件上。(注意focus--1 和 focus2--处的代码)

该方法总的来说做了三件事情:

1.创建并设置回调,如
android:onClick="@{presenter::saveUserName}这种表达式,会在presenter不为空的情况下,创建对应的回调,并设置到mboundView4上;

  1. 将数据模型上的值更新到UI上,如将firstName设置到mboundView1上,lastName设置到mboundView2上。可以看到,每一个<variable>标签声明的变量都有一个专属的标记位,当改变量的值被更新时,对应的脏标记位就会置为1,executeBindings的时候变回将这些变动更新到对应的控件。
  1. 在设置了双向绑定的控件上,为其添加对应的监听器,监听其变动,如:EditText上设置TextWatcher。具体的设置逻辑放置到了TextViewBindingAdapter.setTextWatcher里。源码如下,也就是创建了一个新的TextWatcher,将我们传进来的监听器包裹在其中。在这里看到了@BindingAdapter注解,这个注解实现了控件属性和代码内的方法调用的映射,编译期,数据绑定框架通过这种方式,为对应的控件生成对应的方法调用。
@BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",
            "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)
    public static void setTextWatcher(TextView view, final BeforeTextChanged before,
            final OnTextChanged on, final AfterTextChanged after,
            final InverseBindingListener textAttrChanged) {
        final TextWatcher newValue;
        if (before == null && after == null && on == null && textAttrChanged == null) {
            newValue = null;
        } else {
            newValue = new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                    if (before != null) {
                        before.beforeTextChanged(s, start, count, after);
                    }
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    if (on != null) {
                        on.onTextChanged(s, start, before, count);
                    }
                    if (textAttrChanged != null) {
                        textAttrChanged.onChange();
                    }
                }

                @Override
                public void afterTextChanged(Editable s) {
                    if (after != null) {
                        after.afterTextChanged(s);
                    }
                }
            };
        }
        final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
        if (oldValue != null) {
            view.removeTextChangedListener(oldValue);
        }
        if (newValue != null) {
            view.addTextChangedListener(newValue);
        }
    }
小结一下DataBinding的绑定过程

DataBinding渲染处理后的布局文件生成ActivityMainBindingImpl实例对象,在其构造函数中调用invalidateAll引发数据绑定。

数据更新与View更新

数据更新通知View

继续回到executeBindings中focus--处的代码:

 if (viewModel != null) {
                        // read viewModel.loginErrorTip
                        viewModelLoginErrorTip = viewModel.loginErrorTip;
                    }
                    updateRegistration(0, viewModelLoginErrorTip);

先从viewModel中获取属性loginErrorTip,在LoginViewModel中它是这么定义的:

public ObservableField<String> loginErrorTip = new ObservableField<>();

然后,对ViewModel中定义的这个值也就是viewModelLoginErrorTip注册了监听。它是ObservableField类型,也是最后设置到xml中的变量,我们修改它的值,view中就能够得到通知,那么是怎么实现的呢?
进入updateRegistration():

#ViewDataBinding.java
protected boolean updateRegistration(int localFieldId, Observable observable) {
        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
    }

 private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
        @Override
        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
            return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
        }
    };

CREATE_PROPERTY_LISTENER是WeakPropertyListener对象的工厂,负责创建一个WeakPropertyListener对象,继续看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内有持有变量WeakListener,能通过它的getListener()得到WeakListener。

 private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
        private final ObservableReference<T> mObservable;
        protected final int mLocalFieldId;
        private T mTarget;

        public WeakListener(ViewDataBinding binder, int localFieldId,
                ObservableReference<T> observable) {
            super(binder, sReferenceQueue);
            mLocalFieldId = localFieldId;
            mObservable = observable;
        }

        public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
            mObservable.setLifecycleOwner(lifecycleOwner);
        }

        public void setTarget(T object) {
            unregister();
            mTarget = object;
            if (mTarget != null) {
                mObservable.addListener(mTarget);
            }
        }

        public boolean unregister() {
            boolean unregistered = false;
            if (mTarget != null) {
                mObservable.removeListener(mTarget);
                unregistered = true;
            }
            mTarget = null;
            return unregistered;
        }

        public T getTarget() {
            return mTarget;
        }

        protected ViewDataBinding getBinder() {
            ViewDataBinding binder = get();
            if (binder == null) {
                unregister(); // The binder is dead
            }
            return binder;
        }
    }

原来它是一个弱引用,它持有ViewDataBinding以及mTarget(泛型T即VM)。

继续看updateRegistration():

    private boolean updateRegistration(int localFieldId, Object observable,
            CreateWeakListener listenerCreator) {
        if (observable == null) {
            return unregisterFrom(localFieldId);
        }
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener == null) {
            registerTo(localFieldId, observable, listenerCreator);
            return true;
        }
        if (listener.getTarget() == observable) {
            return false;//nothing to do, same object
        }
        unregisterFrom(localFieldId);
        registerTo(localFieldId, observable, listenerCreator);
        return true;
    }

mLocalFieldObservers维持了从localFieldIdWeakListener的映射。先从mLocalFieldObserverslocalFieldId对应的WeakListener,如果为null的话,就调用registerTo()进行注册;如果不为空,而且与之前注册过的不一致的话,则重新注册。那registerTo()里面如何进行注册?

    protected void registerTo(int localFieldId, Object observable,
            CreateWeakListener listenerCreator) {
        if (observable == null) {
            return;
        }
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener == null) {
            listener = listenerCreator.create(this, localFieldId);
            mLocalFieldObservers[localFieldId] = listener;
        }
        listener.setTarget(observable);
    }

通过CreateWeakListener.create()得到WeakListener之后,registerTo()WeakListener存储在mLocalFieldObservers中。然后调用WeakListenersetTarget()

listener是WeakListener对象,查看WeakListener.setTarger:

#WeakListener.java

private final ObservableReference<T> mObservable;
public WeakListener(ViewDataBinding binder, int localFieldId,
                ObservableReference<T> observable) {
            super(binder, sReferenceQueue);
            mLocalFieldId = localFieldId;
            mObservable = observable;
        }
 public void setTarget(T object) {
            unregister();
            mTarget = object;
            if (mTarget != null) {
                mObservable.addListener(mTarget);
            }
        }

mTager是被观察者,在我们例子中是updateRegistration中传进来的viewModelLoginErrorTip,也就是viewModel中定义的ObservableField对象
mObservableWeakPropertyListener对象,是构建WeakListener时传进来的,WeakListener构造函数是WeakPropertyListener构造函数中传进来的

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

WeakPropertyListener对象是在CREATE_PROPERTY_LISTENER.create中创建的:

private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
        @Override
        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
            return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
        }
    };

create是在registerTo中调用的。
通过 CreateWeakListener.create()得到WeakListener之后,registerTo()WeakListener存储在mLocalFieldObservers中。然后调用WeakListenersetTarget()

查看WeakPropertyListener.addListener

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

targetObservableFiledthisWeakPropertyListener
addOnPropertyChangedCallback的具体实现是在BaseObservable,ObservableFiled继承了BaseObservable

#BaseObservable.java
 private transient PropertyChangeRegistry mCallbacks;
...
  @Override
    public void addOnPropertyChangedCallback(@NonNull OnPropertyChangedCallback callback) {
        synchronized (this) {
            if (mCallbacks == null) {
                mCallbacks = new PropertyChangeRegistry();
            }
        }
        mCallbacks.add(callback);
    }

addOnPropertyChangedCallback里,将WeakPropertyListener加入到了mCallbacks(PropertyChangeRegistry)里面,PropertyChangeRegistry继承了CallbackRegistry<Observable.OnPropertyChangedCallback, Observable, Void>,其内部有一个List<WeakPropertyListener> mCallbacksWeakPropertyListener 继承了Observable.OnPropertyChangedCallback

这样最终是将WeakPropertyListener监听添加到了PropertyChangeRegistry的mCallbacks中。`也就是将数据变化的监听加入到了集合中。

那么监听什么时候回调的?
查看ObservableField.set

 public void set(T value) {
        if (value != mValue) {
            mValue = value;
            notifyChange();
        }
    }

当我们set值时,会调用到notifyChange(),最后会调用到NotifierCallback.onNotifyCallback(),NotifierCallback是抽象类,其实现类是:

# PropertyChangeRegistry.java

private static final CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void> NOTIFIER_CALLBACK = new CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void>() {
        @Override
        public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,
                int arg, Void notUsed) {
            callback.onPropertyChanged(sender, arg);
        }
    };

因此调用mNotifier.onNotifyCallback实际上就是调用Observable.OnPropertyChangedCallback .onPropertyChanged(),我们进一步看看:

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

    // …… 

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

再进入ViewDataBinding的handleFieldChange():

    private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
        boolean result = onFieldChange(mLocalFieldId, object, fieldId);
        if (result) {
            requestRebind();
        }
    }

onFieldChange方法是ViewDataBinding的抽象方法,其实现类:

@Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
            case 0 :
                return onChangeViewModelLoginErrorTip((androidx.databinding.ObservableField<java.lang.String>) object, fieldId);
      ...
}

private boolean onChangeViewModelLoginErrorTip(androidx.databinding.ObservableField<java.lang.String> ViewModelLoginErrorTip, int fieldId) {
        if (fieldId == BR._all) {
            synchronized(this) {
                    mDirtyFlags |= 0x1L;
            }
            return true;
        }
        return false;
    }

可以看到修改了标志位。
可以看到onPropertyChanged调用了编译生成的ActivityLogingBindingImpl的onFieldChange方法将mDirtyFlags标识为需要重新绑定VM到V上,并且调用requestRebind方法。requestRebind方法前面已经介绍过,最后能调用到ActivityLogingBindingImpl的executeBindings()进行界面绑定的工作。

简化版

层层调用实在是太深了。我们稍微简化一下,其内部依然是观察者模式:
首先在DataBinding实现类中:

public class DemoDataBinding extends ViewDataBinding {
    CustomObservableField<String> name = new CustomObservableField<>();
    AppCompatTextView textView;

    public DemoDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
        super(bindingComponent, root, localFieldCount);
        textView = new AppCompatTextView(root.getContext());
        updateRegistration(0,name);
    }

    protected boolean updateRegistration(int localFieldId, CustomObservableField observable) {
        WeakPropertyListener weakPropertyListener = new WeakPropertyListener(this,localFieldId);

        observable.addOnPropertyChangedCallback(weakPropertyListener);
        return true;
    }
    
    // 为控件赋值
    public void executeBindingsLayout() {
        String nameStr = name.getName();
        textView.setText(nameStr);
    }
}

被观察者 - CustomObservableField。观察者 - ViewDataBinding

public class CustomObservableField<T> implements CustomObservable{
    private List<WeakPropertyListener> mCallbacks = new ArrayList<>();
    private T name;

    public void setName(T name) {
        this.name = name;
        notifyCalls(mCallbacks);
    }

    public T getName() {
        return name;
    }

    public void addOnPropertyChangedCallback(@NonNull CustomObservable.OnPropertyChangedCallback callback) {
        mCallbacks.add((WeakPropertyListener) callback);
    }

    @Override
    public void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
        mCallbacks.remove(callback);
    }

    void notifyCalls(List<WeakPropertyListener> mCallbacks){
        for(WeakPropertyListener callback:mCallbacks){
            callback.onPropertyChanged();
        }
    }

}

public interface CustomObservable {
    void addOnPropertyChangedCallback(CustomObservable.OnPropertyChangedCallback callback);
    void removeOnPropertyChangedCallback(CustomObservable.OnPropertyChangedCallback callback);
    abstract class OnPropertyChangedCallback {
        public abstract void onPropertyChanged();
    }
}
public class WeakPropertyListener extends CustomObservable.OnPropertyChangedCallback{
    private ViewDataBinding viewDataBinding;

    public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
        this.viewDataBinding = binder;
    }

    public void onPropertyChanged() {
        DemoDataBinding binder = (DemoDataBinding) viewDataBinding;
        binder.executeBindingsLayout();
    }
}

第一步,我们在DataBinding实现类的构造函数中调用updateRegistration为我们的Observable和Observer之间创建绑定关系。

第二步,调用Observable的set()方法时通知Observer进行更新。

View中的值改变通知数据更新

这种情况往往是编辑框更改了输入文字,则ViewModel中承载着文字的ObservableField变量中的值也发生了改变,因此一般我们使用编辑框时会用双向绑定的binding表达式:android:text = @={viewModel.name}

回到ActivityLoginBindingImpl的实现类的executeBindings方法中:

        if ((dirtyFlags & 0x100L) != 0) {
            // api target 1

            androidx.databinding.adapters.CompoundButtonBindingAdapter.setListeners(this.checkBox, (android.widget.CompoundButton.OnCheckedChangeListener)null, checkBoxandroidCheckedAttrChanged);
            androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etPassword, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etPasswordandroidTextAttrChanged);
            androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView2androidTextAttrChanged);
        }

在这个方法里调用了setTextWatcher去监听EditTextView的TextWatcher:

    @BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",
            "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)
    public static void setTextWatcher(TextView view, final BeforeTextChanged before,
            final OnTextChanged on, final AfterTextChanged after,
            final InverseBindingListener textAttrChanged) {
        final TextWatcher newValue;
        if (before == null && after == null && on == null && textAttrChanged == null) {
            newValue = null;
        } else {
            newValue = new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                    if (before != null) {
                        before.beforeTextChanged(s, start, count, after);
                    }
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    if (on != null) {
                        on.onTextChanged(s, start, before, count);
                    }
                    // focus--1 
                    if (textAttrChanged != null) {
                        textAttrChanged.onChange();
                    }
                }

                @Override
                public void afterTextChanged(Editable s) {
                    if (after != null) {
                        after.afterTextChanged(s);
                    }
                }
            };
        }
        final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
        if (oldValue != null) {
            view.removeTextChangedListener(oldValue);
        }
        if (newValue != null) {
            view.addTextChangedListener(newValue);
        }
    }

当我们在EditTextView中的输入发生变化时,将会回调到TextWatcher.onTextChanged(),在其中会调用textAttrChanged的onChange(),textAttrCHanged是我们在ActivityLoginBindingImpl中调用传递的InverseBindingListener对象:

#ActivityLoginBindingImpl.java
private androidx.databinding.InverseBindingListener etPasswordandroidTextAttrChanged = new androidx.databinding.InverseBindingListener() {
        @Override
        public void onChange() {
            // Inverse of viewModel.password.get()
            //         is viewModel.password.set((java.lang.String) callbackArg_0)
            java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(etPassword);
            // localize variables for thread safety
            // viewModel.password != null
            boolean viewModelPasswordJavaLangObjectNull = false;
            // viewModel.password.get()
            java.lang.String viewModelPasswordGet = null;
            // viewModel
            com.cmnit.sign.ui.login.LoginViewModel viewModel = mViewModel;
            // viewModel.password
            androidx.databinding.ObservableField<java.lang.String> viewModelPassword = null;
            // viewModel != null
            boolean viewModelJavaLangObjectNull = false;



            viewModelJavaLangObjectNull = (viewModel) != (null);
            if (viewModelJavaLangObjectNull) {


                viewModelPassword = viewModel.password;

                viewModelPasswordJavaLangObjectNull = (viewModelPassword) != (null);
                if (viewModelPasswordJavaLangObjectNull) {




                    viewModelPassword.set(((java.lang.String) (callbackArg_0)));
                }
            }
        }
    };

第一步,获取控件中输入的值
第二步,通过ObservableField.set()将值赋值给viewModel中的viewModelPassword对象
第三步,更新数据之后,判断新旧数据是否一致,不一致的话会再更新一次UI。

实现中会比对新旧数据是否一致,不一致的情况下才进行更新,这样也避免了:设置数据 -> 触发数据变动回调 -> 更新数据 -> 再次触发数据变动回调 -> ...引起的死循环问题。

Q:Databinding的优点是什么?

  • 无需多处调用控件,原本调用的地方只需要set数据即可
  • 无需手动判空
  • 完全不用写模板代码 findViewById
  • 引入DataBinding后,原本的 UI 逻辑无需改动,只需设置终态数据

Q:Databinding中使用了什么设计模式?是如何使用的?

DataBinding中使用了观察者模式, 我们在activity中把 mUser对象传入了binding类,在每次对它进行set操作的时候都会触发notify, 之后DataBinding框架会回调execute方法, 框架通过注解拿到get方法,然后拿到和UI所对应的数据,之后结合layout中对应的标注去更新UI。

Q:Databinding中的数据变化是如何更新到UI上的?UI上的状态变化又是如何更新到数据上的?

当我们setViewModel到为ActivityMainBinding时,将会调用到requestRebind()方法,而requestRebind最终会调用到executeBindings()方法中的updateRegistration(),此方法将用于在ViewModel和View之间创建一个“桥”,也就是WeakPropertyListener,它持有MainDataBinding的一个弱引用,WeakListener中的setTarget方法被调用将绑定ViewModel到MainDataBinding,setTarget被调用的时机是updateRegistration中的registerTo()方法。从ViewModel修改isShowView变量会调用WeakPropertyListener的onPropertyChanged(),在此方法中会调用requestRebind(),最终又内聚到了ViewDataBinding中的executeBindings()来刷新View。

在设置了双向绑定的控件上,为其添加了对应的监听器,监听其变动,如EditText上设置TextWatacher。具体的设置逻辑放置到了TextViewBindingAdapter.setTextWatcher里,也就是创建了一个新的TextWatcher,将我们传进来的监听器包裹在其中。为了监听代码改动我们传入的监听器是一个androidx.databinding.InverseBindingListener对象对应TextViewBindingAdapter.setTextWatcher的第四个参数,当数据发生变化的时候,TextWatch在回调onTextChanged的最后,会通过InverseBindingListener发送通知,InverseBindingListener的实现中,会去对应的View中取得控件中最新的值,并检查*Binding类是否为空,非空的话则调用对应的方法更新数据。

Q: 以activity_main.xml谈谈Databinding的初始化。

  • 对布局文件进行预处理
    DataBinding会对根布局为<layout>的布局文件进行预处理,生成三个额外的文件(正常布局版本)activity_main.xml、(绑定布局版本)activity_main-layout.xml
    在activity_main.xml文件中,根元素和使用了binding表达式的view将被设置Tag。而在activity_main-layout.xml文件中,包含了<layout>标签、data标签 、Tag标记以及里面的 variable标签,还有各个view中的 binding表达式。
  • 生成ActivityMainBinding与BR类
    DataBinding 将会依据上面两个xml文件(即 activtiy_main.xml 和 activtiy_main-layout.xml)生成两个类,一个类是 ActivityMainBinding,它继承自 ViewDataBinding,里面包含了使用了Binding表达式的view控件变量以及我们传入布局variable字段下的变量;
    另一个类是BR类,它的内容非常简单:
    _all变量是默认生成的,user变量是对应 ActivityMainBinding类 中传入的 variable字段下的mUser变量
  • 生成ActivityMainBinding实例并绑定
    1 --Inflate处理后的布局文件,DataBindingUtil 将会 渲染activity_main.xml文件,得到一个ViewGroup变量root;
    2 --生成 ActivityMainBinding实例对象,DataBindingUtil 会将这个 变量root 传递给 ActivityMainBinding的构造方法,生成一个 ActivityMainBinding的实例。在ActivityMainBinding的构造方法中将会调用ViewDataBinding的mapBindings方法,通过对root view进行一次遍历,将view中所有的控件查找出来并根据对应的tag进行绑定,初始化相应的field,查找效率比使用findViewById更加高效。在setTagRoot()中,将 ActivityMainBinding 和布局文件中的 LinearLayout 关联起来了。
    最后调用invalidateAll()引发数据绑定。
    3 --数据绑定
    ActivityMainBinding 将会计算 各个view上的 binding表达式,然后赋值给view相应的属性。

Q:activity_main.xml、activity_main-layout.xml和ActivityMainBinding.java的关系?(data-binding编译器是怎样生成代码的?)

编译器的核心为compiler.android.databinding.annotationprocessor包下的ProcessDataBinding类。这个类的职责是一步一步执行处理列表。

  mProcessingSteps = Arrays.asList(
                new ProcessMethodAdapters(),
                new ProcessExpressions(),
                new ProcessBindable()
        );
  • 我们先看第一个处理步骤——ProcessMethodAdapters。这个类提供搜索工程中所有的类,哪一个类哪一个方法添加了下面的注解:@BindingAdapter, @Untaggable, @BindingMethods, @BindingConversion, @InverseBindingAdapter, @InverseBindingMethods。并且把它们保存在SetterStore,后面应该在executeBinding用到正如我们上面所说。在编译期间,注解处理器拿到的这些信息会被存放在setter_store.bin文件中。
  • 第二步是ProcessExpressions处理表达式。在这一步中会搜索工程中所有xml文件并且会转换最外层为<layout></layout>标签支持data-binding的xml文件。会把这个文件拆分为2个文件正如第一部分所提到的:activity_main.xml(正常的布局文件)和activity_main-layout.xml(包含绑定信息)。LayoutBinder是最有意思的类,它使用(XmlParser中)layoutBundle在activity_main-layout.xml中计算表达式,位置和目标。
  • 第三步是ProcessBindable。这个处理生成BR类,绑定属性的id,例如:BR.text, BR.item, BR.isShowView, ...

Android DataBinding 数据绑定

DataBinding实现原理探析

(译)深入理解Data Binding原理

Android - DataBinding源码解读(内存消耗和双向绑定原理分析)

“终于懂了“系列:Jetpack AAC完整解析(五)DataBinding 重新认知!

MVVM架构篇之databinding机制解析

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

推荐阅读更多精彩内容