Android 架構思想與 MVVM 框架封裝

語言: CN / TW / HK

theme: scrolls-light

關於Android項目架構也是一個老生常談的話題了,網上關於Android架構的文章不勝枚舉,但是通過Google檢索關鍵字,首頁的熱門文章多數是對於MVC、MVP及MVVM等架構的概念介紹,概念性的文章對於不瞭解Android架構的同學來説並不一定能起到很好的幫助。本篇文章其實源自筆者在公司內部的技術分享,稍作修改後作為文章發佈出來。文章內容涉及從 MVC、MVP 到 MVVM 的演化,同時為便於理解,每種架構都做了代碼演示,最後基於 Jetpack 提供的組件封裝了 MVVM 架構。文章內容比較基礎,幾乎沒有晦澀難懂的知識,對於想要了解Android架構的同學會有很大的幫助。

一、Android 項目架構的演化

首先,我們應該明白一點,對於架構而言並不分平台。不管MVC、MVP 還是 MVVM 都不是Android平台獨有的,甚至由於Android平台起步較晚,Android項目的架構或多或少的參考了前端的架構實現。

對於前端或者Android端項目而言代碼可以分為三部分,分別為UI部分、業務邏輯部分以及數據控制部分。這三部分流轉的起點來自於用户輸入,即用户通過操作UI調起對應的業務邏輯獲取數據,並最終將數據反饋到UI界面上,其流轉圖如下圖所示。

image.png

根據這三部分內容,我們可以將代碼分為三層,即最初的MVC架構分層。但是隨着項目的業務逐漸複雜,MVC架構的弊端顯露,不能夠支撐已有的業務。於是在此背景下衍生出了MVP架構來彌補MVC的不足。甚至後來谷歌官方推出的部分Jetpack組件來專門解決Android架構問題,從主推MVVM,到如今主推的MVI架構。但是不管架構如何演變,都脱離不了上述提到的三層邏輯,只不過新的架構在已有的基礎上彌補了老架構的不足,讓項目代碼更容易維護。

接下來的內容我們主要探討Android項目架構從MVC到MVVM的演化。

1.MVC 簡介

基於上面提到的三層邏輯,最初的Android項目採用的是MVC架構。MVC是 Model-View-Controller 的簡稱。簡單來説MVC是用户操作View,View調用Controller去操作Model層,然後Model層將數據返回給View層展示。

  • 模型層(Model) 負責與數據庫和網絡層通信,並獲取和存儲應用的數據;
  • 視圖層(View) 負責將 Model 層的數據做可視化的處理,同時處理與用户的交互;
  • 控制層(Controller) 用於建立Model層與View層的聯繫,負責處理主要的業務邏輯,並根據用户操作更新 Model 層數據。

MVC 的結構圖如下圖所示。

image.png

在 Android 項目的 MVC 架構中由於 Activity 同時充當了 View 層與 Controller 層兩個角色,所以 Android 中的 MVC 更像下面的這種結構:

image.png

基於上圖,我們可以以APP的登錄流程為例實現 Android 中 MVC 架構的代碼。

(1)Model 層代碼實現

Model 層用於處理登錄請求並從服務器獲取登錄相關數據,成功後通知 View 層更新界面。代碼如下:

```java public class LoginModel { public void login(String username, String password, LoginListener listener) { RetrofitManager.getApiService() .login(username, password) .enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { listener.onSuccess(response.data); }

                @Override
                public void onFailed(Exception e) {
                    listener.onFailed(e.getMessage());
                }
            });

}

} ```

上述代碼通過 LoginListener 來通知 View 層更新UI,LoginListener 是一個接口,如下:

```java public interface LoginListener { void onSuccess(User data);

void onFailed(String msg);

} ```

(2)Controller/View 代碼實現

由於 Android 的 MVC 架構中 Controller 與 View 層都是由 Activity 負責的,因此 Activity 需要實現 LoginListener 用來更新 UI。其代碼如下:

