1. 程式人生 > >Android官方MVP架構解讀

Android官方MVP架構解讀

綜述

  對於MVP (Model View Presenter)架構是從著名的MVC(Model View Controller)架構演變而來的。而對於Android應用的開發中本身可視為一種MVC架構。通常在開發中將XML檔案視為MVC中的View角色,而將Activity則視為MVC中的Controller角色。不過更多情況下在實際應用開發中Activity不能夠完全充當Controller,而是Controller和View的合體。於是Activity既要負責檢視的顯示,又要負責對業務邏輯的處理。這樣在Activity中程式碼達到上千行,甚至幾千行都不足為其,同時這樣的Activity也顯得臃腫不堪。所以對於MVC架構並不很合適運用於Android的開發中。下面就來介紹一下MVP架構以及看一下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結構圖來看一下MVP中各個層次之間的關係。
    這裡寫圖片描述
      在MVP架構中將這三層分別抽象到各自的介面當中。通過介面將層次之間進行隔離,而Presenter對View和Model的相互依賴也是依賴於各自的介面。這點符合了介面隔離原則,也正是面向介面程式設計。在Presenter層中包含了一個View介面,並且依賴於Model介面,從而將Model層與View層聯絡在一起。而對於View層會持有一個Presenter成員變數並且只保留對Presenter介面的呼叫,具體業務邏輯全部交由Presenter介面實現類中處理。

官方MVP架構分析

專案介紹

  對於MVP架構有了一些的瞭解,而在前端時間Google給出了一些App開發架構的實現。
  專案地址為:

https://github.com/googlesamples/android-architecture.
  在這裡進入README看一下這次Google給出那些Android開發架構的實現。

這裡寫圖片描述
  對於上面五個開發架構的實現表示到目前為止是已經完成的專案,而下面兩個則表示正在進行的中的專案。現在首先來介紹一下這幾個架構。

  • todo-mvp: 基礎的MVP架構。
  • todo-mvp-loaders:基於MVP架構的實現,在獲取資料的部分採用了loaders架構。
  • todo-mvp-databinding: 基於MVP架構的實現,採用了資料繫結元件。
  • todo-mvp-clean: 基於MVP架構的clean架構的實現。
  • todo-mvp-dagger2: 基於MVP架構,採用了依賴注入dagger2。
  • dev-todo-mvp-contentproviders: 基於mvp-loaders架構,使用了ContenPproviders。
  • dev-todo-mvp-rxjava: 基於MVP架構,對於程式的併發處理和資料層(MVP中的Model)的抽象。

      從上述的介紹中可以看出,對於官方給出所有的架構的實現最終都是基於MVP架構。所以在這裡就對上面的基礎的MVP架構todo-mvp進行分析。

專案結構的分析

  對於這個專案,它實現的是一個備忘錄的功能。對於工作中未完成的任務新增到待辦任務列表中。我們能夠在列表中可以對已完成的任務做出標記,能夠進入任務詳細頁面修改任務內容,也能夠對已完成的任務和未完成的任務數量做出統計。
  首先在這裡來看一下todo-mvp整體的專案結構

  從上圖中可以看出,專案整體包含了一個app src目錄,四個測試目錄。在src目錄下面對程式碼的組織方式是按照功能進行劃分。在這個專案中包含了四個功能,它們分別是:任務的新增編輯(addedittask),任務完成情況的統計(statistics),任務的詳情(taskdetail),任務列表的顯示(tasks)。對於data包它是專案中的資料來源,執行資料庫的讀寫,網路的請求操作都存放在該包內,也是MVP架構中的Model層。而util包下面則是存放一些專案中使用到的工具類。在最外層存放了兩介面BasePresenter和BaseView。它們是Presenter層介面和View層介面的基類,專案中所有的Presenter介面和View層介面都繼承自這兩個介面。
  現在進入功能模組內看下在模組內部對類是如何劃分的。在每個功能模組下面將類分作xxActivity,xxFragment,xxPresenter,xxContract。也正是這些類構成了專案中的Presenter層與View層。下面就來分析在這個專案中是如何實現MVP架構。

MVP架構的實現

  在這裡只從巨集觀上關注MVP架構的實現,對於程式碼的內部細節在就不在具體分析。那麼就以任務的新增和編輯這個功能來看一下Android官方是如何實現MVP架構。

