【问题标题】:onPostExecute not being called in AsyncTask (Handler runtime exception)在 AsyncTask 中未调用 onPostExecute(处理程序运行时异常)
【发布时间】:2010-11-25 19:32:28
【问题描述】:

我有一个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 源代码,以追踪异常的来源。这是sendMessageAtTimeHandler 的第456 和457 行:

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

这是来自MessageQueueenqueueMessage

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

我对@9​​87654336@ 是什么感到有些困惑,但看起来上次enqueueMessage 被称为msg.target 时为空。

【问题讨论】:

  • 创建 AsyncTask 的线程必须已经退出。你是如何创建你的 AsyncTask 的?
  • 我用代码更新了我的问题。当我仍然可以看到加载屏幕时,我看不到 Activity 会如何退出。
  • 你去。我很确定问题出在这种方法上,因为它是唯一一个已更改的方法,但如果您愿意,我也可以发布MyFetcher 代码。谢谢你的帮助。我一直在挖掘 Android 源代码,但我被卡住了。
  • 我还添加了我从 Android 源代码中学到的知识,以防万一。
  • 下面提到的任何解决方案是否真的适合您?我面临着非常相似的情况,到目前为止,下面提到的任何事情都没有对我有用。

标签: android android-asynctask alarmmanager


【解决方案1】:

这是由于 Android 框架中的 AsyncTask 中的错误造成的。 AsyncTask.java 代码如下:

private static final InternalHandler sHandler = new InternalHandler();

它希望在主线程上初始化它,但这不能保证,因为它会在恰好导致类运行其静态初始化程序的任何线程上初始化。我在 Handler 引用工作线程时重现了这个问题。

导致这种情况发生的常见模式是使用 IntentService 类。 C2DM 示例代码执行此操作。

一个简单的解决方法是将以下代码添加到应用程序的 onCreate 方法中:

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

这将强制 AsyncTask 在主线程中初始化。我在 android 错误数据库中提交了一个错误。见http://code.google.com/p/android/issues/detail?id=20915

【讨论】:

  • 谢谢!我把头撞在墙上几次试图弄清楚为什么在我阅读本文之前没有调用onPostExecute()
  • 这在尝试将应用程序移植到 Google TV 时也帮助了我。它可以在其他平台上运行,onPostExecute 没有被调用让我发疯。
【解决方案2】:

为了概括 Jonathan Perlow 对他特别指出的错误的解决方案,我在任何使用 AsyncTask 的类中使用以下内容。 looper/handler/post 是您可以在 Android 应用程序中的任何位置在 UI 线程上运行某些东西的方式,而无需将句柄传递给 Activity 或其他上下文。在类中添加这个静态初始化块:

{ // 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 测试中运行多线程异步代码:

Using CountDownLatch in Android AsyncTask-based JUnit tests

为了与非 UI 单元测试一起使用,我创建了一个简单的 android.test.InstrumentationTestCase 子类。它有一个“ok”标志和一个 CountDownLatch。 reset() 或 reset(count) 创建一个新的 CountDownLatch({1,count})。 good() 在锁存器上设置 ok=true、count-- 和 call.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 线程上运行。

我现在使用的另一个习惯用法是测试当前线程是否是 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();
     }
 }

【讨论】:

  • 优秀。我在进行 Android JUnit 测试时遇到了这个问题,但仅在 2.3.4 上。你能告诉我它是只在 JUnit 测试中运行时发生,还是在 2.3.4 时也会在应用程序中发生。在我的例子中,AsyncTask 是在 Receiver 的 onReceive() 方法中创建和运行的——它工作正常。
  • 这个错误有一段时间没有被注意到的原因是,在一个普通的应用程序中,AsyncTask 往往首先从 UI 线程中使用。在这种情况下,静态被分配一个正确的值。除非您设法在正常 UI 堆栈之前触发静态初始化程序,并且在另一个线程上执行此操作,否则应该没问题。
【解决方案3】:

我在带有 IntentService 的 Android 4.0.4 设备上遇到了同样的问题,并按照 sdw 所说的 Class.forName("android.os.AsyncTask") 解决了它。在 Android 4.1.2、4.4.4 或 5.0 上没有发生同样的情况。我想知道这个 Google 是否从 2011 年解决了 Martin West 问题。

我在我的应用程序 onCreate 上添加了这段代码,它起作用了:

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

很高兴知道是否需要将 Android 版本更改为其他版本。

【讨论】:

    【解决方案4】:

    AsyncTask.execute() 必须在 UI 线程上执行,即在 Activity 内部。

    【讨论】:

    • 它是从我的活动中调用的。
    • “Withi Activity”和“on UI thread”是不同的东西。 AsyncTask 使用 Handler 将 doInBackground 的结果发布到 UI 线程。如果您在 UI 以外的某个线程上启动异步任务,则结果将排队到处理程序的队列中,但永远不会被检索和执行。
    【解决方案5】:

    我有同样的问题,它似乎发生在 AsyncTask 在暂停/恢复期间运行时。

    编辑: 是的,没想到我有,但我用了这个http://developer.android.com/guide/appendix/faq/commontasks.html#threading 始终在 UI 线程上启动 AsyncTask 并且问题已经消失。 添加授权功能后出现问题,sigghhhhh

    谢谢

    【讨论】:

    • 这很有趣,我有一个应用程序完美运行了几周,然后当我启用对许可库函数的调用时,它也开始抛出这个错误。当然,它是许可库,我在最终发布构建之前启用了它,所以它把发布周变成了完全的恐慌。
    【解决方案6】:

    尽管这并不能直接回答 OP 的问题,但我认为它对于在运行测试时寻找相同问题的解决方案的人们会很有用。

    总的来说,Peter Knego's answer 总结得很好。

    我的问题特别是在使用 Android 的 AsyncTask 进行 API 调用的 Activity 之外的类上运行测试。该类在应用程序中工作,因为它由 Activity 使用,但我想运行一个测试,从测试中进行实际的 API 调用。

    虽然Jonathan Perlow's answer 有效,但我不喜欢仅仅因为测试而对我的应用程序进行更改。

    因此,在测试的情况下可以使用runTestOnUiThread(不能使用@UiThreadTest,因为在使用该注释的测试中您不能等待结果)。

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

    但有时,尤其是在功能测试中,Jonathan Perlow 的答案似乎是唯一有效的答案。


    * Take a look here 了解如何暂停测试以等待结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-07
      • 1970-01-01
      • 2015-07-26
      • 1970-01-01
      相关资源
      最近更新 更多