2011-03-01 14 views
22

Hay algo que echo de menos con la noción de sincronización de código en Android.java.util.ConcurrentModificationException en la animación de Android

Escenario

Siempre hay 3 elementos dibujados en la pantalla. Cada imagen se almacena en una ArrayList (lstGraphics). Para este propósito, uso SurfaceView. Una vez que el usuario toque una imagen, se eliminará el mercado de obtención de imagen y se agregará uno nuevo.

Ejemplos de código:

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); 
        } 
       } 
      } 
     }  
... 

Así como puede parecer a primera vista que updatePhysics(). Esto significa que calculo la dirección hacia donde se moverá cada imagen. Aquí también eliminaré las imágenes clicadas de mi lista. Después de eso, verifico si necesito agregar un nuevo ítem en mi lista en manageAnimations() y luego el último paso dibujar todo.

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; 
.... 
} 

El error que consigo es:

> 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) 

Cualquier ayuda es muy apreciada. Gracias.

Respuesta

77

Su problema está en el método de la física, donde se agrega el gráfico y la lista

public void updatePhysics() { 
    synchronized (getHolder()) { 
     for (Graphic graphic : lstGraphics) { 
     //.... Do some checks 
     if (graphic.isTouched()) { 
      lstGraphics.remove(graphic); //your problem 
     } 
    } 
} 

la combinación de for(Graphic graphic : lstGraphics) y lst.Graphics.remove(graphic); hace que el ConcurrentModificationException porque está ejecutando su lista y al mismo tiempo tratar de modificarlo.

Hasta ahora sé dos soluciones:

  1. utilizar un iterador en cambio, si está disponible (no codificado para Android hasta el momento).

    while (iter.hasNext) { 
        if (physicsCondition) iter.remove(); 
    } 
    
  2. uso de una segunda lista para almacenar los elementos de quitar y eliminarlos posteriormente

    List<GraphicsItem> toRemove = new .... 
    for (Graphic graphic : lstGraphics) { 
        if (physicsCondition) { 
         toRemove.add(graphic); 
        } 
    } 
    lstGraphics.removeAll(toRemove); 
    
+1

creé Lista toremove y lo ha probado, trabajó como un encanto. Gracias por tu ayuda. – Alin

+5

@Alin, debe considerar la solución con 'Iterator'. Crear objetos innecesarios en un bucle de juego generalmente es una mala idea. –

8

Como @idefix Dicho esto, usted puede conseguir fácilmente ConcurrentModificationException en el contexto de un único subproceso de esta manera:

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); 
     } 
    } 
} 
0

Este es mi método con @idefix segunda solución:

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;   
} 

Gracias @idefix 1

3

Puede utilizar CopyOnWriteArrayList, como a continuación:

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"); 
     } 
    }