Android: get CalledFromWrongThreadException en onPostExecute () – ¿Cómo podría ser?

Tengo una aplicación en producción durante unas semanas, utilizando ACRA, y tuve cero errores hasta que un error extraño informó hoy.

Tengo:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 

Procedentes de este método en el rastreo de pila (retrocedido):

 at my.app.CountdownFragment$1.void onPostExecute(java.lang.Object)(SourceFile:1) 

Y este es el fragmento fuente relevante:

  private void addInstructionsIfNeeded() { if (S.sDisplayAssist) { new AsyncTask<String, Void, String>() { @Override protected String doInBackground(String... params) { return null; } /* * runs on the ui thread */ protected void onPostExecute(String result) { Activity a = getActivity(); if (S.sHelpEnabled && a != null) { in = new InstructionsView(a.getApplicationContext()); RelativeLayout mv = (RelativeLayout) a .findViewById(R.id.main_place); mv.addView(in.prepareView()); } }; }.execute(""); } } 

Donde addInstructionsIfNeeded() se llama desde un mensaje distribuido por el manejador (thead de la interfaz de usuario).

  • onPostExecute() ejecuta en el subproceso de interfaz de usuario, así que ¿por qué tengo "subproceso incorrecto"?
  • Este código ya funcionaba en más de 150 dispositivos, y más de 100000 veces (según Flurry), y nunca tuvo este error.
  • El dispositivo de origen es Samsung SGH-I997 que ejecuta el SDK 4.0.4

Mi pregunta es: ¿Cómo podría ser?

EDIT : Todo esto sucede en un fragmento

Yo estaba sufriendo el mismo problema, este es otro error de marco android …

que esta pasando:

En determinadas circunstancias una aplicación puede tener más de un "looper" y por lo tanto más de un "hilo UI"

