DataBinding實現(xiàn)原理探析

前言

最近在構建MVVM框架,因為這個框架最重要的就是利用DataBinding框架實現(xiàn)VM與V層的交互(以下所稱VM和V都指代這個框架)掸读,所以有必要深入研究下DataBinding的原理寺枉。本文重點講解DataBinding的原理,里面會穿插DataBinding的一些基本用法砌烁,所以需要讀者有一定DataBinding使用經(jīng)驗函喉,不了解DataBinding用法的管呵,請移步MVVM之DataBinding入門捐下、官方文檔

本文講解代碼 https://github.com/foxleezh/TestDatabinding

DataBinding做了些什么事

DataBinding主要做了兩件事:
1.取代煩瑣的findviewbyid坷襟,自動在binding中生成對應的view
2.綁定VM層和V層的監(jiān)聽關系婴程,使得VM和V層的交互更簡單
這里的交互是指兩個方向的交互档叔,一是VM通知V層作改變衙四,比如setText,setColor届搁,二是V層通知VM作改變卡睦,比如click事件表锻,edittext的輸入

取代findviewbyid

DataBinding如何取代那些可惡的findviewbyid的呢瞬逊?在講原理前我首先說明一點确镊,DataBinding框架并沒有用其他高級的API替代現(xiàn)有的API蕾域,只是對原有API的封裝旨巷,也就是說不管是findviewbyid采呐,還是setText斧吐,click事件会通,DataBinding還是用findviewbyid這些原有的API實現(xiàn)的涕侈,只是把它隱藏起來了裳涛,我們開發(fā)過程中不用自己寫端三,因為框架幫我們寫了郊闯。下面我就把這些隱藏起來的代碼呈現(xiàn)出來,看看它到底用了什么魔法。

  • 改造xml

我們在寫xml文件的時候笋粟,需要在頭部加layout害捕,并設置data倘待,你以為這是高級API掀潮,其實不然择诈,這個xml在編譯的時候會被改造畏浆。我們先看一個原始的

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

    <data>

        <variable
            name="viewModel"
            type="com.foxlee.testdatabinding.NewsViewModel" />

        <import type="android.view.View" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:onText="@{viewModel.name}" />

        <TextView
            android:id="@+id/tv_value"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp"
            app:onText="@{viewModel.value1}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp" />

        <TextView
            android:id="@+id/tv_value1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp"
            app:onText="@{viewModel.value1}" />
    </LinearLayout>
</layout>
</layout>

我們再看看最后生成的xml宰闰,這個xml在build\intermediates\data-binding-layout-out\debug目錄下觅够,你也可以反編譯apk來看看

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:tag="layout/fragment_new_list_0">

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

    <TextView
        android:id="@+id/tv_value"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:tag="binding_2" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp" />

    <TextView
        android:id="@+id/tv_value1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:tag="binding_3" />
</LinearLayout>
         

比較之后就可以看到砂轻,我們手動添加的layout斜纪,data籍铁,以及@{viewModel.name}這些看似高級的API用法,其實在編譯后都去掉了武契,取代他們的是在各個綁定了@{}的View添加一個tag浸船,這些tag以binding_開頭黔州,后面接一個數(shù)字,這里注意:沒有綁定@{}的view不會添加tag俺泣,比如上面的tv_value1扎拣。然后在根布局里也加了一個tag,名字是"layout/xxxx_xx"决帖。為什么要在xml里面加一些莫明其妙的tag呢,接下來我們看看綁定layout的代碼就知道了

  • 綁定layout

我們綁定layout的代碼有兩種,Acitivity里是DataBindingUtil.setContentView,F(xiàn)ragment里是DataBindingUtil.inflate,兩個方法調用后都會走到bind這個方法

    private static DataBinderMapper sMapper = new DataBinderMapper();

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

這個DataBinderMapper是編譯器自己生成的一個類,getDataBinder方法如下:

    public android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View view, int layoutId) {
        switch(layoutId) {
                case com.foxlee.testdatabinding.R.layout.fragment_new_list:
                    return com.foxlee.testdatabinding.databinding.FragmentNewListBinding.bind(view, bindingComponent);
        }
        return null;
    }

