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 = "https://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架構進行了封裝。當然,由於每個人對於架構的理解並不一定相同,所有文章中避免不了會有與讀者理解不一致的地方,歡迎留言討論。