沉思篇-剖析Jetpack的ViewModel

2023-06-13 21:01:07

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的生命週期)的利用也是它獨到之處,真的是受益匪淺。青山不改,綠水長流,咱們下期見!