這里通過一個switch判斷l(xiāng)ayoutId,然后調用對應layoutId的xxxBinding類的bind方法,這個xxxBinding也是自動生成的,正好是layout名字轉成駝峰標識后加Binding枢纠,比如你的layout叫fragment_new_list吗讶,這個Binding就叫FragmentNewListBinding,看看該類的bind方法

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

首先判斷view的tag是不是叫l(wèi)ayout/fragment_new_list_0昌简,是不是有點眼熟疗疟,這就是之前xml被轉變后根布局加上的那個tag违霞!還記得那些binding_1,binding_2嗎?接下來也會用到。接著new了一個FragmentNewListBinding,看看構造方法

    public FragmentNewListBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
        super(bindingComponent, root, 1);
        final Object[] bindings = mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds);
        this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView3 = (android.widget.TextView) bindings[3];
        this.mboundView3.setTag(null);
        this.tvName = (android.widget.TextView) bindings[1];
        this.tvName.setTag(null);
        this.tvValue = (android.widget.TextView) bindings[2];
        this.tvValue.setTag(null);
        this.tvValue1 = (android.widget.TextView) bindings[4];
        setRootTag(root);
        // listeners
        invalidateAll();
    }

這里似乎有點接近findviewbyid了,通過mapBindings方法得到一個數(shù)組,然后將數(shù)組里的每個對象強轉為FragmentNewListBinding里的各個View,這些View的名字與xml中設置的id有關新锈,如果設置了id就用駝峰標識切省,沒有設置id就用mboundView加上該view在bindings中的下標,這里注意:mapBindings傳遞了一個參數(shù)5练湿,這個值跟bindings數(shù)組的數(shù)量是一樣的埃碱,這個數(shù)量=綁定了@{}的view+有設置id的view+根布局的view辉阶,那些沒有設置id睛藻,也沒有設置@{}的view不在此列店印。接下來我們看看mapBindings方法包券,這個方法在父類ViewDataBinding中:

   protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
            int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
        Object[] bindings = new Object[numBindings];
        mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
        return bindings;
    }

   private static void mapBindings(DataBindingComponent bindingComponent, View view,
            Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
            boolean isRoot) {
        final int indexInIncludes;
        final ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding != null) {
            return;
        }
        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;
                }
            }
        }

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

第一個方法初始化了bindinds數(shù)組侍郭,大小等于傳進來的numBindings亮元,然后調用另一個mapBindings方法,這個方法比較長煮甥,我們一段段分析

======華麗分割線========================================
        //定義常量indexInIncludes
        final int indexInIncludes;
        //是否初始化過binding成肘,如果初始化過直接return
        final ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding != null) {
            return;
        }
        Object objTag = view.getTag();
        //獲得該view的tag,也就是layout/fragment_new_list_0或者binding_1,binding_2等
        final String tag = (objTag instanceof String) ? (String) objTag : null;
        boolean isBound = false;
======華麗分割線========================================

    static ViewDataBinding getBinding(View v) {
        if (v != null) {
            if (USE_TAG_ID) {
                return (ViewDataBinding) v.getTag(R.id.dataBinding);
            } else {
                final Object tag = v.getTag();
                if (tag instanceof ViewDataBinding) {
                    return (ViewDataBinding) tag;
                }
            }
        }
        return null;
    }

    //這個方法在FragmentNewListBinding的構造方法中有調用店煞,api14以下不同處理
    protected void setRootTag(View view) {
        if (USE_TAG_ID) {
            view.setTag(R.id.dataBinding, this);
        } else {
            view.setTag(this);
        }
    }

    //兼容api14以下
    private static final boolean USE_TAG_ID = DataBinderMapper.TARGET_MIN_SDK >= 14;

這段主要是防止多次初始化,然后獲得view的tag

