Google官方MVP模式示例项目解析 todo-mvp

转载请注明出处:http://www.cnblogs.com/cnwutianhao/p/6700668.html html

 

引言:在Google没有给出一套权威的架构实现以前,不少App项目在架构方面都有或多或少的问题。第一种常见问题是没有架构,需求中的一个页面对应项目中的一个activity或一个fragment,全部的界面响应代码、业务逻辑代码、数据请求代码等等都集中在其中。第二种常见的问题是架构实现的不断变化,不断在各类架构间摇摆,一直找不到一个适合本身的架构。java

Google官方示例项目地址 https://github.com/googlesamples/android-architecture/tree/todo-mvp/android

Google提供这个示例项目有两个目的:git

  • Provide a basic Model-View-Presenter (MVP) architecture without using any architectural frameworks.
  • Act as a reference point for comparing and contrasting the other samples in this project.

中文解释:github

  • 提供了一个基础的MVP架构,而不是用其余的架构。
  • 用这个项目和其余相似的作一个参考对比。

 

固然Google也明确表示了这些示例只是用来作参考,而并非要为了当作标准缓存

 

下面咱们从源码的角度来分析todo-mvp(mvp基础架构示例)的实现。咱们先从项目的总体组织方式开始,再看项目究竟使用了哪些组件,最后固然是最重要的具体mvp的实现方式。安全

先看一下项目代码组织方式:架构

项目含一个app src目录,4个测试目录,分别是androidTest(UI层测试)、androidTestMock(UI层测试mock数据支持)、test(业务层单元测试)、mock(业务层单元测试mock数据支持)。并发

src目录的代码组织方式彻底是按照功能来组织的,功能内部分为xActivity、xContract、xFragment、xPresenter四个类文件(x表明业务名称)。app

 

组件使用

因为项目是基于gradle进行编译的,因此咱们能够从build.gradle文件看到项目依赖的全貌。

dependencies {
    // App's dependencies, including test
    compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
    compile "com.android.support:cardview-v7:$rootProject.supportLibraryVersion"
    compile "com.android.support:design:$rootProject.supportLibraryVersion"
    compile "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion"
    compile "com.android.support:support-v4:$rootProject.supportLibraryVersion"
    compile "com.android.support.test.espresso:espresso-idling-resource:$rootProject.espressoVersion"
    compile "com.google.guava:guava:$rootProject.guavaVersion"

    // Dependencies for local unit tests
    testCompile "junit:junit:$rootProject.ext.junitVersion"
    testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
    testCompile "org.hamcrest:hamcrest-all:$rootProject.ext.hamcrestVersion"

    // Android Testing Support Library's runner and rules
    androidTestCompile "com.android.support.test:runner:$rootProject.ext.runnerVersion"
    androidTestCompile "com.android.support.test:rules:$rootProject.ext.runnerVersion"

    // Dependencies for Android unit tests
    androidTestCompile "junit:junit:$rootProject.ext.junitVersion"
    androidTestCompile "org.mockito:mockito-core:$rootProject.ext.mockitoVersion"
    androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
    androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'

    // Espresso UI Testing
    androidTestCompile "com.android.support.test.espresso:espresso-core:$rootProject.espressoVersion"
    androidTestCompile "com.android.support.test.espresso:espresso-contrib:$rootProject.espressoVersion"
    androidTestCompile "com.android.support.test.espresso:espresso-intents:$rootProject.espressoVersion"

    // Resolve conflicts between main and test APK:
    androidTestCompile "com.android.support:support-annotations:$rootProject.supportLibraryVersion"
    androidTestCompile "com.android.support:support-v4:$rootProject.supportLibraryVersion"
    androidTestCompile "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion"
    androidTestCompile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
    androidTestCompile "com.android.support:design:$rootProject.supportLibraryVersion"
}

项目中使用到了Guava库,官网地址 https://github.com/google/guava

该库是Google在基于java的项目中都会引用到得一个库,库中包含大约14k的方法数,是个很大的库,其中包含了集合、缓存、并发、基本注解、字符串处理、io处理等等。项目中使用Guava库主要是处理null这种不安全的状况,由于通常咱们在使用有可能为null的对象时,通常会增长一次判断。好比项目中的出现的:

public boolean isEmpty() {
        return Strings.isNullOrEmpty(mTitle) &&
               Strings.isNullOrEmpty(mDescription);
    }

这样面对空的时候,就不用再多写不少代码了,确实是方便了不少。可是不建议为了null安全直接引入如此大的一个库,由于咱们都知道android apk的65k方法数限制,若是要用的话能够把源码中涉及到得部分直接拿出来用。固然Guava中还有不少重要的功能,其余功能读者能够自行研究,关于Guava就先到这里了。

 

测试相关组件

