You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中LiveData出现ConcurrentModificationException的解决方法

ConcurrentModificationException 解决方案(LiveData+ArrayList场景)

问题场景

后台服务通过BLE的onCharacteristicChanged方法更新MutableLiveData中的ArrayList,UI层观察者遍历该集合时触发ConcurrentModificationException,小列表加延迟可临时解决,但大列表仍报错。相关代码如下:

LiveData定义

val _receivedCommandList = MutableLiveData<ArrayList<String>>() 
val receivedCommandList: LiveData<ArrayList<String>> get() = _receivedCommandList

UI观察者遍历代码

homeViewModel.command.observe(viewLifecycleOwner, Observer {
    binding.descriptionText.text = ""
    var cmd = ""
    for (command in it){
        cmd += "${command.trim()}\n\n"
    }
    binding.descriptionText.text = cmd
})

后台更新代码

repository?.let {
       val sdf = SimpleDateFormat("yyyyMMdd hh:mm:ss ")
       val command = "\n${sdf.format(Date())} A -> B = $cmd\n"
       commandList.add(0,command)
       it._receivedCommandList.postValue(commandList)
}

可行解决方案

1. 使用线程安全集合CopyOnWriteArrayList替代ArrayList

CopyOnWriteArrayList在修改操作(如add)时会复制底层数组,遍历操作始终基于修改前的数组快照,从根源避免并发修改异常。

  • 修改LiveData定义:
val _receivedCommandList = MutableLiveData<CopyOnWriteArrayList<String>>() 
val receivedCommandList: LiveData<CopyOnWriteArrayList<String>> get() = _receivedCommandList
  • 后台初始化及更新代码:
// 初始化commandList为CopyOnWriteArrayList
val commandList = CopyOnWriteArrayList<String>()

repository?.let {
       val sdf = SimpleDateFormat("yyyyMMdd hh:mm:ss ")
       val command = "\n${sdf.format(Date())} A -> B = $cmd\n"
       commandList.add(0,command)
       it._receivedCommandList.postValue(commandList)
}
  • 观察者代码无需修改,直接遍历即可。

2. 更新LiveData时传递集合副本

每次更新LiveData时,传递原集合的副本,让观察者遍历的是独立的副本,与后台修改的原集合解耦。

  • 修改后台更新代码:
repository?.let {
       val sdf = SimpleDateFormat("yyyyMMdd hh:mm:ss ")
       val command = "\n${sdf.format(Date())} A -> B = $cmd\n"
       commandList.add(0,command)
       // 创建ArrayList副本传递给LiveData
       it._receivedCommandList.postValue(ArrayList(commandList))
}
  • LiveData和观察者代码无需修改。

3. 观察者遍历前创建集合副本

在UI层拿到集合后,先复制一份再遍历,确保遍历过程中不受后台修改影响。

  • 修改观察者代码:
homeViewModel.command.observe(viewLifecycleOwner, Observer { originalList ->
    binding.descriptionText.text = ""
    var cmd = ""
    // 创建副本后遍历
    val safeList = ArrayList(originalList)
    for (command in safeList){
        cmd += "${command.trim()}\n\n"
    }
    binding.descriptionText.text = cmd
})
  • LiveData和后台更新代码无需修改。

方案选择建议

  • 若频繁修改集合且遍历操作多,优先选方案1(CopyOnWriteArrayList),无需额外复制操作。
  • 若仅需快速修复现有代码,方案2或方案3更便捷,无需修改集合类型。

内容的提问来源于stack exchange,提问作者saket kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 01:55:18