2010-11-25 6 views
16

Ich habe eine AsyncTask, die einige Daten abruft und dann die Benutzeroberfläche mit diesen neuen Daten aktualisiert. Es funktioniert seit Monaten einwandfrei, aber ich habe kürzlich eine Funktion hinzugefügt, die eine Benachrichtigung anzeigt, wenn neue Daten vorhanden sind. Jetzt, wenn meine App durch die Benachrichtigung gestartet wird, bekomme ich manchmal diese Ausnahme und onPostExecute wird nicht aufgerufen.onPostExecute nicht in AsyncTask (Handler-Laufzeitausnahme)

Dies ist, was passiert, wenn die App gestartet wird:

1) die Benutzeroberfläche erweitern und den Alarm (durch AlarmManager), die für neue Daten und stellen Sie den Alarm überprüft Abbrechen Ansichten

2) finden. (Wenn der Benutzer den Alarm deaktiviert, wird er abgebrochen, bevor er das nächste Mal neu gestartet wird.)

3) Starten Sie die AsyncTask. Wenn die App von der Benachrichtigung gestartet wurde, geben Sie ein wenig Daten ein und brechen Sie dann die Benachrichtigung ab.

Ich stecke auf, was könnte diese Ausnahme verursachen. Es scheint, dass die Ausnahme von der AsyncTask Code ist, also bin ich mir nicht sicher, wie ich es beheben kann.

Danke! Hier

ist die Ausnahme:

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: Hier ist meine onCreate Methode in meiner Haupttätigkeit (die man durch die Benachrichtigung geöffnet). Es gibt einige onClickListeners, die ich weggelassen habe, um Platz zu sparen. Ich denke nicht, dass sie irgendeinen Effekt haben sollten, da die Knöpfe, an denen sie befestigt sind, nicht gedrückt werden.

@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 

BEARBEITEN 2: Ich habe durch Android-Quellcode gegraben, wo die Ausnahme herkommt. Dies ist Linien 456 und 457 von sendMessageAtTime in Handler:

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

Und das ist enqueueMessage von 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; 
    } 

ich ein wenig verwirrt darüber bin, was mQuiting ist, aber es sieht aus wie die vorherige Zeit enqueueMessage hieß msg.target war Null.

+0

Der Thread, der Ihre AsyncTask erstellt hat, muss beendet worden sein. Wie erstellst du deine AsyncTask? –

+0

Ich habe meine Frage mit dem Code aktualisiert. Ich kann nicht sehen, wie die Aktivität beendet wurde, wenn ich den Ladebildschirm noch sehen kann. – Computerish

+0

Wir müssen mehr Code sehen. – Falmarri

Antwort

17

Um Jonathan Perlows Lösung für den von ihm identifizierten Fehler zu verallgemeinern, verwende ich Folgendes in jeder Klasse, die AsyncTask verwendet. Mit dem Looper/Handler/Post können Sie auf dem UI-Thread irgendwo in einer Android-App etwas ausführen, ohne ein Handle an eine Aktivität oder einen anderen Kontext übergeben zu müssen. Fügen Sie diese statische Initialisierung Block innerhalb der Klasse:

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

Wir in das Problem laufen hatte bei dem Versuch, Unit-Tests zu erhalten auszuführen. Ich habe dafür einen Workaround gefunden, aber das Problem nicht speziell identifiziert. Wir wussten nur, dass die Verwendung von AsyncTask <> im Android-JUnit-Test dazu führte, dass onPostExecute() nicht aufgerufen wurde. Jetzt wissen wir warum.

Dieser Beitrag zeigt, wie Multithreading-Asynchron-Code in einem Android-JUnit-Test auszuführen:

Using CountDownLatch in Android AsyncTask-based JUnit tests

Für den Einsatz mit nicht-UI-Unit-Tests, habe ich eine einfache Unterklasse von android.test.InstrumentationTestCase erstellt. Es hat ein "OK" -Flag und ein CountDownLatch.reset() oder reset (count) erstellt einen neuen CountDownLatch ({1, count}). good() setzt ok = true, count--, und calls.countDown() auf dem Latch. bad() setzt ok = false und zählt den ganzen Weg herunter. waitForIt (Sekunden) wartet auf Timeout oder den Coun- down-Latch auf Null. Dann ruft er assertTrue (ok) auf.

