Android AsyncTask para operaciones de larga duración

90

Citando la documentación para AsyncTask que se encuentra aquí , dice:

Lo ideal es que AsyncTasks se utilice para operaciones cortas (unos pocos segundos como máximo). Si necesita mantener los subprocesos en ejecución durante largos períodos de tiempo, se recomienda encarecidamente que utilice las diversas API proporcionadas por el paquete java.util.concurrent, como Executor, ThreadPoolExecutor y FutureTask.

Ahora surge mi pregunta: ¿por qué? La doInBackground()función se ejecuta fuera del hilo de la interfaz de usuario, entonces, ¿qué daño hay al tener una operación de larga duración aquí?

usuario1730789
fuente
2
El problema que enfrenté al implementar una aplicación (que usa asyncTask) en un dispositivo real fue que la función de larga duración doInBackgroundcongela la pantalla si no se usa una barra de progreso.
venkatKA
2
Debido a que una AsyncTask está vinculada a la Actividad en la que se inicia. Por lo tanto, si se elimina la Actividad, su instancia de AsyncTask también se puede eliminar.
IgorGanapolsky
¿Qué sucede si creo AsyncTask dentro de un servicio? ¿No resolvería eso el problema?
eddy
1
Utilice IntentService, la solución perfecta para ejecutar operaciones largas en segundo plano.
Keyur Thumar

Respuestas:

120

Es una muy buena pregunta, se necesita tiempo como programador de Android para comprender completamente el problema. De hecho, AsyncTask tiene dos problemas principales relacionados:

  • Están mal vinculados al ciclo de vida de la actividad.
  • Crean pérdidas de memoria muy fácilmente.

Dentro de la aplicación RoboSpice Motivations ( disponible en Google Play ) respondemos esa pregunta en detalle. Le dará una vista en profundidad de AsyncTasks, Loaders, sus características e inconvenientes y también le presentará una solución alternativa para solicitudes de red: RoboSpice. Las solicitudes de red son un requisito común en Android y, por naturaleza, son operaciones de larga duración. Aquí hay un extracto de la aplicación:

El ciclo de vida de AsyncTask y Activity

AsyncTasks no sigue el ciclo de vida de las instancias de actividad. Si inicia una AsyncTask dentro de una actividad y gira el dispositivo, la actividad se destruirá y se creará una nueva instancia. Pero AsyncTask no morirá. Seguirá viviendo hasta que se complete.

Y cuando se complete, AsyncTask no actualizará la interfaz de usuario de la nueva actividad. De hecho, actualiza la instancia anterior de la actividad que ya no se muestra. Esto puede llevar a una excepción del tipo java.lang.IllegalArgumentException: Vista no adjunta al administrador de ventanas si usa, por ejemplo, findViewById para recuperar una vista dentro de la Actividad.

Problema de pérdida de memoria

Es muy conveniente crear AsyncTasks como clases internas de sus actividades. Como AsyncTask necesitará manipular las vistas de la Actividad cuando la tarea esté completa o en progreso, el uso de una clase interna de la Actividad parece conveniente: las clases internas pueden acceder directamente a cualquier campo de la clase externa.

Sin embargo, significa que la clase interna tendrá una referencia invisible en su instancia de clase externa: la Actividad.

A largo plazo, esto produce una pérdida de memoria: si AsyncTask dura mucho, mantiene la actividad "viva" mientras que a Android le gustaría deshacerse de ella porque ya no se puede mostrar. La actividad no puede recolectarse basura y ese es un mecanismo central para que Android preserve los recursos en el dispositivo.


Realmente es una muy muy mala idea usar AsyncTasks para operaciones de larga ejecución. Sin embargo, están bien para los de vida corta, como actualizar una Vista después de 1 o 2 segundos.

Te animo a que descargues la aplicación RoboSpice Motivations , que realmente explica esto en profundidad y proporciona muestras y demostraciones de las diferentes formas de realizar algunas operaciones en segundo plano.

Snicolas
fuente
@Snicolas Hola. Tengo una aplicación en la que escanea datos de una etiqueta NFC y los envía al servidor. Funciona bien en áreas de buena señal pero donde no hay señal, la AsyncTask que hace que la llamada web siga funcionando. por ejemplo, el cuadro de diálogo de progreso se ejecuta durante minutos y luego, cuando desaparece, la pantalla se vuelve negra y no responde. Mi AsyncTask es una clase interna. Estoy escribiendo un controlador para cancelar la tarea después de X segundos. La aplicación parece enviar datos antiguos al servidor horas después del escaneo. ¿Podría esto deberse a que AsyncTask no finaliza y tal vez se completa horas después? Agradecería cualquier idea. gracias
turtleboy
Rastrea para ver qué pasa. Pero sí, ¡eso es bastante posible! Si desinfecta bien su asynctask, puede cancelarlo correctamente, ese sería un buen punto de partida si no desea migrar a RS o un servicio ...
Snicolas
@Snicolas Gracias por la respuesta. Hice una publicación en SO ayer describiendo mi problema y mostrando el código del controlador que escribí para intentar detener AsyncTask después de 8 segundos. ¿Le importaría echar un vistazo, si tiene tiempo? ¿Llamar a AsyncTask.cancel (true) desde un controlador cancelará la tarea correctamente? Sé que debería verificar periódicamente el valor de iscancelled () en mi doInBackgroud, pero no creo que se aplique a mis circunstancias, ya que solo estoy haciendo una llamada web de una línea HttpPost y no estoy publicando actualizaciones en la interfaz de usuario. ¿Hay alternativas a AsyncTask por ejemplo, ¿es posible hacer un HttPost desde un IntentService?
turtleboy
aquí está el enlace stackoverflow.com/questions/17725767/…
turtleboy
Deberías probar RoboSpice (en github). ;)
Snicolas
38

