1. 程式人生 > >MVP架構-Android官方MVP專案和響應式MVP-RxJava專案架構分析對比解讀

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各個元件間的關係。
MVP架構

從圖中可以看出,View層不再和Model層關聯,他們之間通過Presenter層關聯,這裡就出明顯的感覺出P層的任務會比較重,邏輯會相對其他層複雜,同時也是MVP中最關鍵的層。

  在MVP架構中將這三層分別抽象到各自的介面當中。通過介面將層次之間進行隔離,而Presenter對View和Model的相互依賴也是依賴於各自的介面。這點符合了介面隔離原則,也正是面向介面程式設計。在Presenter層中包含了一個View介面,並且依賴於Model介面,從而將Model層與View層聯絡在一起。而對於View層會持有一個Presenter成員變數並且只保留對Presenter介面的呼叫,具體業務邏輯全部交由Presenter介面實現類中處理。

面向介面程式設計:每個層次不是直接向其上層提供服務(即不是直接例項化在上層中),而是通過定義一組介面,僅向上層暴露其介面功能,上層對於下層僅僅是介面依賴,而不依賴具體類。

程式碼分析

專案說明

目前Google在GitHub上面公佈7個專案:
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層
        );

看到上面的程式碼,感覺下圖非常符合
MV層注入P層

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整個專案的邏輯圖。
MVP專案圖解

需要具體程式碼可以到GitHub上Clone

總結

  • 通過對比分析清晰了官方MVP的架構邏輯。
  • 每個類都儘量符合面向物件設計原則,採用單一職責原則。整個專案架構清晰分工明確。
  • 水平有限,請大家指正。依賴注入這塊我也不是很懂,具體的需要大家去Google。
  • 最後對響應式MVP結合Retrofit提出一點自己的構想。

參考