======華麗分割線========================================
       //isRoot判斷是否為根布局鸣个,這是方法傳進來的值囤萤,接著判斷是否以layout開頭涛舍,這里滿足條件的是layout/fragment_new_list_0
       if (isRoot && tag != null && tag.startsWith("layout")) {
            final int underscoreIndex = tag.lastIndexOf('_');
            //underscoreIndex是下劃線的下標掸驱,isNumeric方法是判斷下劃線后面的是不是數(shù)字
            if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
                final int index = parseTagInt(tag, underscoreIndex + 1);
                //得到下劃線后面的數(shù)字毕贼,然后把view裝進bindings對應的位置鬼癣,這里bindings[0]就是根布局LinearLayout
                if (bindings[index] == null) {
                    bindings[index] = view;
                }
                indexInIncludes = includes == null ? -1 : index;
                //是否已綁定
                isBound = true;
            } else {
                indexInIncludes = -1;
            }
        }
        //BINDING_TAG_PREFIX="binding_"扣溺,這里滿足條件的是binding_1,binding_2等
         else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
            //BINDING_NUMBER_START=BINDING_TAG_PREFIX.length();
            int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
            //得到下劃線后面的數(shù)字痢掠,然后裝進bindings對應位置足画,這里bingding[1],bindings[2]就是對應的TextView
            if (bindings[tagIndex] == null) {
                bindings[tagIndex] = view;
            }
            isBound = true;
            indexInIncludes = includes == null ? -1 : tagIndex;
        } else {
            // Not a bound view
            indexInIncludes = -1;
        }
        //如果傳進來的view的tag沒有以layout開頭淹辞,也沒有以binding_開頭,對應的是tv_value1這個view
        if (!isBound) {
            final int id = view.getId();
            if (id > 0) {
                int index;
                //viewsWithIds是傳進來的值,該值在FragmentNewListBinding的靜態(tài)代碼塊中初始化央星,下面有代碼,這樣bindings[4]就是tv_value1這個id對應的TextView了
                if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
                        bindings[index] == null) {
                    bindings[index] = view;
                }
            }
        }
======華麗分割線========================================


    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = new android.util.SparseIntArray();
        sViewsWithIds.put(R.id.tv_value1, 4);
    }

這段代碼做了主要的賦值操作,將xml中的view一個個裝進bindings這個數(shù)組颓遏,分三種情況叁幢,一是根布局,二是設置了@{}的view,三是設置了id的view漂洋,優(yōu)先級就是根布局>@{}>id

        //如果view是ViewGroup
        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;
                //indexInIncludes = includes == null ? -1 : index;這里includes為空遥皂,暫時不分析這段
                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;
                            }
                        }
                    }
                }
                //如果不是include的力喷,就遞歸該方法,isRoot值傳false演训,就會走上一步中的第二種或第三種
                if (!isInclude) {
                    mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
                }
            }
        }

這段方法主要是有個遞歸方法,重復執(zhí)行mapBindings方法样悟,這樣一層層的遞歸下去就會把所有的view都裝進bindings數(shù)組了

至此拂募,xml中所有的View就一一綁定到FragmentNewListBinding的成員變量上去了,我們在代碼中就可以用FragmentNewListBinding獲得xml中各個view

綁定VM和V的交互

  • 設置VM

在開發(fā)中完成VM和V的綁定需要binding.setVariable或者binding.setViewModel窟她,兩者效果一樣陈症,因為setVariable會間接調用setViewModel方法

    public boolean setVariable(int variableId, Object variable) {
        switch(variableId) {
            case BR.viewModel :
                setViewModel((com.foxlee.testdatabinding.NewsViewModel) variable);
                return true;
        }
        return false;
    }

值得注意的是,setVariable一定會有震糖,setViewModel是根據(jù)xml中data定義的name生成的录肯,也就是說name="viewModel",會生成一個叫setViewModel的方法吊说,如果把name改為AAA论咏,那么就會生成一個叫setAAA的方法,你設置多少個variable颁井,就會生成多少個setXXX方法

    <data>
        <variable
            name="AAA"
            type="com.foxlee.testdatabinding.NewsViewModel" />
        <variable
            name="stringA"
            type="String" />
    </data>
    public boolean setVariable(int variableId, Object variable) {
        switch(variableId) {
            case BR.stringA :
                setStringA((java.lang.String) variable);
                return true;
            case BR.AAA :
                setAAA((com.foxlee.testdatabinding.NewsViewModel) variable);
                return true;
        }
        return false;
    }

