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開發資訊!
關注科技評論家,領略科技、創新、教育以及最大化人類智慧與想象力!

相關文章
相關標籤/搜索