前言
做過 iOS 的同學(xué)應(yīng)該都了解過 KVO,是觀察者模式在 Objective-C 中的應(yīng)用。使用 KVO,能很方便的實(shí)現(xiàn)對(duì)對(duì)象屬性的監(jiān)聽。雖然 iOS 提供了對(duì)對(duì)象屬性的觀察者模式機(jī)制,但想想很多 Android 同學(xué)們應(yīng)該不會(huì)在意。這不是很容易么,我分分鐘也能寫一個(gè):
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("呵呵,這你都信");
但是冷靜下來想想,如果一個(gè)大的工程中有很多這種需求呢?是不是 User1, User2,...... 都要寫這些機(jī)械的代碼了?那回過頭想想,如果不想自己寫這些代碼的話,那么我們大 Android 真的就沒有這種機(jī)制么?想想不服氣,于是翻了翻資料,果然我們還是有的: ObservableField
ObservableField
1. 使用方式
使用還是很簡(jiǎn)單的,我們直接看代碼吧
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("我叫張三");
這下舒服多了,不用自己實(shí)現(xiàn) Observable 接口和 setObservable 方法,同時(shí)對(duì)于其他類型的變量,如 int,float 或者 自定義的類型,也不用重新實(shí)現(xiàn)了,直接定義 ObservableField<Integer>、ObservableField<Float> 就行了,好開心_
2. 原理實(shí)現(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 是一個(gè)泛型,所以支持多種類型的觀察者模式。BaseObservable 是其父類,實(shí)現(xiàn)了觀察者模式的核心代碼,可以看到 addOnPropertyChangedCallback 和 addOnPropertyChangedCallback 2個(gè)添加和移除監(jiān)聽的方法,真正的監(jiān)聽者都被保存到 PropertyChangeRegistry.mCallbacks (類型是 List) 里面。
當(dāng)調(diào)用 ObservableField 的 set 方法時(shí),會(huì)執(zhí)行 ObservableField.notifiyCallbacks 方法,如下:
public class BaseObservable implements Observable {
public synchronized void notifyChange() {
if (mCallbacks != null) {
mCallbacks.notifyCallbacks(this, 0, null);
}
}
......
}
最終會(huì)執(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)容很少很簡(jiǎn)單。不過還沒完呢,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的點(diǎn)擊響應(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 會(huì)自動(dòng)生成 ViewModel 類。假設(shè)文件名是
activity_main.xml,那么程序編譯后,會(huì)生成ActivityMainBinding類。代替原來的setContentView(R.layout.activity_main);方法,使用ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);來設(shè)置布局資源,并返回binding對(duì)象。新建數(shù)據(jù)和事件處理對(duì)象,并設(shè)置給binding對(duì)象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); } } -
效果展示
image
注:這里僅僅講了 DataBinding 的基本使用,并沒有打算深入講述 DataBinding 的進(jìn)一步使用,如果有同學(xué)想要了解高級(jí)使用的話,如類方法,類型別名等,可以查看官方文檔 Data Binding Library
數(shù)據(jù)和事件關(guān)聯(lián)原理
知道了 DataBinding 如何使用之后,就很好奇,這個(gè)DataBinding 機(jī)制是如何實(shí)現(xiàn)的了。但是在 Android Studio 里面怎么也沒有找到 ActivityMainBinding 的代碼,在 ObservableUser 的 getLastName 方法中設(shè)置斷點(diǎn),查看調(diào)用棧。我們可以發(fā)現(xiàn),其實(shí)是 ActivityMainBinding.executeBindings 方法調(diào)用了 model 的 get 方法。

然而,悲劇的是,我想點(diǎn)擊查看 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都對(duì)應(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對(duì)象是在這里被創(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 分別對(duì)應(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); } } } }注:這里會(huì)遞歸的執(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的定義。則在下一幀的時(shí)候,調(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對(duì)象,處理mboundView1(Button)的點(diǎn)擊事件,而最終也還是會(huì)調(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ū)動(dòng)視圖改變
查看下代碼,set 函數(shù)中,需要添加一句 notifyPropertyChanged 方法。其實(shí)這里對(duì) lastName 的監(jiān)聽者,就是 ViewDataBinding$WeakPropertyListener,而內(nèi)部調(diào)用的還是 AcitivityMainBinding.handleFieldChange 方法,最終還是調(diào)用了 AcitivityMainBinding.requestRebind。這里就已經(jīng)和前面分析的過程一樣,也就是說最終視圖發(fā)生改變生效,走的還是消息隊(duì)列。
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)知道幾點(diǎn)
數(shù)據(jù)如何和 view 關(guān)聯(lián)起來的
事件處理如何和 view 關(guān)聯(lián)起來的
-
數(shù)據(jù)和事件處理的關(guān)聯(lián)發(fā)生是扔給消息隊(duì)列處理的
- SDK_INT >= 16: mChoreographer.postFrameCallback(mFrameCallback);
- SDK_INT < 16: mUIThreadHandler.post(mRebindRunnable);
當(dāng)數(shù)據(jù)改變,通知視圖改變時(shí),走的是消息隊(duì)列。因此一次數(shù)據(jù)改動(dòng),并界面可能不會(huì)立馬生效
數(shù)據(jù)和視圖的綁定,其實(shí)是單向的,即數(shù)據(jù)發(fā)生改變通知了視圖,而視圖發(fā)生并不能自動(dòng)通知數(shù)據(jù)
雖然沒看到 Android Studio 是如何實(shí)現(xiàn)代碼生成,但相關(guān)的工具大家可以看下 javapoet
總結(jié)
有了 DataBinding,后面就有人玩出了 MVVM 模式了。當(dāng)然啦,這里主要是抱著學(xué)習(xí)的態(tài)度在闡述 Android 里面的 DataBinding,并不是在推崇 DataBinding 或 MVVM。這些概念有人推崇有人貶低,引用別人的一句話,希望大家對(duì)新知識(shí)都能做到:
我們需要保持的是一個(gè)擁抱變化的心,以及理性分析的態(tài)度。
在新技術(shù)的面前,不盲從,也不守舊,一切的決策都應(yīng)該建立在認(rèn)真分析的基礎(chǔ)上,這樣才能應(yīng)對(duì)技術(shù)的變化
