onpostexecute asynctaskで呼び出されていません(ハンドラーランタイム例外)

StackOverflow https://stackoverflow.com/questions/4280330

質問

を持っています AsyncTask これにより、いくつかのデータが取得され、この新しいデータでUIが更新されます。それは何ヶ月も正常に動作してきましたが、最近、新しいデータがあるときに通知を表示する機能を追加しました。今、私のアプリが通知を通じて起動されたとき、時々私はこの例外を取得し、 onPostExecute 呼ばれていません。

これは、アプリが起動されたときに起こることです。

1)UIを展開し、ビューを見つけます

2)アラームをキャンセルします(スルー AlarmManager)新しいデータをチェックし、アラームをリセットします。 (これは、ユーザーがアラームを無効にした場合、次回再起動する前にキャンセルされるようにします。)

3)開始します AsyncTask. 。通知からアプリが起動された場合は、データを少し渡してから通知をキャンセルします。

私はこの例外を引き起こす可能性があるものに固執しています。例外はからであるようです AsyncTask コードなので、どのように修正できるかわかりません。

ありがとう!

これが例外です:

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)

編集:これが私です onCreate 私のメインアクティビティのメソッド(通知によって開かれたアクティビティ)。幾つかある onClickListeners スペースを節約するために省略したこと。それらが取り付けられているボタンが押されていないので、私は彼らが何の効果を持っているべきではないと思います。

@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

編集2:例外がどこから来たのかを追跡するAndroidソースコードを掘り下げています。これは456行と457行です sendMessageAtTimeHandler:

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

そしてこれは enqueueMessage から 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;
    }

私は何について少し混乱しています mQuiting そうですが、以前のように見えます enqueueMessage と呼ばれていました msg.target ヌルでした。

役に立ちましたか?

解決

Jonathan Perlowのソリューションを彼が具体的に特定したバグに対する解決策を一般化するために、Asynctaskを使用するクラスで以下を使用します。ルーパー/ハンドラー/投稿は、ハンドルをアクティビティやその他のコンテキストに渡すことなく、AndroidアプリのどこでもUIスレッドで何かを実行する方法です。クラス内にこの静的初期化ブロックを追加します。

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

ユニットテストを実行しようとしたときに問題に遭遇しました。そのための回避策を見つけましたが、問題を具体的に特定していませんでした。 Android Junitテストでasynctask <>を使用しようとすると、onpostexecute()が呼び出されないことを知っていました。今、私たちはその理由を知っています。

この投稿は、Android Junitテストでマルチスレッドの非同期コードを実行する方法を示しています。

Android AsynctaskベースのJunitテストでCountDownLatchを使用します

非UIユニットテストで使用するために、android.test.instrumentationTestcaseのシンプルなサブクラスを作成しました。 「OK」フラグとCountDownLatchがあります。 reset()またはreset(count)は、新しいcountdownlatch({1、count})を作成します。 good()set ok = true、count-、およびcalls.countdown()をラッチ上に設定します。 bad()はok = falseを設定し、ずっとカウントダウンします。 Waitforit(秒)タイムアウトまたはサウンドダウンラッチがゼロまで待機します。その後、Asserttrue(OK)を呼び出します。

その後、テストは次のようです。

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

Asynctaskの静的初期化バグのため、runtestonuithread()に渡された実行可能な内部で実際のテストを実行する必要がありました。上記の適切な静的初期化を使用すると、テストされている呼び出しがUIスレッドで実行する必要がない限り、これは必要ありません。

私が現在使用している他のイディオムは、現在のスレッドがUIスレッドであるかどうかをテストし、それに関係なく適切なスレッドで要求されたアクションを実行することです。時には、発信者が同期と非同期を要求し、必要に応じてオーバーライドできるようにすることは理にかなっています。たとえば、ネットワークリクエストは常にバックグラウンドスレッドで実行する必要があります。ほとんどの場合、Asynctaskスレッドプーリングはこれに最適です。特定の番号のみが一度に実行され、追加のリクエストをブロックすることを理解してください。現在のスレッドがUIスレッドであるかどうかをテストするには:

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

次に、asynctask <>の単純なサブクラス(doinbackground()とonpostexecute()が必要です)を使用して、UIスレッドで実行するために非uiスレッドまたはhandler.post()またはpostdelayed()を実行します。

発信者に同期または非同期を実行するオプションを与える(ここには表示されていないローカルで有効なonuithread値を取得します。上記のようにローカルブール人を追加):

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

また、Asynctaskの使用は非常にシンプルで簡潔にすることができます。以下のrunasynctask.javaの定義を使用して、次のようなコードを書きます。

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

または単純に: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();
     }
 }

他のヒント

これは、AndroidフレームワークのAsynctaskのバグによるものです。 asynctask.javaには次のコードがあります。

private static final InternalHandler sHandler = new InternalHandler();

これはメインスレッドで初期化されると予想されますが、クラスが静的イニシャル化器を実行する場合、どのスレッドで初期化されるため、それは保証されません。ハンドラーがワーカースレッドを参照するこの問題を再現しました。

これを引き起こす一般的なパターンは、クラスインテンツサービスを使用することです。 C2DMサンプルコードはこれを行います。

簡単な回避策は、次のコードをアプリケーションのOnCreateメソッドに追加することです。

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

これにより、Asynctaskはメインスレッドで初期化されます。 Androidバグデータベースにこれに関するバグを提出しました。見る http://code.google.com/p/android/issues/detail?id=20915.

Android 4.0.4を搭載したデバイスで同じ問題を抱えており、SDWがClass.forname( "android.os.asynctask")で言ったように解決しました。 Android 4.1.2、4.4.4、または5.0でも同じことは起こりませんでした。このGoogleは2011年からMartin Westの問題を解決したのだろうか。

このコードをアプリケーションにCREATEに追加しましたが、機能しました。

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

Androidのバージョンを何か他のものに変更する必要があるかどうかを知るのはいいことです。

AsyncTask.execute() UIスレッド、つまり内部アクティビティで実行する必要があります。

私は同じ問題を抱えています。それは、停止/履歴書中にAsynctaskが実行されているときに起こるようです。

編集:ええ、私は持っていたとは思わなかったが、私はこれを使った http://developer.android.com/guide/appendix/faq/commontasks.html#threadingUIスレッドで常にAsynctaskを起動すると、問題が発生しました。ライセンス機能を追加した後に問題が現れました。

ありがとう

これはOPの質問に直接答えるわけではありませんが、テストを実行するときに同じ問題の解決策を検索する人々に役立つと思います。

全体、 ピーター・クネゴの答え それをよく合計します。

私の問題は、API呼び出しにAndroidのAsynctaskを使用したアクティビティの外側のクラスでテストを実行することでした。クラスはアクティビティで使用されているため、アプリケーションで機能しますが、テストから実際のAPI呼び出しを行うテストを実行したかったのです。

その間 ジョナサン・パーローの答え うまくいきましたが、テストのためだけにアプリケーションに変更を導入するのは好きではありませんでした。

したがって、テストの場合 runTestOnUiThread に使える (@UiThreadTest その注釈を使用するテストで結果を待つことができないため、使用できません)。

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

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

しかし、特に機能的なテストでは、ジョナサン・パーローの答えが機能するのは唯一のものであるようです。


* ここを見てください 結果を待っているテストを一時停止する方法を確認します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top