17

いくつかのデータをフェッチしてAsyncTaskから、この新しいデータでUIを更新するがあります。何ヶ月も問題なく動作していますが、最近、新しいデータがあるときに通知を表示する機能を追加しました。通知を通じてアプリを起動すると、この例外onPostExecuteが発生して呼び出されないことがあります。

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

1)UIを展開し、ビューを検索します

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

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行目sendMessageAtTimeですHandler

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はヌルだったようです。

4

6 に答える 6

40

これは、AndroidフレームワークのAsyncTaskのバグが原因です。AsyncTask.javaのコードは次のとおりです。

private static final InternalHandler sHandler = new InternalHandler();

これはメインスレッドで初期化されることを想定していますが、クラスが静的初期化子を実行する原因となるスレッドで初期化されるため、これは保証されません。ハンドラーがワーカースレッドを参照するこの問題を再現しました。

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

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

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

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

于 2011-10-19T08:57:19.490 に答える
18

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

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

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

AndroidAsyncTaskベースのJUnitテストでのCountDownLatchの使用

UI以外の単体テストで使用するために、android.test.InstrumentationTestCaseの単純なサブクラスを作成しました。「ok」フラグとCountDownLatchがあります。reset()またはreset(count)は、新しいCountDownLatch({1、count})を作成します。good()は、ラッチにok = true、count--、calls.countDown()を設定します。bad()はok = falseを設定し、ずっとカウントダウンします。waitForIt(seconds)は、タイムアウトまたはカウンダウンラッチがゼロになるまで待機します。次に、assertTrue(ok)を呼び出します。

その場合、テストは次のようになります。

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

AsyncTaskの静的初期化バグのため、runTestOnUiThread()に渡されたRunnable内で実際のテストを実行する必要がありました。上記のように適切な静的初期化があれば、テスト対象の呼び出しをUIスレッドで実行する必要がない限り、これは必要ありません。

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

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

次に、AsyncTask <>の単純なサブクラス(doInBackground()とonPostExecute()が必要)を使用して非UIスレッドで実行するか、handler.post()またはpostDelayed()を使用してUIスレッドで実行します。

呼び出し元に同期または非同期を実行するオプションを与えると、次のようになります(ローカルで有効な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();
     }
 }
于 2012-03-17T18:11:06.670 に答える
2

IntentServiceを搭載したAndroid4.0.4のデバイスでも同じ問題が発生し、sdwがClass.forName( "android.os.AsyncTask")で述べたように解決しました。同じことはAndroid4.1.2、4.4.4または5.0では起こりませんでした。このグーグルは2011年からマーティンウェストの問題を解決したのだろうか。

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

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

Androidのバージョンを別のバージョンに変更する必要があるかどうかを知っておくと便利です。

于 2014-11-29T14:54:34.013 に答える
1

AsyncTask.execute()UIスレッド、つまりActivity内で実行する必要があります。

于 2010-11-25T21:22:19.943 に答える
0

同じ問題があります。サスペンド/レジューム中にAsyncTaskが実行されているときに発生するようです。

編集:ええ、私は持っていたとは思いませんでしたが、私はこのhttp://developer.android.com/guide/appendix/faq/commontasks.html#threading を使用して常にUIスレッドでAsyncTaskを開始し、問題は解決しました。ライセンス機能siggghhhhhを追加した後に問題が発生しました

ありがとう

于 2011-03-06T15:28:06.677 に答える
0

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

全体として、PeterKnegoの答えはそれをうまくまとめています。

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

Jonathan Perlowの答えはうまくいきましたが、テストだけのためにアプリケーションに変更を加えるのは好きではありませんでした。

したがって、テストの場合はrunTestOnUiThread使用できます(@UiThreadTestそのアノテーションを使用するテストでは結果を待つことができないため、使用できません)。

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

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

ただし、特に機能テストでは、JonathanPerlowの答えだけが機能するように見える場合があります。


*結果を待ってテストを一時停止する方法については、こちらをご覧ください。

于 2011-12-21T17:31:30.490 に答える