原文地址:https://upday.github.io/blog/model-view-presenter/

Android架构模式:MVP

我们的开发人员是时候考虑如何在Android应用中应用良好的架构模式了。为此Google推出了Android Architecture Blueprints项目,在这个项目中,Erik Hellman与我基于MVP与RxJava创建了一些示例。现在来看看我们是如何应用的,以及这种方案的优点与缺点。

MVP模式

以下是各个组件的角色:

  • Model-数据层,负责管理业务逻辑并维护网络和数据库接口。
  • View-UI层,显示数据并通知Presenter用户行为。
  • Presenter-从Model中查询数据,处理UI逻辑,管理View状态,决定显示内容,并响应来自View的用户输入。

由于View与Presenter密切合作,它们需要互相引用。为了让Presenter可以使用JUnit作单元测试,View会被抽象为一个接口。Presenter与其相应View之间的关系定义在Contract接口中,使得代码更易读,两者之间的联系更易于理解。

Android架构模式二:MVP_第1张图片

在Android Architecture Blueprints中的MVP与RxJava

项目中的示例是一个“待办事项”应用。它允许用户创建、阅读、更新和删除“待办事项”储备,以及过虑任务列表。RxJava用于隔离主线程与执行异步操作。

Model

Model与远程和本地数据源协作,以获取和保存数据。这里正是处理业务逻辑的地方。例如,当请求任务列表时,Model会试着先在本地数据源查询。如果没用,它就请求网络,并将响应的数据保存在本地数据源中,最后返回列表。
任务查询是在RxJava的帮助下来完成的:

public Observable> getTasks(){    ...}

Model通过构造方法持有本地和远程数据源,这使得Model完全独立于Android类,从而便于做单元测试。例如,为了测试getTasks从本地请求数据,我们实现以下测试方法:

