Android MVP 实战经验

一、表现层模式架构的演变

三层架构通常是指表现层(Presentation Layer)、业务逻辑层(Business Layer)和数据访问层(Data Access Layer)。表现层是用户和系统之间交流的桥梁,它一方面提供与用户交互的界面,另一方面也提供了与数据交互的逻辑,便于协调用户与系统的操作。我们所谈论的 MVC、MVP、MVVM 等设计模式都属于表现层的设计模式。

1. MVC(Model-View-Controller)模式

MVC

如果把 MVC 模式套用在 Android 中,那么:

  • View 对应 xml 布局,实现数据的展示
  • Controller 对应 Activity / Fragment ,处理业务逻辑
  • Model 对应数据源,包括网络接口数据、数据库、缓存等

使用 MVC 模式看似分工明确,但是应用到 Android 中,会带不少的问题:

  • Activity / Fragment 实现了多重职责,即是 View,又是 Controller,导致代码复杂臃肿,难以复用
  • 把 Activity / Fragment 作为 Controller,无法对 Controller 进行单元测试
    所以,在 Android 中,MVC 模式不太适用。

2. MVP(Model-View-Presenter) 模式

MVP 模式是 MVC 的进化版,它把 Controller 的职责从 Activity/Fragment 中拆分出来,作为 Presenter,这样就实现了 Activity/Fragment 和业务逻辑的解耦,更好地解决了数据与界面的关系。

二、细说 MVP

1. 职责划分

MVP 各自的职责分别是:

  • View

    1. 对应 Activity / Fragment / Custom View
    2. 与用户交互,响应用户操作,分派事件行为给 Presenter 处理
    3. 响应 Presenter 回调,对数据进行显示
  • Presenter

    1. 是连接 VIew 和其它代码的胶水
    2. 用于转换 Model 的数据以便于 VIew 显示
    3. Presenter 不做 UI 相关处理,也不包含上下文对象(Context)
  • Model

    1. 与数据进行交互,对数据进行加工处理
    2. 通常是与 Android 无关的,不会用到 Android SDK
    3. 关注从哪里拿数据(Retrofit、Sqlite etc.)

2. 架构实现

MVP

谷歌官方已经给出了一个 MVP 架构的实践示例[googlesamples/android-architecture](上面图中的 REPOSITORIES 就是指 Model 层)。

接下来,我们以它为例来看一下具体的实现。我们简化一下代码,更直观地看一下它们之间的关系。

首先官方的例子定义了一个契约(Contract)类:

public interface TasksContract {
    interface View extends BaseView {
        void setLoadingIndicator(boolean active);
        void showTasks(List tasks);'' 
        void showLoadingTasksError();
        // ....
    }

    interface Presenter extends BasePresenter {
       void loadTasks();
       void addNewTask();
        // ....
    }
}

契约类就是把 MVP 所需要定义的几个接口都写在一个类里面。在项目实现中,我会把 Model 接口也写到契约类里,定义契约类的好处除了可以少写几个 Java 文件外,也比较直观。比如先在 Presenter 定义一个 loadTasks 方法,那么相应地,Model 就接口需要定义一个 getTasks 方法来为 Presenter 提供数据,View 接口则需要定义一个 showTasks 来显示获取到 Tasks 数据,以及 setLoadingIndicator、showLoadingTasksError 等方法来更新 UI 状态。

  • Model 层的实现代码:
public class TasksRepository implements TasksDataSource  {
    @Inject 
    TasksRepository(@Remote TasksDataSource tasksRemoteDataSource, 
                    @Local TasksDataSource tasksLocalDataSource) {
        mTasksRemoteDataSource = tasksRemoteDataSource;
        mTasksLocalDataSource = tasksLocalDataSource;
    }

    @Override
    public Observable> getTasks() {
        // 从缓存或者网络接口等数据源获取数据
    }
}

