Google MVVM 示例代码分析

项目的代码位于 github

项目中采用了MVVM方式进行开发,与之前的MVP项目模式相比,直接去掉了MVP模式中的Presenter,数据加载的操作直接用ViewModel中开始. 在获取到了数据之后,通过databiding 来完成数据的展示。

在to-do中,有几个部分,下面是主界面的分析内容。

在TasksViewModel 这个类当中,封装了数据所有的操作,包括数据的加载,判空(isEmpty),清除, 添加过滤条件。

同时应该注意到TasksViewModel 继承了 BaseObservable 这个对象,BaseObservable 最终实现了Observable 接口, 这个接口提供了让数据绑定的UI接受数据变化的通知。
同时 BaseObservable 还有其他的类型的子类,例如 ObservableField 等。

在 TasksViewModel 我们也可以找到 BaseObservable 类型的属性,通过这些属性,可以监听到具体属性值 的变化。

除了属性值之外,还可以将方法转可以监听的对象,做法就是给方法加上 @Bindable 注解。
下面是这个注解的说明

The Bindable annotation should be applied to any getter accessor method of an
{@link Observable} class. Bindable will generate a field in the BR class to identify
the field that has changed.

它用来修饰任意一个get method,被这个注解修饰的方法会在 BR.java这个类中生成对应的属性,例如如果定义了 String getName()方法,那么就会在BR中添加一个name的常量字段。当数据发生变化我们需要通过getName来获取最新的数据的时候,调用 BaseObserverable 的 notifyPropertyChanged(BR.name),之后getName将再次被调用。

同时也可以看到这个被 @Bindable 注解修饰的方法,可以在 布局文件中直接通过 xxx.name 的方式来访问。

上面说到数据加载操作也是在TasksViewModel中完成的,在loadTasks方法中 调用了 mTasksRepository 的 getTasks, 然后在callback中将数据 添加到了 items当中去,
item 是一个 ObservableList

public final ObservableList<Task> items = new ObservableArrayList<>();

当调用了 items.addAll(tasksToShow); 方法之后,数据就被绑定到界面上去了。

下面跟踪一下,数据绑定是如何实现的。

在TasksViewModel当中,有一个 ObservableBoolean类型的成员变量dataLoading.在这个类中提供了一个set方法,在改变了value的值之后,它调用了notifyChange();

notifyChange 是一个定义在其父类 BaseObservable 中的一个同步的方法, 这个方法当中简单调用了PropertyChangeRegistry 这个类的 notifyCallbacks 方法 ,进入到notifyCallbacks 方法,实际上这个方法又是定义在其父类CallbackRegistry之中,通过CallbackRegistry的说明可知,这个类是用来存放callback和进行各种回调的,但是CallbackRegistry对于回调的处理实际上只是对回调的集合进行了管理,它通过递归的方式来通知集合中所有的回调,这里之所以采用递归的方式,文档之中给出的说明是避免在堆上进行临时状态的分配(但是采用递归需要每次都在栈上分配内存,并且堆上同样会有内存的分配,这里还不清楚),既然CallbackRegistry只是集合回调的管理,那么真正的回调应该如何处理实际上 是通过其内部类 NotifierCallback 来完成。

/**
 * Class used to notify events from CallbackRegistry.
 *
 * @param <C> The callback type.
 * @param <T> The notification sender type. Typically this is the containing class.
 * @param <A> An opaque argument to pass to the notifier
 */
public abstract static class NotifierCallback<C, T, A> {
    /**
     * Called by CallbackRegistry during
     * {@link CallbackRegistry#notifyCallbacks(Object, int, Object)}} to notify the callback.
     *
     * @param callback The callback to notify.
     * @param sender The opaque sender object.
     * @param arg The opaque notification parameter.
     * @param arg2 An opaque argument passed in
     *        {@link CallbackRegistry#notifyCallbacks}
     * @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback)
     */
    public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2);
}

通过实际上在CallbackRegistry 处理回调的时候就是调用了其 onNotifyCallback来处理。

所以在CallbackRegistry 的说明中可以看到 一句话 :A subclass of CallbackRegistry.NotifierCallback must be passed to the constructor to define how notifications should be called

简单说就是由 NotifierCallback的子类来处理notification。

回到 PropertyChangeRegistry 当中看 NOTIFIER_CALLBACK

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);
    }
};

在接收到通知之后,实际上会调用Observable.OnPropertyChangedCallback的 onPropertyChanged 方法。

从上面的分析可以看来,CallbackRegistry 实际上是一个事件分发中心和订阅中,所有的事件回调都是通过CallbackRegistry来处理,但是真正对事件的处理,却是通过自定义的 NotifierCallback来完成的。

在android的databinding中,同样是通过这样一套机制来工作的,在ViewDataBinding中,定义了WeakPropertyListener,通过重写 onPropertyChanged 方法完成对数据的重新绑定。

回到 TasksFragBinding.java 这个类是tasks_frag_xml这个文件生成的。
在其中定义了一个变量, mViewmodel ,因此在这个类中,也为我们生成了一个setViewmodel 方法,在这个方法当中,完成了对数据的mViewmodel 的赋值,然后调用了notifyPropertyChanged 方法,这个方法在前面提到过,定义BaseObservable 当中,所以可以简单的推断 ViewDataBinding 也是继承于BaseObservable。

通过mViewmodel 同样可以推断出,如果我们只是修改了 mViewmodel 的内部属性是无法导致试图被刷新的(已经验证),所以在TaskViewModel 这个类中,我们可以看到,在定义了 private final ObservableField mTaskObservable = new ObservableField<>();之后有这么一段代码

mTaskObservable.addOnPropertyChangedCallback(new OnPropertyChangedCallback() {
        @Override
        public void onPropertyChanged(Observable observable, int i) {
            Task task = mTaskObservable.get();
            if (task != null) {
                title.set(task.getTitle());
                description.set(task.getDescription());
            } else {
                title.set(mContext.getString(R.string.no_data));
                description.set(mContext.getString(R.string.no_data_description));
            }
        }
    });

需要自己手动去添加一个监听器来数据的更新。

参照这个例子,如果需要一个更新了成员变量也会刷新对应试图的对象,应该这么定义

public class Task {
    public final ObservableField<String> title = new ObservableField();
    public final ObservableField<String> content = new ObservableField();

    public Task(String title, String content) {
        this.title.set(title);
        this.content.set(content);
    }

    public void setContent(String content) {
        this.content.set(content);
    }
}

对应的布局文件如下

<?xml version="1.0" encoding="utf-8"?>
<layout  xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="task"
            type="com.devtom.databindingtest.Task"/>
    </data>

    <RelativeLayout
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.devtom.databindingtest.MainActivity">

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:text="@{task.title}" />

        <TextView
            android:id="@+id/content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="@{task.content}" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Change"
            android:onClick="change"
            android:layout_alignParentBottom="true"
            />
    </RelativeLayout>
</layout>

Activity 代码如下

public class MainActivity extends AppCompatActivity {
        private Task task;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            View view = this.findViewById(R.id.activity_main);
            ActivityMainBinding mainBinding = DataBindingUtil.bind(view);
            task = new Task("this is title","kai gong da ji");
            mainBinding.setTask(task);
        }

        public void change(View view) {
            task.setContent("this is chinese new year");
        }
}

当手动的去改变task的某一个属性的时候,也会去刷新对应的试图。

这里需要注意的是Task其中的两个成员变量可以定义为public的也可以定义为private 然后给他添加对应的private 方法。