Подтвердить что ты не робот

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

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

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

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

2) Отмените аварийный сигнал (через AlarmManager), который проверяет наличие новых данных и reset тревогу. (Это значит, что если пользователь отключит будильник, он будет отменен до того, как он перезагрузится.)

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)

EDIT: Вот мой метод 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 было null.

4b9b3361

Ответ 1

Чтобы обобщить решение Jonathan Perlow на обнаруженную им ошибку, я использую следующее в любом классе, использующем AsyncTask. Петлером/обработчиком/сообщением является то, как вы можете что-то запускать в потоке пользовательского интерфейса в любом приложении 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 test вызвана onPostExecute() не для вызова. Теперь мы знаем почему.

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

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

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

Затем тесты выглядят следующим образом:

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

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

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

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

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

Предоставление вызывающему абоненту возможности запуска синхронизации или асинхронного просмотра (получение локально допустимого значения onUiThread, не показанного здесь; добавление локальных булевых элементов, как указано выше):

void method(final args, sync, listener, callbakOnUi) {
  Runnable run = new Runnable() { public void run() {
    // method 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

Это связано с ошибкой в ​​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.

Ответ 3

У меня была такая же проблема на устройстве с 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 на что-то еще.

Ответ 4

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

Ответ 5

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

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

Спасибо

Ответ 6

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

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

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

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


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