淺談Android官方MVP架構(gòu)解讀
綜述
對于MVP (Model View Presenter)架構(gòu)是從著名的MVC(Model View Controller)架構(gòu)演變而來的。而對于Android應(yīng)用的開發(fā)中本身可視為一種MVC架構(gòu)。通常在開發(fā)中將XML文件視為MVC中的View角色,而將Activity則視為MVC中的Controller角色。不過更多情況下在實(shí)際應(yīng)用開發(fā)中Activity不能夠完全充當(dāng)Controller,而是Controller和View的合體。于是Activity既要負(fù)責(zé)視圖的顯示,又要負(fù)責(zé)對業(yè)務(wù)邏輯的處理。這樣在Activity中代碼達(dá)到上千行,甚至幾千行都不足為其,同時(shí)這樣的Activity也顯得臃腫不堪。所以對于MVC架構(gòu)并不很合適運(yùn)用于Android的開發(fā)中。下面就來介紹一下MVP架構(gòu)以及看一下google官方給出的MVP架構(gòu)示例。
MVP架構(gòu)簡介
對于一個(gè)應(yīng)用而言我們需要對它抽象出各個(gè)層面,而在MVP架構(gòu)中它將UI界面和數(shù)據(jù)進(jìn)行隔離,所以我們的應(yīng)用也就分為三個(gè)層次。
- View: 對于View層也是視圖層,在View層中只負(fù)責(zé)對數(shù)據(jù)的展示,提供友好的界面與用戶進(jìn)行交互。在Android開發(fā)中通常將Activity或者Fragment作為View層。
- Model: 對于Model層也是數(shù)據(jù)層。它區(qū)別于MVC架構(gòu)中的Model,在這里不僅僅只是數(shù)據(jù)模型。在MVP架構(gòu)中Model它負(fù)責(zé)對數(shù)據(jù)的存取操作,例如對數(shù)據(jù)庫的讀寫,網(wǎng)絡(luò)的數(shù)據(jù)的請求等。
- Presenter:對于Presenter層他是連接View層與Model層的橋梁并對業(yè)務(wù)邏輯進(jìn)行處理。在MVP架構(gòu)中Model與View無法直接進(jìn)行交互。所以在Presenter層它會(huì)從Model層獲得所需要的數(shù)據(jù),進(jìn)行一些適當(dāng)?shù)奶幚砗蠼挥蒝iew層進(jìn)行顯示。這樣通過Presenter將View與Model進(jìn)行隔離,使得View和Model之間不存在耦合,同時(shí)也將業(yè)務(wù)邏輯從View中抽離。
下面通過MVP結(jié)構(gòu)圖來看一下MVP中各個(gè)層次之間的關(guān)系。
在MVP架構(gòu)中將這三層分別抽象到各自的接口當(dāng)中。通過接口將層次之間進(jìn)行隔離,而Presenter對View和Model的相互依賴也是依賴于各自的接口。這點(diǎn)符合了接口隔離原則,也正是面向接口編程。在Presenter層中包含了一個(gè)View接口,并且依賴于Model接口,從而將Model層與View層聯(lián)系在一起。而對于View層會(huì)持有一個(gè)Presenter成員變量并且只保留對Presenter接口的調(diào)用,具體業(yè)務(wù)邏輯全部交由Presenter接口實(shí)現(xiàn)類中處理。
官方MVP架構(gòu)分析
項(xiàng)目介紹
對于MVP架構(gòu)有了一些的了解,而在前端時(shí)間Google給出了一些App開發(fā)架構(gòu)的實(shí)現(xiàn)。
項(xiàng)目地址為:https://github.com/googlesamples/android-architecture.
在這里進(jìn)入README看一下這次Google給出那些Android開發(fā)架構(gòu)的實(shí)現(xiàn)。
對于上面五個(gè)開發(fā)架構(gòu)的實(shí)現(xiàn)表示到目前為止是已經(jīng)完成的項(xiàng)目,而下面兩個(gè)則表示正在進(jìn)行的中的項(xiàng)目。現(xiàn)在首先來介紹一下這幾個(gè)架構(gòu)。
- todo-mvp: 基礎(chǔ)的MVP架構(gòu)。
- todo-mvp-loaders:基于MVP架構(gòu)的實(shí)現(xiàn),在獲取數(shù)據(jù)的部分采用了loaders架構(gòu)。
- todo-mvp-databinding: 基于MVP架構(gòu)的實(shí)現(xiàn),采用了數(shù)據(jù)綁定組件。
- todo-mvp-clean: 基于MVP架構(gòu)的clean架構(gòu)的實(shí)現(xiàn)。
- todo-mvp-dagger2: 基于MVP架構(gòu),采用了依賴注入dagger2。
- dev-todo-mvp-contentproviders: 基于mvp-loaders架構(gòu),使用了ContenPproviders。
- dev-todo-mvp-rxjava: 基于MVP架構(gòu),對于程序的并發(fā)處理和數(shù)據(jù)層(MVP中的Model)的抽象。
從上述的介紹中可以看出,對于官方給出所有的架構(gòu)的實(shí)現(xiàn)最終都是基于MVP架構(gòu)。所以在這里就對上面的基礎(chǔ)的MVP架構(gòu)todo-mvp進(jìn)行分析。
項(xiàng)目結(jié)構(gòu)的分析
對于這個(gè)項(xiàng)目,它實(shí)現(xiàn)的是一個(gè)備忘錄的功能。對于工作中未完成的任務(wù)添加到待辦任務(wù)列表中。我們能夠在列表中可以對已完成的任務(wù)做出標(biāo)記,能夠進(jìn)入任務(wù)詳細(xì)頁面修改任務(wù)內(nèi)容,也能夠?qū)σ淹瓿傻娜蝿?wù)和未完成的任務(wù)數(shù)量做出統(tǒng)計(jì)。
首先在這里來看一下todo-mvp整體的項(xiàng)目結(jié)構(gòu)
從上圖中可以看出,項(xiàng)目整體包含了一個(gè)app src目錄,四個(gè)測試目錄。在src目錄下面對代碼的組織方式是按照功能進(jìn)行劃分。在這個(gè)項(xiàng)目中包含了四個(gè)功能,它們分別是:任務(wù)的添加編輯(addedittask),任務(wù)完成情況的統(tǒng)計(jì)(statistics),任務(wù)的詳情(taskdetail),任務(wù)列表的顯示(tasks)。對于data包它是項(xiàng)目中的數(shù)據(jù)源,執(zhí)行數(shù)據(jù)庫的讀寫,網(wǎng)絡(luò)的請求操作都存放在該包內(nèi),也是MVP架構(gòu)中的Model層。而util包下面則是存放一些項(xiàng)目中使用到的工具類。在最外層存放了兩接口BasePresenter和BaseView。它們是Presenter層接口和View層接口的基類,項(xiàng)目中所有的Presenter接口和View層接口都繼承自這兩個(gè)接口。
現(xiàn)在進(jìn)入功能模塊內(nèi)看下在模塊內(nèi)部對類是如何劃分的。在每個(gè)功能模塊下面將類分作xxActivity,xxFragment,xxPresenter,xxContract。也正是這些類構(gòu)成了項(xiàng)目中的Presenter層與View層。下面就來分析在這個(gè)項(xiàng)目中是如何實(shí)現(xiàn)MVP架構(gòu)。
MVP架構(gòu)的實(shí)現(xiàn)
在這里只從宏觀上關(guān)注MVP架構(gòu)的實(shí)現(xiàn),對于代碼的內(nèi)部細(xì)節(jié)在就不在具體分析。那么就以任務(wù)的添加和編輯這個(gè)功能來看一下Android官方是如何實(shí)現(xiàn)MVP架構(gòu)。
Model層的實(shí)現(xiàn)
首先我們從MVP架構(gòu)的最內(nèi)層開始分析,也就是對應(yīng)的Model層。在這個(gè)項(xiàng)目中對應(yīng)的data包下的內(nèi)容。在data下對數(shù)據(jù)庫等一些數(shù)據(jù)源的封裝。對于Presenter層提供了TasksDataSource接口。在這里看一下這個(gè)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接口的實(shí)現(xiàn)是TasksLocalDataSource,在TasksDataSource中的方法也就是一些對數(shù)據(jù)庫的增刪改查的操作。而在TasksDataSource的兩個(gè)內(nèi)部接口LoadTasksCallback和GetTaskCallback是Model層的回調(diào)接口。它們的真正實(shí)現(xiàn)是在Presenter層。對于成功獲取到數(shù)據(jù)后變或通過這個(gè)回調(diào)接口將數(shù)據(jù)傳遞Presenter層。同樣,若是獲取失敗同樣也會(huì)通過回調(diào)接口來通知Presenter層。下面來看一下TasksDataSource的實(shí)現(xiàn)類。
public class TasksLocalDataSource implements TasksDataSource { ...... @Override public void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback) { //根據(jù)taskId查訓(xùn)出相對應(yīng)的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(); } ...... }
在這里我們針對任務(wù)的添加和編輯功能,所以省略很多代碼??梢钥闯鲈赥asksLocalDataSource中實(shí)現(xiàn)的getTask方法,在這個(gè)方法中傳入TasksDataSource內(nèi)的GetTaskCallback回調(diào)接口。在getTask方法的實(shí)現(xiàn)可以看出在查詢到Task以后調(diào)用回調(diào)方法,若是在Presenter層中實(shí)現(xiàn)了這兩個(gè)回調(diào)方法,便將數(shù)據(jù)傳遞到Presenter層。而對于查詢到的Task為空的時(shí)候也是通過回調(diào)方法執(zhí)行對應(yīng)的操作。
同樣對于通過網(wǎng)絡(luò)請求獲取到數(shù)據(jù)也是一樣,對于成功請求到的數(shù)據(jù)可以通過回調(diào)方法將數(shù)據(jù)傳遞到Presenter層,對于網(wǎng)絡(luò)請求失敗也能夠通過回調(diào)方法來執(zhí)行相對應(yīng)的操作。
Presenter與View層提供的接口
由于在Presenter和View層所提供的接口在一個(gè)類中,在這里就先來查看他們對外所提供了哪些接口。首先觀察一下兩個(gè)基類接口BasePresenter和BaseView。
BasePresenter
package com.example.android.architecture.blueprints.todoapp; public interface BasePresenter { void start(); }
在BasePresenter中只存在一個(gè)start方法。這個(gè)方法一般所執(zhí)行的任務(wù)是在Presenter中從Model層獲取數(shù)據(jù),并調(diào)用View接口顯示。這個(gè)方法一般是在Fragment中的onResume方法中調(diào)用。
BaseView
package com.example.android.architecture.blueprints.todoapp; public interface BaseView<T> { void setPresenter(T presenter); }
在BaseView中只有一個(gè)setPresenter方法,對于View層會(huì)存在一個(gè)Presenter對象。而setPresenter正是對View中的Presenter進(jìn)行初始化。
AddEditTaskContract
在Android官方給出的MVP架構(gòu)當(dāng)中對于Presenter接口和View接口提供的形式與我們平時(shí)在網(wǎng)上所見的有所不同。在這里將Presenter中的接口和View的接口都放在了AddEditTaskContract類里面。這樣一來我們能夠更清晰的看到在Presenter層和View層中有哪些功能,方便我們以后的維護(hù)。下面就來看一下這個(gè)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層中處理了一些數(shù)據(jù)顯示的操作,而在Presenter層中則是對Task保存,更新等操作。
Presenter層的實(shí)現(xiàn)
下面就來看一下在Presenter是如何實(shí)現(xiàn)的。
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中它不僅實(shí)現(xiàn)了自己的Presenter接口,也實(shí)現(xiàn)了GetTaskCallback的回調(diào)接口。并且在Presenter中包含了Model層TasksDataSource的對象mTasksRepository和View層AddEditTaskContract.View的對象mAddTaskView。于是整個(gè)業(yè)務(wù)邏輯的處理就擔(dān)負(fù)在Presenter的身上。
從Presenter的業(yè)務(wù)處理中可以看出,首先調(diào)用Model層的接口getTask方法,通過TaskId來查詢Task。在查詢到Task以后,由于在Presenter層中實(shí)現(xiàn)了Model層的回調(diào)接口GetTaskCallback。這時(shí)候在Presenter層中就通過onTaskLoaded方法獲取到Task對象,最后通過調(diào)用View層接口實(shí)現(xiàn)了數(shù)據(jù)的展示。
View層的實(shí)現(xiàn)
對于View的實(shí)現(xiàn)是在Fragment中,而在Activity中則是完成對Fragment的添加,Presenter的創(chuàng)建操作。下面首先來看一下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的提供的功能也是非常的簡單,首先創(chuàng)建Fragment對象并將其添加到Activity當(dāng)中。之后創(chuàng)建Presenter對象,并將Fragment也就是View傳遞到Presenter中。
下面再來看一下View的實(shí)現(xiàn),也就是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對象。并通過調(diào)用Presenter中的方法來實(shí)現(xiàn)業(yè)務(wù)的處理。而在Fragment中則只是對UI的一些操作。這樣一來對于Fragment類型的代碼減少了很多,并且邏輯更加清晰。
我們注意到View層的實(shí)現(xiàn)是通過Fragment來完成的。對于View的實(shí)現(xiàn)為什么要采用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架構(gòu)的實(shí)現(xiàn)。在這里將Activity作為全局的控制者將Presenter于View聯(lián)系在一起。
- 采用Fragment更有利于平板電腦的布局或者是多視圖屏幕。
總結(jié)
通過MVP架構(gòu)的使用可以看出對于各個(gè)層次之間的職責(zé)更加單一清晰,同時(shí)也很大程度上降低了代碼的耦合度。對于官方MVP架構(gòu)示例,google也明確表明對于他們所給出的這些架構(gòu)示例只是作為參考,而不是一個(gè)標(biāo)準(zhǔn)。所以對于基礎(chǔ)的MVP架構(gòu)有更大的擴(kuò)展空間。例如綜合google給出的示例。我們可以通過在MVP架構(gòu)的基礎(chǔ)上使用dagger2,rxJava等來構(gòu)建一個(gè)Clean架構(gòu)。也是一個(gè)很好的選擇。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實(shí)現(xiàn)使用流媒體播放遠(yuǎn)程mp3文件的方法
這篇文章主要介紹了Android實(shí)現(xiàn)使用流媒體播放遠(yuǎn)程mp3文件的方法,結(jié)合實(shí)例形式分析了Android遠(yuǎn)程播放音頻文件的相關(guān)步驟與實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-08-08Rxjava+Retrofit+Okhttp進(jìn)行網(wǎng)絡(luò)訪問及數(shù)據(jù)解析
這篇文章主要介紹了Rxjava+Retrofit+Okhttp進(jìn)行網(wǎng)絡(luò)訪問及數(shù)據(jù)解析,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-08-08Android系統(tǒng)默認(rèn)對話框添加圖片功能
這篇文章主要介紹了Android系統(tǒng)默認(rèn)對話框添加圖片的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01Android實(shí)現(xiàn)長按圖片保存至相冊功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)長按圖片保存至相冊功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Android ListView用EditText實(shí)現(xiàn)搜索功能效果
本篇文章主要介紹了Android ListView用EditText實(shí)現(xiàn)搜索功能效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03自己實(shí)現(xiàn)Android View布局流程
這篇文章主要介紹了自己實(shí)現(xiàn)Android View布局流程,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03