thread studio onpreexecute onpostexecute method hilos examples example create asynctask android android-asynctask alarmmanager

studio - hilos asynctask android



onPostExecute no se llama en AsyncTask(excepción de tiempo de ejecución del controlador) (6)

Aunque esto no responde directamente a la pregunta del OP, creo que será útil para las personas que buscan la solución del mismo problema al ejecutar las pruebas.

En general, la respuesta de Peter Knego lo resume bien.

Mi problema fue específicamente con la ejecución 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 funciona en la aplicación, ya que es utilizada por una actividad, pero quería realizar una prueba haciendo una llamada a la API real desde la prueba.

Si bien la respuesta de Jonathan Perlow funcionó, no me gustó introducir cambios en mi solicitud debido únicamente a una prueba.

Por lo tanto, en el caso de una prueba, se puede usar @UiThreadTest (no se puede usar @UiThreadTest , ya que no se puede esperar un resultado en una prueba que usa esa anotación).

public void testAPICall() throws Throwable { this.runTestOnUiThread(new Runnable() { public void run() { underTest.thisMethodWillMakeUseOfAnAsyncTaskSomehow(); } }); // Wait for result here * // Asserts here }

Aunque a veces, especialmente en las pruebas funcionales, la respuesta de Jonathan Perlow parece ser la única que funciona.

* Echa un vistazo aquí para ver cómo pausar una prueba esperando un resultado.

Tengo una AsyncTask que obtiene algunos datos y luego actualiza la interfaz de usuario con estos nuevos datos. Ha estado funcionando bien durante meses, pero recientemente agregué una función que muestra una notificación cuando hay nuevos datos. Ahora, cuando mi aplicación se inicia a través de la notificación, a veces recibo esta excepción y no se llama a onPostExecute .

Esto es lo que sucede cuando se lanza la aplicación:

1) Expandir la interfaz de usuario y encontrar vistas

2) Cancele la alarma (a través del AlarmManager ) que comprueba los nuevos datos y reinicie la alarma. (Esto es para que si el usuario desactiva la alarma, se cancele antes de la próxima vez que se reinicie).

3) Inicia la AsyncTask . Si la aplicación se inició desde la notificación, pase un poco de los datos y luego cancele la notificación.

Estoy atascado en lo que podría estar causando esta excepción. Parece que la excepción es del código AsyncTask , así que no estoy seguro de cómo puedo solucionarlo.

¡Gracias!

Aquí está 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)

EDITAR: Aquí está mi método onCreate en mi actividad principal (la que abre la notificación). Hay algunos onClickListeners que omití para ahorrar espacio. No creo que deban tener ningún efecto, ya que los botones a los que están conectados no se están presionando.

@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 buscando en el código fuente de Android rastreando de dónde proviene la excepción. Estas son 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 qué es mQuiting , pero parece que la vez anterior enqueueMessage fue llamado msg.target era nulo.


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 inicialice en el subproceso principal, pero eso no está garantizado, ya que se inicializará en cualquier subproceso que haga que la clase ejecute sus inicializadores estáticos. Reproducí este problema donde el controlador hace referencia a un subproceso de trabajo.

Un patrón común que hace que esto suceda es usar la clase IntentService. El código de ejemplo C2DM hace esto.

Una solución simple es agregar el siguiente código al método onCreate de la aplicación:

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

Esto forzará la inicialización de AsyncTask en el hilo principal. He archivado un error en esto en la base de datos de errores de Android. Consulte http://code.google.com/p/android/issues/detail?id=20915 .


Para generalizar la solución de Jonathan Perlow al error que identificó específicamente, uso lo siguiente en cualquier clase que use AsyncTask. El looper / handler / post es cómo puedes ejecutar algo en el hilo de la IU en cualquier lugar de una aplicación de Android sin pasar un identificador a una actividad u otro contexto. Agregue este bloque de inicialización estática dentro de la clase:

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

Nos habíamos topado con el problema al intentar que se ejecutaran las pruebas unitarias. Encontré una solución alternativa para eso, pero no había identificado específicamente el problema. Solo sabíamos que al intentar usar AsyncTask <> en la prueba de Android JUnit no se podía llamar a OnPostExecute (). Ahora sabemos por qué.

Esta publicación muestra cómo ejecutar código asíncrono multihilo en una prueba de Android JUnit:

Uso de CountDownLatch en pruebas de JUnit basadas en Android AsyncTask

Para usar con pruebas unitarias que no son UI, creé una subclase simple de android.test.InstrumentationTestCase. Tiene una bandera "ok" y CountDownLatch. reset () o reset (count) crea un nuevo CountDownLatch ({1, count}). good () establece ok = true, count-- y calls.countDown () en el pestillo. bad () establece ok = false, y realiza una cuenta regresiva hasta el final. waitForIt (segundos) espera a que se agote el tiempo de espera o el cierre de cierre. Entonces llama assertTrue (ok).

Entonces las pruebas son como:

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

Debido al error de inicialización estática de AsyncTask, tuvimos que ejecutar nuestras pruebas reales dentro de un Runnable pasado a runTestOnUiThread (). Con la inicialización estática adecuada como se indicó anteriormente, esto no debería ser necesario, a menos que la llamada que se está probando deba ejecutarse en el subproceso de la interfaz de usuario.

El otro idioma que uso ahora es probar si el subproceso actual es el subproceso de la interfaz de usuario y luego ejecutar la acción solicitada en el subproceso apropiado independientemente. A veces, tiene sentido permitir que la persona que llama solicite sincronización frente a asíncrono, anulando cuando sea necesario. Por ejemplo, las solicitudes de red siempre deben ejecutarse en un subproceso en segundo plano. En la mayoría de los casos, la agrupación de hebras AsyncTask es perfecta para esto. Solo tenga en cuenta que solo un cierto número se ejecutará a la vez, bloqueando solicitudes adicionales. Para probar si el hilo actual es el hilo de la interfaz de usuario:

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

Luego use una subclase simple (solo se necesitan DoInBackground () y onPostExecute ()) de AsyncTask <> para ejecutar en un subproceso que no sea UI o handler.post () o postDelayed () para ejecutar en el subproceso UI.

Le da al interlocutor la opción de ejecutar sync o async (obteniendo un valor onUiThread válido localmente que no se muestra aquí; agregue valores booleanos locales como se indica arriba):

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 de AsyncTask puede ser muy simple y conciso. Use la definición de RunAsyncTask.java a continuación, luego escriba el código de esta manera:

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: new 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(); } }


Tengo el mismo problema, parece ocurrir cuando se ejecuta AsyncTask durante una suspensión / reanudación.

EDIT: Sí, no creía que lo tuviera, pero utilicé este http://developer.android.com/guide/appendix/faq/commontasks.html#threading para iniciar siempre la AsyncTask en el subproceso de la interfaz de usuario y el problema ha desaparecido. El problema apareció después de que agregué la función de licencia, siggghhhhh

Gracias


Tuve el mismo problema en un dispositivo con Android 4.0.4 con IntentService y lo resolví como sdw dijo con Class.forName ("android.os.AsyncTask"). Lo mismo no sucedió en Android 4.1.2, 4.4.4 o 5.0. Me pregunto si este Google resolvió el problema de Martin West a partir de 2011.

Agregué 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 necesita cambiarse a otra cosa.