引言
目前Android開(kāi)發(fā)比較流行的是MVP開(kāi)發(fā)架構(gòu),與MVC不同的是把Activity、Fragment等頁(yè)面作為View層,與Android平臺(tái)相關(guān)的東西基本上都被隔離在View層,Presenter負(fù)責(zé)處理業(yè)務(wù)邏輯和調(diào)度View和Model,Presenter的理想境界都是純Java的東西,數(shù)據(jù)加載、網(wǎng)絡(luò)請(qǐng)求看作是Model,還是原來(lái)的Model。
由于使用了MVP架構(gòu),接口會(huì)明顯變多,會(huì)有一種本來(lái)很簡(jiǎn)單幾行的代碼的事,反而寫(xiě)很多接口,想起之前某人提起過(guò)的,“接口不值錢(qián)”。那我們來(lái)看看Google是怎么做的吧。
項(xiàng)目地址:https://github.com/googlesamples/android-architecture
首先我們看到的項(xiàng)目是以功能模塊劃分的,估計(jì)大多數(shù)人喜歡用文件類(lèi)型來(lái)劃分吧。

眼神好的同學(xué)可能已經(jīng)看到了最下邊兩個(gè)接口了BasePresenter和BaseView
那就從他們?nèi)胧趾昧耍?/p>
BaseView: 所有View的父接口
public interface BaseView<T>
{
void setPresenter(T presenter);
}
BaseView中有一個(gè)泛型,其實(shí)就是Presenter了,是用于Presenter向View傳值,得到Presenter的引用,這樣在View中就可以調(diào)用Presenter的方法來(lái)實(shí)現(xiàn)一些功能了,比如向Presenter請(qǐng)求數(shù)據(jù),。
BasePresenter: BasePresnter是所有Presenter的父接口,只有一個(gè)start()方法,用于在onResume()中來(lái)看代碼:
public interface BasePresenter
{
void start();
}
在谷歌官方的MVP中還有一個(gè)比較有意思的就是Contract了,這個(gè)類(lèi)似合同類(lèi)的接口把P和V的所有方法全部寫(xiě)在一起,看起來(lái)代碼格外清楚,所有的功能清清楚楚,其實(shí)只要看這個(gè)Contract差不多所有的功能就已經(jīng)有所了解了,如圖。

這里就不再過(guò)多的介紹細(xì)節(jié)問(wèn)題,關(guān)鍵還是要看代碼,谷歌的代碼水平確實(shí)高,自己看了也收獲頗豐。
總結(jié):谷歌的mvp與其他的不同的是多了一個(gè)Contract,當(dāng)接口變多了,Contract優(yōu)勢(shì)明顯出來(lái)了,一目了然,相比而言,寫(xiě)一個(gè)個(gè)接口把他們都寫(xiě)在一起這種做法還是不錯(cuò)的。
重新解讀MVP Contract
綜述
對(duì)于MVP (Model View Presenter)架構(gòu)是從著名的MVC(Model View Controller)架構(gòu)演變而來(lái)的。而對(duì)于Android應(yīng)用的開(kāi)發(fā)中本身可視為一種MVC架構(gòu)。通常在開(kāi)發(fā)中將XML文件視為MVC中的View角色,而將Activity則視為MVC中的Controller角色。不過(guò)更多情況下在實(shí)際應(yīng)用開(kāi)發(fā)中Activity不能夠完全充當(dāng)Controller,而是Controller和View的合體。于是Activity既要負(fù)責(zé)視圖的顯示,又要負(fù)責(zé)對(duì)業(yè)務(wù)邏輯的處理。這樣在Activity中代碼達(dá)到上千行,甚至幾千行都不足為其,同時(shí)這樣的Activity也顯得臃腫不堪。所以對(duì)于MVC架構(gòu)并不很合適運(yùn)用于Android的開(kāi)發(fā)中。下面就來(lái)介紹一下MVP架構(gòu)以及看一下google官方給出的MVP架構(gòu)示例。
MVP架構(gòu)簡(jiǎn)介
對(duì)于一個(gè)應(yīng)用而言我們需要對(duì)它抽象出各個(gè)層面,而在MVP架構(gòu)中它將UI界面和數(shù)據(jù)進(jìn)行隔離,所以我們的應(yīng)用也就分為三個(gè)層次。
- View: 對(duì)于View層也是視圖層,在View層中只負(fù)責(zé)對(duì)數(shù)據(jù)的展示,提供友好的界面與用戶進(jìn)行交互。在Android開(kāi)發(fā)中通常將Activity或者Fragment作為View層。
- Model: 對(duì)于Model層也是數(shù)據(jù)層。它區(qū)別于MVC架構(gòu)中的Model,在這里不僅僅只是數(shù)據(jù)模型。在MVP架構(gòu)中Model它負(fù)責(zé)對(duì)數(shù)據(jù)的存取操作,例如對(duì)數(shù)據(jù)庫(kù)的讀寫(xiě),網(wǎng)絡(luò)的數(shù)據(jù)的請(qǐng)求等。
- Presenter:對(duì)于Presenter層他是連接View層與Model層的橋梁并對(duì)業(yè)務(wù)邏輯進(jìn)行處理。在MVP架構(gòu)中Model與View無(wú)法直接進(jìn)行交互。所以在Presenter層它會(huì)從Model層獲得所需要的數(shù)據(jù),進(jìn)行一些適當(dāng)?shù)奶幚砗蠼挥蒝iew層進(jìn)行顯示。這樣通過(guò)Presenter將View與Model進(jìn)行隔離,使得View和Model之間不存在耦合,同時(shí)也將業(yè)務(wù)邏輯從View中抽離。
下面通過(guò)MVP結(jié)構(gòu)圖來(lái)看一下MVP中各個(gè)層次之間的關(guān)系。

