前言

關於ViewModel的內容,大概半年前已經寫過兩篇內容(但是建議看官方文檔):

官方文檔:ViewModel

上述官方文檔:ViewModel地址:https://developer.android.com/topic/libraries/architecture/viewmodel我之前的文章:一點點入坑JetPack:ViewModel篇

官方文檔:Saved State module for ViewModel

上述官方文檔:Saved State module for ViewModel地址:https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate我之前的文章:ViewModel的侷限,銷燬重建的方案SavedStateHandle

之前寫的文章現在來看也是沒啥毛病,ViewModel該聊的基本也都聊到了。因此今天這篇文章更多的是對之前文章的補充:

ViewModel存在的意義Android-KTX對ViewModel的增強ViewModel的錯誤用法正文

官方對ViewModel的定義:

1、類職責:負責爲界面準備數據(意味着一切處理數據邏輯的業務代碼,應該寫在ViewModel中)2、在配置更改期間會自動保留ViewModel對象:因此可以作爲跨頁面(Fragment)通訊的基石

接下來我們進一步深究一下這倆個定義:

一、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層進行數據綁定,尤其是在Model層是開發人員無法控制的情況下(由其他人提供)。該Model層提供的數據很可能是無法直接映射到控件上。此外UI控件可能需要執行復雜的操作,而這些代碼寫在View層中沒有意義(因爲它們不屬於UI控件的邏輯),並且這些操作邏輯太過具體,也無法包含在Model層中(因爲這也不是Model層應該關心的) 。所以,我們需要一個處理View層狀態的地方。

ViewModel層就是負責這些任務。ViewModel層包含將Model層數據結構轉換爲View層數據結構的數據轉換器,並且包含View層可用於與Model層進行交互的命令。

由上述定義,我們可以清晰的明確:ViewModel層是轉換層。用於把Model層的輸出數據轉換成View層使用的輸入數據,把View層的輸出數據轉換成Model層使用的輸入數據。

咱們會發現MVVM中ViewModel的定義,和Google推出的ViewModel庫的第一個職責定義很類似。

因此,可以順其自然使用ViewModel來作爲MVVM中ViewModel層的實現方案。既然使用ViewModel作爲ViewModel層,那麼它的一大意圖也就明確了:規範我們的代碼組織結構,告訴我們處理數據數據的代碼應該寫在ViewModel中。

明確了第一大意圖,咱們再看一看上述第二大意圖:在配置更新期間,保存ViewModel實例。

短短的幾個字,有2個很重要的信息:

1、ViewModel實例的生命週期對Activity/Fragment長。2、ViewModel不能處理Activity銷燬重建的情況。第一個信息意味着我們不能這麼幹:

如果需要context,可以使用AndroidViewModel

第二個信息如何處理?詳見ViewModel的侷限,銷燬重建的方案SavedStateHandle

二、Android-KTX

引用官方的一句話解釋一下什麼叫KTX:

Android KTX 是包含在 Android Jetpack 及其他 Android 庫中的一組Kotlin 擴展程序。

KTX 擴展程序可以爲 Jetpack、Android平臺及其他API提供簡潔的慣用Kotlin代碼。爲此,這些擴展程序利用了多種 Kotlin 語言功能,其中包括:

擴展函數擴展屬性Lambda命名參數參數默認值協程KTX有很多,有興趣瞭解其他KTX的內容,可以訪問官網。

上述官網地址:https://developer.android.google.cn/kotlin/ktx?hl=zh_cn#viewmodel

2.1、Fragment-KTX爲我們提供了什麼?

日常我們獲取到ViewModel實例時,大概是這個樣子:

lateinitvar viewModel: XXViewModeloverridefunonCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) viewModel = ViewModelProviders.of(this)[XXViewModel::class.java]}使用帶SavedStateHandle還要這樣:

lateinitvar viewModel: XXViewModeloverridefunonCreate(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關鍵字:

// Fragment級別的ViewModelprivateval viewModel: XXViewModel by viewModels()// Activity級別的ViewModelprivateval viewModel: XXViewModel by activityViewModels()2.2、ViewModel-KTX爲我們提供了什麼?

給我們提供了一協程環境viewModelScope,因此在ViewModel中,如果我們想要使用協程,可以直接:

viewModelScope.launch {// ...}而且也不用擔心Job是否被cancel掉。

注意,協程是協作式的。不是說調了Job.canel()就萬事大吉了,我們還需要在對應的launch中顯示的基於isActive去判斷當前的協程是否存活(協程部分有機會再展開)。

三、ViewModel的錯誤用法

聊完上述部分,我們再聊一聊錯誤或者是有坑的點。

3.1、AndroidViewModel中慎重進行R.string.xxx

先上一段代碼:

publicclassMyViewModelextendsAndroidViewModel{publicfinal MutableLiveData<String> statusLabel = new MutableLiveData<>();publicSampleViewModel(Application context){super(context); statusLabel.setValue(context.getString(R.string.labelString)); }}這種用法的問題在於,ViewModel在配置更新的時候,並不會銷燬重建因此構造函數不會重走。

因此如果此時需要動態替換R.string.labelString,那麼這種情況下是不正確的。

因此在ViewModel裏動態加載string,是有坑的,需要慎重。

3.2、ViewModel不能解決銷燬重建問題

銷燬重建,這個問題八成沒有人注意,但是一旦遇到這個問題,基本上是“致命”的。

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

不知道大家日常有沒有處理過:“一定”不爲null的情況下,出現了空指針的crash。這種case歸咎於銷燬重建基本跑不了。

復現銷燬重建的場景很簡單,在開發者選項中,開啓:不保留活動。

因此,在ViewModel中存儲成員變量、Callback等行爲。在銷燬重建的場景下都是很危險的。那針對這種情況該怎麼辦?

成員變量(如果業務場景不在意銷燬重建,可以無視。如果不能無視,回頭瞅一下開篇的文章。)Callback(下一篇LiveData篇,會結合Google的文章,展開一段合理的處理CallBack的方案)尾聲

關於ViewModel的內容,想聊的就這麼多啦。

上述的內容主要從兩個架構和“坑”的角度展開,更多的是想輸出一種思路(希望各位同學能夠接受),先從官方文檔中思考技術出現的意義。有了這個基礎再看其他的網文,就可以取其精華去其糟粕。

相關文章