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

У меня есть 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) 

EDIT: Вот мой метод onCreate в моем основном 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 

EDIT 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.

Solutions Collecting From Web of "OnPostExecute не вызывается в AsyncTask (исключение выполнения обработчика Handler)"

Чтобы обобщить решение Джонатана Перлоя на обнаруженную им ошибку, я использую следующее в любом классе, который использует 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'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(); } } 

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

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

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

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

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

благодаря

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

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

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

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


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