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

Android中访问MutableLiveData对象时出现ConcurrentModificationException排查

问题分析与解决方案

异常原因

java.util.ConcurrentModificationException 触发的核心是多线程环境下对同一列表的并发访问与修改冲突,结合你的代码场景具体有两点:

  1. ArrayList迭代器的快速失败机制
    ArrayList的迭代器采用快速失败(fail-fast)设计:迭代器创建后,若列表结构(元素数量)被修改,或多线程中一个线程遍历、另一个线程修改列表,迭代器在执行next()时会检测到列表的modCount(修改次数)与迭代器内部的expectedModCount不一致,直接抛出异常。

    你的代码中,虽然set()方法不会改变列表大小,但遍历Answer列表后,你执行了postValue(exam)和updateQuestion(1),这两个操作可能触发其他线程(比如LiveData观察者线程、主线程UI更新逻辑)访问并修改同一个Exam/Question/Answer列表,和当前遍历操作产生竞态条件,触发异常。

  2. 冗余操作增加并发风险
    遍历逻辑里的exam.getQuestions().get(questionPosition).getAnswers().set(indexAnswer, answer)完全多余:Answer是引用类型,通过iterator.next()拿到的就是列表中的对象引用,直接调用answer.setAnswer(...)会同步修改列表内的元素,无需再调用set()放回列表。这个冗余操作虽不直接修改列表结构,但在多线程场景下会增加竞态条件的触发概率。

解决方案

针对你的需求,从避免并发修改和规范LiveData使用两个方向调整代码:

1. 移除冗余操作,简化遍历逻辑

直接修改Answer对象属性即可,无需调用列表set()方法:

Iterator<Answer> iterator = exam.getQuestions().get(questionPosition).getAnswers().iterator();
while (iterator.hasNext()) {
    Answer answer = iterator.next();
    // 直接修改对象属性,无需放回列表
    answer.setAnswer(answer.getId() == checkedId);
}

2. 使用深副本修改后再post,避免共享对象的并发冲突

LiveData的最佳实践是不直接修改其持有的对象实例,而是创建对象的深副本,修改副本后再post新实例,确保观察者拿到的是独立对象,不会和修改操作冲突:

// 1. 创建Exam的深副本
Exam newExam = new Exam();
List<Question> newQuestions = new ArrayList<>();
for (Question q : exam.getQuestions()) {
    Question newQ = new Question();
    newQ.setText(q.getText());
    newQ.setIndex(q.getIndex());
    
    // 拷贝Answer列表
    List<Answer> newAnswers = new ArrayList<>();
    for (Answer a : q.getAnswers()) {
        Answer newA = new Answer();
        newA.setId(a.getId());
        newA.setAnswer(a.isAnswer());
        newAnswers.add(newA);
    }
    newQ.setAnswers(newAnswers);
    newQuestions.add(newQ);
}
newExam.setQuestions(newQuestions);

// 2. 修改副本中的Answer
List<Answer> targetAnswers = newExam.getQuestions().get(questionPosition).getAnswers();
for (Answer answer : targetAnswers) {
    answer.setAnswer(answer.getId() == checkedId);
}

// 3. 提交新副本到LiveData
questionViewModel.getExam().postValue(newExam);

3. 封装ViewModel内的修改逻辑,避免外部直接操作LiveData对象

在ViewModel中封装Answer修改逻辑,避免Fragment直接操作LiveData持有的实例,进一步降低并发风险:

// ViewModel内新增方法
public void updateAnswer(int questionPos, int checkedId) {
    Exam currentExam = liveExam.getValue();
    if (currentExam == null) return;

    // 创建深副本并修改
    Exam newExam = createDeepCopy(currentExam);
    List<Answer> answers = newExam.getQuestions().get(questionPos).getAnswers();
    for (Answer answer : answers) {
        answer.setAnswer(answer.getId() == checkedId);
    }

    liveExam.postValue(newExam);
}

// 封装深拷贝逻辑
private Exam createDeepCopy(Exam original) {
    Exam copy = new Exam();
    List<Question> questionCopies = new ArrayList<>();
    for (Question q : original.getQuestions()) {
        Question qCopy = new Question();
        qCopy.setText(q.getText());
        qCopy.setIndex(q.getIndex());
        
        List<Answer> answerCopies = new ArrayList<>();
        for (Answer a : q.getAnswers()) {
            Answer aCopy = new Answer();
            aCopy.setId(a.getId());
            aCopy.setAnswer(a.isAnswer());
            answerCopies.add(aCopy);
        }
        qCopy.setAnswers(answerCopies);
        questionCopies.add(qCopy);
    }
    copy.setQuestions(questionCopies);
    return copy;
}

Fragment中直接调用ViewModel方法即可:

questionViewModel.updateAnswer(questionPosition, checkedId);
try {
    questionViewModel.updateQuestion(1);
} catch (Exception e) {
    e.printStackTrace();
}
onUpdateQuestion();

内容的提问来源于stack exchange,提问作者Farhood Naqizadeh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 22:54:56