我有一个 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);

这就是 enqueueMessageMessageQueue:

    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的任何类中使用以下内容。 Looper/Handler/Post是您可以在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的子类。它具有“确定”标志和一个CountDownLatch。 reset()或reset(count)创建一个新的CountDownLatch({1,count})。 good()设置确定= true,count--和calls.countdown()在闩锁上。 bad()设置确定= false,并一直倒数。 Waitforit(秒)等待超时或将Coundown闩锁锁定为零。然后它调用asserttrue(确定)。

然后测试就像:

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

由于异步静态初始化错误,我们必须在可运行的可运行中运行的实际测试传递给runtestonuithread()。在适当的静态初始化中,这不必是必要的,除非经过测试的呼叫需要在UI线程上运行。

我现在使用的另一个成语是测试当前线程是否为UI线程,然后在正确的线程上运行所请求的操作。有时,允许呼叫者请求同步与异步,在必要时覆盖是有意义的。例如,网络请求应始终在背景线程上运行。在大多数情况下,异步线程池非常适合此。只需意识到,只有一定数量才能一次运行,从而阻止其他请求。测试当前线程是否是UI线程:

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

然后,使用一个简单的子类(仅需要doinbackground()和onpostexecute())asynctask <>在non-ui thread或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);
}

另外,使用异步箱可以变得非常简单明了。使用下面的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(“”)。执行(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.java具有以下代码:

private static final InternalHandler sHandler = new InternalHandler();

它预计将在主线程上初始化,但是不能保证,因为它将在任何线程上初始化,无论哪种线程都会导致类运行其静态初始化器。我复制了此问题,其中处理程序引用了工人线程。

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

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

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

这将迫使异步在主线程中初始化。我在Android错误数据库中对此提出了错误。看 http://code.google.com/p/android/issues/detail?id=20915.

我在带有IntentService的Android 4.0.4的设备上遇到了相同的问题,并将其解决,如SDW所说的class.Forname(“ android.os.asynctask”)。在Android 4.1.2、4.4.4或5.0上没有发生同样的情况。我想知道该Google是否从2011年开始解决Martin West问题。

我在我的应用程序上添加了此代码,并且它起作用了:

    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线程上执行,即在活动中。

我遇到了同样的问题,当暂停/简历期间的异步运行时,似乎会发生这种情况。

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

谢谢

即使这没有直接回答OP的问题,我认为这对于在运行测试时搜索相同问题的解决方案的人将很有用。

总体, 彼得·奈戈的答案 总结得很好。

我的问题是专门用于在一个活动外进行测试,该活动使用Android的Asynctask进行API调用。该类在应用程序中起作用,因为它是由活动使用的,但是我想运行测试,从测试中进行实际API调用。

尽管 乔纳森·佩洛的回答 有效,我不喜欢仅由于测试而引入更改。

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

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

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

但是,有时候,尤其是在功能测试中,乔纳森·佩洛(Jonathan Perlow)的答案似乎是唯一起作用的答案。


* 在这里看看 查看如何暂停测试等待结果。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top