前言
系列文章:
只需跟著Google學(xué)android:ViewModel篇
關(guān)于ViewModel的內(nèi)容,大概半年前已經(jīng)寫過兩篇內(nèi)容(但是建議看官方文檔):
上述官方文檔:ViewModel地址:https://developer.android.com/topic/libraries/architecture/viewmodel
我之前的文章:一點(diǎn)點(diǎn)入坑JetPack:ViewModel篇
官方文檔:Saved State module for ViewModel
上述官方文檔:Saved State module for ViewModel地址:https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate
我之前的文章:ViewModel的局限,銷毀重建的方案SavedStateHandle
之前寫的文章現(xiàn)在來看也是沒啥毛病,ViewModel該聊的基本也都聊到了。因此今天這篇文章更多的是對(duì)之前文章的補(bǔ)充:
- ViewModel存在的意義
- Android-KTX對(duì)ViewModel的增強(qiáng)
- ViewModel的錯(cuò)誤用法
正文
官方對(duì)ViewModel的定義:
- 1、類職責(zé):負(fù)責(zé)為界面準(zhǔn)備數(shù)據(jù)(意味著一切處理數(shù)據(jù)邏輯的業(yè)務(wù)代碼,應(yīng)該寫在ViewModel中)
- 2、在配置更改期間會(huì)自動(dòng)保留ViewModel對(duì)象:因此可以作為跨頁面(Fragment)通訊的基石

接下來我們進(jìn)一步深究一下這倆個(gè)定義:
一、ViewModel的意義
在Model/View/ViewModel(MVVM)模型中,ViewModel層是這樣的定義:
上述Model/View/ViewModel(MVVM)模型鏈接地址:https://docs.microsoft.com/zh-cn/archive/blogs/johngossman/introduction-to-modelviewviewmodel-pattern-for-building-wpf-apps
只有一小部分View層控件可以直接與Model層進(jìn)行數(shù)據(jù)綁定,尤其是在Model層是開發(fā)人員無法控制的情況下(由其他人提供)。該Model層提供的數(shù)據(jù)很可能是無法直接映射到控件上。 此外UI控件可能需要執(zhí)行復(fù)雜的操作,而這些代碼寫在View層中沒有意義(因?yàn)樗鼈儾粚儆赨I控件的邏輯),并且這些操作邏輯太過具體,也無法包含在Model層中(因?yàn)檫@也不是Model層應(yīng)該關(guān)心的) 。 所以,我們需要一個(gè)處理View層狀態(tài)的地方。
ViewModel層就是負(fù)責(zé)這些任務(wù)。ViewModel層包含將Model層數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換為View層數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)轉(zhuǎn)換器,并且包含View層可用于與Model層進(jìn)行交互的命令。
由上述定義,我們可以清晰的明確:ViewModel層是轉(zhuǎn)換層。用于把Model層的輸出數(shù)據(jù)轉(zhuǎn)換成View層使用的輸入數(shù)據(jù),把View層的輸出數(shù)據(jù)轉(zhuǎn)換成Model層使用的輸入數(shù)據(jù)。

咱們會(huì)發(fā)現(xiàn)MVVM中ViewModel的定義,和Google推出的ViewModel庫的第一個(gè)職責(zé)定義很類似。
因此,可以順其自然使用ViewModel來作為MVVM中ViewModel層的實(shí)現(xiàn)方案。既然使用ViewModel作為ViewModel層,那么它的一大意圖也就明確了:規(guī)范我們的代碼組織結(jié)構(gòu),告訴我們處理數(shù)據(jù)數(shù)據(jù)的代碼應(yīng)該寫在ViewModel中。
明確了第一大意圖,咱們?cè)倏匆豢瓷鲜?strong>第二大意圖:在配置更新期間,保存ViewModel實(shí)例。
短短的幾個(gè)字,有2個(gè)很重要的信息:
- 1、ViewModel實(shí)例的生命周期對(duì)Activity/Fragment長(zhǎng)。
- 2、ViewModel不能處理Activity銷毀重建的情況。
第一個(gè)信息意味著我們不能這么干:

