(譯)深入理解Data Binding原理

本文首發(fā):http://yuweiguocn.github.io/

本文介紹了Data Binding的原理硝逢。

關于Data Binding的使用請查看Data Binding炼七。

原文鏈接:Understanding Data-Binding's generated code and How does Android Data-Binding compiler work

這篇文章并不是介紹怎樣使用Data Binding或了解基本概念引润。建議你直接查看Google文檔酒甸,會幫助你輕松集成,有大量的示例代碼,當你決定應用它到你的工程你可以實現(xiàn)很多很酷的東西。體驗之后嗽冒,你可能會好奇它是如何實現(xiàn)的,Google是怎樣讓傳統(tǒng)的xml文件和data binding混合的补履,xml文件是怎樣和java代碼交互的添坊,編譯器是怎樣處理的。本文主題旨在揭露data binding的機制箫锤,深入底層看看到底發(fā)生了什么贬蛙。因此,開發(fā)者可以深入理解幫助他們正確地使用data binding麻汰,利用data binding的強大構(gòu)建完美的應用速客。

近來,data binding是android很火的趨勢五鲫,讓開發(fā)者敲代碼時更輕松。由于它的強大大量的開發(fā)者已經(jīng)開始使用data binding岔擂。但說實話位喂,它也帶給我們一些麻煩。雖然data binding的概念很簡單:“Yay乱灵!定義一個ViewModel類在 layout.xml 文件中引入并且不需要關注任何UI的東西塑崖,我們的數(shù)據(jù)會直接綁定到UI上用一種神奇的方式”。真的很快痛倚,很簡單规婆。當出現(xiàn)一些錯誤,當你的數(shù)據(jù)突然不能綁定到UI上蝉稳,當編譯器報出大量的錯誤信息抒蚜,你真的不想知道這是什么意思么。我們能做些什么耘戚!

我不得不去面對這些data binding的問題并且使用不同的辦法去解決它嗡髓。并且我認為,只有通過查看data binding的源碼收津,理解它工作的原理饿这,我就不用再去處理這些問題了浊伙。讓我們從Google clone下這個倉庫一起閱讀下它的代碼Data-Binding Repository

Part 1:Data Binding 流长捧,Obsevable模式機制和data-binding生成代碼的意思

為了能更好的理解代碼嚣鄙,我創(chuàng)建了一個簡單的使用data-binding的示例。

1.創(chuàng)建一個布局文件R.layout.activity_main

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.example.main.MainViewModel" />

    </data>

    <TextView android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@{ viewModel.text }" />

</layout>

2.創(chuàng)建MainActivity

java
public class MainActivity extends AppCompatActivity {

    @Inject
    MainViewModel mViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding bind = DataBindingUtil.setContentView(this, R.layout.activity_main);
        bind.setViewModel(mViewModel);
    }
}

3.創(chuàng)建MainViewModel

java
public class MainViewModel extends BaseObservable  {

    public final ObservableField<String> text = new ObservableField<>();

    public MainViewModel() {
    }

}

編譯工程之后串结,我們可以看到data-binding生成的新的文件:
1.activity_main-layout.xml(在data-binding-info文件夾中)

xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout layout="activity_main" absoluteFilePath="/home/framgia/Projects/harpa-crista/harpacrista/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>
    <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>

2.activity_main.xml(正常的布局文件)的簡潔版本(在data-binding-layout-out文件夾中)

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

<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" />

3.ActivityMainBinding.java文件哑子,這是我們的主角,最神奇的地方奉芦。

java
public class ActivityMainBinding extends android.databinding.ViewDataBinding  {

