Android
開發近年愈來愈趨於成熟穩定,不少第三方組件也愈來愈完善,上半年 Google
爸爸發佈了名爲 Jetpack
的工具包堪稱應用開發者的福音,這裏就討論一下這個開發套件中 ViewModel
究竟是個什麼樣的存在。前端
先說一下 Jetpack,官方對此介紹是幫助開發者專一應用的業務代碼、更輕鬆地開發出色的應用程序的一個 Android 軟件開發組件集合java
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
裏面包含了幾乎全部開發 Android App 能用到的涉及程序架構、導航、UI、行爲相關的組件庫git
AppCompat
, Android KTX
, Multidex
, Test
Data Binding
, Lifecycles
, LiveData
, Navigation
, Paging
, Room
, ViewModel
, WorkManager
Download manager
, Media & playback
, Notifications
, Permissions
, Preferences
, Sharing
, Slices
Animation & transitions
, Auto
, Emoji
, Fragment
, Layout
, Palette
, TV
, Wear OS by Google
在這裏就來分析一下 ViewModel
到底應該怎麼使用github
討論 ViewModel 以前須要先弄清楚一個問題,那就是一般在 Android App 開發中使用軟件應用程序架構。
Google 爸爸一樣發佈了一系列軟件架構的示例參考 👉 android-architecture緩存
在 8012 年的今天,MVC 這種上古時代的移動軟件架構應該已經淘汰的差很少了,先說一下 MVP 這個如今幾乎軟件必備的軟件架構。網絡
所謂 MVP
就是把程序代碼 按照 model-view-presenter 分層解耦
Presenter
一般是經過對 網絡層調用、數據解析、業務處理以及其餘邏輯代碼的封裝
而後經過對 View
層接口的調用來通知 UI
界面更新
經過將 數據、視圖、邏輯 三層代碼隔離來達到解耦的目的
可是這種架構有個問題,那就是 Presenter
很是依賴 View
層的接口架構
這種模式下,邏輯-視圖之間的通信徹底依賴於 View 層的接口,一旦業務發生了變動,須要修改 View 層接口的聲明、Presenter 對 View 通知調用的入口、以及 Activity
/ Fragment
等視圖接口的實現app
那麼在這種狀況下 Android
開發的關注焦點轉移到了 MVVM
架構模式
這種模式實際上是對 MVC
的另外一層變種,這裏的 VM
指的就是 ViewModel
(跟下面討論的 Architecture ViewModel 不同)
區別於 MVP
對業務邏輯的徹底抽離,MVVM
在視圖之上抽象出了一個 ViewModel
的概念,這個層面關注了界面視圖所關聯的 數據 + 邏輯 的操做ide
有過網頁前端或者 iOS 相關開發經驗的同窗應該可以瞭解,其實就是 Data Binding 這種開發模式
經過將數據綁定到視圖層,更新相應的數據 Model 來觸發UI視圖的狀態更新
一個典型的場景就是基於 React
環境的開發,經過改變組件的 props
和 state
來通知 UI 視圖刷新顯示內容
關於 MVVM
的更多內容這裏不展開細說,下面開始討論一下 Android Jetpack
架構組件中 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 如何工做的示意圖 👇
能夠看到 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() {
}
}
複製代碼
那麼問題來了,這樣只怎麼實現數據和生命週期的管理呢
翻看 viewmodel 的源代碼會發現這個庫實在是簡單到了極點,只有幾個類和接口,這就是爲何要使用 Jetpack
組件是應該依賴 AppCompat
兼容套件了
由於 Jetpack
實現生命週期相關的組件實際上是基於 support-v4 來實現,經過 FragmentActivity
來實現組件相關的接口來完成對 ViewModel
的管理的
至於具體是怎麼實現這個管理過程,這裏暫不細講,有興趣能夠自行閱讀 FragmentActivity
的源碼
要使用 ViewModel
來管理數據,首先須要聲明一個 model 類來集成 android.arch.lifecycle.ViewModel
class SampleModel : ViewModel() {
var profile: UserInfo? = null
override fun onCleared() {
profile = null
}
}
複製代碼
回到正題,實際開發中要怎麼來使用 ViewModel 呢? 庫中提供了一個 ViewModelProviders
的工具類來幫助建立 ViewModel
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 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 */
@MainThread
protected 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 中 對 props
和 stat
賦值同樣簡單
因此 … 趕忙開始擁抱、以及享受 ViewModel + LiveData 帶來的便捷吧