網站首頁 編程語言 正文
前言
我曾經在一篇介紹 Compose Navigation 的文章 中提到了 Navigation 的狀態保存實際是由 rememberSaveable
實現的,有同學反饋希望單獨介紹一下 rememberSaveable 的功能及實現原理。
我們都知道 remember 可以保存數據、避免狀態因重組而丟失,但它依然無法避免在 ConfigurationChanged 時的數據丟失。想要在橫豎屏切換等場景下依然保存狀態,就需要使用 rememberSavable。
從一個報錯說起
首先,在代碼使用上 rememberSaveable 和 remember 沒有區別:
//保存列表狀態 val list = rememberSaveable { mutableListOf<String>() } //保存普通狀態 var value by rememberSaveable { mutableStateOf("") }
如上,只要將 remember 改為 rememberSaveable,我們創建的狀態就可以跨越橫豎屏切換甚至跨越進程持續保存了。不過 rememberSaveable 中并非任何類型的值都可以存儲:
data class User( val name: String = "" ) val user = rememberSaveable { User() }
上面代碼運行時會發生錯誤:
java.lang.IllegalArgumentException: User(name=) cannot be saved using the current SaveableStateRegistry. The default implementation only supports types which can be stored inside the Bundle. Please consider implementing a custom Saver for this class and pass it to rememberSaveable().
User 無法存入 Bundle。這非常合理,因為 rememberSaveable 中數據的持久化最終在 ComponentActivity#onSaveInstanceState
中執行,這需要借助到 Bundle 。
rememberSaveable 源碼分析
那么,rememberSaveable 是如何關聯到 onSaveInstanceState 的呢?接下來簡單分析一下內部實現
@Composable fun <T : Any> rememberSaveable( vararg inputs: Any?, saver: Saver<T, out Any> = autoSaver(), key: String? = null, init: () -> T ): T { //... // 通過 CompositionLocal 獲取 SaveableStateRegistry val registry = LocalSaveableStateRegistry.current // 通過 init 獲取需要保存的數據 val value = remember(*inputs) { // registry 根據 key 恢復數據,恢復的數據是一個 Saveable val restored = registry?.consumeRestored(finalKey)?.let { // 使用 Saver 將 Saveable 轉換為業務類型 saver.restore(it) } restored ?: init() } // 用一個 MutableState 保存 Saver,主要是借助 State 的事務功能避免一致性問題發生 val saverHolder = remember { mutableStateOf(saver) } saverHolder.value = saver if (registry != null) { DisposableEffect(registry, finalKey, value) { //ValueProvider:通過 Saver#save 存儲數據 val valueProvider = { with(saverHolder.value) { SaverScope { registry.canBeSaved(it) }.save(value) } } //試探數值是否可被保存 registry.requireCanBeSaved(valueProvider()) //將ValueProvider 注冊到 registry ,等到合適的時機被調用 val entry = registry.registerProvider(finalKey, valueProvider) onDispose { entry.unregister() } } } return value }
如上,邏輯很清晰,主要是圍繞 registry
展開的:
- 通過 key 恢復持久化的數據
- 基于 key 注冊 ValueProvider,等待合適時機執行數據持久化
- 在 onDispose 中被注銷注冊
registry 是一個 SaveableStateRegistry
。
恢復 key 的數據
rememberSaveable 是加強版的 remember,首先要具備 remember 的能力,可以看到內部也確實是調用了 remember 來創建數據同時緩存到 Composition 中。init
提供了 remember 數據的首次創建。被創建的數據在后續某個時間點進行持久化,下次執行 rememberSaveable 時會嘗試恢復之前持久化的數據。具體過程分為以下兩步:
- 通過 registry.consumeRestored 查找 key 獲取 Saveable,
- Saveable 經由 saver.restore 轉換為業務類型。
上述過程涉及到兩個角色:
- SaveableStateRegistry:通過 CompositionLocal 獲取,它負責將 Bundle 中的數據反序列化后,返回一個 Saveable
- Saver:Saver 默認有 autoSaver 創建,負責 Saveable 與業務數據之間的轉換。
Saveable 并不是一個在具體類型,它可以是可被持久化(寫入 Bundle)的任意類型。對于 autoSaver
來說, 這個 Saveable 就是業務數據類型本身。
private val AutoSaver = Saver<Any?, Any>( save = { it }, restore = { it } )
對于一些復雜的業務結構體,有時并非是所有字段都需要持久化。Saver 為我們提供了這樣一個機會機會,可以按照需要將業務類型轉化為可序列化類型。Compose 也提供了兩個預置的 Saver:ListSaver
和 MapSaver
,可以用來轉換成 List 或者 Map。
關于恢復數據的 Key :可以看到數據的保存和恢復都依賴一個 key,按道理 key 需要在保存和恢復時嚴格保持一致 ,但我們平日調用 rememberSaveable 時并沒有指定具體的 key,那么在橫豎屏切換甚至進程重啟后是如何恢復數據的呢?其實這個 key 是 Compose 自動幫我們設置的,它就是編譯期插樁生成的基于代碼位置的 key ,所以可以保證每次進程執行到此處都保持不變
注冊 ValueProvider
SaveableStateRegistry 在 DisposableEffect 中關聯 key 注冊 ValueProvider
。 ValueProvider 是一個 lambda,內部會調用 Saver#save
將業務數據轉化為 Saveable。
Saver#save 是 SaverScope 的擴展函數,所以這里需要創建一個 SaverScope 來調用 save 方法。SaverScope 主要用來提供 canBeSaved 方法,我們在自定義 Saver 時可以用來檢查類型是否可被持久化
ValueProvider 創建好后緊接著會調用 registry.registerProvider
進行注冊,等待合適的時機(比如 Activity 的 onSaveInstanceState)被調用。在注冊之前,先調用 requireCanBeSaved
判斷數據類型是否可以保存,這也就是文章前面報錯的地方。先 mark 一下,稍后我們看一下具體檢查的實現。
注銷 registry
最后在 onDispose 中調用 unregister 注銷之前的注冊 。
rememberSaveable 的基本流程理清楚了,可以看見主角就是 registry,因此有必要深入 SaveableStateRegistry 去看一下。我們順著 LocalSaveableStateRegistry
可以很容易找到 registry 的出處。
DisposableSavableStateRegistry 源碼分析
override fun setContent(content: @Composable () -> Unit) { //... ProvideAndroidCompositionLocals(owner, content) //... } @Composable @OptIn(ExperimentalComposeUiApi::class) internal fun ProvideAndroidCompositionLocals( owner: AndroidComposeView, content: @Composable () -> Unit ) { val view = owner val context = view.context //... val viewTreeOwners = owner.viewTreeOwners ?: throw IllegalStateException( "Called when the ViewTreeOwnersAvailability is not yet in Available state" ) val saveableStateRegistry = remember { DisposableSaveableStateRegistry(view, viewTreeOwners.savedStateRegistryOwner) } //... CompositionLocalProvider( //... LocalSaveableStateRegistry provides saveableStateRegistry, //... ) { ProvideCommonCompositionLocals( owner = owner, //... content = content ) } }
如上,我們在 Activity 的 setContent 中設置各種 CompositionLocal,其中就有 LocalSaveableStateRegistry,所以 registry 不僅是一個 SaveableStateRegistry,更是一個 DisposableSaveableStateRegistry 。
接下來看一下 DisposableSaveableStateRegistry 的創建過程 。
saveableStateRegistry 與 SavedStateRegistry
注意下面這個 DisposableSaveableStateRegistry 不是真正的構造函數,它是同名構造函數的一個 Wrapper,在調用構造函數創建實例之前,先調用 androidxRegistry
進行了一系列處理:
internal fun DisposableSaveableStateRegistry( id: String, savedStateRegistryOwner: SavedStateRegistryOwner ): DisposableSaveableStateRegistry { //基于 id 創建 key val key = "${SaveableStateRegistry::class.java.simpleName}:$id" // 基于 key 獲取 bundle 數據 val androidxRegistry = savedStateRegistryOwner.savedStateRegistry val bundle = androidxRegistry.consumeRestoredStateForKey(key) val restored: Map<String, List<Any?>>? = bundle?.toMap() // 創建 saveableStateRegistry,傳入 restored 以及 canBeSaved val saveableStateRegistry = SaveableStateRegistry(restored) { canBeSavedToBundle(it) } val registered = try { androidxRegistry.registerSavedStateProvider(key) { //調用 register#performSave 并且轉為 Bundle saveableStateRegistry.performSave().toBundle() } true } catch (ignore: IllegalArgumentException) { false } return DisposableSaveableStateRegistry(saveableStateRegistry) { if (registered) { androidxRegistry.unregisterSavedStateProvider(key) } } }
androidxRigistry 跟 rememberSaveable 中的 registry 做的事情類似:
- 基于 key 恢復 bundle 數據,
- 基于 key 注冊 SavedStateProvider。
但 androidxRegistry 不是一個 SaveableStateRegistry 而是一個 SavedStateRegistry
。名字上有點繞,后者來自 androidx.savedstate
,屬于平臺代碼,而 SaveableStateRegistry 屬于 compose-runtime 的平臺無關代碼。可見這個構造函數的同名 Wrapper 很重要,他就像一個橋梁,解耦和關聯了平臺相關和平臺無關代碼。
DisposableSaveableStateRegistry 與 SaveableStateRegistryImpl
DisposableSaveableStateRegistry 真正的構造函數定義如下:
internal class DisposableSaveableStateRegistry( saveableStateRegistry: SaveableStateRegistry, private val onDispose: () -> Unit ) : SaveableStateRegistry by saveableStateRegistry { fun dispose() { onDispose() } }
這里用了參數 saveableStateRegistry 作為 SaveableStateRegistry 接口的代理。saveableStateRegistry 實際是一個 SaveableStateRegistryImpl
對象,它像這樣創建:
val saveableStateRegistry = SaveableStateRegistry(restored) { canBeSavedToBundle(it) } fun SaveableStateRegistry( restoredValues: Map<String, List<Any?>>?, canBeSaved: (Any) -> Boolean ): SaveableStateRegistry = SaveableStateRegistryImpl(restoredValues, canBeSaved)
SaveableStateRegistryImpl 被創建時傳入兩個參數:
- restoredValues:androidxRegistry 恢復的 bundle 數據,是一個 Map 對象。
- canBeSaved : 用來檢查數據是否可持久化,可以的看到這里實際調用了 canBeSavedToBundle。
canBeSavedToBundle
文章開頭的報錯就是 requireCanBeSaved -> canBeSavedToBundle
檢查出來的,通過 canBeSavedToBundle 看一下 rememberSaveable 支持的持久化類型:
private fun canBeSavedToBundle(value: Any): Boolean { // SnapshotMutableStateImpl is Parcelable, but we do extra checks if (value is SnapshotMutableState<*>) { if (value.policy === neverEqualPolicy<Any?>() || value.policy === structuralEqualityPolicy<Any?>() || value.policy === referentialEqualityPolicy<Any?>() ) { val stateValue = value.value return if (stateValue == null) true else canBeSavedToBundle(stateValue) } else { return false } } for (cl in AcceptableClasses) { if (cl.isInstance(value)) { return true } } return false } private val AcceptableClasses = arrayOf( Serializable::class.java, Parcelable::class.java, String::class.java, SparseArray::class.java, Binder::class.java, Size::class.java, SizeF::class.java )
首先, SnapshotMutableState
允許被持久化,因為我們需要在 rememberSaveable 中調用 mutableStateOf;其次,SnapshotMutableState 的泛型必須是 AcceptableClasses
中的類型,我們自定義的 User 顯然不符合要求,因此報了開頭的錯誤。
SaveableStateRegistryImpl 源碼分析
前面理清了幾個 Registry 類型的關系,整理如下圖
SaveableStateRegistry 接口的各主要方法都由 SaveableStateRegistryImpl 代理的:
- consumeRestored:根據 key 恢復數據
- registerProvider:注冊 ValueProvider
- canBeSaved:用來檢查數據是否是可保存類型
- performSave:執行數據保存
canBeSaved 前面介紹過,其實會回調 canBeSavedToBundle。接下來看一下 SaveableStateRegistryImpl 中其他幾個方法是如何實現的:
consumeRestored
override fun consumeRestored(key: String): Any? { val list = restored.remove(key) return if (list != null && list.isNotEmpty()) { if (list.size > 1) { restored[key] = list.subList(1, list.size) } list[0] } else { null } }
我們知道 restored
是從 Bundle 中恢復的數據,實際是一個 Map了類型。而 consumeRestored
就是在 restored 中通過 key 查找數據。restore 的 Value 是 List 類型。當恢復數據時,只保留最后一個只。順便吐槽一下 consumeRestored 這個名字,將 restore 這個 private 成員信息暴露給了外面,有些莫名其妙。
registerProvider
override fun registerProvider(key: String, valueProvider: () -> Any?): Entry { require(key.isNotBlank()) { "Registered key is empty or blank" } @Suppress("UNCHECKED_CAST") valueProviders.getOrPut(key) { mutableListOf() }.add(valueProvider) return object : Entry { override fun unregister() { val list = valueProviders.remove(key) list?.remove(valueProvider) if (list != null && list.isNotEmpty()) { // if there are other providers for this key return list back to the map valueProviders[key] = list } } } }
將 ValueProvider 注冊到 valueProviders ,valueProviders 也是一個值為 List 的 Map,同一個 Key 可以對應多個 Value。返回的 Entry 用于 onDispose 中調用 unregister。
DisposableSaveableStateRegistry 是一個 CompositionLocal 單例,所以需要 unregister 避免不必要的泄露。注意這里要確保同一個 key 中的 List 中的其它值不被移除
不解:什么情況下同一個 key 會 registerProvider 多個值呢?
performSave
override fun performSave(): Map<String, List<Any?>> { val map = restored.toMutableMap() valueProviders.forEach { (key, list) -> if (list.size == 1) { val value = list[0].invoke() if (value != null) { check(canBeSaved(value)) map[key] = arrayListOf<Any?>(value) } } else { map[key] = List(list.size) { index -> val value = list[index].invoke() if (value != null) { check(canBeSaved(value)) } value } } } return map }
在這里調用了 ValueProvider 獲取數據后存入 restored ,這里也是有針對 Value 是 List 類型的特別處理。performSave 的調用時機前面已經出現了,是 androidxRegistry 注冊的 Provider 中調用:
androidxRegistry.registerSavedStateProvider(key) { //調用 register#performSave 并且轉為 Bundle saveableStateRegistry.performSave().toBundle() }
SavedStateProvider 會在 onSaveInstance 時被執行。
至此, rememberSaveable 持久化發生的時機與平臺進行了關聯。
最后回看 androidxRegistry
最后我們再回看一下 DisposableSavableStateRegistry,主要是使用 androidxRegistry 獲取 key 對應的數據,并注冊 key 對應的 Provider。那么 androidxRegistry 和 key 是怎么來的?
internal fun DisposableSaveableStateRegistry( id: String, savedStateRegistryOwner: SavedStateRegistryOwner ): DisposableSaveableStateRegistry { val key = "${SaveableStateRegistry::class.java.simpleName}:$id" val androidxRegistry = savedStateRegistryOwner.savedStateRegistry //... }
先說 key 。key 由 id 唯一決定,而這個 id 其實是 ComposeView
的 layoutId。我們知道 ComposeView 是 Activity/Fragment 承載 Composable 的容器,rememberSaveable 會按照 ComposeView 為單位來持久化數據。
因為你 ComposeView 的 id 決定了 rememberSaveable 存儲數據的位置,如果 Activity/Fragment 范圍內如果有多個 ComposeView 使用了同一個 id,則只有第一個 ComposeView 能正常恢復數據,這一點要特別注意
再看一下 androidxRegistry,他由 SavedStateRegistryOwner 提供,而這個 owner 是ComposeView 被 attach 到 Activity 時賦的值,就是 Activity 本身:
public class ComponentActivity extends androidx.core.app.ComponentActivity implements ContextAware, LifecycleOwner, ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner, // ComponentActivity 是一個 SavedStateRegistryOwner OnBackPressedDispatcherOwner, ActivityResultRegistryOwner, ActivityResultCaller { //... public final SavedStateRegistry getSavedStateRegistry() { return mSavedStateRegistryController.getSavedStateRegistry(); } //... }
mSavedStateRegistryController
會在 Activity 重建時 onCreate 中調用 performRestore
;在 onSaveInstanceState 時執行 performSave
。
protected void onCreate(@Nullable Bundle savedInstanceState) { mSavedStateRegistryController.performRestore(savedInstanceState); //... } protected void onSaveInstanceState(@NonNull Bundle outState) { //... mSavedStateRegistryController.performSave(outState); }
mSavedStateRegistryController 最終調用到 SavedStateRegistry 的同名方法,看一下 SavedStateRegistry#performSave
:
fun performSave(outBundle: Bundle) { //... val it: Iterator<Map.Entry<String, SavedStateProvider>> = this.components.iteratorWithAdditions() while (it.hasNext()) { val (key, value) = it.next() components.putBundle(key, value.saveState()) } if (!components.isEmpty) { outBundle.putBundle(SAVED_COMPONENTS_KEY, components) } }
components 是注冊 SavedStateProvider 的 Map。 performSave 中調用 Provider 的 saveState 方法獲取到 rememberSaveable 中保存的 bundle,然后存入 outBundle 進行持久化。
至此,rememberSaveable 在 Android 平臺 完成了橫豎屏切換時的狀態保存。
最后我們用一個圖收尾,紅色是保存數據時的數據流流向,綠色是恢復數據時的數據流流向:
原文鏈接:https://juejin.cn/post/7166043043651387406
相關推薦
- 2022-06-18 Qt?事件處理機制的深入理解_C 語言
- 2023-11-13 【云原生】docker-compose安裝,解決Warning: the “docker“ comm
- 2022-11-16 Python制作數據分析透視表的方法詳解_python
- 2022-05-25 <C++>詳解類對象作為類成員時調用構造和析構的時機及靜態成員解釋
- 2022-06-29 C語言超詳細講解getchar函數的使用_C 語言
- 2022-05-03 基于docker部署skywalking實現全鏈路監控功能_docker
- 2022-12-31 C++模擬Linux?Shell編寫一個自定義命令_C 語言
- 2022-11-02 React+Mobx基本使用、模塊化操作_React
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支