He investigado este problema durante meses, se me ocurrieron diferentes soluciones, que no estoy contento, ya que todos son hacks masivos. Todavía no puedo creer que una clase con un diseño defectuoso haya llegado al marco y nadie esté hablando de eso, así que supongo que me falta algo.
El problema es con AsyncTask
. De acuerdo con la documentación
"permite realizar operaciones en segundo plano y publicar resultados en el subproceso de la interfaz de usuario sin tener que manipular subprocesos y / o controladores".
El ejemplo continúa mostrando cómo showDialog()
se llama a algún método ejemplar onPostExecute()
. Esto, sin embargo, me parece totalmente artificial , porque mostrar un diálogo siempre necesita una referencia a una válida Context
, y una AsyncTask nunca debe contener una referencia fuerte a un objeto de contexto .
La razón es obvia: ¿qué pasa si la actividad se destruye, lo que desencadenó la tarea? Esto puede suceder todo el tiempo, por ejemplo, porque volteó la pantalla. Si la tarea tuviera una referencia al contexto que la creó, no solo se está aferrando a un objeto de contexto inútil (la ventana se habrá destruido y cualquier interacción de la IU fallará con una excepción), incluso se arriesga a crear un pérdida de memoria.
A menos que mi lógica sea defectuosa aquí, esto se traduce en: onPostExecute()
es completamente inútil, porque ¿de qué sirve que este método se ejecute en el hilo de la interfaz de usuario si no tiene acceso a ningún contexto? No puedes hacer nada significativo aquí.
Una solución alternativa sería no pasar instancias de contexto a una AsyncTask, sino una Handler
instancia. Eso funciona: dado que un controlador vincula libremente el contexto y la tarea, puede intercambiar mensajes entre ellos sin correr el riesgo de una fuga (¿verdad?). Pero eso significaría que la premisa de AsyncTask, es decir, que no necesita molestarse con los controladores, está mal. También parece abusar de Handler, ya que está enviando y recibiendo mensajes en el mismo hilo (lo crea en el hilo de la interfaz de usuario y lo envía a través de onPostExecute (), que también se ejecuta en el hilo de la interfaz de usuario).
Para colmo, incluso con esa solución alternativa, todavía tiene el problema de que cuando se destruye el contexto, no tiene registro de las tareas que realizó. Eso significa que debe reiniciar cualquier tarea al volver a crear el contexto, por ejemplo, después de un cambio de orientación de la pantalla. Esto es lento y derrochador.
Mi solución a esto (como se implementa en la biblioteca Droid-Fu ) es mantener una asignación de WeakReference
s de los nombres de los componentes a sus instancias actuales en el objeto de aplicación único. Cada vez que se inicia una AsyncTask, registra el contexto de llamada en ese mapa, y en cada devolución de llamada, obtendrá la instancia de contexto actual de esa asignación. Esto garantiza que nunca hará referencia a una instancia de contexto obsoleto y que siempre tendrá acceso a un contexto válido en las devoluciones de llamada para que pueda realizar un trabajo de interfaz de usuario significativo allí. Tampoco tiene fugas, porque las referencias son débiles y se borran cuando ya no existe una instancia de un componente dado.
Aún así, es una solución alternativa compleja y requiere subclasificar algunas de las clases de la biblioteca Droid-Fu, lo que lo convierte en un enfoque bastante intrusivo.
Ahora simplemente quiero saber: ¿Me estoy perdiendo algo masivamente o AsyncTask es realmente completamente defectuoso? ¿Cómo están tus experiencias trabajando con él? ¿Cómo resolviste este problema?
Gracias por tu contribución.
Respuestas:
Qué tal algo como esto:
fuente
Desasociar manualmente la actividad desde
AsyncTask
adentroonDestroy()
. Manualmente volver a asociar la nueva actividad a laAsyncTask
deonCreate()
. Esto requiere una clase interna estática o una clase estándar de Java, más quizás 10 líneas de código.fuente
Parece que
AsyncTask
es un poco más que un defecto conceptual . También es inutilizable por problemas de compatibilidad. Los documentos de Android dicen:Cuando se introdujo por primera vez, las AsyncTasks se ejecutaron en serie en un solo hilo de fondo. Comenzando con DONUT, esto se cambió a un grupo de subprocesos que permite que varias tareas funcionen en paralelo. Al iniciar HONEYCOMB, las tareas vuelven a ejecutarse en un solo subproceso para evitar errores comunes de la aplicación causados por la ejecución paralela. Si realmente desea una ejecución paralela, puede usar la
executeOnExecutor(Executor, Params...)
versión de este método conTHREAD_POOL_EXECUTOR
; sin embargo, vea comentarios allí para advertencias sobre su uso.Ambos
executeOnExecutor()
yTHREAD_POOL_EXECUTOR
se agregan en API nivel 11 (Android 3.0.x, HONEYCOMB).Esto significa que si crea dos
AsyncTask
segundos para descargar dos archivos, la segunda descarga no comenzará hasta que finalice la primera. Si chatea a través de dos servidores, y el primer servidor está inactivo, no se conectará al segundo antes de que se agote la conexión con el primero. (A menos que utilice las nuevas características API11, por supuesto, pero esto hará que su código sea incompatible con 2.x).Y si desea apuntar a 2.xy 3.0+, las cosas se vuelven realmente complicadas.
Además, los documentos dicen:
Precaución: Otro problema que puede encontrar al usar un subproceso de trabajo es el reinicio inesperado de su actividad debido a un cambio en la configuración del tiempo de ejecución (como cuando el usuario cambia la orientación de la pantalla), lo que puede destruir su subproceso de trabajo . Para ver cómo puede persistir su tarea durante uno de estos reinicios y cómo cancelar adecuadamente la tarea cuando se destruye la actividad, consulte el código fuente de la aplicación de ejemplo Shelves.
fuente
Probablemente todos, incluido Google, estamos haciendo un mal uso
AsyncTask
desde el punto de vista de MVC .Una actividad es un controlador , y el controlador no debe iniciar operaciones que puedan sobrevivir a la vista . Es decir, AsyncTasks debe usarse desde Model , desde una clase que no está vinculada al ciclo de vida de la Actividad; recuerde que las Actividades se destruyen en la rotación. (En cuanto a la Vista , generalmente no se programan clases derivadas de, por ejemplo, android.widget.Button, pero se puede. Por lo general, lo único que se hace con respecto a la Vista es el xml).
En otras palabras, es incorrecto colocar derivados de AsyncTask en los métodos de Actividades. OTOH, si no debemos usar AsyncTasks en Actividades, AsyncTask pierde su atractivo: solía anunciarse como una solución rápida y fácil.
fuente
No estoy seguro de que sea cierto que corras el riesgo de una pérdida de memoria con una referencia a un contexto de una AsyncTask.
La forma habitual de implementarlos es crear una nueva instancia de AsyncTask dentro del alcance de uno de los métodos de la Actividad. Entonces, si la actividad se destruye, una vez que la AsyncTask se complete, ¿no será inalcanzable y elegible para la recolección de basura? Por lo tanto, la referencia a la actividad no importará porque AsyncTask en sí no se quedará.
fuente
Sería más robusto mantener una WeekReference sobre su actividad:
fuente
¿Por qué no simplemente anular el
onPause()
método en la Actividad propietaria y cancelarloAsyncTask
desde allí?fuente
onPause
porque es tan malo como sostenerlo en otro lugar. Es decir, ¿podrías obtener un ANR?onPause
o otra cosa) porque corremos el riesgo de obtener un ANR.Tiene toda la razón: es por eso que un movimiento para evitar el uso de tareas / cargadores asíncronos en las actividades para obtener datos está ganando impulso. Una de las nuevas formas es utilizar un marco de Volley que esencialmente proporciona una devolución de llamada una vez que los datos están listos, mucho más consistente con el modelo MVC. Volley apareció en Google I / O 2013. No estoy seguro de por qué más personas no lo saben.
fuente
Personalmente, solo extiendo Thread y uso una interfaz de devolución de llamada para actualizar la interfaz de usuario. Nunca podría hacer que AsyncTask funcione correctamente sin problemas de FC. También uso una cola sin bloqueo para administrar el grupo de ejecución.
fuente
Pensé que cancelar funciona pero no lo hace.
aquí ellos RTFMing al respecto:
"" Si la tarea ya se ha iniciado, el parámetro mayInterruptIfRunning determina si el subproceso que ejecuta esta tarea debe interrumpirse en un intento de detener la tarea ".
Sin embargo, eso no implica que el hilo sea interrumpible. Eso es una cosa de Java, no una cosa de AsyncTask ".
http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d
fuente
Sería mejor que pienses en AsyncTask como algo que está más estrechamente vinculado con una Actividad, Contexto, ContextWrapper, etc. Es más conveniente cuando su alcance se entiende completamente.
Asegúrese de tener una política de cancelación en su ciclo de vida para que eventualmente se recolecte basura y ya no guarde una referencia a su actividad y también se pueda recolectar basura.
Sin cancelar su AsyncTask mientras se aleja de su Contexto, se encontrará con pérdidas de memoria y NullPointerExceptions, si simplemente necesita proporcionar comentarios como un brindis por un diálogo simple, un solo tono de su Contexto de aplicación ayudaría a evitar el problema de NPE.
AsyncTask no es del todo malo, pero definitivamente hay mucha magia que puede conducir a algunos escollos imprevistos.
fuente
En cuanto a "experiencias de trabajo con él": es posible para matar el proceso , junto con todos los AsyncTasks, Android volverá a crear la pila de actividad para que el usuario no se menciona nada.
fuente