MVP這個架構一直是Android開發社區討論的焦點,每個人都有自己的分析理解眾說紛紜。直到GitHub上Google官方發布用MVP架構搭建的項目。感覺是時候分析了。
MVP架構簡介
對于一個應用而言我們需要對它抽象出各個層面,而在MVP架構中它將UI界面和數據進行隔離,所以我們的應用也就分為三個層次。
View:對于View層也是視圖層,在View層中只負責對數據的展示,提供友好的界面與用戶進行交互。在Android開發中通常將Activity或者Fragment作為View層。 Model:對于Model層也是數據層。它區別于MVC架構中的Model,在這里不僅僅只是數據模型。在MVP架構中Model它負責對數據的存取操作,例如對數據庫的讀寫,網絡的數據的請求等。 Presenter:對于Presenter層他是連接View層與Model層的橋梁并對業務邏輯進行處理。在MVP架構中Model與View無法直接進行交互。所以在Presenter層它會從Model層獲得所需要的數據,進行一些適當的處理后交由View層進行顯示。這樣通過Presenter將View與Model進行隔離,使得View和Model之間不存在耦合,同時也將業務邏輯從View中抽離。更好的使得單元測試得以實現。下圖很好的展示了MVP各個組件間的關系。
從圖中可以看出,View層不再和Model層關聯,他們之間通過Presenter層關聯,這里就出明顯的感覺出P層的任務會比較重,邏輯會相對其他層復雜,同時也是MVP中最關鍵的層。
在MVP架構中將這三層分別抽象到各自的接口當中。通過接口將層次之間進行隔離,而Presenter對View和Model的相互依賴也是依賴于各自的接口。這點符合了接口隔離原則,也正是面向接口編程。在Presenter層中包含了一個View接口,并且依賴于Model接口,從而將Model層與View層聯系在一起。而對于View層會持有一個Presenter成員變量并且只保留對Presenter接口的調用,具體業務邏輯全部交由Presenter接口實現類中處理。
面向接口編程:每個層次不是直接向其上層提供服務(即不是直接實例化在上層中),而是通過定義一組接口,僅向上層暴露其接口功能,上層對于下層僅僅是接口依賴,而不依賴具體類。
本文主要分析:
基礎MVP架構todo-mvp和響應式MVP架構todo-mvp-rxjava
目前Google在GitHub上面公布7個項目:
每個項目都是便簽App,都采用MVP架構但是每個項目都會有些不同。目前網絡上大多數都是分析第一個todo-mvp,作為其他項目的基礎,對比分析todo-mvp-rxjava找出兩者的差異和相同點,是本文的主要內容。
為了簡潔,約定有:
mvp:指代todo-mvp項目
mvp-rxjava:指代todo-mvp-rxjava項目
響應式MVP:作為mvp-rxjava的中文描述
本章主要分析mvp和mvp-rxjava兩個項目基礎類的差異,以查看任務模塊taskdetail
包中的4個類和2個基礎父類作為分析點。分析同樣的功能MVP和響應式MVP的差異。
BaseView作為所有的View層的父類,功能是實現P層的依賴注入。mvp和mvp-rxjava都采用一樣邏輯。
代碼如下:
public interface BaseView {
void setPresenter(T presenter);
}
View層的具體實現類xxFragment實現接口,就能夠得到和它關聯的P層的注入。
//內部變量 從setPresenter方法注入
private AddEditTaskContract.Presenter mPresenter;
@Override
public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
而注入的時機肯定就是在P層已經得到實例化之后,所以我們在對應的P層構造方法中可以看到這樣的代碼:
public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
@NonNull AddEditTaskContract.View addTaskView) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository);
mAddTaskView = checkNotNull(addTaskView);
//向V層注入自己 自己就是對應的P層實例
mAddTaskView.setPresenter(this);
}
BasePresenter作為所有P層的父類,主要實現V層和P層生命周期同步。
這里響應式MVP明顯的和MVP不同,
MVP的P層父類代碼:
public interface BasePresenter {
void start();
}
在View層的具體實現類xxFragment的生命周期onResume中啟動通過注入得到的P層實例開始P層的工作。
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
響應式MVP的P層父類:
public interface BasePresenter {
void subscribe();//開啟訂閱
void unsubscribe();//結束訂閱
}
在View層的具體實現類xxFragment中就需要做兩步操作,同步P層和V層的生命周期
@Override
public void onResume() {
super.onResume();
mPresenter.subscribe();//V層獲得焦點 開始訂閱
}
@Override
public void onPause() {
super.onPause();
mPresenter.unsubscribe();//V層失去焦點 取消訂閱
}
這么寫的原因是,RxJava的特點決定的。
響應式編碼中數據Model是可以觀察到的數據流,已經準備好數據,隨時等待發射。
我們需要做的就是,在需要數據的點開始訂閱數據,接收數據。不再需要數據就取消訂閱數據,讓數據不再發送。這在使用RxJava是很重要的操作。
在一般的MVC的項目中,如果使用Fragment做為數據的主要展示類,就直接定義內部變量CompositeSubscription對象訂閱者集合,在onDestroy生命周期回調中統一操作,取消正在等待的訂閱,因為當前View已經不可見了。
一般的代碼是這樣的:
//父類統一提供管理方法
public abstract class BaseFragment extends Fragment {
private CompositeSubscription mCompositeSubscription; //這個類的內部是由Set 維護訂閱者
//提供給子類的方法
public void addSubscription(Subscription s) {
if (this.mCompositeSubscription == null) {
this.mCompositeSubscription = new CompositeSubscription();
}
this.mCompositeSubscription.add(s);
}
@Override
public void onDestroy() {
super.onDestroy();
//在銷毀時統一取消
if (this.mCompositeSubscription != null) {
this.mCompositeSubscription.unsubscribe();
}
}
}
而在響應式MVP架構中P層作為控制邏輯的主要實現,就需要和V層的生命周期同步,把這段代碼搬到P層中。
在我的另一篇博文RxAndroid和Retrofit使用記錄-有關網絡調用和生命周期中有具體代碼和分析。
不同于其他的MVP項目,官方的MVP架構中都定義有xxContract契約類,把P層和V層的接口統一寫在契約類中,能夠更清晰的看到在Presenter層和View層中有哪些功能,方便我們以后的維護。這是其他MVP架構沒有的類。mvp和mvp-rxjava都采用一樣邏輯。
每個契約類都定義了P層的數據操作方法和V層控制UI的方法,
并能夠通過參數傳入需要的值。
每個模塊的契約類都是需要我們根據具體的需求進行抽象,定義方法和參數的。
下面的代碼是,添加任務模塊的契約類,通過方法名可以大概了解V層和P層需要具體是實現的邏輯功能。
public interface AddEditTaskContract {
interface View extends BaseView {
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();
}
}
在官方的MVP架構中Activity類不再負責任何的View層功能。mvp和mvp-rxjava都采用一樣邏輯。
普通的View控件都包含在V層的Fragment中。 甚至在布局文件中和Fragment同級的FloatingActionButton
控件也由Fragment控制 同樣布局文件中和Fragment同級的Menu菜單視圖,也由Fragment控制。
這樣使得Fragment才變成真正的View層。而使得Activity符合面向對象設計原則的SRP(單一職責原創,Single Responsibility Principle)。
而Activity最重要的功能就是P層對M/V層的綁定。
// Create the presenter
//P層的構造 依賴注入
new TaskDetailPresenter(
taskId,//P層需要的關鍵數據 任務id
Injection.provideTasksRepository(getApplicationContext()),//Model層的注入
taskDetailFragment//View層
);
看到上面的代碼,感覺下圖非常符合
說了這么多終于到MVP的View層了,官方MVP架構中Fragment作為View層實現類。
分層之后Fragment的代碼就簡潔多了。
implements實現相關接口方法,做視圖操作,分發給P層做處理。得到P層回調展示數據。
下面的代碼,作為示例,它實現同級視圖控制。
上文提到: 甚至在布局文件中和Fragment同級的
FloatingActionButton
控件也由Fragment控制
//得到和自己同級的View
// Set up floating action button
FloatingActionButton fab =
(FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task);
//響應點擊事件 分發給P層
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.editTask();
}
});
同樣Menu也重寫了onCreateOptionsMenu()
方法和onOptionsItemSelected()
方法控制菜單視圖。
每個包中的xxPresenter類是某個具體的P層控制類。因為Model數據層有負責了數據的讀取功能,P層的代碼邏輯也很簡單。
從M層得到數據,做邏輯判斷分發給V層;蛘呤窍騇層發送某個讀寫操作,回調操作是否成功的結果。
而它的數據來源就是剛才Activity類里面的依賴注入進來的。
//通過 構造方法得到的依賴注入
private final TasksRepository mTasksRepository;
值得一提的是,響應式MVP和MVP在獲取數據方面會有不同。
數據層讀寫操作會發生在子線程,要在Model層某個具體的數據發送類,做線程處理。P層的回調才能將數據分發給V層顯示。
MVP中P層通過接口回調得到數據,如下代碼:
mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
// The view may not be able to handle UI updates anymore
if (!mTaskDetailView.isActive()) {
return;
}
mTaskDetailView.setLoadingIndicator(false);
if (null == task) {
mTaskDetailView.showMissingTask();
} else {
showTask(task);
}
}
如果你用過RxJava,下面的代碼,相信就不需要我說什么了?梢蕴^下面的說明。
mTaskDetailView.setLoadingIndicator(true);
Subscription subscription = mTasksRepository
.getTask(mTaskId)//取出可觀察數據 Observable
.subscribeOn(Schedulers.io())//在IO線程 產生數據
.observeOn(AndroidSchedulers.mainThread())//在UI線程 分發數據
.subscribe(new Observer() {
@Override
public void onCompleted() {
mTaskDetailView.setLoadingIndicator(false);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Task task) {
showTask(task);
}
});
mSubscriptions.add(subscription);//加入集合 才能在及時取消訂閱
代碼說明:
subscribeOn(): 指定 subscribe() 所發生的線程,即 Observable.OnSubscribe 被激活時所處的線程;蛘呓凶鍪录a生的線程。
observeOn(): 指定 Subscriber 所運行在的線程;蛘呓凶鍪录M的線程。
所以mTasksRepository數據M層的數據,就不需要做線程處理了,兩行代碼搞定線程切換,然后直接訂閱就等待數據的到達。
感覺上面說了這么多,Model層幾乎都說完了。
數據層負責數據的在本地或者遠程讀寫數據,每個應用的數據結構表示和存儲形式都不會有些不同。
官方的MVP數據使用SQL數據庫存儲,外部再維護一個懶漢式單例的TasksRepository
做數據緩存。
MVP架構通過接口回調分發數據,響應式MVP通過Observable得到可觀察數據。
如果結合Retrofit網絡框架,哪響應式MVP的Model層數據來源。就是可以是ServiceGenerator。
Retrofit的靜態構造方法構造網絡請求對象,傳入接口,代理模式生成出數據,P層就可以直接拿到數據了。
當然這只是我的初步想法,打算正用于我的個人項目。