    private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = null;
    }
    // views
    private final android.widget.TextView mboundView0;
    // variables
    private com.example.main.MainViewModel mViewModel;
    // values
    // listeners
    // Inverse Binding Event Handlers

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

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

    @Override
    public boolean hasPendingBindings() {
        synchronized(this) {
            if (mDirtyFlags != 0) {
                return true;
            }
        }
        return false;
    }

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

    public void setViewModel(com.example.main.MainViewModel viewModel) {
        updateRegistration(0, viewModel);
        this.mViewModel = viewModel;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.viewModel);
        super.requestRebind();
    }
    public com.example.main.MainViewModel getViewModel() {
        return mViewModel;
    }

    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
            case 0 :
                return onChangeViewModel((com.example.main.MainViewModel) object, fieldId);
            case 1 :
                return onChangeTextViewMode((android.databinding.ObservableField<java.lang.String>) object, fieldId);
        }
        return false;
    }
    private boolean onChangeViewModel(com.example.main.MainViewModel viewModel, int fieldId) {
        switch (fieldId) {
            case BR._all: {
                synchronized(this) {
                        mDirtyFlags |= 0x1L;
                }
                return true;
            }
        }
        return false;
    }
    private boolean onChangeTextViewMode(android.databinding.ObservableField<java.lang.String> textViewModel, int fieldId) {
        switch (fieldId) {
            case BR._all: {
                synchronized(this) {
                        mDirtyFlags |= 0x2L;
                }
                return true;
            }
        }
        return false;
    }

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

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



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


                if (textViewModel1 != null) {
                    // read viewModel.text.get()
                    textViewModel = textViewModel1.get();
                }
        }
        // batch finished
        if ((dirtyFlags & 0x7L) != 0) {
            // api target 1

            android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView0, textViewModel);
        }
    }
    // Listener Stub Implementations
    // callback impls
    // dirty flag
    private  long mDirtyFlags = 0xffffffffffffffffL;

    public static ActivityMainBinding inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {
        return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());
    }
    public static ActivityMainBinding inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {
        return android.databinding.DataBindingUtil.<ActivityMainBinding>inflate(inflater, com.harpacrista.R.layout.activity_main, root, attachToRoot, bindingComponent);
    }
    public static ActivityMainBinding inflate(android.view.LayoutInflater inflater) {
        return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());
    }
    public static ActivityMainBinding inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {
        return bind(inflater.inflate(com.harpacrista.R.layout.activity_main, null, false), bindingComponent);
    }
    public static ActivityMainBinding bind(android.view.View view) {
        return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());
    }
    public static ActivityMainBinding bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {
        if (!"layout/activity_main_0".equals(view.getTag())) {
            throw new RuntimeException("view tag isn't correct on view:" + view.getTag());
        }
        return new ActivityMainBinding(bindingComponent, view);
    }
    /* flag mapping
        flag 0 (0x1L): viewModel
        flag 1 (0x2L): viewModel.text
        flag 2 (0x3L): null
    flag mapping end*/
    //end
}

正如我們所看到的赵抢,data-binding從activity_main.xml文件幫我們生成了3個額外的文件:activity_main-layout.xmlactivity_main.xml的簡潔版本和ActivityMainBinding.java声功。至此烦却,我們對xml布局生成的代碼有了一個初步的了解。

基本上先巴,一個xml布局文件最外層的<layout></layout>標簽表示和正常布局文件之間的區(qū)別其爵。如果一個正常的布局文件是直接用于android應用,放置在apk包中的res/layout文件夾中伸蚯,那布局文件中最外層的<layout></layout>標簽則是被間接使用摩渺。編譯器通過搜索應用的layout文件夾編譯所有最外層為<layout></layout>標簽的布局文件為activity_main.xml(正常的布局文件)的簡潔版本。這個版本的xml看起來就像我們沒有使用data-binding的正常的布局文件剂邮。