```java public class MainActivity extends AppCompactActivity implements LoginListener {

private LoginModel loginModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    model = new LoginModel();
    findViewbyId(R.id.btn_fetch_data).setOnClickListener(view -> {
                String username = findViewById(R.id.et_username).getText().toString();
                String password = findViewById(R.id.et_password).getText().toString();
                loginModel.login(username, password, this);
            }
    );
}

@Override
public void onSuccess(User data) {
    // Update UI
}

@Override
public void onFailed(String msg) {
    // Update UI
}

} ```

從上述代碼中可以看到,Android中的MVC代碼對於分層並不明確,導致了Controller層與View層混為一體。與此同時,大家在寫Android代碼的時候一般不會刻意的再去抽出一個Model層,而是將 Model 層的代碼也一股腦的塞到 Activity 中實現,

```java public class MainActivity extends AppCompactActivity implements LoginListener, View.OnClickListener {

private LoginModel loginModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    model = new LoginModel();

    findViewbyId(R.id.btn_fetch_data).setOnClickListener(view -> {
                showLoading();
                String username = findViewById(R.id.et_username).getText().toString();
                String password = findViewById(R.id.et_password).getText().toString();
                RetrofitManager.getApiService()
                        .login(username, password)
                        .enqueue(new Callback<User>() {
                            @Override
                            public void onResponse(Call<User> call, Response<User> response) {
                                onSuccess(response.data);
                            }

                            @Override
                            public void onFailed(Exception e) {
                                listener.onFailed(e.getMessage());
                            }
                        });
            }
    );
}

@Override
public void onSuccess(User data) {
    dismissLoading();
    // Update UI
}

@Override
public void onFailed(String msg) {
    dismissLoading();
    // Update UI
}

public void showLoading() {
    // ...
}

public void dismissLoading() {
    // ...
}

} ```

這樣的代碼分層變得更加模糊,同時也使得代碼的結構層次更加混亂。致使一些業務比較複雜的頁面 Activity 中的代碼可能多達數千行。由此可以看出 Android 項目中的 MVC 架構是存在很多問題的。總結主要有如下幾點:

  • Activity/Fragment 同時承擔了 View 層與 Controller 層的工作,違背了單一職責;
  • Model 層與 View 層存在耦合,存在相互依賴關係;
  • 開發時不注重分層,Model層代碼也被塞進了Activity/Fragment,使得代碼層次更加混亂。

### 2. MVP 簡介

針對以上MVC架構中存在的問題,我們可以在MVC的基礎上進行優化解決。即從Activity中剝離出控制層的邏輯,並阻斷Model層與View層的耦合,Model層不直接與View通信,而是在數據改變時讓 Model通知控制控制層,控制層再通知View層去做界面更新,這就是MVP的架構思想。MVP 是 Model-View-Presenter 的簡稱。 簡單來説 MVP 就是將 MVC 的 Controller 改為 Presenter,即把邏輯層的代碼從 Activity 中抽離到了 Presenter 中,這樣代碼層次變得更加清晰,其次 Model 層不再持有 View 層,代碼更加解耦。

  • 模型層(Model) 與MVC中的一致,同樣是負責與數據庫和網絡層通信,並獲取和存儲應用的數據,區別在於Model層不再與View層直接通信,而是與Presenter層通信。
  • 視圖層(View) 負責將 Model 層的數據做可視化的處理,同時與Presenter層交互。跟MVC相比,MVP的View層與Model層不再耦合。
  • 控制層(Presenter) 主要負責處理業務邏輯,並監聽Model層數據改變,然後調用View層刷新UI。

MVP 的結構圖如下圖所示。

image.png

從上圖中可以看出,View直接與Presenter層通信,當View層接收到用户操作後會調用 Presenter層去處理業務邏輯。接着Presenter層通過Model去獲取數據,Model層獲取到數據後又將最新的數據傳回 Presenter。 由於Presenter層又持有View層的引用,進而將數據傳給View層進行展示。

下面我們我們仍然以登錄為例通過代碼來演示MVP架構的實現。

(1)Model 層代碼實現

MVP中的Model層與MVC中的Model層是一致的,代碼如下:

