Android 架構思想與 MVVM 框架封裝
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介面上,其流轉圖如下圖所示。
根據這三部分內容,我們可以將程式碼分為三層,即最初的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 的結構圖如下圖所示。
在 Android 專案的 MVC 架構中由於 Activity 同時充當了 View 層與 Controller 層兩個角色,所以 Android 中的 MVC 更像下面的這種結構:
基於上圖,我們可以以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 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 的結構圖如下圖所示。
從上圖中可以看出,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 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 架構的結構圖如下。
MVVM架構的本質是資料驅動,它的最大的特點是單向依賴。MVVM架構通過觀察者模式讓ViewModel與View解耦,實現了View依賴ViewModel,ViewModel依賴Model的單向依賴。
接下來我們仍然以登入為例,通過觀察者模式來實現MVVM的架構程式碼。
(1)觀察者模式
由於 MVVM 架構需要將 ViewModel 與 View 解耦。因此,這裡可以使用觀察者模式來實現。下面實現觀察者模式的程式碼。
- 實現觀察者介面
```java
public interface Observer
// 失敗的回撥
void onFailed(String msg);
} ```
- 抽象被觀察者
```java
public abstract class Observable
// 取消註冊
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 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
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
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
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
// 提供給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 given
value`.
*
* @param value initial value
*/
constructor(value: BaseResponse
/**
* 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
/**
* 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架構進行了封裝。當然,由於每個人對於架構的理解並不一定相同,所有文章中避免不了會有與讀者理解不一致的地方,歡迎留言討論。