前言
做過 iOS
的同學(xué)應(yīng)該都了解過 KVO
呜象,是觀察者模式在 Objective-C 中的應(yīng)用膳凝。使用 KVO
,能很方便的實現(xiàn)對對象屬性的監(jiān)聽恭陡。雖然 iOS
提供了對對象屬性的觀察者模式機制,但想想很多 Android
同學(xué)們應(yīng)該不會在意上煤。這不是很容易么休玩,我分分鐘也能寫一個:
public class User {
String mName;
Observable mObservable;
public User(String name) {
mName = name;
}
public String getName() {
return mName;
}
public void setName(String name) {
boolean isSame = TextUtils.equals(this.mName, name);
this.mName = name;
if (!isSame && mObservable != null) {
mObservable.onNameChanged(name);
}
}
public void setObservable(Observable observer) {
this.mObservable = observer;
}
public interface Observable {
void onNameChanged(String newName);
}
}
User user = new User("我叫王尼瑪");
user.setObservable(new User.Observable() {
@Override
public void onNameChanged(String newName) {
Log.i("user newName = ", newName);
}
});
user.setName("呵呵,這你都信");
但是冷靜下來想想劫狠,如果一個大的工程中有很多這種需求呢拴疤?是不是 User1
, User2
独泞,...... 都要寫這些機械的代碼了呐矾?那回過頭想想,如果不想自己寫這些代碼的話懦砂,那么我們大 Android
真的就沒有這種機制么蜒犯?想想不服氣,于是翻了翻資料荞膘,果然我們還是有的: ObservableField
ObservableField
1. 使用方式
使用還是很簡單的罚随,我們直接看代碼吧
ObservableField<String> name = new ObservableField<>();
name.addOnPropertyChangedCallback(
new android.databinding.Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(android.databinding.Observable observable, int i) {
Log.d("name = ", "name = " + observable.toString() + "; i= " + i);
}
});
name.set("我叫張三");
這下舒服多了,不用自己實現(xiàn) Observable
接口和 setObservable
方法羽资,同時對于其他類型的變量淘菩,如 int,float 或者 自定義的類型屠升,也不用重新實現(xiàn)了潮改,直接定義 ObservableField<Integer>
、ObservableField<Float>
就行了腹暖,好開心_
2. 原理實現(xiàn)
還是直接看源碼吧汇在,反正代碼量也不多 O(∩_∩)O~
public class ObservableField<T> extends BaseObservable implements Serializable {
static final long serialVersionUID = 1L;
private T mValue;
......
public T get() {
return mValue;
}
public void set(T value) {
if (value != mValue) {
mValue = value;
notifyChange();
}
}
}
public class BaseObservable implements Observable {
private transient PropertyChangeRegistry mCallbacks;
......
@Override
public synchronized void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
......
mCallbacks.add(callback);
}
@Override
public synchronized void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
if (mCallbacks != null) {
mCallbacks.remove(callback);
}
}
public synchronized void notifyChange() {
if (mCallbacks != null) {
mCallbacks.notifyCallbacks(this, 0, null);
}
}
......
}
注:mCallbacks.notifyCallbacks(this, 0, null); 方法中,0 表示的是 fieldID微服,在 dataBinding 中表示數(shù)據(jù)資源 id趾疚。因此這里并沒有關(guān)聯(lián)視圖資源,所以這里設(shè)置為 0
可以看到以蕴,ObservableField
是一個泛型糙麦,所以支持多種類型的觀察者模式。BaseObservable
是其父類丛肮,實現(xiàn)了觀察者模式的核心代碼赡磅,可以看到 addOnPropertyChangedCallback
和 addOnPropertyChangedCallback
2個添加和移除監(jiān)聽的方法,真正的監(jiān)聽者都被保存到 PropertyChangeRegistry.mCallbacks
(類型是 List
) 里面宝与。
當(dāng)調(diào)用 ObservableField
的 set
方法時焚廊,會執(zhí)行 ObservableField.notifiyCallbacks
方法冶匹,如下:
public class BaseObservable implements Observable {
public synchronized void notifyChange() {
if (mCallbacks != null) {
mCallbacks.notifyCallbacks(this, 0, null);
}
}
......
}
最終會執(zhí)行到 callback.onPropertyChanged(sender, arg);
,如下代碼所示:
public class CallbackRegistry<C, T, A> implements Cloneable {
private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
final int endIndex, final long bits) {
......
for (int i = startIndex; i < endIndex; i++) {
......
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
.....
}
}
......
}
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);
}
};
這里咆瘟,我們可以看到嚼隘,通過遍歷的方式,去執(zhí)行 callback
方法袒餐,將前面通過 BaseObservable.addOnPropertyChangedCallback
添加的全部觀察者都響應(yīng)了一邊
3. 小結(jié)
到這里為止飞蛹,雖然把 ObservableField
的觀察者模式給講清楚了,但還是感覺有些失望灸眼,內(nèi)容很少很簡單卧檐。不過還沒完呢,Google 大神們就是基于此焰宣,玩出了 DataBinding
DataBinding
使用步驟
-
IDE 配置
Android SDK API 版本 7 以上
使用
Gradle 1.5.0-alpha1
及以上使用
Android Studio 1.3
及以上
-
配置開啟 dataBinding
在主工程的
build.gradle
中霉囚,添加代碼:android { ...... dataBinding { enabled = true } }
-
定義數(shù)據(jù)層 Model
定義的
ObservableUser
類,繼承自BaseObservable
(同前面的ObservableField
)匕积。在set
接口里添加notifyPropertyChanged
調(diào)用盈罐,通知視圖更新public class ObservableUser extends BaseObservable { private String firstName; private String lastName; public ObservableUser(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } @Bindable public String getFirstName() { return firstName; } @Bindable public String getLastName() { return lastName; } public void setFirstName(String firstName) { this.firstName = firstName; notifyPropertyChanged(com.netease.mvvmsample.BR.firstName); } public void setLastName(String lastName) { this.lastName = lastName; notifyPropertyChanged(com.netease.mvvmsample.BR.lastName); } }
-
定義事件響應(yīng) Handler
public class Handler { private ObservableUser mObservableUser; public Handler(ObservableUser user) { mObservableUser = user; } public void onClickButton(View view) { mObservableUser.setLastName("呵呵呵,我變了 " + mCount++ + " 次"); } }
-
定義布局代碼
在 data 標(biāo)簽下面闸天,定義 model 數(shù)據(jù)和事件響應(yīng) handler暖呕。使用
@{}
分別將Button
的文本信息和user.lastName
,Button
的點擊響應(yīng)和handler.onClickButton
綁定在一起<?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="user" type="com.netease.mvvmsample.ObservableUser"> </variable> <variable name="handler" type="com.netease.mvvmsample.Handler"> </variable> </data> <LinearLayout <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}" android:onClick="@{handler.onClickButton}"/> </LinearLayout </layout>
-
設(shè)置布局和綁定數(shù)據(jù)和事件
在定義了上面的布局 xml 文件之后苞氮,Android Studio 會自動生成 ViewModel 類湾揽。假設(shè)文件名是
activity_main.xml
,那么程序編譯后笼吟,會生成ActivityMainBinding
類库物。代替原來的setContentView(R.layout.activity_main);
方法,使用ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
來設(shè)置布局資源贷帮,并返回binding
對象戚揭。新建數(shù)據(jù)和事件處理對象,并設(shè)置給binding
對象public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); ObservableUser user = new ObservableUser("Zhang", "San"); Handler handler = new Handler(user); binding.setUser(user); binding.setHandler(handler); } }
-
效果展示
注:這里僅僅講了 DataBinding 的基本使用撵枢,并沒有打算深入講述 DataBinding 的進一步使用民晒,如果有同學(xué)想要了解高級使用的話,如類方法锄禽,類型別名等潜必,可以查看官方文檔 Data Binding Library
數(shù)據(jù)和事件關(guān)聯(lián)原理
知道了 DataBinding
如何使用之后,就很好奇沃但,這個DataBinding 機制是如何實現(xiàn)的了磁滚。但是在 Android Studio
里面怎么也沒有找到 ActivityMainBinding
的代碼,在 ObservableUser
的 getLastName
方法中設(shè)置斷點,查看調(diào)用棧垂攘。我們可以發(fā)現(xiàn)维雇,其實是 ActivityMainBinding.executeBindings
方法調(diào)用了 model 的 get 方法。
然而晒他,悲劇的是吱型,我想點擊查看 executeBindings
調(diào)用情況,Android Studio
是跳到了 activity_main.xml
里去了仪芒。
好吧唁影,還是想看源碼,那就用 dex2jar
和 jd-gui
工具查看了 class.dex 文件掂名,果然看到了源碼
當(dāng)然,其他 Android Studio
上沒能看到的代碼哟沫,如 DataBinderMapper
等的代碼也都能找到了饺蔑。
直接查看 DataBindingUtil.setContentView
里面的源碼吧:
-
MainActivity.onCreate
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
-
省略部分代碼,直接來到 DataBindingUtil.bind
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root, int layoutId) { return (T) sMapper.getDataBinder(bindingComponent, root, layoutId); }
其中嗜诀,
bindingComponent
為null
猾警;root
和layoutId
都對應(yīng)activity_main.xml
中定義的LinearLayout
-
DataBinderMapper.getDataBinder
public ViewDataBinding getDataBinder(DataBindingComponent paramDataBindingComponent, View paramView, int paramInt) { switch (paramInt) { default: return null; case 2130968601: } return ActivityMainBinding.bind(paramView, paramDataBindingComponent); }
-
ActivityMainBinding.bind
public static ActivityMainBinding bind(View paramView, DataBindingComponent paramDataBindingComponent) { if (!"layout/activity_main_0".equals(paramView.getTag())) throw new RuntimeException("view tag isn't correct on view:" + paramView.getTag()); return new ActivityMainBinding(paramDataBindingComponent, paramView); }
這里可以看到,返回的
ActivityMainBinding
對象是在這里被創(chuàng)建的了 -
ActivityMainBinding 構(gòu)造函數(shù)
public ActivityMainBinding(DataBindingComponent paramDataBindingComponent, View paramView) { super(paramDataBindingComponent, paramView, 1); paramDataBindingComponent = mapBindings(paramDataBindingComponent, paramView, 2, sIncludes, sViewsWithIds); this.mboundView0 = ((LinearLayout)paramDataBindingComponent[0]); this.mboundView0.setTag(null); this.mboundView1 = ((Button)paramDataBindingComponent[1]); this.mboundView1.setTag(null); setRootTag(paramView); invalidateAll(); }
這里還有好多疑問隆敢,mboundView0 和 mboundView1 分別對應(yīng)什么控件呢发皿?setRootTag 和 invalidateAll 都是干啥的呢?
-
ViewDataBinding.mapBindings
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) { ...... if (view instanceof ViewGroup) { ...... for (int i = 0; i < count; i++) { ...... if (bindings[index] == null) { bindings[index] = view; } ...... { bindings[index] = DataBindingUtil.bind(bindingComponent, included, layoutId); } ...... if (!isInclude) { mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false); } } } }
注:這里會遞歸的執(zhí)行
mapBindings
將傳入的bindings
數(shù)據(jù)給填充好拂蝎。
binding
數(shù)組里面的數(shù)據(jù)穴墅,可能是 view 也可能是 ViewDataBinding
在當(dāng)期的示例程序中,bindings[0]
是LinearLayout
温自,bindings[1]
是Button
玄货;所以,ActivityMainBinding.mboundView0
就是 layout 中定義的 LinearLayout悼泌;ActivityMainBinding.mboundView1
就是 layout 中定義的 Button松捉。 -
ViewDataBinding.setRootTag
protected void setRootTag(View view) { ...... view.setTag(R.id.dataBinding, this); ...... }
將
ActivityMainBinding
和布局文件中的LinearLayout
關(guān)聯(lián)起來了。 -
ActivityMainBinding 構(gòu)造函數(shù)
public ActivityMainBinding(DataBindingComponent paramDataBindingComponent, View paramView) { ...... invalidateAll(); }
-
跳過部分代碼馆里,調(diào)用到 ActivityMainBinding.requestRebind
protected void requestRebind() { ...... if (SDK_INT >= 16) { mChoreographer.postFrameCallback(mFrameCallback); } else { mUIThreadHandler.post(mRebindRunnable); } }
假設(shè)當(dāng)前 SDK_INT == 23隘世,直接查看
mFrameCallback
的定義。則在下一幀的時候鸠踪,調(diào)用mRebindRunnable.run();
mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { mRebindRunnable.run(); } };
-
最終執(zhí)行
ActivityMainBinding.executeBindings
方法protected void executeBindings() { ...... Handler localHandler = this.mHandler; ObservableUser localObservableUser = this.mUser; ...... while (true) { ...... localObject1 = new OnClickListenerImpl(); this.mAndroidViewViewOnCl = ((OnClickListenerImpl)localObject1); localObject1 = ((OnClickListenerImpl)localObject1).setValue(localHandler); localObject3 = localObject4; ...... localObject3 = localObservableUser.getLastName(); TextViewBindingAdapter.setText(this.mboundView1, (CharSequence)localObject3); this.mboundView1.setOnClickListener((View.OnClickListener)localObject1); return; ...... } }
注:這里
mUser
和mHandler
是 MainActivity.onCreate 中的設(shè)置的:binding.setUser(user); binding.setHandler(handler);
這里清楚的看到調(diào)用
localObservableUser.getLastName
獲取 model 中的數(shù)據(jù)丙者,然后設(shè)置給mboundView1
(Button)新建
OnClickListenerImpl
對象,處理mboundView1
(Button)的點擊事件慢哈,而最終也還是會調(diào)用到Handler.onClickButton
方法上public static class OnClickListenerImpl implements View.OnClickListener { private Handler value; public void onClick(View paramView) { this.value.onClickButton(paramView); } ...... }
數(shù)據(jù)變化驅(qū)動視圖改變
查看下代碼蔓钟,set 函數(shù)中,需要添加一句 notifyPropertyChanged
方法卵贱。其實這里對 lastName
的監(jiān)聽者滥沫,就是 ViewDataBinding$WeakPropertyListener
侣集,而內(nèi)部調(diào)用的還是 AcitivityMainBinding.handleFieldChange
方法,最終還是調(diào)用了 AcitivityMainBinding.requestRebind
兰绣。這里就已經(jīng)和前面分析的過程一樣世分,也就是說最終視圖發(fā)生改變生效,走的還是消息隊列缀辩。
public class ObservableUser extends BaseObservable {
......
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(com.netease.mvvmsample.BR.lastName);
}
}
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();
......
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
}
小結(jié)
由上面的源碼解析臭埋,已經(jīng)知道幾點
數(shù)據(jù)如何和 view 關(guān)聯(lián)起來的
事件處理如何和 view 關(guān)聯(lián)起來的
-
數(shù)據(jù)和事件處理的關(guān)聯(lián)發(fā)生是扔給消息隊列處理的
- SDK_INT >= 16: mChoreographer.postFrameCallback(mFrameCallback);
- SDK_INT < 16: mUIThreadHandler.post(mRebindRunnable);
當(dāng)數(shù)據(jù)改變,通知視圖改變時臀玄,走的是消息隊列瓢阴。因此一次數(shù)據(jù)改動,并界面可能不會立馬生效
數(shù)據(jù)和視圖的綁定健无,其實是單向的荣恐,即數(shù)據(jù)發(fā)生改變通知了視圖,而視圖發(fā)生并不能自動通知數(shù)據(jù)
雖然沒看到 Android Studio 是如何實現(xiàn)代碼生成累贤,但相關(guān)的工具大家可以看下 javapoet
總結(jié)
有了 DataBinding叠穆,后面就有人玩出了 MVVM 模式了。當(dāng)然啦臼膏,這里主要是抱著學(xué)習(xí)的態(tài)度在闡述 Android 里面的 DataBinding
硼被,并不是在推崇 DataBinding
或 MVVM
。這些概念有人推崇有人貶低渗磅,引用別人的一句話嚷硫,希望大家對新知識都能做到:
我們需要保持的是一個擁抱變化的心,以及理性分析的態(tài)度夺溢。
在新技術(shù)的面前论巍,不盲從,也不守舊风响,一切的決策都應(yīng)該建立在認(rèn)真分析的基礎(chǔ)上嘉汰,這樣才能應(yīng)對技術(shù)的變化