Question

J'ai un AsyncTask qui récupère des données et met à jour l'interface utilisateur avec ces nouvelles données. Il travaille bien depuis des mois, mais je a récemment ajouté une fonctionnalité qui affiche une notification lorsque de nouvelles données. Maintenant, quand mon application est lancée par la notification, parfois je reçois cette exception et onPostExecute n'est pas appelé.

est ce qui se passe lorsque l'application est lancée:

1) Développer l'interface utilisateur et trouver des vues

2) annuler l'alarme (par AlarmManager) qui vérifie la présence de nouvelles données et réinitialiser l'alarme. (Il en est ainsi que si l'utilisateur désactive l'alarme, il est annulé avant la prochaine fois qu'il / elle redémarre.)

3) Lancer la AsyncTask. Si l'application a été lancée à partir de la notification, passer un peu de données, puis annuler la notification.

Je suis bloqué sur ce qui pourrait être à l'origine de cette exception. Il semble que l'exception est du code AsyncTask, donc je ne sais pas comment je peux le réparer.

Merci!

Voici 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: Voici ma méthode de onCreate dans mon activité principale (celle ouverte par la notification). Il y a quelques onClickListeners que j'omis pour économiser l'espace. Je ne pense pas qu'ils devraient avoir aucun effet, puisque les boutons ils sont attachés ne sont pas pressés.

@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: J'ai été en train de creuser dans le code source Android traquer où l'exception vient. C'est des lignes 456 et 457 de sendMessageAtTime en Handler:

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

Et voici 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;
    }

Je suis un peu confus au sujet de ce mQuiting est, mais il semble que le temps enqueueMessage précédent a été appelé msg.target était nulle.

Était-ce utile?

La solution

Pour généraliser la solution de Jonathan Perlow au bug il a identifié spécifiquement, j'utilise ce qui suit dans une classe qui utilise AsyncTask. Le looper / gestionnaire / post est comment vous pouvez exécuter quelque chose sur le thread d'interface utilisateur ne importe où dans une application Android sans passer vers le bas d'une poignée à une activité ou un autre contexte. Ajouter ce bloc d'initialisation statique à l'intérieur de la 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();
        }
      }
    });
}

Nous avait couru le problème en essayant d'obtenir des tests unitaires pour exécuter. J'ai trouvé une solution pour cela, mais n'a pas identifié spécifiquement le problème. Nous ne savions que d'essayer d'utiliser AsyncTask <> dans le test Android JUnit causé onPostExecute () ne doit pas être appelé. Maintenant, nous savons pourquoi.

Cela montre article comment exécuter du code multithread async dans un test Android JUnit:

En utilisant CountDownLatch dans les tests JUnit basés-AsyncTask Android

Pour une utilisation avec les tests unitaires non-UI, je créé une sous-classe simple android.test.InstrumentationTestCase. Il a un drapeau « ok » et un CountDownLatch. reset () ou remise à zéro (nombre) crée une nouvelle CountDownLatch ({1, count}). bon () définit ok = true, count-- et calls.countDown () sur le loquet. mauvais () définit ok = false, et compte tout le chemin. attend waitForIt (secondes) pour temporisation ou le verrou coundown à zéro. Ensuite, il appelle assertTrue (ok).

tests, sont comme:

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

En raison du bug d'initialisation statique AsyncTask, nous avons dû exécuter nos tests réels dans un Runnable passé à runTestOnUiThread (). Avec l'initialisation de la statique appropriée comme ci-dessus, cela ne devrait pas être nécessaire, à moins que les besoins en appel mis à l'essai à exécuter sur le thread d'interface utilisateur.

L'autre idiome que je maintenant utiliser est de tester si le thread courant est le thread d'interface utilisateur et puis exécutez l'action demandée sur le fil approprié quel que soit. Parfois, il est logique de permettre à l'appelant de demander la synchronisation par rapport à async, remplaçant si nécessaire. Par exemple, les demandes de réseau doivent toujours être exécutés sur un thread d'arrière-plan. Dans la plupart des cas, la mise en commun de fil AsyncTask est parfait pour cela. Il suffit de se rendre compte que seul un certain nombre se déroulera à la fois, le blocage des demandes supplémentaires. Pour tester si le thread courant est le fil conducteur de l'interface utilisateur:

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

