代码之家  ›  专栏  ›  技术社区  ›  Computerish

异步任务中未调用onPostExecute(处理程序运行时异常)

  •  17
  • Computerish  · 技术社区  · 15 年前

    我有一个 AsyncTask 它获取一些数据,然后用这些新数据更新UI。它已经工作了好几个月了,但是我最近添加了一个功能,当有新数据时,它会显示一个通知。现在,当我的应用程序通过通知启动时,有时会出现此异常并 onPostExecute 不被调用。

    应用程序启动时会发生以下情况:

    1)展开UI并查找视图

    2)取消警报(通过 AlarmManager )它检查新数据并重置警报。(这样,如果用户禁用警报,则在下次重新启动之前取消警报。)

    3)启动 异步任务 . 如果应用程序是从通知中启动的,请传入一点数据,然后取消通知。

    我不明白是什么导致了这个异常。似乎例外是 异步任务 代码,所以我不确定如何修复它。

    谢谢!

    以下是例外情况:

    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 是的,但看起来像上一次 排队消息 被称为 msg.target 是空的。

    6 回复  |  直到 15 年前
        1
  •  18
  •   Community Mohan Dere    8 年前

    为了将Jonathan Perlow的解决方案推广到他特别指出的bug,我在任何使用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测试中运行多线程异步代码:

    Using CountDownLatch in Android AsyncTask-based JUnit tests

    为了与非UI单元测试一起使用,我创建了android.test.InstrumentationTestCase的一个简单子类。它有一个“ok”标志和一个倒计时锁。reset()或reset(count)创建一个新的countdownlock({1,count})。good()设置ok=true、count--,并在闩锁上调用.countDown()。bad()设置ok=false,并一直倒计时。等待(秒)等待超时或coundown闩锁为零。然后它调用assertTrue(ok)。

    那么测试就像:

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

    由于AsyncTask静态初始化错误,我们必须在传递给runTestOnUiThread()的Runnable中运行实际的测试。对于上述正确的静态初始化,这是不必要的,除非正在测试的调用需要在UI线程上运行。

    我现在使用的另一个习惯用法是测试当前线程是否是UI线程,然后在适当的线程上运行所请求的操作。有时,允许调用者请求同步和异步是有意义的,在必要时重写。例如,网络请求应该始终在后台线程上运行。在大多数情况下,异步任务线程池非常适合这样做。只需意识到一次只会运行一个特定的数字,从而阻止其他请求。要测试当前线程是否为UI线程,请执行以下操作:

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

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

    为调用方提供运行sync或async的选项,如下所示(获取此处未显示的本地有效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();
         }
     }
    
        2
  •  40
  •   Jonathan Perlow    14 年前

    这是由于Android框架中AsyncTask中的一个bug造成的。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 .

        3
  •  2
  •   Carlos Jimenez    11 年前

    我在带有Android 4.0.4和IntentService的设备上遇到了同样的问题,并像sdw对Class.forName(“Android.os.AsyncTask”)所说的那样解决了这个问题。在Android 4.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版本是否需要更改为其他版本。

        4
  •  1
  •   Peter Knego    15 年前

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

        5
  •  0
  •   Martin West    15 年前

    我也有同样的问题,当异步任务在挂起/恢复期间运行时,似乎会发生这种情况。

    编辑: 是的,没想到我有但是我用了这个 http://developer.android.com/guide/appendix/faq/commontasks.html#threading 总是在UI线程上启动AsyncTask,问题就解决了。 问题出现在我添加了许可功能sigghhhh之后

    谢谢

        6
  •  0
  •   Community Mohan Dere    8 年前

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

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

    我的问题是在一个活动之外的类上运行测试,该活动使用Android的AsyncTask进行API调用。这个类在应用程序中工作,因为它被一个活动使用,但是我想运行一个测试,从测试中发出一个实际的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 查看如何暂停测试以等待结果。

    推荐文章