沉思篇-剖析Jetpack的ViewModel

来源:https://www.cnblogs.com/honguilee/archive/2023/06/13/17478573.html
-Advertisement-
Play Games

> ViewModel做為架構組件的三元老之一,是實現MVVM的有力武器。 ### ViewModel的設計目標 ViewModel的基本功能就是管理UI的數據。其實,從職責上來說,這又是對Activity和Fragment的一次功能拆分。以前存儲在它們內部的數據,需要它們自己處理創建,更新,存儲, ...


ViewModel做為架構組件的三元老之一,是實現MVVM的有力武器。

ViewModel的設計目標

ViewModel的基本功能就是管理UI的數據。其實,從職責上來說,這又是對Activity和Fragment的一次功能拆分。以前存儲在它們內部的數據,需要它們自己處理創建,更新,存儲,恢復的所有過程,同時它們還要處理UI的數據綁定,更新,動畫等操作。職責的多元化就容易出現不好定位和調試的問題。另外,Activity和Fragment作為UI的承載者,很多時候需要共用數據和復用功能。而UI的差異讓復用的粒度劃分很難把控,容易寫出擴展性差的代碼。基於這些痛點,ViewModel被設計出來了。
同時ViewModel還將保存數據的功能強化了——它將設備配置變更後數據保存和恢復自動化了,在UI生命周期內都能保證數據的有效性,這大大減少了樣板代碼的編寫,提高了開發效率。

ViewModel的架構設計

ViewModel用了兩種粒度劃分來完成數據管理功能。 第一層是對ViewModel自身存儲數據的管理。目標就是完成ViewModel的創建,對應的抽象實體是ViewModelProvider.Factory。第二層則是對已存在的ViewModel組的管理,目標就是保證意外情況下ViewModel的有效性,對應的抽象實體是ViewModelStore。當然,這些都只是概念上的抽象,還需要一個粘合劑把它們的抽象層級體現出來,這就是ViewModelProvider。這三個主體類共同搭建了ViewModel的體系框架。剩下的類都是對這三個概念的補充和完善。接下來我將分別以這些抽象為主線,逐層分析它們的實現邏輯。

ViewModel的組管理

前面也提到過,ViewModelStore是完成組管理的,那麼我們首先應該確定的是組的概念,也就是這些ViewModel都歸屬於誰的問題。這不難理解,要管理組,那就必須得找到組的主人啊,由此引申出了ViewModelStoreOwner,它代表著某個擁有組管理許可權的對象,通過它提供的ViewModelStore對象就能對裡面的ViewModel進行管理了,同時這些ViewModel也就共同形成了組。所以ViewModelStoreOwner其實就是組的抽象實體,它代表著某個組,也是管理分組的單位。
ViewModel有兩個預設實現的組——ComponentActivity和Fragment。也就是說ComponentActivity和Fragment都實現了ViewModelStoreOwner這個介面。
先來看ComponentActivity的實現。根據介面,首先查看介面方法getViewModelStore的實現。裡面主要涉及到兩個對象,一個就是ViewModelStore的引用mViewModelStore,另一個就很有意思了,它是一個NonConfigurationInstances對象,這是一個簡單類,就是保存ViewModelStore對象的。那麼它特殊在什麼地方呢?它是onRetainNonConfigurationInstance方法的返回對象。

插一個課外知識科普,onRetainNonConfigurationInstance是Activity的一個方法,這個方法是設備配置發生變化(如橫豎屏切換的時候)時被系統自動調用的,用於用戶保存數據。只要這個方法返回的對象,在設備配置放生改變時都不會被銷毀。稍後在重建完成後,可以通過getLastNonConfigurationInstance方法獲取到。