```java public class LoginModel { public void login(String username, String password, LoginListener listener) { RetrofitManager.getApiService() .login(username, password) .enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { listener.onSuccess(response.data); }

                @Override
                public void onFailed(Exception e) {
                    listener.onFailed(e.getMessage());
                }
            });
}

} ```

在登錄接口返回結果後通過 LoginListener 將結果回調出來。

```java public interface LoginListener { void onSuccess(User user);

void onFailed(String errorInfo);

} ```

(2)Presenter 層代碼實現

由於 Presenter 需要通知 View 層更新UI,因此需要持有View,這裏可以抽象出一個 View 接口。如下:

```java public interface ILoginView { void showLoading();

void dismissLoading();

void loginSuccess(User data);

void loginFailer(String errorMsg);

} ```

另外,Presenter也需要與Model層通信,因此Presenter層也會持有Model層,在用户觸發登錄操作後,調用Presenter的登錄邏輯,Presenter通過Model進行登錄操作,登錄成功後再將結果反饋給View層更新界面。代碼實現如下:

```java public class LoginPresenter {

// Model 層 
private LoginModel model;

// View 層 
private ILoginView view;

public LoginPresenter(LoginView view) {
    this.model = new LoginModel();
    this.view = view;
}

public void login(String username, String password) {
    view.showLoading();
    model.login(username, password, new LoginListener() {
        @Override
        public void onSuccess(User user) {
            view.loginSuccess(user);
            view.dismissLoading();
        }

        @Override
        public void onFailed(String msg) {
            view.loginFailer(msg);
            view.dismissLoading();
        }
    });
}

@Override
public void onDestory() {
    // recycle instance.
}

}

```

(3)View 層代碼實現

Activity作為View層需要實現上述ILoginView接口,且View層需要持有Presenter來處理業務邏輯。View層的代碼實現就變得非常簡單了。

```java public class MainActivity extends AppCompatActivity implements ILoginView {

private LoginPresenter presenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    presenter = new LoginPresenter(this);
    findViewById(R.id.button).setOnClickListener(v -> {
        String username = findViewById(R.id.et_username).getText().toString();
        String password = findViewById(R.id.et_password).getText().toString();
        presenter.login(username, password);
    });
}

@Override
public void loginSuccess(User user) {
    // Update UI.
}

@Override
public void loginFailer(String msg) {
    // Update UI.
}

@Override
protected void onDestroy() {
    super.onDestroy();
    presenter.onDestroy();
}

@Override
public void showLoading() {

}

@Override
public void dismissLoading() {
}

} ```

MVP的本質是面向接口編程,實現依賴倒置原則。可以看得出來MVP架構在分層上相比 MVC更加清晰明確,且解耦了Model層與View層。但MVP也存在一些弊端,列舉如下:

  • 引入大量的 IView、IPresenter接口實現類,增加項目的複雜度。
  • View 層與 Presenter 相互持有,存在耦合。

### 3. MVVM 簡介

MVP相比於MVC無疑有很多優點,但其自身也並非無懈可擊,再加之MVP也並非Google官方推薦的架構,因此也只能算得上程序員對於Android項目架構優化的野路子。從2015年開始,Google官方開始對Android項目架構做出指導,隨後推出DataBinding組件以及後邊一系列的Jetpack組件來幫助開發者優化項目架構。Google官方給出的這一套指導架構就是MVVM。MVVM是 Model-View-ViewModel 的簡稱。這一架構在一定程度上解決了MVP架構中存在的問題。雖然近期官方的指導架構由MVVM變為了MVI,但MVVM依然是目前Android項目的主流架構。

  • 模型層(Model) 與MVP中的Model層一致,負責與數據庫和網絡層通信,獲取並存儲數據。與MVP的區別在於Model層不再通過回調通知業務邏輯層數據改變,而是通過觀察者模式實現。
  • 視圖(View) 負責將Model層的數據做可視化的處理,同時與ViewModel層交互。
  • 視圖模型(ViewModel) 主要負責業務邏輯的處理,同時與 Model 層 和 View層交互。與MVP的Presenter相比,ViewModel不再依賴View,使得解耦更加徹底。

