onPostExecute no ser llamado en AsyncTask (Handler tiempo de ejecución excepción)
-
28-09-2019 - |
Pregunta
Tengo una AsyncTask
que recupera algunos datos y luego actualiza la interfaz de usuario con estos nuevos datos. Se ha trabajando muy bien desde hace meses, pero recientemente he añadido una característica que muestra una notificación cuando hay nuevos datos. Ahora bien, cuando se pone en marcha mi aplicación a través de la notificación, a veces me da esta excepción y onPostExecute
no se llama.
Esto es lo que sucede cuando se inicia la aplicación:
1) Abrir la interfaz de usuario y encontrar puntos de vista ??p>
2) Cancelar la alarma (a través de AlarmManager
) que los controles para los nuevos datos y restablecer la alarma. (Esto es para que si el usuario desactiva la alarma se cancela antes de la próxima vez que él / ella se reinicia.)
3) Iniciar el AsyncTask
. Si la aplicación se puso en marcha a partir de la notificación, pasar un poco de los datos y luego cancelar la notificación.
estoy atascado en lo que podría ser la causa de esta excepción. Parece que la excepción es el código de AsyncTask
, así que no estoy seguro de cómo puedo solucionarlo.
Gracias!
Esta es la excepción:
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: Aquí está mi método onCreate
en mi actividad principal (el abierto por la notificación). Hay algunos onClickListeners
que he omitido para ahorrar espacio. No creo que deberían tener ningún efecto, ya que los botones están unidos a no ser presionado.
@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: He estado cavando a través del código fuente de Android rastrear donde la excepción está viniendo. Esto es las líneas 456 y 457 de sendMessageAtTime
en Handler
:
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
Y esto es enqueueMessage
de 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;
}
Estoy un poco confundido acerca de lo que es mQuiting
, pero parece que la vez anterior enqueueMessage
fue llamado msg.target
era nula.
Solución
Para generalizar la solución de Jonathan Perlow que el insecto se identificó específicamente, utilizo el siguiente en cualquier clase que utiliza AsyncTask. El medidor / controlador / post es cómo se puede ejecutar en cualquier lugar algo en el hilo de interfaz de usuario en una aplicación para Android, sin pasar por un mango para una actividad o de otro contexto. Añadir este bloque de inicialización estática dentro de la clase:
{ // 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();
}
}
});
}
se había encontrado con el problema cuando se trata de obtener pruebas de unidad para correr. He encontrado una solución para eso, pero no había identificado específicamente el problema. Sólo sabíamos que tratar de utilizar AsyncTask <> en la prueba JUnit Android causó onPostExecute () no debe ser llamado. Ahora sabemos por qué.
Este post muestra cómo ejecutar código asíncrono multiproceso en una prueba JUnit Android:
Usando CountDownLatch en Android AsyncTask basados ??en pruebas JUnit
Para el uso con las pruebas de unidad de interfaz de usuario no, he creado una subclase sencilla de android.test.InstrumentationTestCase. Cuenta con una bandera de "ok" y un CountDownLatch. reset () o reinicio (recuento) crea un nuevo CountDownLatch ({1,} recuento). buena () establece ok = true, count-- y calls.countDown () en el pestillo. mala () establece ok = falso, y cuenta atrás hasta el final. waitForIt (segundos) espera a que el tiempo de espera o coundown pestillo a cero. A continuación, llama assertTrue (OK).
pruebas son, entonces, como:
someTest() {
reset();
asyncCall(args, new someListener() {
public void success(args) { good(); }
public void fail(args) { bad(); }
});
waitForIt();
}
Debido al fallo de inicialización AsyncTask estática, que tuvo que llevar a cabo nuestras pruebas reales dentro de un Ejecutable pasó a runTestOnUiThread (). Con la inicialización estática adecuado que el anterior, esto no debería ser necesario, a menos que la llamada está probando tiene que ejecutar en el subproceso de interfaz de usuario.
El otro idioma que ahora uso es para probar si el subproceso actual es el hilo de interfaz de usuario y luego ejecuta la acción solicitada en el hilo adecuado independientemente. A veces, tiene sentido para permitir que la persona que llama para solicitar sincronización vs asíncrono, anulando cuando sea necesario. Por ejemplo, las solicitudes de red siempre se deben ejecutar en un subproceso en segundo plano. En la mayoría de los casos, AsyncTask conjunto de hilos es perfecto para esto. Sólo se dan cuenta de que sólo un cierto número se ejecutará a la vez, el bloqueo de peticiones adicionales. Para probar si el hilo actual es el hilo de interfaz de usuario:
boolean onUiThread = Looper.getMainLooper().getThread() == Thread.currentThread();
A continuación, utilice una subclase sencilla (justo doInBackground () y onPostExecute () son necesarios) de AsyncTask <> para ejecutar en un subproceso no interfaz de usuario o handler.post () o postDelayed () para ejecutar en el subproceso de interfaz de usuario.
Dar a la persona que llama la opción de ejecutar sincronización o asincrónicos se parece a (obtener un valor onUiThread localmente válida no se muestra aquí, añadir booleanos locales como el anterior):
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);
}
Además, el uso AsyncTask puede hacerse muy simple y concisa. Utilice la definición de RunAsyncTask.java abajo, a continuación, escribir el código siguiente:
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);
}});
. O simplemente: nueva RunAsyncTask ( "") ejecutar (nueva Ejecutable () {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();
}
}
Otros consejos
Esto se debe a un error en AsyncTask en el marco de Android. AsyncTask.java tiene el siguiente código:
private static final InternalHandler sHandler = new InternalHandler();
Se espera que esto se puede inicializar en el hilo principal, pero que no está garantizada ya que se iniciará en el que sea el hilo que pasa a causar la clase para ejecutar sus inicializadores estáticos. I reproducido este problema por el que el controlador hace referencia a un subproceso de trabajo.
Un modelo común que causa que esto suceda es utilizar el IntentService clase. El código de ejemplo C2DM hace esto.
Una solución sencilla consiste en añadir el siguiente código al método onCreate de la aplicación:
Class.forName("android.os.AsyncTask");
Esto obligará AsyncTask a ser inicializado en el hilo principal. Yo presenté un error en esto en la base de datos de errores androide. Ver http://code.google.com/p/android/issues/ detalle? id = 20915 .
Yo tenía el mismo problema en un dispositivo con Android 4.0.4 con la IntentService y solucioné como dijo sdw con el Class.forName ( "android.os.AsyncTask"). Lo mismo no ocurrió en Android 4.1.2, 4.4.4 o 5.0. Me pregunto si esto resuelve Google Martin West tema a partir de 2011.
He añadido este código en mi aplicación onCreate y funcionó:
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
try {
Class.forName("android.os.AsyncTask");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
Sería bueno saber si la versión de Android necesidad de cambiarse a otra cosa.
AsyncTask.execute()
debe haber ejecutado el hilo de interfaz de usuario, es decir, dentro de la actividad.
Tengo el mismo problema, que parece ocurrir cuando el AsyncTask se está ejecutando durante una hoja de vida de suspensión /.
EDIT: Sí, no pensé que tenía pero me utilizado este http://developer.android .com / guía / Apéndice / Preguntas / commontasks.html # roscado Siempre a iniciar el AsyncTask en el hilo de interfaz de usuario y el problema se ha ido. El problema apareció después de añadir la función de la concesión de licencias, siggghhhhh
Gracias
A pesar de que esto no responde directamente a la pregunta de la OP, creo que va a ser útil para las personas que buscan la solución del mismo problema cuando se ejecutan las pruebas.
La respuesta deEn general, Peter Knego lo resume bien.
Mi problema fue específicamente con el funcionamiento de una prueba en una clase fuera de una actividad que hizo uso de AsyncTask de Android para una llamada a la API. La clase trabaja en la aplicación, ya que es utilizado por una actividad, pero quería realizar una prueba de hacer una llamada a la API actual de la prueba.
Mientras que de Jonathan Perlow respuesta trabajó, no me gustaba la introducción de cambios en mi aplicación debe únicamente a una prueba.
Por lo tanto, se puede utilizar en el caso de una prueba de runTestOnUiThread
(@UiThreadTest
no se puede utilizar, ya que no puede esperar a que un resultado en una prueba que utiliza ese anotación).
public void testAPICall() throws Throwable {
this.runTestOnUiThread(new Runnable() {
public void run() {
underTest.thisMethodWillMakeUseOfAnAsyncTaskSomehow();
}
});
// Wait for result here *
// Asserts here
}
A veces, sin embargo, especialmente en las pruebas funcionales, la respuesta de Jonathan Perlow parece ser el único que funciona.
Echa un vistazo aquí para ver cómo hacer una pausa en una prueba a la espera de un resultado.