[关闭]
@zyl06 2017-02-11T16:27:08.000000Z 字数 11867 阅读 1821

Android 从观察者模式到 DataBinding

Android


前言

做过 iOS 的同学应该都了解过 KVO,是观察者模式在 Objective-C 中的应用。使用 KVO,能很方便的实现对对象属性的监听。虽然 iOS 提供了对对象属性的观察者模式机制,但想想很多 Android 同学们应该不会在意。这不是很容易么,我分分钟也能写一个:

  1. public class User {
  2. String mName;
  3. Observable mObservable;
  4. public User(String name) {
  5. mName = name;
  6. }
  7. public String getName() {
  8. return mName;
  9. }
  10. public void setName(String name) {
  11. boolean isSame = TextUtils.equals(this.mName, name);
  12. this.mName = name;
  13. if (!isSame && mObservable != null) {
  14. mObservable.onNameChanged(name);
  15. }
  16. }
  17. public void setObservable(Observable observer) {
  18. this.mObservable = observer;
  19. }
  20. public interface Observable {
  21. void onNameChanged(String newName);
  22. }
  23. }
  1. User user = new User("我叫王尼玛");
  2. user.setObservable(new User.Observable() {
  3. @Override
  4. public void onNameChanged(String newName) {
  5. Log.i("user newName = ", newName);
  6. }
  7. });
  8. user.setName("呵呵,这你都信");

但是冷静下来想想,如果一个大的工程中有很多这种需求呢?是不是 User1User2,...... 都要写这些机械的代码了?那回过头想想,如果不想自己写这些代码的话,那么我们大 Android 真的就没有这种机制么?想想不服气,于是翻了翻资料,果然我们还是有的: ObservableField

ObservableField

1. 使用方式

使用还是很简单的,我们直接看代码吧

  1. ObservableField<String> name = new ObservableField<>();
  2. name.addOnPropertyChangedCallback(
  3. new android.databinding.Observable.OnPropertyChangedCallback() {
  4. @Override
  5. public void onPropertyChanged(android.databinding.Observable observable, int i) {
  6. Log.d("name = ", "name = " + observable.toString() + "; i= " + i);
  7. }
  8. });
  9. name.set("我叫张三");

这下舒服多了,不用自己实现 Observable 接口和 setObservable 方法,同时对于其他类型的变量,如 int,float 或者 自定义的类型,也不用重新实现了,直接定义 ObservableField<Integer>ObservableField<Float> 就行了,好开心^_^

2. 原理实现

还是直接看源码吧,反正代码量也不多 O(∩_∩)O~

  1. public class ObservableField<T> extends BaseObservable implements Serializable {
  2. static final long serialVersionUID = 1L;
  3. private T mValue;
  4. ......
  5. public T get() {
  6. return mValue;
  7. }
  8. public void set(T value) {
  9. if (value != mValue) {
  10. mValue = value;
  11. notifyChange();
  12. }
  13. }
  14. }
  1. public class BaseObservable implements Observable {
  2. private transient PropertyChangeRegistry mCallbacks;
  3. ......
  4. @Override
  5. public synchronized void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
  6. ......
  7. mCallbacks.add(callback);
  8. }
  9. @Override
  10. public synchronized void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
  11. if (mCallbacks != null) {
  12. mCallbacks.remove(callback);
  13. }
  14. }
  15. public synchronized void notifyChange() {
  16. if (mCallbacks != null) {
  17. mCallbacks.notifyCallbacks(this, 0, null);
  18. }
  19. }
  20. ......
  21. }

注:mCallbacks.notifyCallbacks(this, 0, null); 方法中,0 表示的是 fieldID,在 dataBinding 中表示数据资源 id。因此这里并没有关联视图资源,所以这里设置为 0

可以看到,ObservableField 是一个泛型,所以支持多种类型的观察者模式。BaseObservable 是其父类,实现了观察者模式的核心代码,可以看到 addOnPropertyChangedCallbackaddOnPropertyChangedCallback 2个添加和移除监听的方法,真正的监听者都被保存到 PropertyChangeRegistry.mCallbacks (类型是 List) 里面。

当调用 ObservableFieldset 方法时,会执行 ObservableField.notifiyCallbacks 方法,如下:

  1. public class BaseObservable implements Observable {
  2. public synchronized void notifyChange() {
  3. if (mCallbacks != null) {
  4. mCallbacks.notifyCallbacks(this, 0, null);
  5. }
  6. }
  7. ......
  8. }

