浅谈MVP

1,231 阅读6分钟

前言

MVP (Model View Presenter)架构是从MVC(Model View Controller)架构演变而来的。严格来说于Android应用开发本身就可以认为是一种MVC架构。一般来说我们会将XML文件视为MVC中的View角色,而将Activity则视为MVC中的Controller角色。不过大多数情况下Activity不仅仅是Controller,而是Controller和View的合体。我们一般会将业务逻辑代码和数据绑定代码都写在Activity中。这样在Activity中代码量就无法得到控制,少则成百上千,多的话也许无法估量。所以才慢慢有了MVP,将业务与数据分离开来。

MVP说明

MVP 简而言之就是在MVC的基础上将业务与数据剥离开来,以减少Activity臃肿的情况,同时增加了代码的可读性。

  • Model:对于Model层也是数据层,用来存储数据,并用于绑定View层,但实际上Model层与View层毫无关联
  • View: 对于View层也是视图层,在View层中只负责对数据的展示,提供界面与用户进行交互。通常将Activity或者Fragment作为View层或者说是xml中组成的可视化控件。
  • Presenter:对于Presenter也是控制层,起到了一个中间人的作用。一般来说将网络请求放在presenter中,将获取到的数据存储到Model层,通过回调将Model中的数据传递给View层,View层与Model通过Presenter层进行交互,使得View和Model之间不存在耦合,同时也将业务逻辑从View中抽离

通过上图可以发现,View层与Model层并不存在关联。在Presenter层中包含了一个Viewer,并且依赖于Model接口,从而将Model层与View层联系在一起。大多数人应该都知道接口回调这个概念,Presenter与View之间的交互就是通过接口的。

Google官方的MVP框架

项目介绍

项目地址: github.com/googlesampl…. 可以进入到项目中看看Google官方是如何介绍MVP的。

Stable samples

Sample Description

todo‑mvp

Demonstrates a basic Model‑View‑Presenter (MVP) architecture and provides a foundation on which the other samples are built. This sample also acts as a reference point for comparing and contrasting the other samples in this project.
todo‑mvp‑clean Uses concepts from Clean Architecture.
todo‑mvp‑dagger Uses Dagger 2 to add support for dependency injection.
todo‑mvp‑rxjava Uses RxJava 2 to implement concurrency, and abstract the data layer.
todo‑mvvm‑databinding Based on the todo-databinding sample, this version incorporates the Model‑View‑ViewModel pattern.
todo‑mvvm‑live Uses ViewModels and LiveData from Architecture Components and the Data Binding library with an MVVM architecture.

Samples in progress

Sample &nsp Description
dev‑todo‑mvvm‑rxjava Based on the todo-rxjava sample, this version incorporates the Model‑View‑ViewModel pattern.

从上述介绍中可以看到,官方给出的所有架构都是基于MVP架构,所以我们可以看看基础架构todo-mvp。

先看看项目结构

  在最外层很清晰的可以看到两个接口BasePresenter和BaseView。它们是Presenter层接口和View层接口的基类,项目中所有的Presenter接口和View层接口都继承自这两个接口。

MVP架构的实现

Model层的实现

public interface TasksDataSource {

    interface LoadTasksCallback {

        void onTasksLoaded(List<Task> tasks);

        void onDataNotAvailable();
    }
    ......
}

TasksDataSource实现了TasksLocalDataSource,在TasksDataSource中的方法也就是一些对数据操作。而在TasksDataSource的两个内部接口LoadTasksCallback和GetTaskCallback是Model层的回调接口。它们的真正实现是在Presenter层。对于成功获取到数据后变或通过这个回调接口将数据传递Presenter层。若是获取失败也会通过回调接口来通知Presenter层。

public class TasksLocalDataSource implements TasksDataSource {
    
    ......
 
  
    @Override
    public void getTasks(@NonNull final LoadTasksCallback callback) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                final List<Task> tasks = mTasksDao.getTasks();
                mAppExecutors.mainThread().execute(new Runnable() {
                    @Override
                    public void run() {
                        if (tasks.isEmpty()) {
                            // This will be called if the table is new or just empty.
                            callback.onDataNotAvailable();
                        } else {
                            callback.onTasksLoaded(tasks);
                        }
                    }
                });
            }
        };

        mAppExecutors.diskIO().execute(runnable);
    }

 
    @Override
    public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                final Task task = mTasksDao.getTaskById(taskId);

                mAppExecutors.mainThread().execute(new Runnable() {
                    @Override
                    public void run() {
                        if (task != null) {
                            callback.onTaskLoaded(task);
                        } else {
                            callback.onDataNotAvailable();
                        }
                    }
                });
            }
        };

        mAppExecutors.diskIO().execute(runnable);
    }

    @Override
    public void saveTask(@NonNull final Task task) {
        checkNotNull(task);
        Runnable saveRunnable = new Runnable() {
            @Override
            public void run() {
                mTasksDao.insertTask(task);
            }
        };
        mAppExecutors.diskIO().execute(saveRunnable);
    }
     ......
}

