Android ConcurrentModificationException问题:BLE扫描数据处理代码异常求助
嘿,我来帮你搞定这个问题!你遇到的ConcurrentModificationException完全是因为在遍历List的同时修改它——也就是你在for-each循环里调用list.add(),这是Java集合框架的一个经典坑。
问题原因分析
先拆解下你的代码逻辑:你在遍历list的过程中,一旦发现当前遍历的设备和新扫描到的设备地址不匹配,就直接往列表里添加新元素。但Java的for-each循环本质是通过迭代器(Iterator)实现的,迭代器有「快速失败」机制——只要检测到集合在遍历期间被迭代器以外的方式修改(比如直接调用add()/remove()),就会抛出这个异常。
另外,BLE扫描的回调通常运行在系统工作线程,如果你同时在UI线程操作这个list,多线程并发修改会让问题更严重,但核心触发点还是遍历+修改集合的冲突。而且你的原逻辑还有个小bug:如果列表里有多个设备,只要遍历到第一个不匹配的设备就添加新元素,会导致重复添加同一个设备多次。
解决方案
下面给你三种靠谱的解决方式,按需选择:
1. 改用索引遍历List(最直接的修复)
放弃for-each循环,用基于索引的普通for循环遍历,这样不会触发迭代器的检查,同时调整逻辑先统一检查设备是否存在,再决定是否添加:
@Override public void bleDataReceiverObserver(BluetoothDevice bluetoothDevice) { Log.v("bleDataReceiverObserver", bluetoothDevice.getName()); boolean deviceExists = false; // 先遍历检查设备是否已存在 for (int i = 0; i < list.size(); i++) { BleData bleData = list.get(i); if (bleData.getAddress().equals(bluetoothDevice.getAddress())) { deviceExists = true; break; } } // 不存在则添加 if (!deviceExists) { list.add(new BleData(bluetoothDevice, bluetoothDevice.getAddress(), bluetoothDevice.getName())); } }
2. 使用线程安全的CopyOnWriteArrayList
如果你的场景是多线程操作List(比如BLE回调线程+UI线程),直接用CopyOnWriteArrayList替代普通ArrayList。它会在修改时复制一份新数组,遍历用的是原数组的快照,完全避免并发修改异常,同时自带线程安全性:
// 初始化List时改成CopyOnWriteArrayList private List<BleData> list = new CopyOnWriteArrayList<>(); @Override public void bleDataReceiverObserver(BluetoothDevice bluetoothDevice) { Log.v("bleDataReceiverObserver", bluetoothDevice.getName()); boolean deviceExists = false; for (BleData bleData : list) { if (bleData.getAddress().equals(bluetoothDevice.getAddress())) { deviceExists = true; break; } } if (!deviceExists) { list.add(new BleData(bluetoothDevice, bluetoothDevice.getAddress(), bluetoothDevice.getName())); } }
这种方式适合读多写少的场景(比如BLE扫描),唯一的小缺点是修改时会复制数组,性能开销略高,但对BLE扫描来说完全可以忽略。
3. 遍历List的副本
先复制一份原List的快照,遍历副本而不是原List,这样即使原List被修改,也不会影响遍历过程:
@Override public void bleDataReceiverObserver(BluetoothDevice bluetoothDevice) { Log.v("bleDataReceiverObserver", bluetoothDevice.getName()); boolean deviceExists = false; // 复制原List的副本用于遍历 List<BleData> tempList = new ArrayList<>(list); for (BleData bleData : tempList) { if (bleData.getAddress().equals(bluetoothDevice.getAddress())) { deviceExists = true; break; } } if (!deviceExists) { list.add(new BleData(bluetoothDevice, bluetoothDevice.getAddress(), bluetoothDevice.getName())); } }
如果是多线程场景,建议配合synchronized锁使用,避免复制副本时原List被修改导致的数据不一致。
内容的提问来源于stack exchange,提问作者Genie

