onPostExecute не вызывается в AsyncTask (исключение во время выполнения обработчика)

StackOverflow https://stackoverflow.com/questions/4280330

Вопрос

У меня есть 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 было равно нулю.

Это было полезно?

Решение

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

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

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

Этот пост показывает, как запустить многопоточенный ASYNC код в тесте Android Junit:

Использование CountDownlatch в Android Asynctask на основе Junit Tests

Для использования с модульными тестами неэй я создал простой подкласс android.test.instrumentationtestcas. У него есть флаг «ОК» и CountDownlatch. Сброс () или сброс (счетчик) создает новый CountDownAtch ({1, count}). Хорошие () Устанавливает ok = true, count-- и call.Countdown () на защелке. Плохие () Устанавливает OK = false и подсчитывает весь путь. Waitforit (Seconds) ждет время ожидания или защелкой COUNDOWN до нуля. Затем он называет AssertTrue (OK).

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

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

Из-за ошибки статической инициализации асинктракса нам пришлось управлять нашими фактическими тестами внутри прохождения, передаваемых в RuntestOnuithRead (). С надлежащей статической инициализацией, как указано выше, это не нужно необходимость, если не требуется тестирование вызова, не требует запуска по течению UI.

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

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

Затем используйте простой подкласс (просто Doinbackground () и onPostexecute () необходимы) aSynctask <> для запуска на ните не-пользовательского интерфейса или обработчика.

Давая вызывающее абонеру возможность запуска синхронизации или 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);
    }});

Или просто: новая рунасинеркт («»). EXECUTE (новый Runnable () {public proting () {doSomethinginbackgroundground ();}});

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 в платформе Android.AsyncTask.java имеет следующий код:

private static final InternalHandler sHandler = new InternalHandler();

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

Распространенным шаблоном, который приводит к этому, является использование класса IntentService .Пример кода C2DM делает это.

Простым обходным решением является добавление следующего кода в метод onCreate приложения:

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

Это заставит AsyncTask инициализироваться в основном потоке.Я зарегистрировал ошибку по этому поводу в базе данных Android bug database.Видишь http://code.google.com/p/android/issues/detail?id=20915.

У меня была такая же проблема на устройстве с Android 4.0.4 с интенсивнымСервицем и решила его, поскольку SDW по словам SDW с классом. Функция («Android.os.asyncyskask»). То же самое не произошло на Android 4.1.2, 4.4.4 или 5.0. Интересно, разрешил ли этот Google решить проблему Martin West с 2011 года.

Я добавил этот код на моем приложении OnCreate, и он работал:

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

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

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

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

Редактировать: Да, не думал, что я имел, но я использовал это http://developer.android.com/guide/appendix/faq/commontasks.html#thhtreading.Чтобы всегда начать асинктюдку на ните UI, и проблема прошла. Проблема появилась после добавления функции лицензирования, SIGGGHHHHHH

Спасибо

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

В общем и целом, Ответ Питера Кнего суммирует это хорошо.

Моя проблема была специально с проведением теста на классе за пределами деятельности, в использовании Asynctask 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
}

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


* Посмотрите здесь Чтобы увидеть, как приостановить тест, ожидающий результата.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top