接著回到getViewModelStore的實現,剛纔說到NonConfigurationInstances對象,它是通過調用getLastNonConfigurationInstance方法獲得的。如果方法返回了有效的對象,說明Activity被重建了,就直接獲取保存在NonConfigurationInstances對象中的值,然後更新mViewModelStore。否則就說明還沒有有效的ViewModelStore對象,則直接創建。從這個邏輯不難看出,我們的ViewModel不會隨著設備變化而重建,這正好滿足了我們的設計目標。那麼對於Fragment,它的實現又是怎樣的呢。
Fragment的實現比較曲折,它直接委托給了FragmentManager,又委托給了FragmentManagerViewModel的getViewModelStore方法,方法實現也很簡單,就是對HashMap查找,沒有就創建新的。這顯然不是我們想看到的,因為這裡並沒有和Activity類似的處理狀態變更的邏輯。那麼唯一的突破點就是那個HashMap對象了。搜索一圈發現,它會作為getSnapshot方法的返回值返回,有點Activity那味了。往上回溯,會發現它最終就是作為不銷毀的對象,在Fragment銷毀前保存下來了。
以上就是兩種應用場景下ViewModelStore的創建邏輯,另外,還有清除邏輯沒有講到。這個邏輯本質上就是調用ViewModelStore的clear方法,唯一的問題就是確定調用時機。具體來說就是,Activity通過註冊Lifecycle的狀態監聽,在Lifecycle.Event.ON_DESTROY的時候,調用了clear方法,而Fragment則是繼續通過FragmentManager的desctory方法作為調用的入口點。在FragmentManagerViewModel里完成了方法調用。
總結一下,ViewModelStoreOwner是對ViewModel組的一種抽象。雖然對應著兩個不同的實現,但是殊途同歸,最終的目的就是保證在設備配置發生變化的時候對應ViewModelStore對象的有效性, 從而保證ViewModel對象的有效性。同時在真正需要銷毀的時候做好清理工作。這就是這ViewModel的組管理功能。

ViewModel的創建管理

ViewModel用ViewModelProvider.Factory來管理創建過程。具體來說就是怎樣根據一個ViewModel子類的類信息創建對應的對象。這有兩個難點——必要的依賴註入、數據的恢復。對於依賴註入,ViewModel還是耍了老把戲,和創建ViewModelStore類似,提供了HasDefaultViewModelProviderFactory的一個抽象,把依賴註入轉移到了ComponentActivity和Fragment中。之所以這麼做,是因為在創建ViewModel的過程中,可能需要使用到Application和Bundle等信息,而這些信息是只能在在Activity和Fragment中才能獲取到的。數據恢復則是關註怎樣利用現有的數據將對象恢復到原來的狀態。當然這些過程其實都可以沒有,不需要傳遞Application或者Bundle對象,不需要恢復ViewModel狀態,則庫提供了預設的實現。就是簡單的調用反射創建對象而已。
針對剛纔說的各種情況,ViewModelProvider.Factory有多個實現,那麼實際上它到底是使用哪個實現呢,我們得從ViewModelProvider中尋找答案。在它的構造方法里,會對ViewModelStoreOwner做類型判斷,假如它是HasDefaultViewModelProviderFactory的實例,則使用實例返回的對象,否則預設的實現。結合上面的分析,讓我們繼續到ComponentActivity和Fragment中尋找答案。不看不知道,一看嚇一跳,它們竟然都是使用了SavedStateViewModelFactory類,那麼我們一起來看看它是怎麼實現的吧。
在構建SavedStateViewModelFactory對象的時候,會傳入三個對象——Application,SavedStateRegistryOwner,Bundle,這三個對象中最重要的就是第二個,它的主要功能就是提供在SavedStateRegistry對象,這個對象會在合適的時候保存數據,然後在合適的時候再恢復過來。它也是生命周期感知的組件。在它的create方法里,也是通過反射構建ViewModel對象的,唯一的不同就是反射多了個參數。接著往下看,最終會利用這些信息構造出SavedStateHandle對象,這個對象就是真正對我們當前創建的ViewModel對象有用的信息。SavedStateHandle提供了根據鍵值對保存數據的方法,也提供了查詢方法,所以ViewModel可以根據這個對象,恢復自己的LiveData數據,最重要的,這個類還提供了LiveData的另一個子類SavingStateLiveData,能自動處理數據保存的問題。
一句話總結,ComponentActivity和Fragment會使用SavedStateViewModelFactory對象作為ViewModelProvider中的Factory來創建ViewModel。只要ViewModel提供了帶有Application或者SavedStateHandle的構造方法,就能享受從Bundle中恢複數據的便利。

ViewModel的粘合劑ViewModelProvider

為什麼說ViewModelProvider是粘合劑呢?因為這個類就做了一件事,把ViewModelStore和ViewModelProvider.Factory組合起來,實現了一個叫get的方法,這個方法的內部實現就是有兩個步驟。

  1. 調用ViewModelStore的get方法查詢是否有創建好的對象,如果有就返回,方法結束,否則進入步驟2。

  2. 調用ViewModelProvider.Factorycreate方法創建對象,並將之保存到ViewModelStore中。

