Android 中的 MVP 和 MVVM 架构

基本架构

Android 默认模板鼓励创建大型 Activity 或 Fragment。这些组件通常同时包含业务和 UI 逻辑。这使得 Android 应用程序的测试和维护变得更加困难。为了提高可测试性,Android 社区流行几种模式。

最流行的架构选择是:

  • Model View Presenter (MVP)
  • Model View View Model (MVVM)

MVP 或 MVVM 中的视图与 Android 中的视图类不同。MVP 中的视图通常通过 Fragment、Activity 或 Dialog 来实现。

MVP 架构

Model View Presenter(MVP)体系结构模式改进了应用程序体系结构以提高可测试性。 MVP 模式通过演示者(Presenter)从视图中分离数据模型。

下图演示了一个完整的 MVP 分离数据模型的流程:

视图 View

MVP 中的视图组件包含应用程序的可视部分。

它只包含 UI,不包含显示数据的任何逻辑或知识。在典型的实现中,MVP 中的视图组件导出演示者(Presenter)使用的接口。演示者(Presenter)使用这些接口方法来操作视图。示例方法名是: showProgressBar, updateData。

演示者 Presenter

演示程序(Presenter)触发业务逻辑并告诉视图(View)何时更新。因此,它与模型(Model)交互,从模型中获取和转换数据以更新视图。如果可能,演示者不应该有对 Android SDK 的依赖。

数据模型 Model

包含数据提供程序和用于获取和更新数据的代码。MVP 的这一部分更新数据库或与 web 服务器通信。

使用 MVP 设计模式的注意事项

MVP 使测试演示者(Presenter)的逻辑和替换依赖项变得更容易。但是使用 MVP 也有成本,它会使你的应用程序代码变长。另外,由于目前标准的 Android 模板没有使用这种方法,并不是每个 Android 开发人员都会发现这种代码结构很容易理解。

与 MVC 模式的比较

在模型视图呈现器模式(MVP)中,视图(View)与模型(Model)更加分离。演示者(Presenter)在模型和视图之间进行通信。这使得创建单元测试更加容易,通常在视图和演示程序之间存在一对一的映射,但是对于复杂的视图也可以使用多个演示程序。

在模型视图控制器模式(MVC)中,控制器(Controller)是基于行为的,可以共享多个视图。视图(View)可以直接与模型(Model)通信。

MVP 目前是 Android 社区喜欢的模式之一。

MVVM 架构

Model View View Model - MVVM 模式又称为模型视图绑定器模式。

视图 View

视图通常使用数据绑定框架绑定到视图模型公开的可观察变量和动作。例如,视图负责处理:

  • 菜单
  • 权限
  • 事件监听
  • 显示对话框,Toast,Snackbars
  • 和 Android 的视图和组件一起使用
  • 启动 Activity
  • 所有与 Android 上下文 Context 相关的功能

视图模型 View Model

视图模型(View Model)包含视图(View)所需的数据。它是视图的抽象,并公开公共属性和命令。它使用可观察的数据通知视图有关更改。它还允许将事件传递给模型(Model)。它还是一个从原始模型数据到表示友好属性的值转换器)

视图模型有以下职责:

  • 暴露数据
  • 显示状态(进度,开关,空,错误等)
  • 处理可见状态
  • 输入验证
  • 执行对模型的调用
  • 执行视图中的方法

视图模型应该只知道应用程序上下文。应用程序上下文可以:

  • 启动一个服务
  • 绑定一个服务
  • 发送一个广播
  • 注册广播接收者
  • 加载资源文件

不可以:

  • 显示一个弹框
  • 启动一个 Activity
  • 加载一个布局

模型 Model

包含数据提供程序和用于获取和更新数据的代码。例如,可以从不同的数据源检索数据:

  • REST API
  • Realm db
  • SQLite db
  • 处理广播
  • Shared Preferences
  • Firebase

与MVP中的模型基本相同。

和 MVP 模式的区别

MVVM 使用数据绑定,因此是一种事件驱动的体系结构。MVP 通常在表现者和视图之间有一对一的映射,而 MVVM 可以在 MVVM 中将多个视图映射到一个视图模型,视图模型没有对视图的引用,而在 MVP 视图中,视图知道表现者。

使用 flavors 构建来改进可测试性

提高应用程序可测试性的一种方法是使用构建风格。在您的构建风格中,您定义了不同的类来提供数据。例如,如果您有两个口味“prod”和“mock”,那么您的口味中可能有以下不同的实现。

这可能是“mock”味道的假设实现:

public class Injection {

    public static ImageFile provideImageFile() {
        return new FakeImageFileImpl();
    }

    public static NotesRepository provideNotesRepository() {
        return NoteRepositories.getInMemoryRepoInstance(new FakeNotesServiceApiImpl());
    }
}

这可能是“prod”口味的假设实现:

public class Injection {

    public static ImageFile provideImageFile() {
        return new ImageFileImpl();
    }

    public static NotesRepository provideNotesRepository() {
        return NoteRepositories.getInMemoryRepoInstance(new NotesServiceApiImpl());
    }
}

如果您使用的是应用程序代码,那么您可以通过 inject.providenotesrepository() 访问提供的实现。根据您构建的口味,您将收到模拟版本或真实版本。您的单元测试将再次构建模拟,以模拟外部依赖项。

依赖项注入是构建可测试应用程序的另一种方式。 Android 应用程序在大多数情况下使用 Dagger2 进行依赖注入。如果测试正在运行,就会注入模拟对象或伪对象。如果真正的应用程序启动了,就会注入正确的对象。