轉載請註明出處: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
中文解釋:github
固然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 呢?
有兩個緣由:
咱們先看 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層和業務層能夠分別進行單元測試;最後是可維護性和可擴展性,因爲架構的引入,雖然代碼量有了必定的上升,可是因爲界限很是清晰,各個類職責都很是明確且單一,後期的擴展,維護都會更加容易。