MVVM&Android實踐(四):MVVM的前世今生

第四部分:MVVM


前面的主要內容,基本只是介紹了MVVM的基礎DataBinding的語法和使用。但這遠遠不夠,下面纔是本文的重頭戲。Android 的關於代碼的組織方式(你也可以稱之爲設計模式),從MVC 到MVP 再到MVVM,經歷了三次重要變化。

MVVM的前世今生

MVC

Android設計之初,就遵循的MVC模式,此時的模塊劃分是:

  • Model:所有和數據相關的類
  • View:佈局文件
  • Controller:Activity、Fragment

但因爲佈局文件又不能完全承擔自己的職責,我們被迫將一部分更新View的代碼寫在了Activity(也就是Controller)中,導致Controller中的代碼混亂,不利於閱讀和維護,既有Controller的邏輯,也有View層邏輯。

想象一下一個業務場景:在界面上點擊一個Button,登錄一個網站並將登錄結果在界面上呈現。

這個需求,我們需要有一個界面,來放點擊按鈕,和登錄結果的TextView,這就是View層。再新建一個LoginHelper類來處理網絡數據,這就是Model層。剩下一個Controller就是Activity,在Activity中,通過button的OnClickListener觸發Model層的網絡請求後,將登錄的結果返回給Activity,Activity可以直接將結果顯示到界面的TextView上。

是不是很清晰,各司其職。但當你的需求中,包含需要動態改變界面控件隱藏和顯示、界面顏色變化、動畫控制,你也只能把這些View相關的代碼寫在Controller的Activity中。

造成這種混亂的根本原因,是作爲View層的佈局文件,本身控制能力太弱,View層需要動態改變的東西,不得不依賴Contoller提供控制。

MVP

爲了解決Controller邏輯混亂的問題,於是有了MVP模式。MVP模塊劃分:

  • Model:所有和數據相關的類,和MVC中的Model職責一致,處理數據邏輯。
  • View:佈局文件 + Activity or Fragment
  • Presenter:需要額外增加接口和類

MVC中的作爲View層的xml不是控制力弱嗎?直接把Activity和Fragment劃分給View層,控制力是不是爆表!!!這就是MVP模式。

Presenter作爲Controller的替代者,接替了它的工作。什麼工作?當然是連接Model和View層

Presenter通常由兩部分組成:

  1. 接口:讓View層的Activity或者Fragment實現,用於通知View層更新界面。
  2. 具體的Presenter類,比如LoginPresenter,它持有Model層和View層的引用。用於處理業務邏輯。

MVP的工作原理是,通過View層(Activity或者Fragment)創建對應的Presenter,並相互持有彼此的引用。在Presenter中,創建Model實例,並獲取Model處理後的數據,通過接口通知View層更新界面。

MVP的一大好處是,通過接口,讓Model和View層解耦,可以實現面向接口編程的目的,這對於大型項目的開發,和各模塊單獨測試幫助很大。

那麼,MVP有什麼缺點呢?

MVP的缺點是,額外增加了一個接口,會增加額外的維護成本。真是的應用場景是,需求不斷變化,接口也需要做對應調整。

該問題的解決方法是,在定義接口時,儘可能的斟酌接口,合理利用繼承、實現,合理的使用設計模式。

MVVM

對於MVVM來說,它的模塊分爲:

  • Model:Model還是哪個Model,一直都沒變。
  • View:依然是佈局文件 + Activity + Fragment。
  • ViewModel:就是被綁定在View層對象所屬的類。

本文的最終目的就是這個,以一個下載代碼的例子說明。只貼核心代碼,Demo源碼見:

Model

public class Model {
    public interface OnDownloadListener {
        void onDownloadSuccess();
        void onDownloading(int progress);
        void onDownloadFailed();
    }

    public static void downloadFile(final String url, final String dir, final OnDownloadListener listener) {
		// ...
                           listener.onDownloading(progress);
		// ...
    }
}

