Domanda

ho un AsyncTask che recupera alcuni dati e quindi aggiorna l'interfaccia utente con questi nuovi dati.Funziona bene da mesi, ma recentemente ho aggiunto una funzionalità che visualizza una notifica quando sono presenti nuovi dati.Ora, quando la mia app viene avviata tramite la notifica, a volte ricevo questa eccezione e onPostExecute non viene chiamato.

Questo è ciò che accade all'avvio dell'app:

1) Espandi l'interfaccia utente e trova le visualizzazioni

2) Annullare l'allarme (tramite AlarmManager) che verifica la presenza di nuovi dati e ripristina l'allarme.(In questo modo, se l'utente disabilita l'allarme, questo verrà annullato prima del successivo riavvio.)

3) Avviare il AsyncTask.Se l'app è stata avviata dalla notifica, trasmetti un po' di dati e poi cancella la notifica.

Sono bloccato su cosa potrebbe causare questa eccezione.Sembra che l'eccezione provenga da AsyncTask codice, quindi non sono sicuro di come posso risolverlo.

Grazie!

Ecco l'eccezione:

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)

MODIFICARE:Ecco il mio onCreate nella mia attività principale (quella aperta dalla notifica).Ci sono alcuni onClickListeners che ho omesso per risparmiare spazio.Non penso che dovrebbero avere alcun effetto, dato che i pulsanti a cui sono attaccati non vengono premuti.

@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

MODIFICA 2:Ho analizzato il codice sorgente di Android per individuare la provenienza dell'eccezione.Queste sono le righe 456 e 457 di sendMessageAtTime In Handler:

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

E questo è enqueueMessage da 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;
    }

Sono un po' confuso su cosa mQuiting lo è, ma sembra come la volta precedente enqueueMessage è stato chiamato msg.target era nullo.

È stato utile?

Soluzione

Per generalizzare la soluzione di Jonathan Perlow al bug identificato specificamente, utilizzo quanto segue in qualsiasi classe che utilizza AsyncTask.Il looper/gestore/post è il modo in cui puoi eseguire qualcosa sul thread dell'interfaccia utente ovunque in un'app Android senza passare un handle a un'attività o ad un altro contesto.Aggiungi questo blocco di inizializzazione statica all'interno della classe:

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

Ci siamo imbattuti nel problema durante il tentativo di eseguire i test unitari.Ho trovato una soluzione alternativa a questo, ma non avevo identificato specificamente il problema.Sapevamo solo che il tentativo di utilizzare AsyncTask<> nel test JUnit di Android causava la mancata chiamata di onPostExecute().Ora sappiamo perché.

Questo post mostra come eseguire il codice asincrono multithread in un test JUnit Android:

Utilizzo di CountDownLatch nei test JUnit basati su Android AsyncTask

Per l'utilizzo con unit test non UI, ho creato una semplice sottoclasse di android.test.InstrumentationTestCase.Ha un flag "ok" e un CountDownLatch.reset() o reset(count) crea un nuovo CountDownLatch({1,count}).good() imposta ok=true, count-- ecalls.countDown() sul latch.bad() imposta ok=false e conta alla rovescia fino in fondo.waitForIt(secondi) attende il timeout o il conteggio del conto alla rovescia fino a zero.Quindi chiama assertTrue(ok).

Quindi i test sono come:

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

A causa del bug di inizializzazione statica di AsyncTask, abbiamo dovuto eseguire i nostri test effettivi all'interno di un Runnable passato a runTestOnUiThread().Con una corretta inizializzazione statica come sopra, ciò non dovrebbe essere necessario, a meno che la chiamata da testare non debba essere eseguita sul thread dell'interfaccia utente.

L'altro modo di dire che ora utilizzo è verificare se il thread corrente è il thread dell'interfaccia utente e quindi eseguire l'azione richiesta sul thread corretto a prescindere.A volte ha senso consentire al chiamante di richiedere la sincronizzazione anziché la sincronizzazione.asincrono, sovrascrivendo quando necessario.Ad esempio, le richieste di rete dovrebbero essere sempre eseguite su un thread in background.Nella maggior parte dei casi, il pooling di thread AsyncTask è perfetto per questo.Tieni presente che solo un certo numero verrà eseguito contemporaneamente, bloccando ulteriori richieste.Per verificare se il thread corrente è il thread dell'interfaccia utente:

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