por qué ?

Porque AsyncTask, de forma predeterminada, usa un grupo de subprocesos que usted no creó . Nunca inmovilices recursos de un grupo que no hayas creado, ya que no sabes cuáles son los requisitos de ese grupo. Y nunca inmovilice recursos de un grupo que usted no creó si la documentación para ese grupo le indica que no lo haga, como es el caso aquí.

En particular, a partir de Android 3.2, el grupo de subprocesos que se usa de AsyncTaskforma predeterminada (para las aplicaciones con android:targetSdkVersion13 o superior) tiene solo un subproceso; si ata este subproceso indefinidamente, ninguna de sus otras tareas se ejecutará.

CommonsWare
fuente
Gracias por esta explicación ... No pude encontrar nada realmente defectuoso sobre el uso de AsyncTasks para operaciones de larga duración (aunque generalmente las delego a Servicios yo mismo), pero su argumento sobre la vinculación de ThreadPool (para lo cual de hecho puede no haga suposiciones sobre su tamaño) parece acertado. Como complemento a la publicación de Anup sobre el uso de un servicio: ese servicio también debería ejecutar la tarea en un hilo en segundo plano y no bloquear su propio hilo principal. Una opción podría ser IntentService o, para requisitos de concurrencia más complejos, aplicar su propia estrategia de subprocesos múltiples.
baske el
¿Es lo mismo incluso si inicio AsyncTask dentro de un servicio? Quiero decir, ¿seguiría siendo un problema la operación de larga duración?
eddy
1
@eddy: Sí, ya que eso no cambia la naturaleza del grupo de subprocesos. Para a Service, simplemente use a Threado a ThreadPoolExecutor.
CommonsWare
Gracias @CommonsWare solo la última pregunta, ¿TimerTasks comparte las mismas desventajas de AsyncTasks? ¿O es algo completamente diferente?
eddy
1
@eddy: TimerTaskes de Java estándar, no de Android. TimerTaskse ha abandonado en gran medida a favor de ScheduledExecutorService(que, a pesar de su nombre, forma parte del estándar Java). Ninguno de los dos está vinculado a Android, por lo que aún necesita un servicio si espera que esas cosas se ejecuten en segundo plano. Y, realmente debería considerar AlarmManager, desde Android, por lo que no necesita un servicio merodeando solo mirando el tictac del reloj.
CommonsWare
4

Las tareas de Aysnc son subprocesos especializados que aún están destinados a ser utilizados con la GUI de su aplicación, pero al mismo tiempo mantienen las tareas de la interfaz de usuario que requieren muchos recursos. Entonces, cuando cosas como actualizar listas, cambiar sus vistas, etc.requieran que realice algunas operaciones de recuperación o actualización, debe usar tareas asíncronas para poder mantener estas operaciones fuera del hilo de la interfaz de usuario, pero tenga en cuenta que estas operaciones todavía están conectadas a la interfaz de usuario de alguna manera .

Para las tareas de ejecución más prolongada, que no requieren la actualización de la interfaz de usuario, puede utilizar los servicios en su lugar porque pueden vivir incluso sin una interfaz de usuario.

Por lo tanto, para tareas cortas, use tareas asíncronas porque el sistema operativo puede matarlas después de que su actividad de desove muera (generalmente no morirá en medio de la operación, pero completará su tarea). Y para tareas largas y repetitivas, utilice los servicios en su lugar.

para obtener más información, consulte los hilos:

¿AsyncTask durante más de unos segundos?

y

AsyncTask no se detendrá incluso cuando la actividad se haya destruido

Anup Cowkur
fuente
1

El problema con AsyncTask es que si se define como una clase interna no estática de la actividad, tendrá una referencia a la actividad. En el escenario donde la actividad finaliza el contenedor de la tarea asíncrona, pero el trabajo en segundo plano en AsyncTask continúa, el objeto de actividad no se recolectará como basura ya que hay una referencia a él, esto causa la pérdida de memoria.

La solución para solucionar esto es definir la tarea asíncrona como una clase de actividad interna estática y usar una referencia débil al contexto.

Pero aún así, es una buena idea usarlo para tareas en segundo plano simples y rápidas. Para desarrollar una aplicación con código limpio, es mejor usar RxJava para ejecutar tareas complejas en segundo plano y actualizar la interfaz de usuario con los resultados.

Arnav Rao
fuente