Aprende Android en 20 conceptos: Conceptos 3 y 4
Como cada semana, hoy toca nueva sección de Aprende Android en 20 conceptos.
La semana pasada vimos los fundamentos de una aplicación y los diferentes tipos de recursos que podíamos tener en una aplicación Android. Hoy pasamos a uno de los componentes de una aplicación (las actividades) y a sus homólogos (los fragmentos), fundamentales especialmente desde que Android se metió de lleno en las tablets, a partir de su versión 3.0 (Honeycomb).
3. La clase Activity
Una Actividad es uno de los componentes de una aplicación, concretamente el encargado de ofrecer una pantalla con la que los usuarios pueden interactuar, con el único objetivo de hacer algo. Es por ello que lleva asociada una interfaz de usuario.
De hecho, una aplicación suele estar compuesta por varias actividades que están vinculadas unas a otras de alguna forma. Generalmente, toda aplicación tiene una actividad considerada la actividad principal (main), la cual es la que se muestra al usuario cuando se abre la aplicación por primera vez.
Como desarrolladores, podremos lanzar nuevas actividades desde otras actividades, de tal forma que la actividad lanzadora es pausada, pero el sistema la mantiene en memoria en una cola denominada back stack. Básicamente esta cola consiste en una cola tipo LIFO (Last In, First Out), o lo que es lo mismo, la última actividad que fue añadida, será la primera en la cola. Así, cuando el usuario pulse el botón atrás (Back), el sistema nos quitará la actividad actual y nos mostrará justo la anterior en la cola, aunque este comportamiento por defecto puede ser modificado según nuestro interés.
Varias actividades pertenecerán a una tarea (task), la cual se define como un conjunto de actividades destinados a un trabajo determinado. A nivel de Manifest podemos gestionas las tareas, con la definición de algunos atributos (taskAffinity, launchMode, allowTaskReparenting, clearTaskOnLaunch, alwaysRetainTaskState, finishOnTaskLaunch) y flags o banderas (FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_CLEAR_TOP, FLAG_ACTIVITY_SINGLE_TOP), de los cuales puedes consultar más información en la documentación.
Sin embargo, el comportamiento por defecto se puede entender bastante bien en este ejemplo:
- La Actividad A lanza B
- A para y guarda su estado
- Le damos el botón Home
- Se mantiene el estado de cada actividad en la tarea
- Le damos a Back
- La actividad actual de la sale de la pila backstack y se destruye
Como connotación final sobre la pila backstack, mencionar que las actividades pueden ser instanciadas más de una vez.
Para crear una actividad, basta con que creemos una clase que herede de la clase Activity. Además de heredar de esta clase, deberemos sobreescribir algunos métodos que pertenecen al ciclo de vida de la actividad. Este ciclo de vida consiste en los diferentes estados por los que puede pasar una actividad y los métodos que nos permiten cambiar de un estado a otro. De este modo, podemos distinguir los siguientes estados:
- Resumed: En este estado, la actividad está en primer plano para el sistema
- Paused: La actividad está aún visible, pero el foco está en otro componente que está por encima de ésta
- Stopped: La actividad aún está viva, pero está totalmente oculta
De esta forma, podemos distinguir 3 procesos principales en la actividad:
- Tiempo de vida completo: Entre onCreate y onDestroy
- Tiempo de vida visible: Entre onStart y onStop
- Tiempo de vida en primer plano: Entre onResume y onPause
Tal como he comentado, en los cambios de un estado a otro, la actividad irá ejecutando una serie de métodos. Estos métodos son los considerados pertenecientes al ciclo de vida de la misma. Para nosotros, los dos más importantes son:
- onCreate: El sistema llama este método al iniciar una actividad, y en él deberemos iniciar todos los componentes de la actividad. Además, este método deberá llamar siempre al método setContentView, encargado de cargar la interfaz gráfica (un recurso layout, indicado a través de su ID) que la actividad utilizará.
- onPause: Es el primer método que se llama cuando el usuario está abandonando la actividad. Es el método donde deberemos guardar todos los cambios que queramos que sean persistentes cuando el usuario abandone esta pantalla.
Pero cuando queremos optimizar nuestra aplicación, deberemos sobreescribir también otros métodos del ciclo de vida, los cuales son:
Para terminar una actividad, basta con que llamemos al método finish.
Como componente de una aplicación que es, la actividad deberá ser registrada en el fichero Manifest. Para ello, utilizaremos la etiqueta
Llegados este punto, sabemos definir una actividad, incluirla en el Manifest e, incluso, establecerla como la actividad principal. Pero ¿cómo lanzar una actividad? Para ello tenemos dos posibles formas, pero siempre mediante el uso de Intent y el método startActivity:
- Implícita: Sabemos qué actividad vamos a lanzar, y suele ser una perteneciente a nuestra propia aplicación
-
Explícita: Sabemos la funcionalidad que queremos hacer, pero al no conocer qué actividades pueden hacerlo (de nuestra aplicación o de otras), delegamos en el sistema operativo. Éste, según sus categorías, acciones… buscará las posibilidades y nos la dará a elegir. ¿Cómo distingue el sistema entre todas sus actividades? Pues precisamente mediante el uso de los
Además, podemos necesitar lanzar una actividad pero esperar un resultado para volver a nuestra actividad previa. Para ello, en lugar de startActivity utilizaremos startActivityForResult. De esta forma, podremos registrar los resultados que nosotros deseemos y nuestra actividad previa estará esperando a uno de estos resultados para lanzar alguna funcionalidad específica:
Y ¿qué ocurre cuando una actividad es pausada pero aún no destruida? Para ello, podemos hacer uso del salvado de estado de la actividad, sobreescribiendo el método onSaveInstanceState, gracias al cual podremos salvar todos aquellos datos que queramos, y serán recuperados al restaurar la actividad:
Otras cosas que podemos hacer con la actividad es registrar cambios en la misma, tales como cambios de orientación, del estado del teclado, de idioma.. Para ello haremos uso del método onConfigurationChanged:
Pero este método sólo se disparará ante los eventos que hayamos registrado en el Manifest, mediante el atributo android:configChanges. A continuación puedes ver un ejemplo de registro de eventos de teclado y de orientación del dispositivo:
Por último, mencionar que podemos hacer uso de los Loaders, cuando queramos poder precargar de forma asíncrona (tanto en actividades como en fragmentos) información proveniente de algún ContentProvider.
En el siguiente enlace tenéis un ejemplo perfecto completo para comprender mejor las actividades y sus ciclos de vida.
4. La clase Fragment
Con la llegada de las tablets, las actividades parecían no satisfacer todas las necesidades que éstas traían consigo. ¿Por qué? La respuesta es sencilla: ahora tenemos más pantalla para mostrar más datos, pero no nos gustaría tener que rehacer el código haciendo actividades totalmente nuevas. Con toda esta idea, surge el concepto fragmento desde Android HoneyComb 3.0 (API 11).
Un fragmento representa una porción de interfaz de usuario o un comportamiento en una actividad. De esta forma, podemos combinar varios fragmentos en una única actividad, de tal forma que podemos crear un panel multi interfaz y reusar un fragmento en diferentes actividades. ¿Quién no ha visto el típico caso de datos maestro (izquierda) – esclavo (derecha) en una tablet, que en su versión móvil son dos pantallas independientes, la cual la primera nos lleva a la segunda?
Un fragmento debe siempre ser incluido en una actividad, y su ciclo de vida está totalmente relacionado con el ciclo de vida de la actividad que lo contiene. De esta forma, por ejemplo, si una actividad es pausada, todos sus fragmentos lo serán también.
He comentado que los fragmentos fueron incluidos en HoneyComb, pero han resultado ser tan trascendentales, que Google nos ha facilitado una librería de retrocompatibilidad, de modo que podamos usar los fragmentos en versiones anteriores, si deseamos que nuestra aplicación sea compatible (por ejemplo, con Gingerbread).
La transición de fragmentos dentro de una actividad se hace por medio de fragment transaction, las cuales podemos añadir a la cola backstack de nuestra actividad. De esta forma, mediante el uso del botón Back podremos deshacer una transacción de fragmentos y volver a uno anterior, muy similar a como hacíamos con la gestión de la cola en las actividades.
De todas formas, para gestionar los fragmentos dispondremos de diferentes métodos, tanto para encontrar un fragmento concreto (findFragmentById / findFragmentByTag) o para gestionar la cola backstack (popBackStack -el cual permite deshacer un cambio del backstack-/ addOnBackStackChangedListener).
Hemos hablado de que los fragmentos tienen su propio ciclo de vida, pero íntimamente relacionado con el de su actividad contenedora. Si bien esto es cierto, hay que decir que el ciclo de vida es muy similar al de la actividad, pero con ligeras diferencias. En el caso de los fragmentos, la inicialización de la interfaz gráfica o layout se hará en el método onCreateView, y no será llamando al método setContentView, sino que el objeto de la clase View que devuelva onCreateView será el objeto que se mostrará como interfaz gráfica. Para ello haremos uso de un inflater.
Los métodos que permiten coordinar el ciclo de vida de un fragmento con una actividad son:
- onAttach: Llamado cuando el fragmento ha sido asociado con la actividad
- onCreateView: Llamado para crear la vista asociada con el fragmento
- onActivityCreated: Llamado cuando termine el método onCreate de la actividad
- onDestroyView: Llamado cuando se elimina la vista asociada al fragmento
- onDetach: Llamado cuando el fragmento está siendo eliminado de la actividad
No obstante, en todo momento tendremos acceso a la actividad contenedora de un fragmento mediante la llamada al método getActivity.
Otra opción muy importante es cuando queremos que un fragmento ejecute cierta funcionalidad asociada a la actividad, que desconoce y tan sólo quiere delegar en la actividad e indicarle que debe ejecutarla. Para ello haremos uso de callbacks. Un callback nos permite implementar una interfaz con nuestra actividad, y obtener una instancia de esa actividad implementada en nuestro fragmento y que sea este objeto quien llame a la funcionalidad en cuestión, la cual pertenecerá a la actividad.
Los fragmentos tendrán los mismos estados que las actividades: resumed, paused, stopped.
Puesto que los fragmentos no son componentes de una aplicación, éstos no deben ser declarados en el Manifest. Pero, ¿cómo añadimos un fragmento a una actividad? Para ello, tenemos dos opciones:
- Declarando el fragmento de manera estática en el layout de una actividad
- Añadiendo dinámicamente en código el fragmento a un objeto ViewGroup existente
Por último, mencionar que Google, para hacernos las cosas más fáciles, nos ofrece algunos fragmentos particulares ya creados, de tal forma que nos resulta mucho más fácil desarrollar cierta funcionalidad. Entre ellos, encontramos:
- DialogFragment: Es un fragmento que nos permite mostrar un diálogo
- ListFragment: Fragmento para gestionar una lista de vistas que se repiten. Perfecto para cualquier lista
- PreferenceFragment: Fragmento para gestionar preferencias de la aplicación. Hay que remarcar que este tipo de fragmento no está incluido dentro de los compatibles en la librería de retrocompatibilidad, por lo que no podremos hacer uso de ellos en versiones anteriores a Honeycomb.
Al igual que las actividades, la mejor forma de comprender los fragmentos es un ejemplo completo de cómo funcionan, el cual podéis encontrar aquí.
Con esto damos por terminada la sección por hoy, habiendo explicado cómo funcionan las pantallas que vemos en una aplicación, ya sean completas (actividades) o parciales (fragmentos). El uso de estos dos conceptos es vital para el desarrollo de una buena aplicación, especialmente el conocimiento de sus ciclos de vida. Llegará un punto que un setContentView, un onResume, etc… será más normal para vosotros que muchas cosas casi vitales de la vida misma.
¿Con ganas de más conceptos?
Ver la sección Aprende Android en 20 conceptos