Los métodos retrollamada relacionados con el ciclo de vida de una actividad nos serán útiles para especificar el comportamiento de ésta ante las idas y venidas del usuario. Por ejemplo, si estamos programando un reproductor para vídeo streaming, deberíamos detener la reproducción y cerrar la conexión de red cuando el usuario cambia de aplicación. Si el usuario decide volver al reproductor, volveremos a reestablecer la conexión de red y la reproducción justo donde se había quedado.
Iniciando nuestra actividad.
A diferencia de otros paradigmas de la programación en los que las aplicaciones empiezan su ejecución desde un método principal, el sistema Android puede iniciar una actividad cualquiera de nuestra aplicación, invocando sus métodos retrollamada.Las retrollamadas del ciclo de vida.
Cada vez que el sistema invoca una secuencia de métodos retrollamada de una actividad, lo hace siguiendo una estructura piramidal. Cada uno de los estados por los que transita la actividad se correspondería con cada uno de los escalones de la pirámide (ver imagen 1). Cada vez que el sistema inicia una actividad, invoca una secuencia de métodos que la hacen transitar hacia la parte superior de la pirámide (estado reanudada), pasando a un primer plano.Cuando el usuario comienza a abandonar la actividad, el sistema invocará otra secuencia de métodos que la hacen transitar hacia la parte más baja de la pirámide. El camino de descenso en la pirámide puede ser parcial, es decir, que la actividad pase a un estado de espera (el usuario ha cambiado de aplicación, por ejemplo). Desde un estado de espera, la actividad podría volver a emprender el camino de ascenso en la pirámide (el usuario vuelve a la actividad, por ejemplo) y volver al punto de partida.
Imagen 1. Ciclo de vida de una actividad y su estructura piramidal. Secuencia de llamadas de ascenso por la izquierda. Secuencia de llamadas de ascensos/descensos parciales por la derecha. |
No siempre tendremos que implementar todos los métodos retrollamada asociados a una actividad. Esto dependerá de la complejidad de la misma. Sin embargo, es importante saber cómo funcionan y ofrecer al usuario el comportamiento que espera, garantizándole entre otras cosas:
- Que no se produzca un error en nuestra aplicación mientras recibimos una llamada de teléfono o cambiamos de aplicación.
- Que no se consuman recursos importantes del sistema mientras no estemos usando la aplicación.
- Que no se pierda el progreso del usuario si éste sale y entra de la aplicación.
- Que no se produzcan errores o se pierdan datos cuando se cambie la orientación de la pantalla.
Una actividad podrá estar en uno de en uno de los siguientes tres estados:
Reanudada.
La actividad se muestra en primer plano y el usuario puede interactuar con ella. En ocasiones a este estado también se le puede llamar en ejecución.
Pausada o en pausa.
Aún se puede ver la actividad mientras otra actividad pasa a primer plano y obtiene el foco. Nuestra actividad aún se sigue visualizando aunque cuente con otra actividad por encima de ella que es semi transparente, o bien, no ocupa toda la pantalla. El usuario no podrá interactuar con una actividad en pausa y ésta no podrá ejecutar código alguno.
Detenida.
La actividad pasa a estar oculta en su totalidad por otra actividad, pasando a un segundo plano. Mientras una actividad está detenida, su estado será conservado y no podrá ejecutar código alguno.
Los estados creada e iniciada son estados transitorios de la actividad por los que ésta pasa cuando es ejecutada por primera vez. Es decir, el sistema llama a la secuencia de métodos
onCreate()
, onStart()
y onResume()
, pasando la actividad directamente a estar estar en estado reanudada.Configurando nuestra actividad para que aparezca en el lanzador de aplicaciones.
Toda aplicación cuenta con un icono en el lanzador de aplicaciones desde el que podremos ejecutarla. Cuando ejecutamos una aplicación desde el lanzador de aplicaciones, ésta inicia su actividad principal que será la encargada de mostrar la interfaz del usuario.Podemos declarar qué actividad va a ser la actividad principal de nuestra aplicación desde en el archivo de configuración
AndroidManifest.xml
en el directorio raíz de nuestro proyecto. Para ello utilizaremos un filtro de intenciones especial (<intent-filter>
), donde la acción será de tipo MAIN
y la categoría de tipo LAUNCHER
:<activity android:name=".MainActivity" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Nota.Cuando creamos un proyecto haciendo uso de las herramientas del SDK, por defecto se crea una actividad principal con este filtro.Si el filtro anterior no se encuentra declarado en ninguna de las actividades de nuestra aplicación, ésta no aparecerá en el lanzador de aplicaciones.
Creando una nueva instancia.
La mayoría de las aplicaciones incluyen múltiples actividades permitiendo al usuario realizar múltiples operaciones. Cada vez que el usuario inicia una actividad, el sistema crea una nueva instancia de ella e invoca su métodoonCreate()
. En éste, implementaremos operaciones básicas que deben ejecutarse tan solo una vez a lo largo de toda la vida útil de la actividad. Por ejemplo, es el lugar adecuado para definir la interfaz del usuario y sus variables miembro asociadas.En el siguiente ejemplo, el método
onCreate()
realiza algunas operaciones para configurar la actividad: declara la interfaz del usuario (definida en una plantilla XML), declara algunas variables miembro y configura parte de la interfaz del usuario: TextView mTextView; // Variable miembro que mostrará un texto en la plantilla
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Asignamos la plantilla a la actividad
// El archivo de la plantilla se encuentra en res/layout/main_activity.xml
setContentView(R.layout.main_activity);
// Iniciamos la variable miembro mTextView
mTextView = (TextView) findViewById(R.id.text_message);
// Nos aseguramos de que el sistema es Honeycomb o superior
// para poder usar la barra de acciones
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// Hacemos que el icono de la actividad principal NO se comporte como un botón
ActionBar actionBar = getActionBar();
actionBar.setHomeButtonEnabled(false);
}
}
Nota. La constante Build.VERSION.SDK_INT
se encuentra disponible a partir de la versión 2.0 de Android (versión 5 de la API).Una vez finalizada la ejecución del método
onCreate()
, el sistema llama a los métodos onStart()
y onResume()
. La actividad nunca estará en los estados creada o iniciada, pasando directamente al estado reanudada y permaneciendo en él hasta que sucede algo: una llamada telefónica, el usuario cambia de actividad o la pantalla entra en reposo.Los métodos
onStart()
y onResume()
nos serán útiles para controlar cuándo la aplicación pasa al estado reanudada habiendo sido previamente pausada o detenida. Este tema lo abordaremos más adelante.Nota. El método
onCreate()
incluye un parámetro savedInstanceState
que nos servirá para restaurar el estado de una aplicación, respectivamente. Este tema lo abordaremos más adelante.Destruyendo nuestra actividad.
El último de los métodos retrollamada invocado por el sistema dentro del ciclo de vida de una actividad esonDestroy()
. Que el sistema llame a este método es sintomático de que la actividad va a ser eliminada de la memoria.La mayoría de las aplicaciones no necesitan implementar este método porque la mayoría de las referencias locales a la clase se acabarán liberando con la desaparición de la propia actividad. Para realizar tareas de limpieza, los métodos
onPause()
y onStop()
son más apropiados. Sin embargo, si utilizamos hebras (threads) ejecutándose en segundo plano, creados en el método onCreate()
, esto podría traducirse en una pérdida de memoria. En este último caso, el método onDestroy()
es el más adecuado para acabar con la ejecución de esas hebras.@Override
public void onDestroy() {
super.onDestroy(); // Siempre llamaremos al método de la clase padre
// Detenemos la depuración del código iniciada en el método onCreate()
android.os.Debug.stopMethodTracing();
}
Nota. El sistema llama al método onDestroy()
después de llamar a los métodos onPause()
y onStop()
en todas las situaciones a excepción de una: cuando terminamos la actividad llamando al método finish()
dentro del método onCreate()
. En este caso, el sistema llama inmediatamente después al método onDestroy()
, obviando el resto de métodos.Pausando y reanudando nuestra actividad.
Durante el uso normal de una aplicación, la actividad que se encuentra en primer plano puede quedar parcialmente oculta por la superposición de otra actividad semi transparente o que no ocupa toda la pantalla (un cuadro de diálogo, por ejemplo). En este caso, nuestra actividad pasará a estar pausada.Cuando una actividad entra en pausa, el sistema llama a su método
onPause()
. Esto nos permitirá implementar acciones para detener ciertos procesos (parar la reproducción de un video, por ejemplo) o guardar cierta información de manera persistente si el usuario abandona la aplicación. Cuando el usuario vuelve a la aplicación, el sistema llamará al método onResume()
.Nota. Cuando el sistema llama al método
onPause()
de una actividad, podríamos pensar que después de entrar en pausa durante un tiempo, el usuario volverá a ella. Sin embargo, en la mayoría de los casos, este hecho está más bien relacionado con el abandono de la actividad por parte del usuario.Pausando nuestra actividad.
Cuando el sistema llama al métodoonPause()
de nuestra actividad, técnicamente significa que ésta pasa a ser parcialmente visible, pero en la mayoría de los casos esto no será así y lo que realmente nos indica, es que el usuario estaría abandonando la actividad y que ésta cambiará su estado a detenida. Deberíamos reservar el uso del método onPause()
para:- Detener animaciones o cualquier otro tipo de operaciones que hagan un uso intensivo de la CPU.
- Guardar los datos que el usuario espera sean permanentes (el borrador de un correo electrónico, por ejemplo).
- Liberar recursos del sistema tales como: receptores de avisos, sensores (como el GPS), o cualquier otro recurso que pueda afectar al consumo de la batería.
Por ejemplo, si nuestra aplicación usa la cámara, el método
onPause()
es un buen sitio para pasar a liberarla para que otras actividades puedan hacer uso de ella:@Override
public void onPause() {
super.onPause(); // Siempre hay que llamar al método de la clase padre
// Liberamos la cámara porque no la necesitaremos mientras estemos en pausa
// y otras actividades podrían necesitarla.
if (mCamera != null) {
mCamera.release()
mCamera = null;
}
}
Como norma, no deberíamos usar el método
onPause()
para almacenar cualquier cambio hecho por el usuario (cambios en los campos de un formulario, por ejemplo), salvo que el usuario espere que se haga un guardado automático; como cuando se redacta un correo electrónico y éste se guarda como borrador. Deberíamos evitar hacer operaciones que hagan un uso intensivo de CPU en este método, como podrían ser las operaciones de escritura sobre una base de datos, porque esto ralentiza visualmente la transición hacia otras actividades (utiliza el método onStop()
en su lugar).Nota. Cuando nuestra actividad entra en pausa, su instancia se conservará en memoria y seguirá disponible cuando ésta sea reanudada. No necesitaremos volver a definir los componentes que fueron creados durante cualquiera de los métodos previos a la reanudación.
Reanudando nuestra actividad.
Cuando el usuario reanuda una actividad pausada, el sistema invoca su métodoonResume()
. Ten en cuenta que el sistema llama a este método cada vez que nuestra actividad entra en primer plano, incluyendo cuando es creada por primera vez. Deberíamos usar este método para reactivar componentes que previamente desactivamos en el método onPause()
(activar las animaciones y otros componentes usados mientras la actividad tiene el foco).En el ejemplo siguiente, el método
onResume()
hace justo lo contrario al método onPause()
:@Override
public void onResume() {
super.onResume(); // Siempre llamamos al método de la clase padre
// Inicializamos la cámara si su instancia es null
if (mCamera == null) {
initializeCamera(); // Método local encargado de inicializar la cámara
}
}
Deteniendo y reiniciando nuestra actividad.
Detener y reiniciar nuestra actividad es un proceso importante que nos asegura que los usuarios perciban nuestra aplicación como que siempre está viva y que no van a perder su trabajo. Hay unos pocos escenarios en los que nuestra actividad será detenida y reiniciada:- El usuario cambia a una aplicación de la lista de tareas recientes, pasando nuestra actividad a ser detenida. Si el usuario vuelve a nuestra aplicación a través del lanzador de aplicaciones, o a través de la lista de tareas recientes, ésta será reiniciada.
- El usuario realiza una acción desde la propia aplicación que inicia una nueva actividad. La actividad actual será detenida mientras que la segunda será creada. Si el usuario pulsa el botón de retroceso, la primera actividad será reiniciada.
- El usuario recibe una llamada telefónica mientras interactúa con la aplicación en su teléfono.
La clase
Activity
proporciona dos métodos, onStop()
y onRestart()
, para gestionar nuestra actividad en los escenarios descritos. A diferencia del estado en pausa, que se caracteriza por la ocultación parcial de nuestra actividad, el estado detenida se caracteriza porque esta ocultación es completa y el usuario estará ocupado con otra actividad.Nota. Debido a que el sistema mantiene la instancia de la actividad en memoria cuando ésta es detenida, es posible que no necesitemos implementar los métodos
onStop()
y onRestart()
(o incluso onStart()
). La mayoría de las actividades son relativamente simples, la actividad se detendrá y se reiniciará correctamente y solo necesitamos el método onPause()
para detener ciertas acciones y liberar recursos del sistema.Deteniendo nuestra actividad.
Cuando el sistema llama al métodoonStop()
, la actividad se oculta. Es en este momento cuando deberíamos liberar todos los recursos que no vayan a ser utilizados por el usuario. Mientras la actividad está detenida, el sistema podría eliminarla si anda escaso de memoria. En situaciones límite, el sistema puede destruir nuestra aplicación sin llegar a invocar el método onDestroy()
, por ello deberíamos usar el método onStop()
para liberar recursos y así evitar pérdidas de memoria.Aunque el método
onPause()
se llama antes que el método onStop()
, usaremos el método onStop()
para realizar operaciones voluminosas de escritura sobre una base de datos.Una implementación del método
onStop()
que guarda el contenido de una nota borrador, podría ser la siguiente:@Override
protected void onStop() {
super.onStop(); // Siempre se llama al método de la clase padre
// Guardamos la nota borrador porque la actividad va a ser detenida
// y queremos asegurarnos de que los cambios no se pierden
ContentValues values = new ContentValues();
values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());
getContentResolver().update(
mUri, // El URI de la nota a actualizar.
values, // Nombres de columna y sus valores actualizados.
null, // No utilizamos cláusula SELECT.
null // No utilizamos cláusula WHERE.
);
}
Cuando la actividad es detenida, ésta permanece en memoria y se reutiliza cuando la actividad pasa a ser reanudada. No necesitas reiniciar componentes que fueron creados durante métodos previos a la reanudación. El sistema también mantiene el estado actual para cada una de las vistas de la plantilla y si el usuario introdujo información en un campo de texto, por ejemplo, su contenido será conservado y no tendremos que implementar ningún mecanismo adicional.
Nota. Aún si el sistema destruye nuestra actividad mientras ésta está detenida, el estado de las vistas se seguirá conservando en un objeto
Bundle
.Iniciando/reiniciando nuestra actividad.
Cuando nuestra actividad vuelve al primer plano habiendo estado detenida, el sistema primero invoca su métodoonRestart()
. A continuación, el sistema llama al método onStart()
y la aplicación empieza a hacerse visible (cosa que sucede tanto si ha sido reiniciada como si ha sido creada por primera vez). El método onRestart()
será invocado solo cuando la actividad se reanuda desde el estando detenida.No es común que una aplicación necesite usar el método
onRestart()
para recuperar parte del estado de una actividad. Debido a que el método onStop()
libera todos los recursos de nuestra actividad, necesitaremos recuperarlos cuando la actividad sea reiniciada. Por otro lado, también será necesario crear una instancia de esos mismos recursos cuando la actividad se crea por primera vez. Es por esta razón, por la que deberíamos usar el método onStart()
como el método homólogo al método onStop()
.Por ejemplo, cuando el usuario vuelve a nuestra aplicación desde otra aplicación, el método
onStart()
será un buen sitio para verificar que los recursos requeridos aún se encuentran activos:@Override
protected void onStart() {
super.onStart(); // Siempre llamamos al método de la clase padre.
// Tanto si la actividad se inicia por primera vez como si se ha reiniciado
// comprobaremos si el GPS se encuentra activo.
LocationManager locationManager =
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
boolean gpsEnabled =
locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
if (!gpsEnabled) {
// Creamos un cuadro de diálogo aquí que solicite al usuario que active el GPS.
// Usamos una intención del tipo android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS.
// Esto muestra el panel de configuración del GPS al usuario para que pueda activarlo.
}
}
@Override
protected void onRestart() {
super.onRestart(); // Siempre llamamos al método de la clase padre.
// La actividad reinicia desde el estado detenida.
}
Cuando el sistema destruye nuestra actividad, llama a su método
onDestroy()
. Debido a que deberíamos liberar casi todos nuestros recursos desde el método onStop()
, no hay mucho más que hacer desde el método onDestroy()
. Este método es nuestra última posibilidad para liberar recursos que podrían ocasionar pérdidas de memoria: hebras adicionales que hayamos creado, trazas de depuración del código, conexiones a bases de datos, etc.Recreando nuestra actividad.
Hay algunos escenarios en los que una actividad es destruida durante el uso habitual de nuestra aplicación: cuando el usuario pulsa el botón de retroceso, o cuando nuestra actividad se destruye así misma invocando el métodofinish()
. El sistema también eliminará nuestra actividad si se encuentra detenida y no ha sido usada durante un largo período de tiempo, o si la actividad en primer plano require de más recursos y el sistema tiene que recurrir a cerrar procesos en segundo plano para recuperar memoria.Tanto si nuestra actividad se destruye porque el usuario pulsa el botón de retroceso, como si se destruye por llamar al método
finish()
, la instancia de la actividad también será destruida con ello. Aunque la instancia se haya destruido debido a las limitaciones del sistema, si el usuario vuelve a la actividad, el sistema volverá a recrear su instancia a partir de los datos previamente almacenados a su destrucción. Estos datos, datos de estado, se almacenan en una colección de pares clave-valor de tipo Bundle
.Importante. Nuestra actividad será destruida y recreada cada vez que cambiemos la orientación de la pantalla de nuestro dispositivo. El sistema actúa de esta manera para que la actividad pueda adaptarse a la nueva configuración cargando recursos alternativos (por ejemplo, una nueva plantilla optimizada para cierta orientación de la pantalla).
Por defecto, el sistema usa un objeto de tipo
Bundle
para guardar la información asociada a las vistas de una plantilla (como podría ser un campo de texto EditText
). De esta manera, si nuestra actividad es destruida y recreada, la plantilla volverá a recuperar su estado previo.Nota. Para que el sistema Android sea capaz de recuperar un estado previo de las vistas de una plantilla, será necesario que éstas dispongan de un identificador único, es decir, un valor para el atributo
android:id
.Para almacenar nuestros propios datos de estado, utilizaremos el método retrollamada
onSaveInstanceState()
. El sistema llama a este método cuando el usuario abandona la actividad, pasándole como argumento un objeto de tipo Bundle
en el que se almacena el estado de la actividad. Si el sistema recrea la actividad más tarde, pasa el mismo objeto Bundle
tanto a los métodos onRestoreInstanceState()
y onCreate()
.Guardando el estado de nuestra actividad.
Cuando nuestra actividad comienza a detenerse, el sistema invoca el métodoonSaveInstanceState()
y nuestra actividad guarda su información de estado. La implementación por defecto de este método, guarda información referente a las vistas, como el texto introducido en un campo de texto EditText
, o la posición del desplazamiento de una lista ListView
.Para guardar información extra de nuestra actividad, debemos implementar el método
onSaveInstanceState()
y añadir los pares clave-valor que necesitemos en el objeto Bundle
. Por ejemplo:static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
...
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Guardamos los datos del usuario referentes al juego
savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
// Siempre llamaremos al método de la clase padre
super.onSaveInstanceState(savedInstanceState);
Importante. Llama siempre al método de la clase padre para que la implementación por defecto tenga efecto y se almacene el estado de las vistas.Restaurando el estado de nuestra actividad.
Cuando nuestra actividad es recreada después de haber sido destruida, puede recuperar su estado original a partir del objetoBundle
proporcionado tanto por su método onCreate()
, como por su método onRestoreInstanceState()
.Puesto que el método
onCreate()
se invoca tanto para crear la actividad por primera vez, como cuando ésta es recreada, tendremos que comprobar si el objeto Bundle
es nulo o no antes de consultar su contenido. Si es nulo, es porque el sistema está creando por primera vez la actividad y ésta no necesita restaurar nada:@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
} else {
// Probably initialize members with default values for a new instance
}
...
}
Si no quieres tener que comprobar si el objeto Bundle
es nulo o no, podemos usar el método onRestoreInstanceSatate()
. El sistema invoca este método solo si se produce una recuperación del estado de la actividad, por lo que el objeto Bundle
siempre tendrá datos:public void onRestoreInstanceState(Bundle savedInstanceState) {
// Siempre hay que llamar al método de la clase padre
super.onRestoreInstanceState(savedInstanceState);
// Restauramos variables miembro de la actividad
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
}
Importante. Tendremos que invocar siempre al método de la clase padre si queremos recuperar el estado previo de las vistas.
No hay comentarios:
Publicar un comentario