Pregunta

Tengo una AsyncTask que recupera algunos datos y luego actualiza la interfaz de usuario con estos nuevos datos. Se ha trabajando muy bien desde hace meses, pero recientemente he añadido una característica que muestra una notificación cuando hay nuevos datos. Ahora bien, cuando se pone en marcha mi aplicación a través de la notificación, a veces me da esta excepción y onPostExecute no se llama.

Esto es lo que sucede cuando se inicia la aplicación:

1) Abrir la interfaz de usuario y encontrar puntos de vista

2) Cancelar la alarma (a través de AlarmManager) que los controles para los nuevos datos y restablecer la alarma. (Esto es para que si el usuario desactiva la alarma se cancela antes de la próxima vez que él / ella se reinicia.)

3) Iniciar el AsyncTask. Si la aplicación se puso en marcha a partir de la notificación, pasar un poco de los datos y luego cancelar la notificación.

estoy atascado en lo que podría ser la causa de esta excepción. Parece que la excepción es el código de AsyncTask, así que no estoy seguro de cómo puedo solucionarlo.

Gracias!

Esta es la excepción:

I/My App(  501): doInBackground exiting
W/MessageQueue(  501): Handler{442ba140} sending message to a Handler on a dead thread
W/MessageQueue(  501): java.lang.RuntimeException: Handler{442ba140} sending message to a Handler on a dead thread
W/MessageQueue(  501):  at android.os.MessageQueue.enqueueMessage(MessageQueue.java:179)
W/MessageQueue(  501):  at android.os.Handler.sendMessageAtTime(Handler.java:457)
W/MessageQueue(  501):  at android.os.Handler.sendMessageDelayed(Handler.java:430)
W/MessageQueue(  501):  at android.os.Handler.sendMessage(Handler.java:367)
W/MessageQueue(  501):  at android.os.Message.sendToTarget(Message.java:348)
W/MessageQueue(  501):  at android.os.AsyncTask$3.done(AsyncTask.java:214)
W/MessageQueue(  501):  at java.util.concurrent.FutureTask$Sync.innerSet(FutureTask.java:252)
W/MessageQueue(  501):  at java.util.concurrent.FutureTask.set(FutureTask.java:112)
W/MessageQueue(  501):  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:310)
W/MessageQueue(  501):  at java.util.concurrent.FutureTask.run(FutureTask.java:137)
W/MessageQueue(  501):  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1068)
W/MessageQueue(  501):  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:561)
W/MessageQueue(  501):  at java.lang.Thread.run(Thread.java:1096)

EDIT: Aquí está mi método onCreate en mi actividad principal (el abierto por la notificación). Hay algunos onClickListeners que he omitido para ahorrar espacio. No creo que deberían tener ningún efecto, ya que los botones están unidos a no ser presionado.

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Call the parent

    setContentView(R.layout.main); // Create the UI from the XML file

    // Find the UI elements
    controls = (SlidingDrawer) findViewById(R.id.drawer); // Contains the
    // buttons
    // comic = (ImageView) findViewById(R.id.comic); // Displays the comic
    subtitle = (TextView) findViewById(R.id.subtitleTxt); // Textbox for the
    // subtitle
    prevBtn = (Button) findViewById(R.id.prevBtn); // The previous button
    nextBtn = (Button) findViewById(R.id.nextBtn); // The next button
    randomBtn = (Button) findViewById(R.id.randomBtn); // The random button
    fetchBtn = (Button) findViewById(R.id.comicFetchBtn); // The go to specific id button
    mostRecentBtn = (Button) findViewById(R.id.mostRecentBtn); // The button to go to the most recent comic
    comicNumberEdtTxt = (EditText) findViewById(R.id.comicNumberEdtTxt); // The text box to Zooming image view setup
    zoomControl = new DynamicZoomControl();

    zoomListener = new LongPressZoomListener(this);
    zoomListener.setZoomControl(zoomControl);

    zoomComic = (ImageZoomView) findViewById(R.id.zoomComic);
    zoomComic.setZoomState(zoomControl.getZoomState());
    zoomComic.setImage(BitmapFactory.decodeResource(getResources(), R.drawable.defaultlogo));
    zoomComic.setOnTouchListener(zoomListener);

    zoomControl.setAspectQuotient(zoomComic.getAspectQuotient());

    resetZoomState();

    // enter the new id
    imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); // Used to hide the soft keyboard

    Log.i(LOG_TAG, "beginning loading of first comic");
    int notificationComicNumber = getIntent().getIntExtra("comic", -1);
    Log.i(LOG_TAG, "comic number from intent: " + notificationComicNumber);
    if (notificationComicNumber == -1) {
        fetch = new MyFetcher(this, zoomComic, subtitle, controls, comicNumberEdtTxt, imm, zoomControl);
        fetch.execute(MyFetcher.LAST_DISPLAYED_COMIC);
    } else {
        fetch = new MyFetcher(this, zoomComic, subtitle, controls, comicNumberEdtTxt, imm, zoomControl);
        fetch.execute(notificationComicNumber);
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
    }
    Log.i(LOG_TAG, "ending loading of new comic");

    Log.i(LOG_TAG, "first run checks beginning");
    // Get SharedPreferences
    prefs = getSharedPreferences("prefs", Context.MODE_PRIVATE);

    // Check if this is the first run of the app for this version
    if (prefs.getBoolean("firstRun-" + MAJOR_VERSION_NUMBER, true)) {
        prefs.edit().putBoolean("firstRun-" + MAJOR_VERSION_NUMBER, false).commit();
        firstRunVersionDialog();
    }

    // Check if this is the first run of the app
    if (prefs.getBoolean("firstRun", true)) {
        prefs.edit().putBoolean("firstRun", false).commit();
        firstRunDialog();
    }
    Log.i(LOG_TAG, "First run checks done");

            // OnClickListener s for the buttons omitted to save space

