Android Compose之mutableStateListOf

本文介绍了mutableStateListOf在处理List数据状态变化中的优势,通过分析SnapshotStateList和PersistentList的实现原理,展示了如何高效地感知和处理并发修改。同时提到成为架构师需关注选型和编程思维的提升。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

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


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

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

原理分析

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. 当前节点列表
                                    var oldList: PersistentList<T>? = null
                    var currentModification = 0
                    synchronized(sync) {
                        val current = withCurrent { this }
                        currentModification = current.modification
                        oldList = current.list
                    }
    
    
    
  2. 将当前节点PersistentList调用更新的block,产生新的PersistentList

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

    if (synchronized(sync) {
                        writable {
                            if (modification == currentModification) {
                                list = newList
                                modification++
                                true
                            } else false
                        }
                    }
                    ) {
                        result = true
                        break
                    }
    
    
    

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

PersistentList家族的类图如下

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

// 可以看到在数组大小小于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数据变换的逻辑都会重新执行

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

mutableStateListOf(mutableStateListOf(a),mutableStateListOf(b))


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

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末优快云官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

图片

<think>好的,用户想在Android Jetpack Compose中实现闹钟功能,我需要结合提供的引用内容来回答。首先,我需要回忆Jetpack Compose的相关知识,特别是与后台任务和闹钟相关的组件。根据引用[3],Space Dawn应用使用了WorkManager和AlarmManager来实现可靠的提醒触发,这应该是关键点。 接下来,用户可能需要一个示例教程,所以应该分步骤讲解。首先,界面设计部分,使用Compose创建设置时间的界面,可能需要时间选择器。然后,处理用户输入的时间,转换成具体的触发时间。接下来是设置闹钟的逻辑,这里可能需要AlarmManager,但要注意Android不同版本的差异,尤其是后台限制。同时,引用[2]提到Jetpack组件如WorkManager可以管理后台任务,所以可能需要结合使用。 另外,引用[3]还提到应用是离线优先,并且有测试覆盖,所以在回答中可以提到这些最佳实践。需要确保代码符合现代Android开发标准,比如使用ViewModel来处理逻辑,保持UI层简洁。 用户可能对权限部分有疑问,比如精确闹钟权限,需要说明如何请求和处理。此外,唤醒设备可能需要使用WakeLock,但要注意正确释放,避免内存泄漏。最后,提醒用户测试的重要性,特别是不同API级别的行为差异。 可能的相关问题包括如何处理后台限制、替代AlarmManager的方案、Compose与旧视图的交互等,这些都需要在相关问题部分列出,帮助用户进一步学习。</think>在Android Jetpack Compose中实现闹钟功能需要结合AlarmManager和后台任务管理组件。以下是基于现代Android开发实践的实现步骤: --- ### 一、界面设计与时间选择 使用Jetpack Compose构建时间选择界面: ```kotlin @Composable fun AlarmPicker(onTimeSelected: (LocalTime) -> Unit) { var showPicker by remember { mutableStateOf(false) } var selectedTime by remember { mutableStateOf(LocalTime.now()) } Column { Button(onClick = { showPicker = true }) { Text("设置闹钟时间") } Text("已选时间: ${selectedTime.format(DateTimeFormatter.ofPattern("HH:mm"))}") if (showPicker) { TimePickerDialog( onDismissRequest = { showPicker = false }, onTimeSelected = { selectedTime = it onTimeSelected(it) showPicker = false } ) } } } ``` --- ### 二、设置精确闹钟权限 在AndroidManifest.xml添加权限: ```xml <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> ``` --- ### 三、AlarmManager实现 结合AlarmManager设置精确闹钟: ```kotlin class AlarmHelper(private val context: Context) { fun setExactAlarm(triggerTime: Long) { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(context, AlarmReceiver::class.java).let { PendingIntent.getBroadcast(context, 0, it, PendingIntent.FLAG_IMMUTABLE) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (alarmManager.canScheduleExactAlarms()) { alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerTime, intent ) } } else { alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, intent) } } } ``` --- ### 四、后台任务处理 使用BroadcastReceiver接收闹钟触发事件: ```kotlin class AlarmReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val notificationManager = NotificationHelper(context) notificationManager.showAlarmNotification() // 唤醒设备 val wakeLock = (context.getSystemService(POWER_SERVICE) as PowerManager).run { newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AlarmApp::AlarmWakelock") } wakeLock.acquire(60_000L) // 播放铃声 val mediaPlayer = MediaPlayer.create(context, R.raw.alarm_tone) mediaPlayer.start() } } ``` --- ### 五、现代Android开发实践 1. **使用WorkManager处理复杂任务**:对于需要持久化的后台任务,建议结合WorkManager实现[^3] 2. **ViewModel集成**:通过ViewModel管理闹钟状态 ```kotlin class AlarmViewModel : ViewModel() { private val _alarms = mutableStateListOf<AlarmItem>() val alarms: List<AlarmItem> get() = _alarms fun addAlarm(time: LocalTime) { _alarms.add(AlarmItem( id = UUID.randomUUID().toString(), time = time )) } } ``` --- ### 六、注意事项 1. Android 12+需要动态申请`SCHEDULE_EXACT_ALARM`权限 2. 使用`AlarmManagerCompat`保持API兼容性 3. 在后台限制(Android 9+)下,建议结合前台服务实现持久化提醒 4. 推荐使用Room数据库持久化存储闹钟设置[^2] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值