Model層的實現

  首先我們從MVP架構的最內層開始分析,也就是對應的Model層。在這個專案中對應的data包下的內容。在data下對資料庫等一些資料來源的封裝。對於Presenter層提供了TasksDataSource介面。在這裡看一下這個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介面的實現是TasksLocalDataSource,在TasksDataSource中的方法也就是一些對資料庫的增刪改查的操作。而在TasksDataSource的兩個內部介面LoadTasksCallback和GetTaskCallback是Model層的回撥介面。它們的真正實現是在Presenter層。對於成功獲取到資料後變或通過這個回撥介面將資料傳遞Presenter層。同樣,若是獲取失敗同樣也會通過回撥介面來通知Presenter層。下面來看一下TasksDataSource的實現類。


public class TasksLocalDataSource implements TasksDataSource {

    ......

    @Override
    public void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback) {

        //根據taskId查訓出相對應的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();
    }

    ......
}

  在這裡我們針對任務的新增和編輯功能,所以省略很多程式碼。可以看出在TasksLocalDataSource中實現的getTask方法,在這個方法中傳入TasksDataSource內的GetTaskCallback回撥介面。在getTask方法的實現可以看出在查詢到Task以後呼叫回撥方法,若是在Presenter層中實現了這兩個回撥方法,便將資料傳遞到Presenter層。而對於查詢到的Task為空的時候也是通過回撥方法執行對應的操作。
  同樣對於通過網路請求獲取到資料也是一樣,對於成功請求到的資料可以通過回撥方法將資料傳遞到Presenter層,對於網路請求失敗也能夠通過回撥方法來執行相對應的操作。

Presenter與View層提供的介面

  由於在Presenter和View層所提供的介面在一個類中,在這裡就先來檢視他們對外所提供了哪些介面。首先觀察一下兩個基類介面BasePresenter和BaseView。

BasePresenter

package com.example.android.architecture.blueprints.todoapp;

public interface BasePresenter {

    void start();

}

  在BasePresenter中只存在一個start方法。這個方法一般所執行的任務是在Presenter中從Model層獲取資料,並呼叫View介面顯示。這個方法一般是在Fragment中的onResume方法中呼叫。

BaseView

package com.example.android.architecture.blueprints.todoapp;

public interface BaseView<T> {

    void setPresenter(T presenter);

}

  在BaseView中只有一個setPresenter方法,對於View層會存在一個Presenter物件。而setPresenter正是對View中的Presenter進行初始化。

AddEditTaskContract

  在Android官方給出的MVP架構當中對於Presenter介面和View介面提供的形式與我們平時在網上所見的有所不同。在這裡將Presenter中的介面和View的介面都放在了AddEditTaskContract類裡面。這樣一來我們能夠更清晰的看到在Presenter層和View層中有哪些功能,方便我們以後的維護。下面就來看一下這個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層中處理了一些資料顯示的操作,而在Presenter層中則是對Task儲存,更新等操作。

Presenter層的實現

  下面就來看一下在Presenter是如何實現的。


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中它不僅實現了自己的Presenter介面,也實現了GetTaskCallback的回撥介面。並且在Presenter中包含了Model層TasksDataSource的物件mTasksRepository和View層AddEditTaskContract.View的物件mAddTaskView。於是整個業務邏輯的處理就擔負在Presenter的身上。
  從Presenter的業務處理中可以看出,首先呼叫Model層的介面getTask方法,通過TaskId來查詢Task。在查詢到Task以後,由於在Presenter層中實現了Model層的回撥介面GetTaskCallback。這時候在Presenter層中就通過onTaskLoaded方法獲取到Task物件,最後通過呼叫View層介面實現了資料的展示。

View層的實現

  對於View的實現是在Fragment中,而在Activity中則是完成對Fragment的新增,Presenter的建立操作。下面首先來看一下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的提供的功能也是非常的簡單,首先建立Fragment物件並將其新增到Activity當中。之後建立Presenter物件,並將Fragment也就是View傳遞到Presenter中。
  下面再來看一下View的實現,也就是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物件。並通過呼叫Presenter中的方法來實現業務的處理。而在Fragment中則只是對UI的一些操作。這樣一來對於Fragment型別的程式碼減少了很多,並且邏輯更加清晰。
  我們注意到View層的實現是通過Fragment來完成的。對於View的實現為什麼要採用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架構的實現。在這裡將Activity作為全域性的控制者將Presenter於View聯絡在一起。
  • 採用Fragment更有利於平板電腦的佈局或者是多檢視螢幕。

總結

  通過MVP架構的使用可以看出對於各個層次之間的職責更加單一清晰,同時也很大程度上降低了程式碼的耦合度。對於官方MVP架構示例,google也明確表明對於他們所給出的這些架構示例只是作為參考,而不是一個標準。所以對於基礎的MVP架構有更大的擴充套件空間。例如綜合google給出的示例。我們可以通過在MVP架構的基礎上使用dagger2,rxJava等來構建一個Clean架構。也是一個很好的選擇。