onPostExecute не вызывается в AsyncTask (исключение времени выполнения обработчика) - PullRequest
17 голосов
/ 25 ноября 2010

У меня есть AsyncTask, который выбирает некоторые данные, а затем обновляет пользовательский интерфейс этими новыми данными. Он работал нормально в течение нескольких месяцев, но недавно я добавил функцию, которая отображает уведомление при появлении новых данных. Теперь, когда мое приложение запускается через уведомление, иногда я получаю это исключение и onPostExecute не вызывается.

Вот что происходит при запуске приложения:

1) Расширение пользовательского интерфейса и поиск представлений

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 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 был нулевым.

Ответы [ 6 ]

40 голосов
/ 19 октября 2011

Это связано с ошибкой в ​​AsyncTask в платформе Android. 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.

18 голосов
/ 17 марта 2012

Чтобы обобщить решение Джонатана Перлоу об обнаруженной им ошибке, я использую следующее в любом классе, использующем AsyncTask. Цикл / обработчик / публикация - это то, как вы можете запустить что-либо в потоке пользовательского интерфейса в любом месте приложения Android, не передавая дескриптор действия или другого контекста. Добавьте этот статический блок инициализации внутри класса:

{ // /3736361/onpostexecute-ne-vyzyvaetsya-v-asynctask-isklychenie-vremeni-vypolneniya-obrabotchika
    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();
        }
      }
    });
}

Мы столкнулись с проблемой при попытке запустить модульные тесты. Я нашел обходной путь для этого, но конкретно не определил проблему. Мы только знали, что попытка использовать AsyncTask <> в тесте Android JUnit приводила к тому, что onPostExecute () не вызывался. Теперь мы знаем, почему.

В этом посте показано, как запустить многопоточный асинхронный код в тесте Android JUnit:

Использование CountDownLatch в Android-тестах JUnit на основе AsyncTask

Для использования с юнит-тестами без пользовательского интерфейса я создал простой подкласс android.test.InstrumentationTestCase. Он имеет флаг "ОК" и CountDownLatch. reset () или reset (count) создает новый CountDownLatch ({1, count}). good () устанавливает в защелку ok = true, count-- и Call.CountDown (). bad () устанавливает ok = false и ведет обратный отсчет. waitForIt (секунд) ожидает истечения времени ожидания или защелки coundown к нулю. Затем он вызывает assertTrue (ок).

Тогда тесты похожи на:

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

Из-за ошибки статической инициализации AsyncTask нам пришлось запускать наши реальные тесты внутри Runnable, переданного runTestOnUiThread (). При правильной статической инициализации, как указано выше, в этом нет необходимости, если только тестируемый вызов не должен выполняться в потоке пользовательского интерфейса.

Другая идиома, которую я сейчас использую, заключается в том, чтобы проверить, является ли текущий поток потоком пользовательского интерфейса, а затем выполнить запрошенное действие в соответствующем потоке независимо от этого. Иногда имеет смысл разрешить вызывающей стороне запрашивать синхронизацию и асинхронность, переопределяя при необходимости. Например, сетевые запросы всегда должны выполняться в фоновом потоке. В большинстве случаев пул потоков AsyncTask идеально подходит для этого. Просто поймите, что только определенное количество будет работать одновременно, блокируя дополнительные запросы. Чтобы проверить, является ли текущий поток потоком пользовательского интерфейса:

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

Затем используйте простой подкласс (необходимы только doInBackground () и onPostExecute ()) AsyncTask <> для запуска в потоке, не являющемся пользовательским интерфейсом, или handler.post () или postDelayed () для запуска в потоке пользовательского интерфейса.

Предоставление вызывающей стороне опции для запуска 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);
    }});

Или просто: новый 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 голосов
/ 29 ноября 2014

У меня была такая же проблема на устройстве с Android 4.0.4 с IntentService и я решил ее, как sdw сказал с Class.forName ("android.os.AsyncTask") Такого не было на Android 4.1.2, 4.4.4 или 5.0. Интересно, разрешил ли этот Google вопрос Мартина Уэста с 2011 года.

Я добавил этот код в свое приложение onCreate, и оно заработало:

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

Было бы неплохо узнать, нужно ли менять версию Android на другую.

1 голос
/ 26 ноября 2010

AsyncTask.execute() должно быть выполнено в потоке пользовательского интерфейса, т.е. внутри Activity.

0 голосов
/ 21 декабря 2011

Даже если это не дает прямого ответа на вопрос ОП, я думаю, что это будет полезно для людей, ищущих решение той же проблемы при выполнении тестов.

В целом, Ответ Питера Кнего хорошо подытоживает.

Моя проблема была, в частности, с запуском теста для класса вне Activity, который использовал AsyncTask Android для вызова API.Класс работает в приложении, так как он используется Activity, но я хотел запустить тест, выполняющий реальный вызов API из теста.

Пока Ответ Джонатана Перлоу сработал, яМне не понравилось вносить изменения в мое приложение только из-за теста.

Таким образом, в случае теста можно использовать runTestOnUiThread (нельзя использовать @UiThreadTest, так как вы не можете ждать результатав тесте, использующем эту аннотацию).

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

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

Иногда, особенно в функциональных тестах, ответ Джонатана Перлоу кажется единственным, который работает.


* Взгляните сюда , чтобы увидеть, как поставить тест на паузу в ожидании результата.

0 голосов
/ 06 марта 2011

У меня та же проблема, похоже, это происходит, когда AsyncTask работает во время приостановки / возобновления.

EDIT: Да, не думаю, что я имел, но я использовал это http://developer.android.com/guide/appendix/faq/commontasks.html#threading всегда запускать AsyncTask в потоке пользовательского интерфейса, и проблема исчезла. Проблема появилась после того, как я добавил функцию лицензирования, siggghhhhh

Спасибо

...