MVVM 架構的結構圖如下。

image.png

MVVM架構的本質是數據驅動,它的最大的特點是單向依賴。MVVM架構通過觀察者模式讓ViewModel與View解耦,實現了View依賴ViewModel,ViewModel依賴Model的單向依賴。

接下來我們仍然以登錄為例,通過觀察者模式來實現MVVM的架構代碼。

(1)觀察者模式

由於 MVVM 架構需要將 ViewModel 與 View 解耦。因此,這裏可以使用觀察者模式來實現。下面實現觀察者模式的代碼。

  • 實現觀察者接口

```java public interface Observer { // 成功的回調 void onSuccess(T data);

// 失敗的回調
void onFailed(String msg);

} ```

  • 抽象被觀察者

```java public abstract class Observable { // 註冊觀察者 void register(Observer observer);

// 取消註冊
void unregister(Observer observer);

// 數據改變(設置成功數據)
protected void setValue(T data) {

}

// 失敗,設置失敗原因
void onFailed(String msg);

} ```

這裏我們需要注意被觀察者Observable中的setValue方法被設置成了protect。意味着如果View層拿到的是Observable實例,則無法調用setValue來設置數據,以此來保證代碼的安全性。

  • 被觀察者具體實現

```java public class LoginObservable implements Observable {

private final List<Observer<User>> list = new ArrayList<>();

private User user;

@Override
public void register(Observer<User> observer) {
    list.add(observer);
}

@Override
public void unregister(Observer<User> observer) {
    liset.remove(observer);
}

@Override
public void setValue(User user) {
    this.user = user;
    for (Observer observer : list) {
        observer.onSuccess(user);
    }
}

@Override
public void onFailed(String msg) {
    for (Observer observer : list) {
        observer.onFailed(msg);
    }
}

} ```

在被觀察者的實現類中setValue方法被設置為了public,意味着如果是LoginObservable,那麼可以通過setValue來對其設置數據。

(2)Model 層代碼實現

Model層代碼與MVP/MVC的實現基本一致,如下:

```java public class LoginModel { public void login(String username, String password, LoginObservable observable) { RetrofitManager.getApiService() .login(username, password) .enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { observable.setValue(response.data); }

                @Override
                public void onFailed(Exception e) {
                    observable.onFailed(e.getMessage());
                }
            });
}

} ```

需要注意的是LoginModel的login方法中接收的是LoginObservable類型,因此這裏可以通過setValue來設置數據。

(3)ViewModel 層實現

ViewModel層需要持有Model層,並且ViewModel層持有一個LoginObservable,並開放一個getObservable的方法供View層使用。代碼如下:

```java public class LoginViewModel {

private LoginObservable observable;

private LoginModel loginModel;

public LoginViewModel() {
    loginModel = new LoginModel();
    observable = new LoginObservable();
}

public void login(String username, String password) {
    loginModel.login(username, password, observable);
}

// 這裏返回值是父類Observable,即View層得到的Observable無法調用setValue
public Observable getObservable() {
    return observable;
}

} ```

這裏需要注意的是getObservable方法的返回值是Observable,意味着View層只能調用register方法來觀察數據改變,而無法通過setValue來設置數據。

(4)View 層代碼實現

View層持有ViewModel,用户觸發登錄事件通過View層交給Model處理,Model層在登錄成後將數據交給ViewModel中的觀察者。因此,View層只需要獲取到ViewModel層的觀察者並觀察到數據改變後更新UI即可。代碼如下:

```java public class MainActivity extends AppCompatActivity implements Observer {

private LoginViewModel viewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    viewModel = new LoginViewModel();
    viewModel.getObservable().register(this);
    findViewById(R.id.button).setOnClickListener(v -> {
        String username = findViewById(R.id.et_username).getText().toString();
        String password = findViewById(R.id.et_password).getText().toString();
        viewModel.login(username, password);
    });
}

@Override
public void onDestroy() {
    super.onDestory();
    viewModel.getObservable().unregister(this);
}

@Override
public void onSuccess(User user) {
    // fetch data success,update UI.
}

@Override
public void onFailed(String message) {
    // fetch data failed,update UI.
}

} ```

