Android-ViewModel 使用指北

Alvince楊小咩發表於2018-12-03

Android 開發近年越來越趨於成熟穩定,很多第三方元件也越來越完善,上半年 Google 爸爸釋出了名為 Jetpack 的工具包堪稱應用開發者的福音,這裡就討論一下這個開發套件中 ViewModel 到底是個什麼樣的存在。

什麼是 Jetpack

先說一下 Jetpack,官方對此介紹是幫助開發者專注應用的業務程式碼、更輕鬆地開發出色的應用程式的一個 Android 軟體開發元件集合

Jetpack is a collection of Android software components to make it easier for you to develop great Android apps.These components help you follow best practices, free you from writing boilerplate code, and simplify complex tasks, so you can focus on the code you care about.

裡面包含了幾乎所有開發 Android App 能用到的涉及程式架構、導航、UI、行為相關的元件庫

  • Foundation
    AppCompat, Android KTX, Multidex, Test
  • Architecture
    Data Binding, Lifecycles, LiveData, Navigation, Paging, Room, ViewModel, WorkManager
  • Behavior
    Download manager, Media &
    playback
    , Notifications, Permissions, Preferences, Sharing, Slices
  • UI
    Animation &
    transitions
    , Auto, Emoji, Fragment, Layout, Palette, TV, Wear OS by Google

在這裡就來分析一下 ViewModel 到底應該怎麼使用

Android 應用程式架構

討論 ViewModel 之前需要先弄清楚一個問題,那就是通常在 Android App 開發中使用軟體應用程式架構。
Google 爸爸同樣釋出了一系列軟體架構的示例參考 ? android-architecture

在 8012 年的今天,MVC 這種上古時代的移動軟體架構應該已經淘汰的差不多了,先說一下 MVP 這個現在幾乎軟體必備的軟體架構。

MVP

所謂 MVP 就是把程式程式碼 按照 model-view-presenter 分層解耦
Presenter 通常是通過對 網路層呼叫、資料解析、業務處理以及其他邏輯程式碼的封裝
然後通過對 View 層介面的呼叫來通知 UI 介面更新
通過將 資料檢視邏輯 三層程式碼隔離來達到解耦的目的
但是這種架構有個問題,那就是 Presenter 非常依賴 View 層的介面

這種模式下,邏輯-檢視之間的通訊完全依賴於 View 層的介面,一旦業務發生了變更,需要修改 View 層介面的宣告、Presenter 對 View 通知呼叫的入口、以及 Activity / Fragment 等檢視介面的實現

MVVM

那麼在這種情況下 Android 開發的關注焦點轉移到了 MVVM 架構模式
這種模式其實是對 MVC 的另一層變種,這裡的 VM 指的就是 ViewModel(跟下面討論的 Architecture ViewModel 不一樣)
區別於 MVP 對業務邏輯的完全抽離,MVVM 在檢視之上抽象出了一個 ViewModel 的概念,這個層面關注了介面檢視所關聯的 資料 + 邏輯 的操作

有過網頁前端或者 iOS 相關開發經驗的同學應該能夠了解,其實就是 Data Binding 這種開發模式
通過將資料繫結到檢視層,更新相應的資料 Model 來觸發UI檢視的狀態更新
一個典型的場景就是基於 React 環境的開發,通過改變元件的 propsstate 來通知 UI 檢視重新整理顯示內容

關於 MVVM 的更多內容這裡不展開細說,下面開始討論一下 Android Jetpack 架構元件中 ViewModel 的使用

ViewModel

ViewModel 這個元件是什麼?

官方介紹是這樣的:

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way.The ViewModel class allows data to survive configuration changes such as screen rotations.

這裡明確說明了 ViewModel 是出於管理和儲存 UI 相關資料的目的,通過對 Android 元件生命週期感知的方式而設計出來的一個元件,並且允許在裝置的配置發生變更(如螢幕旋轉)時繼續保持。

這樣的話最直接的好處就是開發者不再需要關心 Activity / Fragment 的相關狀態資料持久化(saveInstanceStatus)的問題了,能夠專注於產品業務的開發,避免快取恢復資料這種重複模板化操作。

這是官方文件上對 ViewModel 如何工作的示意圖 ?

Illustrates the lifecycle of a ViewModel as an activity changes state.viewmodel-loaderviewmodel-replace-loader

可以看到 ViewModel 跟隨 Activity 建立之後會一直在在記憶體中生存並且工作
直到與其關聯的 Activity 銷燬 destroy 時,會觸發一個叫 onCleared 的生命週期函式,最後宣告死亡。

ViewModel 的生命週期感知依賴於 Jetpack 的另一個元件 Lifecycles關於 Lifecycles 這裡不詳細展開,只需明確 Lifecycles 通過對 Activity 生命週期的捕獲來觸發生命週期事件的上報來達到監控生命週期的目的

基於 Lifecycles 意味著開發者不再需要手動關注元件的生命週期問題,那麼 ViewModel 是如何感知並且同步應用元件的生命週期呢?

通過檢視 ViewModel 原始碼可以看出,它本身其實就是一個普通的 Java Bean,沒有任何複雜的地方

public abstract class ViewModel { 
/** * This method will be called when this ViewModel is no longer used and will be destroyed. * <
p>
* It is useful when ViewModel observes some data and you need to clear this subscription to * prevent a leak of this ViewModel. */
@SuppressWarnings("WeakerAccess") protected void onCleared() {

}
}複製程式碼

