DataBinding原理分析

前言

現在公司用的框架是MVVM,用到MVVM肯定少不了databinding

最近業務上遇到一個問題,需要動態生成一張圖片,並將圖片分享出去。我的思路就是寫一個xml動態生成一個View,利用databinding綁定這個View,然後獲取數據刷新對應的ViewModel,再將View轉化成Bitmap分享出去。結果發現,數據並沒有填充到View上。這是爲什麼呢?

當時的猜想,可能是因爲我需要的這個View並沒有加載到屏幕上而databinding應該是和生命週期有關的,所以databinding不做處理。

這週末正好有空,所以來了解一下databinding。

databinding生成的文件

我們使用databinding,會綁定一個xml,這個過程中,編譯器會默默地幫我們生成一系列文件,如下圖:
這裏寫圖片描述
當然以上只是部分,還有xml文件等等。

activity_test.xml

位於app/build/intermediates/data-binding-layout-out/debug/layout目錄下

<?xml version="1.0" encoding="utf-8"?>









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

        <TextView
            android:id="@+id/content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:tag="binding_1"                   />
    </RelativeLayout>

對比最初的activity_test.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="viewModel"
            type="com.example.tsnt.mvvm.TestVM" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:text="@{viewModel.bottomContent}" />
    </RelativeLayout>
</layout>

可以看出,生成的xml與原始的xml差不多,區別是:
1.去掉了layout標籤
2.增加了tag標記,這裏是android:tag="layout/activity_test_0"

我們使用DataBinding添加layout標籤後的佈局文件會經過DataBinding處理成正常的佈局文件activity_test.xml與包含綁定信息的文件activity_test-layout.xml

activity_test-layout.xml

位置於app/build/intermediates/data-binding-info/debug目錄下:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout absoluteFilePath="/Users/zhangxiaozong/Documents/projects/others/AndroidStudy/app/src/main/res/layout/activity_test.xml" directory="layout"
    isMerge="false"
    layout="activity_test" modulePackage="com.example.tsnt">
    <Variables name="viewModel" declared="true" type="com.example.tsnt.mvvm.TestVM">
        <location endLine="7" endOffset="49" startLine="5" startOffset="8" />
    </Variables>
    <Targets>
        <Target tag="layout/activity_test_0" view="RelativeLayout">
            <Expressions />
            <location endLine="26" endOffset="20" startLine="10" startOffset="4" />
        </Target>
        <Target tag="binding_1" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="viewModel.bottomContent">
                    <Location endLine="25" endOffset="52" startLine="25" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="25" endOffset="50" startLine="25" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="25" endOffset="55" startLine="20" startOffset="8" />
        </Target>
        <Target id="@+id/content" view="TextView">
            <Expressions />
            <location endLine="18" endOffset="50" startLine="14" startOffset="8" />
        </Target>
    </Targets>
</Layout>

它會告訴我們生成的activity_test.xml對應的tag的數據信息。

ActivityTestBinding

這就是我們生成之後可以直接調用的ViewDataBinding

public class ActivityTestBinding extends android.databinding.ViewDataBinding  {

    private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = new android.util.SparseIntArray();
        sViewsWithIds.put(R.id.content, 2);
    }
    // views
    public final android.widget.TextView content;
    private final android.widget.RelativeLayout mboundView0;
    private final android.widget.TextView mboundView1;
    // variables
    private com.example.tsnt.mvvm.TestVM mViewModel;
    ……
}

其中可以看到ActivityTestBinding裏面持有了一些view的變量,那是不是佈局中所有的view都會在ActivityTestBinding中?其實不是,ActivityTestBinding裏的view變量有三種類型:
根佈局
含有@{}綁定的view
含有id屬性的view

BR

位於app/build/generated/source/apt/debug/com/android/databinding/library/baseAdapters目錄下:

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

DataBinderMapper

位於app/build/generated/source/apt/debug/android/databinding/目錄下:

