本文正在参加「金石方案 . 瓜分6万现金大奖」

前语

我曾经在一篇介绍 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 打开的:

  1. 经过 key 康复耐久化的数据
  2. 根据 key 注册 ValueProvider,等候适宜机遇履行数据耐久化
  3. 在 onDispose 中被刊出注册

registry 是一个 SaveableStateRegistry

康复 key 的数据

rememberSaveable 是加强版的 remember,首要要具有 remember 的能力,能够看到内部也确实是调用了 remember 来创立数据同时缓存到 Composition 中。init 供给了 remember 数据的首次创立。被创立的数据在后续某个时刻点进行耐久化,下次履行 rememberSaveable 时会测验康复之前耐久化的数据。详细进程分为以下两步:

  1. 经过 registry.consumeRestored 查找 key 获取 Saveable,
  2. 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:ListSaverMapSaver,能够用来转换成 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 做的工作类似:

  1. 根据 key 康复 bundle 数据,
  2. 根据 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 类型的联系,整理如下图

Compose 状态保存:rememberSaveable 原理分析

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 查找数据。查找返回的是一个 List 。由于存储时可能同一个 key 以此存储了多个值,所以当康复数据时,以此康复 List[0] 的数据。顺便吐槽一下 consumeRestored 这个姓名,将 restore 这个 private 成员信息暴露给了外面,有些不可思议。

什么情况下同一个 key 会存储多个值?首要当开发者手动设置 key 的时候,无法保证 key 是否去重,即使使用默许的 Compoiler 生成的 key,也只要同 Group 下的 key 会去重。所以放眼整个运行时很可能存呈现同一个 key 存储多个值的情况。

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 中的其它值不被移除

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 渠道完成了反正屏切换时的状况保存。

最终咱们用一个图收尾,赤色是保存数据时的数据流流向,绿色是康复数据时的数据流流向:

Compose 状态保存:rememberSaveable 原理分析