一苟径、DataBinding使用
本文著重講解DataBinding原理,使用的例子比較簡單,若讀者想要了解更多的DataBinding的使用方法介紹茅姜,可以自尋相關(guān)資料,本文純屬個(gè)人理解月匣,若有錯(cuò)誤钻洒,還望指出(抱拳)
在app模塊的build.gradle中加入如下配置
android {
...
dataBinding {
enabled = true
}
}
現(xiàn)在你就可以在代碼中使用DataBinding了,這里我們舉個(gè)簡單例子锄开,給一個(gè)TextView設(shè)置單向綁定一個(gè)ObservableField< String>類型的name素标,給一個(gè)EditText設(shè)置雙向綁定一個(gè)類型為ObservableField< String>的nickName,點(diǎn)擊一個(gè)Button可以獲取nickName里面的值萍悴,nickName首先顯示“美女”头遭,在代碼中延遲三秒后將nickName的值改為“延遲三秒”,三秒后然后觀察到EditText上文本變?yōu)椤把舆t三秒”癣诱,然后再將EditText上文本刪除计维,輸入“beauty”,點(diǎn)擊button獲取nickName的值狡刘,發(fā)現(xiàn)也是“beauty”享潜,這里的name是單項(xiàng)綁定到TextView上,nickName是雙向綁定到EditText上嗅蔬。
來看下布局文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="userInfo"
type="com.jokerwan.databinding.UserInfo" />
<variable
name="listener"
type="android.view.View.OnClickListener" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{userInfo.name}"
android:textSize="16sp"
tools:text="姓名"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={userInfo.nickName}"
tools:text="昵稱"
android:layout_marginTop="10dp"/>
<Button
android:id="@+id/btn_get_nick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="獲取model里面的昵稱"
android:layout_marginTop="20dp"
android:onClick="@{listener}"/>
</LinearLayout>
</layout>
注意這里給TextView和EditText綁定數(shù)據(jù)的區(qū)別:
給TextView是設(shè)置單項(xiàng)綁定
android:text="@{userInfo.name}"
,
給EditText是設(shè)置雙向綁定
android:text="@={userInfo.nickName}"
可以看到單項(xiàng)綁定和雙向綁定的區(qū)別就是“@”和“{}”之間多了個(gè)“=”剑按。
xml綁定了一個(gè)UserInfo對(duì)象和一個(gè)listener疾就,listener是Button的點(diǎn)擊監(jiān)聽,我們來看下UserInfo的代碼
public class UserInfo {
private ObservableField<String> name = new ObservableField<>();
private ObservableField<String> nickName = new ObservableField<>();
public ObservableField<String> getName() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public ObservableField<String> getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName.set(nickName);
}
}
再看下MainActivity中的代碼艺蝴,主要就是構(gòu)造UserInfo并給binding的屬性賦值
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private UserInfo userInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
userInfo = new UserInfo();
userInfo.setName("王昭君");
userInfo.setNickName("美人");
binding.setUserInfo(userInfo);
binding.setListener(this);
binding.getRoot().postDelayed(new Runnable() {
@Override
public void run() {
userInfo.setNickName("延遲三秒");
}
}, 3000);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_get_nick) {
Toast.makeText(this, userInfo.getNickName().get(), Toast.LENGTH_SHORT).show();
}
}
}
可以看到猬腰,使用了數(shù)據(jù)綁定,我們的代碼邏輯結(jié)構(gòu)變得清晰猜敢,由數(shù)據(jù)綁定框架替我們生成findViewById和給View設(shè)置數(shù)據(jù)的代碼姑荷,數(shù)據(jù)綁定框架幫我們做了控件的數(shù)據(jù)變化監(jiān)聽,并將數(shù)據(jù)同步更新到控件上缩擂。
二鼠冕、DataBinding原理分析
數(shù)據(jù)綁定的運(yùn)行機(jī)制是怎樣的呢?胯盯,為什么我們改變nickName的值UI上可以直接更新懈费,我們操作UI,對(duì)應(yīng)的nickName的值也會(huì)更新呢博脑,下面我們一探DataBinding的究竟憎乙。
首先我們要先找到一個(gè)切入點(diǎn),就是MainActivity中的
DataBindingUtil.setContentView(this, R.layout.activity_main);
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId, @Nullable 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);
}
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);
}
從DataBindingUtil.setContentView()
一路方法調(diào)用跟下來叉趣,這里的parent是布局id為R.id.content
的跟布局泞边,一般跟布局里面就是我們自己的布局,最外層是一個(gè)容器疗杉,所以childrenAdded == 1阵谚,并調(diào)用bind(component, childView, layoutId)
方法,跟進(jìn)bind()方法發(fā)現(xiàn)調(diào)用sMapper.getDataBinder(bindingComponent, root, layoutId)
乡数,
這里的sMapper是DataBinderMapper類椭蹄,該類是抽象類,找到它的實(shí)現(xiàn)類DataBinderMapperImpl净赴,路徑是:
app/build/generated/ap_generated_sources/debug/out/com/jokerwan/databinding/DataBinderMapperImpl.java
DataBinderMapperImpl#getDataBinder(bindingComponent, root, layoutId)
@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0) {
final Object tag = view.getTag();
if(tag == null) {
throw new RuntimeException("view must have a tag");
}
switch(localizedLayoutId) {
case LAYOUT_ACTIVITYMAIN: {
if ("layout/activity_main_0".equals(tag)) {
return new ActivityMainBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
}
}
}
return null;
}
判斷view的tag是不是與layout/activity_main_0
相等绳矩,如果相等,就new ActivityMainBindingImpl(component, view)
玖翅,這個(gè)ActivityMainBindingImpl就是DataBinding框架根據(jù)我們的activity_main.xml通過APT在編譯時(shí)生成的類翼馆,ActivityMainBindingImpl的路徑為:
app/build/generated/ap_generated_sources/debug/out/com/jokerwan/databinding/databinding/ActivityMainBindingImpl.java
有的小伙伴就有疑問了,為什么是layout/activity_main_0
金度,view的tag又是在哪里set進(jìn)去的呢?
原來应媚,數(shù)據(jù)綁定在處理布局的時(shí)候生成了兩個(gè)xml文件
- activity_main-layout.xml(DataBinding需要的布局控件信息)
- activity_main.xml(Android OS 渲染的布局文件)
activity_main-layout.xml
路徑:
app/build/intermediates/data_binding_layout_info_type_merge/debug/mergeDebugResources/out/activity_main-layout.xml
文件內(nèi)容:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout"
filePath="/Users/jokerwan/AndroidStudioProjects/DataBinding/app/src/main/res/layout/activity_main.xml"
isMerge="false" layout="activity_main" modulePackage="com.jokerwan.databinding">
<Variables name="userInfo" declared="true" type="com.jokerwan.databinding.UserInfo">
<location endLine="8" endOffset="54" startLine="6" startOffset="8" />
</Variables>
<Variables name="listener" declared="true" type="android.view.View.OnClickListener">
<location endLine="12" endOffset="54" startLine="10" startOffset="8" />
</Variables>
<Targets>
<Target tag="layout/activity_main_0" view="LinearLayout">
<Expressions />
<location endLine="44" endOffset="18" startLine="15" startOffset="4" />
</Target>
<Target tag="binding_1" view="TextView">
<Expressions>
<Expression attribute="android:text" text="userInfo.name">
<Location endLine="25" endOffset="42" startLine="25" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="25" endOffset="40" startLine="25" startOffset="28" />
</Expression>
</Expressions>
<location endLine="27" endOffset="28" startLine="22" startOffset="8" />
</Target>
<Target tag="binding_2" view="EditText">
<Expressions>
<Expression attribute="android:text" text="userInfo.nickName">
<Location endLine="32" endOffset="47" startLine="32" startOffset="12" />
<TwoWay>true</TwoWay>
<ValueLocation endLine="32" endOffset="45" startLine="32" startOffset="29" />
</Expression>
</Expressions>
<location endLine="34" endOffset="44" startLine="29" startOffset="8" />
</Target>
<Target id="@+id/btn_get_nick" tag="binding_3" view="Button">
<Expressions>
<Expression attribute="android:onClick" text="listener">
<Location endLine="42" endOffset="40" startLine="42" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="42" endOffset="38" startLine="42" startOffset="31" />
</Expression>
</Expressions>
<location endLine="42" endOffset="42" startLine="36" startOffset="8" />
</Target>
</Targets>
</Layout>
activity_main.xml
路徑:
app/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
文件內(nèi)容:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:gravity="center" android:tag="layout/activity_main_0" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="binding_1"
android:textSize="16sp"
tools:text="姓名"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="binding_2"
tools:text="昵稱"
android:layout_marginTop="10dp"/>
<Button
android:id="@+id/btn_get_nick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="獲取model里面的昵稱"
android:layout_marginTop="20dp"
android:tag="binding_3" />
</LinearLayout>
可以看到在activity_main.xml生成了輔助信息tag,每個(gè)容器和view都對(duì)應(yīng)一個(gè)tag猜极,而布局的第一個(gè)容器LinearLayout的tag就為layout/activity_main_0
中姜,這些tag在我們build工程時(shí)會(huì)隨著activity_main.xml的生成而存在。
接著看生成的ActivityMainBindingImpl#構(gòu)造器
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 4, sIncludes, sViewsWithIds));
}
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 1
, (android.widget.Button) bindings[3]
);
this.btnGetNick.setTag(null);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView1 = (android.widget.TextView) bindings[1];
this.mboundView1.setTag(null);
this.mboundView2 = (android.widget.EditText) bindings[2];
this.mboundView2.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
構(gòu)造函數(shù)內(nèi)首先調(diào)用mapBindings()
遞歸把root
中所有的view找出來,數(shù)字4是指布局中總共有4個(gè)View丢胚,然后還傳入sIncludes
和sViewsWithIds
,前者是布局中include進(jìn)來的布局的索引翩瓜,后者是布局中包含id的索引。
再回到構(gòu)造函數(shù)携龟,mapBindings()
查找到的View都放置在bindings
這個(gè)數(shù)組中氏义,并通過生成代碼的方式虚青,將它們一一取出來龄减,轉(zhuǎn)化為對(duì)應(yīng)的數(shù)據(jù)類型璃氢,有設(shè)置id的控件,會(huì)以id作為變量名蕊蝗,沒有設(shè)置id的控件仅乓,則以mboundView + 數(shù)字
的方式依次賦值。然后通過setRootTag(root)
方法通過setTag的方式將這個(gè)Binding和root關(guān)聯(lián)起來
protected void setRootTag(View view) {
view.setTag(R.id.dataBinding, this);
}
ViewDataBinding#mapBindings()
protected static Object[] mapBindings(DataBindingComponent bindingComponent, View[] roots, int numBindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds) {
Object[] bindings = new Object[numBindings];
for(int i = 0; i < roots.length; ++i) {
mapBindings(bindingComponent, roots[i], bindings, includes, viewsWithIds, true);
}
return bindings;
}
private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot) {
ViewDataBinding existingBinding = getBinding(view);
if (existingBinding == null) {
Object objTag = view.getTag();
String tag = objTag instanceof String ? (String)objTag : null;
boolean isBound = false;
int indexInIncludes;
int id;
int count;
if (isRoot && tag != null && tag.startsWith("layout")) {
id = tag.lastIndexOf(95);
if (id > 0 && isNumeric(tag, id + 1)) {
count = parseTagInt(tag, id + 1);
if (bindings[count] == null) {
bindings[count] = view;
}
indexInIncludes = includes == null ? -1 : count;
isBound = true;
} else {
indexInIncludes = -1;
}
} else if (tag != null && tag.startsWith("binding_")) {
id = parseTagInt(tag, BINDING_NUMBER_START);
if (bindings[id] == null) {
bindings[id] = view;
}
isBound = true;
indexInIncludes = includes == null ? -1 : id;
} else {
indexInIncludes = -1;
}
if (!isBound) {
id = view.getId();
if (id > 0 && viewsWithIds != null && (count = viewsWithIds.get(id, -1)) >= 0 && bindings[count] == null) {
bindings[count] = view;
}
}
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup)view;
count = viewGroup.getChildCount();
int minInclude = 0;
for(int i = 0; i < count; ++i) {
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(47) > 0) {
int includeIndex = findIncludeIndex(childTag, minInclude, includes, indexInIncludes);
if (includeIndex >= 0) {
isInclude = true;
minInclude = includeIndex + 1;
int index = includes.indexes[indexInIncludes][includeIndex];
int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
int lastMatchingIndex = findLastMatching(viewGroup, i);
if (lastMatchingIndex == i) {
bindings[index] = DataBindingUtil.bind(bindingComponent, child, layoutId);
} else {
int includeCount = lastMatchingIndex - i + 1;
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);
}
}
}
}
}
mapBindings()
方法主要是把root內(nèi)所有的view給查找出來匿又,并放置到bindings
對(duì)應(yīng)的索引內(nèi)方灾,這個(gè)索引如何確定呢?上面我們分析過碌更,DataBinding在加載我們的布局activity_main.xml時(shí)會(huì)生成兩個(gè)xml文件,一個(gè)用來關(guān)聯(lián)布局控件信息洞慎,一個(gè)是布局文件并對(duì)每個(gè)ViewGroup和View都打了一個(gè)tag痛单,通過解析這個(gè)tag,就能知道對(duì)應(yīng)的索引了劲腿。所以旭绒,為了避免自己inflate布局文件后,不小心操作了view的tag對(duì)解析產(chǎn)生干擾焦人,盡量使用數(shù)據(jù)綁定來得到inflate之后的view挥吵。
通過代碼我們可以發(fā)現(xiàn)mapBindings()
通過自身遞歸調(diào)用把root
中所有的view找出來,這里雖然用到了遞歸花椭,但實(shí)際上是通過這種方式實(shí)現(xiàn)對(duì)root下所有的控件的遍歷忽匈,因此整個(gè)方法的時(shí)間復(fù)雜度是O(n),通過一次遍歷矿辽,找到所有的控件丹允,整體性能比使用findViewById還優(yōu)秀,因?yàn)閒indViewById每次都要從DecorView開始循環(huán)遍歷找到對(duì)應(yīng)的View袋倔。
回到ActivityMainBindingImpl實(shí)現(xiàn)類的構(gòu)造器雕蔽,可以看到最后調(diào)用了invalidateAll()
@Override
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x8L;
}
requestRebind();
}
invalidateAll()
方法實(shí)現(xiàn)很簡單,將臟標(biāo)記位mDirtyFlags
標(biāo)記為0x8L
宾娜,即在二進(jìn)制表示上批狐,第4位的值為1,這個(gè)臟標(biāo)記位是一個(gè)long的值前塔,也就是最多有64個(gè)位可供使用嚣艇。由于mDirtyFlags
這個(gè)變量是成員變量缘眶,且多處會(huì)對(duì)其進(jìn)行寫操作,所以對(duì)它的寫操作加了同步鎖髓废。接著調(diào)用了requestRebind()
巷懈,ViewDataBinding#requestRebind()
protected void requestRebind() {
if (mContainingBinding != null) {
mContainingBinding.requestRebind();
} else {
final LifecycleOwner owner = this.mLifecycleOwner;
if (owner != null) {
Lifecycle.State state = owner.getLifecycle().getCurrentState();
if (!state.isAtLeast(Lifecycle.State.STARTED)) {
return; // wait until lifecycle owner is started
}
}
synchronized (this) {
if (mPendingRebind) {
return;
}
mPendingRebind = true;
}
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
mContainingBinding
表示當(dāng)前ViewDataBinding所包含的另外一個(gè)ViewDataBinding,如果當(dāng)前ViewDataBinding包含另一個(gè)ViewDataBinding慌洪,則子ViewDataBinding會(huì)優(yōu)先執(zhí)行rebind操作顶燕,從if (mContainingBinding != null)
則執(zhí)行mContainingBinding.requestRebind()
代碼中可以看出,若當(dāng)前ViewDataBinding內(nèi)部沒有包含另一個(gè)ViewDataBinding冈爹,則程序繼續(xù)走else邏輯涌攻,獲取到此ViewDataBinding綁定的LifecycleOwner,如果LifecycleOwner的生命周期狀態(tài)不是Lifecycle.State.STARTED
频伤,則直接return恳谎,不進(jìn)行綁定數(shù)據(jù)的相關(guān)操作,否則繼續(xù)執(zhí)行下面的程序代碼憋肖。
如果此前沒請(qǐng)求執(zhí)行rebind操作因痛,那么會(huì)將mPendingRebind
置為true,API等級(jí)16及以上岸更,會(huì)往mChoreographer
發(fā)一個(gè)mFrameCallback
鸵膏,在系統(tǒng)刷新界面(doFrame)的時(shí)候執(zhí)行rebind操作,API等級(jí)16以下怎炊,則是往UI線程post一個(gè)mRebindRunnable
任務(wù)谭企。mFrameCallback
的內(nèi)部實(shí)際上調(diào)用的是mRebindRunnable
的run方法,因此這兩個(gè)任務(wù)僅僅是調(diào)用時(shí)機(jī)不同评肆。
mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mRebindRunnable.run();
}
};
而如果此前請(qǐng)求過執(zhí)行rebind操作债查,即已經(jīng)post了一個(gè)任務(wù)到隊(duì)列去,而且這個(gè)任務(wù)還未獲得執(zhí)行瓜挽,此時(shí)mPendingRebind
的值為true盹廷,那么requestRebind將直接返回,避免重復(fù)秸抚、頻繁執(zhí)行rebind操作帶來的性能損耗速和。接著我們來看下mRebindRunnable
里面究竟干了什么不為人知的事情:
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
mPendingRebind = false;
}
processReferenceQueue();
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();
}
};
當(dāng)mRebindRunnable
的run()
方法執(zhí)行時(shí),先同步吧mPendingRebind
賦值為false剥汤,以便后續(xù)其他requestRebind能往主線程發(fā)起rebind的任務(wù)颠放。在 API 19及以上的版本,檢查下UI控件是否附加到了窗口上吭敢,如果沒有附到窗口上碰凶,則設(shè)置監(jiān)聽器,以便在UI附加到窗口上的時(shí)候立即執(zhí)行rebind操作,然后返回欲低。當(dāng) API 19 以下或UI控件已經(jīng)附加到窗口上辕宏,,則調(diào)用executePendingBindings()
執(zhí)行binding邏輯砾莱。繼續(xù)跟進(jìn)ViewDataBinding#executePendingBindings()
public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
在這里依舊先判斷若當(dāng)前ViewDataBinding內(nèi)部有沒有包含另一個(gè)ViewDataBinding瑞筐,若有包含,則先執(zhí)行子ViewDataBinding的executePendingBindings()
腊瑟,否則執(zhí)行自己的executePendingBindings()
private void executeBindingsInternal() {
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;
}
通過以上代碼可以看出聚假,此處進(jìn)行了binding操作之前的一些判定,如果已經(jīng)開始執(zhí)行綁定操作了闰非,即這段代碼正在執(zhí)行膘格,那么調(diào)用一次requestRebind,然后返回财松。接著調(diào)用hasPendingBindings()
判斷是否需要刷新UI瘪贱,若返回true表示需要刷新,繼續(xù)執(zhí)行代碼辆毡,否則直接return掉該方法菜秦。
Override
public boolean hasPendingBindings() {
synchronized(this) {
if (mDirtyFlags != 0) {
return true;
}
}
return false;
}
這里mDirtyFlags != 0
表示需要刷新UI,還記得之前invalidateAll()
中的mDirtyFlags = 0x8L
嗎胚迫?
接下來在執(zhí)行具體的executeBindings()
操作前喷户,調(diào)用下mRebindCallbacks.notifyCallbacks
通知所有回調(diào)將開始rebind操作,回調(diào)可以在執(zhí)行的過程中访锻,將mRebindHalted
置為true,阻止executeBindings()
方法的執(zhí)行闹获,攔截成功同樣通過回調(diào)進(jìn)行通知期犬。如果沒有被攔截,executeBindings()
方法會(huì)被執(zhí)行避诽,運(yùn)行結(jié)束后龟虎,同樣通過回調(diào)進(jìn)行通知。executeBindings()
是ViewDataBinding的一個(gè)抽象方法沙庐,具體實(shí)現(xiàn)在ActivityMainBindingImpl#executeBindings()
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
android.view.View.OnClickListener listener = mListener;
androidx.databinding.ObservableField<java.lang.String> userInfoNickName = null;
java.lang.String userInfoNickNameGet = null;
java.lang.String userInfoName = null;
com.jokerwan.databinding.UserInfo userInfo = mUserInfo;
if ((dirtyFlags & 0xaL) != 0) {
}
if ((dirtyFlags & 0xdL) != 0) {
if (userInfo != null) {
// read userInfo.nickName
userInfoNickName = userInfo.getNickName();
}
updateRegistration(0, userInfoNickName);
if (userInfoNickName != null) {
// read userInfo.nickName.get()
userInfoNickNameGet = userInfoNickName.get();
}
if ((dirtyFlags & 0xcL) != 0) {
if (userInfo != null) {
// read userInfo.name
userInfoName = userInfo.getName();
}
}
}
// batch finished
if ((dirtyFlags & 0xaL) != 0) {
// api target 1
this.btnGetNick.setOnClickListener(listener);
}
if ((dirtyFlags & 0xcL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, userInfoName);
}
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userInfoNickNameGet);
}
if ((dirtyFlags & 0x8L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView2androidTextAttrChanged);
}
}
首先將臟標(biāo)記為存儲(chǔ)到局部變量dirtyFlags
中鲤妥,然后將臟數(shù)據(jù)標(biāo)記位mDirtyFlags
置0,然后對(duì)綁定到布局文件的model對(duì)象進(jìn)行判空拱雏,并取到對(duì)應(yīng)的值賦值給對(duì)應(yīng)的控件棉安,這里又體現(xiàn)出了數(shù)據(jù)綁定的一個(gè)優(yōu)勢(shì):在進(jìn)行數(shù)據(jù)相關(guān)的操作前,會(huì)檢查變量是否為空铸抑,倘若沒有傳入對(duì)應(yīng)的變量贡耽,或者傳入null,在布局上進(jìn)行的操作并不會(huì)執(zhí)行,因此蒲赂,假如上述例子中阱冶,我們沒有傳入對(duì)應(yīng)的userInfo對(duì)象也不會(huì)引發(fā)Crash。
接著根據(jù)臟標(biāo)記位和相關(guān)的值進(jìn)行位與運(yùn)算來判斷滥嘴,上面我們已經(jīng)知道木蹬,在ActivityMainBindingImpl構(gòu)造函數(shù)調(diào)用了invalidateAll()
將mDirtyFlags
置0x8L
,轉(zhuǎn)為二進(jìn)制第四位為1若皱,上述四個(gè)if條件經(jīng)過位與運(yùn)算后與0比較都為真镊叁,即這種情況下上述條件里面的代碼都會(huì)執(zhí)行。這里主要執(zhí)行的操作:
獲取
userInfoName
是尖,將userInfoName
綁定到mboundView1
意系,獲取userInfoNickNameGet
,并將其綁定到mboundView2
饺汹,通過代碼可以看到蛔添,每一個(gè)被綁定到View上的通過variable
標(biāo)簽定義的變量都會(huì)有一個(gè)專屬的標(biāo)記位,當(dāng)該變量的值被更新時(shí)兜辞,對(duì)應(yīng)的臟標(biāo)記位就會(huì)置為1迎瞧,executeBindings()
執(zhí)行的時(shí)候就會(huì)將變動(dòng)的數(shù)據(jù)更新到對(duì)應(yīng)的UI控件上。-
在設(shè)置了雙向綁定的控件上逸吵,為其添加對(duì)應(yīng)的監(jiān)聽器凶硅,監(jiān)聽其變動(dòng),如:
EditText
上設(shè)置TextWatcher
扫皱,具體的設(shè)置邏輯放置到了TextViewBindingAdapter.setTextWatcher里足绅。源碼如下:@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); } }
代碼中創(chuàng)建了一個(gè)新的
TextWatcher
,并將傳進(jìn)來的mboundView2androidTextAttrChanged
監(jiān)聽器包裹在里面韩脑。
當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候氢妈,TextWatcher
在回調(diào)onTextChanged()
的最后,會(huì)通過textAttrChanged.onChange()
回調(diào)到傳入的mboundView2androidTextAttrChanged
的onChange()
段多。在這里我們也看到了熟悉的
@BindingAdapter
注解首量,這個(gè)注解實(shí)現(xiàn)了控件屬性和代碼內(nèi)的方法調(diào)用的映射,編譯期进苍,數(shù)據(jù)綁定框架通過這種方式加缘,為對(duì)應(yīng)的控件生成對(duì)應(yīng)的方法調(diào)用。接著我們來看下傳進(jìn)來的監(jiān)聽器
mboundView2androidTextAttrChanged
的代碼private androidx.databinding.InverseBindingListener mboundView2androidTextAttrChanged = new androidx.databinding.InverseBindingListener() { @Override public void onChange() { // Inverse of userInfo.nickName.get() // is userInfo.nickName.set((java.lang.String) callbackArg_0) java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView2); // localize variables for thread safety // userInfo != null boolean userInfoJavaLangObjectNull = false; // userInfo.nickName androidx.databinding.ObservableField<java.lang.String> userInfoNickName = null; // userInfo.nickName.get() java.lang.String userInfoNickNameGet = null; // userInfo com.jokerwan.databinding.UserInfo userInfo = mUserInfo; // userInfo.nickName != null boolean userInfoNickNameJavaLangObjectNull = false; userInfoJavaLangObjectNull = (userInfo) != (null); if (userInfoJavaLangObjectNull) { userInfoNickName = userInfo.getNickName(); userInfoNickNameJavaLangObjectNull = (userInfoNickName) != (null); if (userInfoNickNameJavaLangObjectNull) { userInfoNickName.set(((java.lang.String) (callbackArg_0))); } } } };
這段代碼會(huì)去對(duì)應(yīng)的View中取得控件中最新的值觉啊,然后一系列地判斷布局文件中綁定的userInfo以及對(duì)應(yīng)的屬性是否為null拣宏,不為null時(shí)將值更新為從控件中取出的最新值。
上面講到的情況是當(dāng)UI控件上
EditText
顯示內(nèi)容改變時(shí)通知綁定到布局文件的ObservableField更新柄延,那當(dāng)ObservableField更新后是如何通知UI更新的呢蚀浆?我們繼續(xù)回到ActivityMainBindingImpl#executeBindings()... if ((dirtyFlags & 0xdL) != 0) { if (userInfo != null) { // read userInfo.nickName userInfoNickName = userInfo.getNickName(); } updateRegistration(0, userInfoNickName); ... } ...
注意我們綁定到布局中的
userInfo
中的nickName
的類型是ObservableField<String>
缀程,通過注釋我們也可以看到此處的userInfoNickName
就是讀取的userInfo.nickName
,接著調(diào)用updateRegistration(0, userInfoNickName)
市俊,ObservableField最終是繼承Observable杨凑,我們不妨猜想一下,updateRegistration()方法傳入一個(gè)Observable對(duì)象應(yīng)該是要觀察這個(gè)對(duì)象的變化摆昧,當(dāng)被觀察者發(fā)生變化時(shí)去重新執(zhí)行rebind操作撩满,第一個(gè)參數(shù)0表示的是userInfoNickName的id,看代碼ViewDataBinding#updateRegistration()protected boolean updateRegistration(int localFieldId, Observable observable) { return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER); }
這個(gè)方法中接著調(diào)用updateRegistration方法并傳入一個(gè)
CREATE_PROPERTY_LISTENER
常量private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() { @Override public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) { return new WeakPropertyListener(viewDataBinding, localFieldId).getListener(); } };
通過代碼發(fā)現(xiàn)
CREATE_PROPERTY_LISTENER
是一個(gè)屬性監(jiān)聽器創(chuàng)建類绅你,其中new WeakPropertyListener(viewDataBinding, localFieldId)
并返回其監(jiān)聽器伺帘,我們先繼續(xù)看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; }
當(dāng)傳進(jìn)來的observable為null時(shí),解注冊(cè)該被觀察者忌锯,后面的邏輯就是先判斷傳進(jìn)來的observable的監(jiān)聽器在
mLocalFieldObservers
中是否存在伪嫁,存在就根據(jù)localFieldId
拿出來先重新綁定監(jiān)聽器,不存在就新創(chuàng)建一個(gè)監(jiān)聽器并綁定該observable根據(jù)localFieldId
存進(jìn)mLocalFieldObservers
中偶垮,mLocalFieldObservers
是一個(gè)數(shù)組张咳,我們繼續(xù)看registerTo(localFieldId, observable, listenerCreator)
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; if (mLifecycleOwner != null) { listener.setLifecycleOwner(mLifecycleOwner); } } listener.setTarget(observable); }
當(dāng)
listener
為null時(shí),調(diào)用傳進(jìn)來的監(jiān)聽器CreateWeakListener
的create(this, localFieldId)
方法創(chuàng)建listener似舵,并且給listener設(shè)置當(dāng)前ViewDataBinding的LifecycleOwner和對(duì)應(yīng)的Target為observable
脚猾,這里的this
就是指當(dāng)前的ViewDataBinding,CreateWeakListener是一個(gè)抽象類砚哗,我們之前new出來的WeakPropertyListener
是它的實(shí)現(xiàn)類龙助,接下來我們看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 setLifecycleOwner(LifecycleOwner lifecycleOwner) { } @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); } }
首先看其構(gòu)造方法
mListener = new WeakListener<Observable>(binder, localFieldId, this);
這里new一個(gè)WeakListener并且將ViewDataBinding類型的binder對(duì)象和localFieldId傳進(jìn)去,好蛛芥,繼續(xù)往下看提鸟,注意看onPropertyChanged()
方法,見名知意仅淑,這個(gè)方法應(yīng)該是當(dāng)屬性變化時(shí)會(huì)調(diào)用沽一,從mListener
中取出binder,當(dāng)binder為null時(shí)直接退出漓糙,從mListener取出之前存入的Observable對(duì)象,并校驗(yàn)下當(dāng)前獲取到的Observable對(duì)象是否是發(fā)出更新消息的Observable對(duì)象烘嘱,如果不是昆禽,則不作處理,如果是蝇庭,則調(diào)用binder的handleFieldChange()
方法醉鳖,跟進(jìn)ViewDataBinding#handleFieldChange()private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) { if (mInLiveDataRegisterObserver) { // We're in LiveData registration, which always results in a field change // that we can ignore. The value will be read immediately after anyway, so // there is no need to be dirty. return; } boolean result = onFieldChange(mLocalFieldId, object, fieldId); if (result) { requestRebind(); } }
這里面會(huì)調(diào)用
onFieldChange()
方法來判斷需要更新的數(shù)據(jù)是否有變化,這個(gè)方法是ViewDataBinding的抽象方法哮内,具體實(shí)現(xiàn)在ActivityMainBindingImpl#onFieldChange()@Override protected boolean onFieldChange(int localFieldId, Object object, int fieldId) { switch (localFieldId) { case 0 : return onChangeUserInfoNickName((androidx.databinding.ObservableField<java.lang.String>) object, fieldId); } return false; } private boolean onChangeUserInfoNickName(androidx.databinding.ObservableField<java.lang.String> UserInfoNickName, int fieldId) { if (fieldId == BR._all) { synchronized(this) { mDirtyFlags |= 0x1L; } return true; } return false; }
還記得調(diào)用
updateRegistration(0, userInfoNickName)
方法嗎盗棵,第一個(gè)參數(shù)就是nickName的localFieldId壮韭,傳的值是0,匹配到switch語句的0纹因,返回onChangeUserInfoNickName()
的結(jié)果喷屋,這里會(huì)重新將臟數(shù)據(jù)進(jìn)行位或運(yùn)算,將nickName對(duì)應(yīng)的二進(jìn)制標(biāo)記位置為1瞭恰,并返回true屯曹,handleFieldChange()
方法中會(huì)接受結(jié)果為true則調(diào)用requestRebind()
重新綁定更改過的數(shù)據(jù),在requestRebind()
會(huì)重新獲取nickName的值惊畏,并判斷臟標(biāo)記位是否滿足更新UI控件條件恶耽,滿足則更新UI,當(dāng)我們給ObservableField對(duì)象set值的時(shí)候會(huì)調(diào)用notifyChange()
public void notifyChange() { synchronized (this) { if (mCallbacks == null) { return; } } mCallbacks.notifyCallbacks(this, 0, null); }
mCallbacks
的類型是PropertyChangeRegistry颜启,通過addOnPropertyChangedCallback(OnPropertyChangedCallback callback)
方法創(chuàng)建偷俭,并且把callback添加到mCallbacks
中,繼續(xù)回到
WeakPropertyListener
類@Override public void addListener(Observable target) { target.addOnPropertyChangedCallback(this); } @Override public void removeListener(Observable target) { target.removeOnPropertyChangedCallback(this); }
發(fā)現(xiàn)它集成自O(shè)bservable.OnPropertyChangedCallback缰盏,并且在被調(diào)用
addListener()
方法是將自己添加到ObservableField對(duì)象的mCallbacks
中涌萤,并在removeListener()
被調(diào)用時(shí)移除回調(diào),這里就是把屬性監(jiān)聽器綁定在ObservableField對(duì)象中乳规,當(dāng)ObservableField對(duì)象值改變時(shí)會(huì)調(diào)用notifyChange()
形葬,經(jīng)過一系列的回調(diào)和通信,最終會(huì)調(diào)用WeakPropertyListener
類的onPropertyChanged()
方法來通知ViewDataBinding來執(zhí)行rebind操作暮的。
至此笙以,ObservableField更新后是通知UI更新的流程就分析完畢。
三冻辩、總結(jié)
DataBingding用起來很方便猖腕,可以幫助我們簡化使Activity/Fragment中部分操作View的代碼,讓我們更加關(guān)注業(yè)務(wù)邏輯的實(shí)現(xiàn)恨闪。但本文講的DataBingding原理可能理解起來比較困難倘感,你也沒有必要去計(jì)較DataBingding庫中的一些細(xì)枝末節(jié)的代碼,我們只需要疏通整個(gè)鏈路咙咽,知道DataBingding框架如何實(shí)現(xiàn)雙向綁定的大概思路即可老玛,了解其中的觀察者設(shè)計(jì)模式。通過這篇文章的學(xué)習(xí)钧敞,了解了DataBingding原理蜡豹,在日后使用DataBingding和定位使用DataBinding產(chǎn)生的bug時(shí)也會(huì)得心應(yīng)手。