2010-11-03 15 views
24

En un proyecto existente Android que he encontrado el siguiente fragmento de código (donde inserté la camada depuración)Ejecutable se registró con éxito pero no corre

ImageView img = null; 

public void onCreate(...) { 

    img = (ImageView)findViewById(R.id.image); 

    new Thread() { 
     public void run() { 
      final Bitmap bmp = BitmapFactory.decodeFile("/sdcard/someImage.jpg"); 
      System.out.println("bitmap: "+bmp.toString()+" img: "+img.toString()); 
      if (!img.post(new Runnable() { 
       public void run() { 
        System.out.println("setting bitmap..."); 
        img.setImageBitmap(bmp); 
        System.out.println("bitmap set."); 
       } 
      })) System.out.println("Runnable won't run!"); 
      System.out.println("runnable posted"); 
     } 
    }.start(); 

Nuevo en el desarrollo de Android, y después de haber buscado en Google alrededor, Entiendo que esta es la forma de hacer las cosas sin bloquear el hilo principal (UI), mientras aún se configura la imagen en el hilo de la interfaz de usuario después de la decodificación. (at least according to android-developers) (que he verificado por la tala Thread.currentThread().getName() en varios lugares)

ahora veces la imagen simplemente no se presenta, única y stdout dice

I/System.out(8066): bitmap: [email protected] img: [email protected] 
I/System.out(8066): runnable posted 

con no dejar rastro de los mensajes de la Runnable. Aparentemente, Runnable no hace run(), aunque img.post() devuelve true. Tirando de ImageView en onCreate() y declararlo final no ayuda.

No tengo ni idea. Simplemente establecer el mapa de bits directamente, mientras bloquea el hilo de la interfaz de usuario, arregla las cosas, pero quiero hacer las cosas bien. ¿Alguien entiende lo que está pasando aquí?

(ps. Todo esto fue observado en un Android 1.6 y el androide-3 SDK)

+0

preguntas similares con respuestas útiles: [¿Cuál es la diferencia entre Activity.runOnUiThread (acción ejecutable) y Handler.post()?] (Http: // stackoverflow .com/questions/1839625/whats-the-difference-between-activity-runonuithreadrunnable-action-and-handler), [Diferencia entre Handler.post (Runnable r) y Activity.runOnUiThread (Runnable r)] (http://stackoverflow.com/questions/7452884/difference-between-handler-postrunnable-r-and-activity-runonuithreadrunnable) –

Respuesta

52

Si nos fijamos en los documentos de View.post hay algo de información relevante:

Este método se puede invocar desde fuera de la interfaz de usuario enhebrar solo cuando esta vista se adjunta a una ventana.

Puesto que usted está haciendo esto en onCreate, es probable que a veces su View no se pueden unir a la ventana todavía. Puede verificar esto anulando onAttachedToWindow y colocando algo en los registros y también registrando cuando publica. Verá que cuando la publicación falla, la llamada posterior ocurre antes del onAttachedToWindow.

Como los otros han mencionado, puede usar Activity.runOnUiThread o proporcionar su propio controlador. Sin embargo, si desea hacerlo directamente desde el View sí, sólo tiene que conseguir el View 's controlador:

view.getHandler().post(...); 

Esto es especialmente útil si usted tiene una vista personalizada que incluye algún tipo de carga en segundo plano. También existe la ventaja adicional de no tener que crear un nuevo controlador separado.

+9

Solo he investigado esto brevemente, pero parece que cuando la vista no está conectada a una ventana tampoco tiene un controlador. – ThomasW

+0

@ThomasW Mi solución funcionó en mi escenario, pero YMMV seguro. Por lo que yo sé, los documentos no especifican cuándo 'getHandler()' va a ser válido, pero al examinar el [código] (http://grepcode.com/file/repository.grepcode.com/java /ext/com.google.android/android/2.3_r1/android/view/View.java#View.dispatchAttachedToWindow%28android.view.View.AttachInfo%2Cint%29) Parece que mi solución solo funciona cuando la vista está en menos * sobre * para adjuntar. Si nunca intentas conectarlo a una ventana, entonces estás en lo cierto, no habrá ningún controlador disponible. – kabuko

+1

@kabuko esto debería editarse ahora que la funcionalidad ha cambiado. Los documentos ya no tienen la cláusula que referencia. Supongo que esto se debe a que, según la fuente, la funcionalidad parece haber cambiado: '// Ejecutar acciones en cola en cada recorrido en caso de que una vista separada pusiera en cola una acción \ n getRunQueue(). ExecuteActions (attachInfo.mHandler); 'Esto significa que publicar en una Vista debería funcionar siempre que la Vista se haya adjuntado una vez (donde obtiene la referencia inicial al Manejador de ViewRootImpl). Pero aún no puede publicar en una vista antes de que se haya adjuntado inicialmente. – dcow

7

Creo que el problema es que está actualizando la interfaz de usuario (ImageView) con un hilo separado, lo cual no es el hilo de interfaz de usuario . La interfaz de usuario solo se puede actualizar con el subproceso UI.

Puede resolver esto mediante el uso Handler:

Handler uiHandler; 

public void onCreate(){ 
    ... 
    uiHandler = new Handler(); // This makes the handler attached to UI Thread 
    ... 
} 

luego vuelva a colocar el:

if (!img.post(new Runnable() { 

con

uiHandler.post(new Runnable() { 

para asegurarse de que el imageview se actualiza en el hilo de interfaz de usuario.

Handler es un concepto bastante confuso, Yo también me llevó horas de investigación para comprender realmente sobre esto;)

+0

No entiendo por qué, pero parece que también funciona. Entonces, cualquier cosa es más confiable que 'View.post()'. ¡Gracias! – mvds

+0

¡Sorprendente una vez más! Esto funciona. ¡Gracias! ¡Pero me pregunto si 'View.post' y' postDelayed' son tan impredecibles! Funcionan perfectamente en algunos dispositivos Android y fallan miserablemente en algunos. Duh! – sud007

6

, no veo nada de malo, obviamente, lo que tienes ahí; llamar a View.post() debería hacer que se ejecute en el subproceso de UI. Si tu actividad se fue (quizás a través de una rotación de pantalla), tu ImageView no se actualizaría, pero aún esperaría que una entrada de registro dijera "setting bitmap ...", incluso si no pudieras verla.

Sugiero tratar lo siguiente y ver si se hace una diferencia:

1) Uso Log.d (el registrador estándar de Android), en lugar de que System.out

2) Pase su Ejecutable a la actividad. runOnUiThread() en lugar de View.post()

+0

No entiendo por qué, pero parece que funciona. En uno de cada diez intentos, obtuve una falla de segmentación (aparentemente en libc), pero no puedo creer que esto esté relacionado. – mvds

+0

Buena llamada @Shawn. Nunca noté la función runOnUiThread(). Curiosamente, revisé el código fuente y runOnUiThread usa Handler para implementarlo. Así que @mvds, ambos trabajos no son coincidencia, están usando el mismo método bajo el capó. – xandy

+0

Lo que todavía no está claro para mí es por qué la solución de @mdvs no funciona. El artículo de Google en http://developer.android.com/resources/articles/painless-threading.html hace referencia a View.post (Runnable), que es lo que usó, y el Javadoc dice "Causa que Runnable se agregue al cola de mensajes. El ejecutable se ejecutará en el subproceso de la interfaz de usuario." –

1

Tuve el mismo problema y el uso de view.getHandler() también falló porque el controlador no estaba presente. runOnUiThread() resolvió el problema. Es de suponer que esto realmente hace algunas colas hasta que la interfaz de usuario esté lista.

La causa para mí estaba llamando a la tarea de carga icono en una clase base y el resultado que se devuelven tan rápidamente que la clase principal no hubiera estabished la vista (getView() en el fragmento).

Soy un poco sospechoso de que falle falsamente alguna vez. ¡Pero ahora estoy listo para eso! Gracias chicos.

10

Extendí ImageView clase para resolver este problema. Recopilo los ejecutables pasados ​​a publicar mientras que la vista no está adjuntada a la ventana y en el onAttachedToWindow ejecutable recopilada.

public class ImageView extends android.widget.ImageView 
{ 
    List<Runnable> postQueue = new ArrayList<Runnable>(); 
    boolean attached; 

    public ImageView(Context context) 
    { 
     super(context); 
    } 

    public ImageView(Context context, AttributeSet attrs) 
    { 
     super(context, attrs); 
    } 

    public ImageView(Context context, AttributeSet attrs, int defStyle) 
    { 
     super(context, attrs, defStyle); 
    } 

    @Override 
    protected void onAttachedToWindow() 
    { 
     super.onAttachedToWindow(); 

     attached = true; 

     for (Iterator<Runnable> posts = postQueue.iterator(); posts.hasNext();) 
     { 
      super.post(posts.next()); 
      posts.remove(); 
     } 
    } 

    @Override 
    protected void onDetachedFromWindow() 
    { 
     attached = false; 
     super.onDetachedFromWindow(); 
    } 

    @Override 
    public boolean post(Runnable action) 
    { 
     if (attached) return super.post(action); 
     else postQueue.add(action); 
     return true; 
    } 
} 
+0

¡Esto realmente me ayudó! Gracias!) – ruslik

+0

Impresionante. ¡Esta es la respuesta! – AndyB

3

utilizar el siguiente código, puede publicar su código de TrenzadoPrincipal cualquier momento y lugar, pero no depende de ninguna Context o Activity. Eso puede evitar que view.getHandler() fracaso o tediosas onAttachedToWindow() telas etc.

new Handler(Looper.getMainLooper()).post(new Runnable() { 
     @Override 
     public void run() { 
      //TODO 
     } 
    }); 
Cuestiones relacionadas