class DataBinderMapper {
    final static int TARGET_MIN_SDK = 16;
    public DataBinderMapper() {
    }
    public android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View view, int layoutId) {
        switch(layoutId) {
                case com.example.tsnt.R.layout.activity_test:
                    return com.example.tsnt.databinding.ActivityTestBinding.bind(view, bindingComponent);
        }
        return null;
    }
    android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View[] views, int layoutId) {
        switch(layoutId) {
        }
        return null;
    }
    int getLayoutId(String tag) {
        if (tag == null) {
            return 0;
        }
        final int code = tag.hashCode();
        switch(code) {
            case 628154894: {
                if(tag.equals("layout/activity_test_0")) {
                    return com.example.tsnt.R.layout.activity_test;
                }
                break;
            }
        }
        return 0;
    }
    String convertBrIdToString(int id) {
        if (id < 0 || id >= InnerBrLookup.sKeys.length) {
            return null;
        }
        return InnerBrLookup.sKeys[id];
    }
    private static class InnerBrLookup {
        static String[] sKeys = new String[]{
            "_all"
            ,"viewModel"};
    }
}

DataBinderMapper主要提供了從佈局文件layoutId到ViewBinding類的映射

綁定ViewModel的過程

首先來看TestActivity主要代碼:

public class TestActivity extends AppCompatActivity {

    private ActivityTestBinding dataBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initView();
    }

    private void initView() {
        dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_test);
        dataBinding.content.setText("Hello, databinding!");
        dataBinding.setViewModel(new TestVM());
    }
}

其中關鍵的代碼是:dataBinding.setViewModel(new TestVM());
這裏調用的是ActivityTestBinding的setViewModel()

    public void setViewModel(com.example.tsnt.mvvm.TestVM viewModel) {
        this.mViewModel = viewModel;
        synchronized(this) {
            mDirtyFlags |= 0x2L;
        }
        notifyPropertyChanged(BR.viewModel);
        super.requestRebind();
    }

這裏通過notifyPropertyChanged(BR.viewModel)去發出ViewModel數據變化的通知。
然後進入ViewDataBinding的requestRebind()

    protected void requestRebind() {
        synchronized (this) {
            if (mPendingRebind) {
                return;
            }
            mPendingRebind = true;
        }
        if (USE_CHOREOGRAPHER) {
            mChoreographer.postFrameCallback(mFrameCallback);
        } else {
            mUIThreadHandler.post(mRebindRunnable);
        }

    }

其中mPendingRebind的定義如下:

    /**
     * Flag indicates that there are pending bindings that need to be reevaluated.
     */
    private boolean mPendingRebind = false;

在requestRebind()中獲取同步鎖,如果已經處於pendingRebind狀態就直接返回,如果不是pendingRebind狀態就繼續,並且標記mPendingRebind爲true

再來看USE_CHOREOGRAPHER的定義:

private static final boolean USE_CHOREOGRAPHER = SDK_INT >= 16;

這裏根據系統版本做了點不同的處理,16及以上的,會往mChoreographer發一個mFrameCallback;否則直接往UI線程發一個mRebindRunnable。其實這裏倆個分支的結果基本一致,mChoreographer會在界面刷新時執行mRebindRunnable,Choreographer是系統版本16後引入的用於解決UI卡頓的,當收到VSYNC(定時中斷)時,在doFrame()裏去執行相應的操作。

Android Choreographer和VSYNC機制

接着我們來看mRebindRunnable

    /**
     * Runnable executed on animation heartbeat to rebind the dirty Views.
     */
    private final Runnable mRebindRunnable = new Runnable() {
        @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();
        }
    };

這裏又將mPendingRebind置爲false,然後對系統版本進行了判斷。如果是19及以上時,會判斷View是否加載到了屏幕上,如果沒有的話,則對這個attach的狀態進行監聽。這就是我遇到問題的原因,因爲View一直沒有加載到屏幕上,所以監聽也一直沒回調。

正常情況下,最終都會執行executePendingBindings()

    /**
     * Evaluates the pending bindings, updating any Views that have expressions bound to
     * modified variables. This <b>must</b> be run on the UI thread.
     */
    public void executePendingBindings() {
        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;
    }

如果mIsExecutingPendingBindings爲true,表明當前已經在執行這段代碼了,則調用一次requestRebind()

如果hasPendingBindings()返回false,表明當前沒有需要需要綁定的數據,則返回不處理。

接下來又進行了一系列判斷,如果沒被終止,會進入executeBindings(),執行最終綁定。executeBindings()是一個抽象方法,由ViewDataBinding的子類去實現。