如果需要context,可以使用
AndroidViewModel
第二個(gè)信息如何處理?詳見ViewModel的局限,銷毀重建的方案SavedStateHandle
二、Android-KTX
引用官方的一句話解釋一下什么叫KTX:
Android KTX 是包含在 Android Jetpack 及其他 Android 庫中的一組Kotlin 擴(kuò)展程序。
KTX 擴(kuò)展程序可以為 Jetpack、Android平臺(tái)及其他API提供簡(jiǎn)潔的慣用Kotlin代碼。為此,這些擴(kuò)展程序利用了多種 Kotlin 語言功能,其中包括:
- 擴(kuò)展函數(shù)
- 擴(kuò)展屬性
- Lambda
- 命名參數(shù)
- 參數(shù)默認(rèn)值
- 協(xié)程
KTX有很多,有興趣了解其他KTX的內(nèi)容,可以訪問官網(wǎng)。
上述官網(wǎng)地址:https://developer.android.google.cn/kotlin/ktx?hl=zh_cn#viewmodel
2.1、Fragment-KTX為我們提供了什么?
日常我們獲取到ViewModel實(shí)例時(shí),大概是這個(gè)樣子:
lateinit var viewModel: XXViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this)[XXViewModel::class.java]
}
使用帶SavedStateHandle還要這樣:
lateinit var viewModel: XXViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(
this,
SavedStateViewModelFactory(Application, this)
)[XXViewModel::class.java]
}
巨麻煩,而且這都是樣本代碼。如果我們引入Fragment-KTX:
dependencies {
implementation "androidx.fragment:fragment-ktx:1.2.4"
}
上述倆種初始化ViewModel的方式,可以簡(jiǎn)化為一個(gè)by關(guān)鍵字:
// Fragment級(jí)別的ViewModel
private val viewModel: XXViewModel by viewModels()
// Activity級(jí)別的ViewModel
private val viewModel: XXViewModel by activityViewModels()
2.2、ViewModel-KTX為我們提供了什么?
給我們提供了一協(xié)程環(huán)境viewModelScope,因此在ViewModel中,如果我們想要使用協(xié)程,可以直接:
viewModelScope.launch {
// ...
}
而且也不用擔(dān)心Job是否被cancel掉。
注意,協(xié)程是協(xié)作式的。不是說調(diào)了Job.canel()就萬事大吉了,我們還需要在對(duì)應(yīng)的launch中顯示的基于isActive去判斷當(dāng)前的協(xié)程是否存活(協(xié)程部分有機(jī)會(huì)再展開)。
三、ViewModel的錯(cuò)誤用法
聊完上述部分,我們?cè)倭囊涣腻e(cuò)誤或者是有坑的點(diǎn)。
3.1、AndroidViewModel中慎重進(jìn)行R.string.xxx
先上一段代碼:
public class MyViewModel extends AndroidViewModel {
public final MutableLiveData<String> statusLabel = new MutableLiveData<>();
public SampleViewModel(Application context) {
super(context);
statusLabel.setValue(context.getString(R.string.labelString));
}
}
這種用法的問題在于,ViewModel在配置更新的時(shí)候,并不會(huì)銷毀重建因此構(gòu)造函數(shù)不會(huì)重走。
因此如果此時(shí)需要?jiǎng)討B(tài)替換R.string.labelString,那么這種情況下是不正確的。
因此在ViewModel里動(dòng)態(tài)加載string,是有坑的,需要慎重。
3.2、ViewModel不能解決銷毀重建問題
銷毀重建,這個(gè)問題八成沒有人注意,但是一旦遇到這個(gè)問題,基本上是“致命”的。
不知道大家日常有沒有處理過:“一定”不為null的情況下,出現(xiàn)了空指針的crash。這種case歸咎于銷毀重建基本跑不了。
復(fù)現(xiàn)銷毀重建的場(chǎng)景很簡(jiǎn)單,在開發(fā)者選項(xiàng)中,開啟:不保留活動(dòng)。
因此,在ViewModel中存儲(chǔ)成員變量、Callback等行為。在銷毀重建的場(chǎng)景下都是很危險(xiǎn)的。那針對(duì)這種情況該怎么辦?
- 成員變量(如果業(yè)務(wù)場(chǎng)景不在意銷毀重建,可以無視。如果不能無視,回頭瞅一下開篇的文章。)
- Callback(下一篇LiveData篇,會(huì)結(jié)合Google的文章,展開一段合理的處理CallBack的方案)
尾聲
關(guān)于ViewModel的內(nèi)容,想聊的就這么多啦。
上述的內(nèi)容主要從兩個(gè)架構(gòu)和“坑”的角度展開,更多的是想輸出一種思路(希望各位同學(xué)能夠接受),先從官方文檔中思考技術(shù)出現(xiàn)的意義。有了這個(gè)基礎(chǔ)再看其他的網(wǎng)文,就可以取其精華去其糟粕。