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 日:初次发布