只需跟著Google學(xué)android:ViewModel篇

前言

系列文章:
只需跟著Google學(xué)android:ViewModel篇

關(guān)于ViewModel的內(nèi)容,大概半年前已經(jīng)寫過兩篇內(nèi)容(但是建議看官方文檔):

官方文檔:ViewModel

上述官方文檔: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)通訊的基石
image.png

接下來我們進(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ù)。

image.png

咱們會(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è)信息意味著我們不能這么干:

image.png

如果需要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è)問題,基本上是“致命”的。

上述銷毀重建地址:https://developer.android.com/topic/libraries/architecture/saving-states#use_onsaveinstancestate_as_backup_to_handle_system-initiated_process_death

不知道大家日常有沒有處理過:“一定”不為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)文,就可以取其精華去其糟粕。

個(gè)人公眾號(hào):咸魚正翻身
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容