Java.util.ConcurrentModificationException в анимации Android
Есть что-то, что я пропустил с понятием Синхронизация кода на Android.
Сценарий
На экране всегда отображаются 3 элемента. Каждое изображение хранится в ArrayList (lstGraphics). Для этого я использую SurfaceView. Как только пользователь нажимает на изображение, изображение будет удалено, и новый будет добавлен.
Примеры кода:
AnimationHideThread
...
@Override
public void run() {
Canvas c;
while (run) {
c = null;
try {
c = panel.getHolder().lockCanvas(null);
synchronized (panel.getHolder()) {
panel.updatePhysics();
panel.manageAnimations();
panel.onDraw(c);
}
} finally {
if (c != null) {
panel.getHolder().unlockCanvasAndPost(c);
}
}
}
}
...
Итак, как вы можете показаться, я сначала обновляю Physics(). Это означает, что я рассчитываю направление, в которое будут перемещаться каждое изображение. Здесь я также удалю клики из моего списка. После этого я проверяю, нужно ли добавлять новый элемент в свой список в manageAnimations(), а затем на последнем шаге все это вычеркивает.
public class Panel extends SurfaceView implements SurfaceHolder.Callback {
....
public void manageAnimations()
{
synchronized (this.getHolder()) {
...
while (lstGraphics.size()<3) {
lstGraphics.add(createRandomGraphic());
}
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
synchronized (getHolder()) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//... check if a image has been clicked and then set its property
graphic.setTouched(true);
}
}
return true;
}
}
public void updatePhysics() {
synchronized (getHolder()) {
for (Graphic graphic : lstGraphics) {
//.... Do some checks
if (graphic.isTouched())
{
lstGraphics.remove(graphic);
}
}
}
}
@Override
public void onDraw(Canvas canvas) {
/// draw the backgrounds and each element from lstGraphics
}
public class Graphic {
private Bitmap bitmap;
private boolean touched;
private Coordinates initialCoordinates;
....
}
Ошибка, которую я получаю:
> 03-01 10:01:53.365: ERROR/AndroidRuntime(454): Uncaught handler: thread Thread-12 exiting due to uncaught exception
> 03-01 10:01:53.365: ERROR/AndroidRuntime(454): java.util.ConcurrentModificationException
> 03-01 10:01:53.365: ERROR/AndroidRuntime(454): at java.util.AbstractList$SimpleListIterator.next(AbstractList.java:66)
> 03-01 10:01:53.365: ERROR/AndroidRuntime(454): at com.test.customcontrols.Panel.updatePhysics(Panel.java:290)
> 03-01 10:01:53.365: ERROR/AndroidRuntime(454): at com.test.customcontrols.AnimationHideThread.run(AnimationHideThread.java:41)
Любая помощь приветствуется. Спасибо.
Ответы
Ответ 1
Ваша проблема заключается в вашем физическом методе, где вы добавляете графику и список
public void updatePhysics() {
synchronized (getHolder()) {
for (Graphic graphic : lstGraphics) {
//.... Do some checks
if (graphic.isTouched()) {
lstGraphics.remove(graphic); //your problem
}
}
}
комбинация for(Graphic graphic : lstGraphics)
и lst.Graphics.remove(graphic);
вызывает ConcurrentModificationException, потому что вы работаете над своим списком и одновременно пытаетесь его модифицировать.
До сих пор я знаю два решения:
-
Вместо этого используйте Iterator, если он доступен (он никогда не кодируется для Android).
while (iter.hasNext) {
if (physicsCondition) iter.remove();
}
-
используйте второй список для хранения элементов для их удаления и удаления после этого
List<GraphicsItem> toRemove = new ....
for (Graphic graphic : lstGraphics) {
if (physicsCondition) {
toRemove.add(graphic);
}
}
lstGraphics.removeAll(toRemove);
Ответ 2
Как сказал @idefix, вы можете легко получить ConcurrentModificationException в однопоточном контексте следующим образом:
public static void main(String[] args) {
List<String> list = new ArrayList<String>(Arrays.asList("AAA", "BBB"));
for (String s : list) {
if ("BBB".equals(s)) {
list.remove(s);
}
}
}
Ответ 3
Вы можете использовать CopyOnWriteArrayList, как показано ниже:
List<String> myList = new CopyOnWriteArrayList<String>();
myList.add("1");
myList.add("2");
myList.add("3");
myList.add("4");
myList.add("5");
Iterator<String> it = myList.iterator();
while(it.hasNext()){
String value = it.next();
System.out.println("List Value:"+value);
if(value.equals("3")){
myList.remove("4");
myList.add("6");
myList.add("7");
}
}
Ответ 4
Это мой метод с использованием второго решения @idefix:
private List<TYPE> getFilteredData(List<TYPE> data){
List<TYPE> toRemove = new ArrayList<TYPE>(data.size());
synchronized(data){
for(TYPE f : data){
if([CONDITION]){
toRemove.add(f);
Log.w(TAG, "Element removed: "+ f);
}
}
}
data.removeAll(toRemove);
return data;
}
Спасибо @idefix +1