Android MVP 实战经验

作者: 东东东鲁 | 来源:发表于2016-11-06 02:06 被阅读244次

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

    三层架构通常是指表现层(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<Presenter> {
            void setLoadingIndicator(boolean active);
            void showTasks(List<Task> 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<List<Task>> 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<List<Task>>() {
                        @Override
                        public void onCompleted() {
                            mTasksView.setLoadingIndicator(false);
                        }
                        @Override
                        public void onError(Throwable e) {
                            mTasksView.showLoadingTasksError();
                        }
                        @Override
                        public void onNext(List<Task> 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<Task> 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 实战经验

        本文链接:https://www.haomeiwen.com/subject/eaiguttx.html