所以我們在用的時候就要注意了厅贪,不要亂寫,要根據(jù)xml定義的name來寫雅宾,而setVariable一定會有养涮,但是要傳遞一個id值,這個id值是BR中的眉抬,BR文件是一個Map表贯吓,作用跟R文件差不多,會自動生成

package com.foxlee.testdatabinding;

public class BR {
        public static final int _all = 0;
        public static final int name = 1;
        public static final int value1 = 2;
        public static final int viewModel = 3;
}

哪些情況會生成BR文件中的值呢吐辙,有三種
1.xml中中設置variable的name屬性

 <data>
        <variable
            name="viewModel"
            type="com.foxlee.testdatabinding.NewsViewModel" />
        <import type="android.view.View" />
    </data>

2.VM繼承BaseObservable宣决,將某個成員變量加上@Bindable注解

    @Bindable
    public String name;

3.VM繼承BaseObservable,將get,set,is開頭的方法加上@Bindable注解

    @Bindable
    public String getValue2(){
        return "test";
    }
    @Bindable
    public void setValue3(String s){

    }
    @Bindable
    public boolean isvalue4(){
        return true;
    }

注意這里的get和is方法不能帶參數(shù)昏苏,必須有返回值尊沸,set方法不能帶返回值,必須且只能帶一個參數(shù)贤惯,不然編譯報錯洼专。方法后面的Value2,Value3等第一個字母會自動轉為小寫,BR文件中就是value2,value3孵构,不會是Value2,Value3屁商。

我們接著上面的setVariable方法講,這個方法傳的id值必須與xml中variable的name一致颈墅,不然就不會調用setViewModel方法蜡镶,我們看setViewModel方法干了什么

    public void setViewModel(com.foxlee.testdatabinding.NewsViewModel ViewModel) {
        updateRegistration(0, ViewModel);
        this.mViewModel = ViewModel;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.viewModel);
        super.requestRebind();
    }

這個方法主要是調用updateRegistration方法雾袱,然后將mDirtyFlags |= 0x1L,再調用notifyPropertyChanged(BR.viewModel)官还,主要做的工作是updateRegistration注冊監(jiān)聽器芹橡,notifyPropertyChanged調用監(jiān)聽器回調,我們先看updateRegistration,這個方法在父類

 protected boolean updateRegistration(int localFieldId, Observable observable) {
        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
    }

    /**
     * @hide
     */
    protected boolean updateRegistration(int localFieldId, ObservableList observable) {
        return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
    }

    /**
     * @hide
     */
    protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
        return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
    }

這里有三個重載的方法望伦,分別對應Observable 林说,ObservableList 和ObservableMap ,我們一般用到的是Observable 屯伞,我們VM是繼承自BaseObservable的腿箩,BaseObservable實現(xiàn)Observable接口,那我們先只看第一個方法劣摇,這個方法傳遞了一個CREATE_PROPERTY_LISTENER參數(shù)

    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的create方法時就會返回一個WeakPropertyListener的實例,我們先接著上面的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;
    }

這里主要有兩個方法的調用末融,一個registerTo(localFieldId, observable, listenerCreator)剑梳,一個unregisterFrom(localFieldId),其他的代碼是處理不同情況對兩個方法的調用滑潘,registerTo就是注冊監(jiān)聽器,unregisterFrom就是刪除監(jiān)聽器锨咙,以上其他代碼的邏輯主要處理三種情況
1.observable傳進來為null语卤,刪除監(jiān)聽器
2.mLocalFieldObservers[localFieldId]為空,也就是第一次注冊酪刀,那么就注冊監(jiān)聽器
3.mLocalFieldObservers[localFieldId]不為空并且里面的監(jiān)聽器和傳進來監(jiān)聽器不一致粹舵,先刪除監(jiān)聽器,再重新注冊新的監(jiān)聽器