因此摇幻,基本上,使用data-binding的布局文件就是正常布局文件的一個特殊的版本挥萌,data-binding給我們一些詞匯和語法绰姻,通過這種方式強制它們重寫正常的布局文件。Xml文件不能被Android框架理解引瀑,它只能被data-binding編譯器理解狂芋,這讓編譯器知道什么地方從ViewModel有怎樣的數(shù)據(jù)映射到View上。最后編譯器把xml文件轉(zhuǎn)換成可以打包到apk中的正常的布局文件憨栽,以及activity_main-layout.xml 和 ActivityMainBinding.java帜矾。

我們可以想象原始的布局文件包含兩部分:正常部分和綁定部分。正常部分就像我們沒有使用data-binding時寫的布局文件屑柔。綁定部分用于幫助編譯器生成java代碼屡萤,它是一個在UI和數(shù)據(jù)之間很好的橋梁。

activity_main-layout.xml (在data-binding-info文件夾中)包含綁定部分锯蛀。就像它的名字灭衷,這個文件是布局的綁定信息。我們再來回顧下這個文件旁涤。外層仍是<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">
    ...
</Layout>

layout="activity_main"屬性表示這個文件是acitivity_main.xml的綁定部分,當前存放在"/home/framgia/android/app/src/main/res/layout/activity_main.xml"瞳遍。

<Layout>標簽的里面闻妓,毫無疑問,有<Variables>標簽和<Imports>標簽掠械。因為我們前面我們用到setViewModel由缆,并且在布局文件中引用了一些類。

xml
<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>

它們也包含位置location信息猾蒂。我不知道為什么編譯器需要存儲Variable/Import的位置信息均唉,可能用于追溯或其它什么作用。

接下來是這個文件中最重要的部分肚菠,<Target>標簽告訴ViewModel應該映射到哪個View舔箭,綁定類型為單向還是雙向,View的位置及View值的位置蚊逢。

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>

我們有一個<Target>標簽的列表层扶。在布局文件中我們可以有多個View/ViewGroup,但只有包含data-binding表達式的View/ViewGroup才會出現(xiàn)在這里烙荷。<Target>標簽里面的<Expressions>標簽表示View的data-binding表達式镜会。例如:

  • Data-binding表達式android:visibility="@{ viewModel.isVisible ? View.VISIBLE : View.INVISIBLE }"會被編譯成
<Expression attribute="android:visibility"
            text=" viewModel.isVisible ? View.VISIBLE : View.INVISIBLE ">
        <Location endLine="29" endOffset="88" startLine="29" startOffset="12" />
        <TwoWay>false</TwoWay>
        <ValueLocation endLine="29" endOffset="86" startLine="29" startOffset="34" />
</Expression>
  • Data-binding表達式android:text="@{ viewModel.text }"會被編譯成
<Expression attribute="android:text" text=" viewModel.text ">
             <Location endLine="23" endOffset="45" startLine="23" startOffset="12" />
             <TwoWay>false</TwoWay>
             <ValueLocation endLine="23" endOffset="43" startLine="23" startOffset="28" />
     </Expression>

注意<Expression attribute="android:text" text=" viewModel.text ">。它正是我們要找的View和ViewModel之間的橋梁终抽。ViewModel中的text變量會連接到TextView的android:text屬性戳表。這正是我們所期待的。但從這個布局文件到java代碼仍是一個謎昼伴。接下來看下ActivityMainBinding.java扒袖,一探究竟。

ActivityMainBinding.java是ViewDataBinding的一個子類亩码,開發(fā)者可以setViewModel到布局文件。從我們分析的過程來看它就像xml的java版本野瘦。布局文件中的每個View/ViewGroup標簽在這個類是一個變量描沟。例如:

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

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@{ viewModel.text }"
            />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="@{ viewModel.isVisible ? View.VISIBLE : View.INVISIBLE }"
            />
    </LinearLayout>

將會生成:

java
public class ActivityMainBinding extends android.databinding.ViewDataBinding  {

    private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = null;
    }
    // views
    private final android.widget.LinearLayout mboundView0;
    private final android.widget.TextView mboundView1;
    private final android.widget.Button mboundView2;
    ...
}