Quindi utilizzare una semplice sottoclasse (sono necessari solo doInBackground() e onPostExecute()) di AsyncTask<> per eseguire su un thread non UI o handler.post() o postDelayed() per eseguire sul thread UI.

Dare al chiamante la possibilità di eseguire la sincronizzazione o l'asincrono assomiglia a (ottenendo un valore onUiThread valido localmente non mostrato qui;aggiungi booleani locali come sopra):

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

Inoltre, l'utilizzo di AsyncTask può essere reso molto semplice e conciso.Utilizza la definizione di RunAsyncTask.java di seguito, quindi scrivi il codice in questo modo:

    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 semplicemente:new RunAsyncTask("").execute(new Runnable(){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();
     }
 }

Altri suggerimenti

Ciò è dovuto a un bug in AsyncTask nel framework Android.AsyncTask.java ha il seguente codice:

private static final InternalHandler sHandler = new InternalHandler();

Si aspetta che questo venga inizializzato sul thread principale, ma ciò non è garantito poiché verrà inizializzato su qualunque thread accada per far sì che la classe esegua i suoi inizializzatori statici.Ho riprodotto questo problema in cui il gestore fa riferimento a un thread di lavoro.

Un modello comune che fa sì che ciò accada è l'utilizzo della classe IntentService.Il codice di esempio C2DM esegue questa operazione.

Una soluzione semplice consiste nell'aggiungere il seguente codice al metodo onCreate dell'applicazione:

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

Ciò forzerà l'inizializzazione di AsyncTask nel thread principale.Ho segnalato un bug su questo nel database dei bug di Android.Vedere http://code.google.com/p/android/issues/detail?id=20915.

Ho avuto lo stesso problema su un dispositivo con Android 4.0.4 con IntentService e l'ho risolto come ha detto sdw con Class.forName("android.os.AsyncTask").Lo stesso non è successo su Android 4.1.2, 4.4.4 o 5.0.Mi chiedo se questo Google abbia risolto il problema di Martin West del 2011.

Ho aggiunto questo codice sulla mia applicazione onCreate e ha funzionato:

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

Sarebbe bello sapere se è necessario modificare la versione di Android con qualcos'altro.

AsyncTask.execute() deve essere eseguito sul thread dell'interfaccia utente, ad es.all'interno dell'attività.

Ho lo stesso problema, sembra che accada quando AsyncTask è in esecuzione durante una sospensione/ripresa.

MODIFICARE:Sì, non pensavo di averlo fatto, ma ho usato questo http://developer.android.com/guide/appendix/faq/commontasks.html#threadingper avviare sempre AsyncTask sul thread dell'interfaccia utente e il problema è scomparso.Il problema si è verificato dopo aver aggiunto la funzione di licenza, siggghhhhh

Grazie

Anche se questo non risponde direttamente alla domanda dell'OP, penso che sarà utile per le persone che cercano la soluzione dello stesso problema durante l'esecuzione dei test.

Complessivamente, La risposta di Peter Knego lo riassume bene.

Il mio problema riguardava specificamente l'esecuzione di un test su una classe esterna a un'attività che utilizzava AsyncTask di Android per una chiamata API.La classe funziona nell'applicazione, poiché viene utilizzata da un'attività, ma volevo eseguire un test effettuando una vera e propria chiamata API dal test.

Mentre La risposta di Jonathan Perlow ha funzionato, non mi piaceva introdurre modifiche alla mia applicazione dovute esclusivamente ad un test.

Quindi, nel caso di un test runTestOnUiThread può essere utilizzata (@UiThreadTest non può essere utilizzato, poiché non è possibile attendere un risultato in un test che utilizza quell'annotazione).

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

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

A volte però, soprattutto nei test funzionali, la risposta di Jonathan Perlow sembra essere l'unica che funziona.


* Dai un'occhiata qui per vedere come mettere in pausa un test in attesa di un risultato.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top