Puede que tengamos una pantalla, que en algún momento ejecute un cambio o solicitud de manera asíncrona. Para esto debemos mostrarle al usuario algo mientras, ya sea de manera determinada (progreso del trabajo o tarea) o indeterminada (el famoso spinner).
Cuando hay un cambio de orientación, si la actividad no maneja el cambio de orientación, probablemente sea destruida y recreada. Entonces ¿Qué pasa con el ProgressDialog?.
Iniciemos con dos reglas que debemos seguir:
- Si va a crear el AsyncTask dentro de la actividad como un inner class, asegúrese de que sea estática. Las inner class no estáticas, guardan una referencia de la outer class, en nuestro caso, la Actividad. Esto quiere decir que estamos “filtrando” (leaking) memoria, pues esa referencia de la actividad queda ahi mientras siga vivo el AsyncTask, y esto hay que evitarlo.
- Para crear el ProgressDialog, utilice los métodos que tiene disponible la actividad. Me refiero a onCreateDialog, showDialog y dismissDialog. Esto por que a la hora de cambiar la orientación de la pantalla y al destruir y recrear la actividad, Android va a mantener el estado de los dialogs que estaban presentes y se encargará de mostrarlos nuevamente cuando la actividad es recreada.
El consenso es utilizar el método onRetainNonConfigurationInstance, para “guardar” la referencia del AsynctTask, así, cuando la nueva actividad es creada, ella se dará cuenta si la tarea todavia está en progreso, para poder “acoplarse” a ella.
public class EjemploProgressDialogActivity extends Activity { public static final String TAG = "EXAMPLE_DIALOG"; public static final int PROGRESS_DIALOG = 1; MyTask task; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Log.d(TAG, "onCreate"); Object obj = getLastNonConfigurationInstance(); if (obj != null && obj instanceof MyTask) { Log.d(TAG, "Tarea previa ejecutandose"); task = (MyTask) obj; task.attach(this); } else { task = new MyTask(this); task.execute(10); Log.d(TAG, "Nueva tarea creada y ejecutada"); } } @Override protected Dialog onCreateDialog(int id) { switch (id) { case PROGRESS_DIALOG: ProgressDialog pd = new ProgressDialog(this); pd.setTitle("Trabajando"); pd.setMessage("Por favor espere..."); return pd; default: break; } return super.onCreateDialog(id); } @Override protected void onPause() { super.onPause(); Log.d(TAG, "onPause"); } @Override protected void onRestart() { super.onRestart(); Log.d(TAG, "onRestart"); } @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume"); } @Override protected void onStart() { super.onStart(); Log.d(TAG, "onStart"); } @Override protected void onStop() { super.onStop(); Log.d(TAG, "onStop"); } @Override public Object onRetainNonConfigurationInstance() { // Aqui es donde se hace la magia if (task != null) { task.deattach(); return task; } return super.onRetainNonConfigurationInstance(); } private static class MyTask extends AsyncTask { WeakReference ctx; public MyTask(Activity activity) { super(); attach(activity); } @Override protected void onPreExecute() { Activity activity = ctx.get(); if (activity != null && !activity.isFinishing()) { Log.d(TAG, "Mostrando Progress Dialog"); activity.showDialog(PROGRESS_DIALOG); } } @Override protected Void doInBackground(Integer... params) { int seconds = params[0]; try { Log.d(TAG, "Tarea va a durar " + seconds + " segundos"); Thread.sleep(seconds * 1000); Log.d(TAG, "Tarea Lista"); } catch (InterruptedException e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void result) { if (ctx != null && ctx.get() != null) { Activity activity = ctx.get(); if (!activity.isFinishing()) { Log.d(TAG, "Ocultando Progress Dialog"); activity.dismissDialog(PROGRESS_DIALOG); } } } public void attach(Activity activity) { this.ctx = new WeakReference(activity); } public void deattach() { ctx = null; } } }
En la línea 14, pueden ver que en el método onCreate se pregunta si se salvó algún objeto previamente y luego se hacen los chequeos respectivos para asegurarse que el objeto sea el AsyncTask. En este ejemplo en particular, se crea el AsyncTask en el método onCreate, por lo que en el caso de que no exista, se crea.
En la línea 18 se ejecuta el método attach,para que el AsyncTask tenga la nueva referencia de la Actividad.
En la línea 77 que es el momento en que la actividad da su último suspiro, el AsyncTask se desacopla y se “salva” la referencia al AsyncTask para que la siguiente actividad (si es el caso) la retome, como vimos en el método onCreate.
Si ven la implementación del AsyncTask, primero que todo, se guarda la referencia a la actividad dentro de un WeakReference, para así evitar “filtrar” memoria.
En los métodos onPreExecute y onPostExecute se hacen validaciones para asegurarse de que la referencia de la actividad sea válidad (que exista la referencia y que la actividad no esté en proceso de morirse).
Si ejecutáramos éste código, sin mover el dispositivo, esta sería la salida en la bitácora:
10-04 21:54:46.254: DEBUG/EXAMPLE_DIALOG(6155): onCreate 10-04 21:54:46.294: DEBUG/EXAMPLE_DIALOG(6155): Mostrando Progress Dialog 10-04 21:54:46.514: DEBUG/EXAMPLE_DIALOG(6155): Tarea va a durar 10 segundos 10-04 21:54:46.514: DEBUG/EXAMPLE_DIALOG(6155): Nueva tarea creada y ejecutada 10-04 21:54:46.514: DEBUG/EXAMPLE_DIALOG(6155): onStart 10-04 21:54:46.524: DEBUG/EXAMPLE_DIALOG(6155): onResume 10-04 21:54:56.518: DEBUG/EXAMPLE_DIALOG(6155): Tarea Lista 10-04 21:54:56.524: DEBUG/EXAMPLE_DIALOG(6155): Ocultando Progress Dialog 10-04 21:55:12.395: DEBUG/EXAMPLE_DIALOG(6155): onPause 10-04 21:55:12.554: DEBUG/EXAMPLE_DIALOG(6155): onStop
Pero si cambiamos la orientación del dispositivo, esto sería el resultado:
te 10-04 21:56:08.584: DEBUG/EXAMPLE_DIALOG(6155): Mostrando Progress Dialog 10-04 21:56:08.754: DEBUG/EXAMPLE_DIALOG(6155): Nueva tarea creada y ejecutada 10-04 21:56:08.754: DEBUG/EXAMPLE_DIALOG(6155): onStart 10-04 21:56:08.754: DEBUG/EXAMPLE_DIALOG(6155): Tarea va a durar 10 segundos 10-04 21:56:08.764: DEBUG/EXAMPLE_DIALOG(6155): onResume 10-04 21:56:10.424: DEBUG/EXAMPLE_DIALOG(6155): onPause 10-04 21:56:10.424: DEBUG/EXAMPLE_DIALOG(6155): onStop 10-04 21:56:10.544: DEBUG/EXAMPLE_DIALOG(6155): onCreate 10-04 21:56:10.544: DEBUG/EXAMPLE_DIALOG(6155): Tarea previa ejecutandose 10-04 21:56:10.544: DEBUG/EXAMPLE_DIALOG(6155): onStart 10-04 21:56:10.704: DEBUG/EXAMPLE_DIALOG(6155): onResume 10-04 21:56:18.755: DEBUG/EXAMPLE_DIALOG(6155): Tarea Lista 10-04 21:56:18.755: DEBUG/EXAMPLE_DIALOG(6155): Ocultando Progress Dialog 10-04 21:56:21.626: DEBUG/EXAMPLE_DIALOG(6155): onPause 10-04 21:56:21.784: DEBUG/EXAMPLE_DIALOG(6155): onStop
Imprimí además cuando se ejecutan otros métodos del ciclo de vida, para tener una referencia de cuando ocurre qué.
buen ejemplo gran aportacion todo esta explicacion lo he visto en ingles y de forma mas compleja
Muuuy buen ejemplo. Tengo una consulta. Si quiero manejar Timeout dentro de la Clase AsyncTask, es posible? O sea, suponiendo que mi aplicación se conecta a un servidor remoto, y que por X motivo el servidor no responde. entonces la aplicación debería tener en cuenta este escenario, y así mostrar un mensaje que diga, por ejemplo: “Tiempo de espera superado.”. Cómo se sería en el caso que se quiera incorporar un control por Timeout? Gracias de antemano. Saludos, Cristian.
Hola Cristian. Ese timeout del que usted habla es “responsabilidad” del “cliente” http que use, ya sea un URLConnection o el DefaultHTTPClienit (apache). Hay dos timeouts de hecho. El primero es el timeout de conexion, que es el tiempo de espera antes de expirar un intento de accesso a un servidor. El segundo es el timeout de socket, una vez que el servidor le ha atendido, cuanto tiempo usted va a esperar por la respuesta. Para más información puede ver la clase HTTPConnectionParams http://hc.apache.org/httpclient-3.x/apidocs/org/apache/commons/httpclient/params/HttpConnectionParams.html