Вы находитесь на странице: 1из 179

BASES DE DATOS Y XML

NDICE
6.1 BASES DE DATOS .................................................................. 275 6.1.1 Introduccin ...........................................................................275 6.1.2 Teora sobre Bases de Datos ..............................................275 6.1.3 Ventajas de las bases de datos ..........................................279 6.1.4 Bases de datos relacionales................................................281 6.1.5 Diseo de bases de datos ...................................................283 6.2 SQLite en Android .................................................................. 284 6.2.1 Gestin de la informacin en Android ........................... 284 6.2.2 Gestin de la Base de Datos SQLite en Android........... 285 6.2.3 Creacin de Bases de datos SQLite.............................. 286 6.2.4 Modificacin de la informacin de BD SQLite............... 292 6.2.4.1 Mtodo insert() .....................................................293 6.2.4.2 Mtodo update() y mtodo delete() ..................294 6.2.5 Uso de parmetros en los mtodos SQLite................... 294 6.3 Consultas SQLite en Android ............................................... 295 6.3.1 Seleccin y recuperacin de Consultas de BD SQLite ..295 6.3.2 Ejemplo prctico de BD SQLite con Android ................ 298 6.3.3 Acceso y creacin de la Base de datos......................... 299 6.3.4 Recursos de diseo XML ............................................... 303 6.3.5 Actividades..................................................................... 304 6.3.6 Fichero Androidmanifest.xml ......................................... 310 6.4 GESTIN DE FICHEROS XML .............................................. 311 6.4.1 SAX es el modelo clsico en Android ............................ 316 6.4.2 SAX simplificado en Android.......................................... 323 6.4.3 DOM en Android ............................................................ 327 6.4.4 StAX en Android............................................................. 331

Introduccin al entorno Android

6.1

BASES DE DATOS

6.1.1

Introduccin En esta Unidad vamos a repasar, como prembulo , la teora general sobre bases de

datos. Despus, explicaremos cmo gestionar bases de datos SQLite en Android dentro de las aplicaciones. Finalmente, detallaremos el uso de ficheros XML en Android.

6.1.2

Teora sobre Bases de Datos En este apartado vamos a explicar brevemente algunos conceptos

fundamentales sobre las bases de datos. Suponemos que el alumno o alumna ya tiene conocimientos suficientes sobre las mismas y, por tanto, abordaremos slo algunos conocimientos ms relacionados con la forma en que Android accede a las bases de datos y trata su informacin. El trmino base de datos es informtico, pero puede aplicarse tambin a la forma como se almacena, ordena y utiliza manualmente la informacin. Por ejemplo, ya hemos visto en la Unidad 4 cmo leer y almacenar informacin en ficheros de preferencias o archivos en la memoria del dispositivo Android. La informacin de estos ficheros puede ser considerada una base de datos en un sentido amplio. Es decir, se almacena y se consulta la informacin cuando es necesario. Sin embargo, en el sentido informtico, la palabra base de datos se refiere a una coleccin, conjunto o depsito de datos, almacenados en un soporte magntico o de otro tipo, accesibles por mltiples usuarios de forma rpida y eficaz mediante el ordenador a travs de una o de varias aplicaciones informticas independientes de los datos. stos se relacionan entre s y estn organizados de tal manera que es fcil introducirlos, actualizarlos, recuperarlos o llevar a cabo con ellos otras operaciones de gestin. En el caso de Android, las bases de datos son privadas y nicamente una aplicacin puede acceder a ellas para leer y escribir datos. Cuando una aplicacin desea consultar o modificar la informacin de la base de datos de otra aplicacin, Android dispone de los Content Providers que permiten a otras aplicaciones hacer las peticiones necesarias a la aplicacin que alberga la base de datos. sta devuelve a la aplicacin la informacin solicitada con los resultados de esas operaciones. Android usa SQLite como motor de base de datos relacional. En el siguiente apartado veremos sus caractersticas. La informacin que mostramos a continuacin est basada en la versin 3 de SQLite. 275

Generalmente, en las bases de datos relacionales, de las que hablaremos despus, la informacin est almacenada y organizada en ficheros formados por filas y columnas, como puede verse en el ejemplo siguiente, en el que se presentan algunos datos de cinco libros de una biblioteca: Columnas Ttulo Autor Editorial Seix Barral

Antonio El invierno en Lisboa Muoz Molina

Tener o ser? Filas

Erich Fromm

Fondo de Econmica

Cultura

Crnica de una Gabriel Garca Bruguera muerte anunciada Mrquez Hermann Hesse

El lobo estepario

Anaya Editores

La vida est en otra Milan Kundera Seix Barral parte

Cada fila contiene el ttulo, el autor y la editorial de un libro y se relaciona con las dems filas gracias a que incluye el mismo tipo de informacin (datos de los libros) y en todas ellas la informacin est organizada de la misma forma: la primera columna contiene el ttulo del libro, la segunda, el autor y la tercera, la editorial. As pues, una base de datos contiene un conjunto de ficheros cuya informacin est organizada de tal forma que puede ser tratada informticamente con rapidez y eficacia. La informacin de una base de datos puede almacenarse en un solo fichero o en varios. Los ficheros de una base de datos estn grabados en el servidor. Tienen un nombre (por ejemplo, flores, ros, libros, coches, amigos, artculos, clientes, ventas, facturas, etctera). Su denominacin debe seguir las normas establecidas para que el nombre de un fichero sea correcto. Como puede verse, hemos prescindido de las tildes en los identificadores que las llevan ortogrficamente. El tipo o extensin de estos ficheros de base de datos puede ser muy variado, segn el tipo de base de datos utilizado: en dBase es dbf (Data Base File, Fichero de Base de Datos), en Access mdb, en Interbase db o dbf, en MySQL myd, etctera. En el caso de SQLite en Android, el archivo de la base de datos suele tener la extensin .db, si bien, puede almacenarse en el directorio especfico de la aplicacin con

276

Introduccin al entorno Android

cualquier extensin e, incluso, sin ella. En otros sistemas operativos, los archivos de una base de datos de tipo SQLite suelen tener la extensin .sqlite. Las filas de un archivo de base de datos se denominan registros y las columnas, campos (fields, en ingls). As pues, un fichero de base de datos est integrado por registros, que son cada uno de sus elementos o componentes (flor, ro, libro, coche, amigo, artculo, cliente, venta o factura). Todos los registros contienen un conjunto de campos en los que se almacena su informacin; este conjunto define la estructura del fichero que integra una base de datos. En la representacin grfica siguiente puede observarse, en forma de tabla, la estructura de un fichero que contiene una base de datos con informacin sobre personas: Campos

Nombre Sueldo 1 2 3 4 Registros 5 6 7 8

Fecha_nac

Observacion Foto

En las filas aparecen hasta once registros, cada uno de los cuales, en este caso, contiene los cinco campos siguientes: Nombre, Sueldo, Fecha_nac, Observacion y Foto. En el ejemplo anterior slo se han incluido once registros y cinco campos, pero de hecho en las bases de datos que vamos a usar el nmero de registros es ilimitado (depende de la capacidad del soporte) y el de campos es muy amplio, segn el tipo de base de datos usada. Todos los registros tienen los mismos campos. Si comparamos un fichero de base de datos con los archivadores de una biblioteca, podemos decir que stos integran la base de datos. Cada archivador es como un fichero de la base de datos, las fichas que hay en su interior son los registros y los apartados de cada ficha (ttulo, autor, editorial, etctera) son los campos.

277

Cada campo de un registro tiene un nombre, un tipo, una longitud o ancho, un nmero de decimales si es de tipo numrico o de coma flotante y un ndice opcional. Segn el tipo de base de datos que se est utilizando, el identificador del campo, la clase de tipos y la longitud de los mismos pueden ser diferentes. Vamos a centrarnos en los tipos de campos que define SQLite. Este tipo de base de datos no define todos los tipos de campos tpicos en bases de datos relacionales. nicamente define unos tipos de campos bsicos y luego los reutiliza para especificar otros tipos de campos. El nombre de cada campo puede ser muy largo, si bien recomendamos que en el orden prctico sea lo ms breve posible y tenga algn significado. Debe atenerse a las reglas de todos los identificadores ya comentadas anteriormente. Hay estos tipos de campos bsicos en SQLite: 1. Campo de tipo Carcter. Es el ms comn (letras, dgitos, signos, etctera), y contiene informacin que es tratada como una cadena de caracteres. Se asigna este tipo a un campo cuando no se realizan operaciones aritmticas con sus datos, ni contiene una fecha, ni es un texto mayor de 255 caracteres. Por ejemplo, se asigna este tipo al campo cuyo contenido va a ser el nombre de una persona, sus apellidos, domicilio, localidad, provincia, etctera. Admite ndice. En SQLite hay un tipo nico de campo para almacenar datos de esta clase: TEXT que tiene una longitud mxima de 255 caracteres. Tambin podemos usar los siguientes tipos de campos de texto en SQLite: CHARACTER(20): campo de texto con 20 caracteres de longitud. VARCHAR(255): campo de texto de longitud variable hasta 255 caracteres. VARYING CHARACTER(255): similar al anterior. NCHAR(x): campo de texto con x caracteres de longitud. NATIVE CHARACTER(70): campo de texto con 70 caracteres de longitud. NVARCHAR(100): campo de texto de longitud variable hasta 100 caracteres. CLOB: campo similar a TEXT. Todos estos tipos de campo de texto se pueden definir al crear una tabla, si bien, internamente, SQLite los traduce por afinidad al tipo TEXT inicial. 2. Campo de tipo Numrico. Se utiliza para escribir nmeros, incluidos los signos positivo y negativo. Se asigna este tipo a un campo cuando se realizan operaciones aritmticas con nmeros enteros o reales, como sumar, restar, multiplicar, dividir, etctera. Admite ndice. SQLite admite estos valores para determinar los campos de este tipo: INTEGER y REAL. Como puede verse, en realidad los valores posibles se refieren a si es un campo de nmero entero o decimal. Podemos usar los siguientes tipos de campos de tipo entero en SQLite:

278

Introduccin al entorno Android

INT TINYINT SMALLINT MEDIUMINT BIGINT UNSIGNED BIG INT INT2 INT8 Todos estos tipos de campo de nmero entero se pueden definir al crear una tabla, si bien, internamente, SQLite los traduce por afinidad al tipo INTEGER anterior. En el caso del tipo de campo numrico con decimales, podemos usar los siguientes tipos de campos: DOUBLE DOUBLE PRECISION FLOAT Todos estos tipos de campo de nmero con decimales se pueden definir al crear una tabla, si bien, internamente, SQLite los traduce por afinidad al tipo REAL anterior. 3. Campo de tipo Fecha y Lgico. Puede contener fechas y tiempos (horas, minutos, segundos) o almacenar valores lgicos (true / false). Admite ndice. SQLite define el tipo de campo interno NUMERIC para almacenar otros tipos de campos necesarios en las aplicaciones en una tabla, tales como los campos lgicos o de fecha, as como los que establecen los decimales exactos en un campo numrico. Podemos usar los siguientes tipos de campos en SQLite: DECIMAL(10,5) BOOLEAN DATE DATETIME Todos estos tipos de campo se pueden definir al crear una tabla, si bien, internamente, SQLite los traduce por afinidad al tipo NUMERIC anterior. 4. Campo de tipo Memo. Es un campo de longitud variable que admite gran cantidad de texto o datos binarios segn nuestras necesidades. Para cada registro tendr una longitud distinta, segn la cantidad de datos que se introduzcan en este campo. No admite ndice. SQLite admite nicamente BLOB.

6.1.3

Ventajas de las bases de datos Hemos dicho que los archivadores de una biblioteca o de una agenda pueden

considerarse, en cierta forma, bases de datos, pues en ellos se almacena informacin en un 279

determinado orden y es posible buscar esta informacin, consultarla, modificarla o eliminarla con facilidad. Sin embargo, todas estas operaciones suelen llevar mucho tiempo y, en ocasiones, no se efectan tan fcilmente como desearamos. Adems, ocupan bastante espacio si la informacin es abundante. Incluso, en ocasiones, algunas operaciones fundamentales son imposibles de realizar manualmente. Por ejemplo, si tenemos 1.000 fichas bibliogrficas ordenadas por autor y necesitamos ordenarlas por ttulo, la operacin ha de realizarse manualmente, mirando una a una cada ficha, lo cual puede hacerse muy largo y pesado. Podamos haber escrito dos ejemplares de cada ficha, uno para el archivo por autores y otro para el de ttulos, pero esto hubiera llevado el doble de tiempo, de trabajo y stas ocuparan el doble de espacio. Supongamos ahora que necesitamos seleccionar todas las fichas en las que aparece la misma editorial. De nuevo la tarea puede parecernos pesada y larga, y lo es. No digamos si se cambia la situacin de los libros en los armarios de la biblioteca. Tambin ser necesario modificar la signatura en las fichas. Hemos puesto este ejemplo para explicar los graves problemas que se derivan de la gestin manual de la informacin. Las dificultades aumentan a medida que crece el volumen de informacin que debe manejarse y segn sean los criterios de ordenacin y seleccin. En una base de datos informtica, en cambio, al gestionarse la informacin automticamente, muchos de los problemas anteriormente mencionados desaparecen. En primer lugar, la rapidez de las operaciones fundamentales (introduccin de datos, ordenacin por diferentes campos, consultas, bsquedas, elaboracin de informes, actualizacin y modificacin de los datos, etctera) aumenta de una forma muy destacada. En segundo lugar, el espacio que ocupa una base de datos es mucho menor que el de cualquier otra forma de archivo manual. En un disco flexible de 3,5 pulgadas puede almacenarse casi un milln y medio de caracteres. En los discos duros de los actuales servidores el volumen de informacin puede ser prcticamente ilimitado. En tercer lugar, las operaciones fundamentales de gestin de la informacin son automticas, lo cual hace que sean menos pesadas y tediosas si son llevadas a cabo por el ordenador. As pues, el trabajo se humaniza y el tiempo libre de las personas que manejan la informacin es mayor. Finalmente, la seguridad de los datos informatizados tambin es mayor que la contenida en archivos de tipo manual, pues el ordenador nos permite hacer rpidamente cuantas copias queramos de esa informacin en diferentes soportes. Desde la aparicin de los ordenadores, stos se han dedicado al almacenamiento y organizacin de grandes volmenes de datos. Igualmente, se han aplicado a la evaluacin de 280

Introduccin al entorno Android

las diversas soluciones propuestas para resolver los problemas de estructuracin y acceso a dicha informacin.

6.1.4

Bases de datos relacionales Se ha descubierto que la mejor forma de resolver estos problemas es organizar la

informacin de forma relacional. De aqu ha surgido el concepto de bases de datos relacionales (RDBMS, Relation DataBase Management System). El fundamento terico de las bases de datos relacionales es complejo, ya que se basa en el concepto matemtico de relacin entre los elementos de un conjunto. Sus caractersticas y propiedades formales requieren ciertos conocimientos de la teora de conjuntos. Sin embargo, en la prctica, el concepto de relacin es muy sencillo de utilizar porque en sta la organizacin de los datos es muy clara e intuitiva. En otros tipos de organizacin de la informacin, como las bases de datos jerrquicas o las bases de datos en red, anteriores a las relacionales, aparecan distintas categoras de datos y estructuras muy complejas y poco flexibles que dificultaban la posibilidad de relacionar stos con eficacia y rapidez. En cambio, en las bases de datos relacionales la informacin se organiza en ficheros que tienen estructura tabular o en forma de tabla, en la que todos los datos tienen la misma categora. Cada tabla tambin recibe el nombre de relacin. Por ejemplo, en el grfico siguiente puede observarse una tabla que contiene diversos datos de personas:
Cabecera 1 2 Filas (Registros) 3 4 5 Nombre Len Garca Mara Prez Direccin C/ Zurita, 25 C/ Flores, 15 Edad 25 30 Sexo V M Profesin Admtvo. Abogada

Jos Rodrguez

C/ Ro Sil, 11

50

Dependiente

Juana de Dios Begoa Lpez

Avda. Canarias, 50 Pza. Segovia, s/n

70 15

M M

Jubilada Estudiante

Columnas (Campos)

Como se ve, una tabla consta de filas y de columnas; en cada columna, denominada campo en la base de datos, hay un dato: Nombre, Direccin, Edad, etctera; cada fila es un registro que contiene todos los datos de los elementos de la base. Cada tabla tiene un 281

registro especial, denominado cabecera, que contiene los nombres de los campos y sus atributos (tipo y longitud). Generalmente, una base de datos no consta de una sola tabla, sino de varias. Grficamente puede representarse as:

Estas tablas no son independientes unas de otras, sino que tienen al menos un campo comn con las otras a travs del cual se puede acceder a la informacin que contienen todas en conjunto. Por ejemplo, la base de datos de una biblioteca puede estar integrada por una tabla de libros, otra de lectores, otra de prstamos y otra de editoriales. El fichero de libros puede contener la informacin completa de cada volumen: ttulo, autor, editorial, ao de edicin, precio, nmero de pginas, cdigo de materia, nmero de registro, etctera. El fichero de editoriales contendr los datos de cada entidad editora: nombre, direccin, telfono, plazo de entrega, descuentos, etctera. El fichero de lectores estar integrado por los datos personales y profesionales de stos: nombre, DNI, direccin, telfono, profesin, centro de trabajo, nmero de carn, etctera. El fichero de prstamos contendr datos de este tipo: nmero de registro del libro prestado, nmero de carn del lector, fecha del prstamo, plazo, etctera. Como puede verse, la informacin no debe repetirse en todos los ficheros, pero s debe poder relacionarse. Por ejemplo, los ficheros de libros y editoriales, tienen en comn el campo EDITORIAL. Los ficheros de libros y prstamos tienen en comn, al menos, el NMERO DE REGISTRO del libro prestado, gracias a lo cual desde uno se puede acceder a los datos del otro. Los ficheros de lectores y prstamos tienen en comn el campo CARN, etctera. 282

Introduccin al entorno Android

Son bases de datos relacionales Microsoft Access, Oracle, SQL Server, MySQL, SQLite y otras.

6.1.5

Diseo de bases de datos El diseo de bases de datos puede presentar distinto tipo de dificultad dependiendo

de la complejidad e interrelacin de los datos que se quiera gestionar. Imaginemos que una compaa area quiere gestionar toda la informacin contenida en una base de datos relativa a los aviones y su mantenimiento, a los vuelos, viajes, destinos, clientes, personal de la empresa, agencias de viajes, billetes, asistencia, etctera. Es evidente que, en este caso, la complejidad es enorme y que para realizar el diseo de esta base se requiere la colaboracin de tcnicos especialistas que faciliten la tarea. Sin embargo, en la mayora de las ocasiones el diseo de una base de datos se resuelve con uno, dos o tres ficheros como mximo. En este caso no es necesario profundizar en aspectos complejos de tcnicas de diseo, sino que basta aplicar el sentido comn para organizar los ficheros de la base de datos de forma coherente. Deben crearse tantos ficheros como categoras o grupos de elementos distintos haya que organizar. Por ejemplo, en una tienda que vende al por menor bastara con crear un fichero de artculos y otro de proveedores, y a lo sumo otros tres: de pedidos, de ventas y de clientes. Antes de ponerse a crear una base de datos con el ordenador, es preciso disearla previamente sobre el papel. La planificacin es fundamental en este caso para evitar errores graves: falta de datos necesarios, repeticin innecesaria de algunos, equivocacin del tipo de campo o falta de precisin en su longitud. Aunque es posible modificar la estructura de una base de datos, una vez creada, se puede perder mucho tiempo e incluso datos en esta operacin. Disear una base de datos consiste en determinar los datos que van a introducirse en ella, la forma como se van a organizar y el tipo de esos datos. Adems, se debe precisar la forma como se van a solicitar y las clases de operaciones que hay que realizar con los mismos: aritmticas, lgicas, de fechas, de carcter, etctera. Tambin conviene conocer los resultados concretos documentos, etctera. A continuacin, se resumen las operaciones que deben llevarse a cabo al disear una base de datos: 1. Atendiendo a la informacin que contiene es preciso: Identificar los diferentes elementos informativos (artculos, clientes, ventas, facturas, etctera) que forman parte de la base de datos. 283 que se espera obtener: consultas, informes, actualizaciones,

Determinar los datos que debe contener cada uno de esos elementos. Precisar el grado de necesidad y de utilizacin de cada dato. Concretar las operaciones que se van a realizar con los datos: aritmticas, lgicas, de salida slo por la pantalla, de salida tambin por la impresora, etctera. Seleccionar el dato o datos esenciales que deben ser el campo clave por el que se ordenarn las unidades o elementos mencionados. Fijar los datos comunes a los diferentes ficheros de la base de datos que van a permitir relacionar la informacin distribuida entre ellos. 2. Atendiendo a la estructura de la base de datos Distribuir la informacin en ficheros segn los diferentes grupos que se hayan hecho (artculos, clientes, etctera) y dar un nombre a cada fichero. Determinar el nombre de cada campo de los registros de cada fichero. Este nombre ha de ser claro y debe significar algo para que pueda recordarse fcilmente. Decidir qu tipo conviene asignar a cada campo segn la clase de operaciones que vayamos a realizar con sus datos. Asignar a cada campo una longitud apropiada para tener los datos fundamentales sin despilfarro de memoria interna ni de espacio en el disco duro o soporte empleado. Establecer un orden lgico y prctico agrupando los campos segn un criterio concreto: clase e importancia de los datos, frecuencia de utilizacin, proximidad, parecido, etctera. Decidir cul o cules van a ser los campos clave permanentes y situarlos al principio de la estructura. No incluir campos que puedan ser el resultado de diversas operaciones de tratamiento posterior. Fijar los campos comunes a todos los ficheros para poder relacionarlos con otros de la misma aplicacin.

6.2

SQLite en Android

6.2.1

Gestin de la informacin en Android Como ya hemos estudiado, en Android existen tres formas de almacenar informacin

para usarla en las aplicaciones: 284 Preferencias de la aplicacin

Introduccin al entorno Android

Ficheros locales en el sistema de archivos del sistema operativo Base de datos SQLite

En la Unidad 4 hemos tratado las dos primeras formas y en esta Unidad 6 veremos las bases de datos.

6.2.2

Gestin de la Base de Datos SQLite en Android SQLite es un motor de bases de datos relacional muy popular por sus caractersticas,

que son muy especiales, como las siguientes: No necesita un servidor, ya que la librera se enlaza directamente en la aplicacin al compilarla. Ocupa muy poco tamao: slo unos 275 KB. Precisa de poca o nula configuracin. Es posible hacer transacciones. Es de cdigo libre.

Android incorpora todas las herramientas necesarias para la creacin y gestin de bases de datos SQLite mediante una API completa. Despus iremos viendo otros comandos para realizar consultas ms complejas. Usar bases de datos Android puede hacer ms lentas las aplicaciones debido a que es necesario escribir y leer informacin de la memoria fsica del dispositivo. Por lo tanto, es recomendable realizar esta operaciones de forma Asncrona, tal como hemos estudiado en la Unidad 3 (Hilos). En los ejemplos de esta Unidad no vamos a incluir hilos, para mostrar nicamente las sentencias de SQLite.

285

6.2.3

Creacin de Bases de datos SQLite La forma usual en Android de crear, modificar y conectar con una base de datos

SQLite consiste en usar la clase Java SQLiteOpenHelper. En realidad, debemos definir una clase propia que derive de ella y personalizarla para adaptarnos a las necesidades concretas de la aplicacin. La clase SQLiteOpenHelper define un nico constructor que, normalmente, no es necesario reescribir y los dos mtodos abstractos onCreate() y onUpgrade() que tendremos que implementar con el cdigo Java necesario para crear la base de datos acorde con las necesidades de la aplicacin. Tambin debemos hacerlo para modificar su estructura si hace falta cambiar los campos que definen alguna tabla al actualizar la versin de la aplicacin. En el Ejemplo 1 de esta Unidad, vamos a crear una base de datos muy sencilla llamada BDBiblioteca.db, con una nica tabla interna llamada Ejemplares que albergar nicamente cinco campos: _id: id registro de tipo INTEGER, ndice PRIMARIO y autoincremental ttulo: de tipo TEXT autor: de tipo TEXT ao: de tipo INTEGER prestado: BOOLEAN

En el caso de Android es obligatorio definir un ndice primario en la tabla con el identificador _id para poder extraer, de manera sencilla, la informacin de los registros de una consulta SQL mediante cursores usando la clase Cursor de Android.

Para

esto,

vamos

crear

la

clase

BibliotecaSQLiteHelper

derivada

de

SQLiteOpenHelper, donde reescribimos los mtodos onCreate() y onUpgrade() para adaptarlos a la estructura de campos anterior:

// La clase se debe heredar de SQLiteOpenHelper public class BibliotecaSQLiteHelper extends SQLiteOpenHelper { //Sentencia SQL para crear la tabla Ejemplares static String createBDSQL = "CREATE TABLE Ejemplares (_id integer primary key autoincrement, titulo TEXT, autor TEXT, anio TEXT, prestado BOOLEAN)";

// Definimos el constructor indicando el contexto de la aplicacin, // el nombre de la base de datos y la versin de la BD

286

Introduccin al entorno Android

public BibliotecaSQLiteHelper(Context contexto, String nombre, CursorFactory factory, int version) { super(contexto, nombre, factory, version); }

@Override // Si la BD no existe, Android llama a este mtodo public void onCreate(SQLiteDatabase db) { //Se ejecuta la sentencia SQL de creacin de la tabla db.execSQL(createBDSQL); }

@Override public void onUpgrade(SQLiteDatabase db, int versionAnterior, int versionNueva) { /* NOTA: para simplificar este ejemplo eliminamos directamente la tabla * anterior y la creamos de nuevo. * Sin embargo, lo normal sera migrar los datos de la tabla antigua * a la nueva estructura de campos, por lo que las sentencias podran * ser del estilo ALTER TABLE. */ //Se elimina la versin anterior de la tabla db.execSQL("DROP TABLE IF EXISTS Ejemplares");

//Se crea la nueva versin de la tabla db.execSQL(createBDSQL); } }

En el cdigo fuente anterior se define la variable esttica (en Java se definen as las constantes) createBDSQL, donde se establece la orden SQL para crear la tabla llamada Ejemplares con los campos alfanumricos descritos anteriormente. ATENCIN: en este curso no se describe la sintaxis del lenguaje SQL, pues se considera que el alumno o alumna conoce cmo usar una base de datos relacional.

287

El mtodo onCreate() se ejecuta automticamente cuando es necesario crear la base de datos, es decir, cuando an no existe y se instala la aplicacin por primera vez. Por lo tanto, en este mtodo debemos crear todas las tablas necesarias y aadir, si fuera necesario, los registros iniciales. Para la creacin de la tabla hemos aplicado la sentencia SQL ya definida en la constante y la ejecutamos en la base de datos utilizando el mtodo ms sencillo disponible en la API SQLite de Android execSQL(SQL). Este mtodo ejecuta directamente la orden SQL incluida como parmetro. Por otra parte, el mtodo onUpgrade() se ejecuta automticamente cuando sea necesario actualizar la estructura de la base de datos al cambiar la versin de la aplicacin que la alberga. Por ejemplo, desarrollamos la versin 1 de la aplicacin que utiliza una tabla con los campos descritos en el ejemplo anterior. Ms adelante, ampliamos la funcionalidad de la aplicacin desarrollando la versin 2, que incluye en la tabla el campo "Editorial". Si un usuario tiene instalada la versin 1 de la aplicacin en su dispositivo Android, la primera vez que ejecute la versin 2 de la aplicacin hay que modificar la estructura de la tabla, para aadir el nuevo campo; en este caso, Android ejecutar automticamente el mtodo onUpgrade(). Este mtodo recibe como parmetros la versin actual de la base de datos en el sistema y la nueva versin a la que se quiere convertir. En funcin de esta informacin debemos realizar unas acciones u otras. Por ejemplo, modificar la tabla con la orden "ALTER TABLE" para aadir el nuevo campo. Una vez que hemos implementado la clase SQLiteOpenHelper, podemos abrir fcilmente la base de datos desde la aplicacin Android. Lo primero que hacemos es crear un objeto de la clase BibliotecaSQLiteHelper al que pasamos el contexto de la aplicacin (en el ejemplo pasamos la referencia a la actividad principal), el nombre de la base de datos, un objeto CursorFactory (ms adelante veremos cmo funcionan los cursores, en ese caso pasamos el valor null) y, por ltimo, la versin de la base de datos de la aplicacin. Al crear este objeto pueden ocurrir varias cosas: Si ya existe la base de datos y su versin actual coincide con la solicitada, se conecta con ella. Si la base de datos existe, pero su versin actual es anterior a la solicitada, se invocar automticamente al mtodo onUpgrade(), para convertir la base de datos a la nueva versin y conectarla con la base de datos convertida. Si la base de datos no existe, se llamar automticamente al mtodo onCreate() para crearla y se conectar con la base de datos creada.

288

Introduccin al entorno Android

Una vez obtenida una referencia al objeto BibliotecaSQLiteHelper, podemos invocar el mtodo getReadableDatabase() para realizar consultas a la base de datos o el mtodo getWritableDatabase() para llevar a cabo modificaciones, . Despus, utilizando el mtodo execSQL(), podemos ejecutar rdenes SQL para consultar o modificar registros. En el Ejemplo 1 se insertan cinco registros de prueba. Por ltimo, cerramos la conexin con la base de datos llamando al mtodo close(). A continuacin, vemos el aspecto que tiene el cdigo fuente de la actividad principal:

//Abrimos la base de datos 'DBBiblioteca.db' BibliotecaSQLiteHelper bibliodbh = new BibliotecaSQLiteHelper(this, "DBBiblioteca.db", null, 1); // Modo escritura SQLiteDatabase db = bibliodbh.getWritableDatabase(); resultado.append("- La base de datos DBBiblioteca se ha abierto correctamente."); //Si se ha abierto correctamente la base de datos, entonces cargamos algunos registros... if(db != null) { // Hemos definido los datos en un fichero de recursos de la aplicacin Resources res = getResources(); String titulos[] = res.getStringArray(R.array.titulos); String autores[] = res.getStringArray(R.array.autores); String anios[] = res.getStringArray(R.array.anios); String prestados[] = res.getStringArray(R.array.prestados); //Insertamos 5 libros de ejemplo for(int i=0; i<5; i++) { String SQLStr="INSERT INTO Ejemplares (titulo, autor, anio, prestado) " + "VALUES ('" + titulos[i] +"', '" + autores[i] +"', " + anios[i] +", '" + prestados[i] +"')"; resultado.append("\n- SQL ejecutada: "+SQLStr); //Insertamos los datos en la tabla Ejemplares db.execSQL(SQLStr); }

289

//Cerramos la base de datos db.close(); resultado.append("\n- La base de datos DBBiblioteca se ha cerrado."); }

Android almacena los archivos de la base de datos en la memoria interna del dispositivo, en un directorio determinado, siempre el mismo, sin que el programador pueda cambiarlo. Es el siguiente: /data/data/paquete_java/databases/nombre_del_fichero En el ejemplo anterior se almacena en: /data/data/es.mentor.unidad6.eje1.crearbd/databases/DBBiblioteca.db Si ejecutas el Ejemplo 1 de esta Unidad, puedes comprobar en el DDMS cmo se crea el fichero correctamente en el directorio indicado. Para acceder a esta herramienta en Eclipse hay que hacer Clic en la opcin del men principal: Window -> Open Perspective -> DDMS:

Desde Eclipse puedes abrir el proyecto Ejemplo 1 (Crear base de datos SQLite) de la Unidad 6. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa anterior, en el que hemos utilizado los mtodos de la base de datos SQLite.

Hemos visto que el fichero de la base de datos del Ejemplo 1 se ha creado en la ruta correcta. Para comprobar que la tabla se ha creada correctamente y hemos insertado los registros en la misma, podemos usar dos mtodos: 290

Introduccin al entorno Android

1. Transferir la base de datos a nuestro PC y consultarla con cualquier administrador de bases de datos SQLite. Esto resulta un poco incmodo si slo necesitamos hacer una pequea consulta, pero, a veces, es imprescindible hacerlo para depurar errores en consultas SQL complejas. 2. Usar la consola de comandos del emulador de Android y recurrir a comandos SQL para acceder y consultar la base de datos SQLite. El primer mtodo es simple. Podemos transferir el fichero de la base de datos a nuestro PC utilizando el botn de descarga situado en la esquina superior derecha del explorador de archivos (enmarcado en rojo en la imagen anterior). Al lado de este botn hay otro botn para hacer la operacin contraria, es decir, copiar un fichero local al sistema de archivos del emulador. Adems, hay otro botn para eliminar ficheros del emulador. NOTA: a veces, al desarrollar una aplicacin Android con bases de datos, el programador debe eliminar a mano un fichero porque la estructura creada no es correcta y Android no elimina el fichero automticamente cada vez que cargamos la aplicacin en el emulador de Eclipse. Una vez hemos descargado el fichero a nuestro PC, podemos utilizar cualquier administrador de SQLite para abrir y consultar la base de datos. En el segundo mtodo accedemos de forma remota al emulador a travs de su consola de comandos (shell). Vamos a ver cmo hacerlo. Ten en cuenta que para que este mtodo funcione debemos haber incluido bien el PATH del SDK de Android en el sistema operativo del PC donde trabajemos. En caso de duda, conviene repasar el documento de Instalacin de Android de este curso. Con el emulador de Android ejecutndose, abrimos una consola de Windows (o del sistema operativo correspondiente) y utilizamos la utilidad adb.exe (Android Debug Bridge) situada en la carpeta platform-tools del SDK de Android. En primer lugar, consultamos todos los identificadores de los emuladores en ejecucin mediante el comando "adb devices". Este comando debe devolver una nica instancia con el emulador abierto, que en el ejemplo se denomina emulator-5554. Tras obtener este identificador del emulador activo vamos a acceder a su shell mediante el comando adb -s <identificador-del-emulador> shell. Una vez conectados a la consola del emulador, podemos acceder a la base de datos utilizando el comando sqlite3 e indicando la ruta del fichero de la base de datos; en el caso del ejemplo debemos escribir sqlite3 /data/data/es.mentor.unidad6.eje1.crearbd/databases/DBBiblioteca.db. A continuacin, debe aparecer el prompt de SQLite sqlite>, que nos indica que ya podemos escribir consultas SQL sobre la base de datos.

291

Vamos a comprobar que se han insertado bien los cinco registros del ejemplo en la tabla Ejemplares. Para ello, escribimos la siguiente orden: SELECT * FROM Ejemplares;. Si la orden est escrita correctamente, veremos el resultado en la pantalla; si no, se mostrar el error correspondiente. En la imagen siguiente se muestra el resultado de todos los comandos:

Para salir del cliente SQLite debemos escribir el comando ".exit" (fjate que lleva un punto delante) y para abandonar la shell del emulador debemos escribir el comando "exit".

6.2.4

Modificacin de la informacin de BD SQLite La librera de SQLite incluida en Android proporciona dos formas para llevar a cabo

operaciones sobre una base de datos que no devuelven resultados. Por ejemplo, aadir, actualizar y eliminar registros de una tabla; tambin se puede crear tablas, ndices de bsqueda, etctera. La primera forma. que ya la hemos visto anteriormente, consiste en usar el mtodo execSQL() de la clase SQLiteDatabase. Este mtodo permite ejecutar cualquier orden SQL sobre la base de datos siempre que no sea necesario obtener los resultados de la orden. Ya hemos utilizado este mtodo indicando como parmetro la cadena de texto de la orden SQL. Aunque ya hemos visto en el Ejemplo 1 cmo se usa este mtodo, a continuacin, mostramos algunos ejemplos ms:

292

Introduccin al entorno Android

//Insertar un registro db.execSQL("INSERT INTO Ejemplares (titulo, autor, anio, prestado) VALUES ('Ttulo', 'Autor', 2001, 'false')); //Eliminar un registro db.execSQL("DELETE FROM Ejemplares WHERE _id=1"); //Actualizar un registro db.execSQL("UPDATE Ejemplares SET autor='Nombre' WHERE _id=1");

La segunda forma disponible en Android consiste en utilizar los mtodos insert(), update() y delete() proporcionados tambin por la clase SQLiteDatabase. Estos mtodos permiten aadir, actualizar y eliminar registros de la tabla mediante parmetros, el valor del campo y las condiciones en que debe aplicarse la operacin. Veamos un ejemplo de cada uno de ellos: 6.2.4.1 Mtodo insert()

Este mtodo se usa para aadir nuevos registros en una tabla de la base de datos. Al invocar insert (String table, String nullColumnHack, ContentValues values), es necesario definir tres parmetros: table: nombre de la tabla en la que insertamos un registro. nullColumnHack: slo es necesario en casos muy puntuales, por ejemplo, al insertar registros completamente vacos. Normalmente debemos indicar el valor null en este segundo parmetro. values: valores del registro que se inserta.

Los valores que queremos insertar los pasamos como elementos de una coleccin de tipo ContentValues. Esta coleccin es del tipo duplos de clave-valor, donde la clave es el nombre del campo de la tabla y el valor es el dato que debemos insertar en dicho campo. Veamos un ejemplo sencillo:
//Creamos el registro a partir del objeto ContentValues ContentValues nuevoRegistro = new ContentValues(); nuevoRegistro.put("titulo", "Ttulo de la obra"); nuevoRegistro.put("autor","Nombre del autor"); ... //Insertamos el registro en la tabla de la base de datos db.insert("Ejemplares", null, nuevoRegistro);

293

Este mtodo devuelve el campo ID del nuevo registro insertado o el valor -1 si ocurre algn error durante la operacin.

6.2.4.2

Mtodo update() y mtodo delete()

Estos mtodos se usan para actualizar o borrar registros de una tabla. Los mtodos update (String table, ContentValues values, String whereClause, String[] whereArgs) y delete(String table, String whereClause, String[] whereArgs) se invocan de manera parecida a insert(). En estos mtodos hay que usar el parmetro adicional whereArgs para indicar la condicin WHERE de la orden SQL. Por ejemplo, para actualizar el autor del usuario de id 1 escribimos lo siguiente:
//Establecemos los campos-valores que actualizamos ContentValues valores = new ContentValues(); valores.put("autor","Otro autor"); //Actualizamos el registro de la tabla db.update("Ejemplares", valores, "_id=1");

En el tercer parmetro del mtodo update() indicamos la condicin tal como haramos en la clusula WHERE en una orden UPDATE de SQL. El mtodo delete() se aplica de igual forma. Por ejemplo, para eliminar el registro 2 escribimos lo siguiente:

//Eliminamos el registro del _id 2 db.delete("Ejemplares", "_id=2");

De nuevo, indicamos como primer parmetro el nombre de la tabla y como segundo la condicin WHERE. Si queremos vaciar toda la tabla, podemos indicar null en este segundo parmetro.

6.2.5

Uso de parmetros en los mtodos SQLite En el caso de los mtodos execSQL(), update() y delete() de SQLiteDatabase

podemos utilizar argumentos como condiciones de la sentencia SQL. De esta manera, podemos prescindir de SQL formadas con cadenas de texto muy largas y as evitamos errores de codificacin. 294

Introduccin al entorno Android

Estos argumentos son piezas variables de la sentencia SQL, en forma de matriz, que evitan tener que construir una sentencia SQL concatenando cadenas de texto y variables para formar la orden final SQL. Estos argumentos SQL se indican con el smbolo ? y los valores de dichos argumentos deben pasarse en la matriz en el mismo orden que aparecen en la sentencia SQL. Fjate en el siguiente ejemplo:
//Elimina un registro con execSQL() utilizando argumentos String[] args = new String[]{"Nombre de autor"}; db.execSQL("DELETE FROM Ejemplares WHERE autor=?", args); //Actualiza dos registros con update() utilizando argumentos ContentValues valores = new ContentValues(); valores.put("Ttulo 1","Ttulo 2"); String[] args = new String[]{1, "2"}; db.update("Ejemplares", valores, "_id=? OR _id=?", args);

6.3

Consultas SQLite en Android

6.3.1

Seleccin y recuperacin de Consultas de BD SQLite A continuacin, vamos a describir la manera de hacer consultas a una base de datos

SQLite desde Android y de extraer la informacin de datos del resultado. Existen dos formas de buscar y recuperar registros de una base de datos SQLite. La primera de ellas consiste en utilizar directamente un comando de consulta SQL; la segunda forma consiste en utilizar un mtodo especfico con parmetros de consulta a la base de datos. La primera forma se basa en la utilizacin del mtodo rawQuery() de la clase SQLiteDatabase, que ya hemos estudiado en el apartado anterior. En este indicamos directamente como parmetro el comando SQL que queremos usar en la consulta sealando los campos seleccionados y los criterios de seleccin. El resultado de la consulta lo obtenemos en forma de Cursor. La clase Cursor permite acceder en modo lectura/escritura a los resultados devueltos por una consulta a la base de datos. Esta clase Cursor se puede usar con varios hilos (subprocesos) para obtener asncronamente informacin de una BD. Fjate en el siguiente ejemplo:
Cursor c = db.rawQuery(" SELECT autor,titulo FROM Ejemplares WHERE _id=1");

295

Tal y como hemos visto anteriormente en algunos mtodos de modificacin de datos, tambin es posible incluir en este mtodo una lista de argumentos variables que indicamos en la orden SQL con el smbolo ?; por ejemplo, as:
String[] args = new String[] {"1"}; Cursor c = db.rawQuery(" SELECT autor,titulo FROM Ejemplares WHERE _id=? ", args);

La segunda forma de obtencin de datos se basa en utilizar el mtodo query() de la clase SQLiteDatabase. Este mtodo query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) se invoca con varios parmetros: table: nombre de la tabla consultada columns: matriz con los nombres de los campos seleccionados selection: clusula WHERE del lenguaje SQL selectionArgs: matriz con los argumentos variables incluidos en el WHERE o null si no se indican. groupBy: clusula GROUP BY si existe; si no, escribimos null. having: clusula HAVING si existe; si no, escribimos null. orderBy: clusula ORDER BY si existe; si no, escribimos null. limit: nmero mximo de registros devueltos por la consulta.

Veamos el mismo ejemplo anterior utilizando el mtodo query():

String[] campos = new String[] {"autor", "titulo"}; String[] args = new String[] {"1"};

Cursor c = db.query("Ejemplares", campos, "_id=?", args, null, null, null);

Tanto en la primera forma como en la segundo, ambos mtodos devuelven como resultado de su ejecucin un objeto de tipo Cursor, que debemos recorrer para procesar los registros obtenidos. La clase Cursor dispone de varios mtodos para recorrer y manipular los registros devueltos por la consulta. Entre ellos podemos destacar dos de los dedicados a recorrer el cursor de forma secuencial y en orden natural: moveToFirst(): mueve el puntero del cursor al primer registro devuelto. Si no hay ningn primer registro este mtodo devuelve false. 296

Introduccin al entorno Android

moveToNext(): mueve el puntero del cursor al siguiente registro devuelto. Si no hay ningn registro despus, este mtodo devuelve false.

moveToPrevious(): mueve el cursor al registro anterior. Si no hay ningn registro anterior, este mtodo devuelve false.

getCount(): devuelve el nmero de registros devueltos por la consulta. getColumnIndexOrThrow(String columna): devuelve el ndice de la columna dada o lanza la excepin IllegalArgumentException si no existe la columna.

getColumnName(int indice): devuelve el nombre de la columna indicada en el ndice dado.

getColumnNames(): devuelve una matriz con los nombres de las columnas seleccionadas.

moveToPosition(int posicion): mueve el cursor al registro que hay en esa posicin. Si no hay ningn registro en esa posicin, este mtodo devuelve false.

getPosition(): devuelve la posicin actual del cursor.

Una vez colocado el cursor en el registro que queremos leer, podemos utilizar cualquiera de los mtodos getXXX(ndice_columna) existentes para cada tipo de dato y as recuperar el dato de cada campo de ese registro. Por ejemplo, si queremos recuperar la segunda columna del registro actual y sta contiene un campo alfanumrico, usamos la sentencia getString(1). La primera columna de la consulta tiene el ndice 0, la segunda columna tiene ndice 1 y as sucesivamente. En el caso de que la columna contenga un dato de tipo real, ejecutaramos la sentencia getDouble(1). Teniendo todo esto en cuenta, veamos, a continuacin, cmo recorrer todos los registros devueltos por la consulta del ejemplo anterior usando un cursor:
String[] campos = new String[] {"autor", "titulo"}; String[] args = new String[] {"1"};

Cursor c = db.query("Ejemplares", campos, "_id=?", args, null, null, null);

//Comprobamos que existe, al menos, un registro if (c.moveToFirst()) {

297

//Recorremos el cursor mientras haya registros sin leer do { String autor = c.getString(0); String titulo = c.getString(1); } while(c.moveToNext()); }

Adems de los mtodos comentados de la clase Cursor, existen muchos ms mtodos que pueden ser muy tiles. El alumno o la alumna puede consultar la lista completa en la documentacin oficial de la clase Cursor. Como hemos comentado ya en esta Unidad, las bases de datos SQLite de una aplicacin son siempre privadas e internas a esta aplicacin. Para que el resto de aplicaciones pueda acceder a la informacin de la BD, Android define los Content Provider. En la Unidad 7 tratamos este tema en profundidad. Adems, se pueden tratar datos dinmicos de una base de datos usando la clase de Android SQLiteQueryBuilder. Esta clase es similar a la interfaz de un proveedor de contenidos, por lo que suele utilizarse conjuntamente con los Content Providers.

NOTA: Existen funcionalidades ms avanzadas de gestin de BD con Android, como la utilizacin de transacciones, pero no vamos a tratarlas en este curso por considerarse programacin avanzada.

6.3.2 Ejemplo prctico de BD SQLite con Android A continuacin, vamos a mostrar mediante un ejemplo completo cmo se usan todos los mtodos de acceso a base de datos que hemos estudiado hasta ahora.

Desde Eclipse puedes abrir el proyecto Ejemplo 2 (Notas) de la Unidad 6. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa anterior, en el que hemos utilizado mtodos de la base de datos SQLite.

Si ejecutas la aplicacin, vers que tiene el siguiente aspecto:

298

Introduccin al entorno Android

Se trata de una aplicacin donde un usuario puede gestiona notas sencillas por categoras. Estas notas se almacenan en la base de datos "bdnotas.db" en la tabla "notas" que tiene la siguiente estructura: _id: ndice de registro de tipo entero con ndice primario autoincremental categoria: texto no nulo titulo: texto no nulo descripcion: texto no nulo

La aplicacin est formada por dos actividades: la primera muestra todas las notas en un listado y la segunda permite editarlas o dar de alta una nueva. Ambas actividades se interconectan con Intents invocados de manera explcita. Para mostrar el listado con las notas en la actividad principal hemos heredado la clase ListActivity. Como ya hemos visto anteriormente en el curso, esta clase define un ListView interno. Podemos conectarlo con la clase Cursor, que devuelve los resultados de las consultas a la BD, usando la clase SimpleCursorAdapter de Android. Veamos cmo hacerlo en la prctica:

6.3.3

Acceso y creacin de la Base de datos Como ya hemos visto anteriormente, para acceder y crear la base de datos de la

aplicacin es necesario crear una clase heredada de SQLiteOpenHelper. En este ejemplo hemos definido la clase NotasBDHelper: 299

public class NotasBDHelper extends SQLiteOpenHelper { // Definimos el nombre y la versin de la BD private static final String BD_NOMBRE = "bdnotas.db"; private static final int BD_VERSION = 1;

// SQL que crea la base de datos // Es muy importante usar el campo _id private static final String BD_CREAR = "create table notas (_id integer primary key autoincrement, " + "categoria text not null, titulo text not null, descripcion text not null);";

// Contructor de la clase public NotasBDHelper(Context context) {

super(context, BD_NOMBRE, null, BD_VERSION); } // Mtodo invocado por Android si no existe la BD @Override public void onCreate(SQLiteDatabase database) { // Creamos la estructura de la BD database.execSQL(BD_CREAR); } // Mtodo invocado por Android si hay un cambio de versin de la BD @Override public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) { // Eliminamos la BD y la volvemos a crear otra vez database.execSQL("DROP TABLE IF EXISTS notas"); onCreate(database); } }

Basada en esta clase anterior vamos a definir la nueva clase NotasBDAdapter, que es la encargada de hacer las consultas a la base de datos, borrar y actualizar registros de sta. 300

Introduccin al entorno Android

Dentro de esta clase hemos definido el mtodo abrir(), que se conecta a la base de datos utilizando la clase NotasBDHelper. Para actualizar y dar de alta registros hemos usado un argumento del tipo ContentValues, que hemos estudiado en el apartado anterior.

class NotasBDAdapter { // Campos de la BD public static final String CAMPO_ID = "_id"; public static final String CAMPO_CATEGORIA = "categoria"; public static final String CAMPO_TITULO = "titulo"; public static final String CAMPO_DESCRIPCION = "descripcion"; private static final String TABLA_BD = "notas"; private Context contexto; private SQLiteDatabase basedatos; private NotasBDHelper bdHelper;

public NotasBDAdapter(Context context) { this.contexto = context; } // Mtodo que abre la BD public NotasBDAdapter abrir() throws SQLException { // Abrimos la base de datos en modo escritura bdHelper = new NotasBDHelper(contexto); basedatos = bdHelper.getWritableDatabase(); return this; } // Mtodo que cierra la BD public void cerrar() { bdHelper.close(); } // Mtodo que crear una nota. Devuelve el id del registro nuevo si se ha // dado de alta correctamente o -1 si no. public long crearNota(String categoria, String titulo, String descripcion) { // Usamos un argumento variable para aadir el registro

301

ContentValues initialValues = crearContentValues(categoria, titulo, descripcion); // Usamos la funcin insert del SQLiteDatabase return basedatos.insert(TABLA_BD, null, initialValues); } // Mtodo que actualiza una nota public boolean actualizarNota(long id, String categoria, String titulo, String descripcion) { // Usamos un argumento variable para modificar el registro ContentValues updateValues = crearContentValues(categoria, titulo, descripcion); // Usamos la funcin update del SQLiteDatabase return basedatos.update(TABLA_BD, updateValues, CAMPO_ID + "=" + id, null) > 0; } // Mtodo que borra una nota public boolean borraNota(long id) { // Usamos la funcin delete del SQLiteDatabase return basedatos.delete(TABLA_BD, CAMPO_ID + "=" + id, null) > 0; } // Devuelve un Cursor con la consulta a todos los registros de la BD public Cursor obtenerNotas() { return basedatos.query(TABLA_BD, new String[] { CAMPO_ID, CAMPO_CATEGORIA, CAMPO_TITULO, CAMPO_DESCRIPCION }, null, null, null, null, null); } // Devuelve la Nota del id public Cursor getNota(long id) throws SQLException { Cursor mCursor = basedatos.query(true, TABLA_BD, new String[] { CAMPO_ID, CAMPO_CATEGORIA, CAMPO_TITULO, CAMPO_DESCRIPCION }, CAMPO_ID + "=" + id, null, null, null, null, null); // Nos movemos al primer registro de la consulta if (mCursor != null) { mCursor.moveToFirst();

302

Introduccin al entorno Android

} return mCursor; } // Mtodo que crea un objeto ContentValues con los parmetros indicados private ContentValues crearContentValues(String categoria, String titulo, String descripcion) { ContentValues values = new ContentValues(); values.put(CAMPO_CATEGORIA, categoria); values.put(CAMPO_TITULO, titulo); values.put(CAMPO_DESCRIPCION, descripcion);

return values; } }

6.3.4

Recursos de diseo XML A continuacin, indicamos los ficheros XML de Layout que componen el diseo de la

interfaz del usuario: res/menu/menu_listado.xml: define el diseo del men principal de la aplicacin. res/layout/main.xml: define el diseo de la pantalla de la actividad principal NotasActivity. res/layout/editar_nota.xml: define el diseo de la actividad secundaria GestionarNota, que sirve para editar y dar de alta notas. res/layout/fila_notas.xml: define el diseo de los elementos del ListView de la actividad principal, es decir, el estilo de cada nota en el listado. El alumno o alumna puede abrir estos ficheros en su ordenador y ver cmo estn implementados los distintos diseos. Adems, se definen los dos ficheros strings.xml y categorias.xml en la carpeta res/values con los literales que usa la aplicacin.

303

6.3.5

Actividades Como hemos comentado, la aplicacin est formada por dos actividades: la actividad

principal (NotasActivity) muestra un listado con todas las notas y la segunda (GestionarNota) sirve para editarlas o dar de alta una nueva. Veamos el contenido de la actividad principal:

public class NotasActivity extends ListActivity { private NotasBDAdapter bdHelper; private static final int ACTIVIDAD_NUEVA = 0; private static final int ACTIVIDAD_EDITAR = 1; private static final int MENU_ID = Menu.FIRST + 1; private Cursor cursor;

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Hacemos ms ancha la lnea de divisin entre elementos en el listado this.getListView().setDividerHeight(3);

// Creamos el adaptador que conecta con la BD bdHelper = new NotasBDAdapter(this); // Cargamos todos los datos bdHelper.abrir(); cargaDatos(); // Indicamos el men contextual asociado al listado registerForContextMenu(getListView()); } // Creamos el men principal @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menulistado, menu);

304

Introduccin al entorno Android

return true; } // El usuario hace clic en una opcin del men principal @Override public boolean onMenuItemSelected(int id, MenuItem item) { // Buscamos la opcin del men principal seleccionada switch (item.getItemId()) { case R.id.insertar: // Creamos una actividad indicando el tipo de peticin // "ACTIVIDAD_NUEVA" y esperamos el resultado de la misma Intent i = new Intent(this, DetallesNota.class); startActivityForResult(i, ACTIVIDAD_NUEVA); // Indicamos que hemos manejado la opcin del men return true; } return super.onMenuItemSelected(id, item); } // El usuario hace clic en una opcin del men contextual del listado @Override public boolean onContextItemSelected(MenuItem item) { // Buscamos la opcin del men contextual seleccionada switch (item.getItemId()) { case MENU_ID: // Obtenemos el id del elemento seleccionado AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); // Borramos ese registro bdHelper.borraNota(info.id); // Recargamos los datos cargaDatos(); // Indicamos que hemos manejado la opcin del men return true; } return super.onContextItemSelected(item);

305

} // Cuando hacemos clic en un elemento del listado, se edita la Nota @Override protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); // Creamos una actividad indicando el tipo de peticin // "ACTIVIDAD_EDITAR" y esperamos el resultado de la misma Intent i = new Intent(this, DetallesNota.class); // Pasamos el campo _id como un dato extra i.putExtra(NotasBDAdapter.CAMPO_ID, id); startActivityForResult(i, ACTIVIDAD_EDITAR); } // Mtodo que se llama cuando una subactividad devuelve el resultado @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); // Recargamos los datos si se ha modificado algo. // Es decir, el usuario ha hecho clic en OK if (resultCode == Activity.RESULT_OK) cargaDatos(); } private void cargaDatos() { cursor = bdHelper.obtenerNotas(); // Se indica que a la Actividad principal que controle los recursos // cursor. Es decir, si se termina la Actividad, se elimina esta // Cursor de la memoria startManagingCursor(cursor);

// Indicamos cmo debe pasarse el campo ttulo de (from) a (to) // la Vista de la opcin (fila_notas.xml) String[] from = new String[] { NotasBDAdapter.CAMPO_CATEGORIA, NotasBDAdapter.CAMPO_TITULO }; int[] to = new int[] { R.id.fila_categoria, R.id.fila_titulo };

306

Introduccin al entorno Android

// Creamos un sencillo adaptador de tipo Matriz // asociado al cursor SimpleCursorAdapter notas = new SimpleCursorAdapter(this, R.layout.fila_notas, cursor, from, to); // Indicamos al listado el adaptador que le corresponde setListAdapter(notas); }

// Creamos el men contextual @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); menu.add(0, MENU_ID, 0, R.string.menu_borrar); }

// Cuando se acaba la Actividad cerramos la BD // Es muy importante hacer esto para que se escriba toda la informacin @Override protected void onDestroy() { super.onDestroy(); if (bdHelper != null) { bdHelper.cerrar(); } } }

A continuacin, vamos a ver el cdigo de la Actividad secundaria o subactividad:

public class GestionarNota extends Activity { private EditText tituloText; private EditText descripcionText;

307

private Spinner categoriaSpinner; // Usamos esta variable para saber si estamos editando (filaId=id) o // se trata de un registro nuevo (filaId=null) private Long filaId; private NotasBDAdapter bdHelper;

@Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); // Creamos un adaptador u abrimos la BD bdHelper = new NotasBDAdapter(this); bdHelper.abrir(); // Dibujamos el UI y buscamos sus Vistas setContentView(R.layout.editar_nota); categoriaSpinner = (Spinner) findViewById(R.id.category); tituloText = (EditText) findViewById(R.id.nota_editar_titulo); descripcionText = (EditText) findViewById(R.id.nota_editar_descripcion); Button aceptaBoton = (Button) findViewById(R.id.nota_editar_boton); // Variable con el ID del registro actual filaId = null; // Obtenemos el campo ID que se debe haber pasado en la invocacin // de la actividad si estamos editando el registro Bundle extras = getIntent().getExtras(); // Si extras contiene algo cargamos ese ID if (extras != null) { filaId = extras.getLong(NotasBDAdapter.CAMPO_ID); }

// Cargamos el registro en los componentes de la pantalla cargarRegistro(); // Mtodo del botn OK aceptaBoton.setOnClickListener(new View.OnClickListener() {

308

Introduccin al entorno Android

public void onClick(View view) { // Si pulsa este botn guardamos los datos y devolvemos OK a la Actividad String categoria = (String) categoriaSpinner.getSelectedItem(); String titulo = tituloText.getText().toString(); String descripcion = descripcionText.getText().toString();

// Alta de registro if (filaId == null) { bdHelper.crearNota(categoria, titulo, descripcion); } else { // Modificacin de registro bdHelper.actualizarNota(filaId, categoria, titulo, descripcion); }

setResult(RESULT_OK); // Acabamos la actividad finish(); }

}); } // end onCreate

private void cargarRegistro() { if (filaId != null) { Cursor nota = bdHelper.getNota(filaId); // Volvemos a dejar que la actividad actual controle el Cursos startManagingCursor(nota); // Obtenemos el campo categoria String categoria = nota.getString( nota.getColumnIndexOrThrow(NotasBDAdapter.CAMPO_CATEGORIA));

for (int i=0; i<categoriaSpinner.getCount();i++){ // Cargamos una de la opciones del listado desplegable

309

String s = (String) categoriaSpinner.getItemAtPosition(i); // Si coindice con la que est en la BD la seleccionamos en el listado desplegable if (s.equalsIgnoreCase(categoria)){ categoriaSpinner.setSelection(i); break; } }

// Rellenamos las Vistas de Ttulo y Descripcin tituloText.setText(nota.getString( nota.getColumnIndexOrThrow(NotasBDAdapter.CAMPO_TITULO))); descripcionText.setText(nota.getString( nota.getColumnIndexOrThrow(NotasBDAdapter.CAMPO_DESCRIPCION))); } } // end cargarRegistro }

6.3.6

Fichero Androidmanifest.xml Para que la subactividad GestionarNota est disponible en el sistema operativo,

debemos declararla en el archivo "AndroidManifest.xml" del proyecto, incluso si la vamos a invocar de manera explcita. Para esto, escribimos en este fichero las siguientes lneas:

<activity android:name=".GestionarNota" android:windowSoftInputMode="stateVisible|adjustResize"> </activity>

El

atributo

android:windowSoftInputMode

indica

cmo

interacciona

esta

subactividad con el teclado flotante de Android:

310

Introduccin al entorno Android

El establecimiento de este atributo afecta a dos aspectos del teclado: Al estado del teclado de la pantalla, es decir, si est oculto o visible cuando la actividad est en primer plano y el usuario interacciona con ella. Al ajuste que sufren los componentes de la ventana principal de la actividad para que el teclado quepa en la pantalla, es decir, si se ajusta el contenido para dejar espacio al teclado o el contenido se mantiene intacto y el tecla "flota" sobre ste. En este ejemplo hemos usado las opciones stateVisible y adjustResize para que el teclado se muestre cuando el usuario acceda a un componente de introduccin de texto y cambie las proporciones de la pantalla para hacer un "hueco" al teclado. En la ayuda oficial de Android puedes encontrar todos los posibles valores con su descripcin.

6.4

GESTIN DE FICHEROS XML

EXtensible Markup Language (XML) es un formato de datos que se usa comnmente en las aplicaciones web modernas. XML utiliza etiquetas personalizadas para describir los tipos de datos y se codifica como texto sin formato, por lo que es muy flexible y sencillo de utilizar. Android incluye bibliotecas de clases diseadas para el procesamiento de datos en formato XML. Los tres modelos ms extendidos para leer y escribir ficheros de tipo XML son DOM (Document Object Model), SAX (Simple API for XML) y StAX (Streaming API for XML):

311

DOM: vuelca el documento XML en la memoria del dispositivo en forma de estructura de rbol, de manera que se puede acceder aleatoriamente a los elementos de las ramas.

SAX: en este modelo, basado en eventos, la aplicacin recorre todos los elementos del archivo XML de una sola vez. La ventaja respecto al modelo anterior consiste en que es ms rpido y requiere menos memoria, si bien no permite el acceso aleatorio a una de sus ramas.

StAX: es una mezcla de las dos modelos anteriores. En este caso, tambin se lee el fichero XML de forma secuencial, pero podemos controlar la forma en que se leen sus elementos. En el caso de SAX es obligatorio leer todos los elementos a la vez. Este modelo es tambin mucho ms rpido que DOM, pero algo ms lento de SAX.

Un analizador sintctico (en ingls parser) convierte el texto de entrada en otras estructuras (comnmente rboles), que son ms tiles para el posterior anlisis y capturan la jerarqua implcita de la entrada. Android dispone de analizadores XML para estos tres modelos. Con cualquiera de ellos podemos hacer las mismas tareas. Ya veremos ms adelante que, dependiendo de la naturaleza de la aplicacin, es ms eficiente utilizar un modelo u otro. Estas tcnicas se pueden utilizar para leer cualquier documento XML, tanto de Internet como del sistema de archivos. En el Ejemplo 3 de esta Unidad vamos a leer datos XML de un documento RSS de un peridico; concretamente, del canal RSS de noticias de 20minutos.es. Puedes modificar esta direccin cambiado la variable de la Actividad principal XMLActivity:
static String feedUrl = "http://20minutos.feedsportal.com/c/32489/f/478284/index.rss";

Si abrimos el documento RSS de esta fuente de noticias (en ingls feed), vemos la estructura siguiente: <rss version="2.0">
<channel> <title>20minutos.es</title> <link> http://www.20minutos.es/</link> <description> Diario de informacin general y ...</description> <language>es-ES</language> <pubDate> Fri, 28 Oct 2011 18:54:41 GMT</pubDate>

312

Introduccin al entorno Android

<lastBuildDate> Fri, 28 Oct 2011 18:54:41 GMT</lastBuildDate> <item> <title>Ttulo de la noticia 1</title> <link>http://link_de_la_noticia_2.es</link> <description>Descripcin de la noticia 2</description> <pubDate>Fecha de publicacin 2</pubDate> </item> <item> <title>Ttulo de la noticia 2</title> <link>http://link_de_la_noticia_2.es</link> <description>Descripcin de la noticia 2</description> <pubDate>Fecha de publicacin 2</pubDate> </item> ... </channel> </rss>

Como puedes observar, se compone de un elemento principal <channel>, seguido de varios datos relativos al canal y, posteriormente, de una lista de elementos <item> para cada noticia. En este apartado vamos a describir cmo leer este archivo XML sirvindonos de cada una de las tres alternativas citadas anteriormente. Desde Eclipse puedes abrir el proyecto Ejemplo 3 (XML) de la Unidad 6. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa anterior, en el que hemos utilizado mtodos de lectura del formato XML.

Si ejecutas la aplicacin en el emulador de Android, vers que tiene el siguiente aspecto:

313

Para implementar la Actividad principal, hemos usado la clase ListActivity, donde mostraremos un listado con las noticias. Para empezar, en primer lugar debemos definir una clase Java para almacenar los datos ledos de una noticia. Para cargar el listado de la clase ListActivity con los titulares de las noticias usamos una lista de objetos de este tipo. Veamos el cdigo fuente de esta clase que hemos denominado Noticia:

// Clase que sirve para cargar en un objeto cada noticia que leamos del fichero XML public class Noticia { // Dispone de las variables y mtodos tpicos de una clase sencilla private String titulo; private URL enlace; private String descripcion; private String fecha;

public URL getEnlace() { return enlace; } public void setEnlace(String enlace) { // Intentamos cargar el enlace en forma de URL. // Si tenemos un error lanzamos una excepcin

314

Introduccin al entorno Android

try { this.enlace = new URL(enlace); } catch (MalformedURLException e) { throw new RuntimeException(e); } }

public void setFecha(String fecha) { while (!fecha.endsWith("00")){ fecha += "0"; } this.fecha = fecha.trim(); } public String getFecha() { return this.fecha; } public String getTitulo() { return titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } public String getDescripcion() { return descripcion; } public void setDescripcion(String descripcion) { this.descripcion = descripcion; } }

Por simplificacin, hemos tratado todos los datos como cadenas de texto.

315

6.4.1

SAX es el modelo clsico en Android

En el modelo clsico de SAX el tratamiento de un archivo XML se basa en un analizador (parser) que lee secuencialmente el documento XML y va generando diferentes eventos con la informacin de cada elemento ledo. Por ejemplo, a medida que lee el documentos XML, si el analizador encuentra una etiqueta <title> lanzar el mtodo startElement() del parser de inicio de etiqueta con la informacin asociada. Si despus de esa etiqueta encuentra una cadena de texto, invocar el mtodo characters() del parser con toda la informacin necesaria. Por lo tanto, debemos implementar las sentencias necesarias para tratar cada uno de los mtodos posibles que el analizador puede lanzar durante la lectura del documento XML. Los principales mtodos que se pueden producir son los siguientes: startDocument(): comienza el documento XML. endDocument(): termina el documento XML. startElement(): comienza una etiqueta XML. endElement(): termina una etiqueta XML. characters(): se ha encontrado una cadena de texto.

Puedes encontrar la lista completa de los mtodos en este enlace. Estos mtodos se definen en la clase org.xml.sax.helpers.DefaultHandler. Por esto hay que heredar esta clase y sobrescribir los mtodos necesarios. En este ejemplo la clase se llama ParserSaxClasicoHandler:

public class ParserSaxClasicoHandler extends DefaultHandler{ // Variables temporales que usamos a lo largo del Handler // Listado completo de las noticias private List<Noticia> noticias; // Noticia que estamos leyendo en ese momento private Noticia noticiaActual; // Variable temporal para almacenar el texto contenido en una etiqueta private StringBuilder sbTexto;

// Mtodo que devuelve todo el listado de noticias public List<Noticia> getNoticias(){

316

Introduccin al entorno Android

return noticias; }

// Mtodo que se lanza al iniciar la lectura de un XML @Override public void startDocument() throws SAXException { // Lanzamos el mtodo de la clase madre super.startDocument(); // Iniciamos los variables temporales noticias = new ArrayList<Noticia>(); sbTexto = new StringBuilder(); }

// Comienza una etiqueta XML @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { // Lanzamos el mtodo de la clase madre super.startElement(uri, localName, name, attributes); // Si leemos una nueva etiqueta item es que empieza una noticias if (localName.equals(EtiquetasRSS.ITEM)) { noticiaActual = new Noticia(); } }

// Finaliza una etiqueta XML @Override public void endElement(String uri, String localName, String name) throws SAXException { // Lanzamos el mtodo de la clase madre super.endElement(uri, localName, name); // Si estamos leyendo una noticia if (this.noticiaActual != null) {

317

// Cargamos el campo correspondiente de la etiqueta que acabamos de leer if (localName.equals(EtiquetasRSS.TITLE)) { noticiaActual.setTitulo(sbTexto.toString()); } else if (localName.equals(EtiquetasRSS.LINK)) { noticiaActual.setEnlace(sbTexto.toString()); } else if (localName.equals(EtiquetasRSS.DESCRIPTION)) { noticiaActual.setDescripcion(sbTexto.toString()); } else if (localName.equals(EtiquetasRSS.PUB_DATE)) { noticiaActual.setFecha(sbTexto.toString()); } else if (localName.equals(EtiquetasRSS.ITEM)) { // Si leemos el final de la etiqueta "item" aadimos la noticia al listado noticias.add(noticiaActual); } // Reiniciamos la variable temporal de texto sbTexto.setLength(0); } }

// Se ha encontrado una cadena de texto @Override public void characters(char[] ch, int start, int length) throws SAXException { // Lanzamos el mtodo de la clase madre super.characters(ch, start, length); // Si estamos leyendo una noticia if (this.noticiaActual != null) // Asignamos el texto a la variable temporal sbTexto.append(ch, start, length); } }

318

Introduccin al entorno Android

En el cdigo fuente anterior usamos la lista de noticias List<Noticia> para almacenar todas la noticias ledas y el mtodo getNoticias() las devuelve al finalizar la lectura del documento. Despus, hay que implementar los mtodos SAX necesarios. Una vez hemos implementado nuestro handler, vamos a crear la nueva clase ParserSaxClasico que hace uso de este handler para analizar un documento XML en concreto usando el modelo SAX. Esta clase nicamente define un constructor que recibe como parmetro la direccin de Internet del documento XML que hay que analizar. El mtodo pblico analizar() analiza el documento XML y devuelve como resultado una lista de noticias. Veamos cmo queda esta clase:

public class ParserSaxClasico // URL del archivo XML private URL feedUrl;

implements RSSParser {

// Constructor de la clase, se asigna la URL a la variable local protected ParserSaxClasico(String feedUrl){ try { this.feedUrl= new URL(feedUrl); } catch (MalformedURLException e) { throw new RuntimeException(e); } }

// Mtodo que lee el documento XML public List<Noticia> analizar() { // Creamos acceso a la API Sax de Android SAXParserFactory factory = SAXParserFactory.newInstance(); try { // Creamos un analizador (parser) de SAX

319

SAXParser parser = factory.newSAXParser(); // Creamos el handle de SAX que implementamos en otra clase ParserSaxClasicoHandler handler = new ParserSaxClasicoHandler(); // Analizamos el archivo con el handler anterior parser.parse(getInputStream(), handler); // Devolvemos las noticias encontradas return handler.getNoticias(); } catch (Exception e) { throw new RuntimeException(e); } }

// Mtodo que abre una conexin a la URL y devuelve el // puntero de tipo fichero al analizador correspondiente private InputStream getInputStream() { try { return feedUrl.openConnection().getInputStream(); } catch (IOException e) { throw new RuntimeException(e); } } }

El constructor de la clase anterior acepta como parmetro una direccin URL del documento XML que analiza y controla la validez de dicha direccin excepcin si no puede crear la clase URL correspondiente. Por otra parte, el mtodo analizar() es el encargado de crear un nuevo parser SAX y de iniciar el proceso de anlisis pasando al parser una instancia del handler que hemos creado anteriormente con una referencia al documento XML en forma de stream. 320 generando una

Introduccin al entorno Android

Para pasar una referencia en forma de stream, implementamos el mtodo auxiliar privado getInputStream(), que abre la conexin con la direccin URL especificada mediante el mtodo openConnection() y obtiene el stream de entrada mediante el mtodo getInputStream(). En el apartado de Tratamiento de ficheros de la Unidad 4 hemos visto cmo usar la clase stream en Android. Finalmente, slo queda aplicar la clase ParserSaxClasico para cargar un documento XML con el modelo SAX. Para ello, en la Actividad principal XMLActivity de la aplicacin escribimos las siguientes sentencias:

// Creamos un objeto del parser (analizador XML) en funcin del tipo (opcin // men principal). La direccin (URL) de la fuente de noticias es una //constante en este ejemplo RSSParser analizador = XMLParser.getParser(tipo, feedUrl); // Guardamos el momento actual de inicio de long inicio = System.currentTimeMillis(); // Descargamos y analizamos el fichero XML noticias = analizador.analizar(); // Calculamos el tiempo que ha tardado en leer el XML long duracion = System.currentTimeMillis() - inicio; // Mostramos el tiempo de lectura del XML Toast.makeText(this, "Se han cargado los datos en "+duracion+" milisegundos", 1).show(); // Creamos un listado con todos los ttulos de las noticias List<String> titulos = new ArrayList<String>(noticias.size()); for (Noticia msg : noticias){ titulos.add(msg.getTitulo()); }

// Definimos Adaptador sencillo con un diseo sencillo y el listado ttulos ArrayAdapter<String> adaptador = new ArrayAdapter<String>(this, R.layout.fila, titulos); this.setListAdapter(adaptador);

321

Primero creamos el parser correspondiente usando la direccin URL del documento XML y, despus, ejecutamos el mtodo analizar() para obtener una lista de objetos de tipo Noticia que, posteriormente, asignamos al adaptador del listado de la Actividad principal. Si te fijas en el cdigo anterior estamos creando el objeto analizador a partir de la clase XMLParser en lugar de ParserSaxClasico. Si abrimos el fichero que define la clase XMLParser veremos el cdigo siguiente:

// Clase que crea un analizador XML del tipo necesario // Se crea una interface RSSParser para poder renombrar el mtodo analizar() public abstract class XMLParser {

public static RSSParser getParser(TiposParser tipo, String feedURL){ switch (tipo){ case SAX_CLASICO: return new ParserSaxClasico(feedURL); case DOM: return new ParserDom(feedURL); case SAX_SIMPLIFICADO: return new ParserSaxSimplificado(feedURL); case XML_PULL: return new ParserXmlPull(feedURL); default: return null; } } }

Observa que, como estamos usando la misma aplicacin para mostrar cmo funcionan todos los modelos de carga de archivos XML en Android, hemos creado una clase abstracta que devuelve un objeto en funcin del tipo de analizador que el usuario ha decido usar en ese momento.

NOTA: Para que esta aplicacin Android acceda a Internet, es necesario declararlo en el fichero AndroidManifest.xml, que requiere el permiso "android.permission.INTERNET".

322

Introduccin al entorno Android

6.4.2

SAX simplificado en Android El modelo SAX anterior de tratamiento de archivos XML, a pesar de funcionar perfecta

y eficientemente, tiene claras desventajas, ya que es obligatorio definir una clase independiente para el handler. Adicionalmente, el modelo SAX implica poner bastante atencin al definir dicho handler, ya que los mtodos SAX definidos no estn asignados a etiquetas concretas del documento XML, sino que se lanzan para todas ellas. Esto obliga a realizar distinciones entre etiquetas dentro de cada mtodo. Esto se observa perfectamente en el mtodo endElement() que definimos en el ejemplo anterior. En primer lugar, hay que comprobar con la sentencia condicional si el atributo noticiaActual no est vaco (null), para no confundir el elemento <title> descendiente de <channel> con el elemento <title> descendiente de <item>, que es el que queremos leer. Posteriormente, hay que distinguir con unas sentencias IF entre todas las etiquetas posibles la accin que debemos realizar. Tengamos en cuenta que hemos usado un documento XML muy sencillo, pero si tratamos un documento XML ms enrevesado, la complejidad de este handler aumenta mucho y pueda dar lugar a errores de programacin. Para evitar estos problemas, Android propone una variante del modelo SAX que evita definir una clase separada para el handler y que permite asociar directamente las acciones a etiquetas concretas dentro de la estructura del documento XML. Veamos cmo queda el analizador XML utilizando SAX simplificado para Android:

public class ParserSaxSimplificado

implements RSSParser {

// Variables temporales que usamos a los largo del Handler // Noticia que estamos leyendo en ese momento private Noticia noticiaActual; // Variable que define la etiqueta raz del XML que es <rss > static final String RSS = "rss"; // URL del archivo XML private URL feedUrl;

// Constructor de la clase, se asigna la URL a la variable local protected ParserSaxSimplificado(String feedUrl){ try { this.feedUrl= new URL(feedUrl);

323

} catch (MalformedURLException e) { throw new RuntimeException(e); } }

// Mtodo que lee el documento XML public List<Noticia> analizar() { // Variable que almacena las noticias encontradas final List<Noticia> noticias = new ArrayList<Noticia>(); // Buscamos el elemento raz <rss> RootElement root = new RootElement(RSS); // Buscamos el elemento channel dentro de la etiqueta raz (root) Element channel = root.getChild(EtiquetasRSS.CHANNEL); // Buscamos el elemento item dentro de la etiqueta channel Element item = channel.getChild(EtiquetasRSS.ITEM);

/* * Definimos los listerners de estos elementos anteriores */ // Mtodo de inicio de una nueva etiqueta item item.setStartElementListener(new StartElementListener(){ public void start(Attributes attrs) { noticiaActual = new Noticia(); } }); // Mtodo de finalizacin de una nueva etiqueta item item.setEndElementListener(new EndElementListener(){ public void end() { noticias.add(noticiaActual); } });

324

Introduccin al entorno Android

// Mtodo de obtencin etiqueta title dentro de la etiqueta item item.getChild(EtiquetasRSS.TITLE).setEndTextElementListener(new EndTextElementListener(){ public void end(String body) { noticiaActual.setTitulo(body); } }); // Mtodo de obtencin etiqueta link dentro de la etiqueta item item.getChild(EtiquetasRSS.LINK).setEndTextElementListener(new EndTextElementListener(){ public void end(String body) { noticiaActual.setEnlace(body); } }); // Mtodo de obtencin etiqueta description dentro de la etiqueta item item.getChild(EtiquetasRSS.DESCRIPTION).setEndTextElementListener(new EndTextElementListener(){ public void end(String body) { noticiaActual.setDescripcion(body); } }); // Mtodo de obtencin etiqueta pub_date dentro de la etiqueta item item.getChild(EtiquetasRSS.PUB_DATE).setEndTextElementListener(new EndTextElementListener(){ public void end(String body) { noticiaActual.setFecha(body); } }); //Usamos el objeto Xml de Android para leer el archivo XML try { Xml.parse(this.getInputStream(), Xml.Encoding.UTF_8, root.getContentHandler()); } catch (Exception e) { throw new RuntimeException(e); }

325

// Devolvemos las noticias ledas return noticias; }

// Mtodo que abre una conexin a la URL y devuelve el // puntero de tipo fichero al analizador correspondiente private InputStream getInputStream() { try { return feedUrl.openConnection().getInputStream(); } catch (IOException e) { throw new RuntimeException(e); } } }

En este nuevo modelo SAX simplificado de Android las acciones que debemos realizar dentro de cada mtodo se definen dentro de la misma clase asociadas a etiquetas concretas del XML. Para esto, lo primero que hacemos es navegar por la estructura del archivo XML hasta encontrar las etiquetas que tenemos que tratar y asignarlaa a algunos mtodos de tipo listeners ("escuchadores") disponibles como StartElementListener() de inicio de etiqueta o EndElementListener() de finalizacin de etiqueta, incluyendo las sentencias oportunas dentro de estos mtodos. Por ejemplo, para obtener el elemento <item>, en primer lugar buscamos el elemento raz del XML (<rss>) declarando un objeto RootElement. Despus, accedemos a su elemento hijo <channel> y, finalmente, obtenemos de ste ltimo el elemento hijo <item>. En cada "salto" hemos utilizado el mtodo getChild(). Una vez hemos llegado a la etiqueta buscada, asignamos los listeners necesarios. En este caso, uno de apertura y otro de cierre de etiqueta item, donde inicializamos la noticia actual y la aadimos a la lista final, respectivamente, de forma similar a como lo hemos hecho para el modelo SAX clsico.

326

Introduccin al entorno Android

Para el resto de etiquetas internas de item, procedemos de la misma manera, accediendo a ellas con getChild() y asignando los listeners necesarios. Para acabar, arrancamos todo el proceso de anlisis del XML llamando al mtodo parse(), definido en la clase android.Util.Xml, al que pasamos como parmetros el stream del archivo XML, la codificacin del documento XML y un handler SAX obtenido directamente del objeto RootElement definido anteriormente. Este modelo SAX simplificado evita implementar el handler necesario en el modelo SAX clsico. Adems, ayuda a evitar posibles errores de programacin disminuyendo la complejidad del mismo Hay que tener en cuenta que el modelo clsico es tan vlido y eficiente como ste, que nicamente simplifica el trabajo al programador.

6.4.3

DOM en Android Como hemos comentado, el modelo DOM debe leer el documento XML

completamente antes de poder realizar ninguna accin con su contenido. Es decir, cambia radicalmente la manera de leer los archivos XML. Al acabar la lectura del documento XML este modelo devuelve todo su contenido en una estructura de tipo rbol, donde los distintos elementos del fichero XML se representan en forma de nodos y su jerarqua padre-hijo se establece mediante relaciones entre dichos nodos. Por ejemplo, si tenemos el siguiente documento XML:
<noticias> <noticia> <titulo>Ttulo 1</titulo> <enlace>Enlace 1</link> </noticia> <noticia> <titulo>Ttulo 2</titulo> <enlace>Enlace 2</link> </noticia> <noticias>

El modelo DOM traduce este documento XML en el rbol siguiente:

327

Como vemos, este rbol conserva la misma informacin del fichero XML, pero en forma de nodos y relaciones entre nodos. Por esta razn es sencillo buscar fcilmente dentro de la estructura un elemento en concreto. Este rbol se conserva en memoria una vez ledo el documento completo, lo que permite procesarlo en cualquier orden y tantas veces como sea necesario, a diferencia del modelo SAX, donde el tratamiento es secuencial y siempre desde el principio hasta el final del documento. Es decir, no se puede volver atrs una vez finalizada la lectura del documento XML. El modelo DOM de Android ofrece una serie de clases y mtodos que permiten cargar la informacin de la forma descrita y facilitan la bsqueda de elementos dentro de la estructura creada. Veamos cmo queda el analizador XML usando el modelo DOM de Android:

public class ParserDom

implements RSSParser {

// URL del archivo XML private URL feedUrl;

// Constructor de la clase, se asigna la URL a la variable local protected ParserDom(String feedUrl){ try { this.feedUrl= new URL(feedUrl);

328

Introduccin al entorno Android

} catch (MalformedURLException e) { throw new RuntimeException(e); } }

// Mtodo que lee el documento XML public List<Noticia> analizar() { // Creamos acceso a la API DOM de Android DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); List<Noticia> noticias = new ArrayList<Noticia>(); try { // Creamos un analizador (parser) de DOM DocumentBuilder parser = factory.newDocumentBuilder(); // Analizamos el archivo XML Document dom = parser.parse(this.getInputStream()); // Obtenemos el elemento raz del parser Element root = dom.getDocumentElement(); // Buscamos las etiquetas ITEM dentro del elemento raz NodeList items = root.getElementsByTagName(EtiquetasRSS.ITEM); // Recorremos todos los items y vamos cargando la lista de noticias for (int i=0;i<items.getLength();i++){ // Creamos un nuevo objeto de Noticia Noticia noticia = new Noticia(); // Leemos el item i Node item = items.item(i); // Obtenemos todas las etiquetas internas de item // y buscamos los campos que nos interesan NodeList etiquetas = item.getChildNodes(); for (int j=0;j<etiquetas.getLength();j++){ Node contenido = etiquetas.item(j); String nombre = contenido.getNodeName();

329

if (nombre.equalsIgnoreCase(EtiquetasRSS.TITLE)){ noticia.setTitulo(contenido.getFirstChild().getNodeValue()); } else if (nombre.equalsIgnoreCase(EtiquetasRSS.LINK)){ noticia.setEnlace(contenido.getFirstChild().getNodeValue()); } else if (nombre.equalsIgnoreCase(EtiquetasRSS.DESCRIPTION)){ // Puede ocurrir que el texto est distribuido en varias // lneas, por lo que hay que leer todos los nodos internos StringBuilder text = new StringBuilder(); NodeList chars = contenido.getChildNodes(); for (int k=0;k<chars.getLength();k++){ text.append(chars.item(k).getNodeValue()); } noticia.setDescripcion(text.toString()); } else if (nombre.equalsIgnoreCase(EtiquetasRSS.PUB_DATE)){ noticia.setFecha(contenido.getFirstChild().getNodeValue()); } } // end for j // Aadimos la noticia al listado noticias.add(noticia); } // end for i } catch (Exception e) { throw new RuntimeException(e); } return noticias; }

// Mtodo que abre una conexin a la URL y devuelve el // puntero de tipo fichero al analizador correspondiente private InputStream getInputStream() { try { return feedUrl.openConnection().getInputStream(); } catch (IOException e) {

330

Introduccin al entorno Android

throw new RuntimeException(e); } } }

El mtodo ms importante es analizar(). De igual forma que se hace en el modelo SAX, el primer paso es instanciar la API de DOM a partir de la clase

DocumentBuilderFactory. Posteriormente, creamos un nuevo parser a partir del mtodo newDocumentBuilder(). Despus, nicamente hay que leer el documento XML invocando el mtodo parse() del parser DOM, pasndole como parmetro el stream de entrada del fichero. Al hacer esto, el documento XML se lee completamente y se crea la estructura de rbol equivalente, que se devuelve como un objeto de tipo Document por el que podemos movernos para buscar los elementos <item> que necesita la aplicacin. Para esto, lo primero que hacemos es acceder al nodo raz (root) del rbol utilizando el mtodo getDocumentElement(); en este ejemplo es la etiqueta <rss>,. Una vez que sabemos dnde est el nodo raz, vamos a buscar todos los nodos con la etiqueta <item>. Para esto, usamos el mtodo de bsqueda por el nombre de etiqueta getElementsByTagName(nombre_de_etiqueta), que devuelve una lista de tipo NodeList con todos los nodos hijos del nodo actual cuya etiqueta coincida con el nombre indicado. Una vez hemos obtenido todos los elementos <item> que contienen cada noticia, vamos a recorrerlos de uno en uno para crear todos los objetos de tipo Noticia necesarios. Para cada uno de estos elementos obtenemos sus nodos hijos mediante el mtodo getChildNodes(). Despus, recorremos estos nodos hijos obteniendo su texto y

almacenndolo en el campo correspondiente del objeto Noticia. Para saber qu etiqueta representa cada nodo hijo utilizamos el mtodo getNodeName().

6.4.4

StAX en Android Este modelo StAX de lectura de documentos XML es muy parecido a SAX. La

diferencia principal est en que, mientras que en el modelo SAX no hay control de ejecucin una vez iniciada la lectura del XML (el parser lee automticamente todo el XML desde el inicio hasta el final invocando los mtodos necesarios), en el modelo StAX podemos guiar la lectura del documento o intervenir en ella, solicitando de forma explcita la lectura del siguiente elemento del documento XML y respondiendo al resultado con las acciones oportunas. En este ejemplo hemos usado la implementacin de StAX de Android que se lama XmlPull. Fjate en el cdigo fuente de este analizador:

331

public class ParserXmlPull // URL del archivo XML private URL feedUrl;

implements RSSParser {

// Constructor de la clase, se asigna la URL a la variable local protected ParserXmlPull(String feedUrl){ try { this.feedUrl= new URL(feedUrl); } catch (MalformedURLException e) { throw new RuntimeException(e); } }

// Mtodo que lee el documento XML public List<Noticia> analizar() { List<Noticia> noticias = null; // Creamos un analizador (parser) de StAX que en Android se llama XmlPullParser XmlPullParser parser = Xml.newPullParser();

try { // Asignamos el stream del XML al parsr parser.setInput(this.getInputStream(), null); // Guardamos el tipo de evento actual = START_DOCUMENT int eventType = parser.getEventType(); Noticia noticiaActual = null; // Variable que controla si se ha acabado el documento XML boolean docAcabado = false; // Mientras no acabe el documento

332

Introduccin al entorno Android

while (eventType != XmlPullParser.END_DOCUMENT && !docAcabado){ // Variable temporal que guarda el nombre de la etiqueta String nombre = null; switch (eventType){ case XmlPullParser.START_DOCUMENT: // Creamos el listado con las noticias noticias = new ArrayList<Noticia>(); break; // Etiqueta de incicio case XmlPullParser.START_TAG: // Creamos el objeto noticia o guardamos el campo correspondiente nombre = parser.getName(); if (nombre.equalsIgnoreCase(EtiquetasRSS.ITEM)){ noticiaActual = new Noticia(); } else if (noticiaActual != null){ if (nombre.equalsIgnoreCase(EtiquetasRSS.LINK)){ noticiaActual.setEnlace(parser.nextText()); } else if (nombre.equalsIgnoreCase(EtiquetasRSS.DESCRIPTION)){ noticiaActual.setDescripcion(parser.nextText()); } else if (nombre.equalsIgnoreCase(EtiquetasRSS.PUB_DATE)){ noticiaActual.setFecha(parser.nextText()); } else if (nombre.equalsIgnoreCase(EtiquetasRSS.TITLE)){ noticiaActual.setTitulo(parser.nextText()); } } break; // Etiqueta de cierre case XmlPullParser.END_TAG: nombre = parser.getName(); if (nombre.equalsIgnoreCase(EtiquetasRSS.ITEM) && noticiaActual != null){ noticias.add(noticiaActual); } else if (nombre.equalsIgnoreCase(EtiquetasRSS.CHANNEL)){

333

docAcabado = true; } break; } eventType = parser.next(); } // end while } catch (Exception e) { throw new RuntimeException(e); } // Devolvemos las noticias return noticias; }

// Mtodo que abre una conexin a la URL y devuelve el // puntero de tipo fichero al analizador correspondiente private InputStream getInputStream() { try { return feedUrl.openConnection().getInputStream(); } catch (IOException e) { throw new RuntimeException(e); } } }

Una vez ms nos centramos en el mtodo analizar() de la clase. Primero, creamos el nuevo analizador XmlPull y asignamos el fichero de entrada en forma de stream. Despus, definimos un bucle en el que solicitamos en cada iteracin al parser el siguiente evento encontrado en la lectura del archivo XML mediante el mtodo parser.next(). Para cada evento devuelto obtenemos su tipo mediante el mtodo parser.getEventType() y ejecutamos las sentencias oportunas.

334

Introduccin al entorno Android

Una vez identificado el tipo de evento, podemos consultar el nombre de la etiqueta del elemento XML mediante parser.getName() y el texto contenido mediante parser.nextText(). Si ejecutas la aplicacin en el emulador de Android, vers que puedes acceder a los distintos modelos de tratamiento de ficheros XML pulsando la tecla men del emulador:

Si seleccionamos una de las opciones, podemos ver que se recarga el listado de noticias y el tiempo que tarda en cargar el documento XML:

Se muestra un mensaje con el tiempo que ha tardado en cargar el documento XML. As podemos valorar la eficacia de cada modelo a la hora de leer un XML completo. Observars que los modelos SAX son los ms rpidos.

335

Si haces clic sobre una noticia vers que Android te permite seleccionar el navegador que quieres usar para iniciar la accin Intent.ACTION_VIEW que permite abrir una pgina Web:

Esto ocurre porque en el Ejemplo 2 de la Unidad 5 hemos definido un navegador sencillo que carga la pgina en formato HTML para invocar implcitamente una Intencin propia.

336

Introduccin al entorno Android

El trmino base de datos se refiere a una coleccin, conjunto o depsito de datos, almacenados en un soporte magntico o de otro tipo, accesibles por mltiples usuarios de forma rpida y eficaz mediante el ordenador a travs de una o de varias aplicaciones informticas independientes de los datos.Para crear una Intencin hay que usar el objeto Intent de Android. En Android las bases de datos son privadas y nicamente una aplicacin puede acceder a ellas para leer y escribir datos. Para compartir informacin de base de datos entre aplicaciones Android hay que usar los Content Providers.Explcita: invocando la clase Java del componente que queremos ejecutar. Normalmente, se hace para invocar componentes de una misma aplicacin. Android usa SQLite como motor de base de datos relacional. Antes de crear una base de datos con el ordenador, es preciso disearla previamente sobre el papel. Usar bases de datos Android hace ms lentas las aplicaciones debido a que es necesario escribir y leer informacin de la memoria fsica del dispositivo. Por esto, es recomendable usar hilos de ejecucin. La forma usual en Android de crear, modificar y conectar con una base de datos SQLite consiste en usar la clase Java SQLiteOpenHelper. Existen dos formas de buscar y recuperar registros de una base de datos SQLite. La primera de ellas consiste en utilizar directamente el comando de consulta SQL rawQuery(). La segunda forma consiste en utilizar el mtodo especfico query() con parmetros de consulta a la base de datos. EXtensible Markup Language (XML) es un formato de datos que se usa comnmente en las aplicaciones web modernas. Los tres modelos ms extendidos para leer y escribir ficheros de tipo XML son DOM (Document Object Model), SAX (Simple API for XML) y StAX (Streaming API for XML).

337

El modelo DOM vuelca el documento XML en la memoria del dispositivo en forma de estructura de rbol, de manera que se puede acceder aleatoriamente a los elementos de las ramas. El modelo SAX se basa en eventos. La aplicacin recorre todos los elementos del archivo XML de una sola vez. La ventaja respecto a la anterior es que es ms rpido y requiere menos memoria, si bien no permite el acceso aleatorio a una de sus ramas. El modelo StAX es una mezcla de las dos modelos anteriores. En este caso, tambin se lee el fichero XML de forma secuencial, pero podemos controlar la forma en que se leen los elementos. Este modelo es tambin mucho ms rpido que DOM, pero algo ms lento que SAX. Un analizador sintctico (en ingls parser) convierte el texto de entrada en otras estructuras (comnmente rboles), que son ms tiles para el posterior anlisis; tambin captura la jerarqua implcita de la entrada.

338

CONTENT PROVIDERS, SERVICIOS Y NOTIFICACIONES

NDICE
7.1 CONTENT PROVIDERS ............................................................. 341 7.1.1 Introduccin ................................................................... 341 7.1.2 Proveedores de contenido (Content Providers) ....... 341 7.1.3 Construccin de un Content Provider ........................ 342 Uso de un Content Provider nuevo ........................................ 352 Uso de un Content Provider ya existente en Android ......... 355

7.2 7.3

7.4 SERVICIOS DE ANDROID Y RECEPTORES DE MENSAJES DE DIFUSIN .............................................................................................. 359 7.4.1 Servicios (Services) ...................................................... 359 7.4.2 Servicios propios ........................................................... 360 7.4.3 Receptor de mensajes de difucin (Broadcast Receiver) 361 7.4.4 Intencin pendiente (Pending Intent) ......................... 361 7.4.5 Ejemplo de Receptor de mensajes (Broadcast Receiver) ................................................................................ 362 7.4.6 Ejemplo de envo y recepcin de mensajes internos en una aplicacin y uso de servicios por defecto de Android364 7.4.7 Crear un servicio propio ............................................... 367 7.5 NOTIFICACIONES AL USUARIO EN ANDROID ..................... 373 7.5.1 Mensajes emergentes (Toast) .................................... 373 7.5.2 Notificaciones en la barra de estado.......................... 378 USO DE VIEWPAGER EN APLICACIONES ANDROID ......... 383 7.6.1 Cmo se usa el componente ViewPager .................. 385

7.6

Content Providers, servicios y notificaciones

7.1

CONTENT PROVIDERS

7.1.1 Introduccin En esta Unidad vamos a estudiar los proveedores de contenidos (Content Providers) para compartir informacin entre aplicaciones y el Resolvedor de contenidos (Content Resolver) para consultar y actualizar la informacin de los Content Providers. Despus, explicaremos cmo funcionan los servicios en Android. A continuacin, detallaremos el uso de notificaciones en las aplicaciones Android. Finalmente, veremos cmo utilizar el componente ViewPager que permite cambiar de pantalla deslizando el dedo horizontalmente en el dispositivo.

7.1.2 Proveedores de contenido (Content Providers) Un Proveedor de contenido (en ingls Content Provider) es el mecanismo proporcionado por Android para compartir informacin entre aplicaciones. Una aplicacin que desee compartir de manera controlada parte de la informacin que almacena con resto de aplicaciones debe declarar un Content Provider al sistema operativo a travs del cul se realiza el acceso a dicha informacin. Este mecanismo lo utilizan muchas aplicaciones estndar de un dispositivo Android, como la lista de contactos, la aplicacin de SMS para mensajes cortos, el calendario, etctera. Es decir, podemos acceder desde una aplicacin cualquiera a los datos gestionados por otras aplicaciones Android haciendo uso de los Content Providers correspondientes. Para ello, es preciso que la aplicacin tenga asignados los permisos adecuados para acceder a estos contenidos. Android, de serie, incluye varios proveedores de contenido para los tipos de datos ms comunes, como audio, vdeo, imgenes, agenda de contactos personal, etctera. Puedes ver el listado completo en el paquete android.provider. En este apartado vamos a tratar dos funcionalidades diferenciadas, que son las siguientes: Construccin de nuevos Content Providers personalizados, para que otras aplicaciones puedan acceder a la informacin contenida en la nuestra. Utilizacin de un Content Provider ya existente, para que la nuestra pueda acceder a los datos publicados por otras aplicaciones. En la Unidad 5 ya hemos visto un ejemplo muy sencillo sobre del acceso a un Content Provider ya existente, concretamente en la lista de contactos de Android.

341

Dado que es importante conocer el funcionamiento interno de un Content Provider, antes de pasar a utilizarlo en nuestras aplicaciones, vamos a estudiar cmo se construye.

7.1.3 Construccin de un Content Provider Hay dos formas de compartir informacin de una aplicacin: implementando el proveedor de contenidos propio mediante la clase ContentProvider de Android o agregando datos a un proveedor ya existente en el dispositivo, siempre y cuando los tipos de datos sean parecidos y la aplicacin tenga permiso para escribir en el Content Provider. En el Ejemplo 1 de esta Unidad vamos a mostrar cmo crear un Content Provider nuevo y cmo usarlo. Por simplificacin, ser la misma aplicacin la que acceda al Content Provider interno, si bien el cdigo necesario desde otra aplicacin es exactamente el mismo. Desde Eclipse puedes abrir el proyecto Ejemplo 1 (Content Provider) de la Unidad 7. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa, en el que hemos utilizado un Content Provider.

Si ejecutas la aplicacin, vers que tiene el siguiente aspecto:

Fjate que en este ejemplo los botones "Insertar" y "Eliminar" son excluyentes. Slo se puede borrar un alumno si previamente ha sido dado de alta y viceversa. Se trata de un Content Provider que comparte informacin de los alumnos de un colegio. La aplicacin del colegio almacena la informacin que queremos compartir en una base de datos SQLite. 342

Content Providers, servicios y notificaciones

Si bien internamente podemos tener la informacin almacenada de cualquier otra forma, por ejemplo, en un ficheros de tipo texto, en XML, etctera, en este ejemplo vamos a usar una base de datos porque es ms fcil gestionar informacin estructurada. El Content Provider es el mecanismo que permite compartir estos datos con otras aplicaciones de una forma homognea usando una interfaz estandarizada. Las tablas de la base de datos SQLite usadas por un Content Provider deben incluir siempre el campo _ID que identifica sus registros de forma unvoca.

En este ejemplo, los registros devueltos por el Content Provider de alumnos tiene este aspecto:

Lo primero que hemos hecho en este Ejemplo es crear una aplicacin muy simple que almacena y consulta los datos de los alumnos con la estructura similar a la tabla anterior. Para esto, aplicamos los mismos conceptos que ya hemos estudiado en la Unidad 6 para el tratamiento de bases de datos. Creamos una clase heredada de SQLiteOpenHelper donde definimos las sentencias SQL que crean la tabla de alumnos implementando los mtodos onCreate() y onUpgrade(). El cdigo de esta nueva clase tiene este aspecto:

public class ColegioSqliteHelper extends SQLiteOpenHelper {

//Sentencia SQL para crear la tabla de Alumnos en la BD BDColegio String sqlCreate = "CREATE TABLE Alumnos " + "(_id INTEGER PRIMARY KEY AUTOINCREMENT, " + " nombre TEXT, " + " apellidos TEXT, " + " curso TEXT )";

343

public ColegioSqliteHelper(Context contexto, String nombre, CursorFactory factory, int version) { super(contexto, nombre, factory, version); } @Override public void onCreate(SQLiteDatabase db) { //Se ejecuta la sentencia SQL de creacin de la tabla db.execSQL(sqlCreate);

String[] nombres={"Juan", "Jos", "Miguel", "Antonio", "Alicia", "Luis", "Fernanda", "Luca", "Mercedes", "Elisa"}; String[] apellidos={"Valle", "Fernndez", "Martn", "Navas", "Conde", "Daz", "Verd", "Cuenca", "Prez", "Sevilla"}; String[] cursos={"1 ESO", "1 ESO", "2 ESO", "3 ESO", "1 ESO", "4 ESO", "2 ESO", "2 ESO", "1 ESO", "4 ESO"};

//Insertamos 10 alumnos de ejemplo for(int i=0; i<10; i++) { //Insertamos los datos en la tabla Alumnos db.execSQL("INSERT INTO Alumnos (nombre, apellidos, curso) " + "VALUES ('" + nombres[i] + "', '" + apellidos[i] +"', '" + cursos[i] + "')"); } } @Override public void onUpgrade(SQLiteDatabase db, int versionAnterior, int versionNueva) { // NOTA: Por simplicidad, se elimina la tabla anterior y se crea de nuevo. // // Sin embargo, lo normal sera migrar datos de la tabla antigua a la nueva, por lo que este mtodo debera ser ms complejo.

//Se elimina la versin anterior de la tabla db.execSQL("DROP TABLE IF EXISTS Alumnos");

344

Content Providers, servicios y notificaciones

//Se crea la nueva versin de la tabla db.execSQL(sqlCreate); } }

Fjate que hemos incluido el campo _id en la tabla de la base de datos de alumnos. Este campo lo declaramos como INTEGER PRIMARY KEY AUTOINCREMENT para que se incremente automticamente cada vez que insertamos un nuevo registro en la tabla. Adems, esta clase aade algunos registros de ejemplo para poder hacer pruebas. Una vez que ya contamos con una aplicacin que ha definido su base de datos, vamos a construir el nuevo Content Provider que permite compartir sus datos con otras aplicaciones. El acceso a un Content Provider se realiza siempre mediante un identificador URI. Un identificador URI es una cadena de texto parecida a una direccin Web de Internet. Es decir, si para acceder a Google con el navegador escribimos http://www.google.es, para acceder a un Content Provider utilizamos una direccin similar a

content://es.mentor.unidad7.ejemplo/alumnos. Los identificadores URI de los Content Providers se pueden dividir en tres partes: Prefijo content://: indica que dicho recurso debe ser tratado por un Content Provider. Identificador del Content Provider (tambin llamado authority): este campo debe ser nico en cada dispositivo Android; por esto, es una buena prctica definir un authority con el nombre de clase java invertido, por ejemplo, en este ejemplo es es.mentor.ejemplo7.ejemplo. Esquema o Entidad concreta de datos que queremos que comparta el Content Provider. En este caso indicamos simplemente la tabla de alumnos. Un Content Provider puede contener datos de varias entidades distintas en esta ltima parte del URI. Todo esto es importante, ya que ser nuestro Content Provider el encargado de interpretar (parsear) el URI completo para determinar los datos que se le estn solicitando. Esto lo veremos un poco ms adelante en detalle. Por ltimo, apuntamos que, en el URI se puede hacer referencia directamente a un registro concreto de la entidad seleccionada. Esto se hace indicando al final del URI de dicho registro. Por ejemplo, el URI content://es.mentor.unidad7.ejemplo/alumnos/17 hace referencia directa al alumno con _ID = 17. A continuacin, vamos a crear el Content Provider de la aplicacin. Para esto, hay que extender la clase ContentProvider. Esta clase dispone de los mtodos abstractos siguientes , que podemos implementar: 345

onCreate(): se usa para inicializar todos los recursos necesarios para el funcionamiento del nuevo Content Provider.

query(): permite consultar datos que haya en el Content Provider. insert(): permite insertar datos en el Content Provider. update(): permite actualizar datos del Content Provider. delete(): permite borrar datos del Content Provider. getType(): permite conocer el tipo de dato devuelto por el Content Provider.

Adems de implementar estos mtodos, tambin definimos una serie de cadenas constantes en la clase del Content Provider. A continuacin, estudiamos por partes la nueva clase ColegioContentProvider que extienda de ContentProvider. En primer lugar definimos el URI con el que se accede al Content Provider de la aplicacin: Vamos a usar content://es.mentor.unidad7.ejemplo/alumnos:

//Definicin del CONTENT_URI private static final String uri = "content://es.mentor.unidad7.ejemplo/alumnos"; public static final Uri CONTENT_URI = Uri.parse(uri);

En todos los Content Providers de Android es necesario encapsular este identificador URI en un objeto esttico del tipo Uri que hemos llamado CONTENT_URI. A continuacin, definimos varias constantes con los nombres de los campos proporcionados por el Content Provider. Como ya hemos comentado anteriormente, existen columnas predefinidas que deben tener todos los Content Providers, como la columna _ID. Esta columna estndar est definida internamente en la clase BaseColumns, por lo que al aadir los campos (columnas) del Content Provider slo hay que indicar las nuevas columnas.

//Clase interna para declarar las constantes de las columnas = campos public static final class Alumnos implements BaseColumns { private Alumnos() {} //Nombres de las columnas public static final String COL_NOMBRE = "nombre"; public static final String COL_APELLIDOS = "apellidos"; public static final String COL_CURSO = "curso";

346

Content Providers, servicios y notificaciones

Por ltimo, vamos a definir varias cadenas constantes privadas que almacenen informacin auxiliar con el nombre de la base de datos, su versin y la tabla a la que accede el Content Provider.

private ColegioSqliteHelper colegioBDhelper; private static final String BD_NOMBRE = "BDColegio"; private static final int BD_VERSION = 1; private static final String TABLA_ALUMNOS = "Alumnos";

Lo primero que debe hacer un Content Provider cuando otra aplicacin le solicita una operacin es interpretar el URI utilizado. Para facilitar esta tarea al programador, Android proporciona la clase llamada UriMatcher que interpreta los patrones en un URI. Esto es muy til para determinar, por ejemplo, si un URI hace referencia a una tabla genrica o a un registro concreto a travs de su ID: content://es.mentor.unidad7.ejemplo/alumnos: acceso genrico a la tabla de alumnos. content://es.mentor.unidad7.ejemplo/alumnos/17: acceso directo al alumno con el ID = 17. Para ello definimos tambin en esta clase un objeto UriMatcher y dos nuevas constantes que representan los dos tipos de URI que hemos indicado: acceso genrico a la tabla (ALUMNOS) o acceso a un alumno por ID (ALUMNOS_ID). Despus, creamos el objeto UriMatcher indicando el formato de ambos tipos de URI de forma que pueda diferenciarlos y devolvernos su tipo (una de las dos constantes definidas, ALUMNOS o ALUMNOS_ID):
//Necesario para UriMatcher private static final int ALUMNOS = 1; private static final int ALUMNOS_ID = 2; private static final UriMatcher uriMatcher;

static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI("es.mentor.unidad7.ejemplo", "alumnos", ALUMNOS); uriMatcher.addURI("es.mentor.unidad7.ejemplo", "alumnos/#", ALUMNOS_ID); }

347

En el cdigo anterior vemos que mediante el mtodo addUri() indicamos el campo authority del URI, el formato de la entidad que estamos solicitando y el tipo que identifica el formato del dato. Ms adelante veremos cmo utilizar esto de forma prctica. Posteriormente, vamos a implementar los mtodos internos del Content Provider. El primero de ellos es onCreate(). En este mtodo inicializamos la base de datos mediante la clase ColegioSqliteHelper que creamos anteriormente:

public boolean onCreate() { // Inicializamos el conector con la BD colegioBDhelper = new ColegioSqliteHelper( getContext(), BD_NOMBRE, null, BD_VERSION); return true; }

El mtodo ms importante del Content Provider es query(). Este mtodo recibe como parmetros un URI, una lista de nombres de columna, un criterio de seleccin, una lista de valores para las variables utilizadas en el criterio anterior y un criterio de ordenacin. Todos estos parmetros son similares a los que estudiamos cuando tratamos sobre las bases de datos SQLite para Android. El mtodo query() devuelve los datos solicitados segn el URI, los criterios de seleccin y ordenacin indicados como parmetros. As, si el URI hace referencia a un alumno en concreto por su ID, se debe ser el nico registro devuelto. Si se solicita el contenido de la tabla de alumnos, hay que realizar la consulta SQL correspondiente a la base de datos respetando los criterios pasados como parmetros. Para distinguir entre los dos tipos posibles de URI utilizamos el mtodo match() del objeto uriMatcher. Si el tipo devuelto es ALUMNOS_ID, es decir, se ha solicitado informacin de un alumno en concreto, sustituimos el criterio de seleccin por uno que busca en la tabla de alumnos slo el registro con el ID indicado en la URI. Para obtener este ID utilizamos el mtodo getLastPathSegment() del objeto uri, que extrae el ltimo elemento de la URI, en este caso el ID del alumno. Despus, hay que realizar la consulta a la base de datos mediante el mtodo query() de SQLiteDatabase. Esto es muy fcil, ya que los parmetros son similares a los empleados en el mtodo query() del Content Provider:
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // Accedemos a la base de datos en modo lectura SQLiteDatabase db = colegioBDhelper.getReadableDatabase();

348

Content Providers, servicios y notificaciones

//Si es una consulta a un ID concreto construimos el WHERE String where = selection; if(uriMatcher.match(uri) == ALUMNOS_ID){ // Obtenemos el ltimo segmento del URI where = "_id=" + uri.getLastPathSegment(); } // Hacemos la consulta a la BD Cursor c = db.query(TABLA_ALUMNOS, projection, where, selectionArgs, null, null, sortOrder); return c; }

Podemos observar que los resultados se devuelven en forma de Cursor, tal y como lo hace el mtodo query() de SQLiteDatabase. Por otra parte, los mtodos update() y delete() son completamente similares al mtodo anterior. nicamente se diferencian en que stos devuelven como resultado el nmero de registros afectados en lugar de un cursor. Veamos su cdigo:

@Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // Variable temporal int cont; // Accedemos a la base de datos en modo escritura SQLiteDatabase db = colegioBDhelper.getWritableDatabase(); //Si es una actualizacin a un ID concreto construimos el WHERE String where = selection; if(uriMatcher.match(uri) == ALUMNOS_ID){ where = "_id=" + uri.getLastPathSegment(); } // Actualizamos la tabla cont = db.update(TABLA_ALUMNOS, values, where, selectionArgs); // Devolvemos el n de registros afectados por la consulta return cont; }

349

@Override public int delete(Uri uri, String selection, String[] selectionArgs) { // Variable temporal int cont; // Accedemos a la base de datos en modo escritura SQLiteDatabase db = colegioBDhelper.getWritableDatabase(); //Si borramos un ID concreto construimos el WHERE String where = selection; if(uriMatcher.match(uri) == ALUMNOS_ID){ where = "_id=" + uri.getLastPathSegment(); } // Borramos los registros cont = db.delete(TABLA_ALUMNOS, where, selectionArgs); // Devolvemos el n de registros afectados por la consulta return cont; }

El mtodo insert() se implementa de forma ligeramente distinta. La diferencia en este caso est en que hay que devolver el URI que hace referencia al nuevo registro insertado. Para ello, obtenemos el nuevo ID del elemento insertado y construimos el nuevo URI de respuesta mediante el mtodo auxiliar ContentUris.withAppendedId(), que recibe como parmetro el URI del Content Provider y el ID del nuevo elemento:

public Uri insert(Uri uri, ContentValues values) { // Variable temporal que guarda el ID dado de alta long regId = -1; // Accedemos a la base de datos en modo escritura SQLiteDatabase db = colegioBDhelper.getWritableDatabase(); // Insertamos el registro en la tabla regId = db.insert(TABLA_ALUMNOS, null, values); // Uri con el resultado de la operacin Uri newUri = ContentUris.withAppendedId(CONTENT_URI, regId); return newUri; }

350

Content Providers, servicios y notificaciones

Por ltimo, slo queda implementar el mtodo getType(). Este mtodo se utiliza para identificar el tipo de datos que devuelve el Content Provider. Este tipo de datos se expresa como un MIME Type, tal y como hacen los navegadores Web para determinar qu tipo de datos se est recibiendo al hacer una peticin a un servidor. Identificar el tipo de datos que devuelve un Content Provider ayuda a Android a determinar qu aplicaciones son capaces de procesar dichos datos. En este ejemplo, existen dos tipos MIME distintos para cada entidad del Content Provider: el primero se usa cuando se devuelve un registro nico concreto y el segundo cuando se devuelven varios registros simultneamente. As, podemos utilizar los siguientes patrones para definir uno u otro tipo de datos: vnd.android.cursor.item/vnd.xxxxxx: Registro nico vnd.android.cursor.dir/vnd.xxxxxx: Listado de registros

En este ejemplo, hemos definido los siguientes tipos: vnd.android.cursor.dir/vnd.mentor.alumno vnd.android.cursor.item/vnd.mentor.alumno

Teniendo esto en cuenta, la implementacin del mtodo getType() tiene estas sentencias:

@Override public String getType(Uri uri) { // Devolvemos un tipo de dato en funcin del URI int match = uriMatcher.match(uri); switch (match) { case ALUMNOS: return "vnd.android.cursor.dir/vnd.mentor.alumno"; case ALUMNOS_ID: return "vnd.android.cursor.item/vnd.mentor.alumno"; default: return null;

Se puede observar que utilizamos de nuevo el objeto UriMatcher para determinar el tipo de URI que se est solicitando y en funcin de ste devolvemos un tipo MIME u otro. Para finalizar con el Content Provider, debemos declararlo en el fichero

AndroidManifest.xml, para que, al instalar la aplicacin en el dispositivo Android, ste conozca la existencia de dicho recurso. Para ello, basta con aadir un nuevo elemento 351

<provider> dentro de <application> indicando el nombre del Content Provider y su authority:

<application android:icon="@drawable/icon" android:label="@string/app_name">

...

<provider android:name=".ColegioContentProvider" android:authorities="es.mentor.unidad7.ejemplo"/>

</application>

7.2

Uso de un Content Provider nuevo


Una vez completado el Content Provider, vamos a usarlo desde la propia aplicacin

del ejemplo que hemos creado. Lo hacemos as para simplificar el ejemplo; de cualquier forma, el cdigo necesario es exactamente el mismo si lo usamos desde otra aplicacin distinta. Utilizar un Content Provider ya existente es muy sencillo, sobre todo si lo comparamos con todo el proceso anterior de construccin de uno nuevo. Para ello, vamos a usar la clase ContentResolver de Android que permite realizar acciones (consultas de datos, actualizaciones de informacin, etctera) con cualquier Content Provider que est disponible en el sistema operativo Android. Desde la actividad principal hay que utilizar el mtodo getContentResolver() para obtener la referencia de la aplicacin al objeto ContentResolver. Una vez obtenida esta referencia, podemos utilizar sus mtodos query(), update(), insert() y delete() para realizar las acciones equivalentes sobre el Content Provider. En la aplicacin del ejemplo anterior hay tres botones en la pantalla principal: uno para hacer una consulta de todos los alumnos, otro para insertar registros nuevos y el ltimo para eliminar todos los registros nuevos insertados con el segundo botn. Empecemos por la consulta de alumnos. El procedimiento es prcticamente igual al que hemos estudiado para acceder a bases de datos SQLite. Primero definimos una matriz con los nombres de las columnas de la tabla que queremos recuperar en el resultado de la consulta: ID, nombre, apellidos y curso.

352

Content Providers, servicios y notificaciones

Tras esto, obtenemos una referencia al Content Resolver y utilizamos su mtodo query() para obtener los resultados en forma de Cursor. El mtodo query() se invoca con los parmetros siguientes: el Uri del Content Provider al que queremos acceder, la matriz de columnas que queremos recuperar, el criterio de seleccin, los argumentos variables y el criterio de ordenacin de los resultados. En este caso, para no complicar el ejemplo tan slo indicamos los dos primeros: CONTENT_URI del Content Provider y la matriz de columnas que acabamos de definir:
//Columnas de la tabla String[] columnas = new String[] { Alumnos._ID, Alumnos.COL_NOMBRE, Alumnos.COL_APELLIDOS, Alumnos.COL_CURSO };

// Definimos la Uri que queremos usar Uri alumnosUri = ColegioContentProvider.CONTENT_URI;

// Acceso al contentresolver de la aplicacin ContentResolver cr = getContentResolver();

//Hacemos la consulta Cursor cur = cr.query(alumnosUri, columnas, //Columnas solicitadas null, null, null); //Condicin de la query //Argumentos variables de la query //Orden de los resultados

Una vez solicitada la consulta, hay que recorrer el cursor para procesar los registros. Veamos cmo queda el cdigo fuente:
// Si obtenemos resultados if (cur.moveToFirst()) { String nombre; String apellidos; String curso;

353

int colNombre = cur.getColumnIndex(Alumnos.COL_NOMBRE); int colApellidos = cur.getColumnIndex(Alumnos.COL_APELLIDOS); int colCurso = cur.getColumnIndex(Alumnos.COL_CURSO); txtResultados.setText("Resultado consulta:\n\n"); // Recorremos todos los registros y los mostramos en pantalla

do { nombre = cur.getString(colNombre); apellidos = cur.getString(colApellidos); curso = cur.getString(colCurso); txtResultados.append(nombre + " " + apellidos + ". Curso: " + curso + "\n"); } while (cur.moveToNext()); // end while }

Insertar nuevos registros se implementa exactamente igual que si tratramos directamente con bases de datos SQLite. Rellenamos primero un objeto ContentValues con los datos del nuevo alumno y utilizamos el mtodo insert() pasndole como parmetros la URI del Content Provider y los datos del nuevo registro:
ContentValues values = new ContentValues(); values.put(Alumnos.COL_NOMBRE, "Jess"); values.put(Alumnos.COL_APELLIDOS, "Sanz"); values.put(Alumnos.COL_CURSO, "BACHIDERATO"); ContentResolver cr = getContentResolver(); cr.insert(ColegioContentProvider.CONTENT_URI, values); txtResultados.setText("Se ha insertado el alumno. Pulsa el botn 'Consultar' para ver todos los alumnos.");

Por ltimo, la eliminacin de registros la hacemos directamente utilizando el mtodo delete() del Content Resolver, indicando como segundo parmetro el criterio de identificacin de los registros que queremos eliminar:
ContentResolver cr = getContentResolver(); cr.delete(ColegioContentProvider.CONTENT_URI, Alumnos.COL_NOMBRE + " = 'Jess'", null); txtResultados.setText("Se ha borrado el alumno. Pulsa el botn 'Consultar' para ver todos los alumnos.");

354

Content Providers, servicios y notificaciones

7.3

Uso de un Content Provider ya existente en Android

Hemos visto lo sencillo que resulta acceder a los datos proporcionados por un Content Provider. Mediante este mecanismo podemos utilizar en nuestras aplicaciones muchos datos de la propia plataforma Android. En la documentacin oficial del paquete android.provider podemos consultar los datos que estn disponibles a travs de este mecanismo. Entre ellos encontramos el historial de llamadas, la agenda de contactos, etctera. Para ver cmo se usan los Content Providers con un tipo de datos definido por Android, en el Ejemplo 2 de esta Unidad vamos a consultar el historial de llamadas del dispositivo, usando el Content Provider android.provider.CallLog. Desde Eclipse puedes abrir el proyecto Ejemplo 2 (Historial de llamadas) de la Unidad 7. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa, en el que hemos utilizado un Content Provider definido por Android. Para poder ver algn dato en este ejemplo, en primer lugar, vamos a registrar varias llamadas en el emulador de Android. As, los resultados de la consulta al historial de llamadas devolvern algunos registros. A continuacin, vamos a simular varias llamadas salientes desde el emulador y varias llamadas entrantes desde el DDMS. Las llamadas salientes son sencillas de realizar usando el emulador como si se tratara de un telfono normal y corriente. Accedemos al icono telfono, marcamos un nmero y descolgamos como si se tratara de un dispositivo fsico:

355

Para simular llamadas entrantes debemos acceder desde Eclipse a la vista del DDMS. En esta vista, en la pestaa Emulator Control aparece el apartado Telephony Actions, donde podemos introducir un nmero cualquiera de telfono origen Incoming number y pulsar el botn Call para que el dispositivo del emulador reciba una llamada entrante. Sin aceptar la llamada en el emulador, pulsaremos Hang Up para terminar la llamada simulando as una llamada perdida.

Una vez hemos simulado tanto llamadas entrantes como llamadas salientes, vamos a desarrollar una aplicacin que consulte el historial de llamadas. Si consultamos la documentacin del Content Provider android.provider.CallLog, veremos que podemos extraer diferentes datos relacionados con la lista de llamadas. En este ejemplo vamos a usar nicamente el nmero origen o destino de la llamada y el tipo de llamada (entrante, saliente y perdida). Los nombres de estas columnas se almacenan en las constantes Calls.NUMBER y Calls.TYPE respectivamente. A continuacin, definimos una matriz con las columnas que vamos a recuperar, obtenemos la referencia al Content Resolver de la aplicacin y ejecutamos la consulta llamando al mtodo query(). Por ltimo, recorremos el cursor obtenido y procesamos los resultados. Veamos el cdigo fuente:

356

Content Providers, servicios y notificaciones

// Constantes que definen los campos que consultamos String[] columnas = new String[] {Calls.TYPE, Calls.NUMBER }; // La Uri est predefinida en una constante del sistema Uri llamadasUri = Calls.CONTENT_URI;

// Cargamos el Content Resolver de la aplicacin ContentResolver cr = getContentResolver(); // Hacemos una consulta de las llamadas Cursor cur = cr.query(llamadasUri, columnas, //Columnas a devolver null, null, null); //Condicin de la query //Argumentos variables de la query //Orden de los resultados

// Si hay llamadas mostramos la informacin if (cur.moveToFirst()) { int tipo; String tipoLlamada = ""; String telefono; // Obtenemos el ndice de las columnas int colTipo = cur.getColumnIndex(Calls.TYPE); int colTelefono = cur.getColumnIndex(Calls.NUMBER); // Limpiamos la etiqueta de resultados txtResultados.setText(""); // Mientras haya datos mostramos la informacin al usuario do { // Obtenemos la informacin de las columnas tipo = cur.getInt(colTipo); telefono = cur.getString(colTelefono); // Segn el tipo de llamada usamos un texto distinto if(tipo == Calls.INCOMING_TYPE) tipoLlamada = "ENTRADA"; else if(tipo == Calls.OUTGOING_TYPE)

357

tipoLlamada = "SALIDA"; else if(tipo == Calls.MISSED_TYPE) tipoLlamada = "PERDIDA"; // Mostramos la informacin txtResultados.append(tipoLlamada + " : " + telefono + "\n"); } while (cur.moveToNext()); // end while } else txtResultados.setText("No hay ninguna llamada en el histrico del telfono. Para que funcione bien esta aplicacin debes simular alguna llamada entrante o saliente. En la teora del curso de esta Unidad se muestra cmo hacerlo.");

Adems, en el cdigo fuente anterior decodificamos el valor del tipo de llamada comparando el resultado con las constantes Calls.INCOMING_TYPE (llamada entrante), Calls.OUTGOING_TYPE (llamada saliente) y Calls.MISSED_TYPE (llamada perdida). Para que la aplicacin pueda acceder al historial de llamadas del dispositivo hay que incluir en el fichero AndroidManifest.xml el permiso READ_CONTACTS:
<uses-permission android:name="android.permission.READ_CONTACTS"> </uses-permission>

Si ejecutas el ejemplo 2, vers que tiene el siguiente aspecto:

358

Content Providers, servicios y notificaciones

7.4

SERVICIOS DE ANDROID Y RECEPTORES DE MENSAJES DE DIFUSIN

7.4.1 Servicios (Services) Un Servicio (en ingls service) es un componente de una aplicacin Android que se ejecuta en segundo plano, sin interactuar con el usuario (no tiene interfaz de usuario) y realiza operaciones de larga duracin. La plataforma Android ofrece una gran cantidad de servicios predefinidos en el sistema a los que podemos acceder a travs de las clases de tipo Manager. En una podemos acceder a estos servicios a travs del mtodo getSystemService(). Cuando una aplicacin Android define sus propios Servicios, deben ser declarados en el fichero AndroidManifest.xml del proyecto. Un componente de una aplicacin Android puede iniciar un servicio que seguir funcionando en segundo plano, incluso si el usuario cambiara a otra aplicacin. Adems, un componente de la aplicacin puede unirse (en ingls bind) al servicio para interactuar con l e incluso realizar comunicaciones entre procesos. Por ejemplo, un servicio podra conectarse a Internet en un segundo plano para descargar noticias, reproducir msica, etctera,. Un servicio puede funcionar de dos modos: Autnomo: cuando un componente de la aplicacin, por ejemplo, una actividad, inicia el servicio mediante el mtodo StartService(). Una vez arrancado, el servicio puede ejecutarse en segundo plano de forma indefinida, incluso si el componente que lo inici se destruye. Normalmente, un servicio iniciado de esta forma realiza una nica operacin y no devuelve el resultado al componente que lo inicia. Por ejemplo, puede descargar de Internet un archivo o cargarlo. Cuando la operacin finaliza, el servicio debe detenerse. Dependiente o Ligado (en ingls a este modo se le denomina "bind"): cuando un componente de la aplicacin se une al servicio mediante el mtodo bindService(). Un servicio ligado ofrece una interfaz de tipo cliente-servidor que permite a los componentes de una aplicacin interactuar con l enviando peticiones y recibiendo su resultado. Un servicio ligado slo se ejecuta mientras otro componente de la aplicacin est unido a l. Es posible unir un mismo servicio a varios componentes de una o de varias aplicaciones al mismo tiempo; sin embargo, cuando todos ellos se desligan, el servicio se destruye. Un servicio puede funcionar de las dos formas anteriores simultneamente, es decir, se puede arrancar en modo Autnomo (de manera indefinida) y tambin en modo Ligado. 359 Actividad

Simplemente hay que implementar los mtodos onStartCommand() para el modo Autnomo y onBind() para el modo Ligado. Cualquier componente de una aplicacin puede iniciar un servicio. Incluso un componente de otra aplicacin distinta a la que define el servicio tambin puede iniciarlo de la misma forma que iniciaramos una Actividad de otra aplicacin mediante Intenciones. Tambin se puede declarar un servicio como privado en la aplicacin, en el archivo AndroidManifest.xml, y bloquear el acceso desde otras aplicaciones. Los servicios tienen que ser declarados en el archivo AndroidManifest.xml con la etiqueta <service android:name="nombreClase"> </service> y la implementacin de la clase debe heredarse de la clase Service. IMPORTANTE: los servicios propios de una aplicacin se ejecutan en el hilo principal de su proceso; por lo tanto, para no bloquear el hilo principal o de la interfaz debemos, ejecutar estos servicios con hilos de ejecucin, tal y como hemos visto en la Unidad 3. 7.4.2 Servicios propios Una aplicacin puede declarar su propio servicio para llevar a cabo operaciones que tarden en ejecutarse y no necesiten interactuar con el usuario o para suministrar una nueva funcionalidad a otras aplicaciones. A continuacin, se muestra un esquema con los mtodos que invoca Android cuando lanzamos un servicio segn su modo de funcionamiento:

360

Content Providers, servicios y notificaciones

Una Actividad puede iniciar un servicio en modo Autnomo a travs del mtodo StartService() y detenerlo mediante el mtodo StopService(). Cuando lo hacemos, Android invoca su mtodo onCreate(); despus, se invoca el mtodo onStartCommand() con los datos proporcionados por la Intencin de la actividad. En el mtodo startService() tambin podemos indicar como parmetro el comportamiento del ciclo de vida de los servicios: START_STICKY: se utiliza para indicar que el servicio debe ser explcitamente iniciado o parado. START_NOT_STICKY: el servicio termina automticamente cuando el mtodo onStartCommand() finaliza su ejecucin.

Si la actividad quiere interactuar con un servicio (modo Dependiente o Ligado) para, por ejemplo, mostrar el progreso de una operacin, puede utilizar el mtodo bindService(). Para esto, hay que usar el objeto ServiceConnection, que permite conectarse al servicio y devuelve un objeto de tipo IBinder, que la actividad puede utilizar para comunicar con el servicio. Ms adelante veremos en detalle cmo definir servicios en modo Ligado dentro de las aplicaciones Android.

7.4.3 Receptor de mensajes de difucin (Broadcast Receiver) Hay casos en los que se usan mensajes de difusin (Broadcast) para comunicar eventos entre servicios. Estos mensajes son, en realidad, Intents. En este caso usamos la clase Receptor de mensajes de difusin (BroadcastReceiver), que debemos declarar en el archivo AndroidManifest.xml. Esta clase puede recibir Intenciones (Intents), es decir, mensajes enviados por otro componente de Android mediante el mtodo sendBroadcast() de la clase Context (contexto de la aplicacin). La clase BroadCastReceiver define el nico mtodo OnReceive() donde se recibe el mensaje de difusin; por lo tanto, fuera de este mtodo no se puede realizar ninguna operacin asncrona porque el mensaje de difusin ya no est activo.

7.4.4 Intencin pendiente (Pending Intent) En este apartado tambin hacemos uso de las Intenciones pendientes (Pending Intents). Una Intencin pendiente es un tipo de Intent (mensaje entre componentes de Android) que permite que otra aplicacin ejecute un bloque de cdigo predefinido con los permisos de ejecucin de la aplicacin que inicia esta Intencin pendiente. Este tipo de Intenciones se usa mucho para iniciar aplicaciones como el Administrador de notificaciones (Notification Manager) y Administrador de alarmas (Alarm Manager). 361

Para enviar un mensaje de difusin mediante una Intencin pendiente hay que usar su mtodo getBroadcast(). Para iniciar una subactividad mediante una Intencin pendiente hay que usar su mtodo getActivity().

7.4.5 Ejemplo de Receptor de mensajes (Broadcast Receiver) A continuacin, vamos a definir un receptor de mensajes de difusin (Broadcast Receiver) que escucha los mensajes que lanza Android al resto de componentes del sistema operativo cuando ocurre un cambio en el estado del telfono. Si el dispositivo recibe una llamada de telfono, entonces nuestro receptor de mensajes recibir una notificacin y registrar la llamada. Para que la aplicacin funcione bien, debemos incluir las siguientes sentencias en el archivo AndroidManifest.xml del proyecto:

<application android:icon="@drawable/icon" android:label="@string/app_name">

<receiver android:name="ReceptorLlamadas"> <intent-filter> <action android:name="android.intent.action.PHONE_STATE"> </action> </intent-filter> </receiver> </application> ...

<uses-permission android:name="android.permission.READ_PHONE_STATE"> </uses-permission>

En las sentencias anteriores hemos declarado al sistema usando la etiqueta <receiver> que esta aplicacin desea recibir los mensajes de difusin del tipo (etiqueta <intent-filter>) estado del telfono (PHONE_STATE) usando la clase ReceptorLlamadas para gestionarlas. La clase ReceptorLlamadas que implementa el receptor de mensajes de difusin contiene las siguientes sentencias:

public class ReceptorLlamadas extends BroadcastReceiver {

362

Content Providers, servicios y notificaciones

@Override public void onReceive(Context context, Intent intent) { Bundle extras = intent.getExtras(); if (extras != null) { String estado = extras.getString(TelephonyManager.EXTRA_STATE); Log.w("ESTADO TELEFONO", estado); if (estado.equals(TelephonyManager.EXTRA_STATE_RINGING)) { String numeroTelefono= extras .getString(TelephonyManager.EXTRA_INCOMING_NUMBER); Log.w("NUMERO TELEFONO", numeroTelefono); } } } }

Como hemos comentado anteriormente, el mensaje de difusin se recibe en el mtodo onReceive() de la clase BroadcastReceiver. En este mtodo hemos obtenido la informacin extra de la intencin y la hemos mostrado en el Log de mensajes de Eclipse.

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Receptor de mensajes de difusin) de la Unidad 7. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa anterior, en el que hemos definido un Receptor de mensajes de difusin. Si ejecutamos la aplicacin, usando el DDMS para simular una llamada de telfono entrante, veremos la siguiente pantalla:

363

7.4.6 Ejemplo de envo y recepcin de mensajes internos en una aplicacin y uso de servicios por defecto de Android En este Ejemplo 3 vamos a usar el Gestor de alarmas (AlarmManager) y el de vibraciones del telfono (VibratorManager) para iniciar los servicios por defecto "Alarma" y "Vibracin" de Android. Vamos a configurar una alarma en el gestor de alarmas de Android y, cuando termine la cuenta atrs del tiempo que establezca el usuario, el gestor de alertas mandar un mensaje de difusin al receptor de mensajes que hemos definido previamente en la misma aplicacin. Para recibir el mensaje de difusin hemos creado el receptor MiBroadcastReceiver a partir de la clase BroadcastReceiver:

public class MiBroadcastReceiver extends BroadcastReceiver { @Override // Definimos el mtodo onReceive para recibir mensajes de difusin public void onReceive(Context context, Intent intent) { Toast.makeText(context, "Se ha acabado la cuenta atrs! \nEl telfono est vibrando", Toast.LENGTH_LONG).show(); // Vibramos el telfono durante 2 segundos obteniendo el servicio Vibrator de Android Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); vibrator.vibrate(2000);

364

Content Providers, servicios y notificaciones

} }

Este receptor de mensajes busca el servicio de Vibracin (Vibrator), se conecta a l y le indica que vibre el telfono durante dos segundos. Para cargar el servicio "Vibracin" por defecto de Android hemos usado el mtodo getSystemService(), al que indicamos como parmetro el nombre del servicio al que queremos acceder. Para que Android conozca que tiene disponible un receptor de mensajes de difusin y permita a la aplicacin el acceso al servicio de vibracin, debemos aadir al fichero AndroidManifest.xml las siguientes lneas:

<receiver android:name="MiBroadcastReceiver"></receiver> ... <uses-permission android:name="android.permission.VIBRATE"></uses-permission>

A continuacin, solo queda indicar en la actividad principal que se inicie una la cuenta atrs:

public class AlarmaActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); }

public void iniciarCuenta(View view) { // Obtenemos el tiempo de la cuenta atrs EditText texto = (EditText) findViewById(R.id.tiempo); if (texto.getText().equals("")){ Toast.makeText(this, "Al menos debes indicar 1 segundo", Toast.LENGTH_LONG).show(); return; }

365

int i = Integer.parseInt(texto.getText().toString()); // Cargamos el BroadcastReceiver Intent intent = new Intent(this, MiBroadcastReceiver.class); // Lo iniciamos como una Intencin pendiente PendingIntent pendingIntent = PendingIntent.getBroadcast( this.getApplicationContext(), 1, intent, 0); // Creamos una alarma AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); // Establecemos el tiempo de la alarma e indicamos el pendingIntent que se debe ejecutar cuando acabe la cuenta alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (i * 1000), pendingIntent); // Mostramos un mensaje indicando que comienza la cuenta atrs Toast.makeText(this, "Inicio de Cuenta atrs de " + i + " segundos", Toast.LENGTH_LONG).show(); } }

Para cargar el servicio "Alarma" por defecto de Android, hemos usado el mtodo getSystemService(), al que indicamos como parmetro el nombre del servicio al que queremos acceder. En el cdigo anterior podemos observar que hemos usado la clase AlarmManager para acceder al servicio de gestin de alarmas. Con su mtodo set() se crea una nueva alarma que salta pasados n segundos y que lanza, a continuacin, la intencin pendiente (es realidad es una intencin que hereda los permisos de la actividad principal). Esta intencin pendiente se forma a partir de una intencin normal que invoca explcitamente la clase que recibe el mensaje y que transformamos en un mensaje de difusin con el mtodo getBroadcast() de PendingIntent. Si ejecutas el Ejemplo 3 de esta Unidad vers las siguientes pantallas:

366

Content Providers, servicios y notificaciones

7.4.7 Crear un servicio propio En el Ejemplo 4 de esta Unidad vamos a ver cmo definir un servicio privado en modo Ligado dentro de una aplicacin Android. Los servicios deben utilizarse para mantener en segundo plano tareas en ejecucin de la aplicacin, como descargar mensajes de correo de un servidor. Cuando el usuario solicita que se actualice su buzn de correo, la aplicacin que ya est ligada (en ingls bind) al servicio, invoca uno de sus mtodos para obtener los nuevos mensajes recibidos. Como ya hemos comentado, para crear un servicio debemos definir una clase que se extienda de la clase Service de Android:

public class Servicio extends

Service {

// Variable donde guardamos los datos que devuelve el servicio private ArrayList<String> listado = new ArrayList<String>(); // Constante donde tenemos los datos que vamos a ir cargando cada 5 segundos en la variable anterior private static String[] listadoDatos = { "El comercio internacional ha aumentado un 7%", "Hoy se publica un nuevo libro de Prez Jimnez", "Benetton retira la foto que irrit al Vaticano", "Diego Rivera vuelve al Nueva York de la crisis",

367

"Facebook reconoce un ataque coordinado", "Bradley Cooper, el hombre ms sexy del mundo", "Dimite el responsable en Europa del FMI por 'motivos personales'", "El invierno ya est aqu" }; // Usamos el temporizador para ir aadiendo datos al listado private Timer temporizador = new Timer(); // Cada 5 segundos actualizamos los datos del listado private static final long INTERVALO = 5000; // IBinder que usa la actividad principal para unirse al servicio y obtener informacin private final IBinder miBinder = new MiBinder(); // Variable que usamos para controlar el ltimo elemento aadido al listado private int indice = 0;

// Debemos definir redefinir el mtodo onCreate public void onCreate() { super.onCreate(); // Iniciamos el temporizado que va cargando datos poco a poco en el listado temporizador.scheduleAtFixedRate(new TimerTask() { @Override public void run() { // Si el listado ya contiene los 7 elementos, quitamos el primer elemento if (listado.size() >= 8) { listado.remove(0); } // Aadimos el listado el elemento siguiente de la matriz constante listado.add(listadoDatos[indice++]); // Si ya hemos llegado al ltimo elemento, volvemos a empezar if (indice >= listadoDatos.length) { indice = 0; } }

368

Content Providers, servicios y notificaciones

}, 0, INTERVALO); }

// Debemos redefinir el mtodo onDestroy @Override public void onDestroy() { super.onDestroy(); // Si el temporizador sigue funcionando, lo liberamos de la memoria if (temporizador != null) { temporizador.cancel(); } }

// Es obligatorio redefinir este mtodo. // Devuelve el canal de comunicacin con el servicio. @Override public IBinder onBind(Intent arg0) { return miBinder; }

// Clase que devuelve el contexto del servicio public class MiBinder extends Binder { Servicio getService() { return Servicio.this; } }

// Mtodo del servicio que invoca la actividad principal public List<String> getDatos() { return listado; } }

369

Como vamos a usar el servicio en modo Ligado, hemos definido el mtodo onBind() en el cdigo Java anterior. En el archivo AndroidManifest.xml debemos declarar el nuevo servicio:

<service android:name=".Servicio"></service>

En la actividad principal del Ejemplo 4 implementamos cmo usar el servicio en modo Ligado:

public class ServicioActivity extends Activity { // Variable donde almacenamos el servicio private Servicio s; // Matriz que se usa para cargar el adaptador del ListView de la actividad principal private ArrayList<String> matrizAdaptador; // Adaptador del ListView de la actividad principal private ArrayAdapter<String> adaptador;

// Variable que recibe la Conexin al servicio de la aplicacin private ServiceConnection miConexion = new ServiceConnection() { // Al conectar al servicio, obtenemos una referencia del mismo y // mostramos un mensaje al usuario public void onServiceConnected(ComponentName className, IBinder binder) { s = ((Servicio.MiBinder) binder).getService(); Toast.makeText(ServicioActivity.this, "Conectado al servicio", Toast.LENGTH_SHORT).show(); } // Desconexin del servicio, liberamos variables public void onServiceDisconnected(ComponentName className) { s = null; } };

370

Content Providers, servicios y notificaciones

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);

// Unimos esta actividad al servicio indicando mediante una Intencin explcita el nombre del servicio, la variable de conexin que recibe el puntero del servicio y el modo de operacin bindService(new Intent(this, Servicio.class), miConexion, Context.BIND_AUTO_CREATE);

// Cargamos referencias ListView de la pantalla principal matrizAdaptador = new ArrayList<String>(); adaptador = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, matrizAdaptador); ListView list = (ListView) findViewById(R.id.list); list.setAdapter(adaptador); }

// Mtodo que se invoca cuando el usuario hace clic sobre el botn de la pantalla principal public void buscarDatosServicio(View view) { // Si el servicio est activo if (s != null) { // Obtenemos los nuevos datos del servicio List<String> datos = s.getDatos(); // Limpiamos el adaptador con los nuevos datos matrizAdaptador.clear(); matrizAdaptador.addAll(datos); // Indicamos que los datos del adaptador han cambiado adaptador.notifyDataSetChanged(); } } }

Para conectar con el servicio definido en la clase Servicio, hemos escrito la sentencia: 371

bindService(new Intent(this, Servicio.class), miConexion, Context.BIND_AUTO_CREATE);

El mtodo bindService (Intent service, ServiceConnection conn, int flags) se invoca con los siguientes tres parmetros:

service: Intent que identifica el servicio al que queremos conectar. Este Intent puede ser explcito (como en el ejemplo) indicando el nombre de la clase que implementa el servicio o implcito sealando la accin que se define mediante un IntentFilter de un servicio publicado en el sistema.

conn: recibe la informacin del resultado de la clase de conexin ServiceConnection.

flags: opciones que podemos indicar al unirnos al servicio. Puede contener 0, BIND_AUTO_CREATE (crea el servicio mientras haya componentes ligados a l), BIND_DEBUG_UNBIND (incluye informacin de depuracin cuando se produce un desligue de los componentes), BIND_NOT_FOREGROUND (no permite que el servicio cambie de hilo de ejecucin), BIND_ABOVE_CLIENT (el servicio tiene ms prioridad de ejecucin que la aplicacin que lo inicia), BIND_ALLOW_OOM_MANAGEMENT (servicio normal que puede ser

eliminado de memoria si el sistema la necesita) o BIND_WAIVE_PRIORITY (el servicio se trata en segundo plano sin cambio de prioridad), etctera.

Desde Eclipse puedes abrir el proyecto Ejemplo 4 (Servicio) de la Unidad 7. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa anterior, en el que hemos definido un Servicio.

Si ejecutas la aplicacin y pulsas el botn Cargar informacin del servicio, vers la siguiente pantalla:

372

Content Providers, servicios y notificaciones

Si pulsas el botn cada 5 segundos, vers que la aplicacin recarga datos en la pantalla principal.

7.5

NOTIFICACIONES AL USUARIO EN ANDROID

En Android existen varias formas de notificar mensajes o informacin al usuario. En la Unidad 3 de este curso ya hemos visto el uso de Dilogos para mostrar al usuario informacin e, incluso, solicitar que introduzca algn texto. En este apartado vamos a estudiar dos tipos de notificaciones ms: Mensajes emergentes: en ingls Toast. Aunque ya hemos usado este tipo de mensajes previamente en el curso, vamos a describir con ms detalle toda su funcionalidad, ya que son muy tiles en las aplicaciones Android. Mensajes en la barra de estado. Son mensajes que aparecen en forma de icono en la barra de estado en la parte superior del dispositivo:

7.5.1 Mensajes emergentes (Toast) Un mensaje emergente (en ingls Toast) es un mensaje que se muestra en la pantalla del dispositivo Android durante unos segundos y desaparece automticamente sin requerir ningn tipo de actuacin por parte del usuario. 373

Este mensaje no recibe el foco de la aplicacin en ningn momento, es decir, no interfiere con las acciones que est realizando el usuario en ese momento. Por defecto, aparecen en la parte inferior de la pantalla, dentro de un rectngulo gris ligeramente translcido. Este tipo de notificaciones son perfectas para mostrar mensajes rpidos y sencillos al usuario, puesl no requiere confirmacin. Ya hemos visto durante el curso que su utilizacin es muy sencilla. La clase Toast dispone del mtodo esttico makeText(Context context, CharSequence text, int duration) al que debemos pasar como parmetros el contexto de la actividad, el texto del mensaje y el tiempo que de permanecer en la pantalla en milisegundos. En el parmetro duration podemos usar las siguientes constantes definidas por Android: Toast.LENGTH_LONG: mensaje de duracin larga. Se usa para textos muy largos. Toast.LENGTH_SHORT: mensaje de duracin corta. Se usa para mensajes ms cortos. Tras obtener una referencia al objeto Toast a travs de este mtodo, usamos el mtodo show() para mostrar el mensaje en la pantalla. En el Ejemplo 5 de esta Unidad vamos a definir distintos tipos de Toast. Desde Eclipse puedes abrir el proyecto Ejemplo 5 (Notificaciones) de la Unidad 7. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa anterior, en el que hemos usado distintos tipos de Toast.

Para comenzar, vamos a incluir un botn que muestre un Toast bsico cuando hagamos clic sobre l:

// Toast por defecto xDefectoBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // Creamos el mensaje Toast toast1 = Toast.makeText(getApplicationContext(), "Toast por defecto", Toast.LENGTH_SHORT); // Mostramos el mensaje toast1.show();

374

Content Providers, servicios y notificaciones

} });

Si ejecutas la aplicacin y pulsas el botn Toast Por defecto vers la siguiente pantalla:

Tambin podemos personalizar este Toast cambiando su posicin relativa en la pantalla. Para esto utilizamos su mtodo setGravity(), al que indicamos en qu zona deseamos que aparezca la notificacin. Esta zona se marca usando alguna de las constantes definidas en la clase Gravity: CENTER, LEFT, BOTTOM, etctera, o utilizando una combinacin de stas. En el Ejemplo 5 vamos a colocar el mensaje en la zona central derecha de la pantalla. Para esto, hay un segundo botn en la aplicacin que muestra un -Toast con estas caractersticas:

// Toast con posicionamiento en pantalla gravityBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { Toast toast2 = Toast.makeText(getApplicationContext(), "Toast con gravity", Toast.LENGTH_SHORT); // Indicamos el posicionamiento toast2.setGravity(Gravity.CENTER|Gravity.RIGHT,0,0); toast2.show();

375

} });

Si volvemos a ejecutar la aplicacin y pulsamos el nuevo botn, veremos que el Toast aparece en la zona indicada de la pantalla:

Es posible personalizar por completo el aspecto del mensaje. Android ofrece la posibilidad de definir un fichero de diseo (layout) XML propio para Toast, donde podemos incluir todos los elementos necesarios para adaptar la notificacin a las necesidades de la aplicacin. Para este Ejemplo 5 hemos definido un layout sencillo con una imagen y una etiqueta de texto sobre un rectngulo gris. Si abres el fichero res/layout/layout_toast.xml podrs ver su diseo:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layoutToast" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" android:background="#555555"

376

Content Providers, servicios y notificaciones

android:padding="5dip" > <ImageView android:id="@+id/imagen" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/info" />

<TextView android:id="@+id/mensajeLbl" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:textColor="#FFFFFF" android:paddingLeft="10dip" /> </LinearLayout>

Para asignar este fichero de diseo (layout) a un Toast, hay que proceder de una forma algo distinta a como lo hemos hecho en las anteriores notificaciones. En primer lugar, hay que inflar el layout mediante un objeto LayoutInflater, como ya hemos usado en varias ocasiones a lo largo del curso, para disear la interfaz de usuario. Una vez construido el layout, modificamos los valores de los distintos componentes internos de ste para mostrar la informacin. En este ejemplo, modificamos el mensaje de la etiqueta de texto y asignamos estticamente una imagen en el layout XML mediante el atributo android:src. Despus, establecemos la duracin de la notificacin con el mtodo setDuration() y asignamos el layout personalizado al Toast mediante el mtodo setView(). El cdigo fuente incluido en el tercer botn del ejemplo tiene este aspecto:

// Toast con diseo layoutBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // Creamos el Toast Toast toast3 = new Toast(getApplicationContext()); // Inflamos el diseo de layout_toast.xml LayoutInflater inflater = getLayoutInflater();

377

View layout = inflater.inflate(R.layout.layout_toast, (ViewGroup) findViewById(R.id.layoutToast)); // Asignamos los componentes del diseo TextView txtMsg = (TextView)layout.findViewById(R.id.mensajeLbl); txtMsg.setText("Toast con diseo personalizado"); // Indicamos la duracin corta para el mensaje toast3.setDuration(Toast.LENGTH_SHORT); // Asignamos el diseo al Toast toast3.setView(layout); // Mostramos el Toast toast3.show(); } });

Si ejecutamos ahora la aplicacin del ejemplo y pulsamos el botn Toast Personalizado, aparece el Toast con la estructura definida en el archivo de diseo layout personalizado:

7.5.2 Notificaciones en la barra de estado En este apartado vamos a tratar otro tipo de notificaciones ms persistentes y complejas de implementar, que son las notificaciones de la barra de estado de Android. Estas notificaciones son las que muestran los dispositivos Android cuando recibimos un mensaje SMS, hay actualizaciones disponibles, est el reproductor de msica funcionando en segundo plano, etctera. Estas notificaciones consisten en un icono y un texto que aparece en la barra de estado superior. Adicionalmente, podemos indicar un mensaje ms largo y descriptivo y una marca de fecha/hora que aparece al desplegar la bandeja del sistema. 378

Content Providers, servicios y notificaciones

Por ejemplo, cuando hay una llamada perdida en nuestro telfono, se muestra en un lado el siguiente icono en la barra de estado:

Arrastrar

Si arrastramos la barra de estado del dispositivo, se despliega la bandeja del sistema con ms informacin. En este ejemplo en concreto se informa del evento producido (Missed calls), los nmeros de telfonos asociados y la fecha/hora del evento. Adems, al pulsar sobre la notificacin se abre automticamente el historial de llamadas.

En el Ejemplo 5 de esta Unidad vamos a utilizar este tipo de notificaciones. En este ejemplo hemos aadido un nuevo botn que genera una notificacin en la barra de estado con los elementos comentados y con la posibilidad de dirigirnos a la propia aplicacin del ejemplo cuando se pulsa sobre la notificacin. Para generar notificaciones en la barra de estado del sistema, lo primero que hay que hacer es obtener una referencia al servicio de notificaciones de Android usando la clase NotificationManager. Utilizamos el mtodo getSystemService() indicando como parmetro el identificador del servicio al que queremos conectar, en este caso a Context.NOTIFICATION_SERVICE.

//Obtenemos una referencia al servicio de notificaciones

379

String ns = Context.NOTIFICATION_SERVICE; NotificationManager notManager = (NotificationManager) getSystemService(ns);

Despus, configuramos las caractersticas de la notificacin. En primer lugar, establecemos el icono y el texto que aparece en la barra de estado. Tambin registramos la fecha y hora asociadas a la notificacin. Con estos datos construimos un objeto Notification. En este ejemplo, utilizamos un icono predefinido de Android, el mensaje Atencin! y registramos la fecha/hora actual indicada por el mtodo System.currentTimeMillis():

//Configuramos la notificacin que va a aparecer en la barra int icono = android.R.drawable.stat_sys_warning; CharSequence textoEstado = "Atencin!"; long hora = System.currentTimeMillis();

// Creamos la notificacin Notification notificacion = new Notification(icono, textoEstado, hora);

A continuacin, utilizamos el mtodo setLatestEventInfo() para asociar a la notificacin la informacin que aparece al desplegar la bandeja del sistema (ttulo y descripcin) e indicar la actividad que debe iniciarse si el usuario pulsa sobre la notificacin. Los dos primeros datos son simples cadenas de texto. Para indicar la actividad que se debe ejecutar si el usuario pulsa sobre la notificacin, debemos construir una Intencin pendiente PendingIntent, que ya hemos usado en el apartado anterior de esta Unidad. Esta Intencin pendiente contiene la informacin de la actividad asociada a la notificacin que ser lanzada al pulsar sobre ella. Para esto, definimos un objeto Intent indicando la clase de la actividad concreta que se debe ejecutar. En este ejemplo el objeto es la propia actividad principal (NotificacionesActivity.class). Este Intent lo utilizamos para construir el PendingIntent final mediante el mtodo PendingIntent.getActivity(). Veamos cmo queda esta ltima parte del cdigo, comentado:

Intent notIntent = new Intent(contexto, NotificacionesActivity.class); // Usamos una PendingIntent para crear la notificacin PendingIntent contIntent = PendingIntent.getActivity( contexto, 0, notIntent, 0); // Incluimos la informacin de la notificacin notificacion.setLatestEventInfo(contexto, titulo, descripcion, contIntent);

380

Content Providers, servicios y notificaciones

Es posible indicar opciones adicionales, como, por ejemplo, que la notificacin desaparezca automticamente de la bandeja del sistema cuando se pulsa sobre ella. Esto lo conseguimos usando al atributo flags de la notificacin con el valor

Notification.FLAG_AUTO_CANCEL. Tambin podramos indicar que, al crearse la notificacin, el dispositivo suene, vibre o se encienda el LED de estado presente en muchos dispositivos. Para ello, basta con aadir al atributo defaults de la notificacin los valores DEFAULT_SOUND, DEFAULT_VIBRATE o DEFAULT_LIGHTS.

//AutoCancel: cuando se pulsa la notificacin desaparece notificacion.flags |= Notification.FLAG_AUTO_CANCEL;

//Para aadir sonido, vibracin y luces hay que descomentar estas sentencias //notif.defaults |= Notification.DEFAULT_SOUND; //notif.defaults |= Notification.DEFAULT_VIBRATE; //notif.defaults |= Notification.DEFAULT_LIGHTS;

Existen otras muchas opciones y personalizaciones de estos atributos flags y defaults que se pueden consultar en la documentacin oficial de la clase Notification de Android. Para acabar, una vez tenemos definidas las opciones de la notificacin, podemos generarla invocando el mtodo notify() y pasando como parmetro un identificador nico definido por la aplicacin, as como el objeto Notification construido anteriormente.

//Enviamos la notificacin notManager.notify(ID_MEN_BARRA_NOTIF, notificacion);

Si volvemos a ejecutar la aplicacin y pulsamos de nuevo el botn Notificacin en la barra de estado, veremos que aparece un icono en la barra de estado del dispositivo virtual:

381

Si desplegamos la bandeja del sistema, podemos verificar el resto de informacin de la notificacin:

Por ltimo, si pulsamos sobre la notificacin, se abre automticamente de nuevo la aplicacin de este ejemplo. Adems, la notificacin desaparece de la bandeja del sistema, ya que lo habamos configurado en el cdigo Java con la opcin FLAG_AUTO_CANCEL:

382

Content Providers, servicios y notificaciones

Desde Eclipse puedes abrir el proyecto Ejemplo 5 (Notificaciones) de la Unidad 7. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa anterior, en el que hemos lanzado una notificacin a la barra de estado del dispositivo.

7.6

USO DE VIEWPAGER EN APLICACIONES ANDROID

Si has utilizado alguna vez un dispositivo Android, te habrs dado cuenta de que algunas aplicaciones permiten desplazar pginas deslizando el dedo horizontalmente sobre la pantalla. Por ejemplo, en la aplicacin del Android Market y en el visor de imgenes podemos cambiar de pgina dentro de la misma aplicacin:

383

Al desplazar el dedo cambia de pantalla.

se

Para desarrollar esta funcionalidad hay que emplear el componente ViewPager de Android que est heredado de la clase ViewGroup. Este componente no forma parte de las clases por defecto del SDK de Android. Est incluido en el paquete externo de Compatibilidad de Android que deberas haber aadido al instalar el SDK de Android en Eclipse. Para comprobar que est bien aadido, haz clic en el botn "Opens the Android SDK Manager" de Eclipse:

Debe aparecer el siguiente paquete como instalado ("Installed"):

Nota: el nmero de revisin puede ser mayor que 4. Puedes encontrar estas libreras en el directorio 384

Content Providers, servicios y notificaciones

C:\cursos_Mentor\Android\android-sdk-windows\extras\android\support\v4

7.6.1 Cmo se usa el componente ViewPager A continuacin, vamos a mostrar en el Ejemplo 6 de esta Unidad cmo utilizar el componente ViewPager en una aplicacin Android. Una vez que hemos comprobado que tenemos las libreras extra de compatibilidad de Android, procedemos a incluirlas en el proyecto. En este proyecto hemos creado la carpeta "libs" y copiado dentro el archivo androidsupport-v4.jar del directorio donde se encuentre la librera:

A continuacin, aadimos la librera al Build Path haciendo clic con el botn derecho del ratn sobre el archivo de la librera y eligiendo la opcin "Build Path->Add to Build Path" del men desplegable:

385

Para comprobar que hemos incluido la librera correctamente en Eclipse, debe aparecer como Librera referenciada ("Referenced Libraries"):

La aplicacin que vamos a desarrollar consta de una Actividad que muestra un visor sencillo de imgenes dentro del ViewPager. Para generar las pginas contenidas en este ViewPager es necesario usar un objeto PagerAdapter, que se encarga de alimentar de pginas al componente ViewPager. Veamos las sentencias comentadas para crear la Actividad principal de la aplicacin:

public class ViewPagerActivity extends Activity { // Define el n de pginas en el ViewPager private static int NUMERO_VIEWS = 10; // Variable de ViewPager private ViewPager vPager; // Adaptador del ViewPager private CustomPagerAdapter vPagerAdapter;

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Buscamos el ViewPager en el diseo main.xml vPager = (ViewPager) findViewById(R.id.vPager); // Creamos el adaptador de N Pginas y pasamos el contexto de la aplicacin vPagerAdapter = new CustomPagerAdapter(NUMERO_VIEWS, this); // Asignamos el adaptador al ViewPager vPager.setAdapter(vPagerAdapter);

386

Content Providers, servicios y notificaciones

// Definimos el evento de cambio de pgina en el ViewPager vPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageSelected(int position) { Toast.makeText(getBaseContext(), "Has cambiado a la pantalla " + (position+1), 1).show(); }

@Override public void onPageScrollStateChanged(int arg0) { // No definimos nada en el evento al hacer scroll en la pgina }

@Override public void onPageScrolled(int arg0, float arg1, int arg2) { // No definimos nada en el evento al hacer scroll en la pgina } }); // end setOnPageChangeListener } }

En el cdigo anterior no hay nada especial que resaltar. Buscamos en el archivo de diseo el ViewPager y le asignamos su adaptador con el mtodo setAdapter(). Adems, usamos el mtodo setOnPageChangeListener() para mostrar un mensaje Toast cada vez que el usuario cambie de pgina. El archivo de diseo Layout de la actividad principal se implementa as:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#a4c639"> <android.support.v4.view.ViewPager

387

android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/vPager"/> </LinearLayout>

Como se trata de una Vista que se define en un paquete extra de Android, es necesario incluir el nombre completo del mismo android.support.v4.view.ViewPager. Luego, creamos el adaptador personalizado a partir de la clase PagerAdapter, para que cree las pginas internas del ViewPager, devolviendo vistas segn vamos desplazando el dedo horizontalmente por la pantalla:

public class CustomPagerAdapter extends PagerAdapter{ // Variables donde guardamos el contexto y el nmero de pginas private Context contexto; private int nViews; // Constructor de la clase public CustomPagerAdapter(int nViews, Context contexto) { this.contexto=contexto; this.nViews=nViews; } @Override // Devuelve el n de pgina del Adaptador del ViewPager public int getCount() { return nViews; }

/** * * * * @param collection La Vista (View) donde se almacena la pgina. * @param position Nmero de pgina que debemos crear. * @return Devuelve el objeto que representa la pgina. No tiene por qu Crea la pgina de la position indicada. El adaptador es el responsable de aadir componentes a cada pgina.

388

Content Providers, servicios y notificaciones

* ser una Vista, puede contener a su vez otras pginas. */ @Override public Object instantiateItem(View collection, int position) { /* Creamos mediante sentencias Java el diseo de la pgina. * Tambin podramos haber guardado el diseo en un archivo * xml y haberlo inflado aqu. */ // Creamos el Layout donde aadimos el resto de Vistas LinearLayout linearLayout = new LinearLayout(contexto); //Orientacion vertical = 1 linearLayout.setOrientation(1); // Definimos una etiqueta de texto TextView tv = new TextView(contexto); tv.setText("Imagen nmero " + (position+1)); tv.setTextColor(Color.WHITE); tv.setTextSize(30); // Definimos una imagen ImageView imagen = new ImageView(contexto); // Buscamos la imagen en el directorio /res/drawable en funcin del n de pgina int resID = contexto.getResources().getIdentifier("imagen"+ (position+1), "drawable", "es.mentor.unidad7.eje6.viewpager"); // Asignamos la imagen cargada del recurso imagen.setImageResource(resID); // Definimos unos parmetros para alinear la etiwueta superior LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.setMargins(0, 0, 0, 20); params.gravity=Gravity.CENTER; // Aadimos la etiqueta superior al Layout con los parmetros anteriores linearLayout.addView(tv, params); // Aadimos la imagen al Layout linearLayout.addView(imagen);

389

// Aadimos la pgina a la coleccin de pginas ((ViewPager) collection).addView(linearLayout,0); // Devolvemos el diseo de la pgina return linearLayout; } // end instantiateItem

/** * * * * @param collection La Vista (View) donde se elimina la pgina. * @param position Nmero de pgina que debemos eliminar. * @return object El mismo objeto creado en {@link #instantiateItem(View, int)}. */ @Override public void destroyItem(View collection, int position, Object view) { ((ViewPager) collection).removeView((LinearLayout) view); } Destruye el contenido de la pgina indicada en position. El adaptador es el responsable de borrar los componentes de cada pgina.

/** * Compara si la Vista view est instanciada en el Objeto object. Mtodo necesario para la clase ViewPager */ @Override public boolean isViewFromObject(View view, Object object) { return view==((LinearLayout)object); }

/** * Android invoca este mtodo cuando el cambio de una de las pginas se ha completado. */ @Override

390

Content Providers, servicios y notificaciones

public void finishUpdate(View arg0) {}

/** * Mtodo que se invoca cuando Android indica que hay que recuperar el estado de ejecucin */ @Override public void restoreState(Parcelable arg0, ClassLoader arg1) {}

/** * Mtodo que se invoca cuando Android indica que hay que guardar el estado de ejecucin */ @Override public Parcelable saveState() { return null; }

/** * Android invoca este mtodo cuando se inicia el cambio de una de las pginas. */ @Override public void startUpdate(View arg0) {} }

Los mtodos ms importantes del cdigo anterior son: instantiateItem: crea la pgina para la posicin indicada como parmetro del mtodo. Este adaptador es el responsable de aadir las Vistas a cada pgina. Creamos el diseo de la Vistas contenidas en la pgina mediante sentencias Java. Tambin podramos haber guardado el diseo en un archivo xml y haberlo inflado. destroyItem: destruye la pgina indicada en el parmetro posicin.

Las imgenes que se cargan en el visor de imgenes estn almacenadas en el directorio /res/drawable del proyecto. Para cargarlas dinmicamente en funcin del nmero de pgina que el adaptador CustomPagerAdapter debe crear hemos obtenido los recursos del 391

contexto con la orden contexto.getResources(); despus, hemos buscado el ID del recurso de la imagen usando el mtodo getIdentifier(nombre_recurso, tipo_recurso,

paquete_recurso). Desde Eclipse puedes abrir el proyecto Ejemplo 6 (ViewPager) de la Unidad 7. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa anterior, en el que hemos usado un ViewPager.

Si ejecutamos la aplicacin y arrastramos el ratn horizontalmente sobre la pantalla del emulador simulando el efecto de un dedo (puede costar un poco hacerlo con el ratn), veremos las siguientes ventanas:

Arrastrar

Arrastrar

Cambio de pgina

Cambio de pgina

392

Content Providers, servicios y notificaciones

Un Proveedor de contenido (en ingls Content Provider) es el mecanismo proporcionado por Android para compartir informacin entre aplicaciones. Los Proveedores de contenidos los usan muchas aplicaciones estndar de un dispositivo Android, como, por ejemplo, la lista de contactos, la aplicacin de SMS mensajes cortos, el calendario, etctera. Para implementar un Proveedor de contenidos propio, hay que usar la clase ContentProvider de Android. Las tablas de la base de datos SQLite usadas por un Content Provider deben incluir siempre el campo _ID que identifica sus registros de forma unvoca. El acceso a un Content Provider se realiza siempre mediante un identificador URI, que es una cadena de texto parecida a una direccin Web de Internet. La clase ContentResolver de Android permite realizar acciones con cualquier Content Provider que est disponible en el sistema operativo Android. Un Servicio (en ingls Service) es un componente de una aplicacin Android que se ejecuta en segundo plano, sin interactuar con el usuario (no tiene interfaz de usuario), para realizar operaciones de larga duracin. La plataforma Android ofrece una gran cantidad de servicios predefinidos en el sistema, a los que podemos acceder a travs de la clase de tipo Manager. Un servicio puede funcionar de dos modos: Autnomo: el servicio se puede ejecutar en segundo plano de forma indefinida, incluso si el componente que lo inici se destruye. Dependiente o Ligado (bind): ofrece una interfaz de tipo clienteservidor que permite a los componentes de una aplicacin interactuar con l enviando peticiones y recibiendo su resultado. Un servicio ligado slo se ejecuta mientras otro componente de la aplicacin est unido a l.

393

Los servicios propios de una aplicacin se ejecutan en el hilo principal de su proceso; por lo tanto, para no bloquear el hilo principal o de interfaz, debemos ejecutar estos servicios con hilos de ejecucin. Para implementar un servicio propio en una aplicacin tenemos que extender la clase Service de Android. Se pueden usar mensajes de difusin (Broadcast) para comunicar eventos entre servicios. Estos mensajes son, en realidad, Intents. La clase Receptor de mensajes de difusin (BroadcastReceiver) se usa para recibir Intenciones (Intents), es decir, mensajes enviados por otro componente de Android. En Android existen varias formas de notificar mensajes o informacin al usuario. Dilogos: muestran o solicitan informacin al usuario. Mensajes emergentes (en ingls Toast). Mensajes de notificacin en la barra de estado del dispositivo.

Un mensaje emergente (en ingls Toast) es un mensaje que se muestra en la pantalla del dispositivo Android durante unos segundos y desaparece

automticamente sin requerir ningn tipo de actuacin por parte del usuario. Los mensajes de notificacin de la barra de estado de Android se muestran en la barra de estado de los dispositivos Android cuando recibimos un mensaje SMS, hay actualizaciones disponibles, est el reproductor de msica funcionando, etctera. Para generar notificaciones en la barra de estado del sistema, hay que obtener una referencia al servicio de notificaciones de Android usando la clase

NotificationManager. El componente ViewPager permite disear aplicaciones que incluyen pginas que se pueden desplazar deslizando el dedo horizontalmente sobre la pantalla. Este componente ViewPager no forma parte de las clases por defecto del SDK de Android. Est incluido en el paquete externo de Compatibilidad de Android. 394

ANDROID AVANZADO

NDICE
8.1 INTRODUCCIN ................................................................................... 397 8.2 CMO DEPURAR APLICACIONES ANDROID CON ECLIPSE ..... 397 8.2.1 Estableciendo Puntos de interrupcin (Breakpoints) ............... 399 8.2.2 Iniciar la depuracin (Debug) del cdigo .................................... 400 8.2.3 Datos de depuracin (Debug) del cdigo................................... 401 8.2.4 Desactivar la depuracin de cdigo ............................................ 403 8.2.5 Propiedades de los puntos de interrupcin ............................... 404 8.2.6 Puntos de interrupcin de excepciones ..................................... 405 8.2.7 Puntos de interrupcin de mtodo .............................................. 405 8.2.8 Puntos de interrupcin de clase (class) ..................................... 405 8.2.9 Finalizar la Depuracin del cdigo .............................................. 406 8.3 USO DE MAPAS EN APLICACIONES ANDROID ............................ 406 8.3.1 Preparacin del Entorno de programacin ................................ 407 8.3.2 Cmo incluir mapas en las aplicaciones Android ..................... 410 8.4 DESARROLLO DE APLICACIONES SENSIBLES A LA ORIENTACIN DEL DISPOSITIVO .................................................... 418 8.4.1 Cambio de orientacin automtica.............................................. 420 8.4.2 Mantener la informacin del estado durante el cambio de orientacin ...................................................................................... 424 8.4.3 Cambio de orientacin Manual ...................................................... 427 8.5 DESPLEGAR APLICACIONES ANDROID EN DISPOSITIVOS VIRTUALES (AVD) O REALES ........................................................... 431 8.6 CMO PUBLICAR APLICACIONES EN EL ANDROID MARKET .. 435 8.6.1 Alta de cuenta de desarrollador en el Android Market ............. 435 8.6.2 Recomendaciones sobre aplicaciones para Android Market .. 439 8.6.2.1 Recomendaciones sobre aplicaciones para Android Market .................................................................................... 439

8.6.2.2 Buenas prcticas para el desarrollo de aplicaciones Android .................................................................................. 440 8.6.3 Generar fichero APK con certificado para Android Market ..... 441 8.6.4 Publicar una aplicacin Android en el Android Market............. 445

Android Avanzado

8.1

INTRODUCCIN

En esta Unidad vamos a explicar cmo depurar (debug en ingls) aplicaciones Android con Eclipse. Despus, veremos cmo utilizar Mapas en aplicaciones Android mediante la API de Google. Asimismo, veremos cmo cambiar el aspecto de las aplicaciones Android cuando cambia la orientacin del dispositivo. Finalmente, conoceremos cmo desplegar aplicaciones en un dispositivo real Android y publicar una aplicacin en el Android Market.

8.2

CMO DEPURAR APLICACIONES ANDROID CON ECLIPSE

La Depuracin de programas es el proceso de identificar y corregir errores de programacin en tiempo de ejecucin. En ingls se denomina debugging, ya que se asemeja a la eliminacin de bichos (bugs), que es como se denominan informalmente los errores de programacin Para depurar (en ingls Debug) una aplicacin Andriod, vamos a emplear las capacidades disponibles en el entorno de desarrollo Eclipse. Para ello, nos serviremos de la ltima versin disponible, la 3.7, a fecha de edicin de este documento. Para que el alumno o alumna pueda practicar la Depuracin de cdigo Android con Eclipse, hemos creado un proyecto Android con las siguientes clases:

public class DepuracionActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);

// Usamos la clase que ya existe en el otro fichero Contador contador = new Contador(); contador.count(); System.out.println("Hemos contado " + contador.getResultado() + " veces.");

397

Object o = null; o.toString(); } } // Clase sencilla que implementa un contador public class Contador {

// Variable para guardar la cuenta actual private int resultado=0;

public int getResultado() { return resultado; }

// Mtodo que cuenta de 2 en 2 public void count() { for (int i = 0; i < 100; i++) { resultado += i++; } } }

Es recomendable abrir en Eclipse el Ejemplo 1 (Depuracin) de la Unidad 8 y practicar los comandos que se muestran a continuacin.

Si ejecutas la aplicacin tal y como est, vers que aparece el siguiente mensaje de error:

398

Android Avanzado

Si haces clic en el botn "Force close", la aplicacin termina. Veamos cmo depurar este programa que provoca un error.

8.2.1

Estableciendo Puntos de interrupcin (Breakpoints) En el desarrollo de software, un punto de interrupcin (Breakpoint en ingls) es una

marca en el cdigo fuente que indica al depurador del lenguaje en que estemos programando que debe detener o pausar la ejecucin del programa para poder evaluar los valores asignados a las variables y permitir al programador detectar errores en tiempo de ejecucin. Para establecer puntos de interrupcin con Eclipse, hay que hacer clic en la opcin "Toggle Breakpoint" del men desplegable que aparece si pulsamos el botn derecho del ratn sobre el nmero de lnea del cdigo fuente correspondiente. Tambin podemos hacer doble clic en este nmero de lnea para activar o desactivar esta opcin:

399

8.2.2

Iniciar la depuracin (Debug) del cdigo Para iniciar la depuracin del cdigo hay que hacer clic en la opcin "Run->Debug" del

men principal de Eclipse. Tambin podemos usar la tecla rpida [F11] o usar el icono men principal.

del

Si lo hacemos, a continuacin se instalar y se ejecutar la aplicacin en el dispositivo virtual. Despus, Eclipse muestra el siguiente mensaje:

Contestaremos que s para cambiar el tipo de Perspectiva a "Debug", muy til para depurar programas. A continuacin, cambiar la perspectiva de Eclipse as:

Y la ejecucin del programa se parar en la primera lnea del cdigo que tenga un punto de interrupcin. Podemos usar los siguientes atajos de teclado para depurar el programa:

400

Android Avanzado

Comando Descripcin La ejecucin pasa a la siguiente sentencia del programa. Si la sentencia siguiente es la llamada a un mtodo o funcin, se continuar con la ejecucin de las sentencias de este mtodo o funcin. La ejecucin pasa a la siguiente sentencia del programa. Si la sentencia siguiente es la llamada a un mtodo o funcin, se continuar con la ejecucin de la sentencia siguiente sin entrar en el cdigo de este mtodo o funcin. La ejecucin sigue todas las sentencias de todos los mtodos o funciones que formen nuestro programa. Es decir, ejecuta en secuencia todas las rdenes que conforman el programa. El programa se ejecuta hasta que se encuentre otro punto de interrupcin o hasta que el usuario lo cierre.

F5

F6

F7

F8

Nota: tambin existen unos botones de acceso rpido que permiten ejecutar estas rdenes. Observa la imagen siguiente:

8.2.3

Datos de depuracin (Debug) del cdigo La vista "Debug" permite ver el contenido de la Pila "Stack" de la aplicacin:

En la parte superior derecha de Eclipse podemos ver el contenido de las variables. Tambin podemos usar el men para cambiar el tipo de variables que han de visualizarse, opcin muy til cuando hemos definido muchas variables:

401

Es posible tambin usar este men para cambiar las columnas que han de aparecer en esta vista:

Adems, es posible utilizar la opcin "New Detail Formater" (men desplegable con el botn derecho del ratn) para modificar la informacin mostrada sobre la variable. Por ejemplo, como el texto (posicin de memoria de una variable)

es.mentor.unidad8.eje1.depuracion.Contador@4051b760 no dice nada, podemos usar la opcin "New Detail Formater"

402

Android Avanzado

para invocar un mtodo de una clase y mostrar su resultado:

Ahora ya podemos ver el resultado:

8.2.4

Desactivar la depuracin de cdigo Si deseas desactivar temporalmente todos los puntos de interrupcin, puedes pulsar el

botn "Skip All Breakpoints":

Si pulsas este botn otra vez, los puntos de interrupcin se activarn de nuevo.

403

8.2.5

Propiedades de los puntos de interrupcin Despus de establecer un punto de interrupcin, puedes seleccionar las propiedades

de este punto para, por ejemplo, establecer una condicin lgica de parada. En las propiedades se puede, por ejemplo, activar el punto de interrupcin y parar la ejecucin del programa slo cuando una variable tenga cierto valor o se cumpla cierta condicin. Para acceder a las propiedades del punto de interrupcin, hay que hacer clic en la opcin "Breakpoint Properties..." del men desplegable con el botn derecho del ratn sobre el punto de interrupcin:

En la ventana emergente podemos establecer la condicin de parada del punto de interrupcin:

404

Android Avanzado

8.2.6

Puntos de interrupcin de excepciones Los puntos de interrupcin de excepciones detienen la ejecucin de la aplicacin si se

inicia una excepcin especfica. Para definir este tipo de punto de interrupcin, hay que hacer clic en el icono de excepcin siguiente:

8.2.7

Puntos de interrupcin de mtodo Un punto de interrupcin de tipo mtodo se define haciendo doble clic en el borde

izquierdo del editor del mtodo correspondiente. Detiene el programa durante al ejecutar el mtodo o, despus, al finalizar la ejecucin del mismo.

8.2.8

Puntos de interrupcin de clase (class) Un punto de interrupcin de tipo clase se define haciendo doble clic en el borde

izquierdo del editor de la declaracin de la clase correspondiente. Detiene el programa al cargar esta clase Java:

405

8.2.9

Finalizar la Depuracin del cdigo Para finalizar la depuracin del cdigo basta con cambiar la Perspectiva a "Java" de

nuevo. Cuando hagamos alguna modificacin del cdigo fuente, aparecer el siguiente mensaje para indicar que no se puede sustituir el cdigo de una aplicacin ya instalada en el emulador de Android y se pregunta si deseamos desconectar ("Disconnect") el modo Debug:

Nota: en esta Unidad 8 puedes encontrar el vdeo Cmo depurar aplicaciones Android en Eclipse, que muestra visualmente cmo llevar a cabo la depuracin del Ejemplo 1 de esta Unidad.

8.3

USO DE MAPAS EN APLICACIONES ANDROID

En este apartado vamos utilizar mapas en aplicaciones de Android haciendo uso de la API Android de Google Maps. La mayora de los dispositivos Android permiten determinar su ubicacin geogrfica actual a travs de un mdulo GPS (del ingls Global Positioning System, que se traduce como Sistema de Posicionamiento Global). Android dispone del paquete android.location, que proporciona la API para determinar la posicin actual geogrfica.

406

Android Avanzado

8.3.1

Preparacin del Entorno de programacin Antes de empezar a utilizar el servicio de mapas de Google es necesario comprobar

que tenemos instalado el paquete correspondiente a las APIs de Google. Este paquete se llama normalmente Google APIs by Google, Android API x, revisin y. Al instalar el SDK de Android en Eclipse deberas haber aadido ya este paquete. Para comprobar que est correctamente instalado, haz clic en el botn "Opens the Android SDK Manager" de Eclipse:

Debe aparecer el siguiente paquete como instalado ("Installed"):

Nota: el nmero de revisin puede ser mayor que 2. Para poder probar las aplicaciones en el emulador, tambin es necesario crear un nuevo dispositivo virtual AVD que utilice este paquete como "target". Para ello, pulsamos el botn "Opens the Android Virtual Device Manager":

Y se presenta una ventana, donde pulsamos sobre el botn "New". A continuacin, aparece otra nueva ventana, donde rellenamos los campos tal y como aparecen en esta captura:

407

Para acabar de crear el dispositivo virtual, hacemos clic en el botn "Create AVD". Para poder utilizar la API de Google Maps es necesario obtener previamente una clave de uso (API Key) que estar asociada al certificado con el que firmamos digitalmente las aplicaciones. En el apartado "Permisos y Seguridad" de la Unidad 5 ya hemos hablado de estos certificados, necesarios para firmar aplicaciones. Si cambiamos el certificado con el que firmamos nuestra aplicacin, algo que normalmente se hace como paso previo a la publicacin de la aplicacin en el Android Market, tendremos que modificar tambin la clave de uso de la API. Cuando compilamos una aplicacin en Eclipse y la probamos en el emulador de Android, se aplica automticamente un certificado de depuracin creado por defecto. Por lo tanto, para poder depurar en el emulador aplicaciones que hagan uso de Google Maps, hay que solicitar una clave asociada a este certificado de depuracin. En primer lugar, hay que localizar el fichero donde se almacenan los datos del certificado de depuracin "debug.keystore". Podemos conocer la ruta de este fichero accediendo a las preferencias de Eclipse, seccin "Android", apartado "Build":

408

Android Avanzado

En esta ventana copiamos en el portapapeles la ruta que aparece en el campo Default Debug Keystore. Observa que hemos borrado intencionalmente la parte de la ruta que ser distinta en tu ordenador. Una vez conocemos la ruta del fichero debug.keystore, vamos a acceder a l con la herramienta keytool.exe de Java para obtener el hash MD5 del certificado. Esto lo hacemos desde una ventana de lnea de comandos en el directorio C:\Program Files

(x86)\Java\jre6\bin (o donde est instalado Java) mediante la orden:

C:\Program Files (x86)\Java\jre6\bin>keytool -list -alias androiddebugkey "ruta_del_certificado\debug.keystore" -storepass android -keypass android Nota: es necesario usar la versin 6 de Java, pues en la 7 no funciona. Si lo hacemos, veremos la siguiente ventana:

-keystore

409

A continuacin, copiamos en el portapapeles el dato que aparece identificado como Huella digital de certificado (MD5). Despus, accedemos a la Web de Google para solicitar una clave de utilizacin de la API de Google Maps para depurar aplicaciones. En esta Web tendremos que escribir la Huella digital MD5 de nuestro certificado para obtener la clave de uso de la API. En la siguiente imagen se muestra el resultado:

Nota: Observa que hemos borrado intencionalmente parte de la clave, pues, cuando solicites sta, te darn otra diferente. Ya hemos terminado la preparacin del entorno de programacin para poder utilizar los servicios de Google Maps dentro de nuestras aplicaciones Android.

8.3.2

Cmo incluir mapas en las aplicaciones Android En el Ejemplo 2 de esta Unidad vamos a desarrollar una aplicacin que incluye un

mapa sobre el que podemos hacer unas operaciones sencillas, como cambiar a vista satlite o desplazar el mapa. Para poder ver este proyecto en tu emulador Android es necesario que obtengas la clave de uso de la API de Mapas de Google y la cambies en el fichero de diseo main.xml de la interfaz de usuario. Si no lo haces, arrancar la aplicacin del ejemplo pero no se mostrar el mapa, como en la imagen siguiente:

410

Android Avanzado

Hay que tener en cuenta que, a la hora de crear el proyecto Android en Eclipse, tenemos que seleccionar "Google APIs" en el campo "Build Target" en las propiedades del proyecto:

Para incluir un mapa de Google Maps en una aplicacin Android, utilizamos el componente MapView. Este componente se puede aadir al diseo de la pantalla como otro componente normal. Sin embargo, para poder usarlo, hay que indicar la clave de uso de Google Maps en el atributo android:apiKey tal y como se muestra a continuacin: 411

<!-- Aqu se escribe la clave de uso de Google Maps --> <com.google.android.maps.MapView android:id="@+id/mapa" android:layout_width="fill_parent" android:layout_height="fill_parent" android:apiKey="xxxxxxxxxxxxxLIdwwbCEmC3DeN1omnaSkig" android:clickable="true" />

Adems, tambin hemos establecido el atributo clickable a true, para que el usuario pueda interactuar con el componente si quiere, por ejemplo, desplazar el mapa con el dedo. Los componentes MapView slo se pueden utilizar desde una actividad de tipo MapActivity. La clase MapActivity se extiende de la clase Activity y permite la gestin del ciclo de vida de la Actividad y de los servicios de visualizacin de un mapas. De igual forma que ListActivity se usa para mostrar listas, MapActivity se usa para mostrar mapas. En el Ejemplo 2 la Actividad principal hereda la clase MapActivity, tal y como vemos en el siguiente cdigo:
public class MapasActivity extends MapActivity {

// Variables donde se definen los controles de la Actividad private MapView mapa = null; private Button sateliteBtn = null; private Button irBtn = null; private Button animarBtn = null; private Button moverBtn = null; private MapController controlMapa = null; // Constantes que llevan a un punto en el mapa private static Double latitud = 40.6550*1E6; private static Double longitud = -4.7000*1E6;

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //Obtenemos una referencia a las Vistas de la Actividad mapa = (MapView)findViewById(R.id.mapa);

412

Android Avanzado

sateliteBtn = (Button)findViewById(R.id.SateliteBtn); irBtn = (Button)findViewById(R.id.IrBtn); animarBtn = (Button)findViewById(R.id.AnimarBtn); moverBtn = (Button)findViewById(R.id.MoverBtn); //Definimos el Controlador del mapa controlMapa = mapa.getController(); // Definimos un nuevo punto de localizacin GeoPoint loc = new GeoPoint(latitud.intValue(), longitud.intValue()); // Centramos el mapa en este punto controlMapa.setCenter(loc); // Hacemos zoon a 6 (puede tomar el valor de 1 a 21) controlMapa.setZoom(6); //Mostramos los controles de zoom sobre el mapa mapa.setBuiltInZoomControls(true);

// Definimos el evento onClick del botn Satlite sateliteBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // Intercambiamos la capa de tipo satlite en el mapa if(mapa.isSatellite()) mapa.setSatellite(false); else mapa.setSatellite(true); } });

// Definimos el evento onClick del botn Ir a... irBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) {

// Definimos un nuevo punto de localizacin

413

GeoPoint

loc

new

GeoPoint(latitud.intValue(), longitud.intValue());

// Centramos el mapa en este punto controlMapa.setCenter(loc); // Hacemos zoon a 10 (puede tomar el valor de 1 a 21) controlMapa.setZoom(10); } }); // Definimos el evento onClick del botn Animar animarBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // Definimos un nuevo punto de localizacin GeoPoint loc = new GeoPoint(latitud.intValue(), longitud.intValue());

// Movemos con animacin el mapa en este punto controlMapa.animateTo(loc); // Hacemos zoom sobre esa posicin del mapa int zoomActual = mapa.getZoomLevel(); for(int i=zoomActual; i<12; i++) { controlMapa.zoomIn(); } } }); // Definimos el evento onClick del botn Mover moverBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // Movemos el mapa 1000 pxeles en horizontal y 50 en vertical controlMapa.scrollBy(1000, 50); } });

414

Android Avanzado

// Mtodo obligado de la clase que indica si estamos mostrando una ruta @Override protected boolean isRouteDisplayed() { return false; } }

A continuacin, vamos a explicar las partes pertinentes del cdigo anterior. Como la Actividad principal se hereda de la clase MapActivity, es obligatorio implementar el mtodo isRouteDisplayed(), que debe devolver el valor true si vamos a mostrar algn tipo de informacin de ruta sobre el mapa. Segn los trminos de licencia de uso de la API de Google Maps, debe indicarse cundo se usan sus mapas para este propsito. En este ejemplo del curso nos limitamos a mostrar un mapa en la pantalla principal de la aplicacin, por lo que devolvemos el valor false. Adems, en el mtodo onCreate() de la Actividad se invoca el mtodo setBuiltInZoomControls() de la referencia de componente MapView para mostrar los controles de zoom estndar sobre el mapa, de modo que podamos acercar y alejar la vista del mapa. Por defecto, cuando usamos un MapView en una aplicacin, se muestra en el modo de mapa tradicional. Sin embargo, este componente tambin permite cambiar las capas a la vista satlite, ver fotos de la calle con StreetView o mostrar la informacin del trfico. Para ello, podemos usar los siguientes mtodos de la clase MapView: setSatellite(true) setStreetView(true) setTraffic(true)

Tambin existen otros tres mtodos para consultar el estado de cada uno de estos modos: isSatellite(), isStreetView() y isTraffic(). En el evento onClick del botn sateliteBtn hemos usado el mtodo setSatellite() para intercambiar el modo satlite y el estndar. Adems de los mtodos para personalizar el aspecto grfico del mapa, tambin disponemos de varios mtodos para consultar la informacin geogrfica visualizada en el mismo. Por ejemplo, podemos saber las coordenadas geogrficas en las que el mapa est centrado actualmente mediante el mtodo getMapCenter() y el nivel de zoom que est

aplicando a travs del mtodo getZoomLevel(). 415

Como podemos observar en el cdigo anterior, las coordenadas del centro del mapa se obtienen mediante el mtodo getMapCenter() en forma de objeto GeoPoint que encapsula los valores de latitud y longitud expresados en microgrados (grados * 1E6). Los valores en la magnitud grados se pueden obtener mediante los mtodos getLatitudeE6() y

getLongitudeE6() respectivamente. El nivel de zoom del mapa contiene un valor entero entre 1 y 21, siendo 21 el que ofrece mayor nivel de detalle en el mapa. Para modificar el centro del mapa, en primer lugar, debemos acceder al controlador del mapa (MapController) mediante el mtodo getController(). Este mtodo devuelve un objeto MapController con el que podemos modificar la posicin central del mapa. Para ello, podemos usar los mtodos setCenter() y setZoom() a los que podemos indicar las coordenadas centrales del mapa y el nivel de zoom deseado, respectivamente. En este ejemplo hemos incluido un botn irBtn que centra el mapa sobre un punto determinado y hemos aplicado un nivel de zoom (10), que permite distinguir en el mapa algunos detalle. Si pruebas el ejemplo del curso, vers que el desplazamiento a la posicin y el zoom al nivel indicados se hacen de forma instantnea sin ningn tipo de animacin. Para mejorar la sensacin de movimiento en el mapa, la API de Google nos ofrece otra serie de mtodos que permiten desplazar el mapa a una posicin especfica de forma progresiva y aumentar o disminuir el nivel de zoom de forma animada. El mtodo animateTo(GeoPoint) desplaza el mapa hasta un punto determinado y los mtodos zoomIn() y zoomOut() aumentan o disminuyen de forma progresiva, respectivamente, en 1 el nivel de zoom. En el botn animarBtn hemos usado este mtodo para desplazar de forma animada el mapa. Para acabar, disponemos de otro mtodo que permite desplazar el mapa un determinado nmero de pixeles en cierta direccin, tal y como puede hacer un usuario con el dedo sobre el mapa. Este mtodo se llama scrollBy() y recibe como parmetros el nmero de pixeles que queremos desplazarnos en horizontal y en vertical. En el botn moverBtn hemos usado este mtodo para desplazar el mapa automticamente. Finalmente, ten en cuenta que, para ejecutar la aplicacin del ejemplo sobre el emulador de Android, hay que modificar el fichero AndroidManifest.xml. Es necesario especificar que hacemos uso de la API de Google Maps mediante la clusula <uses-library> y, en segundo lugar, hay que solicitar los permisos de acceso a Internet mediante la clusula <uses-permission>. Veamos el aspecto que tiene este fichero:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="es.mentor.unidad8.eje2.mapas" android:versionCode="1"

416

Android Avanzado

android:versionName="1.0" > <uses-sdk android:minSdkVersion="10" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <uses-library android:required="true" android:name="com.google.android.maps"> </uses-library> <activity android:label="@string/app_name" android:name=".MapasActivity" > <intent-filter > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.INTERNET" /> </manifest>

Desde Eclipse puedes abrir el proyecto Ejemplo 2 (Mapas) de la Unidad 8. Estudia el cdigo fuente y ejectalo para mostrar en el AVD el resultado del programa anterior, en el que hemos utilizado un mapa.

Si ejecutas la aplicacin en el emulador de Android, vers que tiene el siguiente aspecto:

417

8.4

DESARROLLO DE APLICACIONES SENSIBLES A LA ORIENTACIN DEL DISPOSITIVO

Si has usado alguna vez un telfono con Android, vers que, al cambiar la orientacin del mismo de vertical a horizontal y viceversa, normalmente se modifica el aspecto de la aplicacin que ests usando distribuyndose las Vistas de la interfaz de usuario de forma acorde. Aunque a priori este cambio de orientacin del dispositivo parece sencillo, a veces los desarrolladores de aplicaciones Android deben desarrollar complejos cdigos para controlarlo. Este apartado describe cmo implementar esta funcionalidad. Por ejemplo, si tenemos abierta la aplicacin de Contactos de Android y cambiamos la orientacin del telfono de vertical a horizontal, la aplicacin modifica el aspecto de la interfaz del usuario proporcionalmente:

418

Android Avanzado

Hay dos formas de controlar el cambio de orientacin del dispositivo Android: Automtica: dejamos a Android que haga todo la tarea y definimos el fichero de diseo xml que debe aplicar para cada tipo de orientacin vertical (portrait) u horizontal (landscape). Manual: controlamos con sentencias Java qu diseo debe cargar en cada momento. ATENCIN Para cambiar la orientacin del emulador de Android [BLOQUE_NUM_7], [Ctrl+F11], [BLOQUE_NUM_9], [Ctrl+F12]. podemos usar las teclas

Ten en cuenta que el cambio de orientacin puede tardar unos segundos en el emulador dependiendo de la capacidad del PC con el que trabajes.

En el Ejemplo 3 de esta Unidad vamos a mostrar cmo funcionan las dos formas de controlar el cambio de orientacin del dispositivo Android.

Nota sobre Android 2.3.3 Hasta ahora en el curso hemos usado la versin 2.3.3 de Android en el emulador de dispositivos. Cuando se ha escrito este texto, esta versin tiene un Bug al cambiar la orientacin del emulador de horizontal a vertical (no informa al emulador de la nueva orientacin y mantiene la horizontal).

419

Por lo tanto, hay que probar el Ejemplo 3 de esta Unidad en otra versin de Android. Tal y como hemos hecho para la versin 2.3.3 en la Instalacin del curso, hay que descargar las libreras de Android 2.2 y crear el dispositivo virtual correspondiente:

Tambin puedes usar la versin de Android del curso teniendo en cuenta que puedes cambiar al modo horizontal, pero no volver de nuevo al vertical.

8.4.1

Cambio de orientacin automtica Se trata de una forma muy fcil de personalizar la interfaz de usuario en funcin de la

orientacin de la pantalla del dispositivo. Consiste en crear una carpeta de diseo separada (/res/layout) que contenga los archivos XML que determinan la interfaz de usuario en cada tipo de orientacin. Para definir el modo horizontal (landscape), hay que crear la carpeta res/layout-land. Esta nueva carpeta contiene tambin el archivo main.xml:

Tambin se puede aplicar el nombre de extensin -land a la carpeta drawable donde estn las imgenes de la aplicacin. Por ejemplo, la carpeta res/drawable-land contiene imgenes que se han diseado teniendo en cuenta el modo horizontal, mientras que los albergados en la carpeta res/drawable estn diseados para el modo vertical:

420

Android Avanzado

El archivo main.xml incluido en la carpeta /res/layout define la interfaz de usuario para el modo vertical del dispositivo, mientras que el archivo main.xml de la carpeta /res/layoutland define la interfaz de usuario en el modo horizontal. A continuacin, se muestra el contenido del archivo main.xml de la carpeta /res/layout:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/relativeLayout1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="0.85" android:gravity="center_horizontal" >

<TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginBottom="10dp" android:layout_marginTop="32dp" android:text="Esta aplicacin muestra cmo orientacin del dispositivo Android. controlar el cambio de

\n\nPara cambiar la orientacin del emulador de Android puede usar las teclas [BLOQUE_NUM_7], [Ctrl+F11], [BLOQUE_NUM_9], [Ctrl+F12] de tu ordenador" android:textAppearance="?android:attr/textAppearanceMedium" />

421

<Button android:id="@+id/boton1" android:layout_width="150px" android:layout_height="60px" android:layout_alignParentLeft="true" android:layout_below="@+id/textView1" android:layout_marginLeft="100dp" android:layout_marginTop="93dp" android:text="Botn" />

<EditText android:id="@+id/editText" android:layout_width="197dp" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/boton1" android:layout_marginLeft="50dp" android:layout_marginTop="150dp" />

</RelativeLayout>

Ahora vamos a ver el contenido del archivo main.xml de la carpeta /res/layout-land:

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/relativeLayout1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="0.85" android:gravity="center_horizontal" >

<TextView android:id="@+id/textView1"

422

Android Avanzado

android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginBottom="10dp" android:layout_marginTop="10dp" android:text="Esta aplicacin muestra cmo orientacin del dispositivo Android. controlar el cambio de

\n\nPara cambiar la orientacin del emulador de Android puede usar las teclas [BLOQUE_NUM_7], [Ctrl+F11], [BLOQUE_NUM_9], [Ctrl+F12] de tu ordenador" android:textAppearance="?android:attr/textAppearanceMedium" /> <Button android:id="@+id/boton1" android:layout_width="150px" android:layout_height="60px" android:layout_alignParentLeft="true" android:layout_below="@+id/textView1" android:layout_marginLeft="100dp" android:layout_marginTop="30dp" android:text="Botn" />

<EditText android:id="@+id/editText" android:layout_width="197dp" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/boton1" android:layout_marginLeft="50dp" android:layout_marginTop="70dp" />

</RelativeLayout>

423

Si no creas este archivo para el modo horizontal y ejecutas la aplicacin, vers que al cambiar al modo horizontal desaparece el componente TextView:

Sin embargo, si creamos el archivo de diseo horizontal, cuando cambiemos la orientacin del dispositivo, Android cambiar automticamente el diseo de la pantalla:

8.4.2

Mantener la informacin del estado durante el cambio de orientacin Si en el ejemplo anterior escribes algo en el TextView y, a continuacin, cambias la

orientacin del dispositivo virtual, vers que el texto escrito en este componente se mantiene sin aadir nuevo cdigo Java. En el apartado "Guardar y recuperar el estado de una Actividad" de la Unidad 3 hemos estudiado que, cuando cambia la orientacin de la pantalla (vertical/horizontal), Android reinicia la Actividad usando el mtodo OnDestroy() e inmediatamente llama de nuevo a onCreate(). Este comportamiento de reinicio est diseado para que la aplicacin se adapte a la nueva configuracin de forma automtica, y as cambiar la posicin de los componentes.

424

Android Avanzado

La mejor manera de manejar un cambio de configuracin de este tipo para preservar el estado de la aplicacin es usar los mtodos onSaveInstanceState() y onCreate(). Lo primero que hay que tener en cuenta es que es imprescindible establecer el atributo android:id de todas las Vistas de la actividad. Este atributo es indispensable para que Android guarde automticamente el contenido de las Vistas cuando cambia la orientacin de la pantalla y se destruye la Actividad. Por ejemplo, si un usuario ha introducido un texto en una Vista de tipo EditText y cambia la orientacin del dispositivo, si este EditText tiene asignado un valor al atributo android:id, Android mantendr el texto existente y lo restaurar de forma automtica cuando la actividad se vuelva a recrear. Si, por el contrario, la Vista de tipo EditText no tiene definido el atributo android:id, el sistema no podr conservar el texto y cuando se recree la actividad, el texto se perder. Android invoca el mtodo onSaveInstanceState() cuando una Actividad est a punto de ser destruida o va a pasar a un segundo plano. Por ejemplo, cuando se cambia la orientacin de la pantalla, se invoca este mtodo para que se pueda guardar el estado actual de la actividad y poder restaurarlo ms tarde. Hay otro procedimiento que permite sustituir el evento onSaveInstanceState() para guardar informacin extra necesaria en la Actividad y restaurarla cuando se recree. Por ejemplo, el siguiente cdigo muestra cmo guardar la orientacin actual del dispositivo sin usar el evento onSaveInstanceState():

* Se llama a este evento cuando Android inicia un cambio de orientacin. * CUIDADO! Para que el cambio se haga de forma AUTOMTICA debemos delegarle * esta funcionalidad. Esto se consigue quitando del archivo * AndroidManifest.xml el atributo android:configChanges="orientation..." * * Si controlamos de forma MANUAL el cambio de orientacin, debes comentar * este mtodo. */ @Override public void onSaveInstanceState(Bundle outState) { // Obtenemos la orientacin actual del dispositivo String texto=""; // Conectamos con el servicio de ventanas de Android y obtenemos los datos de la pantalla principal Display display = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();

425

int orientation = display.getRotation(); if ((orientation==Surface.ROTATION_90) || (orientation==Surface.ROTATION_270)) texto="vertical"; else texto="horizontal";

// Guardamos una informacin del estado outState.putString("dato", texto); super.onSaveInstanceState(outState); }

Cuando la actividad se vuelve a recrear, Android invoca primero el mtodo OnCreate(), seguido por el mtodo onRestoreInstanceState(). Este ltimo mtodo permite recuperar el estado de ejecucin guardado previamente:

* Se llama a este evento cuando Android inicia un cambio de orientacin. * CUIDADO! Para que el cambio se haga de forma AUTOMTICA debemos delegarle * esta funcionalidad. Esto se consigue quitando del archivo * AndroidManifest.xml el atributo android:configChanges="orientation..." * * Si controlamos de forma MANUAL el cambio de orientacin debes comentar * este mtodo. */ @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // Recuperamos la informacin del EditText if (savedInstanceState.containsKey("dato")) Toast.makeText(this, "Orientacin anterior: " + savedInstanceState.getString("dato"), Toast.LENGTH_SHORT).show(); }

Hemos visto que el mtodo onSaveInstanceState() es til para guardar la informacin del estado de ejecucin de una Actividad, aunque tiene la limitacin de que slo se puede 426

Android Avanzado

guardar informacin usando el objeto de tipo Bundle. No permite guardar estructuras de datos ms complejas, como objetos. Para estos casos, podemos usar el mtodo onRetainNonConfigurationInstance(). Este mtodo se activa cuando una actividad est a punto de ser destruida debido a un cambio de configuracin, como un cambio de orientacin de la pantalla. Este mtodo permite guardar una estructura de datos devolviendo un objeto como resultado de su ejecucin. Fjate en el siguiente ejemplo:
@Override public Object onRetainNonConfigurationInstance() { // Devolvemos un objeto donde hemos guardado un estado de ejecucin return(objeto); }

Fjate que el mtodo anterior devuelve el tipo objeto (Object), lo que permite prcticamente devolver cualquier tipo de dato. Para extraer los datos guardados se puede usar dentro del mtodo onCreate() el mtodo getLastNonConfigurationInstance(). Por ejemplo, as:

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Recuperamos el objeto original Objeto objeto = (Objeto) getLastNonConfigurationInstance(); }

8.4.3 Cambio de orientacin Manual Hay casos en los que es necesario controlar el proceso de creacin-destruccin de una aplicacin cuando se cambia la orientacin del dispositivo y no queremos que Android lo haga de manera automtica. En este caso, hay que especificar el atributo android:configChanges del elemento <activity> en el archivo AndroidManifest.xml:

427

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="es.mentor.unidad8.eje3.orientacion" android:versionCode="1" android:versionName="1.0" >

<uses-sdk android:minSdkVersion="8" />

<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- CUIDADO! Para que el cambio lo haga Android debemos permitirle que gestione esta funcionalidad. Esto se consigue quitando el atributo android:configChanges="orientation..." --> <activity android:label="@string/app_name" android:name=".OrientacionActivity" android:configChanges="orientation|keyboardHidden" > <intent-filter > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>

</manifest>

El atributo anterior indica que la Actividad gestiona los cambios de orientacin ocultando el teclado cuando este cambio ocurre. Adems, cuando este giro del dispositivo ocurre, Android invoca el mtodo onConfigurationChanged(), en el que se puede volver a dibujar la interfaz de usuario de la Actividad:

/* Se llama a este evento cuando Android cuando cambia la orientacin * del dispositivo. CUIDADO! Para que este evento se invoque debemos

428

Android Avanzado

* gestionar de forma MANUAL la funcionalidad de cambio de orientacin. * Esto se consigue aadiendo en el archivo AndroidManifest.xml el atributo * android:configChanges="orientation..." * * Si controlamos de forma MANUAL el cambio de orientacin, ya no son * necesarios los mtodos onSaveInstanceState() y onRestoreInstanceState() * y debemos comentarlos. */ @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // Si controlamos el cambio, tambin hay que guardar los contenidos de los componentes visuales String texto = et.getText().toString(); if (newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE) { Toast.makeText(this, "Cambio a horizontal", Toast.LENGTH_SHORT).show();

setContentView(R.layout.main); } else { Toast.makeText(this, "Cambio a vertical", Toast.LENGTH_SHORT).show();

setContentView(R.layout.main); } //Obtenemos una referencia a las Vistas de la Actividad et = (EditText)findViewById(R.id.editText); // Escribimos orientacin el texto que tena el EditText antes del cambio de

et.setText(texto); }

8.4.4 Cambiar la orientacin de la pantalla con sentencias Java

429

En ocasiones, es necesario asegurarse de que una aplicacin se muestra siempre en una orientacin concreta. Por ejemplo, muchos juegos slo se visualizan bien en modo horizontal. En este caso, mediante sentencias Java, se puede cambiar la orientacin de la pantalla con el mtodo setRequestOrientation() de la clase de Activity:

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); //Obtenemos una referencia al EditText de la Actividad et = (EditText)findViewById(R.id.editText); }

Adems de utilizar el mtodo setRequestOrientation() para cambiar la orientacin de la pantalla, tambin se puede utilizar el atributo android:screenOrientation dentro del elemento <activity> en el archivo AndroidManifest.xml. Fjate en el siguiente ejemplo:

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="es.mentor.unidad8.eje3.orientacion" android:versionCode="1" android:versionName="1.0" >

<uses-sdk android:minSdkVersion="8" />

<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:label="@string/app_name" android:name=".OrientacionActivity" android:screenOrientation="landscape" <intent-filter > >

430

Android Avanzado

<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>

</manifest>

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Orientacin) de la Unidad 8. Estudia el cdigo fuente y ejectalo para mostrar en el emulador una aplicacin en la que mostramos cmo manejar la orientacin de la pantalla de un dispositivo Android.

Nota: por defecto, el Ejemplo 3 funciona en modo Manual. Si quieres cambiar a automtico, debes modificar el archivo AndroidManifest.xml del proyecto.

modo

Atencin: el emulador de Android no funciona muy bien a la hora de simular el cambio de orientacin del dispositivo. Dependiendo de la versin de Android, algunos cambios de orientacin no se pueden hacer o, a veces, un giro emulado del dispositivo destruye la Actividad dos veces antes de cambiar la orientacin del terminal. Sin embargo, la teora y funciones aqu expuestas s son vlidas para un dispositivo real que funcionar correctamente segn lo esperado.

8.5

DESPLEGAR APLICACIONES VIRTUALES (AVD) O REALES

ANDROID

EN

DISPOSITIVOS

Para poder desplegar aplicaciones compiladas (tienen la extensin .apk), primero debemos conectar un dispositivo real por USB o arrancar un dispositivo virtual desde Eclipse. Esto es muy til si queremos ver en funcionamiento los modelos de las actividades obligatorias de este curso, ya que nicamente se entregan compiladas.

Para arrancar manualmente un dispositivo virtual desde Eclipse hay que pulsar el siguiente botn de la barra de herramientas:

Desde la ventana de

dispositivos

virtuales

seleccionamos

el

dispositivo

que

deseamos arrancar y pulsamos el botn "Start": 431

A continuacin, arrancar el dispositivo virtual. Si queremos instalar la aplicacin en un dispositivo real de Android, no es necesario iniciar ningn dispositivo virtual.

Nota: en el caso de algunos dispositivos reales, dependiendo de la marca de dispositivo Android, puede ser necesario instalar los drivers para que el sistema operativo lo reconozca correctamente.

Adems, el dispositivo real debe estar configurado para admitir la instalacin de aplicaciones sin firmar por el Android Market. Si accedes en Ajustes->Aplicaciones debes marcar la siguiente opcin:

432

Android Avanzado

Una vez disponemos de un dispositivo (real o virtual) de Android ejecutndose o conectado por USB al PC, abrimos una consola de Windows (o del sistema operativo correspondiente) y utilizamos la utilidad adb.exe (Android Debug Bridge) situada en la carpeta platform-tools del SDK de Android. En primer lugar, consultamos todos los identificadores de los dispositivos en ejecucin mediante el comando "adb devices". Este comando debe devolver todas las instancias con los dispositivos abiertos:

Los dispositivos que aparezcan con la etiqueta "emulator-xxx" son dispositivos virtuales y los que muestren otra etiqueta son dispositivos reales (telfonos, tablets, etctera). Adems, los dispositivos que aparezcan con la etiqueta "offline" estn conectados, pero no estn disponibles al ADB (Android Debug Bridge). Para este ejemplo, hemos seleccionado el dispositivo emulator-5556 que corresponde al dispositivo virtual con Android 2.3.3 para instalar una aplicacin. Puedes ver el "id" del dispositivo en la ventana del emulador:

433

Tras obtener este identificador del emulador, vamos a instalar la aplicacin mediante el comando adb -s identificador-del-emulador install nombre-fichero-apk. Fjate en el siguiente ejemplo:

Una vez instala la aplicacin, tenemos que ejecutarla en el dispositivo buscando su icono en la pantalla de aplicaciones:

434

Android Avanzado

Hacemos clic en el icono de la aplicacin para ver su resultado:

8.6

CMO PUBLICAR APLICACIONES EN EL ANDROID MARKET

El Android Market (en espaol Mercado de Android) es una tienda de software en lnea desarrollada por Google para los dispositivos Android. Es una aplicacin que est preinstalada en la mayora de los dispositivos Android y que permite a los usuarios buscar y descargar aplicaciones publicadas por terceros desarrolladores. Los usuarios tambin pueden buscar y obtener informacin sobre aplicaciones a travs de una pgina Web. Las aplicaciones en el Android Market pueden ser gratuitas o de pago. En este apartado vamos a tratar cmo publicar una aplicacin gratuita. En este apartado vamos a explicar los pasos para publicar una aplicacin en el Android Market.

8.6.1

Alta de cuenta de desarrollador en el Android Market El primer paso obligatorio es darse de alta como desarrollador en el Android Market.

Para ello, necesitamos disponer de una cuenta de Google (GMail). Con el navegador de Internet accederemos a la direccin: 435

http://market.android.com/publish
En esta pgina introducimos el usuario y la contrasea de Google:

La primera vez que accedemos a la pgina se muestra un asistente para dar de alta una nueva cuenta de desarrollador en el Android Market. Introducimos los datos que se solicitan (nombre del desarrollador, correo electrnico, URL del sitio Web y nmero de telfono). Despus, pulsamos el enlace "Seguir":

Para poder darnos de alta como desarrolladores del Android Market y publicar aplicaciones, hay que abonar 25,00$. Se trata de una cuota nica sin caducidad. Para pagar

436

Android Avanzado

esta cuota podemos usar el servicio Google Checkout o pulsar en "Continuar" para pagar con tarjeta de crdito:

A continuacin, aparece el detalle de la factura con el artculo "Android - Developer Registration Free for xxx". En esta pgina introducimos los datos de nuestra tarjeta de crdito para realizar el pago, as como la direccin postal de facturacin donde llegar la correspondiente factura por correo ordinario:

437

Si todo est correcto, el asistente mostrar la siguiente ventana, indicando que "Su pedido se ha enviado al Android Market". Para continuar con el proceso, pulsamos en el enlace "Vuelve al sitio de desarrolladores de Android Market para completar el registro":

Despus, leemos la licencia de desarrollador para el Android Market. Si estamos de acuerdo, hacemos clic en el enlace "Acepto las condiciones y deseo asociar la tarjeta de crdito y la cuenta que he registrado anteriormente al Acuerdo de distribucin para desarrolladores de Android Market". Pulsamos "Acepto. Continuar":

438

Android Avanzado

El asistente indicar que el registro ha concluido, con el mensaje "Se ha aprobado tu registro en Android Market. Ahora puedes subir y publicar aplicaciones de software en Android Market". A partir de este momento ya podremos usar nuestra cuenta para publicar aplicaciones:

8.6.2

Recomendaciones sobre aplicaciones para Android Market Cuando desarrollemos aplicaciones que vamos a publicar en el Android Market,

debemos prestar especial atencin a una serie de caractersticas. 8.6.2.1 Recomendaciones sobre aplicaciones para Android Market

Antes de empezar a desarrollar aplicaciones Android que vamos a publicar en el Market, hay que saber que cuando un usuario realiza una bsqueda de una aplicacin en el Market usando su dispositivo Android, slo le aparecern las aplicaciones que cumplan los filtros (de permisos y de caractersticas del dispositivo) y el nivel de API (API Level) indicados en el archivo AndroidManifest.xml. El "API Level" es la versin de Android compatible con la aplicacin. Por ejemplo, durante el curso hemos usado la versin 2.3.3 de Android que corresponde con el "API Level" 10. Si publicamos una aplicacin desarrollada con esta versin de Android, nicamente ser visible y slo podr instalarse en dispositivos con una versin igual o superior a la 2.3.3 de Android.

439

Los filtros de permisos permiten a una aplicacin solicitar acceso a recursos de Android. Ya hemos estudiado que si, por ejemplo, una aplicacin requiere acceder a la cmara de fotos, debemos indicarlo en el archivo AndroidManifest.xml: <uses-permission android:name="android.permission.CAMERA" /> Al Indicar estos permisos, esta aplicacin no aparecer en las bsquedas realizadas desde dispositivos Android que no dispongan de cmara de fotos. Es decir, si solicitamos acceder a un recurso (cmara, wifi, bluetooth, etctera) que el dispositivo no tiene, la aplicacin no ser visible en el Market. Adems, existen otros filtros con las caractersticas del dispositivo en el archivo AndroidManifest.xml que hacen que la aplicacin aparezca o no en el Market para un dispositivo determinado: <supports-screens>: establece el tipo de pantalla (resolucin mnima) que necesita la aplicacin para funcionar. <uses-feature>: especifica el uso de caractersticas del dispositivo, por ejemplo: o Para utilizar Bluetooth: <uses-feature android:name="android.hardware.bluetooth" /> o Para usar la cmara: <uses-feature android:name="android.hardware.camera" /> <uses-library>: indica las libreras especficas que requiere la aplicacin.

Es importante tener en cuenta que cuanto mayores sean los requisitos de hardware (cmara, bluetooth, GPS, brjula, sensor de movimiento, etctera), la aplicacin ser visible e instalable en un menor nmero de dispositivos Android.

8.6.2.2

Buenas prcticas para el desarrollo de aplicaciones Android

A continuacin, mostramos algunas recomendaciones a la hora de desarrollar aplicaciones Android tiles, profesionales y fiables: Siempre hay que tener en cuenta que estamos desarrollando aplicaciones para dispositivos con pantalla muy pequea, si son telfonos, lo que no ocurre en los tablets, y teclado limitado, por lo que las aplicaciones deberan mostrar pocos campos de texto y opciones reducidas. Antes de desarrollar una aplicacin Android, es recomendable buscar en el Market si ya existe una aplicacin similar. Si queremos que nuestra aplicacin sea til para los usuarios, debe ser interesante, original y sencilla incorporando funciones que no tengan otras. 440

Android Avanzado

Hay que procurar, en la medida de lo posible, desarrollar aplicaciones que se puedan instalar en el mayor nmero posible de dispositivos para que tenga ms difusin. Por lo tanto, debemos realizar aplicaciones con la versin de Android mnima y los requisitos de hardware bsicos.

Las aplicaciones deben ser rpidas. Si es necesario realizar algn proceso que pueda tardar unos segundos, es recomendable avisar al usuario o, incluso, usar hilos de ejecucin, servicios, etctera. El usuario de un dispositivo mvil espera siempre rapidez de respuesta.

8.6.3

Generar fichero APK con certificado para Android Market Cuando compilamos un proyecto Android al hacer "Run" en Eclipse, el fichero .apk

(paquete de instalacin de la aplicacin Android) generado dentro del directorio /bin no es vlido para subirlo directamente al Android Market. Si intentamos subir este fichero directamente aparecer este mensaje:

Market does not accept apks signed with the debug certificate. Create a new certificate that is valid for at least 50 years. Market requires that the certificate used to sign the apk be valid until at least October 22, 2033. Create a new certificate. Market requires the minSdkVersion to be set to a positive 32-bit integer in AndroidManifest.xml. Uno de los requisitos para poder publicar de aplicaciones en Android Market es que el paquete de instalacin APK debe estar firmado con un certificado vlido de al menos 25 aos. A continuacin, explicamos cmo hacerlo. En primer lugar, una vez desarrollada y probada la aplicacin Android con Eclipse, hacemos clic con el botn derecho del ratn sobre la carpeta del proyecto y seleccionamos la opcin "Export" del men emergente:

441

Abrimos la carpeta "Android" y seleccionamos "Export Android Application"; despus, pulsamos el botn "Next":

En la ventana siguiente, en el campo "Project", podemos seleccionar otro proyecto si nos hemos equivocado. Pulsamos de nuevo el botn "Next":

442

Android Avanzado

A continuacin, si no disponemos de una clave, seleccionamos la opcin "Create new keystore". Introducimos un directorio y nombre para el almacn de claves, por ejemplo C:\cursos_Mentor\Android\claves.android. Introducimos la contrasea para el almacn de claves:

Si ya disponemos de un almacn de claves, seleccionamos "Use existing keystore" y seleccionamos el certificado escribiendo la clave correspondiente. Pulsamos el botn "Next" para seguir. A continuacin, escribimos los datos administrativos de la clave que vamos a crear para certificar nuestras aplicaciones: 443

Alias: identificador de la clave. Password: contrasea de la clave, debemos guardarla o recordarla pues la necesitaremos cada vez que vayamos a publicar una nueva aplicacin o actualizar una ya existente en el Android Market.

Confirm: reescribimos la contrasea anterior. Validity (years): validez del certificado, al menos 25 aos. First and Last Name: nombre del desarrollador o de la empresa. Organization Unit: departamento. Organization: nombre de la empresa. City or Locality: ciudad. State or Province: provincia. Country Code: cdigo postal de la ciudad.

Tras introducir los datos pulsamos el botn "Next": A continuacin, indicamos la carpeta y el nombre del paquete APK compilado que se firma con el certificado anterior y que ser el fichero que finalmente subiremos al Android Market. En es caso hemos seleccionado la carpeta C:\cursos_Mentor\Android\androidmarket del curso:

444

Android Avanzado

Si hemos seguido bien los pasos anteriores, ya dispondremos del fichero APK firmado con el certificado que podemos publicar en el Android Market:

8.6.4

Publicar una aplicacin Android en el Android Market Vamos a explicar cmo publicar una aplicacin firmada con el certificado para que

aparezca en Android Market y los usuarios puedan descargarla e instalarla. Accedemos a la web de Android Market con la cuenta de desarrollador que hemos dado de alta anteriormente escribiendo en la barra de direcciones del navegador:

https://market.android.com/publish/Home
Pulsamos en el enlace "Subir aplicacin":

445

Aparecer una pagina donde podemos seleccionar el fichero APK pulsando en "Examinar" para elegir el fichero APK de nuestra aplicacin Android firmada con el certificado:

Pulsamos en el botn "Publicar" para subirla al Android Market. Si el paquete APK est correcto y cumple con todos los requisitos (versin de Android, certificado, compilacin, etctera), el asistente muestra el botn "Guardar" y los datos del APK (nombre de la aplicacin, nombre de la versin, cdigo de la versin, permisos que necesita, funciones que necesita, tamao, nombre de la clase Java). Pulsamos el botn "Guardar" para almacenar la aplicacin:

446

Android Avanzado

Tras subirlo, pulsamos en el enlace "Activar" para introducir los datos necesarios para publicar la aplicacin en el Android Market. Desde esta pgina podemos activar o desactivar la publicacin de las aplicaciones subidas. Por ejemplo, si hemos detectado algn error y no queremos que los usuarios se descarguen una aplicacin hasta solucionar el problema, podremos desactivarla:

Si pulsamos el botn "Activar", a continuacin, aparece una ventana donde debemos aadir todos los datos requeridos en la pestaa "Informacin de producto" para acabar de dar de alta la nueva aplicacin: Capturas de pantalla de la aplicacin: al menos debemos subir dos capturas; es recomendable que tengan buena calidad, para que el usuario se haga una idea del aspecto que tiene la aplicacin. 447

Icono de la aplicacin: la aplicacin se identifica con un icono que aparece en la parte izquierda de la pantalla del Android Market cuando los usuarios buscan aplicaciones.

Imagen promocional, imagen de funciones y vdeo promocional de Youtube: son datos opcionales que sirven para incluir ms informacin de la aplicacin.

Si no deseamos que la aplicacin se anuncie fuera de Android Market, marcamos la Casilla: "No promocionar mi aplicacin salvo en Android Market y en los sitios web o para mviles propiedad de Google. Asimismo, soy consciente de que cualquier cambio relacionado con esta preferencia puede tardar sesenta das en aplicarse".

Podemos elegir varios idiomas para escribir la descripcin de las funciones y uso de la aplicacin. El ingls es obligatorio. En este punto se solicitan los campos: Ttulo de la aplicacin: nombre que aparece en las bsquedas, no debe ser muy largo (inferior a 30 caracteres). Descripcin: descripcin detallada (hasta 4000 caracteres) de la funcionalidad de la aplicacin. Cambios recientes: si se trata de una actualizacin, podemos indicar aqu las ltimas mejoras implementadas. Si hemos incluido un vdeo promocional, podemos aadir un texto promocional. Tipo de aplicacin: seleccionamos en el desplegable el tipo que ms se ajuste a la funcionalidad de la aplicacin. Categora: seleccionamos en el desplegable la categora que ms se ajuste a la aplicacin.

Proteccin contra copias: lo usual es que no est seleccionada esta opcin, ya que, como indica Android Market, esta funcin quedar obsoleta en breve, siendo sustituida por el servicio de licencias.

Clasificacin del contenido: marcamos si nuestra aplicacin es para todos los pblicos o contiene algn tipo de contenido para mayores.

Precios: aqu indicamos si la aplicacin es gratuita o de pago. Precio predeterminado: si hemos elegido de pago, en este campo introducimos el precio de la aplicacin. Pulsando el botn "Autocompletar" har los ajustes para los diferentes pases en los que queramos publicarla.

448

Android Avanzado

Tambin se indica el nmero aproximado de modelos de dispositivos Android sobre los que se podr instalar la aplicacin en funcin de los filtros indicados en el archivo de manifiesto.

Informacin de contacto: o o o Sitio web. Correo electrnico. Telfono.

En la siguiente ventana se muestra parte de los datos que hay que incluir:

Una vez introducidos los datos, pulsamos en el botn "Guardar" de la parte superior derecha. A continuacin, se comprueba si los datos son completos y correctos y, si no hay errores, se guardarn los datos asociados al archivo APK. 449

Despus, pulsamos en el botn "Publicar" (a la izquierda del botn "Guardar") para publicar definitivamente la aplicacin en Android Market:

Tras finalizar la publicacin, se mostrar en "Todos los elementos de Android Market" la nueva aplicacin con el estado "Publicada". En esta pgina podemos llevar a cabo un seguimiento del nmero de instalaciones, posibles errores, comentarios de los usuarios, popularidad, etctera.

450

Android Avanzado

La Depuracin de programas (en ingls Debug) es el proceso de identificar y corregir errores de programacin en tiempo de ejecucin. Un Punto de interrupcin (Breakpoint en ingls) es una marca en el cdigo fuente que pausa la ejecucin de un programa, para que el programador pueda evaluar los valores asignados a las variables y detectar errores en tiempo de ejecucin. El entorno de desarrollo Eclipse permite llevar a cabo de manera sencilla la Depuracin de programas. Es posible incluir mapas en las aplicaciones de Android haciendo uso de la API Android de Google Maps. Para poder utilizar la API de Google Maps, es necesario disponer de una clave de uso (API Key) que estar asociada al certificado con el que firmamos digitalmente las aplicaciones. El Android Market (en espaol Mercado de Android) es una tienda de software en lnea desarrollada por Google para los dispositivos Android. Para poder publicar aplicaciones en el Android Market, es necesario darse de alta y pagar una cuota. El paquete de instalacin APK de una aplicacin del Android Market debe estar firmado con un certificado vlido de al menos 25 aos.

451

Вам также может понравиться