只需跟着Google學android:ViewModel篇
前言
關於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的內容,想聊的就這麼多啦。
上述的內容主要從兩個架構和“坑”的角度展開,更多的是想輸出一種思路(希望各位同學能夠接受),先從官方文檔中思考技術出現的意義。有了這個基礎再看其他的網文,就可以取其精華去其糟粕。