可以看到我們通過觀察者模式實現了View->ViewModel-> Model的單向依賴。相比於MVP,MVVM解耦的更加純粹。但是,上邊的觀察者模式是我們自己實現的,如果直接用到項目中肯定是不穩妥的。上面我們提到Google已經為我們提供了一套Jetpack組件來幫助開發者實現MVVM架構。那接下來我們就來認識一下通過Jetpack實現的MVVM架構。

二、使用 Jetpack 組件封裝 MVVM 架構

如果你已經看到這裏了,相信你對項目架構已經有了一定的認識。這裏需要再強調一點架構是一種思想,它與我們使用什麼工具來實現沒有關係。就像上一章中我們通過自己寫的觀察者模式來實現MVVM一樣,只要遵循了這個架構思想,那它就屬於這一架構。Google官方推出的這些組件可以更高效的幫助我們來實現MVVM。

1. Jetpack MVVM 簡介

MVVM 是 Google 官方推薦的框架。為此,Google 提供了一系列實現 MVVM 的工具,這些工具都被包含在Jetpack 組件中,例如 LiveData、ViewModel以及 DataBinding 等。下面是官方給的一個通過Jetpack組件實現的 MVVM 架構圖。

這張圖與我們上一章的MVVM結構圖是一致的,只不過這裏融入了Jetpack組件。可以看到圖中的箭頭都是單向的,View層指向了ViewModel層,表示View層持有ViewModel層的引用,但ViewModel層不持有View層。ViewModel層持有Repository層,但Repository層不會持有ViewModel。這張圖與MVVM的對應關係如下:

  • 視圖(View) 對應圖中的Activity/Fragment,包含了佈局文件等於界面相關的東西;
  • 視圖模型(ViewModel) 對應圖中的Jetpack ViewModel 與LiveData,用於持有與UI相關的數據,且能保證在旋轉屏幕後不丟失數據。另外還提供了給View層調用的接口以及和Repository通信的接口;
  • 模型層(Model) 對應圖中的 Repository,包含本地數據與服務端數據。

2. MVVM 框架代碼封裝

在瞭解了Jetpack MVVM後,為了更加高效的開發我們通常會做一些基礎封裝。例如如何結合網絡請求與數據庫來獲取數據、Loading的顯示邏輯等。本章內容要求讀者對Jetpack ViewModel、LiveData等組件有一定的瞭解。

2.1 網絡層封裝

針對服務器返回的數據進行封裝,可以抽出來一個帶有泛型的Response基類。如下:

```kotlin class BaseResponse( var errorCode: Int = -1, var errorMsg: String? = null, var data: T? = null, var dataState: DataState? = null, var exception: Throwable? = null, ) { companion object { const val ERROR_CODE_SUCCESS = 0 }

val success: Boolean
    get() = errorCode == ERROR_CODE_SUCCESS

}

/* * 網絡請求狀態 / enum class DataState { STATE_LOADING, // 開始請求 STATE_SUCCESS, // 服務器請求成功 STATE_EMPTY, // 服務器返回數據為null STATE_FAILED, // 接口請求成功但是服務器返回error STATE_ERROR, // 請求失敗 STATE_FINISH, // 請求結束 } ```

對應網絡請求的異常情況一般我們都會進行統一處理。這裏我們可以放在Observer中進行。 對LiveData 的 Observer進行封裝,從而實現Response與異常的統一處理。

```kotlin abstract class ResponseObserver : Observer> {

final override fun onChanged(response: BaseResponse<T>?) {
    response?.let {
        when (response.dataState) {
            DataState.STATE_SUCCESS, DataState.STATE_EMPTY -> {
                onSuccess(response.data)
            }
            DataState.STATE_FAILED -> {
                onFailure(response.errorMsg, response.errorCode)
            }
            DataState.STATE_ERROR -> {
                onException(response.exception)
            }
            else -> {
            }
        }
    }
}

private fun onException(exception: Throwable?) {
    ToastUtils.showToast(exception.toString())
}

/**
 * 請求成功
 *  @param  data 請求數據
 */
abstract fun onSuccess(data: T?)

/**
 * 請求失敗
 *  @param  errorCode 錯誤碼
 *  @param  errorMsg 錯誤信息
 */
open fun onFailure(errorMsg: String?, errorCode: Int) {
    ToastUtils.showToast("Login Failed,errorCode:$errorCode,errorMsg:$errorMsg")
}

} ```