那麼問題來了,這樣只怎麼實現資料和生命週期的管理呢

7d5e164647c71a88dca23044abe0242a.png

翻看 viewmodel 的原始碼會發現這個庫實在是簡單到了極點,只有幾個類和介面,這就是為什麼要使用 Jetpack 元件是應該依賴 AppCompat 相容套件了
因為 Jetpack 實現生命週期相關的元件其實是基於 support-v4 來實現,通過 FragmentActivity 來實現元件相關的介面來完成對 ViewModel 的管理的
至於具體是怎麼實現這個管理過程,這裡暫不細講,有興趣可以自行閱讀 FragmentActivity 的原始碼

建立 ViewModel

要使用 ViewModel 來管理資料,首先需要宣告一個 model 類來整合 android.arch.lifecycle.ViewModel

class SampleModel : ViewModel() { 
var profile: UserInfo? = null override fun onCleared() {
profile = null
}
}複製程式碼

回到正題,實際開發中要怎麼來使用 ViewModel 呢?庫中提供了一個 ViewModelProviders 的工具類來幫助建立 ViewModel

a9e53786ae08929990cafda692b4b330.png
class SampleActivity : AppCompatActivity() { 
val viewModel by lazy {
ViewModelProviders.of(activity).get(SimpleModel::class.java)
}

}複製程式碼

這段程式碼在需要使用到 viewmodel 的時候通過 ViewModelProfiders#of(FragmentActivity) 來建立一個 ViewModelProvider,然後呼叫 get 方法建立出所需要的 model 例項
非常簡單地程式碼,到這裡 viewmodel 看上去好像已經說完了

鹽鵝怎麼可能呢,講到這裡好像跟 MVVM 還是沒什麼太大的關係
那麼這裡需要介紹 Jetpack Architecture 的另一個重要的元件 LiveData

LiveData

還是先看官方說明

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services.This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.

這裡已經清晰的介紹了 LiveData 是一個可觀察的資料持有類,而且跟普通的觀察者不一樣的地方在於
LiveData 可以感知 Android 系統元件生命週期,並且只在觀察者元件處於活躍狀態的時候才通知更新

看到這裡是不是就想起來前面說的資料繫結操作?沒錯,這樣程式碼就變成了介個樣子

class SampleModel : ViewModel() { 
val profileData = MutableLiveData<
UserInfo>
() var profile: UserInfo? = null override fun onCleared() {
profile = null
} fun fetchUserInfo() {
// 網路或本地持久化資料獲取使用者資訊// 獲取成功後使用 LiveData 通知訂閱端 profileData.postValue(profile)
}
}class SampleActivity : AppCompatActivity() {
val viewModel by lazy {
ViewModelProviders.of(activity).get(SampleModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) … viewModel.profileData.observe(this, Observer {
it?.also {
// it ->
接收到的 UserInfo
… … /* 更新 UI */
}
})
}
}複製程式碼

這樣一來,ViewModel 只需要扮演資料持有者以及相關業務控制器,不用像 Presenter 那樣需要關心 View 層的介面變化了,而且能夠有效避免因 Presenter 持有 View 引用造成的記憶體洩漏問題

這裡簡單看一下 LiveData 的程式碼

/** * Posts a task to a main thread to set the given value. So if you have a following code * executed in the main thread: * <
pre class="prettyprint">
* liveData.postValue("a");
* liveData.setValue("b");
* <
/pre>
* The value "b" would be set at first and later the main thread would override it with * the value "a". * <
p>
* If you called this method multiple times before a main thread executed a posted task, only * the last value would be dispatched. * * @param value The new value */
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;

} if (!postTask) {
return;

} ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);

}/** * Sets the value. If there are active observers, the value will be dispatched to them. * <
p>
* This method must be called from the main thread. If you need set a value from a background * thread, you can use {@link #postValue(Object)
} * * @param value The new value */
@MainThreadprotected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);

}複製程式碼

可以看出 LiveData 通過設定 value 來驅動資料變化事件,內部通過分發新的 data 通知到訂閱者(通常是UI介面的資料渲染程式碼)
這裡有兩個相關的方法:setValue 以及 postValue
檢視 postValue 方法最後一行可以看出通過 post 來改變資料時,內部會通過一個任務排程器來事件分發到主執行緒,
setValue 方法通過註解宣告以及首行的斷言檢查可知,此方法需要在主執行緒中呼叫,這意味著訂閱的 Observer 始終會在主執行緒中響應

這兩個方法的可見性修飾是 protected,意味著開發者不能直接呼叫,所以 LiveData 提供了一個子類 MutableLiveData

/** * {@link LiveData
} which publicly exposes {@link #setValue(T)
} and {@link #postValue(T)
} method. * * @param <
T>
The type of data hold by this instance */
public class MutableLiveData<
T>
extends LiveData<
T>
{
@Override public void postValue(T value) {
super.postValue(value);

} @Override public void setValue(T value) {
super.setValue(value);

}
}複製程式碼

程式碼非常簡單,只是單純的修改了這個兩個更新資料的方法的修飾,暴露給了開發者呼叫
當我們需要改變資料的時候只需要呼叫一下 postValue 方法就可以通知檢視訂閱來更新檢視顯示的內容和狀態了,就像 React 中 對 propsstat 賦值一樣簡單

所以 … 趕緊開始擁抱、以及享受 ViewModel + LiveData 帶來的便捷吧

來源:https://juejin.im/post/5c04a4315188253b6e5c43ca

相關文章