所以當我們要使用ViewModel的時候,通常是創建ViewModelProvider對象,然後調用get方法獲取真正的ViewModel對象,這樣,我們的對象就具備了正確處理設備配置變更的能力。

ViewModel的Fragment間通信功能

根據前面的梳理,我們知道,ViewModelStore是管理某個ViewModel組的,只要我們保證ViewModelStore存在,我們就可以保證ViewModel存活。再反推一步,要保證ViewModelStore存活,我們就要保證ViewModelStoreOwner在不同的地方都能返回同一個ViewModelStore對象,而ComponentActivity和Fragment是都實現了這個介面的。結合Activity的生命周期通常是大於Fragment這一事實,不難得出結論——在某個Fragment裡面,用Activity對象創建ViewModelProvider對象,就能保證獲取到和Activity一樣的ViewModelStore對象,也就能保證獲取到相同的ViewModel對象。只要Activity沒有銷毀,該Activity下的所有Fragment都能獲取到相同的ViewModel對象,然後通過更改狀態能方式完成通信。

到此,對ViewModel的分析告一段落了,對創建過程的兩次抽象是我覺得最精彩的環節,另外對現有條件(Activity和Fragment的生命周期)的利用也是它獨到之處,真的是受益匪淺。青山不改,綠水長流,咱們下期見!
viewmodel


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # 文件系統 > 文件是面向OS和麵向使用者而言的,對於人來說,音樂,圖片,文檔,游戲,軟體,郵件,等記錄信息的載體都被操作系統統稱為文件,而存儲在HDD(機械硬碟)和SSD(固態硬碟)里.因此文件是一種實體的抽象,而之所以文件需要文件名,是因為不同的文件需要進行相對應的區分,也就是文件名,而其中的 ...
  • # region Region是HBase數據管理的基本單位,region有一點像關係型數據的分區。 Region中存儲這用戶的真實數據,而為了管理這些數據,HBase使用了RegionSever來管理region。 ## region的分配 一個表中可以包含一個或多個Region。 每個Regio ...
  • 事務隔離級別遺留問題: 在讀已提交的級別下,事務B可以讀到事務A持有寫鎖的的記錄,且讀到的是未更新前的,為何寫讀沒有衝突? 可重覆讀級別,事務B可以更新事務A理論上應該已經獲取讀鎖的記錄,且更新後,事務A依然可以讀到數據,為何讀-寫-讀沒有衝突? 在可重覆讀級別,幻讀沒有產生 其中,前兩個問題就是因 ...
  • 企業數字化轉型以數據為中心,通過數據驅動業務發展、管理協同和運營。因此,數字化轉型關鍵在於數據,數據治理則需先行。從而更好激發數據生產要素潛能,實現業務數據化、數據價值化,助力企業數字化轉型。 ## 那麼何為數據治理? 國際數據管理協會(DAMA)在其《DAMA數據管理知識體系指南(第2版)》一書中 ...
  • # 資料庫模式設計如果不好會導致的問題: 1.冗餘 2.導致數據一致性出現問題 3.插入異常 4.更新異常 5.刪除異常 # 函數依賴 函數依賴是指一個或多個屬性的取值可以確定另一個屬性的取值。具體地說,如果一個關係模式R中屬性集合X的取值能唯一地確定屬性集合Y的取值,那麼我們稱屬性集合Y對於屬性集 ...
  • 這種只含map的操作,如果文件大小控制在合適的情況下,都將只有本地操作,其執行非常高效,運行效率完全不輸於在計算引擎Tez和Spark上運行。 ...
  • 摘要:併發的事務在運行過程中會出現一些可能引發一致性問題的現象,本篇將詳細分析一下。 本文分享自華為雲社區《MySQL讀取的記錄和我想象的不一致——事物隔離級別和MVCC》,作者:磚業洋__。 事務的特性簡介 1.1 原子性(Atomicity) 要麼全做,要麼全不做,一系列操作都是不可分割的,如果 ...
  • 之前寫過一篇文章“SQL Server如何找出視圖依賴的對象和視圖嵌套層數”,這裡我介紹一下Oracle資料庫中如何找出視圖的依賴對象以及視圖嵌套層數關係。主要通過DBA_DEPENDENCIES這個系統視圖(這個系統視圖中包含有對象的依賴關係數據)。另外,我們使用了Oracle的樹形查詢(層級查詢 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...