–side note– estoy usando el término "hilo UI" en el más flojo de los sentidos en esta respuesta, ya que cuando la gente dice "hilo de la interfaz de usuario" por lo general significan hilo principal o de entrada, Android como muchos otros sistemas operativos anteriores, Para las bombas de mensajes múltiples (llamado Looper en Android, consulte: http://en.wikipedia.org/wiki/Event_loop ) para diferentes árboles de interfaz de usuario, como tal android para todos los intentos y propósitos es capaz de ejecutar más de una "UI Hilo "en ciertas circunstancias y el uso de ese término conduce a ambigüedades desenfrenadas … –der nota lateral –

esto significa:

Ya que una aplicación puede tener más de un "hilo UI" y un AsyncTask siempre "Se ejecuta en el hilo UI" [ref] , alguien decidió [mal] que en lugar de AsyncTask siempre se ejecuta en su hilo de creación (que en el 99,999999% de los casos Sería el "hilo de la interfaz de usuario" correcto) decidieron usar hocus pocus (o un atajo mal diseñado, usted decide) para ejecutar en el "looper principal" ..

ejemplo:

  Log.i("AsyncTask / Handler created ON: " + Thread.currentThread().getId()); Log.i("Main Looper: " + Looper.getMainLooper().getThread().getId() + " myLooper: "+ Looper.myLooper().getThread().getId()); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { Log.i("doInBackground ran ON: " + Thread.currentThread().getId()); // I'm in the background, all is normal handler.post(new Runnable() { @Override public void run() { Log.i("Handler posted runnable ON: " + Thread.currentThread().getId()); // this is the correct thread, that onPostExecute should be on } }); return null; } @Override protected void onPostExecute(Void result) { Log.i("onPostExecute ran ON: " + Thread.currentThread().getId()); // this CAN be the wrong thread in certain situations } }.execute(); 

Si se llama desde la mala situación descrita anteriormente la salida se verá algo como esto:

  AsyncTask / Handler created ON: 16 Main Looper: 1 myLooper: 16 doInBackground ran ON: 12 onPostExecute ran ON: 1 Handler posted runnable ON: 16 

AsyncTask es una gran AsyncTask para AsyncTask

Como se muestra esto se puede mitigar con un Handler.post(Runnable) en mi caso específico de la dualidad de mi "hilo de interfaz de usuario" situación fue causada por el hecho de que estaba creando un diálogo en respuesta a un método de interfaz JavaScript llamado de un WebView , Básicamente: el WebView tenía su propio "hilo de interfaz de usuario" y que era el que estaba corriendo actualmente ..

Por lo que puedo decir (sin realmente preocuparse o leer demasiado) parece que los métodos de devolución de llamada de la clase AsyncTask en general ejecutan un solo manejador instanciado estáticamente (vea: http://grepcode.com/file/repository. Grepcode.com/java/ext/com.google.android/android/4.0.3_r1/android/os/AsyncTask.java#AsyncTask.0sHandler ), lo que significa que siempre va a ejecutarse en el "hilo principal" o " Hilo de entrada "que se refieren incorrectamente como el" hilo de interfaz de usuario "(que se presume como cualquier subproceso donde se producen interacciones de interfaz de usuario, por ejemplo, múltiples subprocesos en este caso) esto es tanto la artesanía de mala calidad y la documentación de mala calidad del equipo android … Salsa débil, la salsa es débil

Espero que esto le ayude a buscar

Tenía el mismo problema. Resuelto en mi caso

Breve explicación:

  1. Ejecutar AsynckTask por primera vez en el subproceso no UI con looper conduce a la carga AsyncTask.class y la inicialización sHandler al manejador construido en ese looper no UI .
  2. Ahora sHandler está conectado a ese subproceso no UI para CUALQUIER instancia de las subclases AsyncTask y los métodos onPreExecute , onProgressUpdate y onPostExecute serán invocados en ese subproceso no UI (a menos que AsyncTask.class se descargue)
  3. Cualquier intento de tratar con la interfaz de usuario dentro de cualquiera de los métodos anteriores dará lugar a bloqueo con android.view.ViewRootImpl $ CalledFromWrongThreadException
  4. Para evitar tal situación siempre se debe ejecutar (al menos por primera vez ) AsyncTask en el hilo de la interfaz de usuario para permitir que el campo sHandler de AsyncTask se inicialice con el looper de la interfaz de usuario

La historia:

Había dos aplicaciones de producción: A – la aplicación principal de Android y B – algunos utilty app.

Después de la aplicación de integración B ito app A recibimos muchos fallos:

 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 

Para el método que se ejecuta desde AsynckTask.onPostExecute ()

Después de alguna investigación apareció que la aplicación de utilidad B utilizó AsyncTask dentro de su HandlerThread

Las huellas se encontraron en el código fuente de AsyncTask:

 private static final InternalHandler sHandler = new InternalHandler(); 

Este es el controlador que se utiliza para enviar onPostExecute () al subproceso de UI.

Este controlador es estático y se inicializará durante la carga de la clase, es decir, el primer aspecto de AsyncTask () nuevo

Esto significa que onPostExecute siempre se publicará en ese subproceso en el que se llamó AsyncTask () por primera vez (a menos que AsyncTask.class se descargue y se cargue de nuevo)

En mi caso el flujo era algo como esto:

 1 - starting app A 2 - initializing B form A 3 - B creates its own HandlerThread and launches AsyncTask <- now onPostExecute wil be posted to this HandlerThread no matter where from an instance of AsyncTask will be launched in future 4 - create AsyncTask in the app A for a long operation and update UI in its onPostExecute 5 - when executing onPostExecute() the CalledFromWrongThreadException is thrown 

Entonces un amigo mío me mostró la documentación relacionada de android.developers (sección de reglas de Threading ):

La clase AsyncTask debe cargarse en el subproceso de la interfaz de usuario. Esto se realiza automáticamente a partir de JELLY_BEAN. La instancia de tarea debe crearse en el subproceso de la interfaz de usuario. Execute (Params …) debe ser invocado en el subproceso de UI.

Espero que pueda ayudar a aclarar la situación)

Tal vez la razón es Flurry ? Tuve esta excepción cuando usé Flurry 3.2.1 . Pero cuando volví a Flurry 3.2.0 no tenía esta excepción

Utilice Flurry 3.2.2 y superiores.

Colocar la siguiente línea de código en la aplicación onCreate debería resolver el problema:

  /** * Fixing AsyncTask Issue not called on main thread */ try { Class.forName("android.os.AsyncTask"); } catch (ClassNotFoundException e) { e.printStackTrace(); } 

Parece que el problema se crea cuando la clase AsyncTask se inicia por primera vez en un hilo principal diferente que no es nuestro hilo principal, lo he comprobado agregando el código en la parte inferior, a mi aplicación onCreate

  new Thread(new Runnable() { @Override public void run() { Log.i("tag","1.3onPostExecute ran ON: " + Thread.currentThread().getId()); Looper.prepare(); new AsyncTask<Void,Void,Void>(){ @Override protected Void doInBackground(Void... params) { Log.i("tag","2onPostExecute ran ON: " + Thread.currentThread().getId()); return null; } @Override protected void onPostExecute(Void aVoid) { Log.i("tag","1.2onPostExecute ran ON: " + Thread.currentThread().getId()); super.onPostExecute(aVoid); } }.execute(); Looper.loop(); Looper.myLooper().quit(); } }).start(); 

Este código init el AsynTask en un Thread principal que no es el principal de la aplicación, y hará que la aplicación se bloquee en cualquier AsyncTask otro que hará cualquier interfaz de usuario en la post-ejecución. Fallando con la excepción CalledFromWrongThreadException

Espero que aclaró las cosas un poco más.

Gracias a todos por la gran ayuda en esto.

Dónde está

 runOnUiThread(new Runnable() { public void run() { /*code*/ } ); 

En tu código

 /* * runs on the ui thread */ protected void onPostExecute(String result) { Activity a = getActivity(); if (S.sHelpEnabled && a != null) { in = new InstructionsView(a.getApplicationContext()); runOnUiThread(new Runnable() { public void run() { RelativeLayout mv = (RelativeLayout) a .findViewById(R.id.main_place); mv.addView(in.prepareView()); } } }; 

Pruebe este código. Creo que esto solucionaría el problema

Creo que el problema está en la línea Activity a = getActivity(); Creo que deberías hacer eso antes de entrar en el AsyncTask

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.