前言
最近在構建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這個橋梁建立起來了丑慎。這里應該用一個圖來說明這些關系
-
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框架大體的邏輯搞清楚了,小結一下吧