在MVP架構(gòu)中將這三層分別抽象到各自的接口當(dāng)中。通過(guò)接口將層次之間進(jìn)行隔離,而Presenter對(duì)View和Model的相互依賴(lài)也是依賴(lài)于各自的接口。這點(diǎn)符合了接口隔離原則,也正是面向接口編程。在Presenter層中包含了一個(gè)View接口,并且依賴(lài)于Model接口,從而將Model層與View層聯(lián)系在一起。而對(duì)于View層會(huì)持有一個(gè)Presenter成員變量并且只保留對(duì)Presenter接口的調(diào)用,具體業(yè)務(wù)邏輯全部交由Presenter接口實(shí)現(xiàn)類(lèi)中處理。
對(duì)于MVP架構(gòu)有了一些的了解,而在前段時(shí)間Google給出了一些App開(kāi)發(fā)架構(gòu)的實(shí)現(xiàn)。
項(xiàng)目地址為:https://github.com/googlesamples/android-architecture.
在這里進(jìn)入README看一下這次Google給出那些Android開(kāi)發(fā)架構(gòu)的實(shí)現(xiàn)。

對(duì)于上面五個(gè)開(kāi)發(fā)架構(gòu)的實(shí)現(xiàn)表示到目前為止是已經(jīng)完成的項(xiàng)目,而下面兩個(gè)則表示正在進(jìn)行的中的項(xiàng)目?,F(xiàn)在首先來(lái)介紹一下這幾個(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),采用了依賴(lài)注入dagger2。
- dev-todo-mvp-contentproviders: 基于mvp-loaders架構(gòu),使用了ContenPproviders。
- dev-todo-mvp-rxjava: 基于MVP架構(gòu),對(duì)于程序的并發(fā)處理和數(shù)據(jù)層(MVP中的Model)的抽象。
從上述的介紹中可以看出,對(duì)于官方給出所有的架構(gòu)的實(shí)現(xiàn)最終都是基于MVP架構(gòu)。所以在這里就對(duì)上面的基礎(chǔ)的MVP架構(gòu)todo-mvp進(jìn)行分析。
項(xiàng)目結(jié)構(gòu)的分析
對(duì)于這個(gè)項(xiàng)目,它實(shí)現(xiàn)的是一個(gè)備忘錄的功能。對(duì)于工作中未完成的任務(wù)添加到待辦任務(wù)列表中。我們能夠在列表中可以對(duì)已完成的任務(wù)做出標(biāo)記,能夠進(jìn)入任務(wù)詳細(xì)頁(yè)面修改任務(wù)內(nèi)容,也能夠?qū)σ淹瓿傻娜蝿?wù)和未完成的任務(wù)數(shù)量做出統(tǒng)計(jì)。
首先在這里來(lái)看一下todo-mvp整體的項(xiàng)目結(jié)構(gòu)

