onprogressupdate oncancelled example ejemplo asynctask android concurrency handler android-asynctask

android - oncancelled - ¿AsyncTask realmente es conceptualmente defectuoso o simplemente me estoy perdiendo algo?



oncancelled asynctask android (12)

He investigado este problema desde hace meses, se me ocurrieron diferentes soluciones, con lo que no estoy contento, ya que todos son ataques masivos. Todavía no puedo creer que una clase con un diseño defectuoso haya llegado al marco y que nadie esté hablando de eso, así que supongo que debo estar perdiendo algo.

El problema es con AsyncTask . Según la documentación que

"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 luego continúa mostrando cómo se llama a onPostExecute() método showDialog() ejemplar en onPostExecute() . Esto, sin embargo, me parece totalmente ideado , porque mostrar un diálogo siempre necesita una referencia a un Context válido, y una AsyncTask nunca debe contener una referencia fuerte a un objeto de contexto .

La razón es obvia: ¿qué sucede si se destruye la actividad que desencadenó la tarea? Esto puede suceder todo el tiempo, por ejemplo, porque volteaste la pantalla. Si la tarea mantendría una referencia al contexto que lo creó, no solo se aferrará a un objeto de contexto inútil (la ventana se habrá destruido y cualquier interacción de la interfaz de usuario fallará con una excepción), incluso se corre el riesgo de crear una 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 subproceso 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 las instancias de contexto a una AsyncTask, sino a una instancia de Handler . Eso funciona: ya que un controlador vincula 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 manejadores, es incorrecta. También parece abusar del controlador, 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, todavía tiene el problema de que cuando el contexto se destruye, no tiene registro de las tareas que disparó. Eso significa que tiene que reiniciar cualquier tarea al recrear 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 implementó en la biblioteca Droid-Fu ) es mantener un mapeo de WeakReference s desde 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 significativo de IU allí. Tampoco se filtra, 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 compleja y requiere subclasificar algunas de las clases de la biblioteca de Droid-Fu, haciendo de este un enfoque bastante intrusivo.

Ahora simplemente quiero saber: ¿Me estoy perdiendo algo de forma masiva o AsyncTask está completamente equivocado? ¿Cómo están trabajando tus experiencias con eso? ¿Cómo resolviste estos problemas?

Gracias por tu contribución.


La razón es obvia: ¿qué sucede si se destruye la actividad que desencadenó la tarea?

AsyncTask manualmente la actividad de AsyncTask en onDestroy() . AsyncTask a onCreate() manualmente la nueva actividad a la AsyncTask en onCreate() . Esto requiere una clase interna estática o una clase estándar de Java, más quizás 10 líneas de código.


¿Por qué no anular el método onPause() en la Actividad propietaria y cancelar la AsyncTask desde allí?


En cuanto a las "experiencias de trabajo con él": es possible finalizar el proceso junto con todas las Tareas Asíncronas, Android volverá a crear la pila de actividades para que el usuario no mencione nada.


No estoy seguro de que sea cierto que corre el riesgo de una pérdida de memoria con una referencia a un contexto desde una AsyncTask.

La forma habitual de implementarlas 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 se complete la AsyncTask, ¿no será inalcanzable y luego elegible para la recolección de basura? Así que la referencia a la actividad no importará porque la AsyncTask en sí no se quedará.


Parece que AsyncTask es un poco más que un concepto defectuoso . También es inutilizable por problemas de compatibilidad. Los documentos de Android leen:

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 permitiendo que múltiples tareas funcionen en paralelo. Al iniciar HONEYCOMB, las tareas vuelven a ejecutarse en un solo hilo para evitar errores comunes de aplicación causados ​​por la ejecución paralela. Si realmente desea una ejecución paralela, puede usar la executeOnExecutor(Executor, Params...) de este método con THREAD_POOL_EXECUTOR ; sin embargo, vea el comentario allí para advertencias sobre su uso.

Tanto executeOnExecutor() como THREAD_POOL_EXECUTOR se agregan en el nivel de API 11 (Android 3.0.x, HONEYCOMB).

Esto significa que si creas dos AsyncTask s para descargar dos archivos, la segunda descarga no comenzará hasta que la primera termine. Si chatea a través de dos servidores y el primer servidor está inactivo, no se conectará con el segundo antes de que se agote el primero. (Por supuesto, a menos que use las nuevas características API11, pero esto hará que su código sea incompatible con 2.x).

Y si quieres apuntar a 2.x y 3.0+, las cosas se vuelven realmente difíciles.

Además, los docs dicen:

Precaución: otro problema que puede encontrar al utilizar un subproceso de trabajo es reinicios inesperados en 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), que puede destruir su subproceso de trabajador . 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 de Estantes.



