1 簡介和簡單使用
1.1 簡介
DataBinding是Google推出的一款數(shù)據(jù)和視圖綁定庫脂矫,可以省去findViewById
和setText
枣耀,能大量減少業(yè)務(wù)邏輯和布局之間的繁瑣代碼。支持雙向綁定羹唠,也就是當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候奕枢,不用setText
,UI就會(huì)自動(dòng)更新佩微。而UI上的內(nèi)容發(fā)生變化的時(shí)候缝彬,對(duì)應(yīng)的數(shù)據(jù)也會(huì)同步修改。DataBinding多用于MVVM架構(gòu)哺眯。
DataBinding使用APT技術(shù)谷浅,自動(dòng)生成輔助代碼,底層實(shí)現(xiàn)最終也是通過setText
和TextWatcher
來實(shí)雙向綁定。
優(yōu)點(diǎn):使用簡單一疯,支持雙向綁定撼玄,適合MVVM架構(gòu),適合數(shù)據(jù)變化頻繁的APP墩邀。
缺點(diǎn):在View過于復(fù)雜的時(shí)候性能會(huì)比較低掌猛,不適合低端手機(jī)運(yùn)行。
Google官方文檔:https://developer.android.google.cn/topic/libraries/data-binding
1.2 在Java項(xiàng)目中使用
開啟DataBinding
方式1
android {
...
dataBinding {
enabled = true
}
}
方式2
android {
...
dataBinding.enabled=true
}
創(chuàng)建數(shù)據(jù)實(shí)體眉睹,繼承BaseObservable
荔茬,實(shí)現(xiàn)get/set
方法,然后編譯竹海。會(huì)通過APT生成一個(gè)BR
類慕蔚。然后在get
方法上用@Bindable
標(biāo)注,在set
方法內(nèi)調(diào)用notifyPropertyChanged()
斋配,notifyPropertyChanged()
方法就是用來通知屬性數(shù)據(jù)變化的孔飒。
public class Book extends BaseObservable {
private String name;
private String author;
public Book(String name, String author) {
this.name = name;
this.author = author;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
notifyPropertyChanged(BR.author);
}
}
將xml修改為DataBinding類型的xml,用<layout>標(biāo)簽包裹艰争。<data>標(biāo)簽下用來定義數(shù)據(jù)源坏瞄。然后在TextView中使用@{book.name}
的形式,即可獲取數(shù)據(jù)园细。
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="book"
type="cn.zhangmushui.databindinguseforjava.Book" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{book.name}" />
<TextView
android:id="@+id/author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{book.author}" />
<Button
android:id="@+id/btn_change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="修改數(shù)據(jù)" />
</LinearLayout>
</layout>
在Activity中惦积,建立數(shù)據(jù)和View的綁定關(guān)系。這樣猛频,修改book對(duì)象中的數(shù)據(jù)的時(shí)候狮崩,UI就會(huì)自動(dòng)同步更新。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//替代setContentView鹿寻,ActivityMainBinding是APT生成的代碼睦柴,對(duì)應(yīng)activity_main布局
final ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
Book book = new Book("沉默的大多數(shù)", "王小波");
//建立綁定關(guān)系
binding.setBook(book);
//修改數(shù)據(jù),無需調(diào)用setText毡熏,UI就能更新
binding.btnChange.setOnClickListener(v -> {
book.setName("平凡的世界");
book.setAuthor("路遙");
});
}
}
1.3 在Kotlin項(xiàng)目中使用
開啟DataBinding
方式1
android {
...
dataBinding {
enabled = true
}
}
方式2
android {
...
dataBinding.enabled=true
}
創(chuàng)建數(shù)據(jù)實(shí)體坦敌,與Java不同的是,Kotlin的數(shù)據(jù)實(shí)體自動(dòng)帶有set/get
方法痢法,沒法使用@Bindable
標(biāo)注和notifyPropertyChanged()
狱窘,所以Kotlin中采用以下方式。
class Book {
val name: ObservableField<String> by lazy { ObservableField<String>() }
val author: ObservableField<String> by lazy { ObservableField<String>() }
}
同樣修改xml文件财搁。這里需要注意蘸炸,@{book.name}
是單項(xiàng)綁定,也就是ViewModel修改了數(shù)據(jù)尖奔,UI會(huì)同步搭儒,反之不會(huì)同步穷当。@={book.name}
是雙向綁定,任何一方有了改動(dòng)淹禾,另一方都會(huì)同步馁菜。
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="book"
type="cn.zhangmushui.databindinguseforkt.Book" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:id="@+id/et_name_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="書名1"
android:text="@{book.name}" />
<EditText
android:id="@+id/et_author_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="作者1"
android:text="@{book.author}" />
<EditText
android:id="@+id/et_name_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:hint="書名2"
android:text="@={book.name}" />
<EditText
android:id="@+id/et_author_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="作者2"
android:text="@={book.author}" />
</LinearLayout>
</layout>
在Activity中進(jìn)行綁定。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding =
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
val book = Book()
book.name.set("沉默的大多數(shù)")
book.author.set("王小波")
binding.book=book
}
}
運(yùn)行后發(fā)現(xiàn)铃岔,修改前兩個(gè)輸入框的數(shù)據(jù)汪疮,后兩輸入框的數(shù)據(jù)不會(huì)變化,這說明前兩個(gè)是單向綁定德撬。而修改后兩個(gè)輸入框的數(shù)據(jù)铲咨,前兩個(gè)會(huì)跟著變化,說明后兩個(gè)是雙向綁定蜓洪。
1.4 業(yè)務(wù)架構(gòu)
2 源碼分析
以下源碼分析以上邊Kotlin示例進(jìn)行。
2.1 布局轉(zhuǎn)換
當(dāng)原始的xml文件轉(zhuǎn)換為DataBinding的xml文件之后坯苹,會(huì)被DataBinding拆分生成為兩個(gè)xml文件隆檀。
第一個(gè)是build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_main-layout.xml
。
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="databindinguseforkt/src/main/res/layout/activity_main.xml"
isBindingData="true" isMerge="false"
layout="activity_main" modulePackage="cn.zhangmushui.databindinguseforkt" rootNodeType="android.widget.LinearLayout">
<Variables name="book" declared="true" type="cn.zhangmushui.databindinguseforkt.Book">
<location endLine="8" endOffset="60" startLine="6" startOffset="8" />
</Variables>
<Targets>
<Target tag="layout/activity_main_0" view="LinearLayout">
<Expressions />
<location endLine="46" endOffset="18" startLine="11" startOffset="4" />
</Target>
<Target id="@+id/et_name_1" tag="binding_1" view="EditText">
<Expressions>
<Expression attribute="android:text" text="book.name">
<Location endLine="22" endOffset="38" startLine="22" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="22" endOffset="36" startLine="22" startOffset="28" />
</Expression>
</Expressions>
<location endLine="22" endOffset="41" startLine="17" startOffset="8" />
</Target>
<Target id="@+id/et_author_1" tag="binding_2" view="EditText">
<Expressions>
<Expression attribute="android:text" text="book.author">
<Location endLine="29" endOffset="40" startLine="29" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="29" endOffset="38" startLine="29" startOffset="28" />
</Expression>
</Expressions>
<location endLine="29" endOffset="43" startLine="24" startOffset="8" />
</Target>
<Target id="@+id/et_name_2" tag="binding_3" view="EditText">
<Expressions>
<Expression attribute="android:text" text="book.name">
<Location endLine="35" endOffset="39" startLine="35" startOffset="12" />
<TwoWay>true</TwoWay>
<ValueLocation endLine="35" endOffset="37" startLine="35" startOffset="29" />
</Expression>
</Expressions>
<location endLine="37" endOffset="32" startLine="31" startOffset="8" />
</Target>
<Target id="@+id/et_author_2" tag="binding_4" view="EditText">
<Expressions>
<Expression attribute="android:text" text="book.author">
<Location endLine="43" endOffset="41" startLine="43" startOffset="12" />
<TwoWay>true</TwoWay>
<ValueLocation endLine="43" endOffset="39" startLine="43" startOffset="29" />
</Expression>
</Expressions>
<location endLine="44" endOffset="32" startLine="39" startOffset="8" />
</Target>
</Targets>
</Layout>
里邊定義了數(shù)據(jù)對(duì)象和多個(gè)<Target>粹湃,每個(gè)Target對(duì)應(yīng)著布局文件中的一個(gè)控件恐仑,一個(gè)控件id對(duì)應(yīng)一個(gè)tag屬性。
在第二個(gè)拆分出來的build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
中为鳄,每個(gè)控件會(huì)添加一個(gè)tag屬性裳仆。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" 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">
<EditText
android:id="@+id/et_name_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="書名1"
android:tag="binding_1" />
<EditText
android:id="@+id/et_author_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="作者1"
android:tag="binding_2" />
<EditText
android:id="@+id/et_name_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="binding_3"
android:layout_marginTop="30dp"
android:hint="書名2" />
<EditText
android:id="@+id/et_author_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="binding_4"
android:hint="作者2" />
</LinearLayout>
2.2 源碼分析
首先進(jìn)入DataBindingUtil.setContentView
,可以看到這里返回的類型是ViewDataBinding
孤钦。
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
繼續(xù)往下走歧斟,調(diào)用了bindToAddedViews
方法。
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);
}
然后都走了bind
方法偏形。
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);
}
}
然后調(diào)用了抽象類DataBinderMapper
的getDataBinder
方法静袖。
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}
public abstract class DataBinderMapper {
... int layoutId);
public abstract ViewDataBinding getDataBinder(DataBindingComponent bindingComponent,
View[] view, int layoutId);
...
}
這里DataBinderMapper
里的抽象方法的真正實(shí)現(xiàn)是在APT生成的build/generated/source/kapt/debug/包名/DataBinderMapperImpl.java
中,而不是DataBinding
的aar中的DataBinderMapperImpl.java
俊扭。
public class DataBinderMapperImpl extends DataBinderMapper {
...
@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: {
//這里去找到activity_main-layout.xml中tag為layout/activity_main_0的布局
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;
}
}
然后實(shí)例化了build/generated/source/kapt/debug/包名/databinding/ActivityMainBindingImpl.java
队橙。
public class ActivityMainBindingImpl extends ActivityMainBinding {
...
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds));
}
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 2
, (android.widget.EditText) bindings[2]
, (android.widget.EditText) bindings[4]
, (android.widget.EditText) bindings[1]
, (android.widget.EditText) bindings[3]
);
this.etAuthor1.setTag(null);
this.etAuthor2.setTag(null);
this.etName1.setTag(null);
this.etName2.setTag(null);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
...
}
由于業(yè)務(wù)層調(diào)用public static <T extends ViewDataBinding> T setContentView()
傳入的泛型是ViewDataBinding
,所以ActivityMainBindingImpl
里的super
最終是到了ViewDataBinding
中萨惑。ViewDataBinding
首先執(zhí)行了一個(gè)static
代碼塊捐康。static
中是一個(gè)監(jiān)聽,當(dāng)view附著于屏幕的時(shí)候庸蔼,就會(huì)觸發(fā)監(jiān)聽解总。
static {
if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
ROOT_REATTACHED_LISTENER = null;
} else {
ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
@TargetApi(VERSION_CODES.KITKAT)
@Override
public void onViewAttachedToWindow(View v) {
// execute the pending bindings.
final ViewDataBinding binding = getBinding(v);
binding.mRebindRunnable.run();
v.removeOnAttachStateChangeListener(this);
}
@Override
public void onViewDetachedFromWindow(View v) {
}
};
}
}
監(jiān)聽中執(zhí)行了了一個(gè)mRebindRunnable
,調(diào)用了executePendingBindings
方法朱嘴。
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();
}
};
在ActivityMainBindingImpl
構(gòu)造方法中倾鲫,調(diào)用了invalidateAll()
粗合。
@Override
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x8L;
}
requestRebind();
}
invalidateAll
中調(diào)用了requestRebind
,requestRebind
是ActivityMainBindingImpl
父類ViewDataBinding
中的方法乌昔。
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);
}
}
}
在requestRebind
中隙疚,執(zhí)行了mRebindRunnable
。mRebindRunnable
中的run
方法執(zhí)行了兩個(gè)內(nèi)容:如果mRoot
已經(jīng)和屏幕進(jìn)行了綁定磕道,那么直接執(zhí)行executePendingBindings
方法供屉;如果沒有綁定,先添加監(jiān)聽溺蕉,什么時(shí)候綁定了伶丐,什么時(shí)候執(zhí)行。
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();
}
};
在executePendingBindings
中疯特,執(zhí)行了executeBindingsInternal
哗魂。
public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
在executeBindingsInternal
方法中,會(huì)進(jìn)入executeBindings()
方法漓雅。
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;
}
protected abstract void executeBindings();
executeBindings
的具體實(shí)現(xiàn)在APT生成的ActivityMainBindingImpl
中录别。
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
androidx.databinding.ObservableField<java.lang.String> bookName = null;
java.lang.String bookNameGet = null;
java.lang.String bookAuthorGet = null;
cn.zhangmushui.databindinguseforkt.Book book = mBook;
androidx.databinding.ObservableField<java.lang.String> bookAuthor = null;
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xdL) != 0) {
if (book != null) {
// read book.name
bookName = book.getName();
}
updateRegistration(0, bookName);
if (bookName != null) {
// read book.name.get()
bookNameGet = bookName.get();
}
}
if ((dirtyFlags & 0xeL) != 0) {
if (book != null) {
// read book.author
bookAuthor = book.getAuthor();
}
updateRegistration(1, bookAuthor);
if (bookAuthor != null) {
// read book.author.get()
bookAuthorGet = bookAuthor.get();
}
}
}
// batch finished
if ((dirtyFlags & 0xeL) != 0) {
// api target 1
//單項(xiàng)綁定,最終也是調(diào)用了setText去給TextView設(shè)置數(shù)據(jù)
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etAuthor1, bookAuthorGet);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etAuthor2, bookAuthorGet);
}
if ((dirtyFlags & 0x8L) != 0) {
// api target 1
//雙向綁定邻吞,使用TextWatcher監(jiān)聽EditText數(shù)據(jù)變化组题,去修改Model
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etAuthor2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etAuthor2androidTextAttrChanged);
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etName2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etName2androidTextAttrChanged);
}
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etName1, bookNameGet);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etName2, bookNameGet);
}
}
至此,整個(gè)流程結(jié)束抱冷〈蘖校可以看到DataBindding框架是使用APT去生成輔助工具類,最終也是將setText
和TextWatcher
包裝起來旺遮,在框架內(nèi)部
進(jìn)行調(diào)用赵讯。這樣就簡化了業(yè)務(wù)層的代碼。
3 流程圖
關(guān)注木水小站 (zhangmushui.cn)和微信公眾號(hào)【木水Code】趣效,及時(shí)獲取更多最新技術(shù)干貨瘦癌。