并且如果你在布局文件中為View指定一個ID,這個private field將變成 public field鞭光,并且你可以直接使用它不用調(diào)用findViewById()吏廉。

java
// views
    private final android.widget.LinearLayout mboundView0;
    private final android.widget.Button mboundView2;
    public final android.widget.TextView tvTest;

這用起來很方便!訪問布局文件中的任意View比之前更為容易惰许。變量的名稱和android:id的名稱一致席覆。如果android:id 帶有下劃線,名稱將轉(zhuǎn)換為駝峰式汹买,例如android:id="@+id/tv_test"會轉(zhuǎn)換為public final android.widget.TextView tvTest;佩伤。

<variable>標簽定義了java類中的變量聊倔,我們通過setViewModel方法設置的變量。順便說一句生巡,當我們討論ViewModel時我想提到Obsevable模式耙蔑。Obsevable模式是一個很關鍵的東西,無論ViewModel何時被修改都可以讓View得到更新孤荣,并且ViewModel會自動接收一個新數(shù)據(jù)當用戶和View交互時甸陌。這是所有的Obsevable類型覆蓋了所有java數(shù)據(jù)類型。

ObservableArrayList
ObservableArrayMap
ObservableBoolean
ObservableByte
ObservableChar
ObservableDouble
ObservableField
ObservableFloat
ObservableInt
ObservableLong
ObservableParcelable
ObservableShort

這些類的集合很相似也很簡單盐股。全部繼承自BaseObservable钱豁,只包含一個變量,一個getter和一個setter疯汁。setter會檢查新值和老值是否不同牲尺,如果是新值會調(diào)用notifyChange(),并且View會得到更新涛目。但notifyChange()是怎樣影響到View的秸谢?

為了回答上面的問題,我會使用這個例子盡可能簡單地解釋它:

  • 期望: 我們修改ViewModel中的isShowView為false霹肝,actiivty_main.xml中的View將不可見估蹄。
  • 里面發(fā)生了什么: 當我們setViewModel到ActivityMainBinding,updateRegistration被調(diào)用用于在ViewModel 和View之間創(chuàng)建一個“橋”沫换。

我們來看下“橋”到底長什么樣子:

java
    /**
     * 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();
        }
    };

    ....

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

    ...

     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);
            }
        }
    }
  • WeakPropertyListener中的mListener變量持有MainDataBinding的一個弱引用臭蚁,WeakListener中的setTarget方法被調(diào)用綁定ViewModelMainDataBinding
  • ViewModel修改isShowView變量會調(diào)用mNotifier的notifyCallback():
java
private void notifyCallbacks(T sender, int arg, A arg2, int startIndex, int endIndex, long bits) {
        long bitMask = 1L;

        for(int i = startIndex; i < endIndex; ++i) {
            if((bits & bitMask) == 0L) {
                this.mNotifier.onNotifyCallback(this.mCallbacks.get(i), sender, arg, arg2);
            }

            bitMask <<= 1;
        }

    }

  • 并且mNotifier也是WeakPropertyListener讯赏。因此在ViewModel上修改isShowView將會通知WeakPropertyListener垮兑,這是isShowView影響View讓它不可見的地方。ActivityMainBinding中的executeBindings()拿到值映射到UI上相應的View漱挎。
java
@Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        com.example.main.MainViewModel viewModel = mViewModel;
        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) {


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


                    // read viewModel.isVisible.get() ? View.VISIBLE : View.INVISIBLE
                    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();
                    }
            }
        }
        // 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何時修改它們的值,它都會通知executeBindings()方法磕谅,這個方法依賴于這個值并且會在View上執(zhí)行動作私爷,例如:
this.mboundView2.setVisibility(isVisibleViewModelVI);
android.databinding.adapters.TextViewBindingAdapter.setText(this.tvTest, textViewModel);
  • 這是為啥我們需要寫@BindingAdapter和@BindingMethod用于在View上執(zhí)行一個動作,例如:recyclerView.setAdapter(), viewPager.setOnPageChange(), ...膊夹。
  • 然而衬浑,Android已經(jīng)創(chuàng)建了大量的內(nèi)置BindingAdapters和BindingMethod,因此開發(fā)者大多數(shù)情況下只需要使用它放刨。我們只需要在特殊情況下實現(xiàn)我們自己的代碼或Android現(xiàn)在還不支持它工秩。這種情況下,isShowView為false對應View.INVISIBLE會執(zhí)行View.setVisibility(View.INVISIBLE)。That's all助币。
  • 進行下一部分之前浪听,我想讓你知道data-binding內(nèi)置的adapters。記得使用這些并且不要重復發(fā)明輪子奠支。我已經(jīng)看到有很多開發(fā)者濫用@BindingAdapter重寫已存在的東西馋辈。真是浪費!

Part 2:data-binding編譯器是怎樣生成代碼的倍谜?

你有沒有把我上面所給出的官方git倉庫clone下來迈螟?Data-Binding Repository

在理解了生成的代碼是怎樣在View和ViewModel之間綁定之后,在這一部分尔崔,我們會找出編譯生成神奇代碼的方法答毫。

注意這兩個模塊:compilercompilerCommon。我們看的最多的地方季春。

  • 編譯器的核心為compiler.android.databinding.annotationprocessor包下的ProcessDataBinding類洗搂。這個類的職責是一步一步執(zhí)行處理列表。
java
         mProcessingSteps = Arrays.asList(
                new ProcessMethodAdapters(),
                new ProcessExpressions(),
                new ProcessBindable()
        );
  • 我們先看第一個處理步驟——ProcessMethodAdapters载弄。這個類提供搜索工程中所有的類耘拇,哪一個類哪一個方法添加了下面的注解:@BindingAdapter, @Untaggable, @BindingMethods, @BindingConversion, @InverseBindingAdapter, @InverseBindingMethods。并且把它們保存在SetterStore宇攻,后面應該在executeBinding用到正如我們上面所說惫叛。在編譯期間,注解處理器拿到的這些信息會被存放在setter_store.bin文件中逞刷。
java
@Override
    public boolean onHandleStep(RoundEnvironment roundEnv,
            ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
        L.d("processing adapters");
        final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
        Preconditions.checkNotNull(modelAnalyzer, "Model analyzer should be"
                + " initialized first");
        SetterStore store = SetterStore.get(modelAnalyzer);
        clearIncrementalClasses(roundEnv, store);

        addBindingAdapters(roundEnv, processingEnvironment, store);
        addRenamed(roundEnv, store);
        addConversions(roundEnv, store);
        addUntaggable(roundEnv, store);
        addInverseAdapters(roundEnv, processingEnvironment, store);
        addInverseMethods(roundEnv, store);

        try {
            store.write(buildInfo.modulePackage(), processingEnvironment);
        } catch (IOException e) {
            L.e(e, "Could not write BindingAdapter intermediate file.");
        }
        return true;
    }

    ...

    public void write(String projectPackage, ProcessingEnvironment processingEnvironment)
            throws IOException {
        GenerationalClassUtil.writeIntermediateFile(processingEnvironment,
                projectPackage, projectPackage +
                        GenerationalClassUtil.ExtensionFilter.SETTER_STORE.getExtension(), mStore);
    }

    ...

    public enum ExtensionFilter {
        BR("-br.bin"),
        LAYOUT("-layoutinfo.bin"),
        SETTER_STORE("-setter_store.bin");
        private final String mExtension;
        ExtensionFilter(String extension) {
            mExtension = extension;
        }

        public boolean accept(String entryName) {
            return entryName.endsWith(mExtension);
        }

        public String getExtension() {
            return mExtension;
        }
    }
  • 第二步是ProcessExpressions處理表達式嘉涌。在這一步中會搜索工程中所有xml文件并且會轉(zhuǎn)換最外層為<layout></layout>標簽支持data-binding的xml文件茧吊。會把這個文件拆分為2個文件正如第一部分所提到的:activity_main.xml(正常的布局文件)和activity_main-layout.xml(包含綁定信息)廉羔。LayoutBinder是最有意思的類萨西,它使用(XmlParser中)layoutBundle在activity_main-layout.xml中計算表達式康聂,位置和目標。
java
@XmlAccessorType(XmlAccessType.NONE)
    @XmlRootElement(name="Layout")
    public static class LayoutFileBundle implements Serializable, FileScopeProvider {
        @XmlAttribute(name="layout", required = true)
        public String mFileName;
        @XmlAttribute(name="modulePackage", required = true)
        public String mModulePackage;
        @XmlAttribute(name="absoluteFilePath", required = true)
        public String mAbsoluteFilePath;
        private String mConfigName;
        // The binding class as given by the user
        @XmlAttribute(name="bindingClass", required = false)
        public String mBindingClass;

        // The location of the name of the generated class, optional
        @XmlElement(name = "ClassNameLocation", required = false)
        private Location mClassNameLocation;
        // The full package and class name as determined from mBindingClass and mModulePackage
        private String mFullBindingClass;

        // The simple binding class name as determined from mBindingClass and mModulePackage
        private String mBindingClassName;

        // The package of the binding class as determined from mBindingClass and mModulePackage
        private String mBindingPackage;

        @XmlAttribute(name="directory", required = true)
        public String mDirectory;
        public boolean mHasVariations;

        @XmlElement(name="Variables")
        public List<VariableDeclaration> mVariables = new ArrayList<VariableDeclaration>();

        @XmlElement(name="Imports")
        public List<NameTypeLocation> mImports = new ArrayList<NameTypeLocation>();

        @XmlElementWrapper(name="Targets")
        @XmlElement(name="Target")
        public List<BindingTargetBundle> mBindingTargetBundles = new ArrayList<BindingTargetBundle>();

        @XmlAttribute(name="isMerge", required = true)
        private boolean mIsMerge;

        ...

    }
java
 public LayoutBinder(ResourceBundle.LayoutFileBundle layoutBundle) {
        try {
            Scope.enter(this);
            mExprModel = new ExprModel();
            mExpressionParser = new ExpressionParser(mExprModel);
            mBindingTargets = new ArrayList<BindingTarget>();
            mBundle = layoutBundle;
            mModulePackage = layoutBundle.getModulePackage();
            HashSet<String> names = new HashSet<String>();
            // copy over data.
            for (ResourceBundle.VariableDeclaration variable : mBundle.getVariables()) {
                addVariable(variable.name, variable.type, variable.location, variable.declared);
                names.add(variable.name);
            }
            for (ResourceBundle.NameTypeLocation userImport : mBundle.getImports()) {
                mExprModel.addImport(userImport.name, userImport.type, userImport.location);
                names.add(userImport.name);
            }
            if (!names.contains("context")) {
                mExprModel.builtInVariable("context", "android.content.Context",
                        "getRoot().getContext()");
                names.add("context");
            }
            for (String javaLangClass : sJavaLangClasses) {
                mExprModel.addImport(javaLangClass, "java.lang." + javaLangClass, null);
            }
            // First resolve all the View fields
            // Ensure there are no conflicts with variable names
            for (BindingTargetBundle targetBundle : mBundle.getBindingTargetBundles()) {
                try {
                    Scope.enter(targetBundle);
                    final BindingTarget bindingTarget = createBindingTarget(targetBundle);
                    if (bindingTarget.getId() != null) {
                        final String fieldName = LayoutBinderWriterKt.
                                getReadableName(bindingTarget);
                        if (names.contains(fieldName)) {
                            L.w("View field %s collides with a variable or import", fieldName);
                        } else {
                            names.add(fieldName);
                            mExprModel.viewFieldExpr(bindingTarget);
                        }
                    }
                } finally {
                    Scope.exit();
                }
            }
            for (BindingTarget bindingTarget : mBindingTargets) {
                try {
                    Scope.enter(bindingTarget.mBundle);
                    for (BindingTargetBundle.BindingBundle bindingBundle : bindingTarget.mBundle
                            .getBindingBundleList()) {
                        try {
                            Scope.enter(bindingBundle.getValueLocation());
                            bindingTarget.addBinding(bindingBundle.getName(),
                                    parse(bindingBundle.getExpr(), bindingBundle.isTwoWay(),
                                            bindingBundle.getValueLocation()));
                        } finally {
                            Scope.exit();
                        }
                    }
                    bindingTarget.resolveTwoWayExpressions();
                    bindingTarget.resolveMultiSetters();
                    bindingTarget.resolveListeners();
                } finally {
                    Scope.exit();
                }
            }
            mSortedBindingTargets = new ArrayList<BindingTarget>(mBindingTargets);
            Collections.sort(mSortedBindingTargets, COMPARE_FIELD_NAME);
        } finally {
            Scope.exit();
        }
    }
  • 第三步是ProcessBindable乳附。這個處理生成BR類乌妒,綁定屬性的id典徘,例如:BR.text, BR.item, BR.isShowView, ...
  • 最后坯钦,你可能想知道最重要的類MainDataBinding是在哪里創(chuàng)建的法严。我不知道為啥Google使用Kotlin編寫的,相關文件為DataBinderWriter.kt 和 LayoutBinderWriter.kt葫笼。你可以自己去看這些文件。

總結(jié)

我希望你能讀到這里拗馒,因為這篇文章有難點我們讀起來很難理解路星。Data-Binding用起來不是很容易甚至很難理解。但我認為當我們真正理解后面的代碼,沒有什么是秘密洋丐,因為我們知道data-binding的原理呈昔。在一些工程上使用data-binding之后,我看到一些data-binding相關的bug很難跟蹤和解決友绝,開發(fā)者在它上面花費了很多時間堤尾。通過這篇文章,我們不再害怕深入到生成的代碼查找bug原因迁客。了解了編譯器的知識可以幫助我們用最好的方式去寫代碼郭宝。

在下面留下評論,告訴我哪一塊是你不理解的掷漱,我會盡力更新粘室,讓這篇文章更有用。Thanks for your time!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末卜范,一起剝皮案震驚了整個濱河市衔统,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌海雪,老刑警劉巖锦爵,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奥裸,居然都是意外死亡险掀,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門刺彩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迷郑,“玉大人,你說我怎么就攤上這事创倔∥撕Γ” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵畦攘,是天一觀的道長霸妹。 經(jīng)常有香客問我,道長知押,這世上最難降的妖魔是什么叹螟? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮台盯,結(jié)果婚禮上罢绽,老公的妹妹穿的比我還像新娘。我一直安慰自己静盅,他們只是感情好良价,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般明垢。 火紅的嫁衣襯著肌膚如雪蚣常。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天痊银,我揣著相機與錄音抵蚊,去河邊找鬼。 笑死溯革,一個胖子當著我的面吹牛贞绳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鬓照,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼熔酷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了豺裆?” 一聲冷哼從身側(cè)響起拒秘,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎臭猜,沒想到半個月后躺酒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蔑歌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年羹应,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片次屠。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡园匹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出劫灶,到底是詐尸還是另有隱情裸违,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布本昏,位于F島的核電站供汛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涌穆。R本人自食惡果不足惜怔昨,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宿稀。 院中可真熱鬧趁舀,春花似錦、人聲如沸祝沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至擂送,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唯欣,已是汗流浹背嘹吨。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留境氢,地道東北人蟀拷。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像萍聊,于是被迫代替她去往敵國和親问芬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容