封裝 RetrofitCreator 用來創建 API Service。

```kotlin object RetrofitCreator {

private val mOkClient = OkHttpClient.Builder()
    .callTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
    .connectTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
    .readTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
    .writeTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
    .retryOnConnectionFailure(true)
    .followRedirects(false)
    .addInterceptor(HttpHeaderInterceptor())
    .addInterceptor(LogInterceptor())
    .build()

private fun getRetrofitBuilder(baseUrl: String): Retrofit.Builder {
    return Retrofit.Builder()
        .baseUrl(baseUrl)
        .client(mOkClient)
        .addConverterFactory(GsonConverterFactory.create())
}

/**
 * Create Api service
 *  @param  cls Api Service
 *  @param  baseUrl Base Url
 */
fun <T> getApiService(cls: Class<T>, baseUrl: String): T {
    val retrofit = getRetrofitBuilder(
        baseUrl
    ).build()
    return retrofit.create(cls)
}

} ```

2.2 Model 層封裝

官方代碼中的 Model 層是通過Repository實現的,為了減少模板代碼,我們可以封裝 BaseRepository 來處理網絡請求。

```kotlin open class BaseRepository {

// Loading 狀態的 LiveData
val loadingStateLiveData: MutableLiveData<LoadingState> by lazy {
    MutableLiveData<LoadingState>()
}

/**
 * 發起請求
 *  @param  block 真正執行的函數回調
 *  @param responseLiveData 觀察請求結果的LiveData
 */
suspend fun <T : Any> executeRequest(
    block: suspend () -> BaseResponse<T>,
    responseLiveData: ResponseMutableLiveData<T>,
    showLoading: Boolean = true,
    loadingMsg: String? = null,
) {
    var response = BaseResponse<T>()
    try {
        if (showLoading) {
            loadingStateLiveData.postValue(LoadingState(loadingMsg, DataState.STATE_LOADING))
        }
        response = block.invoke()
        if (response.errorCode == BaseResponse.ERROR_CODE_SUCCESS) {
            if (isEmptyData(response.data)) {
                response.dataState = DataState.STATE_EMPTY
            } else {
                response.dataState = DataState.STATE_SUCCESS
            }
        } else {
            response.dataState = DataState.STATE_FAILED

// throw ServerResponseException(response.errorCode, response.errorMsg) } } catch (e: Exception) { response.dataState = DataState.STATE_ERROR response.exception = e } finally { responseLiveData.postValue(response) if (showLoading) { loadingStateLiveData.postValue(LoadingState(loadingMsg, DataState.STATE_FINISH)) } } }

private fun <T> isEmptyData(data: T?): Boolean {
    return data == null || data is List<*> && (data as List<*>).isEmpty()
}

} ```

2.3 ViewModel 層封裝

如果不做封裝,ViewModel層也會有模板代碼。這裏將通用代碼在 BaseViewModel 中完成。BaseViewModel 需要持有 Repository。

```kotlin abstract class BaseViewModel : ViewModel() { val repository: T by lazy { createRepository() }

val loadingDataState: LiveData<LoadingState> by lazy {
    repository.loadingStateLiveData
}

/**
 * 創建Repository
 */
@Suppress("UNCHECKED_CAST")
open fun createRepository(): T {
    val baseRepository = findActualGenericsClass<T>(BaseRepository::class.java)
        ?: throw NullPointerException("Can not find a BaseRepository Generics in ${javaClass.simpleName}")
    return baseRepository.newInstance()
}

} ```

2.4 View 層封裝

BaseActivity/BaseFragment 中持有 ViewModel,同時根據 LoadingState 處理 Loading 的顯示與隱藏。