示例项目在可测试方面作的很是好,因为对视图逻辑(view层)和业务逻辑(presenter层)进行了拆分,因此咱们就能够对UI、业务代码分别进行测试。为了进行UI测试引入了Espresso,为了对业务层进行单元测试引入了junit,为了生成测试mock对象引入了mockito,为了支撑mockito又引入了dexmaker,hamcrest的引入使得测试代码的匹配更接近天然语言,可读性更高,更加灵活。

 

重头戏:项目MVP实现方式

1.基类

两个Base接口 BasePresenter 和 BaseView,这两个类分别是 presenter 和 view 的基类。

public interface BasePresenter {

    void start();

}

BasePresenter 中含有方法 start(),该方法的做用是 presenter 开始获取数据并调用 view 中方法改变界面显示,其调用时机是在 Fragment 类的 onResume 方法中。

项目中调用 start() 的地方:

 

public interface BaseView<T> {

    void setPresenter(T presenter);

}

BaseView 中含方法 setPresenter(),该方法做用是在将 presenter 实例传入 view 中,其调用时机是 presenter 实现类的构造函数中。

项目中调用 setPresenter() 的地方:

 

2.契约类

与以前见到的全部mvp实现都不一样,Google官方的实现中加入了契约类来统一管理view与presenter的全部的接口,这种方式使得view与presenter中有哪些功能,一目了然,维护起来也方便,实例以下:

public interface TasksContract {

    interface View extends BaseView<Presenter> {

        void setLoadingIndicator(boolean active);

        void showTasks(List<Task> tasks);

        void showAddTask();

        ...
    }

    interface Presenter extends BasePresenter {

        void result(int requestCode, int resultCode);

        void loadTasks(boolean forceUpdate);

        void addNewTask();

        ...
    }
}

 

3.Activity在MVP中的做用

Activity 在项目中是一个全局的控制者,负责建立 view 以及 presenter 实例,并将两者联系起来,下面是 Activity 中建立 view 及 presenter 的代码:

TasksFragment tasksFragment =
                (TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
        if (tasksFragment == null) {
            // Create the fragment
            tasksFragment = TasksFragment.newInstance();
            ActivityUtils.addFragmentToActivity(
                    getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
        }

        // Create the presenter
        mTasksPresenter = new TasksPresenter(
                Injection.provideTasksRepository(getApplicationContext()), tasksFragment);

咱们能够从上面看到整个建立过程,并且要注意的是建立后的 Fragment 实例做为 presenter 的构造函数参数被传入,这样就能够在 presenter 中调用 view 中的方法了。

 

4.MVP的实现与组织

实例中将 Fragment 做为 view 层的实现类,为何是 Fragment 呢?

有两个缘由:

  1. 咱们把 Activity 做为一个全局控制类来建立对象,把 Fragment 做为 view,这样二者就能各司其职。
  2. 由于 Fragment 比较灵活,可以方便的处理界面适配的问题。

咱们先看 view 的实现,咱们只挑一部分重要的方法来看

public class TasksFragment extends Fragment implements TasksContract.View {
    
    ...
    
    @Override
    public void onResume() {
        super.onResume();
        mPresenter.start();
    }

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

上面能够看到 setPresenter() 方法,该方法继承于父类,经过该方法,view 得到了 presenter 得实例,从而能够调用 presenter 代码来处理业务逻辑。咱们看到在 onResume 中还调用了 presenter 得 start() 方法。

下面咱们再看presenter的实现

public class TasksPresenter implements TasksContract.Presenter {

    ...

    public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
        mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");

        mTasksView.setPresenter(this);
    }

    @Override
    public void start() {
        loadTasks(false);
    }
    
    ...

}

presenter 构造函数中调用了 view 的 setPresenter() 方法将自身实例传入,start() 方法中处理了数据加载与展现。若是须要界面作对应的变化,直接调用 view 层的方法便可,这样 view 层与 presenter 层就可以很好的被划分。

 

最后还剩下 model 层实现,项目中 model 层最大的特色是被赋予了数据获取的职责,与咱们日常 model 层只定义实体对象大相径庭,实例中,数据的获取、存储、数据状态变化都是 model 层的任务,presenter 会根据须要调用该层的数据处理逻辑并在须要时将回调传入。这样 model、presenter、view 都只处理各自的任务,此种实现确实是单一职责最好的诠释。

 

5.总结:

咱们再来总体看下官方的实现方式有哪些特性。首先是复杂度,咱们能够从上面的分析看出总体的复杂度仍是较低的,易学的;而后是可测试性,因为将UI代码与业务代码进行了拆分,总体的可测试性很是的好,UI层和业务层能够分别进行单元测试;最后是可维护性和可扩展性,因为架构的引入,虽然代码量有了必定的上升,可是因为界限很是清晰,各个类职责都很是明确且单一,后期的扩展,维护都会更加容易。

 

关注个人新浪微博,获取更多Android开发资讯!
关注科技评论家,领略科技、创新、教育以及最大化人类智慧与想象力!

相关文章
相关标签/搜索