博客主页

解读Google官方MVP

  1. todo-mvp:mvp基础架构
  2. deprecated-todo-mvp-loaders:基于todo-mvp,获取数据使用Loaders,已过时
  3. deprecated-todo-mvp-contentproviders:基于todo-mvp-loaders, 使用Content Providers,已过时
  4. deprecated-todo-databinding:基于todo-mvp,使用数据绑定组件,已过时
  5. todo-mvp-clean:基于todo-mvp, 采用Clean架构的概念
  6. todo-mvp-dagger:基于todo-mvp,使用Dagger2进行依赖注入
  7. todo-mvp-rxjava:基于todo-mvp,使用rxjava
  8. todo-mvp-kotlin:基于todo-mvp,使用kotlin
官方网址:https://github.com/android/ar...

1. 基于todo-mvp分析

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

代码结构:按照功能分包,Activity、Fragment、Contract、Presenter四种类文件
测试代码结构:androidTest(UI层测试)、androidTestMock(UI层测试mock数据支持)、test(业务层单元测试)、mock(业务层单元测试mock数据支持)

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方法

    /**
     * Gets tasks from cache, local data source (SQLite) or remote data source, whichever is available first.
     */
    @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); 
                }
            });
        }
    }


 private void getTasksFromRemoteDataSource(@NonNull final LoadTasksCallback callback) {
        mTasksRemoteDataSource.getTasks(new LoadTasksCallback() {
            @Override
            public void onTasksLoaded(List<Task> tasks) {
                refreshCache(tasks); // 更新内存中缓存数据
                refreshLocalDataSource(tasks); // 更新SQLite中数据
                callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values())); // 成功回调数据
            }

            @Override
            public void onDataNotAvailable() {
                callback.onDataNotAvailable(); // 没有可用的数据
            }
        });
    }

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

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

这两个数据源都实现了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);
    }

总结

Fragment作为View层,View和Presenter通过Activity建立关联,Presenter对数据的调用通过TasksRepository完成,而TasksRepository维护着自己的数据源和实现。

依赖

1、本地数据库SQLite使用Room框架:

https://developer.android.goo...

2、Android中guava的核心类库com.google.guava:guava:28.1-android

https://github.com/google/guava

3、mockito:A mocking framework used to implement unit tests.

https://site.mockito.org/

4、Android Testing Support Library: A framework used to support UI tests, using both Espresso, and AndroidJUnitRunner.

https://developer.android.goo...

5、Common Android support libraries:Packages in the com.android.support.* namespace provide backwards compatibility and other features.

https://developer.android.goo...

开源MVP框架

1. TheMVP

https://github.com/kymjs/TheMVP
用MVP架构开发Android应用:https://kymjs.com/code/2015/1...

TheMVP使用Activity作为Presenter层来处理代码逻辑,通过让Activity包含一个ViewDelegate对象来间接操作View层对外提供方法,从而做到完全解耦视图层。

2. MVPro

https://github.com/qibin0506/...

都是将Activity和Fragment作为Presenter。Presenter即我们的Activity或者Fragment,View呢?说白了就是我们从Activity和Fragment中提取出来的和View操作相关的代码

3. nucleus

https://github.com/konmik/nuc...

4. Beam

https://github.com/Jude95/Beam

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)


小兵兵同学
56 声望23 粉丝

Android技术分享平台,每个工作日都有优质技术文章分享。从技术角度,分享生活工作的点滴。