```kotlin abstract class BaseActivity<VM : BaseViewModel<*>, VB : ViewBinding> : AppCompatActivity() {

protected val viewModel by lazy {
    createViewModel()
}

protected val binding by lazy {
    createViewBinding()
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(binding.root)
    viewModel.loadingDataState.observe(this) {
        when (it.state) {
            DataState.STATE_LOADING ->
                showLoading(it.msg)
            else ->
                dismissLoading()
        }
    }
    onActivityCreated(savedInstanceState)
}

/**
 * Activity content view created.
 *  @param  savedInstanceState savedInstanceState
 */
abstract fun onActivityCreated(savedInstanceState: Bundle?)

/**
 * 顯示Loading
 */
open fun showLoading(msg: String? = null) {
    ToastUtils.showToast("showLoading")
}

/**
 * 隱藏Loading
 */
open fun dismissLoading() {
    ToastUtils.showToast("hideLoading")
}

/**
 * Create ViewBinding
 */
@Suppress("UNCHECKED_CAST")
open fun createViewBinding(): VB {
    val actualGenericsClass = findActualGenericsClass<VB>(ViewBinding::class.java)
        ?: throw NullPointerException("Can not find a ViewBinding Generics in ${javaClass.simpleName}")
    try {
        val inflate = actualGenericsClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
        return inflate.invoke(null, layoutInflater) as VB
    } catch (e: NoSuchMethodException) {
        e.printStackTrace()
    } catch (e: IllegalAccessException) {
        e.printStackTrace()
    } catch (e: InvocationTargetException) {
        e.printStackTrace()
    }
}

/**
 * Create ViewModel
 *  @return  ViewModel
 */
@Suppress("UNCHECKED_CAST")
open fun createViewModel(): VM {
    val actualGenericsClass = findActualGenericsClass<VM>(BaseViewModel::class.java)
        ?: throw NullPointerException("Can not find a ViewModel Generics in ${javaClass.simpleName}")
    if (Modifier.isAbstract(actualGenericsClass.modifiers)) {
        throw IllegalStateException("$actualGenericsClass is an abstract class,abstract ViewModel class can not create a instance!")
    }
    // 判斷如果是 BaseAndroidViewModel,則使用 AppViewModelFactory 來生成
    if (BaseAndroidViewModel::class.java.isAssignableFrom(actualGenericsClass)) {
        return ViewModelProvider(this, AppViewModelFactory(application))[actualGenericsClass]
    }
    return ViewModelProvider(this)[actualGenericsClass]
}

} ```

創建 ViewModel 的過程是在 BaseActivity 中根據子類的泛型自動生成的。這裏使用了反射的方式來實現。如果覺得影響性能可以在子類中重寫createViewModel函數來自行生成 ViewModel。

另外,如果需要使用帶有 Application 的ViewModel,可以繼承 BaseAndroidViewModel,它的實現參照了 AndroidViewModel。

kotlin abstract class BaseAndroidViewModel<T : BaseRepository>(@field:SuppressLint("StaticFieldLeak") var application: Application) : BaseViewModel<T>()

這裏創建 BaseAndroidViewModel 需要用到 AppViewModelFactory。

kotlin class AppViewModelFactory(private val application: Application) : ViewModelProvider.NewInstanceFactory() { override fun <T : ViewModel> create(modelClass: Class<T>): T { return if (BaseAndroidViewModel::class.java.isAssignableFrom(modelClass)) { try { modelClass.getConstructor(Application::class.java).newInstance(application) } catch (e: NoSuchMethodException) { throw IllegalStateException("Cannot create an instance of $modelClass", e) } catch (e: IllegalAccessException) { throw IllegalStateException("Cannot create an instance of $modelClass", e) } catch (e: InstantiationException) { throw IllegalStateException("Cannot create an instance of $modelClass", e) } catch (e: InvocationTargetException) { throw IllegalStateException("Cannot create an instance of $modelClass", e) } } else super.create(modelClass) } }