EDIT 2: He estado cavando a través del código fuente de Android rastrear donde la excepción está viniendo. Esto es las líneas 456 y 457 de sendMessageAtTime en Handler:

msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);

Y esto es enqueueMessage de MessageQueue:

    final boolean enqueueMessage(Message msg, long when) {
        if (msg.when != 0) {
            throw new AndroidRuntimeException(msg
                    + " This message is already in use.");
        }
        if (msg.target == null && !mQuitAllowed) {
            throw new RuntimeException("Main thread not allowed to quit");
        }
        synchronized (this) {
            if (mQuiting) {
                RuntimeException e = new RuntimeException(
                    msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                return false;
            } else if (msg.target == null) {
                mQuiting = true;
            }

            msg.when = when;
            //Log.d("MessageQueue", "Enqueing: " + msg);
            Message p = mMessages;
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                this.notify();
            } else {
                Message prev = null;
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
                msg.next = prev.next;
                prev.next = msg;
                this.notify();
            }
        }
        return true;
    }

Estoy un poco confundido acerca de lo que es mQuiting, pero parece que la vez anterior enqueueMessage fue llamado msg.target era nula.

¿Fue útil?

Solución

Para generalizar la solución de Jonathan Perlow que el insecto se identificó específicamente, utilizo el siguiente en cualquier clase que utiliza AsyncTask. El medidor / controlador / post es cómo se puede ejecutar en cualquier lugar algo en el hilo de interfaz de usuario en una aplicación para Android, sin pasar por un mango para una actividad o de otro contexto. Añadir este bloque de inicialización estática dentro de la clase:

{ // https://stackoverflow.com/questions/4280330/onpostexecute-not-being-called-in-asynctask-handler-runtime-exception
    Looper looper = Looper.getMainLooper();
    Handler handler = new Handler(looper);
    handler.post(new Runnable() {
      public void run() {
        try {
          Class.forName("android.os.AsyncTask");
        } catch (ClassNotFoundException e) {
          e.printStackTrace();
        }
      }
    });
}

se había encontrado con el problema cuando se trata de obtener pruebas de unidad para correr. He encontrado una solución para eso, pero no había identificado específicamente el problema. Sólo sabíamos que tratar de utilizar AsyncTask <> en la prueba JUnit Android causó onPostExecute () no debe ser llamado. Ahora sabemos por qué.

Este post muestra cómo ejecutar código asíncrono multiproceso en una prueba JUnit Android:

Usando CountDownLatch en Android AsyncTask basados ??en pruebas JUnit