@Mockprivate TasksDataSource mTasksRemoteDataSource;@Mockprivate TasksDataSource mTasksLocalDataSource;...@Testpublic void getTasks_requestsAllTasksFromLocalDataSource() {    // Given that the local data source has data available    setTasksAvailable(mTasksLocalDataSource, TASKS);    // And the remote data source does not have any data available    setTasksNotAvailable(mTasksRemoteDataSource);    // When tasks are requested from the tasks repository    TestSubscriber> testSubscriber = new TestSubscriber<>();    mTasksRepository.getTasks().subscribe(testSubscriber);    // Then tasks are loaded from the local data source    verify(mTasksLocalDataSource).getTasks();    testSubscriber.assertValue(TASKS);}

View

View与Presenter一起工作,显示数据,并通知Presenter关于用户的行为。在MVP中,Activity、Fragment与自定义控件都可作为View。这里我们使用Fragment。
所有View都实现了一个可以关联Presenter的BaseView接口。

public interface BaseView {    void setPresenter(T presenter);}

View会在onResume中调用Presenter的subscribe方法来告知Presenter它已经准备好做更新操作。而在onPause中调用presenter.unsubscribe()来告知Presenter它不再接收更新操作。如果View的实现是一个自定义控件,那么subscribeunsubscribe方法会在onAttachedToWindowonDetachedFromWindow中调用。用户行为,如点击按钮,将触发Presenter中相应的方法,这决定了下一步流程。
我们使用Espresso来测试View。例如,要在屏幕上显示活动任务和已完成任务列表。单元测试首先会创建一些任务放在TaskRepository中,然后启动StatisticsActivity来检测是否正确显示:

@Beforepublic void setup() {    // Given some tasks    TasksRepository.destroyInstance();    TasksRepository repository = Injection.provideTasksRepository(            InstrumentationRegistry.getContext());    repository.saveTask(new Task("Title1", "", false));    repository.saveTask(new Task("Title2", "", true));    // Lazily start the Activity from the ActivityTestRule    Intent startIntent = new Intent();    mStatisticsActivityTestRule.launchActivity(startIntent);}@Testpublic void Tasks_ShowsNonEmptyMessage() throws Exception {    // Check that the active and completed tasks text is displayed    Context context = InstrumentationRegistry.getTargetContext();    String expectedActiveTaskText = context            .getString(R.string.statistics_active_tasks);    onView(withText(containsString(expectedActiveTaskText)))            .check(matches(isDisplayed()));    String expectedCompletedTaskText = context            .getString(R.string.statistics_completed_tasks);    onView(withText(containsString(expectedCompletedTaskText)))            .check(matches(isDisplayed()));}

Presenter

Presenter与其相应的View由Activity创建;View与TaskRepository(即Model)的引用由构造方法传入;在构造方法中,Presenter将调用View的setPresenter方法。以上过程可以由依赖注入框架简化,允许向Presenter注入相应的View,以降低类之间的耦合度。这个方案可以在另一个由Dagger实现的实现中找到。
所有Presenter都实现了BasePresetner接口。

public interface BasePresenter {    void subscribe();    void unsubscribe();}

subscribe方法调用时,Presenter向Model请求数据,然后将UI逻辑应用于接收到的数据,最后传递给View。例如,在StatisticsPresenter中,所有任务都是向TaskRepository查询得到的,查询结果会用于计算活动任务和已完成任务的数量。这些数量将用作View方法showStatistics(int numberOfActiveTasks, int numberOfCompletedTasks)的参数。
一个检测showStatistics是否调用了正确值的单元测试是很好实现的。我们将模拟TaskRepositoryStatisticsContract.View,然后将模拟对象作为StatisticsPresenter构造方法的参数。以下是测试实现:

@Testpublic void loadNonEmptyTasksFromRepository_CallViewToDisplay() {    // Given an initialized StatisticsPresenter with    // 1 active and 2 completed tasks    setTasksAvailable(TASKS);    // When loading of Tasks is requested    mStatisticsPresenter.subscribe();    // Then the correct data is passed on to the view    verify(mStatisticsView).showStatistics(1, 2);}

unsubscribe的作用是清除Presenter中的观察者,防止内存泄露。
除了subscribeunsubscribe,每个Presenter也会依据View中的用户行为暴露一些其它方法。例如,AddEditTaskPresenter会添加如createTask这样的方法,来响应用户点击任务创建按钮的行为。这保证所有用户行为(以及随后的UI逻辑)都通过Presenter,从而可以进行单元测试。

MVP模式的缺点

MVP带来了很好的关注分离方案。虽然这确实是一个优点,但是在开发小应用时,这似乎是个不小的开销。为了减少接口数量,一些开发人员删除了Contract,以及Presenter的接口。
当把UI逻辑移到Presenter中时,一个缺陷出现在了:Presenter变成了一个拥有数千行代码的全知类。为了解决这个问题,应该更多地分割代码并记住仅创建单一职责且可作单元测试的类。

结语

MVC模式有两个主要缺点:第一,View同时持有Controller与Model的引用;第二,没有限制UI逻辑在单一的类中,而是由Controller与Model共享。MVP模式通过对View与Model解耦,并且只创建一个类处理所有与View呈现相关的事情,即Presenter(一个容易进行单元测试的类),从而解决了以上问题。

如果我们想要一个事件驱动的架构呢?View在哪里响应变化?请继续关注Android Architecture Blueprints示例中的下一个模式,以了解如何实现这一点。届时请阅读upday应用中关于MVVM模式的实现。

Written with StackEdit.

更多相关文章

  1. Android将数据存放到SDCard
  2. Settings的数据库中加新的字段
  3. Android软键盘弹出时把布局顶上去的解决方法
  4. android sqlite查询数据时报错: get field slot from row 0 col
  5. Android drawArc方法介绍
  6. 【翻译】Android多线程下安全访问数据库
  7. android 联系人数据库
  8. Android应用程序永久获取root权限方法
  9. android post 提交数据

随机推荐

  1. Android(安卓)在应用中使用用户凭证(PIN码
  2. Android(安卓)原始资源文件的使用详解
  3. 7、从头学Android之TextView控件
  4. Android入门(1) 不一样的HelloWorld
  5. 面向 Android* Jelly Bean 4.2 的英特尔
  6. CMake学习
  7. Android(安卓)O/P/Q 版本如何预装 APK
  8. Android(安卓)电子书应用完全开源代码
  9. android的Handler
  10. MTP in Android