onPostExecute non viene chiamato in AsyncTask (exception runtime Handler)

Ho AsyncTask che recupera alcuni dati e poi aggiorna l'UI con questi nuovi dati. Ha funzionato bene per mesi, ma ho recentemente aggiunto una funzionalità che visualizza una notifica quando ci sono nuovi dati. Ora quando la mia applicazione viene lanciata tramite la notifica, a volte ottengo questa exception e non viene chiamato onPostExecute .

Questo è ciò che accade quando viene avviata l'applicazione:

  • getExtra da Intent lanciato da un pendingIntent
  • Come svegliare Android Wear quando è in modalità sleep?
  • Libreria OkHttp - NetworkOnMainThreadException in un post semplice
  • Come avviare un servizio (che mostra dati leggeri) su un porttile da un dispositivo palmare?
  • Come può ripetere AlarmManager avviare AsyncTask?
  • Android: Miglior modo di gestire la tiratura continua dal server?
  • 1) Espandere l'interface utente e trovare le viste

    2) Annullare l'allarme (tramite AlarmManager ) che controlla i nuovi dati e ripristina l'allarme. (In questo modo, se l'utente distriggers l'allarme, viene annullata prima della successiva riavvio).

    3) Avviare l' AsyncTask . Se l'applicazione è stata lanciata dalla notifica, passare in un po 'di dati e quindi annullare la notifica.

    Sono bloccato su ciò che potrebbe causare questa exception. Sembra che l'exception sia dal codice AsyncTask , quindi non so se posso risolvere il problema.

    Grazie!

    Ecco l'exception:

     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: Ecco il mio metodo onCreate nella mia attività principale (quella aperta dalla notifica). Ci sono alcuni onClickListeners che ho omesso per risparmiare spazio. Non credo che dovrebbero avere alcun effetto, poiché i pulsanti a cui sono collegati non vengono pressati.

     @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: Ho scavato il codice sorgente di Android che segue where viene generata l'exception. 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 di ciò che mQuiting è, ma sembra che l' enqueueMessage fosse chiamato msg.target null.

  • Come può ripetere AlarmManager avviare AsyncTask?
  • Alarm Manager non triggers il ricevitore broadcast?
  • Funzionalità di promemory
  • Notifiche quotidiane a determinati tempi
  • Come utilizzare Android AlarmManager con piccoli intervalli come 1 minuto?
  • Il button "cancel" dell'allarme non funziona correttamente
  • 6 Solutions collect form web for “onPostExecute non viene chiamato in AsyncTask (exception runtime Handler)”

    Per generalizzare la soluzione di Jonathan Perlow al bug che ha identificato in modo specifico, utilizzo quanto segue in qualsiasi class che utilizza AsyncTask. Il gestore / gestore / post è il modo in cui è ansible eseguire qualcosa nel thread dell'interface utente ovunque in un'applicazione Android senza passare un handle a un'attività o ad un altro context. Aggiungere questo block di initialization statica all'interno della class:

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

    Avevamo corso nel problema quando cercavamo di eseguire test di unità. Ho trovato una soluzione per questo, ma non ho specificamente identificato il problema. Sapevamo solo che tentare di utilizzare AsyncTask <> nel test di JUnit di Android causato a PostExecute () non essere chiamato. Ora sappiamo perché.

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

    Utilizzo di CountDownLatch in test di JUnit basati su AsyncTask di Android

    Per l'utilizzo con test su unità non UI, ho creato una semplice sottoclass di android.test.InstrumentationTestCase. Ha una bandiera "ok" e una "CountDownLatch". reset () o reset (count) crea un nuovo CountDownLatch ({1, count}). good () imposta ok = true, count–, e calls.countDown () sul latch. bad () imposta ok = falso, e conta giù per tutto il tempo. waitForIt (secondi) attende il timeout o il block di coundown 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 initialization statica di AsyncTask, abbiamo dovuto eseguire i nostri test effettivi all'interno di un Runnable passato per eseguireTestOnUiThread (). Con una corretta initialization statica come sopra, questo non dovrebbe essere necessario, a less che la chiamata in esame non sia necessaria per eseguire il thread UI.

    L'altro idioma che ora uso è quello di verificare se il thread corrente è il thread UI e quindi eseguire l'azione richiesta sul thread corretto indipendentemente. A volte, è consigliabile consentire al chiamante di richiedere la sincronizzazione rispetto all'async, sovrascrivendo quando necessario. Ad esempio, le richieste di networking devono essere sempre eseguite su un thread di background. Nella maggior parte dei casi, il pool di thread AsyncTask è perfetto per questo. Basta rendersi conto che solo un certo numero verrà eseguito contemporaneamente, bloccando ulteriori richieste. Per verificare se il thread corrente è il thread UI:

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

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

    Dare al chiamante l'opzione di eseguire la sincronizzazione o l'asincrono (come get un valore onUiThread valido localmente non mostrato qui; aggiungere 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 qui di seguito, quindi scrivi codice come questo:

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

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

     private static final InternalHandler sHandler = new InternalHandler(); 

    Si aspetta che questo sia inizializzato sul thread principale, ma ciò non è garantito in quanto sarà inizializzato su qualunque thread accada per causare la class a eseguire i suoi inizializzatori statici. Ho riprodotto questo problema in cui il gestore fa riferimento a un thread di lavoratore.

    Un model comune che provoca questo accadere è l'utilizzo della class IntentService. Il codice di esempio C2DM lo fa.

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

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

    Questo obbligherà l'initialization di AsyncTask nel thread principale. Ho presentato un bug su questo nel database degli errori android. Vedi http://code.google.com/p/android/issues/detail?id=20915 .

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

    Ho aggiunto questo codice nella 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 la versione di Android deve essere cambiata in qualcos'altro.

    AsyncTask.execute() deve essere eseguito sul thread UI, cioè all'interno di Attività.

    Ho lo stesso problema, sembra accadere quando l'AsyncTask è in esecuzione durante una sospensione / ripresa.

    EDIT: Sì, non pensavo che avessi ma ho usato questo http://developer.android.com/guide/appendix/faq/commontasks.html#threading per iniziare sempre l'AsyncTask sul thread UI e il problema è andato. Il problema è apparso dopo aver aggiunto la function di licenza, siggghhhhh

    Grazie

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

    Nel complesso, la risposta di Peter Knego lo riassume bene.

    Il mio problema era specificamente quello di eseguire un test su una class al di fuori di un'attività che utilizzava l'AsyncTask di Android per una chiamata API. La class funziona nell'applicazione, in quanto viene utilizzata da un'attività, ma ho voluto eseguire un test che effettua una chiamata API effettiva dal test.

    Mentre la risposta di Jonathan Perlow ha funzionato, non mi piaceva introdurre modifiche alla mia domanda a causa solo di un test.

    Quindi, nel caso di un test runTestOnUiThread può essere utilizzato ( @UiThreadTest non può essere utilizzato, poiché non puoi aspettare un risultato in un test che utilizza quella annotazione).

     public void testAPICall() throws Throwable { this.runTestOnUiThread(new Runnable() { public void run() { underTest.thisMethodWillMakeUseOfAnAsyncTaskSomehow(); } }); // Wait for result here * // Asserts here } 

    A volte tuttavia, specialmente nei test funzionali, la risposta di Jonathan Perlow sembra essere l'unico che funziona.


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

    L'Android è un fan Android di Google, tutto su telefoni Android, Android Wear, Android Dev e applicazioni Android Games e così via.