MVP学习

226 阅读6分钟

一、MVP介绍

3985563-25731fb5c707f5d3.webp

  • Model: 数据层,负责与网络层和数据库层的逻辑交互。
  • View: UI层,显示数据, 并向Presenter报告用户行为。
  • Presenter: 从Model拿数据,应用到UI层,管理UI的状态,响应用户的行为。

MVP是Model, View和Presenter的简称。是非常有名的MVC模式的演化版。MVP模式把显示逻辑和从业务逻辑层中分离出来,理想状况下,MVP模式中,在替换不同的视图(View)的情况下,可以实现完全相同的业务逻辑。

MVP与MVC区别

  1. Presenter代替了MVC中Controller,它比Controller担当更多的任务,也更加复杂。Presenter处理事件,执行相应的逻辑,这些逻辑映射到Model的Command以操作Model。那些处理UI如何工作的代码基本上都位于Presenter。Presenter如同一个乐队的指挥家,表现和协调整个Application,它负责创建和协调其它对象。
  2. 在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller。

MVP优势

  • 分离了视图逻辑和业务逻辑,降低了耦合。

  • Activity只处理生命周期的任务,代码变得更加简洁。

  • 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中,提高代码的阅读性。

  • Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试。

  • 把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM。

二、 基于todo-mvp分析

该示例有四个界面功能:任务列表、任务详情、任务添加编辑、任务统计

3585873492-19e84fca73dec92c_fix732.png

1.项目结构:

3985563-bf64641065361b3a.webp

从上图可以看出,todo-mvp是按照功能模块划分的。

其中tasks, taskdetail, addedittask, statistics是四个业务模块。

data是数据模块,其中具体的类TasksRepository担任Model层,负责远程和本地数据的获取。

BasePresenterBaseView是presenter 和 view 的基类,在具体模块承担实际功能。最后,util是工具类集合。

2. 源码分析

1. 基类

首先看下两个Base接口的基类:BaseView与BasePresenter,分别是所有View与Presenter的基类

// 接受一个泛型参数,泛型类为Presenter的具体实现类
public interface BaseView<T> {
    // View必须实现setPresenter方法,View持有对Presenter的引用
    void setPresenter(T presenter);
}

setPresenter的调用时机在Presenter具体实现的构造方法中,这样View与Presenter关联起来了。

public interface BasePresenter {
    // 规定Presenter必须要实现start方法
    void start();
}

该start方法的作用:Presenter开始获取数据并调用View的方法刷新UI,调用时机是在Activity(或者Fragment)的onResume方法。

2. 定义契约类(合同)

Google引用契约类,主要作用是用来统一管理View和Presenter接口,使得View和Presenter中有哪些功能,一目了然,便于维护。下面通过任何列表界面来分析:

public interface TasksContract {

    interface View extends BaseView<Presenter> {

        void showTasks(List<Task> tasks);
        // ... 省略其它的UI操作
    }

    interface Presenter extends BasePresenter {

        void loadTasks(boolean forceUpdate);
        // ... 省略其它的业务操作
    }
}

  • TasksContract(代表契约、合同)中的View接口定义了该界面中所有UI操作;TasksFragment作为View层,实现了该接口(也就是View接口),TasksFragment只需要关心UI相关的操作,所有的事件操作都通过TasksPresenter完成
  • Presenter接口定义了该界面中所有操作事件;TasksPresenter作为Presenter层,实现了该接口(也就是Presenter接口),TasksPresenter只关心业务相关的逻辑,UI状态更新通过View层的方法

3. Model层

它的作用主要用来获取数据、存取数据、数据状态更新等。先看下TasksRepository的getTasks方法


 // 远程端数据源
 private final TasksDataSource mTasksRemoteDataSource;
 // 本地SQLite数据源
 private final TasksDataSource mTasksLocalDataSource;

    @Override
    public void getTasks(@NonNull final LoadTasksCallback callback) {
        checkNotNull(callback); // 判空处理,提前发现异常的方式

        // 如果内存中有缓存数据并且不是脏数据,立即返回
        if (mCachedTasks != null && !mCacheIsDirty) {
            callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
            return;
        }

        if (mCacheIsDirty) {
            // 如果缓存中是脏数据,需要从网络获取新的数据
            getTasksFromRemoteDataSource(callback);
        } else {
            // 如果本地SQLite中缓存数据可用,返回;否则从网络获取新的数据
            mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
                @Override
                public void onTasksLoaded(List<Task> tasks) {
                    refreshCache(tasks); // 更新内存中缓存数据
                    callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values())); // 成功回调
                }

                @Override
                public void onDataNotAvailable() {
                    // 回调该方法,说明缓存中的数据不可用,从网络获取新的数据
                    getTasksFromRemoteDataSource(callback); 
                }
            });
        }
    }


TasksRepository中维护着两个数据源:mTasksRemoteDataSource(从网络中获取的数据源)和 mTasksLocalDataSource(从本地SQLite获取的数据源)

