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 &
,
playbackNotifications
,Permissions
,Preferences
,Sharing
,Slices
- UI
Animation &
,
transitionsAuto
,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
環境的開發,通過改變元件的 props
和 state
來通知 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 如何工作的示意圖 ?
可以看到 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
要使用 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
還是先看官方說明
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 中 對 props
和 stat
賦值一樣簡單
所以 … 趕緊開始擁抱、以及享受 ViewModel + LiveData 帶來的便捷吧