最终会执行到 callback.onPropertyChanged(sender, arg);,如下代码所示:

  1. public class CallbackRegistry<C, T, A> implements Cloneable {
  2. private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
  3. final int endIndex, final long bits) {
  4. ......
  5. for (int i = startIndex; i < endIndex; i++) {
  6. ......
  7. mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
  8. .....
  9. }
  10. }
  11. ......
  12. }
  1. private static final CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback,
  2. Observable, Void> NOTIFIER_CALLBACK =
  3. new CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void>() {
  4. @Override
  5. public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,
  6. int arg, Void notUsed) {
  7. callback.onPropertyChanged(sender, arg);
  8. }
  9. };

这里,我们可以看到,通过遍历的方式,去执行 callback 方法,将前面通过 BaseObservable.addOnPropertyChangedCallback 添加的全部观察者都响应了一边

3. 小结

到这里为止,虽然把 ObservableField 的观察者模式给讲清楚了,但还是感觉有些失望,内容很少很简单。不过还没完呢,Google 大神们就是基于此,玩出了 DataBinding

DataBinding

使用步骤

  1. IDE 配置

    • Android SDK API 版本 7 以上

    • 使用 Gradle 1.5.0-alpha1 及以上

    • 使用 Android Studio 1.3 及以上

  2. 配置开启 dataBinding

    在主工程的 build.gradle 中,添加代码:

    1. android {
    2. ......
    3. dataBinding {
    4. enabled = true
    5. }
    6. }
  3. 定义数据层 Model

    定义的 ObservableUser 类,继承自 BaseObservable (同前面的 ObservableField)。在 set 接口里添加 notifyPropertyChanged 调用,通知视图更新

    1. public class ObservableUser extends BaseObservable {
    2. private String firstName;
    3. private String lastName;
    4. public ObservableUser(String firstName, String lastName) {
    5. this.firstName = firstName;
    6. this.lastName = lastName;
    7. }
    8. @Bindable
    9. public String getFirstName() {
    10. return firstName;
    11. }
    12. @Bindable
    13. public String getLastName() {
    14. return lastName;
    15. }
    16. public void setFirstName(String firstName) {
    17. this.firstName = firstName;
    18. notifyPropertyChanged(com.netease.mvvmsample.BR.firstName);
    19. }
    20. public void setLastName(String lastName) {
    21. this.lastName = lastName;
    22. notifyPropertyChanged(com.netease.mvvmsample.BR.lastName);
    23. }
    24. }
  4. 定义事件响应 Handler

    1. public class Handler {
    2. private ObservableUser mObservableUser;
    3. public Handler(ObservableUser user) {
    4. mObservableUser = user;
    5. }
    6. public void onClickButton(View view) {
    7. mObservableUser.setLastName("呵呵呵,我变了 " + mCount++ + " 次");
    8. }
    9. }
  5. 定义布局代码

    在 data 标签下面,定义 model 数据和事件响应 handler。使用 @{} 分别将 Button 的文本信息和 user.lastNameButton 的点击响应和 handler.onClickButton 绑定在一起

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <layout
    3. xmlns:android="http://schemas.android.com/apk/res/android"
    4. xmlns:tools="http://schemas.android.com/tools">
    5. <data>
    6. <variable
    7. name="user"
    8. type="com.netease.mvvmsample.ObservableUser">
    9. </variable>
    10. <variable
    11. name="handler"
    12. type="com.netease.mvvmsample.Handler">
    13. </variable>
    14. </data>
    15. <LinearLayout
    16. <Button
    17. android:layout_width="wrap_content"
    18. android:layout_height="wrap_content"
    19. android:text="@{user.lastName}"
    20. android:onClick="@{handler.onClickButton}"/>
    21. </LinearLayout
    22. </layout>
  6. 设置布局和绑定数据和事件

    在定义了上面的布局 xml 文件之后,Android Studio 会自动生成 ViewModel 类。假设文件名是 activity_main.xml,那么程序编译后,会生成 ActivityMainBinding 类。代替原来的 setContentView(R.layout.activity_main); 方法,使用 ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); 来设置布局资源,并返回 binding 对象。新建数据和事件处理对象,并设置给 binding 对象

    1. public class MainActivity extends AppCompatActivity {
    2. @Override
    3. protected void onCreate(Bundle savedInstanceState) {
    4. super.onCreate(savedInstanceState);
    5. ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    6. ObservableUser user = new ObservableUser("Zhang", "San");
    7. Handler handler = new Handler(user);
    8. binding.setUser(user);
    9. binding.setHandler(handler);
    10. }
    11. }
  7. 效果展示

    image