看到ActivityTestBinding的executeBindings()

    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        com.example.tsnt.mvvm.TestVM viewModel = mViewModel;
        android.databinding.ObservableField<java.lang.String> bottomContentViewMod = null;
        java.lang.String bottomContentViewMod1 = null;

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



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


                if (bottomContentViewMod != null) {
                    // read viewModel.bottomContent.get()
                    bottomContentViewMod1 = bottomContentViewMod.get();
                }
        }
        // batch finished
        if ((dirtyFlags & 0x7L) != 0) {
            // api target 1

            android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, bottomContentViewMod1);
        }
    }

這裏做的操作就是綁定ViewModel到對應的View,ActivityTestBinding就持有了ViewModel的引用。

然後對ViewModel中綁定的參數註冊了監聽,最後對View中的數據進行初始化。

接下來看如何註冊監聽的,進入updateRegistration()

return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);

來看CREATE_PROPERTY_LISTENER的定義:

    /**
     * Method object extracted out to attach a listener to a bound Observable object.
     */
    private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
        @Override
        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
            return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
        }
    };

然後繼續看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);
        }
    }

CREATE_PROPERTY_LISTENER是一個CreateWeakListener對象,它是一個創建WeakPropertyListener的工廠類

WeakPropertyListener內有變量WeakListener,能通過它的getListener()得到WeakListener。

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);
            mLocalFieldId = localFieldId;
            mObservable = observable;
        }

        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維持了從localFieldId到WeakListener的映射。先從mLocalFieldObservers取localFieldId對應的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中。然後調用WeakListener的setTarget()。

然後調用mObservable的addListener(),mObservable就是在構造WeakListener傳入的第三個參數。以上的代碼,WeakPropertyListener在構造函數裏構造WeakListener是傳入的是this,因此mObservable其實就是WeakPropertyListener

在addListener()中,調用Observable的addOnPropertyChangedCallback(),參數爲this。BaseObservable實現了接口Observable,在addOnPropertyChangedCallback裏,將WeakPropertyListener加入到了mCallbacks(PropertyChangeRegistry)裏面

@Override
public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
    synchronized (this) {
        if (mCallbacks == null) {
            mCallbacks = new PropertyChangeRegistry();
        }
    }
    mCallbacks.add(callback);
}

這裏寫圖片描述

可以看到這個圖與MVVM的架構圖有點類似。

這樣一來V和VM的聯繫就通過ViewDatabinding建立起來了。V(Activity)內有ViewDatabinding,而ViewDatabinding裏持有各個V(佈局中的元素)的引用。ViewDataBinding有VM的變量,而VM內的PropertyChangeRegistry監聽實則爲WeakPropertyListener,WeakPropertyListener得到的WeakListener能獲取到ViewDatabinding的引用。

VM變化如何通知View

我們知道,如果要達到VM變化時自動綁定到View上,有下面倆種方式:

  1. 繼承自BaseObservable,在getter上增加@Bindable註解,在setter裏增加代碼notifyPropertyChanged(BR.xxx)。

  2. 無需繼承,需要將屬性替換爲Observable類,例如ObservableInt、ObservableField等。

這兩種方式本質上都是一樣的。在第二種方式中,當屬性發生變化時,會調用set()進而調用notifyChange(),而notifyChange()notifyPropertyChanged()做的事情都是一樣的,都是調用mCallbacks.notifyCallbacks()去發出通知:

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

    /**
     * Notifies listeners that all properties of this instance have changed.
     */
    public synchronized void notifyChange() {
        if (mCallbacks != null) {
            mCallbacks.notifyCallbacks(this, 0, null);
        }
    }

    /**
     * Notifies listeners that a specific property has changed. The getter for the property
     * that changes should be marked with {@link Bindable} to generate a field in
     * <code>BR</code> to be used as <code>fieldId</code>.
     *
     * @param fieldId The generated BR id for the Bindable field.
     */
    public void notifyPropertyChanged(int fieldId) {
        if (mCallbacks != null) {
            mCallbacks.notifyCallbacks(this, fieldId, null);
        }
    }

這裏的mCallbacks就是剛纔添加的WeakPropertyListener

我們一層層往下跟蹤,notifyCallbacks(CallbackRegistry) -> notifyRecurse(CallbackRegistry) -> notifyRemainder(CallbackRegistry) -> notifyFirst64(CallbackRegistry),最後到達下面這個方法:

    private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
            final int endIndex, final long bits) {
        long bitMask = 1;
        for (int i = startIndex; i < endIndex; i++) {
            if ((bits & bitMask) == 0) {
                mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
            }
            bitMask <<= 1;
        }
    }

