前言
現在公司用的框架是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()裏去執行相應的操作。
接着我們來看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上,有下面倆種方式:
繼承自BaseObservable,在getter上增加@Bindable註解,在setter裏增加代碼notifyPropertyChanged(BR.xxx)。
無需繼承,需要將屬性替換爲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源碼解析