注:这里仅仅讲了 DataBinding 的基本使用,并没有打算深入讲述 DataBinding 的进一步使用,如果有同学想要了解高级使用的话,如类方法,类型别名等,可以查看官方文档 Data Binding Library

数据和事件关联原理

知道了 DataBinding 如何使用之后,就很好奇,这个DataBinding 机制是如何实现的了。但是在 Android Studio 里面怎么也没有找到 ActivityMainBinding 的代码,在 ObservableUsergetLastName 方法中设置断点,查看调用栈。我们可以发现,其实是 ActivityMainBinding.executeBindings 方法调用了 model 的 get 方法。

image

然而,悲剧的是,我想点击查看 executeBindings 调用情况,Android Studio 是跳到了 activity_main.xml 里去了。

image

好吧,还是想看源码,那就用 dex2jarjd-gui 工具查看了 class.dex 文件,果然看到了源码

image

当然,其他 Android Studio 上没能看到的代码,如 DataBinderMapper 等的代码也都能找到了。

直接查看 DataBindingUtil.setContentView 里面的源码吧:

  1. MainActivity.onCreate

    1. ActivityMainBinding binding =
    2. DataBindingUtil.setContentView(this, R.layout.activity_main);
  2. 省略部分代码,直接来到 DataBindingUtil.bind

    1. static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent,
    2. View root, int layoutId) {
    3. return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    4. }

    其中,bindingComponentnullrootlayoutId 都对应 activity_main.xml 中定义的 LinearLayout

  3. DataBinderMapper.getDataBinder

    1. public ViewDataBinding getDataBinder(DataBindingComponent paramDataBindingComponent,
    2. View paramView, int paramInt) {
    3. switch (paramInt) {
    4. default:
    5. return null;
    6. case 2130968601:
    7. }
    8. return ActivityMainBinding.bind(paramView, paramDataBindingComponent);
    9. }
  4. ActivityMainBinding.bind

    1. public static ActivityMainBinding bind(View paramView, DataBindingComponent paramDataBindingComponent) {
    2. if (!"layout/activity_main_0".equals(paramView.getTag()))
    3. throw new RuntimeException("view tag isn't correct on view:" + paramView.getTag());
    4. return new ActivityMainBinding(paramDataBindingComponent, paramView);
    5. }

    这里可以看到,返回的 ActivityMainBinding 对象是在这里被创建的了

  5. ActivityMainBinding 构造函数

    1. public ActivityMainBinding(DataBindingComponent paramDataBindingComponent, View paramView) {
    2. super(paramDataBindingComponent, paramView, 1);
    3. paramDataBindingComponent = mapBindings(paramDataBindingComponent, paramView, 2, sIncludes, sViewsWithIds);
    4. this.mboundView0 = ((LinearLayout)paramDataBindingComponent[0]);
    5. this.mboundView0.setTag(null);
    6. this.mboundView1 = ((Button)paramDataBindingComponent[1]);
    7. this.mboundView1.setTag(null);
    8. setRootTag(paramView);
    9. invalidateAll();
    10. }

    这里还有好多疑问,mboundView0 和 mboundView1 分别对应什么控件呢?setRootTag 和 invalidateAll 都是干啥的呢?

  6. ViewDataBinding.mapBindings

    1. protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
    2. int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
    3. Object[] bindings = new Object[numBindings];
    4. mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
    5. return bindings;
    6. }
    1. private static void mapBindings(DataBindingComponent bindingComponent, View view,
    2. Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
    3. boolean isRoot) {
    4. ......
    5. if (view instanceof ViewGroup) {
    6. ......
    7. for (int i = 0; i < count; i++) {
    8. ......
    9. if (bindings[index] == null) {
    10. bindings[index] = view;
    11. }
    12. ......
    13. {
    14. bindings[index] = DataBindingUtil.bind(bindingComponent, included,
    15. layoutId);
    16. }
    17. ......
    18. if (!isInclude) {
    19. mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
    20. }
    21. }
    22. }
    23. }

    注:这里会递归的执行 mapBindings 将传入的 bindings 数据给填充好。
    binding 数组里面的数据,可能是 view 也可能是 ViewDataBinding
    在当期的示例程序中,bindings[0]LinearLayoutbindings[1]Button;所以,ActivityMainBinding.mboundView0 就是 layout 中定义的 LinearLayout;ActivityMainBinding.mboundView1 就是 layout 中定义的 Button。

  7. ViewDataBinding.setRootTag

    1. protected void setRootTag(View view) {
    2. ......
    3. view.setTag(R.id.dataBinding, this);
    4. ......
    5. }

    ActivityMainBinding 和布局文件中的 LinearLayout 关联起来了。

  8. ActivityMainBinding 构造函数

    1. public ActivityMainBinding(DataBindingComponent paramDataBindingComponent, View paramView) {
    2. ......
    3. invalidateAll();
    4. }
  9. 跳过部分代码,调用到 ActivityMainBinding.requestRebind

    1. protected void requestRebind() {
    2. ......
    3. if (SDK_INT >= 16) {
    4. mChoreographer.postFrameCallback(mFrameCallback);
    5. } else {
    6. mUIThreadHandler.post(mRebindRunnable);
    7. }
    8. }

    假设当前 SDK_INT == 23,直接查看 mFrameCallback 的定义。则在下一帧的时候,调用 mRebindRunnable.run();

    1. mFrameCallback = new Choreographer.FrameCallback() {
    2. @Override
    3. public void doFrame(long frameTimeNanos) {
    4. mRebindRunnable.run();
    5. }
    6. };
  10. 最终执行 ActivityMainBinding.executeBindings 方法

    1. protected void executeBindings()
    2. {
    3. ......
    4. Handler localHandler = this.mHandler;
    5. ObservableUser localObservableUser = this.mUser;
    6. ......
    7. while (true)
    8. {
    9. ......
    10. localObject1 = new OnClickListenerImpl();
    11. this.mAndroidViewViewOnCl = ((OnClickListenerImpl)localObject1);
    12. localObject1 = ((OnClickListenerImpl)localObject1).setValue(localHandler);
    13. localObject3 = localObject4;
    14. ......
    15. localObject3 = localObservableUser.getLastName();
    16. TextViewBindingAdapter.setText(this.mboundView1, (CharSequence)localObject3);
    17. this.mboundView1.setOnClickListener((View.OnClickListener)localObject1);
    18. return;
    19. ......
    20. }
    21. }

    注:这里 mUsermHandler 是 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。这里就已经和前面分析的过程一样,也就是说最终视图发生改变生效,走的还是消息队列。

  1. public class ObservableUser extends BaseObservable {
  2. ......
  3. public void setLastName(String lastName) {
  4. this.lastName = lastName;
  5. notifyPropertyChanged(com.netease.mvvmsample.BR.lastName);
  6. }
  7. }
  1. private static class WeakPropertyListener
  2. extends Observable.OnPropertyChangedCallback
  3. implements ObservableReference<Observable> {
  4. final WeakListener<Observable> mListener;
  5. @Override
  6. public void onPropertyChanged(Observable sender, int propertyId) {
  7. ViewDataBinding binder = mListener.getBinder();
  8. ......
  9. binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
  10. }
  11. }

小结

由上面的源码解析,已经知道几点

  1. 数据如何和 view 关联起来的

  2. 事件处理如何和 view 关联起来的

  3. 数据和事件处理的关联发生是扔给消息队列处理的

    • SDK_INT >= 16: mChoreographer.postFrameCallback(mFrameCallback);
    • SDK_INT < 16: mUIThreadHandler.post(mRebindRunnable);
  4. 当数据改变,通知视图改变时,走的是消息队列。因此一次数据改动,并界面可能不会立马生效

  5. 数据和视图的绑定,其实是单向的,即数据发生改变通知了视图,而视图发生并不能自动通知数据

  6. 虽然没看到 Android Studio 是如何实现代码生成,但相关的工具大家可以看下 javapoet

总结

有了 DataBinding,后面就有人玩出了 MVVM 模式了。当然啦,这里主要是抱着学习的态度在阐述 Android 里面的 DataBinding,并不是在推崇 DataBindingMVVM。这些概念有人推崇有人贬低,引用别人的一句话,希望大家对新知识都能做到:

我们需要保持的是一个拥抱变化的心,以及理性分析的态度。
在新技术的面前,不盲从,也不守旧,一切的决策都应该建立在认真分析的基础上,这样才能应对技术的变化
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注