可以看到在TasksLocalDataSource中实现的getTask方法,在TasksDataSource内的GetTaskCallback回调接口。在Presenter层中实现回调,将数据传递到Presenter层。

Presenter与View层提供的接口

先来看一下Presenter与View交互息息相关的两个基类接口

BasePresenter

package com.example.android.architecture.blueprints.todoapp;

public interface BasePresenter {

    void start();

}

主要的作用是从Model层中获取数据。

BaseView

package com.example.android.architecture.blueprints.todoapp;

public interface BaseView<T> {

    void setPresenter(T presenter);

}

主要作用是对View层中的presenter进行初始化。

AddEditTaskContract

public interface AddEditTaskContract {

    interface View extends BaseView<Presenter> {

        void showEmptyTaskError();

        void showTasksList();

        void setTitle(String title);

        void setDescription(String description);

        boolean isActive();
    }

    interface Presenter extends BasePresenter {

        void createTask(String title, String description);

        void updateTask( String title, String description);

        void populateTask();
    }
}

从代码中就可以看到是对数据的一些增删改查,以及Task的保存更新。

Presenter层

public class TaskDetailPresenter implements TaskDetailContract.Presenter {

    private final TasksRepository mTasksRepository;

    private final TaskDetailContract.View mTaskDetailView;

    @Nullable
    private String mTaskId;

    public TaskDetailPresenter(@Nullable String taskId,
                               @NonNull TasksRepository tasksRepository,
                               @NonNull TaskDetailContract.View taskDetailView) {
        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() {
        if (Strings.isNullOrEmpty(mTaskId)) {
            mTaskDetailView.showMissingTask();
            return;
        }

        mTaskDetailView.setLoadingIndicator(true);
        mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
            @Override
            public void onTaskLoaded(Task task) {
                // The view may not be able to handle UI updates anymore
                if (!mTaskDetailView.isActive()) {
                    return;
                }
                mTaskDetailView.setLoadingIndicator(false);
                if (null == task) {
                    mTaskDetailView.showMissingTask();
                } else {
                    showTask(task);
                }
            }

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

    @Override
    public void editTask() {
        if (Strings.isNullOrEmpty(mTaskId)) {
            mTaskDetailView.showMissingTask();
            return;
        }
        mTaskDetailView.showEditTask(mTaskId);
    }
    
    ......
}

从上述代码可以看到数据的获取,代码的回调,都可以放到这一层中,在这一层中可以支撑业务逻辑的实现。

View层

public class TaskDetailActivity extends AppCompatActivity {

    public static final String EXTRA_TASK_ID = "TASK_ID";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.taskdetail_act);

        // Set up the toolbar.
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar ab = getSupportActionBar();
        ab.setDisplayHomeAsUpEnabled(true);
        ab.setDisplayShowHomeEnabled(true);

        // Get the requested task id
        String taskId = getIntent().getStringExtra(EXTRA_TASK_ID);

        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);
    }

    @Override
    public boolean onSupportNavigateUp() {
        onBackPressed();
        return true;
    }
}

View层可能是大家最为熟悉的了,就不多说了。

自己的MVP

接下来粗略的介绍一下我在项目中使用的MVP模式。 首先肯定是基础类了

public interface Viewer {
    Activity getActivity();
    
}

Viewer写到接口主要是连接Presenter与Activity的主要,在Presenter中存入数据,在Activity中调用数据。当然这个是基类,你要编辑业务Viewer去继承它。


public abstract class BasePresenter<T extends Viewer> implements Presenter {
    protected T viewer;
    
    public BasePresenter(T viewer) {
        this.setViewer(viewer);
    }
    
    public Activity getActivity() {
        Activity activity = this.viewer == null ? null : this.viewer.getActivity();
        return activity != null && !activity.isFinishing() ? activity : null;
    }

    public void _willDestroy() {
        if (this.mPresenterHandler != null) {
            this.mPresenterHandler.removeCallbacksAndMessages((Object)null);
        }

    }
}

BasePresenter主要写的就是网络请求,以及一些其他的操作,然后将数据存储到Viewer中。

public class **Activity extends BaseActivity implements **Viewer {
    ............
}

在Activity中实现Viewer,实现其中自己定义的各种方法,其实就是数据的获取操作,然后绑定View,实现可视化控件,给予用户良好的体验。

总结

通过MVP框架可以看出每个层级之间的分割是非常清晰的,也很大程度上降低了代码的耦合度。虽然看起来每次都要新建很多个类,有些人会觉得开发都忙不过来了,哪里还有时间去搞这个,但是中国不是有句古话“磨刀不误砍柴工”,所以还是要利用好MVP模式。 当然还有一些其他的MVP,比如跟dagger2结合的,比如跟rxjava结合的,比如跟databinding结合的,看个人的选择吧。 以上只是我个人的粗浅之见。