Model 层的代码维护了两个数据源(mTasksRemoteDataSource 和 mTasksLocalDataSource),用来为 Presenter 提供数据,Presenter 无需关心从哪里拿数据。代码中的注解 @Inject 标记了该构造方法可以被注入,如果你使用了 Google 的 Dagger 框架,Dagger 可以提供 TasksRepository 所需的依赖,并创建一个 TasksRepository 对象。

  • Presenter 层的实现代码:
public class TasksPresenter implements TasksContract.Presenter {
    @Inject
    TasksPresenter(TasksRepository tasksRepository, TasksContract.View tasksView) {
        mTasksRepository = tasksRepository;
        mTasksView = tasksView;
    }

    @Override
    public void loadTasks() {
        mTasksRepository.getTasks()
                .observeOn(mSchedulerProvider.ui())
                .subscribe(new Observer>() {
                    @Override
                    public void onCompleted() {
                        mTasksView.setLoadingIndicator(false);
                    }
                    @Override
                    public void onError(Throwable e) {
                        mTasksView.showLoadingTasksError();
                    }
                    @Override
                    public void onNext(List tasks) {
                        mTasksView.showTasks(tasks);
                    }
                });
    }
}

这里我们使用了 Dagger 和 RxJava(官方例子是分开两个独立的分支),Dagger 注入所需的依赖,并创建 TasksPresenter 对象。View 通过调用 Presenter 的 loadTasks 方法来获取便于 VIew 展示的数据。Presenter 就是用于响应 View 分派的事件,校验数据并提交给 Model 处理,最后把 Model 处理的结果转交给 View。

另外,Presenter 也承担了一部分 Activity / Fragment 的业务逻辑,这样也减轻了 Activity / Fragment 作为 View 的负担。

  • View 层的实现代码
public class TasksFragment extends Fragment implements TasksContract.View {
   private TasksContract.Presenter mPresenter;
   
   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
       swipeRefreshLayout.setOnRefreshListener(new OnRefreshListener() {
           @Override
           public void onRefresh() {
               mPresenter.loadTasks();
           }
       });
   } 

   @Override
   public void showLoadingTasksError() {
       showMessage(getString(R.string.loading_tasks_error));
   }

   @Override
   public void showTasks(List tasks) {
       mListAdapter.replaceData(tasks);
       mTasksView.setVisibility(View.VISIBLE);
       mNoTasksView.setVisibility(View.GONE);
    }
}

View 就比较简单了,纯粹做 UI 相关的处理,不关注业务处理。它将用户的行为传递给 Presenter,同时接收 Presenter 的调用来更新界面。在上面的代码中,当 TasksFragment 初始化之后,会调用 swipeRefreshLayout.setRefreshing 来触发 Presenter 的 loadTasks() 方法。之后,当 Presenter 处理完后,调用 View 的 showTasks 或者 showLoadingTasksError 方法,TasksFragment 只需要关注界面更新的具体实现。

从上面的例子来看,Model-View-Presenter 三种角色之间分工明确,使得数据与界面之间的耦合更低,代码复用性更高,也更方便于测试。

三、总结

使用 MVP 模式来开发 Android 应用给我们带来了很多好处,只是需要多定义 M-V-P 这三个接口(可以写在一个 Contract 类中),正是因为这些接口,才使得类的组织结构更加清晰,每一层实现对应的接口,只关注其本身单一的职责。这样层与层的耦合底非常低,可维护性提高。

一路走来,我们也在不断地尝试,持续地改进现有的架构。我们结合了目前流行的框架来提高生产力;比如,使用 Dagger 来为 Presenter 注入依赖(不需要再用new关键字创建各种对象),还使用了 Retrofit、RxJava、Realm 等框架更好地去组织数据层的接口,以及使用 DataBinding 来简化 UI 界面与数据实体的绑定。

架构的作用是为解决痛点,适合自己的才是最好的。希望本文对你找到适合自己项目的架构组织方式有所帮助。

// 能力一般,水平有限。文中有不妥或谬误之处在所难免,请大家批评指正。

你可能感兴趣的:(Android MVP 实战经验)