MVP架構-Android官方MVP專案和響應式MVP-RxJava專案架構分析對比解讀
介紹
MVP這個架構一直是Android開發社群討論的焦點,每個人都有自己的分析理解眾說紛紜。直到GitHub上Google官方釋出用MVP架構搭建的專案。感覺是時候分析了。
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介面實現類中處理。
面向介面程式設計:每個層次不是直接向其上層提供服務(即不是直接例項化在上層中),而是通過定義一組介面,僅向上層暴露其介面功能,上層對於下層僅僅是介面依賴,而不依賴具體類。
程式碼分析
專案說明
目前Google在GitHub上面公佈7個專案:
每個專案都是便籤App,都採用MVP架構但是每個專案都會有些不同。目前網路上大多數都是分析第一個todo-mvp,作為其他專案的基礎,對比分析todo-mvp-rxjava找出兩者的差異和相同點,是本文的主要內容。
為了簡潔,約定有:
mvp:指代todo-mvp專案
mvp-rxjava:指代todo-mvp-rxjava專案
響應式MVP:作為mvp-rxjava的中文描述
基礎類分析
本章主要分析mvp和mvp-rxjava兩個專案基礎類的差異,分析同樣的功能MVP和響應式MVP的實現的差異和提取出相同點。
基礎類BaseView
BaseView作為所有的View層的父類,功能是實現P層的依賴注入。mvp和mvp-rxjava都採用一樣邏輯。
程式碼如下:
public interface BaseView<T> {
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);
}
上面這3段程式碼,給我感覺是這樣的。
基礎類BasePresenter
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是很重要的操作。
一般使用RxJava的MVC架構專案中,如果使用Fragment做為資料的主要展示類,就直接定義內部變數CompositeSubscription物件訂閱者集合,在onDestroy生命週期回撥中統一操作,取消正在等待的訂閱,因為當前View已經不可見了。
程式碼是這樣的:
//父類統一提供管理方法
public abstract class BaseFragment extends Fragment {
private CompositeSubscription mCompositeSubscription; //這個類的內部是由Set<Subscription> 維護訂閱者
//提供給子類的方法
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層中。
Contract契約類
不同於其他的MVP專案,官方的MVP架構中都定義有xxContract契約類,把P層和V層的介面統一寫在契約類中,能夠更清晰的看到在Presenter層和View層中有哪些功能,方便我們以後的維護。這是其他MVP架構沒有的類。mvp和mvp-rxjava都採用一樣邏輯。
每個契約類都定義了P層的資料操作方法和V層控制UI的方法,
並能夠通過引數傳入需要的值。
每個模組的契約類都是需要我們根據具體的需求進行抽象,定義方法和引數的。
下面的程式碼是,新增任務模組的契約類,通過方法名可以大概瞭解V層和P層需要具體是實現的邏輯功能。
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();
}
}
Activity繫結類
在官方的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層
);
看到上面的程式碼,感覺下圖非常符合
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()
方法控制選單檢視。
Presenter層
每個包中的xxPresenter類是某個具體的P層控制類。因為Model資料層有負責了資料的讀取功能,P層程式碼量會減少一些,但是邏輯不會簡單,因為它負責M/V兩層的通訊。
比如從M層得到資料,做邏輯判斷分發給V層。或者是響應V層某個操作邏輯判斷之後,傳送給M層作讀寫操作,最後回撥操作是否成功的結果。
而它的資料來源Model層,就是剛才Activity類裡面的依賴注入進來的。
//資料層 通過構造方法得到的依賴注入
private final TasksRepository mTasksRepository;
值得一提的是,響應式MVP和MVP在獲取資料方面會有不同。
MVP的P層獲取資料邏輯
在P層和M層的互動中值得注意的有兩個問題。
- 資料層讀寫操作會發生在子執行緒,而P層最終需要將資料傳送給V層做UI主執行緒操作。所以要在Model層某個具體的資料傳送類做執行緒處理。P層的回撥才能正確的將資料分發給V層顯示。
既然使用到多執行緒,如果使用Handler執行緒間通訊,在M層的操作上如果子執行緒的某個操作比較耗時很久才返回資料,而V層的Fragment已經退出呼叫了onDestroy方法,這時如果P層還持有對V層的引用向他傳送資料,有可能會導致記憶體洩露。
總之MVP專案架構,耗時任務沒有處理好就有可能發生異常或者記憶體洩露。
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);
}
}
響應式MVP的P層資料獲取邏輯
上面的兩個問題,在響應式MVP裡可以得到很好的解決。
如果你用過RxJava,下面的程式碼,相信就不需要我說什麼了。可以跳過下面的說明。
mTaskDetailView.setLoadingIndicator(true);
Subscription subscription = mTasksRepository
.getTask(mTaskId)//取出可觀察資料 Observable<Task>
.subscribeOn(Schedulers.io())//在IO執行緒 產生資料
.observeOn(AndroidSchedulers.mainThread())//在UI執行緒 分發資料
.subscribe(new Observer<Task>() {
@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 被啟用時所處的執行緒。或者叫做事件產生的執行緒。
observeOn(): 指定 Subscriber 所執行在的執行緒。或者叫做事件消費的執行緒。
所以M層傳送過來的資料,只要在P層兩行程式碼搞定執行緒切換,然後直接訂閱就等待資料的到達。同時加入訂閱者管理集合在相應的生命週期統一取消。
Model層
感覺上面說了這麼多,Model層幾乎都說完了。
資料層負責資料的在本地或者遠端讀寫資料,每個應用的資料結構表示和儲存形式都不會有些不同。
官方的MVP資料使用SQL資料庫儲存,外部再維護一個懶漢式單例的TasksRepository
做資料快取。
MVP架構通過介面回撥分發資料,響應式MVP通過Observable得到可觀察資料。
如果結合Retrofit網路框架,哪響應式MVP的Model層資料來源。就是可以是ServiceGenerator。
Retrofit的靜態構造方法構造網路請求物件,傳入介面,代理模式生成出資料,P層就可以直接拿到資料了。
當然這只是我的初步想法,打算正用於我的個人專案。
圖解
通過上面對MVP每一個模組功能的分析,最後上一張圖,Android官方MVP整個專案的邏輯圖。
需要具體程式碼可以到GitHub上Clone
總結
- 通過對比分析清晰了官方MVP的架構邏輯。
- 每個類都儘量符合面向物件設計原則,採用單一職責原則。整個專案架構清晰分工明確。
- 水平有限,請大家指正。依賴注入這塊我也不是很懂,具體的需要大家去Google。
- 最後對響應式MVP結合Retrofit提出一點自己的構想。