Para el uso con las pruebas de unidad de interfaz de usuario no, he creado una subclase sencilla de android.test.InstrumentationTestCase. Cuenta con una bandera de "ok" y un CountDownLatch. reset () o reinicio (recuento) crea un nuevo CountDownLatch ({1,} recuento). buena () establece ok = true, count-- y calls.countDown () en el pestillo. mala () establece ok = falso, y cuenta atrás hasta el final. waitForIt (segundos) espera a que el tiempo de espera o coundown pestillo a cero. A continuación, llama assertTrue (OK).

pruebas son, entonces, como:

someTest() {
  reset();
  asyncCall(args, new someListener() {
    public void success(args) { good(); }
    public void fail(args) { bad(); }
  });
  waitForIt();
}

Debido al fallo de inicialización AsyncTask estática, que tuvo que llevar a cabo nuestras pruebas reales dentro de un Ejecutable pasó a runTestOnUiThread (). Con la inicialización estática adecuado que el anterior, esto no debería ser necesario, a menos que la llamada está probando tiene que ejecutar en el subproceso de interfaz de usuario.

El otro idioma que ahora uso es para probar si el subproceso actual es el hilo de interfaz de usuario y luego ejecuta la acción solicitada en el hilo adecuado independientemente. A veces, tiene sentido para permitir que la persona que llama para solicitar sincronización vs asíncrono, anulando cuando sea necesario. Por ejemplo, las solicitudes de red siempre se deben ejecutar en un subproceso en segundo plano. En la mayoría de los casos, AsyncTask conjunto de hilos es perfecto para esto. Sólo se dan cuenta de que sólo un cierto número se ejecutará a la vez, el bloqueo de peticiones adicionales. Para probar si el hilo actual es el hilo de interfaz de usuario:

boolean onUiThread = Looper.getMainLooper().getThread() == Thread.currentThread();

A continuación, utilice una subclase sencilla (justo doInBackground () y onPostExecute () son necesarios) de AsyncTask <> para ejecutar en un subproceso no interfaz de usuario o handler.post () o postDelayed () para ejecutar en el subproceso de interfaz de usuario.

Dar a la persona que llama la opción de ejecutar sincronización o asincrónicos se parece a (obtener un valor onUiThread localmente válida no se muestra aquí, añadir booleanos locales como el anterior):

