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


轉載請注明出處:http://www.cnblogs.com/cnwutianhao/p/6700668.html 

 

引言:在Google沒有給出一套權威的架構實現之前,很多App項目在架構方面都有或多或少的問題。第一種常見問題是沒有架構,需求中的一個頁面對應項目中的一個activity或一個fragment,所有的界面響應代碼、業務邏輯代碼、數據請求代碼等等都集中在其中。第二種常見的問題是架構實現的不斷變化,不斷在各種架構間搖擺,一直找不到一個適合自己的架構。

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

Google提供這個示例項目有兩個目的:

  • 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.

中文解釋:

  • 提供了一個基礎的MVP架構,而不是用其他的架構。
  • 用這個項目和其他類似的做一個參考對比。

 

當然Google也明確表示了這些示例只是用來做參考,而並不是要為了當做標准

 

下面我們從源碼的角度來分析todo-mvp(mvp基礎架構示例)的實現。我們先從項目的整體組織方式開始,再看項目究竟使用了哪些組件,最后當然是最重要的具體mvp的實現方式。

先看一下項目代碼組織方式:

項目含一個app src目錄,4個測試目錄,分別是androidTest(UI層測試)、androidTestMock(UI層測試mock數據支持)、test(業務層單元測試)、mock(業務層單元測試mock數據支持)。

src目錄的代碼組織方式完全是按照功能來組織的,功能內部分為xActivity、xContract、xFragment、xPresenter四個類文件(x代表業務名稱)。

 

組件使用

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM