Entradas etiquetadas ‘Android’
Nunca se sabe cuándo vas a acabar en la isla…
… y los creadores de Android lo han tenido en cuenta.
Echando un vistazo al código fuente de Android para comprobar unas cosas de la clase SensorManager me encontré con esto:
public static final float GRAVITY_THE_ISLAND = 4.815162342f;
La verdad es que está documentado y disponible en la clase SensorManager, pero no había reparado en ello hasta ahora.
También han tenido en cuenta otra posible ubicación de nuestro móvil, aunque no estoy seguro de que Vodafone tenga cobertura aquí:
public static final float GRAVITY_DEATH_STAR_I = 0.000000353036145f;
Realidad aumentada en Android: AR Compass – I
Decíamos en el anterior artículo que la realidad aumentada combina datos generados por ordenador con imágenes del entorno obtenidas en tiempo real. La parte complicada es la superposición de los datos sobre la imagen en la posición correcta. Así que vamos a comenzar con una aplicación de realidad levemente aumentada: una brújula virtual.
AR Compass
AR Compass (brújula de realidad aumentada, en inglés queda más cool) es una aplicación para Android que muestra una brújula superpuesta a la imagen capturada por la cámara. La brújula consiste en una serie de líneas verticales con el norte, el sur, el este y el oeste marcados.
La aplicación usa OpenGL para mostrar los puntos cardinales sobre la imagen de la cámara. A partir de los datos del acelerómetro y del magnetómetro obtenemos la matriz de rotación que nos permite mostrar la dirección correcta en la pantalla.
La imagen de la cámara
Es código para mostrar la imagen de la cámara es bastante simple, y se puede sacar del ejemplo CameraPreview:
1: public class CameraPreview extends Activity {
2: private Preview mPreview;
3:
4: @Override
5: protected void onCreate(Bundle savedInstanceState) {
6: super.onCreate(savedInstanceState);
7:
8: // Hide the window title.
9: requestWindowFeature(Window.FEATURE_NO_TITLE);
10:
11: // Create our Preview view and set it as the content of our activity.
12: mPreview = new Preview(this);
13: setContentView(mPreview);
14: }
15:
16: }
En la línea 9 indicamos que no queremos que la ventana tenga titulo. Después simplemente instanciamos la vista y se la asignamos a la actividad. La vista es la clase Preview, que hereda de SurfaceView. SurfaceView es un tipo de vista que se caracteriza por contener una superficie (un objeto Surface) sobre la que dibujar.
1: class Preview extends SurfaceView implements SurfaceHolder.Callback {
2: SurfaceHolder mHolder;
3: Camera mCamera;
4:
5: Preview(Context context) {
6: super(context);
7:
8: // Install a SurfaceHolder.Callback so we get notified when the
9: // underlying surface is created and destroyed.
10: mHolder = getHolder();
11: mHolder.addCallback(this);
12: mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
13: }
14: ...
15: }
Además de heredar de SurfaceView, la clase Preview implementa la interfaz SurfaceHolder.Callback. La clase SurfaceHolder es el contendor que nos da acceso a la superficie, la cual no se suele usar directamente. A través de los métodos de esta interfaz nos enteraremos de cuando se crea, destruye o cambia de tamaño la superficie.
Las líneas 10 y 11 sirven para indicarle al SurfaceHolder que notifique los cambios de la superficie a nuestra instancia. La línea 12 especifica el tipo de superficie a usar, en este caso una que no posee sus propios buffers. No tengo muy claro qué significa esto, pero imagino que el resto de los tipos de superficie tendrán uno o dos buffers para renderizar la imagen, mientras que este tipo necesita que le propercionen los buffers con la imagen a buscar. En cualquier caso es el único tipo con el que funciona la cámara.
1: public void surfaceCreated(SurfaceHolder holder) {
2: // The Surface has been created, acquire the camera and tell it where
3: // to draw.
4: mCamera = Camera.open();
5: try {
6: mCamera.setPreviewDisplay(holder);
7: } catch (IOException exception) {
8: mCamera.release();
9: mCamera = null;
10: // TODO: add more exception handling logic here
11: }
12: }
13:
14: public void surfaceDestroyed(SurfaceHolder holder) {
15: // Surface will be destroyed when we return, so stop the preview.
16: // Because the CameraDevice object is not a shared resource, it's very
17: // important to release it when the activity is paused.
18: mCamera.stopPreview();
19: mCamera.release();
20: mCamera = null;
21: }
22:
23: public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
24: // Now that the size is known, set up the camera parameters and begin
25: // the preview.
26: Camera.Parameters parameters = mCamera.getParameters();
27: parameters.setPreviewSize(w, h);
28: mCamera.setParameters(parameters);
29: mCamera.startPreview();
30: }
En estos tres métodos se notifican los eventos relacionados con la superficie donde mostramos la imagen de la cámara. El primero de ellos es surfaceCreated. El bucle de nuestra aplicación recibe un mensaje indicándole que la superficie ha sido creada, e invoca este método pasándole el SurfaceHolder de la misma. En la línea 4 “abrimos” la cámara y en la 6 le decimos que muestre la imagen de previsualización usando la superficie asociada al holder. Si hay algún problema liberamos la cámara.
Cuando la superficie se destruye (por ejemplo, porque se va a iniciar otra actividad) detenemos la previsualización y liberamos la cámara. El ejemplo de la SDK no incluye la llamada a release(), lo que provoca excepciones Out of memory cuando la ventana se destruye y posteriormente se quiere volver a usar la cámara.
El método surfaceChanged se invoca si la superficie cambia de tamaño (si la pantalla gira, por ejemplo). En nuestro ejemplo no se usará, porque vamos a fijar la ventana de la actividad a formato landscape.
Para usar la cámara necesitaremos los permisos adecuados, en este caso <uses-permission android:name=”android.permission.CAMERA” />. Precisamente la última revisión de la SDK corrige un error por el cual no se forzaba correctamente este permiso.
En la próxima entrada comenzaremos a usar OpenGL para añadir la brújula sobre la cámara.
Realidad aumentada en Android – Introducción
Un sistema de realidad aumentada es aquel que combina datos generados por ordenador con imágenes del entorno obtenidas en tiempo real. Un sistema básico de realidad aumentada consiste en dispositivo compuesto por una cámara, una fuente de datos y una pantalla donde se muestra la imagen capturada por la cámara con los datos superpuestos. Los datos que aparecen dependerán de la posición y orientación de la cámara, o bien de la presencia en la imagen (y por lo tanto en la escena real) de marcadores que indiquen la información a mostrar.
- Una cámara. No es necesario que grabe vídeo, basta con que la pantalla del móvil sirva como visor.
- Una interfaz de programación que permita acceder a la imagen proporcionada por la cámara.
- El GPS.
- El sensor de orientación
Imaginemos que queremos diseñar una aplicación de realidad virtual que identifique edificios y lugares de interés turístico, mostrando sobre los mismos su nombre, fecha de construcción, horarios de visita… En este caso se plantea el problema técnico de mostrar esta información sobre la parte de la pantalla donde se esté viendo el lugar correspondiente. Para ello necesitamos que la información esté geoposicionada (disponer de las coordenadas del lugar), y necesitamos una forma de relacionar estas coordenadas con la posición y la orientación de la cámara, las cuales obtendremos de los sensores del dispositivo.
Esta última parte es la más complicada desde un punto del desarrollador. En los próximos artículos vamos a ver una serie de ejemplos básicos, con código, para hacernos una idea de cómo se podría desarrollar una aplicación de este tipo.
Errores emulando el GPS en Android
Existen dos formas de enviar datos al GPS del emulador de Android: con el DDMS (Dalvik Debug Monitor Service) y con la instrucción ‘geo’ de la consola del emulador (como se explica en la documentación). El DDMS es especialmente útil, ya que permite cargar archivos kml o gpx a partir de los cuales enviar actualizaciones periódicas al emulador. Sin embargo, en la versión 1.5 r2 de la SDK de Android hay dos bugs que resultan bastante incómodos.
El GPS no recibe ninguna señal
El primero de ellos está relacionado con el formato de las coordenadas enviadas al emulador. Por algún motivo (probablemente relacionado con el carácter usado para separa los decimales, la coma en español y punto en inglés) el emulador sólo recibe correctamente las actualizaciones si la configuración regional está establecida a idioma inglés.
Para solucionar este problema basta cambiar el locale del runtime de java. Usando DDMS, se establece la siguiente variable de entorno: ‘java_debug=-Duser.language=en’, o, si usamos el plugin para Eclipse, se añade ‘-Duser.language=en’ al archivo eclipse.ini. Más información en la incidencia abierta en Google Code.
El GPS sólo recibe la primera localización
| Actualización: La versión 1.5 release 3 de la SDK ya corrige este error |
El segundo error se produce cuando intentamos enviar varias actualizaciones al GPS del emulador. Por algún motivo las localizaciones enviadas después de la primera no llegan al emulador. Esto significa que el método onLocationChanged sólo será invocado una vez. En la discusión sobre la incidencia abierta se indica una solución provisional: cancelar la subscripción al LocationListener y volverla a subscribir:
public void onLocationChanged(Location location) {
mLocationManager.removeUpdates(locationListener);
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
3000,
1,
locationListener);
...
}
No he podido confirmar que este error afecte a todo el mundo que usa la SDK 1.5 r2, aunque sí está bastante extendido. Es de suponer que Google lo corregirá en la próxima revisión de la SDK.
Notificaciones ‘push’ en dispositivos móviles
¿Qué es una notificación push?
Es un mensaje que una aplicación servidora envía a una aplicación cliente indicándole que tiene algún tipo de información nueva disponible. Lo que distingue a las notificaciones push es que el servidor inicia la comunicación, no espera a que el cliente pregunte si hay algo nuevo. La ventaja es la inmediatez: la información llega al cliente (y por tanto al usuario) en cuanto está disponible en el servidor.
El ejemplo más clásico de este tipo de notificaciones es el correo electrónico. Un usuario tiene una aplicación de correo electrónico ejecutándose en su dispositivo. Alguien le envía un correo, que queda almacenado en el servidor. A partir de ahí pueden ocurrir dos cosas:
- El cliente de correo electrónico está configurado para consultar al servidor, con una frecuencia predeterminada, si hay nuevos correos. Si la frecuencia es de una vez cada 5 minutos el usuario puede tardar ese tiempo en enterarse de que ha recibido un correo nuevo.
- El cliente (y el servidor) soportan notificaciones push. En este caso el servidor envía un mensaje al cliente para avisarle de que ha llegado un nuevo correo, y el cliente a su vez se lo notifica de alguna forma al usuario para que lo lea si le interesa.
En el caso de las aplicaciones de mensajería instantánea la necesidad de la inmediatez es aún más manifiesta: para que la conversación sea fluida necesitamos que los mensajes nos lleguen en cuanto son enviados, e incluso queremos saber cuándo nuestro interlocutor está escribiendo algo.
¿Cómo se implementa una notificación push?
La manera habitual de implementar una notificación push es establecer una conexión TCP de larga duración. El cliente abre una conexión TCP con el servidor y la deja abierta. Sobre esta conexión TCP el servidor enviará las notificaciones al cliente, usando algún protocolo de aplicación. IMAP y XMPP son ejemplos de protocolos estándar usados para correo y mensajería respectivamente, aunque existen múltiples protocolos propietarios.
Notificaciones push en BlackBerry
El éxito de las BlackBerry se debe en parte a su sistema de push email. Cuando leer el correo desde el móvil era una experiencia dolorosa en otros sistemas, las BlackBerry ya conseguían que sus usuarios recibiesen el correo de manera instantánea. RIM subscribe un acuerdo con los operadores de telefonía, por el cual se establece una conexión permanente con unos servidores especiales operados por RIM. Estos servidores reciben el correo del usuario desde los servidores BES, y lo notifican de forma inmediata a las BlackBerry.
La tecnología de notificaciones push de RIM está disponible para terceros a través de la BlackBerry Push API.
Notificaciones push en iPhone
Apple incorporó desde el principio un sistema de notificaciones push para el correo electrónico de Yahoo. La aparición de MobileMe añadió notificaciones push tanto para el correo como para el calendario y los contactos.
Cuando Apple diseñó la SDK del iPhone tomó la decisión de no permitir a los desarrolladores crear aplicaciones que se ejecutasen en segundo plano. Como consecuencia, las aplicaciones desarrolladas por terceros sólo se ejecutan mientras están en primer plano, deteniéndose cuando el usuario quiere realizar otra actividad.
La nueva versión de la SDK incorpora el Apple Push Notification service, un servicio que envía notificaciones a las aplicaciones del iPhone aunque estas no se estén ejecutando. Para ello la notificación pasa primero por los servidores de Apple, con los que el móvil mantiene siempre una conexión abierta. La notificación se envía al móvil y se muestra al usuario como un mensaje de texto o como una ventana de notificación, que servirán además para lanzar la aplicación y procesar la notificación recibida.
Notificaciones push en Android
Android proporciona notificaciones push para el correo electrónico, calendario y contactos, tanto en el HTC Dream como en el HTC Magic. Como en los casos anteriores, el sistema establece un canal siempre abierto con los los servidores del proveedor (en este caso Google).
Aunque los desarrolladores de Android tienen la opción de programar aplicaciones que se ejecuten en segundo plano el sistema se reserva el derecho de detener cualquier aplicación cuando así lo decida, en función de los recursos disponibles en la máquina. Así que podemos desarrollar un cliente que mantenga conexiones TCP de larga duración con el servidor ejecutándose en bakcground, pero no podemos garantizar que la aplicación se esté ejecutando para mantener esta conexión abierta. Claro que este problema se presenta aunque queramos traernos la información usando pull, es decir, consultando al servidor de periódicamente.
En las versiones beta de la SDK se incluía un servicio llamada XMPPService o GTalkService, que permitía a los desarrolladores enviar notificaciones utilizando la infraestructura de Google Talk. Este servicio fue retirado de la versión final de la SDK por problemas de seguridad, como se explica aquí: Some information on APIs removed in the Android 0.9 SDK beta. No sería extraño que Google incluyese una versión mejorada en alguna de las próximas versiones de Android.
T-Mobile G1: dudas
- ¿Cómo funcionará el Android Marketplace? ¿Aplicará Google restricciones sobre el tipo de aplicaciones que se pueden publicar? Parece que en la presentación insinuaron que existirá algún tipo de proceso de validación.
- El G1 sólo funcionará con SIMs de T-Mobile. Además, se impide el uso de aplicaciones de voz sobre IP en las conexiones a través de la red de T-Mobile, limitando su uso a conexiones WiFi. ¿Veremos algún dispositivo libre? ¿A qué precio?
- ¿Cómo es de estable el sistema? La versión 1.0 de la SDK acaba de salir, así que habrá que esperar unas semanas antes de comprobar si es mínimamente usable. ¿Cuánto dura la batería?
- ¿Era necesario que fuese tan feo?
Programando en Android – NotePad (II)
La clase NotesList
La actividad principal de la aplicación NotePad es NotesList, como se puede ver en el manifest. La clase NotesList se define en el archivo NotesList.java, y es la responsable de la pantalla principal de la aplicación, mostrando una lista de las notas disponibles. Para ello hacemos que esta clase herede de ListActivity, un tipo de actividad especial diseñada para enlazarse a un cursor y mostrar los elementos del cursor en una lista.
Al iniciar la aplicación se invoca esta clase a través de su método onCreate:
1: @Override
2: protected void onCreate(Bundle icicle) {
3: super.onCreate(icicle);
4:
5: setDefaultKeyMode(SHORTCUT_DEFAULT_KEYS);
6:
7: Intent intent = getIntent();
8: if (intent.getData() == null)
9: intent.setData(NotePad.Notes.CONTENT_URI);
10:
11: setupListStripes();
12:
13: Uri uri = intent.getData();
14: mCursor = managedQuery(uri, PROJECTION, null, null);
15:
16: ListAdapter adapter = new SimpleCursorAdapter(this,
17: R.layout.noteslist_item, mCursor,
18: new String[] {NotePad.Notes.TITLE},
19: new int[] {android.R.id.text1});
20: setListAdapter(adapter);
21: }
En la línea 5 simplemente habilitamos los atajos de teclado. Las líneas 7-9 establecen el esquema de datos sobre el que vamos a operar, que se define en la clase NotePad. Lo veremos más adelante.
Las líneas 13 y 14 acceden a los datos. Las referencias a los orígenes de datos son objetos de tipo Uri, y en nuestro caso tienen esta forma:
1: public static final Uri CONTENT_URI =
2: Uri.parse("content://com.google.provider.NotePad/notes");
El enlace entre este Uri y la base de datos se realiza en la clase NotePadProvider, que veremos en otra entrada. Basta decir por ahora que la línea 14 accede a la base de datos para abrir un cursor con las columnas especificadas por PROJECTION, que se define al comienzo de la clase NotesList:
1: private static final String[] PROJECTION = new String[] {
2: NotePad.Notes._ID, NotePad.Notes.TITLE };
3: private Cursor mCursor;
Como decíamos al principio, la actividad NotesList hereda de ListActivity, lo que le permite mostrar datos en una lista. Para ello creamos un adaptador ListAdapter, como se muestra en las líneas 16-19. La clase SimpleCursorAdaptor es un creador genérico de adaptadores, al que indicamos el layout que vamos a usar (en nuestro caso R.layout.noteslist_item), el cursor que hemos definido, los nombres de las columnas a mostrar (en este caso sólo el título), y los ids de los controles (tienen que ser del tipo TextView) que van a mostrar cada columna. ´
Finalmente el método setListAdapter asigna el ListAdapter a nuestra actividad para mostrar los datos.
Nos queda por ver el método setupListStripes:
1: private void setupListStripes() {
2: Drawable[] lineBackgrounds = new Drawable[2];
3:
4: lineBackgrounds[0] =
5: getResources().getDrawable(R.drawable.even_stripe);
6: lineBackgrounds[1] =
7: getResources().getDrawable(R.drawable.odd_stripe);
8:
9: View view = getViewInflate().inflate(
10: android.R.layout.simple_list_item_1, null, null);
11: TextView v = (TextView)view.findViewById(android.R.id.text1);
12: v.setText("X");
13: v.measure(
14: View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.EXACTLY, 100),
15: View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.UNSPECIFIED, 0)
16: );
17: int height = v.getMeasuredHeight();
18: getListView().setStripes(lineBackgrounds, height);
19: }
Entradas anteriores:
Programando en Android – NotePad (I)
Programando en Android – Conceptos iniciales (II)
Programando en Android – Conceptos iniciales (I)
Programando en Android – Prólogo
Programando en Android – NotePad (I)
La mayoría de los mortales olvidamos cualquier conocimiento abstracto en la décima parte del tiempo que nos costó adquirir dicho conocimiento (dato completamente inventado, pero en mi caso muy próximo a la realidad). Así que lo que vamos a hacer en concretar este conocimiento, y ponernos a programar.
El programa NotePad es un ejemplo que se incluye en la documentación de Google y en la SDK. Es un programa muy simple: permite crear notas, editarlas, borrarlas y modificar el título. Vamos a ver cómo funciona.
El ejemplo NotePad. El archivo Manifest.
Comencemos por el Manifest, que es donde se definen los componentes de la aplicación. El manifest.xml es, como su extensión indica, un archivo xml, que desde hace unos años se convertido en un formato habitual para contener configuraciones. El elemento raíz se llama manifest y contiene el namespace de la aplicación. Dentro se define el elemento aplicación:
<application android:icon="@drawable/app_notes" android:label="@string/app_name">
Aquí asociamos un nombre y un icono a la aplicación. La ‘@’ nos indica una referencia a un recurso. En general tiene este formato: @[package:]type/name, donde el paquete es opcional y sólo se indica cuando no pertenece a nuestra aplicación, el tipo corresponde a uno de los definidos en la carpeta res, y el nombre indica el identificador del recurso. En nuestro caso, el icono se encuentra bajo la carpeta drawable, y la app_name se define en el archivo strings.xml de la carpeta values.
Dentro del elemento application lo primero que aparece es un provider, que nos dará acceso a la base de datos y que veremos más adelante. Tras él podemos ver definidas tres Activities: NotesList, NoteEditor y TitleEditor. Corresponderán a cada una de las ventanas: la que muestra las notas, la que las edita y la que modifica el título. Por ejemplo, este el elemento TitleEditor:
<activity android:name="TitleEditor" android:label="@string/title_edit_title" android:theme="@android:style/Theme.Dialog">
Además de indicar el nombre de la actividad, y la etiqueta que aparecerá en la ventana asociada, también especificamos que vamos a utilizar un tema concreto para esta actividad. Los temas nos permiten cambiar el look&feel de las aplicaciones con temas predefinidos o creados por nosotros. En este caso podemos ver como el recurso correspondiente al tema se referencia a través del paquete android, ya que está definido en el sistema, no en nuestra aplicación.
Dentro de cada activity hay definidos intent-filters, que permiten concretar el ámbito en el que se van a ejecutar, como ya vimos anteriormente. Por ejemplo, en NotesList encontramos:
<intent-filter>;
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
que indica que esta es la actividad principal de la aplicación, y que aparecerá en el menú de aplicaciones del sistema. El filtro
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
</intent-filter>
nos indica que la actividad está disponible para ver, editar o seleccionar elementos del tipo vnd.android.cursor.dir/vnd.google.note, que es el tipo que definiremos más adelante para las notas. Finalmente, el filtro
<intent-filter>
<action android:name=”android.intent.action.GET_CONTENT” />
<category android:name=”android.intent.category.DEFAULT” />
<data android:mimeType=”vnd.android.cursor.item/vnd.google.note” />
</intent-filter>
permite al usuario seleccionar el tipo de dato vnd.android.cursor.dir/vnd.google.note. A diferencia de la acción android.intent.action.PICK, donde se selecciona un elemento de un conjunto de datos, aquí se selecciona un tipo de dato para que el usuario haga algo con él.
Todo esto se verá más claro al examinar el código de las actividades.
Entradas anteriores:
Programando en Android – Conceptos iniciales (II)
Programando en Android – Conceptos iniciales (I)
Programando en Android – Prólogo
Programando en Android – Conceptos iniciales (II)
Intents
Si las Activities son básicamente pantallas, las “intenciones” o Intents son la manera de invocar estas Activities. La definición breve de la documentación es: “Un intent es la descripción abstracta de una operación que se va a llevar a cabo”. O dicho de otro modo, un Intent es una clase que permite especificar una Activity a ejecutar, llamando a uno de los métodos de la clase Activity con ese Intent de parámetro. Parece fácil, pero he de confesar que en la documentación de Android el asunto me pareció un poco confuso, sobre todo por la cantidad de información que puede ir asociada a estas clases.
Dos formas de llamar a una Activity
Explicitamente o implicitamente. La forma explícita es simple de entender: creamos un Intent indicando el nombre de la clase correspondiente a la actividad y el paquete, llamamos a startActivity (o startSubActivity si queremos que nos notifiquen cuándo finaliza dicha actividad) y listo. El sistema busca la clase y crea la instancia, pasándo los datos que podamos haber añadido al Intent en el objeto Bundle del método onCreate de la nueva instancia.
// ClaseActividad1 es la clase de la actividad
//que queremos iniciar. El parámetro this indica
//el Context actual, para saber en qué
//package buscar esta clase
Intent i = new Intent(this, ClaseActividad1.class);
// Esta información se recuperará en el objeto Bundle de onCreate
i.putExtra("NombreParametro", valorParametro);
startActivity(i);
La invocación implícita de una actividad se realiza también a través de la clase Intent. Es implícita porque no se indica el nombre de la clase correspondiente a la actividad a invocar, sino que se establecen una serie de criterios, y se deja que el sistema elija una actividad que cumpla esos criterios.
Intenciones y criterios
A un Intent podemos asociarle una acción, unos datos y una categoría. Y aquí está el verdadero quid de esta clase. Las actividades pueden declarar el tipo de acciones que pueden llevar a cabo y los tipos de datos que pueden gestionar. Las acciones son cadenas de texto estándar que describen lo que que la actividad puede hacer. Por ejemplo, android.intent.action.VIEW es una acción que indica que la actividad puede mostrar datos al usuario. Esta acción viene predefinida en la clase Intent, pero es posible definir nuevas acciones para nuestras actividades. La misma actividad puede declarar que el tipo de datos del que se ocupa es, por ejemplo, “vnd.android.cursor.dir/person”. También puede declarar una categoría, que básicamente indica si la actividad va a ser lanzada desde el lanzador de aplicaciones, desde el menú de otra aplicación o directamente desde otra actividad. En el AndroidManifest.xml quedaría algo así:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.dir/person" />
</intent-filter>
Así, para llamar implícitamente a una actividad a través de un intent, en vez de asignar el nombre de la clase le asignamos una de las acciones que esta puede llevar a cabo, con el tipo de datos adecuado. Las reglas exactas se indican en la documentación de la clase IntentFilter.
Conclusiones
Activities e Intents son los dos ejes sobre los que gira la arquitectura de las aplicaciones Android. Existen muchos más conceptos importantes, por supuesto, pero a partir de aquí lo mejor es verlo funcionando todo en una aplicación ejemplo. Pero eso será en la próxima entrada.
Entradas anteriores:
Programando en Android – Conceptos iniciales (I)
Programando en Android – Prólogo
Programando en Android – Conceptos iniciales (I)
El primer paso para relacionar conceptos es conocer los conceptos. Y en Android existen una serie de conceptos que suponen la piedra y el mortero de cualquier aplicación.
El archivo AndroidManifest.xml
Este archivo está presente en todas las aplicaciones Android. Su contenido especifica los componentes de la aplicación, así como la configuración global de la misma. Su descripción se muestra en esta página de la documentación.
En una aplicación habitual, dentro de este archivo habrá un elemento <application>, dentro del cuál habrá uno o varios elementos <activity>. Cada uno de estos elementos supone una interacción con el usuario (generalmente una ventana), y se corresponde con una clase que hereda de la clase Activity.
La clase Activity
Según la documentación de Google, una Activity es una cosa única con un objetivo determinado que el usuario puede hacer. Esta es una definición abstracta. Podemos concretar más la definición diciendo que una Activity (es decir, una clase de nuestra aplicación que hereda de la clase Activity) se presenta al usuario como una ventana. Esta clase crea una ventana que muestra una interfaz de usuario, la cual está definida a su vez en una instancia de otra clase, la clase View.
Cuando se ejecuta una aplicación Android lo primero que se muestra al usuario es la ventana definida por la actividad que esté marcada en el AndroidManifest.xml como principal. Las actividades se gestionan como una pila, así que desde una actividad se puede llamar a otra, y cuando esta finaliza se retorna a la actividad inicial.
Una actividad puede estar ejecutándose, en pausa o detenida. Simplificando, está en ejecución cuando es visible e interacciona con el usuario, está en pausa cuando es visible pero otra ventana, transparente o que no ocupe toda la pantalla, tiene el foco, y está detenida cuando no es visible. En todos estos casos la clase mantiene su información.
En la documentación encontramos un gráfico que ilustra el ciclo de vida de una actividad:
Aunque no es necesario entender de momento todos los detalles de este gráfico, en él se ven los estados por los que puede pasar una actividad (los óvalos coloreados) y los eventos que se disparan en dichos estados (los rectángulos grises):
- Cuando se crea una actividad, se invoca el evento
onCreate(). Este evento sólo se invoca la primera vez que se llama a una actividad, o bien cuando se llama después de que el sistema haya tenido que eliminarla por falta de recursos (más sobre esto en próximos artículos). onStart()es el evento invocado cuando cada vez que la actividad se muestra al usuario. Es decir, la primera vez que se muestra, y las veces que en las que vuelve a aparecer tras haber estado oculta. En este último caso, se invocaonStop()al desaparecer yonRestart()inmediatamente antes de reaparecer.onFreeze()yonPause()son llamadas secuencialmente cuando otra actividad va a pasar en encargarse de la interacción con el usuario. TrasonPause()la actividad permanece en un estado de espera en el que puede ocurrir que la aplicación sea destruida, por lo que estos eventos se usan para consolidar la información que no queremos que se pierda. Si la actividad no se destruye volverá al primer plano con el eventoonResume().
La idea importante con la que quedarse es que una actividad que esté pausada o detenida (tras onPause() u onStop()) puede ser destruida por el sistema si previo aviso, por lo que deberemos encargarnos de guardar antes la información necesaria (durante onFreeze() y onPause()). Los detalles lo veremos en una próxima entrada.






