前言
做过 iOS
的同学应该都了解过 KVO
,是观察者模式在 Objective-C 中的应用。使用 KVO
,能很方便的实现对对象属性的监听。虽然 iOS
提供了对对象属性的观察者模式机制,但想想很多 Android
同学们应该不会在意。这不是很容易么,我分分钟也能写一个:
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 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("我叫张三");
这下舒服多了,不用自己实现 Observable
接口和 setObservable
方法,同时对于其他类型的变量,如 int,float 或者 自定义的类型,也不用重新实现了,直接定义 ObservableField
、ObservableField
就行了,好开心_
2. 原理实现
还是直接看源码吧,反正代码量也不多 O(∩_∩)O~
public class ObservableField 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 中表示数据资源 id。因此这里并没有关联视图资源,所以这里设置为 0
可以看到,ObservableField
是一个泛型,所以支持多种类型的观察者模式。BaseObservable
是其父类,实现了观察者模式的核心代码,可以看到 addOnPropertyChangedCallback
和 addOnPropertyChangedCallback
2个添加和移除监听的方法,真正的监听者都被保存到 PropertyChangeRegistry.mCallbacks
(类型是 List
) 里面。
当调用 ObservableField
的 set
方法时,会执行 ObservableField.notifiyCallbacks
方法,如下:
public class BaseObservable implements Observable {
public synchronized void notifyChange() {
if (mCallbacks != null) {
mCallbacks.notifyCallbacks(this, 0, null);
}
}
......
}
最终会执行到 callback.onPropertyChanged(sender, arg);
,如下代码所示:
public class CallbackRegistry 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 NOTIFIER_CALLBACK =
new CallbackRegistry.NotifierCallback() {
@Override
public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,
int arg, Void notUsed) {
callback.onPropertyChanged(sender, arg);
}
};
这里,我们可以看到,通过遍历的方式,去执行 callback
方法,将前面通过 BaseObservable.addOnPropertyChangedCallback
添加的全部观察者都响应了一边
3. 小结
到这里为止,虽然把 ObservableField
的观察者模式给讲清楚了,但还是感觉有些失望,内容很少很简单。不过还没完呢,Google 大神们就是基于此,玩出了 DataBinding
DataBinding
使用步骤
-
IDE 配置
Android SDK API 版本 7 以上
使用
Gradle 1.5.0-alpha1
及以上使用
Android Studio 1.3
及以上
-
配置开启 dataBinding
在主工程的
build.gradle
中,添加代码:android { ...... dataBinding { enabled = true } }
-
定义数据层 Model
定义的
ObservableUser
类,继承自BaseObservable
(同前面的ObservableField
)。在set
接口里添加notifyPropertyChanged
调用,通知视图更新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); } }
-
定义事件响应 Handler
public class Handler { private ObservableUser mObservableUser; public Handler(ObservableUser user) { mObservableUser = user; } public void onClickButton(View view) { mObservableUser.setLastName("呵呵呵,我变了 " + mCount++ + " 次"); } }
-
定义布局代码
在 data 标签下面,定义 model 数据和事件响应 handler。使用
@{}
分别将Button
的文本信息和user.lastName
,Button
的点击响应和handler.onClickButton
绑定在一起 -
设置布局和绑定数据和事件
在定义了上面的布局 xml 文件之后,Android Studio 会自动生成 ViewModel 类。假设文件名是
activity_main.xml
,那么程序编译后,会生成ActivityMainBinding
类。代替原来的setContentView(R.layout.activity_main);
方法,使用ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
来设置布局资源,并返回binding
对象。新建数据和事件处理对象,并设置给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 的进一步使用,如果有同学想要了解高级使用的话,如类方法,类型别名等,可以查看官方文档 Data Binding Library
数据和事件关联原理
知道了 DataBinding
如何使用之后,就很好奇,这个DataBinding 机制是如何实现的了。但是在 Android Studio
里面怎么也没有找到 ActivityMainBinding
的代码,在 ObservableUser
的 getLastName
方法中设置断点,查看调用栈。我们可以发现,其实是 ActivityMainBinding.executeBindings
方法调用了 model 的 get 方法。
然而,悲剧的是,我想点击查看 executeBindings
调用情况,Android Studio
是跳到了 activity_main.xml
里去了。
好吧,还是想看源码,那就用 dex2jar
和 jd-gui
工具查看了 class.dex 文件,果然看到了源码
当然,其他 Android Studio
上没能看到的代码,如 DataBinderMapper
等的代码也都能找到了。
直接查看 DataBindingUtil.setContentView
里面的源码吧:
-
MainActivity.onCreate
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
-
省略部分代码,直接来到 DataBindingUtil.bind
static
T bind(DataBindingComponent bindingComponent, View root, int layoutId) { return (T) sMapper.getDataBinder(bindingComponent, root, layoutId); } 其中,
bindingComponent
为null
;root
和layoutId
都对应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
对象是在这里被创建的了 -
ActivityMainBinding 构造函数
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 分别对应什么控件呢?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); } } } }
注:这里会递归的执行
mapBindings
将传入的bindings
数据给填充好。
binding
数组里面的数据,可能是 view 也可能是 ViewDataBinding
在当期的示例程序中,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
关联起来了。 -
ActivityMainBinding 构造函数
public ActivityMainBinding(DataBindingComponent paramDataBindingComponent, View paramView) { ...... invalidateAll(); }
-
跳过部分代码,调用到 ActivityMainBinding.requestRebind
protected void requestRebind() { ...... if (SDK_INT >= 16) { mChoreographer.postFrameCallback(mFrameCallback); } else { mUIThreadHandler.post(mRebindRunnable); } }
假设当前 SDK_INT == 23,直接查看
mFrameCallback
的定义。则在下一帧的时候,调用mRebindRunnable.run();
mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { mRebindRunnable.run(); } };
-
最终执行
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 中的设置的:binding.setUser(user); binding.setHandler(handler);
这里清楚的看到调用
localObservableUser.getLastName
获取 model 中的数据,然后设置给mboundView1
(Button)新建
OnClickListenerImpl
对象,处理mboundView1
(Button)的点击事件,而最终也还是会调用到Handler.onClickButton
方法上public static class OnClickListenerImpl implements View.OnClickListener { private Handler value; public void onClick(View paramView) { this.value.onClickButton(paramView); } ...... }
数据变化驱动视图改变
查看下代码,set 函数中,需要添加一句 notifyPropertyChanged
方法。其实这里对 lastName
的监听者,就是 ViewDataBinding$WeakPropertyListener
,而内部调用的还是 AcitivityMainBinding.handleFieldChange
方法,最终还是调用了 AcitivityMainBinding.requestRebind
。这里就已经和前面分析的过程一样,也就是说最终视图发生改变生效,走的还是消息队列。
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 {
final WeakListener mListener;
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
ViewDataBinding binder = mListener.getBinder();
......
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
}
小结
由上面的源码解析,已经知道几点
数据如何和 view 关联起来的
事件处理如何和 view 关联起来的
-
数据和事件处理的关联发生是扔给消息队列处理的
- SDK_INT >= 16: mChoreographer.postFrameCallback(mFrameCallback);
- SDK_INT < 16: mUIThreadHandler.post(mRebindRunnable);
当数据改变,通知视图改变时,走的是消息队列。因此一次数据改动,并界面可能不会立马生效
数据和视图的绑定,其实是单向的,即数据发生改变通知了视图,而视图发生并不能自动通知数据
虽然没看到 Android Studio 是如何实现代码生成,但相关的工具大家可以看下 javapoet
总结
有了 DataBinding,后面就有人玩出了 MVVM 模式了。当然啦,这里主要是抱着学习的态度在阐述 Android 里面的 DataBinding
,并不是在推崇 DataBinding
或 MVVM
。这些概念有人推崇有人贬低,引用别人的一句话,希望大家对新知识都能做到:
我们需要保持的是一个拥抱变化的心,以及理性分析的态度。
在新技术的面前,不盲从,也不守旧,一切的决策都应该建立在认真分析的基础上,这样才能应对技术的变化