Patrón de WeakReference / AsyncTask en android

Tengo una pregunta sobre esta simple situación que ocurre con frecuencia en android.

Tenemos una actividad principal, invocamos una AsyncTask junto con la referencia de la actividad main, de modo que el AsyncTask puede actualizar las vistas en MainActivity.

Voy a dividir el evento en pasos

  • MainActivity crea una AyncTask, pasa su referencia a ella.
  • AysncTask, inicia su trabajo, descargando diez archivos por ejemplo
  • El usuario cambió la orientación del dispositivo. Esto resulta en un puntero huérfano en el AsyncTask
  • Cuando el AsyncTask se completa, e intenta acceder a la actividad para actualizar el estado, se bloquea, debido al puntero nulo.

La solución para lo anterior es mantener una WeakReference en el AsyncTask según lo recomendado por el libro "Pro Android 4"

WeakReference<Activity> weakActivity; in method onPostExecute Activity activity = weakActivity.get(); if (activity != null) { // do your stuff with activity here } 

¿Cómo soluciona esto la situación?

Mi pregunta es, si mi asynctask está descargando diez archivos, y al completar 5 la actividad se reinicia (debido a un cambio de orientación) entonces mi FileDownloadingTask será invocado una vez más?

¿Qué pasaría con el AsyncTask anterior que se invocó inicialmente?

Gracias, y me disculpo por la duración de la pregunta.

¿Cómo soluciona esto la situación?

La WeakReference permite que la Activity sea ​​recolectada de basura, por lo que no tiene una pérdida de memoria.

Una referencia nula significa que el AsyncTask no puede intentar ciegamente actualizar una interfaz de usuario que ya no está conectada, lo que arrojaría excepciones (por ejemplo, la vista no se adjunta al gestor de ventanas). Por supuesto usted tiene que comprobar para null para evitar NPE.

Si mi asynctask está descargando diez archivos, y al completar 5 la actividad se reinicia (debido a un cambio de orientación) entonces mi FileDownloadingTask será invocado una vez más?

Depende de su implementación, pero probablemente sí – si no deliberadamente hacer algo para hacer una repetición de descarga innecesaria, como el almacenamiento en caché de los resultados en alguna parte.

¿Qué pasaría con el AsyncTask anterior que se invocó inicialmente?

En las versiones anteriores de Android se ejecutaría a la finalización, descargando todos los archivos sólo para desecharlos (o tal vez en caché, dependiendo de su implementación).

En los nuevos Android siento sospechas de que AsyncTask están siendo asesinados junto con la Activity que los inició, pero mi base para la sospecha es sólo que la demostración de fugas de memoria para RoboSpice (ver abajo) no se escape realmente en mis dispositivos JellyBean.

Si puedo ofrecer algunos consejos: AsyncTask no es adecuado para realizar tareas potencialmente largas como la creación de redes.

IntentService es un IntentService mejor (y aún relativamente simple), si un solo hilo de trabajo es aceptable para usted. Utilice un servicio (local) si desea control sobre el hilo de la piscina – y tenga cuidado de no hacer el trabajo en el hilo principal!

RoboSpice parece bueno si usted está buscando una manera confiable de realizar redes en segundo plano (renuncia: no lo he probado, no estoy afiliado). Hay una aplicación de demostración RoboSpice Motivations en la tienda de juegos que explica por qué debería usarla demostrando todas las cosas que pueden fallar con AsyncTask , incluida la solución de WeakReference.

Vea también este hilo: ¿Es AsyncTask realmente conceptualmente defectuoso o simplemente estoy perdiendo algo?

Actualizar:

He creado un proyecto github con un ejemplo de descarga utilizando IntentService para otra pregunta SO ( Cómo arreglar android.os.NetworkOnMainThreadException? ), Pero también es relevante aquí, creo. Tiene la ventaja añadida de que, al devolver el resultado a través de onActivityResult , una descarga que está en vuelo al girar el dispositivo se entregará a la Activity reiniciada.

La clase WeakReference simplemente evita que el JRE aumente el contador de referencia para la instancia dada.

No entraré en la gestión de memoria de Java y contestaré directamente a tu pregunta: El WeakReference resuelve la situación proporcionando al AsyncTask una forma de saber si su actividad principal sigue siendo válida.

El cambio de orientación en sí no reiniciará automáticamente AsyncTask . Tienes que codificar el comportamiento deseado con los mecanismos conocidos ( onCreate / onDestroy , onSave/RestoreInstanceState ).

Con respecto a AsyncTask original, no estoy 100% seguro de cuál de estas opciones va a suceder:

  • O bien Java detiene el hilo y dispone de AsyncTask , porque el único objeto que contiene una referencia a él (la Activity original) se destruye
  • O algún objeto Java interno mantiene una referencia al objeto AsyncTask , bloqueando su colección de elementos no utilizados, dejando efectivamente el AsyncTask para finalizar en segundo plano

De cualquier manera, sería una buena práctica abortar / pausar y reiniciar / reanudar el AsyncTask manualmente (o entregarlo a la nueva Activity ), o usar un Service lugar.

¿Cómo soluciona esto la situación?

No lo hace.

El referente de una WeakReference se establece en null cuando el recolector de basura determina que el referente es débilmente accesible. Esto no sucede cuando una actividad se detiene, y no necesariamente ocurre inmediatamente cuando la actividad es destruida y el marco descarta todas las referencias a ella. Si el GC no se ha ejecutado, es completamente posible que el AsyncTask complete mientras que su WeakReference todavía contiene una referencia a una actividad muerta.

No sólo eso, pero este enfoque no hace nada para evitar que el AsyncTask inútilmente consume CPU.

Un mejor enfoque es hacer que la Activity mantenga una fuerte referencia a AsyncTask y cancel(...) en el método apropiado de ciclo de vida del desmontaje. El AsyncTask debe supervisar isCancelled() y dejar de funcionar si ya no es necesario.

Si desea que un AsyncTask sobreviva a través de los cambios de configuración (pero no otras formas de destrucción de la actividad) puede alojarla en un fragmento retenido.

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.