前言
系列文章:
只需跟著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該聊的基本也都聊到了寂屏。因此今天這篇文章更多的是對之前文章的補(bǔ)充:
- ViewModel存在的意義
- Android-KTX對ViewModel的增強(qiáng)
- ViewModel的錯誤用法
正文
官方對ViewModel的定義:
- 1专酗、類職責(zé):負(fù)責(zé)為界面準(zhǔn)備數(shù)據(jù)(意味著一切處理數(shù)據(jù)邏輯的業(yè)務(wù)代碼,應(yīng)該寫在ViewModel中)
- 2机隙、在配置更改期間會自動保留ViewModel對象:因此可以作為跨頁面(Fragment)通訊的基石
接下來我們進(jìn)一步深究一下這倆個定義:
一纽谒、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)心的) 。 所以肆捕,我們需要一個處理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ù)。
咱們會發(fā)現(xiàn)MVVM中ViewModel的定義润梯,和Google推出的ViewModel庫的第一個職責(zé)定義很類似过牙。
因此,可以順其自然使用ViewModel來作為MVVM中ViewModel層的實(shí)現(xiàn)方案纺铭。既然使用ViewModel作為ViewModel層寇钉,那么它的一大意圖也就明確了:規(guī)范我們的代碼組織結(jié)構(gòu),告訴我們處理數(shù)據(jù)數(shù)據(jù)的代碼應(yīng)該寫在ViewModel中舶赔。
明確了第一大意圖扫倡,咱們再看一看上述第二大意圖:在配置更新期間,保存ViewModel實(shí)例竟纳。
短短的幾個字撵溃,有2個很重要的信息:
- 1、ViewModel實(shí)例的生命周期對Activity/Fragment長蚁袭。
- 2征懈、ViewModel不能處理Activity銷毀重建的情況。
第一個信息意味著我們不能這么干:
如果需要context揩悄,可以使用
AndroidViewModel
第二個信息如何處理?詳見ViewModel的局限鬼悠,銷毀重建的方案SavedStateHandle
二删性、Android-KTX
引用官方的一句話解釋一下什么叫KTX:
Android KTX 是包含在 Android Jetpack 及其他 Android 庫中的一組Kotlin 擴(kuò)展程序亏娜。
KTX 擴(kuò)展程序可以為 Jetpack、Android平臺及其他API提供簡潔的慣用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í)例時,大概是這個樣子:
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的方式,可以簡化為一個by關(guān)鍵字:
// Fragment級別的ViewModel
private val viewModel: XXViewModel by viewModels()
// Activity級別的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()就萬事大吉了,我們還需要在對應(yīng)的launch中顯示的基于isActive去判斷當(dāng)前的協(xié)程是否存活(協(xié)程部分有機(jī)會再展開)功氨。
三序苏、ViewModel的錯誤用法
聊完上述部分,我們再聊一聊錯誤或者是有坑的點(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在配置更新的時候跺涤,并不會銷毀重建因此構(gòu)造函數(shù)不會重走匈睁。
因此如果此時需要動態(tài)替換R.string.labelString
,那么這種情況下是不正確的桶错。
因此在ViewModel里動態(tài)加載string航唆,是有坑的,需要慎重院刁。
3.2糯钙、ViewModel不能解決銷毀重建問題
銷毀重建,這個問題八成沒有人注意,但是一旦遇到這個問題,基本上是“致命”的任岸。
不知道大家日常有沒有處理過:“一定”不為null的情況下再榄,出現(xiàn)了空指針的crash。這種case歸咎于銷毀重建基本跑不了享潜。
復(fù)現(xiàn)銷毀重建的場景很簡單困鸥,在開發(fā)者選項(xiàng)中,開啟:不保留活動剑按。
因此疾就,在ViewModel中存儲成員變量、Callback等行為艺蝴。在銷毀重建的場景下都是很危險(xiǎn)的猬腰。那針對這種情況該怎么辦?
- 成員變量(如果業(yè)務(wù)場景不在意銷毀重建吴趴,可以無視漆诽。如果不能無視,回頭瞅一下開篇的文章锣枝。)
- Callback(下一篇LiveData篇厢拭,會結(jié)合Google的文章,展開一段合理的處理CallBack的方案)
尾聲
關(guān)于ViewModel的內(nèi)容撇叁,想聊的就這么多啦供鸠。
上述的內(nèi)容主要從兩個架構(gòu)和“坑”的角度展開,更多的是想輸出一種思路(希望各位同學(xué)能夠接受)陨闹,先從官方文檔中思考技術(shù)出現(xiàn)的意義楞捂。有了這個基礎(chǔ)再看其他的網(wǎng)文,就可以取其精華去其糟粕趋厉。