爲了顯而易見,我將唯一屬於Model模塊的類直接命名爲Model。該類使用Okhttp網絡框架,用於下載文件。

  • OnDownloadListener接口用於通知ViewModel下載的結果,所以ViewModel需要實現該接口。
  • public static void downloadFile(final String url, final String dir, final OnDownloadListener listener):用於具體的下載邏輯,供外界調用。url爲需要下載的地址,dir爲下載文件存放的目錄,listener爲下載監聽。
  • listener.onDownloading(progress);:下載過程中,調用監聽器,通知View層更新下載進度。

在該例中,數據只有來自網絡的數據流,而Model類負責下載和存儲數據,作爲Model層存在。

ViewModel

public class ViewModel implements Model.OnDownloadListener {
    private static final String TAG = "ViewModel";

    private static final String URL = "http://172.0.6.55/test_for_live.mp4";

    public ObservableInt mDownloadProgress = new ObservableInt(0);

    public void onClick(View view) { // 點擊事件處理函數
        Log.i(TAG, "onClick");
        Model.downloadFile(URL, "/mvvm/", this); // 連接Model
    }

    @Override
    public void onDownloadSuccess() {
        Log.i(TAG, "onDownloadSuccess");
    }

    @Override
    public void onDownloading(int progress) {
        mDownloadProgress.set(progress); // 進度更新變量
    }

    @Override
    public void onDownloadFailed() {
        Log.i(TAG, "onDownloadFailed");
    }
}

本例中,ViewModel類作爲ViewMode層的爲一類,方便起見也起名叫ViewModel,希望不要搞混了。

ViewMode層的主要工作是將View層和Model層連接起來,主要靠如下關鍵調用實現:

  1. public void onClick(View view):該函數定義後,將會被綁定在View層的Dowload按鈕上,點擊DowLoad按鈕,就會執行Model層的下載邏輯。

  2. Model.downloadFile(URL, "/mvvm/", this);:DownLoad按鈕點擊後,立馬執行下載,由該函數調用可以看到Model是如何與ViewModel連接的。函數調用後,下載進度,將會通過OnDownloadListener的回調接口通知到ViewModel層。

    當然,這裏是靜態調用。使用類實例的方式調用應該更加常見一些。

  3. mDownloadProgress.set(progress);:定義了一個ObservableInt對象mDownloadProgress,該對象將和View層綁定,當下載進度更新時,mDownloadProgress值變化將直接觸發View層的刷新。

View

View層的佈局文件:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ProgressBar
            android:id="@+id/pb"
            style="@android:style/Widget.ProgressBar.Horizontal"
            android:layout_width="0dp"
            android:layout_height="10dp"
            android:max="100"
            android:progress="@{viewModel.mDownloadProgress}"
            android:progressDrawable="@drawable/progressbar_preview"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintBottom_toTopOf="@id/bt_download"
            app:layout_constraintWidth_percent="0.875" />

        <Button
            android:id="@+id/bt_download"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{viewModel::onClick}"
            android:text="DownLoad"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>

    <data class="MvvmBinding">

        <variable
            name="viewModel"
            type="com.superli.demo.ViewModel" />
    </data>
</layout>

View層的Activity:

public class MVVMActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
        binding.setViewModel(new ViewModel());
    }
}

View層的工作主要就是將ViewModel和自己綁定,同時通過ViewMode間接和Model中的數據綁定,這種綁定關係並不直觀,但顯然是存在的。

以上就是MVVM代碼原因各層的劃分方式了。

MVVM的不足

MVC的問題是View層和Controller層在Activity中融合到一起了,原因是xml佈局文件的能力太弱,無法控制一些動態的內容。在MVVM中,data binding通過數據綁定的方式解決了這個問題,使View層和Model可以各司其職。但當你需要處理一些特殊的邏輯時,還是會讓View層出現不必要的代碼。

比如在本例中,我現在想控制ProgressBar,做一個移動或者隨便什麼樣式的動畫,我將不得不在Activity中做控制,但控制的邏輯原則上應該交給ViewModel啊。類似的情況還有很多。

關於data binding的使用,Google官方探索除了一套MVP + data binding的道路,原理大概是通過data binding解決數據綁定的問題,使用Presenter和Model交互。相關sample源碼見:

有興趣可以去研究一下。

術語表

文中術語 含義
事件 即Event
發佈了79 篇原創文章 · 獲贊 30 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章