我們先看看注冊監(jiān)聽器的方法

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

listenerCreator就是之前的那個CREATE_PROPERTY_LISTENER骂倘,調用create方法就是調用
return new WeakPropertyListener(viewDataBinding, localFieldId).getListener()眼滤,我們看一下這個類

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

它里面有一個包裝類WeakListener,主要用于避免ViewDataBinding內存泄漏历涝,同時儲存VM對象

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

這樣mLocalFieldObservers就儲存了一個WeakListener對象诅需,這個WeakListener既持有ViewDataBinding的引用,也持有VM的引用荧库,還持有WeakPropertyListener的引用堰塌。
registerTo最后調用了listener.setTarget(observable)方法,
這個方法就是調用WeakPropertyListener 的addListener方法分衫,
也就是調用VM的addOnPropertyChangedCallback(this)场刑,
我們看看VM的父類BaseObservable的addOnPropertyChangedCallback方法

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

這個方法就是new了一個PropertyChangeRegistry的實例然后把WeakPropertyListener這個監(jiān)聽器設置給PropertyChangeRegistry,這樣VM就持有了PropertyChangeRegistry的引用蚪战,也就建立了和ViewDataBinding的聯(lián)系

刪除監(jiān)聽器的方法比較簡單,就是調用WeakPropertyListener的unregister方法牵现,然后把剛才建立的聯(lián)系取消掉

   protected boolean unregisterFrom(int localFieldId) {
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener != null) {
            return listener.unregister();
        }
        return false;
    }

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

至此铐懊,VM到ViewDataBinding的通信就通過WeakPropertyListener建立起來了,其實ViewDataBinding一開始就有VM的引用瞎疼,剛才這么多的邏輯只是為了建立VM到ViewDataBinding的通信科乎,而ViewDataBinding是持有V層各個View的引用的,這樣VM和V之間的交互就通過ViewDataBinding這個橋梁建立起來了丑慎。這里應該用一個圖來說明這些關系

drawing
  • VM回調通知V

這些類之間的聯(lián)系搞清楚之后喜喂,我們再看VM是怎么通知View進行改變的。之前我們在xml里面寫了一些@{}的代碼

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:onText="@{viewModel.name}" />

然后在VM中寫了一個回調方法

    @BindingAdapter("onText")
    public static void onTestChange(TextView view,String text){
        view.setText(text);
        Log.d("onText", "onTestChange: "+text);
    }

這些代碼會在ViewDataBinding中生成一些處理代碼

    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String viewModelName = null;
        java.lang.String viewModelValue1 = null;
        com.foxlee.testdatabinding.NewsViewModel viewModel = mViewModel;

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


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

                    if (viewModel != null) {
                        // read viewModel.name
                        viewModelName = viewModel.name;
                    }
            }
            if ((dirtyFlags & 0xdL) != 0) {

                    if (viewModel != null) {
                        // read viewModel.value1
                        viewModelValue1 = viewModel.value1;
                    }
            }
        }
        // batch finished
        if ((dirtyFlags & 0xdL) != 0) {
            // api target 1

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.mboundView3, viewModelValue1);
            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.tvValue, viewModelValue1);
        }
        if ((dirtyFlags & 0xbL) != 0) {
            // api target 1

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.tvName, viewModelName);
        }
    }

也就是說當執(zhí)行executeBindings方法竿裂,并且dirtyFlags 滿足一定條件的時候玉吁,就會執(zhí)行我們定義好的回調方法,這也是為什么我們在定義回調方法時必須用static方法的原因腻异,因為這個地方是直接用類名調用的进副。
我們先分析executeBindings在什么地方回調的,然后再分析dirtyFlags值的邏輯悔常。

我們在executeBindings的方法上打個斷點

截圖

能找到的最前面的代碼是

            mFrameCallback = new Choreographer.FrameCallback() {
                @Override
                public void doFrame(long frameTimeNanos) {
                    mRebindRunnable.run();
                }
            };

mRebindRunnable.run()這一步是一個回調影斑,調用的地方只有一個

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