假如 LoginViewModel 實現了 BaseAndroidViewModel ,那麼使用 ViewModelProvider 創建 LoginViewModel 時必須傳入 AppViewModelFactory 參數,而不是Jetpack ViewModel 庫中的 AndroidViewModelFactory。

kotlin ViewModelProvider(this, AppViewModelFactory(application))[LoginViewModel::class.java]

3. MVVM 封裝後的實例應用

在完成上述封裝後,我們來看下如何實現登錄的邏輯。

3.1 實現 Repository

kotlin class LoginRepository : BaseRepository() { /** * Login * @param username 用户名 * @param password 密碼 */ suspend fun login(username: String, password: String, responseLiveData: ResponseMutableLiveData<LoginResponse>, showLoading: Boolean = true) { executeRequest({ RetrofitManager.apiService.login(username, password) }, responseLiveData, showLoading) } }

RetrofitManager 用來創建管理 API Service:

```kotlin object RetrofitManager { private const val BASE_URL = "http://www.wanandroid.com/"

val apiService: ApiService by lazy {
    RetrofitCreator.createApiService(
        ApiService::class.java, BASE_URL
    )
}

} ```

3.2 ViewModel 實例

```kotlin class LoginViewModel : BaseViewModel() { // 提供給Model層設置數據 private val _loginLiveData: ResponseMutableLiveData = ResponseMutableLiveData()

// 提供給View層觀察數據
val loginLiveData: ResponseLiveData<LoginResponse> = _loginLiveData

/**
 * Login
 *  @param  username 用户名
 *  @param  password 密碼
 */
fun login(username: String, password: String) {
    viewModelScope.launch {
        repository.login(username, password, _loginLiveData)
    }
}

} ```

3.3 Activity/Fragment 實例

```kotlin class LoginActivity : BaseActivity() {

override fun onActivityCreated(savedInstanceState: Bundle?) {
    binding.tvLogin.setOnClickListener {
        viewModel.login("110120", "123456")
    }

    viewModel.loginLiveData.observe(this, object : ResponseObserver<LoginResponse>() {
        override fun onSuccess(data: LoginResponse?) {
            // Update UI
        }
    })
}

} ```

這裏需要注意的是,在Activity裏邊需要使用不可變的 LiveData,防止開發時候在View層通過 LiveData 來setValue/postValue,造成錯誤的UI更新問題。因此,這裏對LiveData做了一些改動,如下:

``kotlin abstract class ResponseLiveData<T> : LiveData<BaseResponse<T>> { /** * Creates a MutableLiveData initialized with the givenvalue`. * * @param value initial value */ constructor(value: BaseResponse?) : super(value)

/**
 * Creates a MutableLiveData with no value assigned to it.
 */
constructor() : super()

/**
 * Adds the given observer to the observers list within the lifespan of the given owner.
 * The events are dispatched on the main thread. If LiveData already has data set, it
 * will be delivered to the observer.
 */
fun observe(owner: LifecycleOwner, observer: ResponseObserver<T>) {
    super.observe(owner, observer)
}

} ```

ResponseLiveData 繼承自 LiveData,因此ResponseLiveData是不可變的。除此之外還定義了一個 ResponseMutableLiveData ,這是一個可變的 LiveData,代碼如下:

```kotlin class ResponseMutableLiveData : ResponseLiveData {

/**
 * Creates a MutableLiveData initialized with the given `value`.
 *
 *  @param  value initial value
 */
constructor(value: BaseResponse<T>?) : super(value)

/**
 * Creates a MutableLiveData with no value assigned to it.
 */
constructor() : super()

public override fun postValue(value: BaseResponse<T>?) {
    super.postValue(value)
}

public override fun setValue(value: BaseResponse<T>?) {
    super.setValue(value)
}

} ```

至此關於Jetpack MVVM的封裝及使用就結束了。

三、寫在最後的話

本篇文章比較詳細的講解了Android項目架構從MVC、MVP到MVVM的演化,對三種架構都列舉了詳細的實現例子。同時針對目前主流的Jetpack MVVM架構進行了封裝。當然,由於每個人對於架構的理解並不一定相同,所有文章中避免不了會有與讀者理解不一致的地方,歡迎留言討論。