Imaginemos, por ejemplo, la aplicación de Twitter, que al iniciar su ejecución hace un llamado a un método del API de Twitter. Éste llamado tomará su tiempo dependiendo del tipo de conexión del usuario. Mientras se obtienen los tweets más recientes, no queremos que la aplicación se congele provocando un ANR (Application Not Responding) y un eventual Force Close de la aplicación. Por ello, Android provee al desarrollador de diferentes herramientas para ejecutar tareas en hilos a parte del hilo principal, entre ellas, las clases Handler(desde API Level 1) y AsyncTask (desde API Level 3).
El AsyncTask es ampliamente utilizado para ejecutar tareas “simples” o “atómicas” en un hilo aparte. Su ciclo de ejecución es bastante intuitivo y práctico, comparado con el uso de la clase Handler. En Android 1.5, los AsyncTask eran encolados y un único hilo se encargaba de ejecutarlos uno por uno. A partir de 1.6 hasta Android 2.3.4 (inclusive), los AsyncTask son ejecutados cada uno por un hilo aparte (sin asegurar su orden). Aqui la clase AsyncTask maneja una cola de ejecución de 10 hilos(dato no confirmado) y una cola de 10 hilos en espera (aunque según Roman Guy aquí, ese límite ya no existe, aunque no indica en que versión :-/), dándonos aproximádamente 20 AsyncTask “simultáneos”. Cuando este límite se excede, se dispara la excepción RejectedExecutionException. Está planeado despues de Honeycomb (Android 3.0) volver a la ejecución sencilla, para evitar los errores de ejecución paralela.
Esta clase es muy útil pero a la vez se convierte, probablemente, en una de las mayores fuentes de memory leaks y application crashes (Force Close) en Android. En este primer artículo hablaremos primero sobre los Inner clases y cómo definir una AsycTask adecuadamente.
1. Inner clases
Tal vez la razón número uno de leaks en una aplicación Android son las Inner classes dentro de una clase Activity. Las Inner class, tienen una referencia implícita hacia la Outer class. Cuando se crea un AsyncTask, la tendencia es hacerlas dentro de la Actividad donde la ocupamos. Veamos un ejemplo
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // Inicialización de la actividad, layout, etc MyLongTask task = new MyLongTask(); task.execute("http://blog.fr4gus.com/api/test.json"); } @Override protected void onPause() { // Persistamos cualquier cosa que ocupemos } class MyLongTask extends AsyncTask<String, Void, Void>{ @Override protected void onPreExecute() { // Avísele al usuario que estamos trabajando } @Override protected Void doInBackground(String... params) { // Aquí hacemos una tarea laaarga return null; } @Override protected void onPostExecute(Void result) { // Aquí actualizamos la UI con el resultado } } }
El problema es que éstas inner classes no-estáticas no tienen un ciclo de vida controlado por el desarrollador. La solución está en hacer las classes Inner, estáticas y pasarle la referencia de la actividad o contexto, pero envolviéndola dentro de un WeakReference. Esto permitirá al Garbage Collector, liberar la memoria del Activity aunque el AsyncTask siga ejecutándose. Es importante que a la hora de usar el WeakReference, se aseguren que la referencia al Activity sea aun válida, verificando que no sea nula y que la activdad no esta terminando.
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // Inicialización de la actividad, layout, etc MyLongTask task = new MyLongTask(this); task.execute("http://blog.fr4gus.com/api/test.json"); } @Override protected void onPause() { // Persistamos cualquier cosa que ocupemos } static class MyLongTask extends AsyncTask<String, Void, Void> { WeakReference<MyActivity> context; public MyLongTask(MyActivity activity) { context = new WeakReference<MyActivity>(activity); } @Override protected void onPreExecute() { // Avísele al usuario que estamos trabajando } @Override protected Void doInBackground(String... params) { // Aquí hacemos una tarea laaarga return null; } @Override protected void onPostExecute(Void result) { MyActivity activity = context.get(); if (activity != null && !activity.isFinishing()) { // Aquí actualizamos la UI con el resultado } } } }
Fuentes
- Documentación de Android sobre memory leaks http://developer.android.com/resources/articles/avoiding-memory-leaks.html
- Límite de ejecución de los AyncTask: http://stackoverflow.com/questions/2492909/asynctask-rejectedexecutionexception-and-task-limit
- Implementación de AyncTaskEx de CommonsGuy: https://github.com/commonsguy/cwac-task
Una pequeña duda en los métodos doInBackground y onPostExecute, su firma no debería de ser la siguiente?
protected String doInBackground(String… params) {
// Aquí hacemos una tarea laaarga
return null;
}
@Override
protected void onPostExecute(String result) {
// Aquí actualizamos la UI con el resultado
}
Cierto, tiene varios problemas en los parámetros y en los tipos de dato que retorna
Saludos Carlos, En el ejemplo que puse, la definición de MyLongTask, el tercer parametro del genérico, que corresponde al tipo del resultado es Void, osea MyLongTask<String,Void,Void>. Porlo tanto el método doInBackground devuelve Void y onPostExecute recibe Void.
Para que dichos métodos tuvieran la firma que usted indica, la definición de MyLongTask debería ser: MyLongTask<String, Void, String>
La definición de AsyncTask indica que el primer parámetro del genérico es el tipo de los parametros, el segundo parámetro es el tipo del progreso, y el último parámetro es el tipo del resultado.
Llevo varios días buscando y no encuentro nada acerca de lo que necesito hacer. Básicamente necesito una AsyncTask pero como clase aparte, es decir, clase pública en archivo separado. Además, tengo que saber cuándo finaliza su tarea, para habilitar o deshabilitar un botón, que representa una alerta (activada o desactivada, dependiendo de si la AsyncTask ha terminado o no). Lo mismo esta filosofía no es la más adecuada para lo que quiero.
Ocupa estrictamente que el AsyncTask sea una clase aparte? Talvez pueda hacerla así y hacer una subclase de esa AsyncTask suya, dentro de la actividad para que tenga accesso a los elementos de la UI.
Otra cosa que se me ocurre es que el AsyncTask al crearlo reciba la referencia de un Handler, para que usted pueda enviarle mensajes a la actividad que creó ese Handler.
Tienes un problema con las comas. Tu texto es ilegible.
Gracias Uhliuhnliunln, acabo de actualizar el artículo, espero que ahora sí se pueda entender :).
A que te refieres con la ejecución atómica?
Como el llamado a una función o método de algún objeto.
Detalle técnico: en java no existe nada llamado “clases inner estáticas”, dichas clases propiamente se llaman clases anidadas estáticas (static nested clases). Un saludo
Buen post, me aclaro muchas dudas
Muchas gracias !!, las buenas practicas son muy requeridas