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

Tengo una aplicación en producción durante algunas semanas, usando ACRA, y no tuve ningún error hasta que hoy se informó un error extraño.

Tengo:

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

proveniente de este método en el seguimiento de la pila (retrocedido):

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

Y este es el fragmento de 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("");
    }
}

Dónde addInstructionsIfNeeded() se llama desde un mensaje enviado por el controlador (el encabezado de la interfaz de usuario).

  • onPostExecute() se ejecuta en el subproceso de la interfaz de usuario, entonces, ¿por qué tengo un "subproceso incorrecto"?
  • Este código ya se ejecutó 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 con SDK 4.0.4

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

EDITAR: Todo esto sucede en un fragmento

preguntado el 03 de mayo de 12 a las 07:05

El problema esta en la linea Activity a = getActivity(); debe hacer eso antes de ingresar a AsyncTask. y debe usar el constructor de clase para este tipo de inicialización:

Si mantengo una referencia a la actividad en una variable miembro, ¿no mantendrá vivo en algunos casos un objeto de actividad obsoleto, lo que provocará una pérdida de memoria? (hasta que la instancia de la clase interna muera) -

6 Respuestas

estaba sufriendo el mismo problema, este es otro error de marco de Android ...

Qué está pasando:

en determinadas circunstancias, una aplicación puede tener más de un "looper" y, por lo tanto, más de un "subproceso de interfaz de usuario"

