未在 AsyncTask 中调用 onPostExecute(处理程序运行时异常) [英] onPostExecute not being called in AsyncTask (Handler runtime exception)

查看:36
本文介绍了未在 AsyncTask 中调用 onPostExecute(处理程序运行时异常)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 AsyncTask 来获取一些数据,然后用这个新数据更新 UI.几个月来它一直运行良好,但我最近添加了一项功能,可以在有新数据时显示通知.现在,当我的应用程序通过通知启动时,有时我会收到此异常并且 onPostExecute 未被调用.

I have an AsyncTask that fetches some data and then updates the UI with this new data. It has been working fine for months, but I recently added a feature that displays a notification when there is new data. Now when my app is launched through the notification, sometimes I get this exception and onPostExecute is not called.

这是启动应用程序时发生的情况:

This is what happens when the app is launched:

1) 展开 UI 并找到视图

1) Expand the UI and find views

2) 取消检查新数据的警报(通过AlarmManager)并重置警报.(这是为了如果用户禁用警报,它会在他/她下次重新启动之前取消.)

2) Cancel the alarm (through AlarmManager) that checks for new data and reset the alarm. (This is so that if the user disables the alarm it is cancelled before the next time he/she reboots.)

3) 启动AsyncTask.如果应用是通过通知启动的,传入一点数据,然后取消通知.

3) Start the AsyncTask. If the app was launched from the notification, pass in a little bit of the data and then cancel the notification.

我一直在思考可能导致此异常的原因.似乎异常来自 AsyncTask 代码,所以我不确定如何修复它.

I'm stuck on what could be causing this exception. It seems that the exception is from the AsyncTask code, so I'm not sure how I can fix it.

谢谢!

这里有一个例外:

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.我不认为它们应该有任何效果,因为它们所连接的按钮没有被按下.

Here is my onCreate method in my main activity (the one opened by the notification). There are some onClickListeners that I omitted to save space. I don't think they should have any effect, since the buttons they are attached to are not being pressed.

@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 源代码以追踪异常的来源.这是 HandlersendMessageAtTime 的第 456 和 457 行:

EDIT 2: I've been digging through Android source code tracking down where the exception is coming from. This is lines 456 and 457 of sendMessageAtTime in Handler:

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

我对 mQuiting 是什么有点困惑,但看起来上次 enqueueMessage 被称为 msg.target 是 null.

I'm a little confused about what mQuiting is, but it looks like the previous time enqueueMessage was called msg.target was null.

推荐答案

为了概括 Jonathan Perlow 对他特别发现的错误的解决方案,我在任何使用 AsyncTask 的类中使用以下内容.Looper/handler/post 是你如何在 Android 应用程序的任何地方的 UI 线程上运行某些东西,而无需将句柄传递给活动或其他上下文.在类中添加这个静态初始化块:

To generalize Jonathan Perlow's solution to the bug he identified specifically, I use the following in any class that uses AsyncTask. The looper/handler/post is how you can run something on the UI thread anywhere in an Android app without passing down a handle to an activity or other context. Add this static initialization block inside the class:

{ // 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() 不被调用.现在我们知道原因了.

We had run into the problem when trying to get unit tests to run. I found a workaround for that, but hadn't specifically identified the problem. We only knew that trying to use AsyncTask<> in Android JUnit test caused onPostExecute() not to be called. Now we know why.

这篇文章展示了如何在 Android JUnit 测试中运行多线程异步代码:

This post shows how to run multithreaded async code in an Android JUnit test:

在基于 Android AsyncTask 的 JUnit 测试中使用 CountDownLatch

为了用于非 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).

For use with non-UI unit tests, I created a simple subclass of android.test.InstrumentationTestCase. It has an "ok" flag and a CountDownLatch. reset() or reset(count) creates a new CountDownLatch({1,count}). good() sets ok=true, count--, and calls.countDown() on the latch. bad() sets ok=false, and counts down all the way. waitForIt(seconds) waits for timeout or the coundown latch to zero. Then it calls assertTrue(ok).

然后测试就像:

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

由于 AsyncTask 静态初始化错误,我们不得不在传递给 runTestOnUiThread() 的 Runnable 中运行我们的实际测试.使用上述适当的静态初始化,这应该是不必要的,除非被测试的调用需要在 UI 线程上运行.

Because of the AsyncTask static initialization bug, we had to run our actual tests inside a Runnable passed to runTestOnUiThread(). With proper static initialization as above, this shouldn't be necessary, unless the call being tested needs to run on the UI thread.

我现在使用的另一个习惯用法是测试当前线程是否是 UI 线程,然后无论如何在正确的线程上运行请求的操作.有时,允许调用者请求同步与异步,在必要时覆盖是有意义的.例如,网络请求应始终在后台线程上运行.在大多数情况下,AsyncTask 线程池非常适合于此.只需意识到一次只能运行特定数量,从而阻止其他请求.测试当前线程是否为UI线程:

The other idiom I now use is to test whether the current thread is the UI thread and then run the requested action on the proper thread regardless. Sometimes, it makes sense to allow the caller to request sync vs. async, overriding when necessary. For instance, network requests should always be run on a background thread. In most cases, AsyncTask thread pooling is perfect for this. Just realize that only a certain number will run at once, blocking additional requests. To test whether the current thread is the UI thread:

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

然后使用 AsyncTask<> 的简单子类(只需要 doInBackground() 和 onPostExecute())在非 UI 线程上运行,或者使用 handler.post() 或 postDelayed() 在 UI 线程上运行.

Then use a simple subclass (just doInBackground() and onPostExecute() are needed) of AsyncTask<> to run on a non-UI thread or handler.post() or postDelayed() to run on the UI thread.

为调用者提供运行同步或异步的选项看起来像(获取本地有效的 onUiThread 值未在此处显示;添加本地布尔值如上):

Giving the caller the option to run sync or async looks like (getting a locally valid onUiThread value not shown here; add local booleans as above):

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的定义,然后写这样的代码:

Also, using AsyncTask can be made very simple and concise. Use the definition of RunAsyncTask.java below, then write code like this:

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

Or simply:new RunAsyncTask("").execute(new Runnable(){public void run(){ doSomethingInBackground(); }});

RunAsyncTask.java:

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

这篇关于未在 AsyncTask 中调用 onPostExecute(处理程序运行时异常)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