mChoreographer.postFrameCallback(mFrameCallback);這一步就會觸發(fā)回調,我們在requestRebind方法處再打一個斷點

截圖

這下調用關系就清楚明了了机打,其實也就是我們之前那個注冊關系矫户,
VM調用PropertyChangeRegistry的notifyCallbacks,
然后調用到WeakPropertyListener的onPropertyChanged残邀,
然后調用ViewDataBinding的handleFieldChange皆辽,
然后調用requestRebind

executeBindings方法的調用關系清楚后,我們再看看dirtyFlags的邏輯

        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }

這個值是由mDirtyFlags決定的芥挣,而且每次賦值之后都會把mDirtyFlags置為0驱闷,mDirtyFlags改變的地方有三個
1.invalidateAll

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

這個方法會在ViewDataBinding的構造方法中調用
2.setViewModel

     public void setViewModel(com.foxlee.testdatabinding.NewsViewModel ViewModel) {
        updateRegistration(0, ViewModel);
        this.mViewModel = ViewModel;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.viewModel);
        super.requestRebind();
    }

這個方法在setVariable中調用
2.onChangeViewModel

    private boolean onChangeViewModel(com.foxlee.testdatabinding.NewsViewModel ViewModel, int fieldId) {
        switch (fieldId) {
            case BR.name: {
                synchronized(this) {
                        mDirtyFlags |= 0x2L;
                }
                return true;
            }
            case BR.value1: {
                synchronized(this) {
                        mDirtyFlags |= 0x4L;
                }
                return true;
            }
            case BR._all: {
                synchronized(this) {
                        mDirtyFlags |= 0x1L;
                }
                return true;
            }
        }
        return false;
    }

這個方法在handleFieldChange方法中調用,從之前的分析可知空免,在VM中調用notifyPropertyChanged()就會執(zhí)行在handleFieldChange方法空另,而且notifyPropertyChanged傳遞進來的BR值正好就是這個switch語句判斷的條件,當調用notifyChange時BR為0蹋砚,也就是BR._all

分析完了之后我們就可以得出結論扼菠,在初始化的時候因為會先后調用第一和第二種情況,計算后的結果是9坝咐,調用其他notifyPropertyChanged的時候娇豫,就是對應的1,2畅厢,4

我們再看看executeBindings中對mDirtyFlags的判斷

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

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.mboundView3, viewModelValue1);
            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.tvValue, viewModelValue1);
        }
        if ((dirtyFlags & 0xbL) != 0) {
            // api target 1

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.tvName, viewModelName);
        }

這些值有什么聯(lián)系呢冯痢,看似0xd,0xb這些沒什么特殊的,我們多寫幾個BR值來看看

 // batch finished
        if ((dirtyFlags & 0x49L) != 0) {
            // api target 1

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.mboundView3, viewModelValue2);
        }
        if ((dirtyFlags & 0x51L) != 0) {
            // api target 1

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.mboundView4, viewModelValue3);
        }
        if ((dirtyFlags & 0x61L) != 0) {
            // api target 1

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.mboundView5, viewModelValue4);
        }
        if ((dirtyFlags & 0x43L) != 0) {
            // api target 1

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.tvName, viewModelName);
        }
        if ((dirtyFlags & 0x45L) != 0) {
            // api target 1

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.tvValue, viewModelValue1);
        }

變成了0x43,0x45,0x49,0x51,0x61,這些都是些什么數(shù)字啊,感覺毫無規(guī)律可循浦楣,不過經(jīng)驗告訴我袖肥,這種&|運算一般都跟二進制有關,我們把這些十六進制的轉換為二進制看看

截圖

