Compose状态保存rememberSaveable原理解析

目录
  • 前言
  • 从一个报错说起
  • rememberSaveable 源码分析
    • 恢复 key 的数据
    • 注册 ValueProvider
    • 注销 registry
  • DisposableSavableStateRegistry 源码分析
    • saveableStateRegistry 与 SavedStateRegistry
    • DisposableSaveableStateRegistry 与 SaveableStateRegistryImpl
    • canBeSavedToBundle
  • SaveableStateRegistryImpl 源码分析
    • consumeRestored
    • registerProvider
    • performSave
  • 最后回看 androidxRegistry

前言

我曾经在一篇介绍 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: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 做的事情类似:

  • 基于 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 平台 完成了横竖屏切换时的状态保存。

最后我们用一个图收尾,红色是保存数据时的数据流流向,绿色是恢复数据时的数据流流向:

以上就是Compose状态保存rememberSaveable原理解析的详细内容,更多关于Compose rememberSaveable的资料请关注我们其它相关文章!

(0)

相关推荐

  • docker资源限制和compose部署详解

    目录 一.私有仓库建立 二.Cgroup 资源配置方法 三.CPU使用率控制 使用 stress 工具测试 CPU 和内存 四. CPU 周期限制 五. CPU Core 控制 六. CPU 配额控制参数的混合使用 七. 内存限额 八.Block IO 的限制 九. bps 和 iops 的限制 十. 构建镜像(docker build)时指定资源限制 十一. compose部署 十二. consul部署 总结 一.私有仓库建立 docker pull registry 在docker 引擎终端

  • Jetpack Compose Canvas绘制超详细介绍

    目录 1. Canvas 2. 绘制方法 1. drawLine 2. drawRect 3. drawRoundRect 4. drawImage 5. drawCircle 6. drawArc 7. drawPath 8. drawPoints 3. DrawScope拓展方法 1. inset 2. translate 3. rotate与rotateRad 4. scale 5. clipRect 6. drawIntoCanvas 7. withTransform 4.参考 1. C

  • Android动效Compose贝塞尔曲线动画规格详解

    目录 正文 贝塞尔曲线 解析动画曲线 曲线源码分析 总结 正文 写Compose动画的时候使用animateXAsState的时候会注意到一个参数——animationSpec,如下: val borderRadius by animateIntAsState( targetValue = if (isRound) 100 else 0, animationSpec = tween( durationMillis = 3000, easing = LinearEasing ) ) 此处就不深入探

  • Jetpack Compose状态专篇精讲

    目录 1.remember 2.rememberSaveable 3.状态提升 4.状态管理 将Composable作为可信来源 将状态容器作为可信来源 将 ViewModel 作为可信来源 应用中的状态是指可以随时间变化的任何值.这是一个非常宽泛的定义,从 Room 数据库到类的变量,全部涵盖在内. 由于Compose是声明式UI,会根据状态变化来更新UI,因此状态的处理至关重要.这里的状态你可以简单理解为页面上展示的数据,那么状态管理就是处理数据的读写. 1.remember remembe

  • Jetpack Compose常用组件详细介绍

    目录 1. Text 2. Image 3. LazyColumn 1. Text 日常最常用的应该就是显示文字,所以有必要说一下Text控件.首先源码如下: @Composable fun Text( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = nu

  • Jetpack Compose自定义动画与Animatable详解

    目录 AnimationSpec 1.spring 2.tween 3.keyframes 4.repeatable 5.snap Animatable 本篇主要是自定义动画与Animatable. AnimationSpec 上一篇中,出现了多次animationSpec属性,它是用来自定义动画规范的.例如: fun Modifier.animateContentSize( animationSpec: FiniteAnimationSpec<IntSize> = spring(), fin

  • Compose状态保存rememberSaveable原理解析

    目录 前言 从一个报错说起 rememberSaveable 源码分析 恢复 key 的数据 注册 ValueProvider 注销 registry DisposableSavableStateRegistry 源码分析 saveableStateRegistry 与 SavedStateRegistry DisposableSaveableStateRegistry 与 SaveableStateRegistryImpl canBeSavedToBundle SaveableStateReg

  • Android开发Compose remember原理解析

    目录 正文 随机色文本 原因分析 正确实现 remember的原理剖析 小结 正文 看过Compose案例或者源码的你,相信肯定是见过 remember 了的.顾名思义,Compose是要让我们的代码“记住”东西,那到底是记住什么呢?要是不 remember,相关功能就实现不了了吗? 带着这些问题,来一探究竟吧 随机色文本 假设有这么一个“随机底色文本”的需求:实现一个 Text,其背景色每次启动都随机产生,且生命周期内不变 用Compose可以实现如下: private val items =

  • Java线程状态运行原理解析

    这篇文章主要介绍了Java线程状态运行原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 代码实例如下 package com.fgy.demo05; /** * 等待唤醒案例:线程之间通信 * 注意: * 同步使用的锁对象必须唯一 * 只有锁对象才能调用wait和notify()/notifyAll()方法 */ public class Demo1WaitAndNotify { public static void main(Strin

  • 玩转Koa之koa-router原理解析

    一.前言 Koa为了保持自身的简洁,并没有捆绑中间件.但是在实际的开发中,我们需要和形形色色的中间件打交道,本文将要分析的是经常用到的路由中间件 -- koa-router. 如果你对Koa的原理还不了解的话,可以先查看Koa原理解析. 二.koa-router概述 koa-router的源码只有两个文件:router.js和layer.js,分别对应Router对象和Layer对象. Layer对象是对单个路由的管理,其中包含的信息有路由路径(path).路由请求方法(method)和路由执行

  • 一文详解 Compose Navigation 的实现原理

    目录 前言 1. 从 Jetpack Navigation 说起 2. 定义导航 3. 导航跳转 4. 保存状态 SaveableStateHolder & rememberSaveable 导航回退时的状态保存 底部导航栏切换时的状态保存 5. 导航转场动画 6. Hilt & Navigation 7. 总结 前言 一个纯 Compose 项目少不了页面导航的支持,而 navigation-compose 几乎是这方面的唯一选择,这也使得它成为 Compose 工程的标配二方库.介绍 

  • Java 8 动态类型语言Lambda表达式实现原理解析

    Java 8支持动态语言,看到了很酷的Lambda表达式,对一直以静态类型语言自居的Java,让人看到了Java虚拟机可以支持动态语言的目标. import java.util.function.Consumer; public class Lambda { public static void main(String[] args) { Consumer<String> c = s -> System.out.println(s); c.accept("hello lambd

  • javascript History对象原理解析

    这篇文章主要介绍了javascript History对象原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 length history.length属性保存着历史记录的URL数量.初始时,该值为1.由于IE10+浏览器在初始时返回2,存在兼容性问题,所以该值并不常用 跳转方法 go().back()和forward() 如果移动的位置超出了访问历史的边界,以上三个方法并不报错,而是静默失败 [注意]使用历史记录时,页面通常从浏览器缓存之

  • Docker Swarm集群管理的使用及原理解析

    Swarm 集群管理 简介 Docker Swarm 是 Docker 的集群管理工具.它将 Docker 主机池转变为单个虚拟 Docker 主机. Docker Swarm 提供了标准的 Docker API,所有任何已经与 Docker 守护程序通信的工具都可以使用 Swarm 轻松地扩展到多个主机. 支持的工具包括但不限于以下各项: Dokku Docker Compose Docker Machine 原理 如下图所示,swarm 集群由管理节点(manager)和工作节点(work

  • Golang 语言map底层实现原理解析

    在开发过程中,map是必不可少的数据结构,在Golang中,使用map或多或少会遇到与其他语言不一样的体验,比如访问不存在的元素会返回其类型的空值.map的大小究竟是多少,为什么会报"cannot take the address of"错误,遍历map的随机性等等. 本文希望通过研究map的底层实现,以解答这些疑惑. 基于Golang 1.8.3 1. 数据结构及内存管理 hashmap的定义位于 src/runtime/hashmap.go 中,首先我们看下hashmap和buck

  • SpringBoot2.0 中 HikariCP 数据库连接池原理解析

    作为后台服务开发,在日常工作中我们天天都在跟数据库打交道,一直在进行各种CRUD操作,都会使用到数据库连接池.按照发展历程,业界知名的数据库连接池有以下几种:c3p0.DBCP.Tomcat JDBC Connection Pool.Druid 等,不过最近最火的是 HiKariCP. HiKariCP 号称是业界跑得最快的数据库连接池,自从 SpringBoot 2.0 将其作为默认数据库连接池后,其发展势头锐不可当.那它为什么那么快呢?今天咱们就重点聊聊其中的原因. 一.什么是数据库连接池

随机推荐