Android Compose之mutableStateListOf

mutableStateListOf 返回一个可感知状态变化的MutableList

通常的State只能用来值改变的场景,对于List数据,List引用不变,往内部添加数据这个场景是无法感知到状态变化的,除非我们将List拷贝一份再重新赋值,如下代码

java 复制代码
val a = mutableStateOf(mutableListOf(1,2,3)) // a声明为这样
a.value = a.value.toMutableList().apply { add(4} } // a可以感知到状态变化

这样每次赋值就面临拷贝,不是很好,因此这里我们推荐使用mutableStateListOf,构建一个可以感知内部数据变化的List

其实mutableStateListOf内部的实现为了保证不变性,仍然是拷贝元素,只不过它用了更加高效的实现,比我们单纯用toMutableList要高效得多

原理分析

java 复制代码
class SnapshotStateList<T> : MutableList<T>, StateObject {
    override var firstStateRecord: StateRecord =
        StateListStateRecord<T>(persistentListOf())
        private set
     ...
}

首先SnapshotStateList继承了MutableList,意味着我们可以以操作MutableList的方式操作SnapshotStateList,同时它又继承了StateObject,代表这个List是一个具有状态感知能力的List,

内部的firstStateRecord形成了一个链表,链表持有一个PersistentList的数据,那么是不是说每次新插入都是建立一个新的节点,新的节点持有新的PersistentList呢?

答案是如果在一次快照的过程中多次操作数据,那么操作的是同一个节点,如果是不同的快照操作,那么会先新建一个节点.

分析内部代码发现,添加数据和删除数据的api,最终都会调用到conditionalUpdate

这个方法的逻辑如下

  1. 加锁获取当前头节点的状态,头节点始终维护的是最终的状态,加锁是因为状态是可以多线程的环境下修改的

    1. 当前节点修改数
    2. 当前节点列表
    java 复制代码
       var oldList: PersistentList<T>? = null
       var currentModification = 0
       synchronized(sync) {
                   val current = withCurrent { this }
                   currentModification = current.modification
                   oldList = current.list
       }
  2. 将当前节点PersistentList调用更新的block,产生新的PersistentList

    java 复制代码
       val newList = block(oldList!!)
       if (newList == oldList) {
                result = false
                break
       }
  3. 加锁,获取一个可写的节点,如果快照版本不同,这里会产生一个新节点,注意这里由于锁是分段的,可能多线程同时更新状态,因此这里比较了下当前节点的modification和老的list的modification是否相同,如果相同的话,将新产生的list赋值给当前节点,同时通知外部写发生了,通知的方法在writable中,如果不同,说明发生了并发的修改,重新执行1,这里的3个步骤的操作都是在while循环中的

    java 复制代码
    if (synchronized(sync) {
                        writable {
                            if (modification == currentModification) {
                                list = newList
                                modification++
                                true
                            } else false
                        }
                    }
                    ) {
                        result = true
                        break
                    }

看到这里,发现这里依旧是每次产生新的PersistentList,PersistentList必须保证是轻量的,不可覆盖之前的值才能保证这里的操作不会修改之前的值

PersistentList家族的类图如下

调用persistentListOf()返回的是SmallPersistentVector,以add方法为例,分析下SmallPersistentVector是如何实现的

java 复制代码
// 可以看到在数组大小小于MAX_BUFFER_SIZE(32),一直是通过拷贝切换的
override fun add(element: E): PersistentList<E> {
        if (size < MAX_BUFFER_SIZE) {
            val newBuffer = buffer.copyOf(size + 1)
            newBuffer[size] = element
            return SmallPersistentVector(newBuffer)
        }
        val tail = presizedBufferWith(element)
        return PersistentVector(buffer, tail, size + 1, 0)
    }

PersistentVector代码分析看上去是由链表加上二级指针的方式去管理buffer,但两者核心都是一个不变的数据结构,

通常我们外部在使用list数据的地方是将list的数据变换为某种ui类型,因此每次数据改变,发生重组时,整体将list数据变换的逻辑都会重新执行

代码分析完成之后,看一个有趣的事情,

java 复制代码
mutableStateListOf(mutableStateListOf(a),mutableStateListOf(b))

嵌套mutableStateListOf可行吗?看上去可行,并且内部数据的更新可以独立进行!因为每一个都是单独的mutableStateListOf,在内部的更新可以不用扩散到外部

👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀

相关推荐
海水冷却9 小时前
Android 群组聊天怎么实现:基于 ZIM 的完整落地指南(附可运行 Demo)
android
三少爷的鞋11 小时前
现代 Android 架构指南:不要再主动设计 Callback 接口
android
TimeFine17 小时前
AI 编码工作流的设计
android·ai编程
骑着蜗牛撵大象32717 小时前
C++20 协程 × 屏障:手写一个 co_await 版 Barrier,让等待不再钉死线程
android·c++20·协程·并发编程·co_await·屏障同步
山东孙燕姿19 小时前
设备台账重构:从一张表到三层模型
android·java·网络·低代码·重构·rxjava
蒸鱼Yuzheng20 小时前
移动端 CPU 调度分析:线程亲和性、大小核迁移与卡顿
android·perfetto·性能分析·线程优化·cpu调度
booboosui20 小时前
安卓唤醒词:12 维 MFCC 加 DTW,阈值 1.8
android
传奇开心果20 小时前
Compose Multiplatform 共享边界实战:架构契约、互操作同步与可回退设计
android·kotlin
事圆则缓21 小时前
从开机到首帧:Android 系统与应用启动流程源码分析
android
AI吃大瓜21 小时前
指针压力表读数4:Android实现指针压力表读数(可实时检测)
android·指针读数·指针压力表·压力表·压力表读数