Join FlipAndroid.COM Telegram Group: https://t.me/joinchat/F_aqThGkhwcLzmI49vKAiw


¿AsyncTask es realmente falto conceptualmente o simplemente estoy perdiendo algo?

He investigado este problema desde hace meses, surgió con diferentes soluciones a la misma, que no estoy contento con ya que son todos los hacks masivos. Todavía no puedo creer que una clase que falla en el diseño lo hizo en el marco y nadie está hablando de ello, así que supongo que sólo debe estar perdiendo algo.

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

  • ¿Cómo puedo depurar el código doInBackground de un AsyncTask
  • ¿Cuál es la mejor manera de obtener / usar Contexto dentro de AsyncTask?
  • Cómo obtener una cadena de AsyncTask?
  • "Permite realizar operaciones en segundo plano y publicar resultados en el subproceso de interfaz de usuario sin tener que manipular subprocesos y / o controladores".

    El ejemplo continúa mostrando cómo se llama un ejemplo de método onPostExecute() en onPostExecute() . Esto, sin embargo, me parece totalmente artificial , porque mostrar un diálogo siempre necesita una referencia a un Context válido, y un AsyncTask nunca debe tener 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 se volteó la pantalla. Si la tarea llevara a cabo una referencia al contexto que lo creó, no sólo se aferra a un objeto de contexto inútil (la ventana se habrá destruido y cualquier interacción de interfaz de usuario fallará con una excepción!), Incluso se arriesga a crear un pérdida de memoria.

    A menos que mi lógica está defectuosa aquí, esto se traduce en: onPostExecute() es totalmente inútil, ¿para qué sirve este método para ejecutarse en el hilo de interfaz de usuario si no tiene acceso a ningún contexto? No puedes hacer nada significativo aquí.

    Una solución sería no pasar instancias de contexto a AsyncTask, sino una instancia de Handler . Eso funciona: ya que un manejador vincula ligeramente 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 usted no necesita molestarse con los manejadores, está mal. También parece abusar de Handler, ya que está enviando y recibiendo mensajes en el mismo subproceso (lo creas en el subproceso de UI y lo envías a través de onPostExecute () que también se ejecuta en el subproceso de UI).

    Para colmo de todo, incluso con esa solución, todavía tiene el problema de que cuando el contexto se destruye, no tiene ningún registro de las tareas que se disparó. Esto significa que tiene que volver a iniciar cualquier tarea al volver a crear el contexto, por ejemplo, después de un cambio de orientación de pantalla. Esto es lento y derrochador.

    Mi solución a esto (como implementado en la biblioteca Droid-Fu ) es mantener una asignación de WeakReference s de nombres de componentes a sus instancias actuales en el objeto de aplicación único. Cada vez que se inicia un AsyncTask, registra el contexto de llamada en ese mapa, y en cada devolución de llamada, buscará la instancia de contexto actual de esa asignación. Esto asegura que nunca hará referencia a una instancia de contexto obsoleto y 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í. También no se filtra porque las referencias son débiles y se borran cuando ya no existe ninguna instancia de un componente dado.

    Sin embargo, es una solución compleja y requiere subclase algunas de las clases de biblioteca Droid-Fu, lo que hace que este enfoque sea muy intrusivo.

    Ahora simplemente quiero saber: ¿Estoy simplemente perdiendo algo masivamente o AsyncTask realmente defectuoso? ¿Cómo son sus experiencias trabajando con él? ¿Cómo solucionaste estos problemas?

    Gracias por tu aportación.

  • ¿Cómo puedo depurar el código doInBackground de un AsyncTask
  • ¿Cuál es la mejor manera de obtener / usar Contexto dentro de AsyncTask?
  • Cómo obtener una cadena de AsyncTask?
  • 12 Solutions collect form web for “¿AsyncTask es realmente falto conceptualmente o simplemente estoy perdiendo algo?”

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

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

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

    Parece que AsyncTask es un poco más que conceptualmente erróneo . También es inutilizable por problemas de compatibilidad. Los documentos de Android leen:

    Cuando se introdujo por primera vez, AsyncTasks se ejecutaron en serie en un solo hilo de fondo. Comenzando con DONUT, esto fue cambiado a un grupo de hilos permitiendo que múltiples tareas funcionaran en paralelo. Iniciando HONEYCOMB, las tareas vuelven a ejecutarse en un solo hilo para evitar errores de aplicación comunes causados ​​por la ejecución en paralelo. Si realmente desea una ejecución paralela, puede utilizar la executeOnExecutor(Executor, Params...) de este método con THREAD_POOL_EXECUTOR ; Sin embargo, ver comentarios allí para advertencias sobre su uso.

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

    Esto significa que si crea dos AsyncTask s para descargar dos archivos, la segunda descarga no se iniciará hasta que finalice la primera. Si chatea a través de dos servidores y el primer servidor está inactivo, no se conectará a la segunda antes de que la conexión con la primera termine. (A menos que utilice las nuevas características de API11, por supuesto, pero esto hará que su código sea incompatible con 2.x).

    Y si quieres apuntar 2.x y 3.0+, las cosas se vuelven realmente complicadas.

    Además, los documentos dicen:

    Precaución: Otro problema que puede encontrar cuando se utiliza un subproceso de trabajo es reiniciar inesperado en su actividad debido a un cambio de configuración de tiempo de ejecución (como cuando el usuario cambia la orientación de 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 la tarea correctamente cuando se destruye la actividad, consulte el código fuente de la aplicación de ejemplo Shelves.

    Probablemente todos, incluyendo Google, estamos AsyncTask mal AsyncTask desde el punto de vista MVC .

    Una actividad es un controlador y el controlador no debe iniciar operaciones que pueden sobrevivir a la vista . Es decir, AsyncTasks se debe utilizar desde el modelo , de una clase que no está vinculado al ciclo de vida de la actividad – recuerda que las actividades se destruyen en la rotación. (En cuanto a la vista , no suelen programar las clases derivadas de, por ejemplo, android.widget.Button, pero se puede. Normalmente, lo único que haces sobre la vista es el xml.)

    En otras palabras, es incorrecto colocar derivados AsyncTask en los métodos de Actividades. OTOH, si no debemos usar AsyncTasks en Actividades, AsyncTask pierde su atractivo: solía ser anunciado como una solución rápida y fácil.

    No estoy seguro de que es cierto que se arriesga a una pérdida de memoria con una referencia a un contexto de un AsyncTask.

    La forma usual de implementarlos es crear una nueva instancia de AsyncTask dentro del alcance de uno de los métodos de la Actividad. Así que si la actividad se destruye, entonces una vez que el AsyncTask completa no será inalcanzable y luego elegible para la recolección de basura? Por lo tanto, la referencia a la actividad no importará porque el AsyncTask no se quedará.

    Sería más robusto mantener un WeekReference en 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]); } } } } 

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

    Usted tiene toda la razón – es por eso que un movimiento lejos de usar 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 se pobló en el Google I / O 2013. No está seguro de por qué más personas no son conscientes de esto.

    Pensé cancelar funciona pero no lo hace.

    Aquí RTFMing sobre ello:

    Si la tarea ya se ha iniciado, entonces el parámetro mayInterruptIfRunning determina si el subproceso que ejecuta esta tarea debe interrumpirse en un intento de detener la tarea. "

    Esto no implica, sin embargo, que el hilo sea interruptible. Eso es algo de Java, no de AsyncTask.

    http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d

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

    Asegúrese de que tiene una política de cancelación en su ciclo de vida para que eventualmente se recoja basura y ya no mantiene una referencia a su actividad y también puede ser recogida basura.

    Sin cancelar su AsyncTask mientras se aleja de su contexto se ejecuta en fugas de memoria y NullPointerExceptions, si sólo tiene que proporcionar comentarios como un cuadro de diálogo Toast un diálogo sencillo, entonces un singleton de su contexto de aplicación ayudaría a evitar el problema NPE.

    AsyncTask no es todo malo, pero definitivamente hay una gran cantidad de magia que puede llevar a algunas trampas imprevistas.

    Personalmente, sólo extender Thread y utilizar una interfaz de devolución de llamada para actualizar la interfaz de usuario. Nunca podría conseguir AsyncTask para trabajar bien sin problemas de FC. También uso una cola sin bloqueo para administrar el conjunto de ejecución.

    En cuanto a las "experiencias que trabajan con él": es posible matar el proceso junto con todas las AsyncTasks, Android volverá a crear la pila de actividades para que el usuario no mencionar nada.

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