Personalmente, simplemente extiendo Thread y uso una interfaz de devolución de llamada para actualizar la interfaz de usuario. Nunca podría lograr que AsyncTask funcione correctamente sin problemas de FC. También utilizo una cola de no bloqueo para administrar el grupo de ejecución.


Probablemente todos, incluido Google, estamos haciendo AsyncTask mal uso de 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, las AsyncTasks deben usarse desde el Modelo , 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 , no suele programar clases derivadas de, por ejemplo, android.widget.Button, pero puede hacerlo. Por lo general, lo único que hace acerca de 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 publicitarse como una solución rápida y fácil.


Qué tal algo como esto:

class MyActivity extends Activity { Worker mWorker; static class Worker extends AsyncTask<URL, Integer, Long> { MyActivity mActivity; Worker(MyActivity activity) { mActivity = activity; } @Override protected Long doInBackground(URL... urls) { int count = urls.length; long totalSize = 0; for (int i = 0; i < count; i++) { totalSize += Downloader.downloadFile(urls[i]); publishProgress((int) ((i / (float) count) * 100)); } return totalSize; } @Override protected void onProgressUpdate(Integer... progress) { if (mActivity != null) { mActivity.setProgressPercent(progress[0]); } } @Override protected void onPostExecute(Long result) { if (mActivity != null) { mActivity.showDialog("Downloaded " + result + " bytes"); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mWorker = (Worker)getLastNonConfigurationInstance(); if (mWorker != null) { mWorker.mActivity = this; } ... } @Override public Object onRetainNonConfigurationInstance() { return mWorker; } @Override protected void onDestroy() { super.onDestroy(); if (mWorker != null) { mWorker.mActivity = null; } } void startWork() { mWorker = new Worker(this); mWorker.execute(...); } }


Sería más robusto mantener una WeekReference sobre su actividad:

public class WeakReferenceAsyncTaskTestActivity extends Activity { private static final int MAX_COUNT = 100; private ProgressBar progressBar; private AsyncTaskCounter mWorker; @SuppressWarnings("deprecation") @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_async_task_test); mWorker = (AsyncTaskCounter) getLastNonConfigurationInstance(); if (mWorker != null) { mWorker.mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(this); } progressBar = (ProgressBar) findViewById(R.id.progressBar1); progressBar.setMax(MAX_COUNT); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_async_task_test, menu); return true; } public void onStartButtonClick(View v) { startWork(); } @Override public Object onRetainNonConfigurationInstance() { return mWorker; } @Override protected void onDestroy() { super.onDestroy(); if (mWorker != null) { mWorker.mActivity = null; } } void startWork() { mWorker = new AsyncTaskCounter(this); mWorker.execute(); } static class AsyncTaskCounter extends AsyncTask<Void, Integer, Void> { WeakReference<WeakReferenceAsyncTaskTestActivity> mActivity; AsyncTaskCounter(WeakReferenceAsyncTaskTestActivity activity) { mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(activity); } private static final int SLEEP_TIME = 200; @Override protected Void doInBackground(Void... params) { for (int i = 0; i < MAX_COUNT; i++) { try { Thread.sleep(SLEEP_TIME); } catch (InterruptedException e) { e.printStackTrace(); } Log.d(getClass().getSimpleName(), "Progress value is " + i); Log.d(getClass().getSimpleName(), "getActivity is " + mActivity); Log.d(getClass().getSimpleName(), "this is " + this); publishProgress(i); } return null; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); if (mActivity != null) { mActivity.get().progressBar.setProgress(values[0]); } } } }


Sería mejor que piense en AsyncTask como algo que está más estrechamente relacionado con una Actividad, Contexto, ContextWrapper, etc. Es más conveniente cuando se comprende completamente su alcance.

Asegúrese de tener una política de cancelación en su ciclo de vida para que finalmente se recoja la basura y ya no mantenga una referencia a su actividad y también se pueda recoger la basura.

Sin cancelar su AsyncTask mientras se aleja de su Contexto, se encontrará con fugas de memoria y NullPointerExceptions, si simplemente necesita proporcionar retroalimentación como un cuadro de diálogo Toast a simple, entonces una combinación 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 en marcha que puede llevar a algunos escollos imprevistos.


Tiene toda la razón: es por eso que un movimiento para dejar de usar tareas / cargadores asíncronos en las actividades para obtener datos está cobrando impulso. Una de las nuevas formas es usar un marco de Volley que esencialmente proporciona una devolución de llamada una vez que los datos están listos, mucho más coherente con el modelo MVC. Volley fue popularizado en Google I / O 2013. No estoy seguro de por qué más personas no están conscientes de esto.