轉載請注明出處: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 呢?
有兩個原因:
- 我們把 Activity 作為一個全局控制類來創建對象,把 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層和業務層可以分別進行單元測試;最后是可維護性和可擴展性,由於架構的引入,雖然代碼量有了一定的上升,但是由於界限非常清晰,各個類職責都非常明確且單一,后期的擴展,維護都會更加容易。