Android 中的 MVVM
在 Android 应用程序中使用 Android Binding 实现 MVVM 模式。
Android 中的 MVVM
MVVM 代表 Model-View-ViewModel,这是一种在 Microsoft WPF 和 Silverlight 中广泛采用的模式。在本文中,我将借助 Android-Binding 框架,讨论 MVVM 模式在 Android (Java) 中的实现。
本文的源代码可以在 这里 获取。
为 Android 重新定义的 MVVM
Model:Android 中的 Model 可以是来自应用程序内部的数据(包括 Shared Preferences)、数据库(以 Cursor 形式,或通过其他 Data Access Object)或外部数据(通过 Cursor 到其他 Data Contract)。
View:GUI 中显示的所有元素,包括 android.widget.* 系列和自定义视图。
ViewModel:ViewModel 为 View 公开属性和命令,它还负责 View 和 Model 之间的数据绑定。在 Android 中,大部分这项工作是在 Activity 中完成的。
实现
我们以一个简单的计算器为例。如下图所示,计算器包含一些用于输入和操作的按钮,以及一个用于显示结果的文本框。
 
 
一旦 ViewModel 准备就绪,我们就需要将 ViewModel 与 View 进行绑定,这里我们使用 Android-Binding。Android-Binding 可以通过 XML 标记的绑定语法来帮助解耦 View 与 ViewModel。我们只需要包含该库,并在 XML 标记中添加一个额外的命名空间,以便 Android-Binding 能够识别。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:binding="http://www.gueei.com/android-binding/"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
    <TextView android:layout_width="fill_parent"
    android:layout_height="fill_parent" android:textSize="45dip"
    android:gravity="right|bottom" 
	binding:text="com.gueei.tutorials.calculator.FormatDisplay(Display)"
    android:layout_weight="1"/>
本文不会深入介绍如何使用 Android-Binding,但您可以查阅以下文章以及源代码以了解更多信息。
为了使 View 能够感知 ViewModel 属性的变化,我们在 Android-Binding 中有两种方式来公开它们。
- Observable-Command 模式:- View可以绑定的所有属性都包装在- Observable<T>;中,而命令则实现- Command接口。这是 Android-Binding 中主要支持的方法。
- POJO:- ViewModel定义了 Java 风格的 getter-setter 属性,并且- ViewModel实现了- PojoViewModel接口。这是一个 Android-Binding 的插件接口,该插件会将- ViewModel转换为基于- Observable的对象。
 这里,我们的示例将采用- Observable-Command 模式。(- POJO版本也可在源代码中找到。)
Observable<T>
<T> 是任何 Object,而 Observable<T> 表示类型为 T 的属性是 Observable,当对其进行更改时,它会通知其订阅者。
因此,我们必须将上述属性包装在 Observables 中。
Observable<Double> Result = new Observable<Double>(Double.class, 0d);
命令
Command 类似于 ViewModel 的 public 方法,它们可以被 UI 小部件调用。
绑定 View 与 ViewModel
一旦实现了 ViewModel,并声明了我们的布局,我们就需要将 ViewModel 绑定到 View。通过一点努力,Android-Binding 会为您自动完成绑定。在 Activity 中
Binder.setAndBindContentView(this, R.layout.main, new CalculatorViewModel());
DependentObservable/Converter
有时,我们希望在呈现数据之前对其进行格式化。例如,我们在计算器中进行‘double’计算,但我们不希望向用户显示原始 double 值——答案可能过于冗长,不适合实际使用(例如超过 15 位有效数字),我们希望将其限制在 10 位有效数字以内。
当然,我们可以在将显示值发送到 View 之前,在 ViewModel 中对其进行处理;我们通过使用 DependentObservable/Converter 来实现这一点。
DependentObservable 是一个 `Observes` 其他 observables 的 Observable,一旦其中任何一个 observable 发生变化,它就会相应地计算其值;Converter 与 DependentObservable 相同,只是 DependentObservable 是只读的,而 Converter 可以执行反向转换。
由于我们的显示数字纯粹用于显示目的,我们只需要 DependentObservable。我们可以显式地在 ViewModel 中声明 dependentObservable,但是,我认为视图的外观格式化不应该是 ViewModel 的职责,而应该由 View 本身来完成。
Android-Binding 支持自定义 Converters(以及一些内置的),可以在 XML 布局中声明。首先,我们必须创建一个扩展自 DependentObservable 的类。
public class FormatDisplay extends DependentObservable<CharSequence> {
    public FormatDisplay(IObservable<?>[] dependents) {
        super(CharSequence.class, dependents);
    }
    @Override
    public CharSequence calculateValue(Object... args) throws Exception {
        Double display = (Double)args[0];
        DecimalFormat format = new DecimalFormat();
        format.applyPattern("#.######");
        String output = format.format(display);
        if (output.length() <= 10) return output;
        format.applyPattern("0.########E00");
        return format.format(display);
    }
}
现在,我们修改布局以
<TextView android:layout_width="fill_parent"
    android:layout_height="fill_parent" android:textSize="45dip"
    android:gravity="right|bottom" 
    binding:text="com.gueei.tutorials.calculator.FormatDisplay(Display)"
    android:layout_weight="1"/>
这样,Display 的绑定将变为
 
 
使用 MVVM 的优点
与其他任何 MVVM 平台一样,在 Android 中使用 MVVM 有助于将后端代码与 UI 解耦;在上述计算器示例中,我们可以为其连接不同的布局,而无需更改 ViewModel 中的任何内容(例如,以不同的方式格式化显示)。
使用 MVVM 的另一个巨大优势是,您可以轻松地对 ViewModel 进行单元测试。您无需真正“单击”按钮即可查看计算是否正确,只需单元测试 ViewModel 的相应 Command 即可。
借助 Android-Binding 框架,代码通常会更整洁。想象一下在传统的事件模型中为那 15 个按钮实现事件处理程序,它将导致保留对控件的 15 个引用,注册 15 次‘setOnClickListener()’,以及一个巨大的 switch-case-default 块用于 15 个事件处理分支。但有了 MVVM,Command 接口可以更清晰地描述它们的用途。
结束语
我尝试构建了几个 Android MVVM 应用程序,并发现有时 ViewModel 和 Activity 之间的角色可能非常模糊。由于 Android 应用程序中的许多地方都需要使用 Context(例如获取 res、cursor、调用其他 Activity),最终 ViewModel 需要将调用 Activity 传递给它,或者将 ViewModel 嵌套在 Activity 类中,甚至 Activity 类本身就是 ViewModel (对于非常简单的 ViewModel 来说是可以的)。这种方法使得 ViewModel 与 Activity 并行工作:Activity 决定 ViewModel 的创建和哪个 View 将被渲染,并管理 ViewModel 的生命周期;ViewModel 使用 Activity 来分派请求到外部世界。
历史
- 2011 年 3 月 9 日:初次发布




