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
相关产品推荐
相关产品推荐

