Android架构之路-三步实现MVP架构(上)

211 阅读8分钟
原文链接: mp.weixin.qq.com

本文不对MVC进行讲解,相信大家在项目中已经都用过。我直接开始介绍MVP。

1.初始MVP:

M:Model-模型:主要是实体模型,数据的存取与业务逻辑。V:View-视图:对应Activity(或Fragment),负责View的绘制以及用户交互。P: Presenter: 负责View与Model间交互,可以理解为媒介,就像媒婆那样的功能。

我用一张图来描述三者之间的关系。

优点:

1.降低耦合度,隐藏数据,使Activity(或Fragment)中代码更加简洁,只负责处理View的职责;2.模块职责分工明确;3.方便测试开发;4.代码复用性较高。

我们都知道要学一个新东西,我们都会先看官方说明,所以我讲带领大家一起解读Google官方MVP示例。

2.解读Google官方MVP

已完成的示例有:

  • todo-mvp :mvp基础架构。

  • todo-mvp-loaders :基于todo-mvp,获取数据使用Loaders。

  • todo-mvp-databinding:基于todo-mvp,使用数据绑定组件。仍进展中的示例有:

  • dev-todo-mvp-contentproviders:基于todo-mvp-loaders, 使用Content Providers。

  • dev-todo-mvp-clean:基于todo-mvp, 采用Clean架构的概念。

  • dev-todo-mvp-dagger:基于todo-mvp,使用Dagger2进行依赖注入。

对于采用哪种架构,取决于该项目的规模以及后期测试维护。

基于todo-mvp分析

该示例有四个界面(功能):

代码结构:按功能分包,包中又分为Activity、Fragment、Contract、Presenter四种类文件。

测试代码结构:androidTest(UI层测试)、androidTestMock(UI层测试mock数据支持)、test(业务层单元测试)、mock(业务层单元测试mock数据支持)

可以看到,该示例的app界面、功能代码结构,以及测试代码结构非常清晰。

源码分析

1、首先看两个Base接口基类,BaseView与BasePresenter,分别是所有View和Presenter的基类。

  1. public interface BaseView<T> {

  2.    // 规定View必须要实现setPresenter方法,则View中保持对Presenter的引用。

  3.    void setPresenter(T presenter);

  4. }

setPresenter的调用时机是presenter实现类的构造函数中,这样View中的事件请求通过调用presenter来实现。在这里大家特别要注意一个问题,那就是view持有presenter的强引用,处理不当,会导致内存泄漏。后面我们教大家处理这个问题。

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

该start的作用是Presenter开始获取数据并调用View的方法来刷新界面,其调用时机是在Activity(或Fragment)类的onResume方法中。

2、定义了契约类(接口)Google引入契约类,主要作用是用来统一管理view和present的接口,使得view和present中有哪些功能,一目了然,便于维护。我们将通过详情界面(功能)来分析:

  • TaskDetailContract中的View接口定义了该界面(功能)中所有的UI状态情况,TaskDetailFragment作为View层,实现了该接口,TaskDetailFragment 只关注UI相关的状态更新,所有事件操作都调用 TaskDetailPresenter 来完成。

  • Presenter 接口则定义了该界面(功能)中所有的用户操作事件,TaskDetailPresenter 作为Presenter层,实现了该接口,TaskDetailPresenter 则只关注业务层的逻辑相关,UI的更新只需调用View的状态方法。

3、Model层它的任务是用来获取数、存储数据以及数据状态变化。我们来看TasksRepository 中的getTask() 方法