这两个数据源都实现了TasksDataSource接口,其中TasksRepository也实现了该接口

  public interface TasksDataSource {
    interface LoadTasksCallback {
        void onTasksLoaded(List<Task> tasks);

        void onDataNotAvailable();
    }

    interface GetTaskCallback {
        void onTaskLoaded(Task task);

        void onDataNotAvailable();
    }

    void getTasks(@NonNull LoadTasksCallback callback);
    void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
    void saveTask(@NonNull Task task);
    void completeTask(@NonNull Task task);
    void completeTask(@NonNull String taskId);
    void activateTask(@NonNull Task task);
    void activateTask(@NonNull String taskId);
    void clearCompletedTasks();
    void refreshTasks();
    void deleteAllTasks();
    void deleteTask(@NonNull String taskId);
}

4. Presenter层

TasksPresenter实现TasksContract.Presenter接口,重写BasePresenter的start()方法。当收到View层的数据请求后,Presenter层控制Model层进行业务逻辑处理,Model层处理完毕后,把数据返回给Presnerer层,然后通知View层进行UI更新。

// TasksPresenter.java
    @Override
    public void start() {
        loadTasks(false);
    }
    private void loadTasks(boolean forceUpdate, final boolean showLoadingUI) {
        if (showLoadingUI) {
            mTasksView.setLoadingIndicator(true);
        }
        if (forceUpdate) {
            mTasksRepository.refreshTasks();
        }

        mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
            @Override
            public void onTasksLoaded(List<Task> tasks) {
                List<Task> tasksToShow = new ArrayList<Task>();

                // We filter the tasks based on the requestType
                for (Task task : tasks) {
                    switch (mCurrentFiltering) {
                        case ALL_TASKS:
                            tasksToShow.add(task);
                            break;
                        case ACTIVE_TASKS:
                            if (task.isActive()) {
                                tasksToShow.add(task);
                            }
                            break;
                        case COMPLETED_TASKS:
                            if (task.isCompleted()) {
                                tasksToShow.add(task);
                            }
                            break;
                        default:
                            tasksToShow.add(task);
                            break;
                    }
                }
                // The view may not be able to handle UI updates anymore
                if (!mTasksView.isActive()) {
                    return;
                }
                if (showLoadingUI) {
                    mTasksView.setLoadingIndicator(false);
                }

                processTasks(tasksToShow);
            }

            @Override
            public void onDataNotAvailable() {
                // The view may not be able to handle UI updates anymore
                if (!mTasksView.isActive()) {
                    return;
                }
                mTasksView.showLoadingTasksError();
            }
        });
    }

5. View层

负责View视图和Presenter的创建,并将二者关联起来;View层持有Presenter后,通过它进行一系列的操作

// TasksActivity.java
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tasks_act);

        // ...
        // 创建Presenter对象,将当前的View传给Presenter层
        mTasksPresenter = new TasksPresenter(
                Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
    }

// TasksPresenter.java
    // TasksPresenter的构造方法
    public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
        mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");

        // View与Presenter建立关联
        mTasksView.setPresenter(this);
    }

注意事项:

1.View只负责简单的视图更新、界面跳转的代码,只负责初始化自身,根据需要,给Presenter指派任务,然后进入“瞌睡”状态

因为相比起来,如果是Presenter太臃肿,可以根据功能不同,轻易拆分成两个甚至更多的Presenter。但是如果Activity/Fragment(View)太臃肿的话,可能就不好拆分了。

2.View和Presenter的交互,是用接口来实现的。

Activity/Fragment实现XxxContract.View接口,Presenter实现XxxContract .Presenter接口。

3.Presenter异步任务回来后,通知View更新UI之前,要先判断Activity是否为null,或者Fragment是否已经从activity中移除。

因为Presenter的生命周期,通常与Activity/Fragment是不相同的。所以Presenter在执行异步操作后,在结束的时候,都要判断View是否还处于正确的状态,在执行同步操作时不需要进行这种判断。

4.Model,作为数据源,要确保它的可复用性。

Model不应该持有Presenter的引用。

5.如果要在Presenter和Model里面使用Context,应该使用Application的Context。

尽量隔离、减少使用Android类的Presenter、Model层,会更便于我们写相关单元测试,Application的Context足够应付Presenter和Model里面的需求了。

image.png

6.Presenter、Model的构造方法,要使用依赖注入

VersionModel versionModel = new 
VersionModel(getApplicationContext());
mPresenter = new SplashPresenter(versionModel);

7.关于Presenter、Model的划分

Model是“数据处理”,Presenter是“根据业务逻辑,对Model获得的结果进一步处理”。Presenter、Model同样都会进行数据处理

其实MVP三层的关系可以说是层层递进的关系

image.png

三、总结

通过对todo-mvp分析,再次了解学习了MVP。从google提供的例子中可以看出,MVP的实现较为简单,model、view和presenter各个职责明确,便于扩展维护。contract契约类的出现,使得model和presenter结构更加清晰明了。Activity和Fragment的配合,使得Activity职能更为简化,同时View的实现更加灵活。