來看下mNotifier的定義:

    /** The notification mechanism for notifying an event. */
    private final NotifierCallback<C, T, A> mNotifier;

而mNotifier其實就是在BaseObservable構造PropertyChangeRegistry傳入的參數:

    @Override
    public synchronized void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
        if (mCallbacks == null) {
            mCallbacks = new PropertyChangeRegistry();
        }
        mCallbacks.add(callback);
    }

public class PropertyChangeRegistry extends
        CallbackRegistry<Observable.OnPropertyChangedCallback, Observable, Void> {

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

    public PropertyChangeRegistry() {
        super(NOTIFIER_CALLBACK);
    }

    /**
     * Notifies registered callbacks that a specific property has changed.
     *
     * @param observable The Observable that has changed.
     * @param propertyId The BR id of the property that has changed or BR._all if the entire
     *                   Observable has changed.
     */
    public void notifyChange(Observable observable, int propertyId) {
        notifyCallbacks(observable, propertyId, null);
    }
}

因此調用mNotifier.onNotifyCallback實際上就是調用mCallbacks.get(i).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();
        }
    }

可以看到onPropertyChanged調用了編譯生成的ActivityTestBinding的onFieldChange方法將mDirtyFlags標識爲需要重新綁定VM到V上,並且調用requestRebind方法。requestRebind方法前面已經介紹過,最後能調用到ActivityTestBinding的executeBindings()進行界面綁定的工作。

V的變化如何同步到VM

DataBinding在舊版本中是不支持這個功能的,後來才完善了這個功能。

現在對最初的xml進行修改:把android:text="@{viewModel.bottomContent}"改成android:text="@{viewModel.bottomContent}"

在使用雙向綁定後,看看生成的ActivityMainBinding有什麼變化?可以發現在executeBindings裏多了一點代碼:

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

            android.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView1, (android.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView1androidTe);
        }

在這個方法裏調用了setTextWatcher去監聽TextView的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);
                    }
                    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);
        }
    }

當V發生變化時,會調用textAttrChanged的onChange()即ActivityDataBinding中setTextWatcher()時傳入的成員變量的onChange()

    private android.databinding.InverseBindingListener mboundView1androidTe = new android.databinding.InverseBindingListener() {
        @Override
        public void onChange() {
            // Inverse of viewModel.bottomContent.get()
            //         is viewModel.bottomContent.set((java.lang.String) callbackArg_0)
            java.lang.String callbackArg_0 = android.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView1);
            // localize variables for thread safety
            // viewModel.bottomContent
            android.databinding.ObservableField<java.lang.String> bottomContentViewMod = null;
            // viewModel != null
            boolean viewModelObjectnull = false;
            // viewModel
            com.example.tsnt.mvvm.TestVM viewModel = mViewModel;
            // viewModel.bottomContent != null
            boolean BottomContentViewMod1 = false;
            // viewModel.bottomContent.get()
            java.lang.String BottomContentViewMod2 = null;



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


                bottomContentViewMod = viewModel.bottomContent;

                BottomContentViewMod1 = (bottomContentViewMod) != (null);
                if (BottomContentViewMod1) {




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

在上面textAttrChanged的onChange()回調裏,將變動後的值賦值到VM上。這樣,V的變化就自動同步到VM上了。

如何避免findViewById

在初始化時,我們是通過DataBindingUtil.setContentView()來建立layout與ViewBinding的聯繫,因此從這個方法開始分析:

    public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId) {
        return setContentView(activity, layoutId, sDefaultComponent);
    }

    public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId,
            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);
    }

setContentView()直接調用同名方法,而在這個同名方法內,先設置爲activity的contentView,然後將contentView傳入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);
    }
}

static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
        int layoutId) {
    return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}

獲取到layout中的根佈局,並且調用bind方法。bind方法內調用DataBinderMapper的getDataBinder()。DataBinderMapper前面介紹過,也是編譯時生成的類,主要是建立layout與ViewBinding之間的映射

    public android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View view, int layoutId) {
        switch(layoutId) {
                case com.example.tsnt.R.layout.activity_test:
                    return com.example.tsnt.databinding.ActivityTestBinding.bind(view, bindingComponent);
        }
        return null;
    }

    public static ActivityTestBinding bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {
        if (!"layout/activity_test_0".equals(view.getTag())) {
            throw new RuntimeException("view tag isn't correct on view:" + view.getTag());
        }
        return new ActivityTestBinding(bindingComponent, view);
    }