void method(final args, sync, listener, callbakOnUi) {
  Runnable run = new Runnable() { public void run() {
    // method's code... using args or class members.
    if (listener != null) listener(results);
    // Or, if the calling code expects listener to run on the UI thread:
    if (callbackOnUi && !onUiThread)
      handler.post(new Runnable() { public void run() {listener()}});
    else listener();
  };
  if (sync) run.run(); else new MyAsync().execute(run);
  // Or for networking code:
  if (sync && !onUiThread) run.run(); else new MyAsync().execute(run);
  // Or, for something that has to be run on the UI thread:
  if (sync && onUiThread) run.run() else handler.post(run);
}

Además, el uso AsyncTask puede hacerse muy simple y concisa. Utilice la definición de RunAsyncTask.java abajo, a continuación, escribir el código siguiente:

    RunAsyncTask rat = new RunAsyncTask("");
    rat.execute(new Runnable() { public void run() {
        doSomethingInBackground();
        post(new Runnable() { public void run() { somethingOnUIThread(); }});
        postDelayed(new Runnable() { public void run() { somethingOnUIThreadInABit(); }}, 100);
    }});
.

O simplemente: nueva RunAsyncTask ( "") ejecutar (nueva Ejecutable () {public void run () {doSomethingInBackground ();}});

RunAsyncTask.java:

package st.sdw;
import android.os.AsyncTask;
import android.util.Log;
import android.os.Debug;

public class RunAsyncTask extends AsyncTask<Runnable, String, Long> {
    String TAG = "RunAsyncTask";
    Object context = null;
    boolean isDebug = false;
    public RunAsyncTask(Object context, String tag, boolean debug) {
      this.context = context;
      TAG = tag;
      isDebug = debug;
    }
    protected Long doInBackground(Runnable... runs) {
      Long result = 0L;
      long start = System.currentTimeMillis();
      for (Runnable run : runs) {
        run.run();
      }
      return System.currentTimeMillis() - start;
    }
    protected void onProgressUpdate(String... values) {        }
    protected void onPostExecute(Long time) {
      if (isDebug && time > 1) Log.d(TAG, "RunAsyncTask ran in:" + time + " ms");
      v = null;
    }
    protected void onPreExecute() {        }
    /** Walk heap, reliably triggering crash on native heap corruption.  Call as needed. */  
    public static void memoryProbe() {
      System.gc();
      Runtime runtime = Runtime.getRuntime();
      Double allocated = new Double(Debug.getNativeHeapAllocatedSize()) / 1048576.0;
      Double available = new Double(Debug.getNativeHeapSize()) / 1048576.0;
      Double free = new Double(Debug.getNativeHeapFreeSize()) / 1048576.0;
      long maxMemory = runtime.maxMemory();
      long totalMemory = runtime.totalMemory();
      long freeMemory = runtime.freeMemory();
     }
 }

Otros consejos

Esto se debe a un error en AsyncTask en el marco de Android. AsyncTask.java tiene el siguiente código:

private static final InternalHandler sHandler = new InternalHandler();

Se espera que esto se puede inicializar en el hilo principal, pero que no está garantizada ya que se iniciará en el que sea el hilo que pasa a causar la clase para ejecutar sus inicializadores estáticos. I reproducido este problema por el que el controlador hace referencia a un subproceso de trabajo.

Un modelo común que causa que esto suceda es utilizar el IntentService clase. El código de ejemplo C2DM hace esto.

Una solución sencilla consiste en añadir el siguiente código al método onCreate de la aplicación:

Class.forName("android.os.AsyncTask");

Esto obligará AsyncTask a ser inicializado en el hilo principal. Yo presenté un error en esto en la base de datos de errores androide. Ver http://code.google.com/p/android/issues/ detalle? id = 20915 .

Yo tenía el mismo problema en un dispositivo con Android 4.0.4 con la IntentService y solucioné como dijo sdw con el Class.forName ( "android.os.AsyncTask"). Lo mismo no ocurrió en Android 4.1.2, 4.4.4 o 5.0. Me pregunto si esto resuelve Google Martin West tema a partir de 2011.

He añadido este código en mi aplicación onCreate y funcionó:

    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
        try {
            Class.forName("android.os.AsyncTask");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

Sería bueno saber si la versión de Android necesidad de cambiarse a otra cosa.

AsyncTask.execute() debe haber ejecutado el hilo de interfaz de usuario, es decir, dentro de la actividad.

Tengo el mismo problema, que parece ocurrir cuando el AsyncTask se está ejecutando durante una hoja de vida de suspensión /.

EDIT: Sí, no pensé que tenía pero me utilizado este http://developer.android .com / guía / Apéndice / Preguntas / commontasks.html # roscado Siempre a iniciar el AsyncTask en el hilo de interfaz de usuario y el problema se ha ido. El problema apareció después de añadir la función de la concesión de licencias, siggghhhhh

Gracias

A pesar de que esto no responde directamente a la pregunta de la OP, creo que va a ser útil para las personas que buscan la solución del mismo problema cuando se ejecutan las pruebas.

La respuesta de

En general, Peter Knego lo resume bien.

Mi problema fue específicamente con el funcionamiento de una prueba en una clase fuera de una actividad que hizo uso de AsyncTask de Android para una llamada a la API. La clase trabaja en la aplicación, ya que es utilizado por una actividad, pero quería realizar una prueba de hacer una llamada a la API actual de la prueba.

Mientras que de Jonathan Perlow respuesta trabajó, no me gustaba la introducción de cambios en mi aplicación debe únicamente a una prueba.

Por lo tanto, se puede utilizar en el caso de una prueba de runTestOnUiThread (@UiThreadTest no se puede utilizar, ya que no puede esperar a que un resultado en una prueba que utiliza ese anotación).

public void testAPICall() throws Throwable {
    this.runTestOnUiThread(new Runnable() {
        public void run() {
            underTest.thisMethodWillMakeUseOfAnAsyncTaskSomehow();
        }           
    }); 

    // Wait for result here *
    // Asserts here
}

A veces, sin embargo, especialmente en las pruebas funcionales, la respuesta de Jonathan Perlow parece ser el único que funciona.


Echa un vistazo aquí para ver cómo hacer una pausa en una prueba a la espera de un resultado.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top