Android架构模式三:MVVM
原文地址:https://upday.github.io/blog/model-view-viewmodel/
Android架构模式:MVVM
在开发upday应用的前六个月中,经过四次不同设计,我们学到了一个重要的教训:我们需要一个能及时相应设计变化的架构!最终我们选择的解决方案是MVVM。和我一起来探索下什么是MVVM;我们是如何在upday中应用它的以及是什么使得它对我们而言是如此完美地适合。
MVP模式
MVVM的主要参与者是:
- View-向ViewModel报告用户行为
- ViewModel-公开与View相关的数据流
- DataModel-抽象的数据源。ViewModel与DataModel协作以获取和保存数据。
乍一看,MVVM似乎与MVP模式非常接近,因为两者在抽象View的状态和行为方面都做得非常好。MVP抽象了一个独立于特定平台用户界面的View,而MVVM则是为了简化编写事件驱动的用户界面而创建的。
如果MVP是由Presenter直接告知View显示什么,那在MVVM中,ViewModel暴露View可以绑定到的事件流。这样,ViewModel就不需要像Presenter一样再持有View的引用。这也意味着MVP模式所需的所有接口现在都被丢弃了。
View同样也会通知ViewModel不同的用户行为。MVVM模式支持View和ViewModel之间的双向数据绑定,它们之间存在一个多对一的关系。View有一个ViewModel的引用,但ViewModel没有关系View的信息。消费者需要知道生产者,但是生产者,即ViewModel不需要知道,也不关心谁是消费者。
upday中的MVVM
快速浏览下upday博客上Androidi的帖子将立即揭示我们最喜欢的库是:RxJava。毫无疑问,RxJava是upday代码的基石。MVVM所需的事件驱动模型就是由RxJava的Observable
实现的。以下是我们如何在RxJava的帮助下使用MVVM构建upday的Android应用:
DataModel
DataModel公开了便于RxJava的Observable
事件流消费的数据。它组合来自多个数据源的数据,如网络层,数据库或shared preferences,并向任何需要它的地方公开易于消费的数据。DataModel维护着全部业务逻辑。
我们强烈强调单一职责原则,这指导我们为应用中的每一个功能创建一个DataModel。例如,我们有一个ArticleDataModel合并来自API服务和数据库层数据作为输出,这个DataModel处理业务逻辑是通过一个时间过虑器来确保从数据库中获取最新的消息。
ViewModel
ViewModel是应用程序的一个抽象视图模型。ViewModel从DataModel中查询必要的数据,应用UI逻辑,然后公开View使用的相关数据。与DataModel相似,ViewModel通过Observables
公开数据。
我们从ViewModel中学到两件事:
- ViewModel应该向View公开它的状态,而不仅仅是事件。例如,如果我们需要显示用户的姓名和邮箱地址,我们公为一个封装了两者的
DisplayableUser
类创建一个数据流,而不是为此创建两个数据流。每次姓名或邮箱改变时,该数据流都会响应。这样,我们就确保View始终显示最新的用户信息。 - 我们应该确保用户的每个操作都通过ViewModel,View的任何可能的逻辑都迁移到ViewModel中。
我们在common mistakes in MVVM + RxJava这篇博客中详细讨论了这两个话题。
View
View是应用中实际的用户界面。它可以是Activity
,Fragment
或任何自定义控件。对于Activity
与Fragment
,我们在方法onResume()
中绑定事件源并在onPause()
中解除绑定。
private final CompositeSubscription mSubscription = new CompositeSubscription();
@Override
public void onResume() {
super.onResume();
mSubscription.add(mViewModel.getSomeData()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateView,
this::handleError));
}
@Override
public void onPause() {
mSubscription.clear();
super.onPause();
}
如果MVVM中的View是一个Android控件,那么就在它的构造方法中绑定。为了确保不发生内存泄漏,解除绑定在onDetachedFromWindow
中进行。
private final CompositeSubscription mSubscription = new CompositeSubscription();
public MyView(Context context, MyViewModel viewModel) {
...
mSubscription.add(mViewModel.getSomeData()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateView,
this::handleError));
}
@Override
public void onDetachedFromWindow() {
mSubscription.clear();
super.onDetachedFromWindow();
}
MVVM中类的可测试性
我们喜欢MVVM模式的主要原因之一是测试非常简单
DataModel
我们的代码中大量应用控制反转,且不包含任何Android类,这有助于实现单元测试。
ViewModel
我们将View与单元测试视为ViewModel数据的不同消费者。ViewModel完全与UI或Android类分离,因此可以直接进行单元测试。
考虑下面的例子,其中ViewModel仅仅公开DataModel中的一些数据:
public class ViewModel {
private final IDataModel mDataModel;
public ViewModel(IDataModel dataModel) {
mDataModel = dataModel;
}
public Observable<Data> getSomeData() {
return mDataModel.getSomeData();
}
}
这个ViewModel的测试很好实现。借助Mockito库,我们模拟DataModel并控制返回的数据。然后确保当我们订阅由getSomeData()
返回的Observable
时,预期的数据被发射(emit)了。
public class ViewModelTest {
@Mock
private IDataModel mDataModel;
private ViewModel mViewModel;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mViewModel = new ViewModel(mDataModel);
}
@Test
public void testGetSomeData_emitsCorrectData() {
SomeData data = new SomeData();
Mockito.when(mDataModel.getSomeData()).thenReturn(Observable.just(data));
TestSubscriber<SomeData> testSubscriber = new TestSubscriber<>();
mViewModel.getSomeData().subscribe(testSubscriber);
testSubscriber.assertValue(data);
}
}
如果ViewModel需要访问Android类,我们创建一个名为Provider
的包装器。例如,对于Android资源,我们创建一个IResourceProvider
,它会分不开一些如 String getString(@StringRes final int id)
这样的方法。IResourceProvider
的实现会包含对Context
引用,但是ViewModel仅会引用注入的IResourceProvider
。
就像我们上面,和这篇博客中提到的,我们创建模型对象来保存数据状态。这也允许控制ViewModel发射了的数据并作更高层次的测试。
View
鉴于用户界面中的逻辑很少,视图很容易用Espresso进行测试。我们还使用DaggerMock和MockWebServer等库来提高UI测试的稳定性。
MVVM是正确的解决方案吗?
我们已经使用MVVM和RxJava快一年了。我们发现,由于View仅仅作为ViewModel的消费者,这使得替换不同UI元素时,仅需改变一小部分的类,有些甚至为零。
我们同时学习关注分离是多么的重要,我们应该更多地分解代码,创建只有特定职责的小巧的View和ViewModel。View中的ViewModel是注入的。这意味着大多时候,我们仅仅在XML中创建用户界面,且不需要作任何修改。因而,当U需求再次改变时,我们可以轻松地替换掉View。
结语
MVVM结合了MVP提供的关注点分离的优点,同时利用了数据绑定的优点。成为了一种最少化视图逻辑、模型尽可能多地由操作驱动的模式。
经过在应用“婴儿期”的设计变更后,我们在应用的“青春期”转向了MVVM,这期间我们学到了很多东西。现在,我们可以自信地响应下一次设计变更。终于我们可以称upday是一个成熟的应用了。
这里可以找到一个简单的MVVM示例。
这里可以找到一个比较MVP与MVVP的“Hello, World!”式的示例。
Written with StackEdit.