這裏根據layoutId調用對應ViewBinding的bind方法,ViewBinding的名稱是根據layoutId生成的,例如activity_test對應的ViewBinding名稱爲ActivityTestBinding,然後在ActivityTestBinding的靜態方法bind()中構造了一個ActivityTestBinding對象

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

在構造函數裏面,我們看到對button、mboundView0、mboundView1進行了強轉,因此可以知道mapBindings得到的bindings數組就是View數組。mapBindings方法比較長,我們分成三部分來看看:

① 從view的tag中獲取緩存,防止多次初始化

        final int indexInIncludes;
        final ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding != null) {
            return;
        }

② 將view存儲在bindings數組內,分爲三種情況,與前面生成的ViewBinding內的view變量類型一致,一爲根佈局,tag以layout開頭;二爲設置了@{}的,tag以binding開頭;三爲設置了id屬性的。

        Object objTag = view.getTag();
        final String tag = (objTag instanceof String) ? (String) objTag : null;
        boolean isBound = false;
        if (isRoot && tag != null && tag.startsWith("layout")) {
            final int underscoreIndex = tag.lastIndexOf('_');
            if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
                final int index = parseTagInt(tag, underscoreIndex + 1);
                if (bindings[index] == null) {
                    bindings[index] = view;
                }
                indexInIncludes = includes == null ? -1 : index;
                isBound = true;
            } else {
                indexInIncludes = -1;
            }
        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
            int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
            if (bindings[tagIndex] == null) {
                bindings[tagIndex] = view;
            }
            isBound = true;
            indexInIncludes = includes == null ? -1 : tagIndex;
        } else {
            // Not a bound view
            indexInIncludes = -1;
        }
        if (!isBound) {
            final int id = view.getId();
            if (id > 0) {
                int index;
                if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
                        bindings[index] == null) {
                    bindings[index] = view;
                }
            }
        }

③ 這部分判斷如果是ViewGroup的話,則判斷子View是不是include的,如果是的話,則使用DataBindingUtil.bind進行遞歸;如果不是include,則直接使用mapBindings進行遞歸。

        if (view instanceof  ViewGroup) {
            final ViewGroup viewGroup = (ViewGroup) view;
            final int count = viewGroup.getChildCount();
            int minInclude = 0;
            for (int i = 0; i < count; i++) {
                final View child = viewGroup.getChildAt(i);
                boolean isInclude = false;
                if (indexInIncludes >= 0 && child.getTag() instanceof String) {
                    String childTag = (String) child.getTag();
                    if (childTag.endsWith("_0") &&
                            childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
                        // This *could* be an include. Test against the expected includes.
                        int includeIndex = findIncludeIndex(childTag, minInclude,
                                includes, indexInIncludes);
                        if (includeIndex >= 0) {
                            isInclude = true;
                            minInclude = includeIndex + 1;
                            final int index = includes.indexes[indexInIncludes][includeIndex];
                            final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
                            int lastMatchingIndex = findLastMatching(viewGroup, i);
                            if (lastMatchingIndex == i) {
                                bindings[index] = DataBindingUtil.bind(bindingComponent, child,
                                        layoutId);
                            } else {
                                final int includeCount =  lastMatchingIndex - i + 1;
                                final View[] included = new View[includeCount];
                                for (int j = 0; j < includeCount; j++) {
                                    included[j] = viewGroup.getChildAt(i + j);
                                }
                                bindings[index] = DataBindingUtil.bind(bindingComponent, included,
                                        layoutId);
                                i += includeCount - 1;
                            }
                        }
                    }
                }
                if (!isInclude) {
                    mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
                }
            }
        }
    }

通過這三個步驟,遞歸得到最後的bindings數組。如果設置了id的,就將view變量設置爲public,這樣就避免了findViewById的代碼。這種方式從性能上比findViewById高效,因爲databinding只需要遍歷一次view樹,而findViewById多次調用會遍歷多次。

本文大部分內容參考:DataBinding源碼解析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章