Dann Tests sind wie:

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

Wegen der AsyncTask statische Initialisierung Fehler mussten wir unsere eigentlichen Tests innerhalb eines Runnable zu runTestOnUiThread() übergeben laufen. Bei ordnungsgemäßer statischer Initialisierung wie oben sollte dies nicht erforderlich sein, es sei denn, der zu testende Aufruf muss auf dem UI-Thread ausgeführt werden.

Das andere Idiom, das ich jetzt verwende, besteht darin, zu testen, ob der aktuelle Thread der UI-Thread ist, und dann die angeforderte Aktion für den richtigen Thread auszuführen. Manchmal ist es sinnvoll, dem Anrufer zu erlauben, eine Synchronisierung im Gegensatz zu einer asynchronen Synchronisierung anzufordern. Zum Beispiel sollten Netzwerkanforderungen immer in einem Hintergrundthread ausgeführt werden. In den meisten Fällen ist das AsyncTask-Thread-Pooling hierfür ideal. Beachten Sie, dass nur eine bestimmte Anzahl gleichzeitig ausgeführt wird und zusätzliche Anfragen blockiert werden. Um zu testen, ob der aktuelle Thread ist der UI-Thread:

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

Dann eine einfache Unterklasse verwenden (nur doInBackground() und OnPostExecute() sind erforderlich) von AsyncTask <> laufen auf einem Nicht-UI-Thread oder Handler. post() oder postDelayed(), um auf dem UI-Thread ausgeführt zu werden.

dem Anrufer die Option geben, sync oder async laufen sieht aus wie (ein lokal gültigen onUiThread Wert hier nicht gezeigt bekommen, fügen Sie lokale booleans wie oben):

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

Auch die Verwendung von AsyncTask kann sehr einfach gemacht werden und prägnant. Verwenden Sie die Definition von RunAsyncTask.java unten, dann schreiben Sie Code wie folgt:

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

Oder einfach:. Neue RunAsyncTask ("") ausführen (neu 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(); 
    } 
} 
+0

Ausgezeichnet. Ich habe dieses Problem bei einem Android JUnit Test, aber nur auf 2.3.4. Kannst du mir sagen, ob es nur passiert, wenn es im JUnit-Test läuft, oder auch in der App passiert, wenn es unter 2.3.4 ist. In meinem Fall wird die AsyncTask erstellt und in der onReceive() - Methode eines Receivers ausgeführt - was normal funktioniert. –

+0

Der Grund, warum dieser Fehler eine Weile nicht bemerkt wurde, ist, dass in einer normalen Anwendung die AsyncTask vor allem anderen aus dem UInthread verwendet wird. In diesem Fall wird dem statischen Wert ein korrekter Wert zugewiesen. Wenn Sie nicht den statischen Initialisierer vor dem normalen UI-Stack auslösen, und Sie dies in einem anderen Thread tun, sollte es Ihnen gut gehen. – sdw

+0

Beste Antwort :) immer –

1

AsyncTask.execute() muss auf UI-Thread ausgeführt werden, d. H. In Aktivität.

+0

Es wird von innerhalb meiner Aktivität aufgerufen. – Computerish

+0

"Mit Aktivität" und "auf UI-Thread" sind verschiedene Dinge. AsyncTask verwendet Handler, um Ergebnisse Ihres doInBackgrounds an den UI-Thread zu senden. Wenn Sie Ihre asynchrone Task in einem anderen Thread als der UI gestartet haben, wird das Ergebnis in die Warteschlange des Handlers eingereiht, jedoch niemals abgerufen und ausgeführt. – JBM

0

Ich habe das gleiche Problem, es scheint zu passieren, wenn die AsyncTask während einer Aussetzung/Wiederaufnahme ausgeführt wird.

