onPostExecute не вызывается в AsyncTask (исключение среды выполнения обработчика)
У меня есть 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
в моей основной деятельности (тот, который открывается уведомлением). Есть некоторые 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
, был нулевым.6 ответов:
Чтобы обобщить решение Джонатана Перлоу для ошибки, которую он определил специально, я использую следующее В любом классе, который использует 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() не вызывается. Теперь мы знаем почему.
Этот пост показывает, как запустить многопоточный асинхронный код в тесте Android JUnit:Использование CountDownLatch в тестах JUnit на базе Android AsyncTask
Для использования с unit-тестами без пользовательского интерфейса я создал простой подкласс android.тест.InstrumentationTestCase. У него есть флаг " ОК " и обратный отсчет. сбросить или reset (count) создает новый CountDownLatch ({1,count}). good() устанавливает ok=true, count--, и вызывает.обратный отсчет () на защелке. bad() устанавливает ok=false и ведет обратный отсчет до конца. waitForIt (секунды) ожидает таймаута или фиксации обратного отсчета до нуля. Затем он вызывает assertTrue (ok).
Тогда тесты похожи:
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 для запуска в потоке или обработчике, отличном от UI.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); }});
Или просто: new RunAsyncTask ("").выполнить(новый Runnable(){общественная пустота запустить(){ 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 сказал С классом.forName ("андроид.ос.AsyncTask"). То же самое не произошло на Android 4.1.2, 4.4.4 или 5.0. Интересно, разрешил ли этот Google проблему Мартина Уэста с 2011 года?
Я добавил этот код в свое приложение onCreate, и он сработал:
Было бы неплохо узнать, нужно ли менять версию Android на что-то другое.if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) { try { Class.forName("android.os.AsyncTask"); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
AsyncTask.execute()
должен выполняться в потоке пользовательского интерфейса, то есть внутри действия.
У меня та же проблема, кажется, это происходит, когда AsyncTask работает во время приостановки / возобновления.
Править: Да, не думал, что у меня есть, но я использовал это http://developer.android.com/guide/appendix/faq/commontasks.html#threading чтобы всегда запускать AsyncTask в потоке пользовательского интерфейса, и проблема исчезла. Проблема возникла после того, как я добавил функцию лицензирования, siggghhhhh
Спасибо
Хотя это не дает прямого ответа на вопрос ОП, я думаю, что это будет полезно для людей, ищущих решение той же проблемы при выполнении тестов.
В целом, ответ Питера Кнего хорошо подводит итог.
Моя проблема была конкретно с запуском теста на классе вне действия, которое использовало AsyncTask Android для вызова API. Класс работает в приложении, так как он используется активностью, но я хотел запустить тест, делая фактический Вызов API из теста.
Хотя ответ Джонатана Перлоусработал, мне не нравилось вносить изменения в мое приложение исключительно из-за теста.
Таким образом, в случае теста
runTestOnUiThread
можно использовать (@UiThreadTest
нельзя использовать, так как вы не можете ждать результата в тесте, который использует эту аннотацию).Хотя иногда, особенно в функциональных тестах, ответ Джонатана Перлоу кажется единственным, который работает.public void testAPICall() throws Throwable { this.runTestOnUiThread(new Runnable() { public void run() { underTest.thisMethodWillMakeUseOfAnAsyncTaskSomehow(); } }); // Wait for result here * // Asserts here }
* посмотрите здесь , чтобы увидеть, как приостановить тест в ожидании результата.