看到規(guī)律了吧振劳,這些數(shù)字都以1開頭椎组,以1結尾,中間遞進的為1历恐,這樣就可以明白那些判斷條件的邏輯了寸癌,當
dirtyFlags為1或者為最高位的1000000,或者為10000001時弱贼,所有的判斷條件都滿足蒸苇,所有的回調都會執(zhí)行,當dirtyFlags為其中某一個二進制的整數(shù)2吮旅,4溪烤,8,16等等的時候庇勃,就只有對應條件能執(zhí)行了檬嘀。但是這樣我們會有一個疑問,dirtyFlags為long责嚷,有長度限制鸳兽,萬一超過64種BR怎么辦呢,我們來看看超過64個的情況

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

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.mboundView61, viewModelValue60);
        }
        if ((dirtyFlags & 0x4000000000000001L) != 0 || (dirtyFlags_1 & 0x4L) != 0) {
            // api target 1

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.mboundView62, viewModelValue61);
        }
        if ((dirtyFlags & 0x8000000000000001L) != 0 || (dirtyFlags_1 & 0x4L) != 0) {
            // api target 1

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.mboundView63, viewModelValue62);
        }
        if ((dirtyFlags & 0x1L) != 0 || (dirtyFlags_1 & 0x5L) != 0) {
            // api target 1

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.mboundView64, viewModelValue63);
        }
        if ((dirtyFlags & 0x1L) != 0 || (dirtyFlags_1 & 0x6L) != 0) {
            // api target 1

            com.foxlee.testdatabinding.NewsViewModel.onTestChange(this.mboundView65, viewModelValue64);
        }

哈哈罕拂,它加了另外一個dirtyFlags_1 來一起判斷贸铜,相當于延長了那個64位1000...1,真是佩服google的程序員聂受!后來想想,干嘛這個地方要用這么復雜的一個設計烤镐,直接用switch判斷BR的值不行嗎蛋济?但是這樣會漏掉一個情況,如果想讓所有的回調都執(zhí)行呢炮叶,那我不是要把所有回調寫兩遍碗旅?因為得有一個case把所有回調再寫一遍,所以還是覺得google這種算法精妙镜悉。

至此祟辟,我們把DataBinding框架大體的邏輯搞清楚了,小結一下吧

小結

1.DataBindingUtil.setContentView方法將xml中的各個View賦值給ViewDataBinding侣肄,完成findviewbyid的任務
2.ViewDataBinding的setVariable方法建立了ViewDataBinding與VM之間的聯(lián)系旧困,也就搭建了一個可以互相通信的橋梁
3.當VM層調用notifyPropertyChanged方法時,最終在ViewDataBinding的executeBindings方法中處理邏輯
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市吼具,隨后出現(xiàn)的幾起案子僚纷,更是在濱河造成了極大的恐慌,老刑警劉巖拗盒,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怖竭,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門恭朗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來现柠,“玉大人,你說我怎么就攤上這事弧可。” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵艇潭,是天一觀的道長。 經(jīng)常有香客問我戏蔑,道長蹋凝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任总棵,我火速辦了婚禮鳍寂,結果婚禮上,老公的妹妹穿的比我還像新娘情龄。我一直安慰自己迄汛,他們只是感情好,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布骤视。 她就那樣靜靜地躺著鞍爱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪专酗。 梳的紋絲不亂的頭發(fā)上睹逃,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音祷肯,去河邊找鬼沉填。 笑死,一個胖子當著我的面吹牛佑笋,可吹牛的內容都是我干的翼闹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蒋纬,長吁一口氣:“原來是場噩夢啊……” “哼猎荠!你這毒婦竟也來了坚弱?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤法牲,失蹤者是張志新(化名)和其女友劉穎史汗,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拒垃,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡停撞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了悼瓮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戈毒。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖横堡,靈堂內的尸體忽然破棺而出埋市,到底是詐尸還是另有隱情,我是刑警寧澤命贴,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布道宅,位于F島的核電站,受9級特大地震影響胸蛛,放射性物質發(fā)生泄漏污茵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一葬项、第九天 我趴在偏房一處隱蔽的房頂上張望泞当。 院中可真熱鬧,春花似錦民珍、人聲如沸襟士。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陋桂。三九已至,卻和暖如春蝶溶,著一層夾襖步出監(jiān)牢的瞬間嗜历,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工身坐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人落包。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓部蛇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咐蝇。 傳聞我的和親對象是個殘疾皇子涯鲁,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內容