@Override    public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {        // 判空处理        checkNotNull(taskId);        checkNotNull(callback);        // 获取缓存数据        Task cachedTask = getTaskWithId(taskId);        // Respond immediately with cache if available        if (cachedTask != null) {            callback.onTaskLoaded(cachedTask);            return;        }        // Load from server/persisted if needed.        // Is the task in the local data source? If not, query the network.        // 从本地数据源(SQLite数据库)中获取        mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {            @Override            public void onTaskLoaded(Task task) {                // 成功,则回调                callback.onTaskLoaded(task);            }            @Override            public void onDataNotAvailable() {                // 失败,则从远程数据源(网络)中获取                mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {                    @Override                    public void onTaskLoaded(Task task) {                        // 回调成功时的方法                        callback.onTaskLoaded(task);                    }                    @Override                    public void onDataNotAvailable() {                        // 回调失败时的方法                        callback.onDataNotAvailable();                    }                });            }        });    }

TasksRepository 维护了两个数据源,一个是远程(网络服务器),一个是本地(SQLite数据库)。

private final TasksDataSource mTasksRemoteDataSource; private final TasksDataSource mTasksLocalDataSource;

他们(包括TasksRepository类)都实现了 TasksDataSource 接口:

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

这样一来我们就很容易扩展新的数据源(获取数据的方式)。

public static TasksRepository provideTasksRepository(@NonNull Context context) {        checkNotNull(context);        return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),                TasksLocalDataSource.getInstance(context));    }

5、Presenter层

它继承上面定义的BasePresenter

@Override    public void start() {        openTask();    }    private void openTask() {        // 判空处理        if (null == mTaskId || mTaskId.isEmpty()) {            mTaskDetailView.showMissingTask();            return;        }        // 更新状态        mTaskDetailView.setLoadingIndicator(true);        // 获取该条Task数据        mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {            @Override            public void onTaskLoaded(Task task) {                // The view may not be able to handle UI updates anymore                // View已经被用户回退                if (!mTaskDetailView.isActive()) {                    return;                }                // 获取到task数据,并更新UI                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();            }        });    }

它接收到view数据请求,把该请求发送给Model,当Model请求后,把结果返回给presenter,presenter处理返回数据后,把它返回给view,最后view进行界面显示。这就是persenter的作用。

6、View层它负责创建view视图与presenter实例,并将二者关联起来。然后presenter的方法对数据进行请求与返回。

@Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ……        // Create the presenter        new TaskDetailPresenter(                taskId,                Injection.provideTasksRepository(getApplicationContext()),                taskDetailFragment);    }    public TaskDetailPresenter(@Nullable String taskId,                               @NonNull TasksRepository tasksRepository,                               @NonNull TaskDetailContract.View taskDetailView) {        this.mTaskId = taskId;        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");        // 保持对View(TaskDetailFragment)的引用        mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");        // 使View(TaskDetailFragment)也保持对自身(TaskDetailPresenter)的引用        mTaskDetailView.setPresenter(this);    }

在这里我们要特别注意一个问题,因为Presenter经常需要执行一些耗时操作,例如请求网络数据。而presenter持有了Activity(或Fragment)的强引用,如果在请求结束之前Activity(或Fragment)被销毁了,那么由于网络请求还没有返回,导致presenter一直持有它们对象,对象无法被回收,此时就发生了内存泄漏。后面我将带领大家一起架构MVP的时候,解决该问题。

总结

Fragment作为View,View和Presenter通过Activity来进行关联,Presenter对数据的调用是通过TasksRepository来完成的,而TasksRepository维护着它自己的数据源和实现。

用一张图来看它们的关系:

我们对Google官方示例解读完了,关键的事来了,我们要怎么架构一个属于自己的MVP。就让我手把手教大家三步架构MVP。持续关注下篇继续介绍。

参考:

http://www.jianshu.com/p/389c9ae1a82c

答谢粉丝免费送一次书籍:

哆啦投稿:https://www.jianshu.com/p/aa9fe307f915

作者:哆啦(谈恋爱的猴子)

最新粉丝互动请看:跳槽前给你的多重福利,让这个冬天不再冷

免费小密圈请点击:免费小密圈资格邀您加入

相关推荐

App组件化与业务拆分那些事

Android数据统计那点事

Android模块化专题(一)- 模块化概念和路由

Android中实现热补丁动态修复详析