- 從上圖中可以看出,項(xiàng)目整體包含了一個(gè)app src目錄,四個(gè)測(cè)試目錄。
- 在src目錄下面對(duì)代碼的組織方式是按照功能進(jìn)行劃分。
- 在這個(gè)項(xiàng)目中包含了四個(gè)功能,它們分別是:任務(wù)的添加編輯(addedittask),任務(wù)完成情況的統(tǒng)計(jì)(statistics),任務(wù)的詳情(taskdetail),任務(wù)列表的顯示(tasks)。
- 對(duì)于data包它是項(xiàng)目中的數(shù)據(jù)源,執(zhí)行數(shù)據(jù)庫(kù)的讀寫(xiě),網(wǎng)絡(luò)的請(qǐng)求操作都存放在該包內(nèi),也是MVP架構(gòu)中的Model層。而util包下面則是存放一些項(xiàng)目中使用到的工具類(lèi)。
- 在最外層存放了兩接口BasePresenter和BaseView。它們是Presenter層接口和View層接口的基類(lèi),項(xiàng)目中所有的Presenter接口和View層接口都繼承自這兩個(gè)接口。
- 現(xiàn)在進(jìn)入功能模塊內(nèi)看下在模塊內(nèi)部對(duì)類(lèi)是如何劃分的。在每個(gè)功能模塊下面將類(lèi)分作xxActivity,xxFragment,xxPresenter,xxContract。也正是這些類(lèi)構(gòu)成了項(xiàng)目中的Presenter層與View層。下面就來(lái)分析在這個(gè)項(xiàng)目中是如何實(shí)現(xiàn)MVP架構(gòu)。
MVP架構(gòu)的實(shí)現(xiàn)
在這里只從宏觀上關(guān)注MVP架構(gòu)的實(shí)現(xiàn),對(duì)于代碼的內(nèi)部細(xì)節(jié)在就不在具體分析。那么就以任務(wù)的添加和編輯這個(gè)功能來(lái)看一下Android官方是如何實(shí)現(xiàn)MVP架構(gòu)。
Model層的實(shí)現(xiàn)
首先我們從MVP架構(gòu)的最內(nèi)層開(kāi)始分析,也就是對(duì)應(yīng)的Model層。在這個(gè)項(xiàng)目中對(duì)應(yīng)的data包下的內(nèi)容。在data下對(duì)數(shù)據(jù)庫(kù)等一些數(shù)據(jù)源的封裝。對(duì)于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中的方法也就是一些對(duì)數(shù)據(jù)庫(kù)的增刪改查的操作。而在TasksDataSource的兩個(gè)內(nèi)部接口LoadTasksCallback和GetTaskCallback是Model層的回調(diào)接口。它們的真正實(shí)現(xiàn)是在Presenter層。對(duì)于成功獲取到數(shù)據(jù)后變或通過(guò)這個(gè)回調(diào)接口將數(shù)據(jù)傳遞Presenter層。同樣,若是獲取失敗同樣也會(huì)通過(guò)回調(diào)接口來(lái)通知Presenter層。下面來(lái)看一下TasksDataSource的實(shí)現(xiàn)類(lèi)。
public class TasksLocalDataSource implements TasksDataSource {
......
@Override
public void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback) {
//根據(jù)taskId查詢(xún)出相對(duì)應(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();
}
......
}
在這里我們針對(duì)任務(wù)的添加和編輯功能,所以省略很多代碼??梢钥闯鲈赥asksLocalDataSource中實(shí)現(xiàn)的getTask方法,在這個(gè)方法中傳入TasksDataSource內(nèi)的GetTaskCallback回調(diào)接口。在getTask方法的實(shí)現(xiàn)可以看出在查詢(xún)到Task以后調(diào)用回調(diào)方法,若是在Presenter層中實(shí)現(xiàn)了這兩個(gè)回調(diào)方法,便將數(shù)據(jù)傳遞到Presenter層。而對(duì)于查詢(xún)到的Task為空的時(shí)候也是通過(guò)回調(diào)方法執(zhí)行對(duì)應(yīng)的操作。
同樣對(duì)于通過(guò)網(wǎng)絡(luò)請(qǐng)求獲取到數(shù)據(jù)也是一樣,對(duì)于成功請(qǐng)求到的數(shù)據(jù)可以通過(guò)回調(diào)方法將數(shù)據(jù)傳遞到Presenter層,對(duì)于網(wǎng)絡(luò)請(qǐng)求失敗也能夠通過(guò)回調(diào)方法來(lái)執(zhí)行相對(duì)應(yīng)的操作。
Presenter與View層提供的接口
由于在Presenter和View層所提供的接口在一個(gè)類(lèi)中,在這里就先來(lái)查看他們對(duì)外所提供了哪些接口。首先觀察一下兩個(gè)基類(lèi)接口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方法,對(duì)于View層會(huì)存在一個(gè)Presenter對(duì)象。而setPresenter正是對(duì)View中的Presenter進(jìn)行初始化。
AddEditTaskContract
在Android官方給出的MVP架構(gòu)當(dāng)中對(duì)于Presenter接口和View接口提供的形式與我們平時(shí)在網(wǎng)上所見(jiàn)的有所不同。在這里將Presenter中的接口和View的接口都放在了AddEditTaskContract類(lèi)里面。這樣一來(lái)我們能夠更清晰的看到在Presenter層和View層中有哪些功能,方便我們以后的維護(hù)。下面就來(lái)看一下這個(gè)AddEditTaskContract類(lèi)。
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層中則是對(duì)Task保存,更新等操作。
Presenter層的實(shí)現(xiàn)
下面就來(lái)看一下在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的對(duì)象mTasksRepository和View層AddEditTaskContract.View的對(duì)象mAddTaskView。于是整個(gè)業(yè)務(wù)邏輯的處理就擔(dān)負(fù)在Presenter的身上。
從Presenter的業(yè)務(wù)處理中可以看出,首先調(diào)用Model層的接口getTask方法,通過(guò)TaskId來(lái)查詢(xún)Task。在查詢(xún)到Task以后,由于在Presenter層中實(shí)現(xiàn)了Model層的回調(diào)接口GetTaskCallback。這時(shí)候在Presenter層中就通過(guò)onTaskLoaded方法獲取到Task對(duì)象,最后通過(guò)調(diào)用View層接口實(shí)現(xiàn)了數(shù)據(jù)的展示。
View層的實(shí)現(xiàn)
對(duì)于View的實(shí)現(xiàn)是在Fragment中,而在Activity中則是完成對(duì)Fragment的添加,Presenter的創(chuàng)建操作。下面首先來(lái)看一下AddEditTaskActivity類(lèi)。
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);
}
......
}
對(duì)于Activity的提供的功能也是非常的簡(jiǎn)單,首先創(chuàng)建Fragment對(duì)象并將其添加到Activity當(dāng)中。之后創(chuàng)建Presenter對(duì)象,并將Fragment也就是View傳遞到Presenter中。
下面再來(lái)看一下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);
}
......
}
在這對(duì)于源碼就不再過(guò)多貼出。在Fragment中,通過(guò)setPresenter獲取到Presenter對(duì)象。并通過(guò)調(diào)用Presenter中的方法來(lái)實(shí)現(xiàn)業(yè)務(wù)的處理。而在Fragment中則只是對(duì)UI的一些操作。這樣一來(lái)對(duì)于Fragment類(lèi)型的代碼減少了很多,并且邏輯更加清晰。
我們注意到View層的實(shí)現(xiàn)是通過(guò)Fragment來(lái)完成的。對(duì)于View的實(shí)現(xiàn)為什么要采用Fragment而不是Activity。來(lái)看一下官方是如何解釋的。
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.
在這里官方對(duì)于采用Fragment的原因給出了兩種解釋。
- 通過(guò)Activity和Fragment分離非常適合對(duì)于MVP架構(gòu)的實(shí)現(xiàn)。在這里將Activity作為全局的控制者將Presenter與View聯(lián)系在一起。
- 采用Fragment更有利于平板電腦的布局或者是多視圖屏幕。
總結(jié)
通過(guò)MVP架構(gòu)的使用可以看出對(duì)于各個(gè)層次之間的職責(zé)更加單一清晰,同時(shí)也很大程度上降低了代碼的耦合度。對(duì)于官方MVP架構(gòu)示例,google也明確表明對(duì)于他們所給出的這些架構(gòu)示例只是作為參考,而不是一個(gè)標(biāo)準(zhǔn)。所以對(duì)于基礎(chǔ)的MVP架構(gòu)有更大的擴(kuò)展空間。例如綜合google給出的示例。我們可以通過(guò)在MVP架構(gòu)的基礎(chǔ)上使用dagger2,rxJava等來(lái)構(gòu)建一個(gè)Clean架構(gòu)。也是一個(gè)很好的選擇。