EDIT: Ja, dachte nicht, daß ich hatte, aber ich verwendet, um dieses http://developer.android.com/guide/appendix/faq/commontasks.html#threading immer die AsyncTask auf dem UI-Thread zu starten und das Problem ist weg. Das Problem erschien, nachdem ich die Lizenzierungsfunktion hinzugefügt, siggghhhhh

Dank

+0

Das ist interessant, ich hatte eine App, die wochenlang fehlerfrei lief, und sobald ich meinen Aufruf für die Lizenzierungsbibliotheksfunktionen aktiviert hatte, fing es an, diesen Fehler auszulösen. Natürlich war es die Lizensierungsbibliothek, die ich kurz vor meinem endgültigen Release aktiviert habe, sodass die Release-Woche zu einer kompletten Panik wurde. –

40

Dies ist aufgrund eines Fehlers in AsyncTask im Android-Framework. AsyncTask.Java hat den folgenden Code:

private static final InternalHandler sHandler = new InternalHandler(); 

Es erwartet, dass diese auf dem Hauptthread initialisiert werden, aber das ist nicht garantiert, da es auf initialisiert wird je nachdem, welcher Thread ist nun mal die Klasse laufen seine statische Initialisierer zu verursachen. Ich reproduziert dieses Problem, bei dem der Handler auf einen Worker-Thread verweist.

Ein häufiges Muster, das dies verursacht, ist die Verwendung der Klasse IntentService. Der C2DM-Beispielcode tut dies.

Eine einfache Abhilfe ist, den folgenden Code an die Anwendung des Verfahrens onCreate hinzuzufügen:

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

Diese AsyncTask zwingen wird in dem Haupt-Thread initialisiert werden. Ich habe einen Fehler in der Android-Bug-Datenbank gemeldet. Siehe http://code.google.com/p/android/issues/detail?id=20915.

+2

Vielen Dank! Ich schlug meinen Kopf einige Male gegen die Wand und versuchte herauszufinden, warum "onPostExecute()" nicht aufgerufen wurde, bevor ich das las. –

+0

Dies half mir auch beim Versuch, eine App auf Google TV zu portieren. Es funktionierte auf anderen Plattformen, und das nicht aufgerufene 'onPostExecute' machte mich verrückt. – cwc

+0

Super! . Ich akzeptiere dies als Antwort. :) –

0

Auch wenn dies die Frage des OP nicht direkt beantwortet, denke ich, dass es für Leute nützlich sein wird, die beim Ausführen von Tests nach der Lösung des gleichen Problems suchen.

Insgesamt fasst Peter Knego's answer es gut zusammen.

Mein Problem war speziell mit dem Ausführen eines Tests für eine Klasse außerhalb einer Aktivität, die Android AsyncTask für einen API-Aufruf verwendet. Die Klasse funktioniert in der Anwendung, da sie von einer Aktivität verwendet wird, aber ich wollte einen Test ausführen, der einen tatsächlichen API-Aufruf aus dem Test erstellt.

Während Jonathan Perlow's answer funktionierte, habe ich nicht gerne Änderungen an meiner Anwendung nur aufgrund eines Tests eingeführt.

So kann im Fall eines Tests runTestOnUiThread verwendet werden (@UiThreadTest kann nicht verwendet werden, da Sie nicht auf ein Ergebnis in einem Test warten können, der diese Annotation verwendet).

Manchmal, vor allem in Funktionstests, scheint die Antwort von Jonathan Perlow die einzige zu sein, die funktioniert.


* Take a look here zu sehen, wie ein Test für ein Ergebnis warten, um zu pausieren.

2

hatte ich das gleiche Problem auf einem Gerät mit Android 4.0.4 mit dem IntentService und löste es als SDW sagte mit dem Class.forName ("android.os.AsyncTask"). Das Gleiche ist unter Android 4.1.2, 4.4.4 oder 5.0 nicht geschehen. Ich frage mich, ob das Google Martin West-Problem behoben ab 2011

ich diesen Code auf meiner Anwendung onCreate hinzugefügt und es funktionierte:

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

Es wäre schön, zu wissen, ob die Version von Android geändert werden müssen zu etwas anderem.

Verwandte Themen