Google官方MVP模式樣本項目解析 todo-mvp,mvptodo-mvp

來源:互聯網
上載者:User

Google官方MVP模式樣本項目解析 todo-mvp,mvptodo-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 呢?

有兩個原因:

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.