Android MVP架构解读

之前就听说了这种架构模式,网上也讨论的比较多,但是一直没有去研究,最近就将Google上传到Github 上的MVP示例代码研究了一下,对此架构也有了一定的理解。

关于MVC,MVP,MVVM这三种架构的介绍可参见以下文章:(这位前辈总结的很到位!)
http://www.tianmaying.com/tutorial/AndroidMVC

说MVP之前,我们先回顾以下MVC模式,MVC可谓时软件开发中最经典的架构模式,无论是前段,后端还是移动端,都被广泛应用。

MVC中Model,View和Controller的关系如下:

我们可以看出,这种架构的特点就是数据单向流动,View层直接从Model层获取数据,然后通过Controller层对Model进行修改。对于这种架构,我们可以分为两个流程来看:
View——Controller——Model流,如图所示:

在这个流程中,我们可以看到,View如果要对Model进行修改,必须通过Controller来实现,这样数据的处理逻辑全部集中在Controller部分,大大降低了View和Model的耦合度;

而另一个流程,Model——View,却是直接关系,View的改变,就意味着数据逻辑的变化(最简单的例子,产品的设计图决定了后端接口的实现)。也就是说,当我们的业务发生改变时,其中的数据逻辑部分我们也要跟着发生改变,这可不是什么好事,那有没有解决方案呢,答案是肯定的。既然前面的 流程通过Controller降低了耦合度,那么我们照猫画虎,在Model——View这个流程中也引入Controller的角色,这样,MVC的流程就如下所示:

看着是不是有点眼熟,没错,这就是我们要介绍的MVP模式。MVP本身就是在MVC的基础上发展而来的,下面我们重点介绍一下。
MVP中Model,View和Presenter的关系如下:(其实Presenter就相当于MVC中的Controller)

我们可以看到,View和Model已经没有了直接关系,它们直接的所有操作都是通过Presenter来完成,彻底消除了View和Model之间的耦合度。

在Android中,由于Activity既充当View的角色(layout的View角色还是太弱),又充当这Controller的角色,这种尴尬的角色导致我们在开发中,基本上会将所有的逻辑集中在Activity中,导致稍微复杂业务的代码就会有上千行,而且代码臃肿,耦合度高,难以维护。MVP的出现正是为了解决这一问题,弱化Activity充当Controller的能力,彻底将与Model打交道的逻辑部分的代码分离到Presenter中。一方面消除了View与Model层之间的耦合,另一方面,为Presenter中的数据逻辑的复用提供了可能。

下面我们以Google提供的示例来详细分析一下MVP模式:

示例代码地址:

https://github.com/googlesamples/android-architecture

对于MVP架构,Google提供了多种示例,可根据具体的需求进行查看,下面我们以最基本的todo-mvp示例作为分析的项目:

这是整个项目的基本结构。测试,还有本地数据构造的包我们就不看了,重点分析main包中的文件。每个子包的功能图上已经注明了,既然我们分析的是MVP的整个流程,那么我们就选择一个子模块进行分析。下面我们就以taskdetail包作为分析目标:

TaskDetailActivity.java

TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()
            .findFragmentById(R.id.contentFrame);
if (taskDetailFragment == null) {
    taskDetailFragment = TaskDetailFragment.newInstance(taskId);

    ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
            taskDetailFragment, R.id.contentFrame);
}

// Create the presenter
new TaskDetailPresenter(
        taskId,
        Injection.provideTasksRepository(getApplicationContext()),
        taskDetailFragment);

我们可以看出,TaskDetailActivity的作用就添加一个Fragment,并将其与TaskDetailPresenter相关联。倒感觉扮演了控制器的角色,将View(Fragment)与Presenter关联起来。这种做法与Google推崇的多使用Fragment代替Activity的理念倒是一致的。

下面我们看以下Fragment中的部分代码:

TaskDetailFragment.java

public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {

...

@Override
public void onResume() {
    super.onResume();
    mPresenter.start();
}

@Override
public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {
    mPresenter = checkNotNull(presenter);
}
...

}

这里我们只摘取与MVP有关的部分,其他无非也就是加载布局,填充数据等内容。我们可以看出,这个TaskDetailFragment实现了TaskDetailContract.View接口, 同时调用了TaskDetailPresenter的start方法,那么start方法是做什么的呢?看来我们又得继续看一下TaskDetailContract和TaskDetailPresenter这两个文件了。

TaskDetailContract.java
public interface TaskDetailContract {

    interface View extends BaseView<Presenter> {

        void setLoadingIndicator(boolean active);

        void showMissingTask();

        ... // 都是一些View的控制函数
    }

    interface Presenter extends BasePresenter {

        void editTask();

        ... // 有关数据操作的函数
    }
}

原来TaskDetailContract.BaseView中定义的是有关Fragment中的UI的一些操作。

TaskDetailPresenter.java

public class TaskDetailPresenter implements TaskDetailContract.Presenter {

    public TaskDetailPresenter(@Nullable String taskId,
                               @NonNull TasksRepository tasksRepository,
                               @NonNull TaskDetailContract.View taskDetailView) {
        this.mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
        mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");

        mTaskDetailView.setPresenter(this);
    }

    @Override
    public void start() {
        openTask();
    }

    private void openTask() {
        // 数据请求的过程
    }

    @Override
    public void editTask() {
        if (null == mTaskId || mTaskId.isEmpty()) {
            mTaskDetailView.showMissingTask();
            return;
        }
        mTaskDetailView.showEditTask(mTaskId);
    }
}

我们可以看到,Presenter实现了TaskDetailContract.Presenter接口,完成有关TaskDetail模块中的所有数据操作,而start()方法正是用来请求数据的。通过对这四个文件的分析,我们可以看到,View(Fragment)持有Presenter的一个引用,同时Presenter持有View的引用,这种双向引用实现了数据逻辑与View的解耦。具体的代码可自行下载研究,这里我们只看涉及到MVP流程的代码。下面我们通过图解的方式来说明以下Google的这个MVP示例的流程:

我们可以看出,Google的这种组织结构很清晰,通过中间加入Contract将View和Presenter组合起来,同时使用了Fragment代替Activity职责的方式,让View层集中在Fragment中(满足Google倡导的多使用Fragment的思想)。整体看来,架构清晰明了,便于维护。但却引进了一个明显的问题,我们发现,使用这种组织结构,每一个小功能点都需要至少4个文件(XXXActivity, XXXFragment, XXXPresenter, XXXContract),也许一个小的功能点这倒没什么问题,但是对于整个项目,这种简洁的组织方式,却带来了文件冗余的问题。所以,我们接下来对此进行简化:

  1. 取消Activity加载相应Fragment的设计结构。个人觉得,每个页面都使用Activity加载Fragment来实现,显得Activity很鸡肋。同时文件数量也会明显增加。对于不需要复用的Fragment(实际上在项目开发中可复用的Fragment其实并不多),直接使用Activity来实现;

  2. 消除Contract层,纵然它带来了清晰的结构,但却增加开发时文件之间的切换频率,View中的操作,本来就可以在Activity/Fragment来完成,但是却需要在Contract.View中进行定义,也就是说,View发生改变,Contract也需要跟着变化,反复的切换很麻烦,同时Contract的存在,也增加了文件数量。

经过这两步的简化,每一个功能点只需要2个文件(XXXActivity, XXXPresenter)就可以了,也满足之前MVP的基本结构。简化后流程如下图所示:

从图上看起来,好像显得更复杂了,那是为了让项目的代码结构达到统一,引入了BaseFragment/BaseActivity。

其实对于架构,没有最好用的,只有最合适的。我们在设计时,不能死扣设计,还应该根据当前的需求做出合适的调整。毕竟,架构只是为了让我们更高效地实现功能,简化项目的维护成本。离开了需求的架构没什么意义。

对于上图中的组织结构,可参见下一篇文章:Retrofit+RxJava实现MVP架构

,