- todo-mvp: 基礎的MVP架構。
- todo-mvp-loaders:基於MVP架構的實現,在擷取資料的部分採用了loaders架構。
- todo-mvp-databinding: 基於MVP架構的實現,採用了資料繫結組件。
- todo-mvp-clean: 基於MVP架構的clean架構的實現。
- todo-mvp-dagger2: 基於MVP架構,採用了依賴注入dagger2。
- dev-todo-mvp-contentproviders: 基於mvp-loaders架構,使用了ContenPproviders。
dev-todo-mvp-rxjava: 基於MVP架構,對於程式的並發處理和資料層(MVP中的Model)的抽象。
從上述的介紹中可以看出,對於官方給出所有的架構的實現最終都是基於MVP架構。所以在這裡就對上面的基礎的MVP架構todo-mvp進行分析。
項目結構的分析 對於這個項目,它實現的是一個備忘錄的功能。對於工作中未完成的任務添加到待辦工作清單中。我們能夠在列表中可以對已完成的任務做出標記,能夠進入任務詳細頁面修改任務內容,也能夠對已完成的任務和未完成的任務數量做出統計。
首先在這裡來看一下todo-mvp整體的項目結構
從中可以看出,項目整體包含了一個app src目錄,四個測試目錄。在src目錄下面對代碼的組織方式是按照功能進行劃分。在這個項目中包含了四個功能,它們分別是:任務的添加編輯(addedittask),任務完成情況的統計(statistics),任務的詳情(taskdetail),工作清單的顯示(tasks)。對於data包它是項目中的資料來源,執行資料庫的讀寫,網路的請求操作都存放在該包內,也是MVP架構中的Model層。而util包下面則是存放一些項目中使用到的工具類。在最外層存放了兩介面BasePresenter和BaseView。它們是Presenter層介面和View層介面的基類,項目中所有的Presenter介面和View層介面都繼承自這兩個介面。
現在進入功能模組內看下在模組內部對類是如何劃分的。在每個功能模組下面將類分作xxActivity,xxFragment,xxPresenter,xxContract。也正是這些類構成了項目中的Presenter層與View層。下面就來分析在這個項目中是如何?MVP架構。
MVP架構的實現 在這裡只從宏觀上關注MVP架構的實現,對於代碼的內部細節在就不在具體分析。那麼就以任務的添加和編輯這個功能來看一下Android官方是如何?MVP架構。
Model層的實現 首先我們從MVP架構的最內層開始分析,也就是對應的Model層。在這個項目中對應的data包下的內容。在data下對資料庫等一些資料來源的封裝。對於Presenter層提供了TasksDataSource介面。在這裡看一下這個TasksDataSource介面。
public interface TasksDataSource { interface LoadTasksCallback { void onTasksLoaded(List<Task> tasks); void onDataNotAvailable(); } interface GetTaskCallback { void onTaskLoaded(Task task); void onDataNotAvailable(); } void getTasks(@NonNull LoadTasksCallback callback); void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback); void saveTask(@NonNull Task task); ......}
TasksDataSource介面的實現是TasksLocalDataSource,在TasksDataSource中的方法也就是一些對資料庫的增刪改查的操作。而在TasksDataSource的兩個內部介面LoadTasksCallback和GetTaskCallback是Model層的回調介面。它們的真正實現是在Presenter層。對於成功擷取到資料後變或通過這個回調介面將資料傳遞Presenter層。同樣,若是擷取失敗同樣也會通過回調介面來通知Presenter層。下面來看一下TasksDataSource的實作類別。
public class TasksLocalDataSource implements TasksDataSource { ...... @Override public void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback) { //根據taskId查訓出相對應的task ...... if (task != null) { callback.onTaskLoaded(task); } else { callback.onDataNotAvailable(); } } @Override public void saveTask(@NonNull Task task) { checkNotNull(task); SQLiteDatabase db = mDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(TaskEntry.COLUMN_NAME_ENTRY_ID, task.getId()); values.put(TaskEntry.COLUMN_NAME_TITLE, task.getTitle()); values.put(TaskEntry.COLUMN_NAME_DESCRIPTION, task.getDescription()); values.put(TaskEntry.COLUMN_NAME_COMPLETED, task.isCompleted()); db.insert(TaskEntry.TABLE_NAME, null, values); db.close(); } ......}
在這裡我們針對任務的添加和編輯功能,所以省略很多代碼。可以看出在TasksLocalDataSource中實現的getTask方法,在這個方法中傳入TasksDataSource內的GetTaskCallback回調介面。在getTask方法的實現可以看出在查詢到Task以後調用回調方法,若是在Presenter層中實現了這兩個回調方法,便將資料傳遞到Presenter層。而對於查詢到的Task為空白的時候也是通過回調方法執行對應的操作。
同樣對於通過網路請求擷取到資料也是一樣,對於成功請求到的資料可以通過回調方法將資料傳遞到Presenter層,對於網路請求失敗也能夠通過回調方法來執行相對應的操作。
Presenter與View層提供的介面 由於在Presenter和View層所提供的介面在一個類中,在這裡就先來查看他們對外所提供了哪些介面。首先觀察一下兩個基類介面BasePresenter和BaseView。
BasePresenter
package com.example.android.architecture.blueprints.todoapp;public interface BasePresenter { void start();}
在BasePresenter中只存在一個start方法。這個方法一般所執行的任務是在Presenter中從Model層擷取資料,並調用View介面顯示。這個方法一般是在Fragment中的onResume方法中調用。
BaseView
package com.example.android.architecture.blueprints.todoapp;public interface BaseView<T> { void setPresenter(T presenter);}
在BaseView中只有一個setPresenter方法,對於View層會存在一個Presenter對象。而setPresenter正是對View中的Presenter進行初始化。
AddEditTaskContract
在Android官方給出的MVP架構當中對於Presenter介面和View介面提供的形式與我們平時在網上所見的有所不同。在這裡將Presenter中的介面和View的介面都放在了AddEditTaskContract類裡面。這樣一來我們能夠更清晰的看到在Presenter層和View層中有哪些功能,方便我們以後的維護。下面就來看一下這個AddEditTaskContract類。
public interface AddEditTaskContract { interface View extends BaseView<Presenter> { void showEmptyTaskError(); void showTasksList(); void setTitle(String title); void setDescription(String description); boolean isActive(); } interface Presenter extends BasePresenter { void createTask(String title, String description); void updateTask( String title, String description); void populateTask(); }}
在這裡很清晰的可以看出在View層中處理了一些資料顯示的操作,而在Presenter層中則是對Task儲存,更新等操作。
Presenter層的實現 下面就來看一下在Presenter是如何?的。
public class AddEditTaskPresenter implements AddEditTaskContract.Presenter, TasksDataSource.GetTaskCallback { ...... public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository, @NonNull AddEditTaskContract.View addTaskView) { mTaskId = taskId; mTasksRepository = checkNotNull(tasksRepository); mAddTaskView = checkNotNull(addTaskView); mAddTaskView.setPresenter(this); } @Override public void start() { if (mTaskId != null) { populateTask(); } } ...... @Override public void populateTask() { if (mTaskId == null) { throw new RuntimeException("populateTask() was called but task is new."); } mTasksRepository.getTask(mTaskId, this); } @Override public void onTaskLoaded(Task task) { // The view may not be able to handle UI updates anymore if (mAddTaskView.isActive()) { mAddTaskView.setTitle(task.getTitle()); mAddTaskView.setDescription(task.getDescription()); } } ......}
在這裡可以看到在AddEditTaskPresenter中它不僅實現了自己的Presenter介面,也實現了GetTaskCallback的回調介面。並且在Presenter中包含了Model層TasksDataSource的對象mTasksRepository和View層AddEditTaskContract.View的對象mAddTaskView。於是整個商務邏輯的處理就擔負在Presenter的身上。
從Presenter的業務處理中可以看出,首先調用Model層的介面getTask方法,通過TaskId來查詢Task。在查詢到Task以後,由於在Presenter層中實現了Model層的回調介面GetTaskCallback。這時候在Presenter層中就通過onTaskLoaded方法擷取到Task對象,最後通過調用View層介面實現了資料的展示。
View層的實現 對於View的實現是在Fragment中,而在Activity中則是完成對Fragment的添加,Presenter的建立操作。下面首先來看一下AddEditTaskActivity類。
public class AddEditTaskActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.addtask_act); ...... if (addEditTaskFragment == null) { addEditTaskFragment = AddEditTaskFragment.newInstance(); ...... ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), addEditTaskFragment, R.id.contentFrame); } // Create the presenter new AddEditTaskPresenter( taskId, Injection.provideTasksRepository(getApplicationContext()), addEditTaskFragment); } ......}
對於Activity的提供的功能也是非常的簡單,首先建立Fragment對象並將其添加到Activity當中。之後建立Presenter對象,並將Fragment也就是View傳遞到Presenter中。
下面再來看一下View的實現,也就是Fragment。
public class AddEditTaskFragment extends Fragment implements AddEditTaskContract.View { ...... @Override public void onResume() { super.onResume(); mPresenter.start(); } @Override public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) { mPresenter = checkNotNull(presenter); } ...... @Override public void setTitle(String title) { mTitle.setText(title); } ......}
在這對於源碼就不在過多貼出。在Fragment中,通過setPresenter擷取到Presenter對象。並通過調用Presenter中的方法來實現業務的處理。而在Fragment中則只是對UI的一些操作。這樣一來對於Fragment類型的代碼減少了很多,並且邏輯更加清晰。
我們注意到View層的實現是通過Fragment來完成的。對於View的實現為什麼要採用Fragment而不是Activity。來看一下官方是如何解釋的。
The separation between Activity and Fragment fits nicely with this implementation of MVP: the Activity is the overall controller that creates and connects views and presenters.Tablet layout or screens with multiple views take advantage of the Fragments framework.
在這裡官方對於採用Fragment的原因給出了兩種解釋。
- 通過Activity和Fragment分離非常適合對於MVP架構的實現。在這裡將Activity作為全域的控制者將Presenter於View聯絡在一起。
- 採用Fragment更有利於平板電腦的布局或者是多視圖螢幕。
總結 通過MVP架構的使用可以看出對於各個層次之間的職責更加單一清晰,同時也很大程度上降低了代碼的耦合度。對於官方MVP架構樣本,google也明確表明對於他們所給出的這些架構樣本只是作為參考,而不是一個標準。所以對於基礎的MVP架構有更大的擴充空間。例如綜合google給出的樣本。我們可以通過在MVP架構的基礎上使用dagger2,rxJava等來構建一個Clean架構。也是一個很好的選擇。