• Compose要是不 `remember`,相关功能就实现不了了吗?


    看过Compose案例或者源码的你,相信肯定是见过 remember 了的。顾名思义,Compose是要让我们的代码“记住”东西,那到底是记住什么呢?要是不 remember,相关功能就实现不了了吗?

    带着这些问题,来一探究竟吧

    随机色文本

    假设有这么一个“随机底色文本”的需求:实现一个 Text,其背景色每次启动都随机产生,且生命周期内不变

    用Compose可以实现如下:

    private val items = arrayOf(Color.Red, Color.Gray, Color.Magenta, Color.Blue, Color.Green, Color.Cyan)
    
    @Composable
    fun ColorText(name: String) {
        val color = items.random()
        val clicked = mutableStateOf(0)
        Log.d("ct", "ui compose")
        Column {
            Text(
                text = "I'm colored: ${clicked.value}", modifier = Modifier
                    .padding(16.dp)
                    .background(color)
                    .clickable {
                        Log.d("ct", "clicked")
                        clicked.value = clicked.value + 1
                    }
            )
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Text 调用 Modifier.background,设置随机从items中取的颜色,每次新的启动,都可能不一样

    然而很遗憾,上面的背景色虽然是随机产生,但是单次生命周期里,就可能发生变化 —— 比如点击一下文本,如下图:

    更奇怪的是,改变的 clicked 值,并没有如预期一样生效,一直是0……

    现象和代码不一?

    原因分析

    首先,上述代码中的“点击计数” clicked,仅仅是为了测试而存在。因为它是一个 MutableState 对象,点击后改变其值,会触发Recomposition流程,于是组件刷新。这样一来,color 的值将重新由随机函数算出,我们就看到背景色在变化了

    同理,虽然我们好像在点击的时候改变了 clicked 的值,希望像view系统一样,界面直接响应响应。但是,因为Recomposition的存在,它又被重新构造了,所以其值还是0

    正确实现

    要实现背景色的整个生命周期固定,但点击文本后,点击计数要更新,应该这么做:

    @Composable
    fun ColorText(name: String) {
        val color = remember { items.random() }
        val clicked = remember { mutableStateOf(0) }
        //...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    仅仅将color和clicked由 remember 包裹起来就解决了问题

    remember的原理剖析

    前面功能的实现,全仗着remember的加持。它究竟是个啥?

    我们先从颜色的remember着手,它调用的是这个:

    @Composable
    inline fun  remember(calculation: @DisallowComposableCalls () -> T): T =
        currentComposer.cache(false, calculation)
    
    • 1
    • 2
    • 3

    其注释写明了两个关键点,这也是它的功能描述:

    1. 记忆由calculation返回的值,仅在composition中执行
    2. 在Recomposition过程中,不会重新计算,而是直接返回第1步的值

    那么这又是怎么做到的呢?

    进一步的相关代码:

    @ComposeCompilerApi
    inline fun  Composer.cache(invalid: Boolean, block: () -> T): T {
        @Suppress("UNCHECKED_CAST")
        return rememberedValue().let {
            // 无效或Empty值时,走if流程,计算并保存值,否则直接返回
            // remember传入的invalid为false,所以肯定走值判断
            if (invalid || it === Composer.Empty) {
                val value = block()
                updateRememberedValue(value)
                value
            } else it
        } as T
    }
    
    // 要么返回Composer.Empty ,要么返回传给updateRememberedValue的值
    @ComposeCompilerApi
    fun rememberedValue(): Any?
    
    // 更新调用rememberedValue()后的值,且此值在下一次调用rememberedValue()时返回
    @ComposeCompilerApi
    fun updateRememberedValue(value: Any?)
    
    interface Composer {
    
        // ....
        companion object {
            /**
             * 用于标记无值的状态
             */
            val Empty = object {
                override fun toString() = "Empty"
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    从上述代码注释中,基本上已经对原理很清楚了,简单地说就是:

    1. 由composer作为存储控制
    2. 无值时,走初始化逻辑并返回值,同时存储该值;有值时,直接返回已存储的值

    颜色的“值不变”清楚了,那点击计数的“值变”又是怎么回事呢?

    其实如出一辙,只是点击计数remember的,不是普通值,而是一个 MutableState 类型。这样一来,它就有两层含义了:

    • MutableState对象本身在整个composition生命周期不变 —— 即类似普通值的状态一致性
    • MutableState对象所存储的实际值,可变 —— 这用以触发Recomposition,并且获取更新值

    小结

    remember 的存在,其实就是 Compose 机制下的产物,用以解决recomposition时的值恢复问题。而因为它的“值不变”特性,还可以用来解决耗时计算的问题,即,耗时计算被remember了,那它就只会执行一次,避免了不必要的额外开销


    最后分享一份个人整理的 Compose 学习笔记,里面记包含了(基本控件、Composable的理解和使用场景、MutableState的使用和源码浅析、Compose的重组和Remember、Compose的单一信息源和单向数据流原则、状态机制和重组优化、derivedStateOf和remember的使用、CompositionLocal的应用场景、Compose动画之AnimateSpec、Compose动画之DecayAnimation、Compose动画之中止和入场效果等),有需要参考的小伙伴可以点击这里查看获取方式 传送门直达 !!!

  • 相关阅读:
    用yolov5图像分割做人物抠像
    微信小程序异步请求数据promise方法
    机器学习,神经网络中,自注意力跟卷积神经网络之间有什么样的差异或者关联?
    编程常见题目2
    记录一次坑 | 包版本不一致产生的问题的排查过程
    内存还有几十G,为何jvm会oom crash
    mybatis_动态sql
    cuda的安装
    LeetCode - #62 不同路径(Top 100)
    STM32的SPI口的DMA读写[原创www.cnblogs.com/helesheng]
  • 原文地址:https://blog.csdn.net/m0_71263309/article/details/126054010