--nota al margen-- Estoy usando el término "subproceso de interfaz de usuario" en los sentidos más amplios en esta respuesta, ya que cuando las personas dicen "subproceso de interfaz de usuario" generalmente se refieren al subproceso principal o de entrada, Android como muchos de los otros sistemas operativos anteriores, permite múltiples bombas de mensajes ( llamado a Looper en Android, consulte: http://en.wikipedia.org/wiki/Event_loop) para diferentes árboles de interfaz de usuario, ya que Android para todos los efectos es capaz de ejecutar más de un "subproceso de interfaz de usuario" en ciertas circunstancias y el uso de ese término conduce a ambigüedades desenfrenadas... --nota al margen final--

esto significa:

ya que una aplicación puede tener más de un "subproceso de interfaz de usuario" y un AsyncTask siempre "Se ejecuta en el subproceso de la interfaz de usuario" [árbitro], alguien decidió [mal] que, en lugar de que AsyncTask siempre se ejecute en su subproceso de creación (que en el 99.999999 % de los casos sería el "subproceso de interfaz de usuario" correcto), decidió 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á así:

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

eso es un gran FALL para AsyncTask

como se muestra, esto se puede mitigar usando un Handler.post(Runnable) en mi caso específico, la dualidad de mi situación de "subproceso de interfaz de usuario" se debió al hecho de que estaba creando un cuadro de diálogo en respuesta a un método de interfaz de JavaScript llamado desde un WebView, básicamente: el WebView tenía su propio "subproceso de interfaz de usuario" y ese era el que estaba ejecutando actualmente ...

por lo que puedo decir (sin realmente preocuparme o leer demasiado) parece que el AsyncTask Los métodos de devolución de llamada de clase en general se ejecutan en un único controlador instanciado estáticamente (ver: 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 se ejecutará en el "subproceso principal" o "subproceso de entrada", al que incorrectamente se refieren como el "subproceso de interfaz de usuario" (que se supone que es cualquier subproceso en el que tienen lugar interacciones de IU, por ejemplo, subprocesos múltiples en este caso) esto es tanto artesanía de mala calidad como documentación de mala calidad del equipo de Android... salsa débil, la salsa es débil

espero que esto te ayude -ck

contestado el 21 de mayo de 12 a las 19:05

¡Gracias por compartir tu investigación! Es muy interesante. - Amir Uval

Guau. me disculpo por el spam de comentarios de "gracias", pero de verdad, gracias. en mi caso, estaba ejecutando una tarea asíncrona en la actividad de creación de accesos directos, y estaba fallando en todo tipo de errores relacionados con mensajes/controladores. Usé un ejecutor + controlador en lugar de mi tarea asíncrona, y todo está bien. - jeffrey blattman

¿Sabe por qué WebView tiene su propio grupo de subprocesos? Parece que no es completamente culpa de Asynctask en este caso: el error real en su código se debió a que la devolución de llamada de la vista web estaba en el subproceso de interfaz de usuario incorrecto. - David T.

@DavidT. Bueno, la devolución de llamada se realizará de forma asíncrona, p. dado que WebView se ejecuta fuera de su proceso, no hay sincronización. ¿Podría la implementación publicar un mensaje en el controlador adecuado para que la devolución de llamada se produzca en el subproceso inicial de la interfaz de usuario? Claro, pero ralentizaría el proceso y no le permitiría volver al JavaScript desde la devolución de llamada, que actualmente es posible y muy útil. Entonces, no. La interfaz de JavaScript es correcta y se comporta de manera comprensible. - ckozl

@DavidT. myLooper es el looper de WebView, y se estaba ejecutando en 16 en mi escenario (en un dispositivo real, hace más de un año). Le sugiero que comience una nueva pregunta SO y veré si puedo ayudarlo más allí, esta sección de comentarios se está haciendo demasiado larga... - ckozl

Tenía el mismo problema. Resuelto en mi caso

Breve explicación:

  1. Ejecutando AsynckTask para el primera vez on sin interfaz de usuario subproceso con looper conduce a la carga de AsyncTask.class y la inicialización manejador al controlador construido sobre eso sin interfaz de usuario looper
  2. Actuales manejador esta conectado a eso sin interfaz de usuario hilo para CUALQUIER instancia de subclases AsyncTask y onPreExecute, onProgressUpdate y enPostExecute se invocarán métodos en ese sin interfaz de usuario subproceso (a menos que se descargue AsyncTask.class)
  3. Cualquier intento de lidiar con la interfaz de usuario dentro de cualquiera de los métodos anteriores provocará un bloqueo con android.view.ViewRootImpl$CalledFromWrongThreadException
  4. Para evitar tal situación se debe siempre correr (al menos por el primera vez) Tarea asincrónica en Hilo de interfaz de usuario para permitir que AsyncTask manejador-campo ser inicializado con looper de la interfaz de usuario

La historia:

Había dos aplicaciones de producción: A - aplicación principal de Android y B - alguna aplicación de utilidad.

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

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 AsinckTask.onPostExecute()

Después de algunas investigaciones, apareció esa aplicación de utilidad. B usado Tarea asíncrona dentro de su Controlador de hilo

Los rastros 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 la interfaz de usuario.

Este controlador es estático y se inicializará durante la carga de clase, es decir, primero nueva tarea asincrónica () apariencia

Esto significa que enPostExecute siempre se publicará en ese hilo donde nueva tarea asincrónica () fue llamado por primera vez (a menos que AsyncTask.class se descargue y vuelva a cargar)

En mi caso el flujo fue 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

Luego, un amigo mío me mostró documentación relacionada de android.desarrolladores (Reglas de subprocesamiento sección):

La clase AsyncTask debe cargarse en el subproceso de la interfaz de usuario. Esto se hace automáticamente a partir de JELLY_BEAN. La instancia de la tarea debe crearse en el subproceso de la interfaz de usuario. ejecutar (Params...) debe invocarse en el subproceso de la interfaz de usuario.

Espero que pueda ayudar a aclarar la situación)

Respondido 27 Abr '13, 21:04

infinitas gracias, aunque no debe ser, tu explicación es perfecta. Acabo de configurar un AsyncTask en blanco predeterminado y lo ejecuté desde dentro de un ejecutable onUiThread. - JBD

Tal vez la razón es Ráfaga? Tuve esta excepción cuando usé Flurry 3.2.1. Pero cuando volví a Flurry 3.2.0 No tuve esta excepción

usar ráfaga 3.2.2 y por encima.

Respondido el 04 de Septiembre de 13 a las 10:09

Lo mismo aquí, tuve el problema una y otra vez antes de cambiar a un frasco más antiguo. - a54studio

Lo mismo aquí usando Flurry 3.2.1. Ejecutar una AsyncTask vacía solo una vez antes de realizar cualquier llamada a Flurry resolvió el problema. - OfertaR

Hai Aivaler, también tengo el mismo problema. ¿Puedes tener Flurry 3.2.0.jar para resolver mi bloqueo? - Kalpesh

Si. Yo tengo. Escríbeme correo electrónico. - Valerybodak

Esto se solucionó en Flurry 3.2.2, que salió el 26 de agosto. - avram lyon

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 subproceso principal diferente que no es nuestro subproceso principal, lo verifiqué 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 iniciará AsynTask en un subproceso principal que no es el principal de la aplicación, y hará que la aplicación se bloquee en cualquier otra AsyncTask que hará cualquier interfaz de usuario en la ejecución posterior. fallando con CalledFromWrongThreadException

Espero haber aclarado un poco más las cosas.

Gracias a todos por la gran ayuda en esto.

contestado el 18 de mayo de 14 a las 18:05

¿Puede explicar por qué esta línea debería ayudar? (preferiblemente editando la respuesta). ¡Gracias! - Amir Uval

Gracias por editar, entiendo que obligas a que se cree una instancia de la clase AsyncTask en el subproceso principal antes de crearla dentro del subproceso. Creo que podría lograr el mismo efecto creando la instancia real de AsyncTask en el hilo principal, y simplemente llame a la ejecución desde el hilo. Pero mi +1 sin embargo .. - Amir Uval

Acepto que esto funcionará. En realidad, lo que me importaba era probar la solución, que ahora sé que funciona. ¡Gracias! - shimi_tap

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

            };

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

contestado el 03 de mayo de 12 a las 07:05

No onPostExecute() siempre ejecutar en el subproceso de interfaz de usuario de todos modos? (entonces runOnUiThread sería una redundancia) - Amir Uval

creo que el problema esta en la linea Activity a = getActivity(); Creo que deberías hacer eso antes de entrar en AsyncTask

contestado el 03 de mayo de 12 a las 07:05

Gracias por la idea. No estoy seguro de que esté relacionado, ya que getActivity() no está manipulando la interfaz de usuario. Una vez (en alguna prueba de caso extremo) devolvió nulo, es por eso que lo verifico antes de continuar. - Amir Uval

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.