Ensuite, utilisez un simple sous-classe (juste doInBackground () et onPostExecute () sont nécessaires) de AsyncTask <> pour exécuter sur un thread non interface utilisateur ou handler.post () ou postDelayed () pour exécuter sur le thread d'interface utilisateur.

Donner à l'appelant la possibilité d'exécuter des regards de synchronisation ou async comme (obtenir une valeur onUiThread valide localement non représentée ici, ajouter des booléens locales comme ci-dessus):

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

En outre, en utilisant AsyncTask peut être très simple et concis. Utilisez la définition de RunAsyncTask.java ci-dessous, puis écrire du code comme ceci:

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

Ou simplement: nouveau RunAsyncTask ( "") exécuter (nouveau 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();
     }
 }

Autres conseils

Ceci est dû à un bogue dans AsyncTask dans le cadre Android. AsyncTask.java a le code suivant:

private static final InternalHandler sHandler = new InternalHandler();

Il attend que cela soit initialisé sur le thread principal, mais ce n'est pas garanti car il sera initialisé sur fil quel que soit arrive à faire la classe à exécuter ses initialiseurs statiques. Je reproduis cette question où le gestionnaire fait référence à un thread de travail.

Un modèle commun qui provoque cela se produise est d'utiliser le IntentService de classe. L'exemple de code C2DM fait cela.

Une solution simple est d'ajouter le code suivant à la méthode onCreate de l'application:

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

Cela forcera AsyncTask à initialiser dans le thread principal. J'ai déposé un bug à ce sujet dans la base de données android bug. Voir http://code.google.com/p/android/issues/ détail? id = 20915 .

I eu le même problème sur un dispositif avec les applications 4.0.4 avec le IntentService et résolu comme sdw dit avec le Class.forName ( « android.os.AsyncTask »). La même chose ne se produit pas sur Android 4.1.2, 4.4.4 ou 5.0. Je me demande si Google résolu question Martin Ouest de 2011.

J'ajouté ce code sur ma demande onCreate et cela a fonctionné:

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

Il serait bon de savoir si la version de Android besoin d'être changé à autre chose.

AsyncTask.execute() doit être exécuté sur un fil interface utilisateur, à savoir l'activité de l'intérieur.

J'ai le même problème, il semble se produire lorsque le AsyncTask est en cours d'exécution au cours d'une suspension / reprise.

EDIT: Oui, ne pensais pas que j'avais mais j'utilisé cette http://developer.android .com / Guide / annexe / faq / commontasks.html # filetage à toujours commencer la AsyncTask sur le thread d'interface utilisateur et le problème a disparu. Le problème est apparu après avoir ajouté la fonction de licence, siggghhhhh

Merci

Même si cela ne répond pas, je pense directement à la question de l'OP, il sera utile pour les personnes à la recherche de la solution du même problème lors de l'exécution des tests.

Dans l'ensemble, Peter Knego les sommes de la réponse de il bien résisté.

Mon problème était spécifique à la gestion d'un test sur une classe en dehors d'une activité qui a fait usage de AsyncTask Android pour un appel API. La classe travaille dans l'application, car il est utilisé par une activité, mais je voulais exécuter un test émettez un appel d'API réelle du test.

Alors que réponse de Jonathan Perlow a travaillé, ne me plaisait pas introduire des changements dans ma demande uniquement due à un test.

Ainsi, dans le cas d'un runTestOnUiThread de test peut être utilisé (@UiThreadTest ne peut pas être utilisé, puisque vous ne pouvez pas attendre un résultat dans un test qui utilise cette annotation).

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

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

Parfois, cependant, en particulier dans les tests fonctionnels, la réponse de Jonathan Perlow semble être la seule qui fonctionne.


* Jetez un coup d'oeil ici pour voir comment mettre en pause un test en attente d'un résultat.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top