Sie sind auf Seite 1von 468

Android

Guía para desarrolladores


MANNING

TíTULO DE LA OBRA ORIGINAL:


Unlocking Android. A Developer's Guide

RESPONSABLE EDITORIAL:
Víctor Manuel Ruiz Calderón
Susana Krahe Pérez-Rubín

TRADUCTOR:
José Luis Gómez Celador

DISEÑO DE CUBIERTA:
Cecilia Poza Melero
Android
Guía para desarrolladores

Frank Ableson
Charlie Collins
RobiSen

/ , N/,y/,
.-MULTIMEDIA_
Todos los nombres propios de programas, sistemas operativos, equipos
hardware, etc. que aparecen en este libro son marcas registradas de
sus respectivas compañías u organizaciones.

Reservados todos los de rechos. El contenido de


esta obra está protegido por la Ley, que establece
penas de prisión y/o multas, además de las
correspondientes indemnizaciones por daños y
perjuicios, para quienes reprodujeren, plagiaren,
distribuyeren o comunicaren públicamente, en
todo o en parte, una obra literaria, artística o
científica, o su transformación, interpretación
o ejecución artística fijada en cualquier tipo
de soporte o comunicada a tra vés de cualquier
medio, sin la preceptiva autorización.

Authorized translation from English language edition published by Manning


Publications
Copyright © 2009 by Manning Publications Co.
AH rights reserved.

Edición española:
© EDICIONES ANAYA MULTIMEDIA (GRUPO ANAYA, S.A.), 2010
Juan Ignacio Luca de Tena, 15. 28027 Madrid
Depósito legal: M. 47.369-2009
ISBN: 978-84-415-2682-2
Printed in Spain
Impreso en : Fernández Ciudad S. L.
Para Nikki.
.. Agradecimientos

Agradecim ientos

Ingenuos de nosotros, pensamos que este libro estaría acabado hace un año. Algo
hemos aprendido sobre lo que se tarda en escribir un libro técnico . Durante su creación,
hubo momentos de tensión, en especial durante las llamadas en las que intentamos
abrimos paso entre las numerosas actualizaciones del SDK y las fechas indefinidas del
lanzamiento de Android. Afortunadamente, la editorial tomó esas decisiones y lo hizo
a la perfección.
En especial nos gustaría agradecer a todos los de la editorial que nos ayudaron a que
este libro viera la luz. En primer lugar, Troy Mott, nuestro editor, que estuvo desde el
principio, desde las fases de planificación hasta la línea de meta; Tom Cirtin, el editor
del libro, por sus opiniones sobre la estructura y los contenidos; Karen Tegtmeyer, que
se encargó de todos los aspectos necesarios para solidificar el proyecto y Marjan Brace,
cuya influencia se aprecia en muchos pasajes del libro. Marjan siempre quería oir qué
partes del libro no convencían a los revisores, para mejorarlas y satisfacer a los lectores.
No fue fácil, pero lo conseguimos entre todos.
Una vez finalizado el libro, comenzó la siguiente fase y tenemos que dar las gracias a
tres personas en concreto. A Linda Recktenwald, por conseguir que el contenido resultara
legible en aquellas partes demasiado técnicas o demasiado literarias; a Elizabeth Martin,
nuestra revisora que le dio sentido común al proyecto, además de su humor y su continuo
apoyo, y a [esse Dailey, responsable de la edición técnica, de validar el trabajo técnico ,
equilibrar los sangrados XMLy facilitar la lectura del texto. Evidentemente, otras muchas
personas de la editorial contribuyeron en la sombra para conseguir la publicación de
este libro, y estamos en deuda con todos y cada uno de ellos.
Agradecer también a Dick Wall su revisión de toda la obra y su prólogo. Y un agra-
decimiento especial a los demás revisores que hicieron un hueco en su agenda para leer
nuestro manuscrito en las distintas fases de desarrollo: Bruno Lowagie, Hannu Terava,
Maxim Yudin, Dierk Konig, Michael Martin, Charles Hudson, Gabor Palier, Scott Webster,
Aleksey Nudelman, Horaci Macias, Andrew Oswald, Kevin P. Galligan, Chris Gray y
Tyson S. Maxwell .
Por último, nos gustaría agradecer a los suscriptores de MEAP por su participación
en el proceso; este libro es mejor gracias a su colaboración.

Frank Ableson

Me gustaría agradecer la colaboración de mis compañeros Charlie Collins, Rabi Sen


y Troy Mott, y su participación en este proyecto. Ya mi mujer Nikki y a mis hijos Julia,
Tristan, Natalie, Aidan y Liam, ¡ya está terminado! En especial a mi hijo Tristan, que
fue una fuente de apoyo continuo a lo largo de este proceso, y siempre me preguntaba
entusiasmado cómo iba todo y me alentaba en la recta final.
Por último, dar las gracias a Barry Quiner y a Michael Petrin por sus ánimos y su
amistad.
Android. Gl/ía para desarrolladores . .

Charlie Collins
Para empezar, me gustaría dar las gracias a mis compañeros Frank Ableson y Rabi
Sien, por su esfuerzo en este proyecto desde el inicio y quienes me invitaron a bordo. Al
fin es un libro, gracias y felicidades. Además, mostrar mi agradecimiento a la editorial.
Me gustaría dar las gracias a la Open Handset Alliance y al equipo de Android.
Disponer de una plataforma móvil abierta, pero concisa y concreta, como Android es una
ventaja para el universo tecnológico y para los usuarios. No es perfecta, todavía, pero el
camino es largo y el enfoque y la colaboración no se pueden desestimar. Me gustaría dar
las gracias a los colaboradores por las herramientas que he utilizado para trabajar en este
proyecto como Ubuntu Linux, OpenOffice, Eclipse, Subversion, GIMP y Java .
Un agradecimiento especial a mi familia y mis amigos, que asumieron la falta de
tiempo que les dedicaba por tener que trabajar en este libro. Mucha de la gente que me
importa acabará leyendo este libro. Si eres uno de ellos, gracias. En especial a mi esposa
Erin y a mis hijas Skylar y Delaney, por su apoyo y su pasión para animarme en los
momentos adecuados. Mis padres Earl y Margaret Farmer fueron imprescindibles, como
siempre. Mi amigo Mike Beringson tuvo que soportar muchas llamadas de cancelación de
nuestras citas . Y a mis vecinos y todos los que me ayudaron a continuar, los Cheatham,
los Thomspon, los Crowder y los Haff, gracias a todos de nuevo.

RobiSen
Me gustaría dar las gracias a Tropy Mott y al equipo de la editorial por su esfuerzo
por convertir este libro en una lectura amena. También a los otros dos autores, Frank y
Charlie, con los que ha sido un placer trabajar y por su comprensión. Agradecer a [esse
Dailey sus revisiones técnicas y su ayuda con los ejemplos OpenGL ES de uno de los
capítulos.
Por último/ dar las gracias a mi familia que tuvo que aceptar mi ausencia, más tiempo
del que hubiera deseado, mientras trabajaba en mis capítulos.
~

I Ice
ec tenidos

Agradecimien tos 6

Ín dice de contenidos 9

Prólogo 18

Prefacio 22

Introducción 26
El público 27
Estructura 28
Parte 1: Fundament os 28
Parte TI: El en torno de programación 28
Parte ITI: Combinar todas las p iezas 29
Parte IV: Ap éndices 29
Convenciones 30
Cód igo fuen te 30
Requisitos de software 30

Parte l. Introducción a Andro id 31


1. Android 32
Presentación de Android 33
La p lataforma Android 34
.. Índicede contenidos

El mercado de Android '" 36


Operadores mó viles 36
Android fren te a teléfonos tradicion ales 37
Android y teléfonos inteligentes 38
Android frente a Android 39
Licencia s de Android 40
Componentes de Android 41
Los cimientos de Android 42
El núcleo de Linux 43
Ejecutar la máquina virtual Dalvik 43
Iniciar el desarrollo de Android 44
Objetivos de Android 45
Activar Android 48
Activity 49
Service 50
BroadcastReceiver 51
ContentProvider 53
AndroidManifest.xml 55
Asignar ap licaciones a procesos 57
Una aplicación de Android 58
Resumen 61

2. Entorno de desarrollo 64

El SDK de Android 65
Interfaz de programación de aplicaciones 66
Paquetes básicos de Android 66
Paquetes opcionales 67
Combinar las piezas 68
Perspectiva Java 69
Perspectiva DDMS 71
Herramientas de línea de comandos 73
Herramienta de compresión de activos de Android 74
Debug Bridge 74
Crear una aplicación de Android en Eclipse 76
APW 76
Código de la aplicación de Android 77
Generar la aplicación 82
El emulador de Android 84
Skins 84
Velocidad de red 85
Perfiles de emulador 87
Depuración 90
Resumen 91
Índice de con tenidos . .

Parte 11. Ejecutar el SDK de Android 93

3. Interfaces de usuario 94
Crear la actividad 96
Crear una clase Activity 98
Explorar el ciclo de vida de las actividades 103 .
Relación entre procesos y actividades 103
Ciclo de vida de las actividades 104
Trabajar con vistas 106
Analizar las vistas comunes 107
Utilizar ListView 109
Multitarea con Handler y Message 113
Crear vistas personalizadas 115
Comp render el diseño 117
Enfoque 119
Even tos 120
Utilizar recursos 121
Tipos de recursos admitidos 121
Hacer referencia a recursos en Java 122
Definir vis tas y diseños con recursos XML 124
Externalizar valores 126
Animaciones 129
Comprender el archivo de manifiesto 130
Resumen 133

4. Inten t y Serv ice 134


Trabajar con clases Intent 136
Definir Intent 136
Resolución de Intent 140
Acción y categorías 141
Datos 141
Comparar un URI personalizado 143
Utilizar actividades proporcionadas por Android 147
Escuchar con receptores 148
Ampliar el concepto de Intent 148
Crear un recep tor 150
Crear un servicio 151
Naturaleza dual de un servicio 151
Crear un servicio de tarea de fondo 152
Realizar comunicación entre procesos 155
Lenguaje de definición de interfaces de Android 156
Mostrar una interfaz remota 157
Vincularse a un servicio 159
Iniciar frente a vincular 161
lIfIII Índice decontenidos

Ciclo vital de servicios 162


Ciclo vital de servicios iniciados 162
Ciclo vital de servicios vinculados 162
Ciclo vital de servicios iniciados y vinc ulados 163
Limpiar al detener un servicio 163
Binder y Par celable 163
Resu men 164

5. Almacenar y recuperar datos 166


Utilizar preferencias 168
Trabajar con SharedPreferences 168
Permisos de acceso a p referen cias 171
Utilizar el sistema de archivos 175
Crea r archivos 175
Acceder a archiv os 176
Archivos como recursos sin procesar 178
Recursos de archivos XML 179
Almacena miento externo a través de una tarjeta SD 181
Alm acenar dato s en una base de datos 184
Crear y acceder a un a base de datos 184
Utilizar la herramienta sqlite3 190
Trabajar con clases ContentProvider 190
Rep resen taciones URI y manipulación de registros 192
Consul tar datos 195
Añadir dato s 197
Actualizar datos 198
Eliminar datos 199
Crear ContentProvid er 200
Definir Content_Uri y MIME_TYPE 200
Ampliar ContentProvider 201
Manifiestos de proveedor 206
Resumen 207

6. Redes y servicios Web 208


Redes 211
Fun damentos sobre redes 211
Nodos 211
Capas y protocolos 211
IP 212
TCP y UDP 212
Protocolos de aplicaciones 213
Clientes y servidores 213
Comp robar el estado de la red 214
Índice de contenidos . .

Comunicarse con un socket de servidor 215


Trabajar con HTIP 218
HTIP Yjava .net : 219
HTIP con HttpClient 220
Crea r una clase de ayuda HTIP y HTIPS 222
Servicios Web 228
POX con HTTP y XML 229
REST 231
SOAP o no SOAP, esa es la cuestión 235
Resumen 236

7. Telefonía 238

Información y términos sobre telefonía 240


Acceder a información sobre telefonía 242
Recuperar propiedades de telefonía 242
Obtener información de estado del teléfono 245
Interactuar con el teléfono 246
Utilizar Intent para realizar llamadas 246
Utilidades relacionad as con números de teléfono 248
Interceptar llamadas 250
Trabajar con mensajes: SMS 251
Enviar mensajes SMS 251
Recibir mensajes SMS 254
Resumen 255

8. Notificaciones y alarmas 256

Presentación de Toast 257


Introducir notificaciones 260
Alarmas 266
Ejemplo de alarma 266
Resumen 272

9. Gráficos y animaciones 274

Dibujar gráficos en Android 275


Dibujar con XML 277
Animaciones 280
Crear una animación mediante programación ' 283
Animar recursos 284
Presentación de OpenGL para sistemas incrustados 287
Dibuja r forma s en OpenGL ES 292
Formas y superficies tridimensionales con OpenGL ES 295
Resumen 300
.. Índice de contenidos

10. Multimedia 302


Multimedia y apenCaRE 303
Reproducir audio 305
Reproducir víd eo 306
Capturar medios 309
Comprender la cámara 310
Capturar audio 315
Resumen 319
11. Ubicación 320
Simular la ubicación en el emulador 323
Enviar coordenadas con la herramienta DDMS 323
El formato GPS Exchange 325
KML de Google Earth 328
Utilizar LocationManager y LocationProvider 330
Acceder a datos de ubicación con LocationManager 330
Utilizar LocationProvider 332
Recibir actualizaciones de ubicación con LocationListener 334
Trabajar con mapas 337
Ampliar MapActivity 338
Utilizar MapView 338
Añadir datos a un mapa con Overlay 342
Convertir lugares y direcciones con Geocoder 345
Resumen 346
Parte III. Combinar todas las piezas 349
12. Combinar las piezas: la aplicación Field Service 350
Requisitos de la aplicación Field Service 352
Requisitos básicos 352
Mod elo de datos 353
Arquitectura e integración de la aplicación 354
Repaso a la aplicación de Android 355
Flujo de la aplicación 355
Mapa del código 357
AndroidManifest.xml 359
Código de Android 360
Actividad Splash 360
Actividad FieldService, parte 1 : 362
Clase Prefs 363
Actividad FieldService, parte 11 364
Parámetros ·· 366
Estructuras de datos 368
Descripción XML del trabajo 368
JobEntry ······ 369
Índice de contenidos ...

JobList 371
JobListHandler 373
Profundizar en el código 376
RefreshJobs 376
ManageJobs 380
ShowJob 383
Closejob 387
Código del servidor 394
Interfaz de usuario 394
Base de datos 395
Código PHP 395
Código de integración móvil PHP 396
Resumen 397

13. Modificar Android 400


La fusi ón Android/Linux 402
Cadena de herramientas 402
Crear una aplicación 403
Instalar y ejecutar la aplicación 404
Secuencia de comandos de generación 407
Técnica mejorada 407
Revisión del indicador static 408
Vinculación 409
Salir, no volver 412
Código de inicio 413
Qué hora es 416
Aplicación Daytime Server 416
daytime.c 416
La base de datos SQLite 419
Generar y ejecutar el servidor 421
Cliente Daytime 423
Actividad 423
Cliente 425
Probar el cliente Daytime 426
Resumen 427

Parte IV. Apéndices 429

Apéndice A. Instalar el SDK de Android ; 430


Requisitos del entorno de de sarrollo 431
Obtener e instalar Eclipse 432
Obtener e instalar el SDK de Android 434
Obtener e instalar el complemento de Eclipse 435
Configurar el complemento Eclipse 438
.. lndicedecontenidos

Apéndice B. Firmar e instalar aplicaciones en un dispositivo de Android 440


Android Debug Bridge 441
Firmas digitales 442
Keytool 442
Jarsigner 443
Limpiar para distribución 445
Elementos del manifiesto: etiquetas, logo tipo, versión y nivel SDK 445
Pro bar, probar y volver a probar 446
Acuerdo de licencia de us uario fina l 446
Importar y exportar datos : 446
Publicar en Market 447
Reglas de Mark et 447
Envío de aplicaciones a Market 447
Actu alizacion es au tomáticas de Market 447
Importanc ia de Market 448
Otros med ios de distribu ción 448

Índice alfabético 449


Prólog
Los teléfonos móviles y los dispositivos manuales portátiles experimentan en la ac-
tualidad grandes cambios debidos a diferentes factores. Por un lado, los dispositivos
portátiles son más potentes y capaces de realizar tareas que hace unos años nadie podría
haber imaginado. Muchos de nosotros llevamos encima un dispositivo capaz de conec-
tarse a la Web para ver películas o jugar a juegos 3D, e incluso de realizar llamadas. Por
otra parte, los consumidores demandan estas prestaciones en los nuevos dispositivos.
Una tercera parte de la convergencia es que ahora los dispositivos portátiles constitu-
yen un mayor mercado para los programadores de software y aplicaciones, mayor que
el de las principales plataformas informáticas, y la creación de aplicaciones para dichos
dispositivos suele ser más sencilla y racionalizada.
La nueva generación de teléfonos ya incluye hardware de aceleración gráfica, co-
nectividad inalámbrica, planes de acceso a datos, GPS ampliación y conectividad de
hardware, pantallas táctiles, etc. Los sistemas operativos y las aplicaciones se crean para
aprovechar estas nuevas prestaciones, al tiempo que los consumidores controlan lo que
pueden hacer sus dispositivos, lo que establece una conexión fluida entre programadores
y consumidores. Estos últimos consiguen el software que desean y los programadores
acceden a un mercado potencialmente ilimitado para sus productos.
A esta transformación subyace una tendencia hacia un mayor aperturismo: en las
prestaciones de los dispositivos y cómo controlarlas, en el desarrollo y comercializa-
ción de aplicaciones, en la colaboración entre fabricantes de dispositivos, proveedo-
res de red y de software. Evidentemente, queda mucho por mejorar pero creo que la
próxima generación de plataformas móviles asumirá este aperturismo mucho más
que Android.
EiII Prólogo

Android es un sistema operativo nacido de la alianza de 30 organizaciones del sec-


tor de los dispositivos móviles, como fabricantes de hardware, operadores y empresas
de software, comprometidos a ofrecer un mejor teléfono móvil al mercado. El resultado
es un sistema operativo y entorno de desarrollo de aplicaciones capaz de ejecutarse en
distintos dispositivos, lo que constituye un entorno coherente y completo para los pro-
gramadores. El ecosistema de Android incluirá múltiples dispositivos, miles de apli-
caciones y componentes que dominar o mejorar, y múltiples canales de distribución
(incluidos los ya disponibles).
La creación de aplicaciones para Android es, en cierta medida, similar al desarrollo
basado en contenedores. En lugar de una visión en la que la aplicación se ejecuta y en
un punto concreto termina, Android permite que su aplicación se integre en el entorno
general. Este entorno se basa en herramientas y conocimientos de Java, lo que reduce la
curva de aprendizaje y proporciona la facilidad y la seguridad del desarrollo en un len-
guaje gestionado. Android le permite ejecutar servicios de fondo e incluye componentes
y servicios de datos que puede compartir con otras aplicaciones.
En definitiva, Android es un magnífico entorno para programadores de aplicaciones
y este libro le permitirá aprovechar al máximo sus prestaciones. Los autores le guiarán
por las herramientas de desarrollo, la arquitectura, API básicas y avanzadas, hasta lle-
gar a conceptos más complejos como el desarrollo de aplicaciones nativas. Este manual
es una guía valiosa y útil que le permitirá desarrollar sus propias aplicaciones para esta
nueva y apasionante plataforma abierta.

Dick Wall
Ingeniero, experto deAndroidpara Google y copreseniador deJava Posse.
Prefacio
Las primeras aplicaciones móviles con las que tuve la oportunidad de trabajar fue-
ron programas de control de inventario utilizados en entornos de venta y fabricación de
productos. Las terminales, como se denominaban entonces, eran pesadas y caras. Tenían
grandes antenas, multitud de teclas, pantallas LeO de escala de grises y parecían saca-
das del plató de una película de ciencia ficción.
Desde esos austeros inicios, mis horizontes móviles se ampliaron cuando Palm Pilot
se convirtió en el dispositivo de moda a mediados de los 90. Mi primer proyecto impor-
tante PalmOS consistió en desarrollar una biblioteca de comunicaciones IrOA para una
aplicación que imprimía calendarios, contactos y listas de tareas. Por aquel entonces, las
impresoras disponían de un puerto IrOA. Irónicamente, siempre disfruté con el diseño
del software más que con el uso de los propios dispositivos.
Adelantemos diez años y me veo con el privilegio de trabajar en proyectos de soft-
ware móvil para distintos clientes. Gran parte de mi carrera actual tiene su origen en
mis primeras experiencias con el desarrollo móvil, algo por lo que estaré eternamente
agradecido. Me encanta plantearme retos que, en la mayoría de los casos, suelen ser
factibles. En especial, me atrae ayudar a cambiar el funcionamiento de las empresas o
cómo se resuelven los problemas a través de la aplicación de software móvil. La tecno-
logía móvil seguirá cambiando nuestra forma de vida, de trabajo y de ocio... lo que nos
lleva a Android y a este libro.
En otoño de 2007, me encontré con mi amigo Troy Mott, que causalmente es editor
de esta editorial. Hablamos sobre el mercado de los móviles, algo en lo que llevábamos
inmersos durante años. Y se nos ocurrió escribir un libro sobre Android. El desafío
era que Android no existía. Por cierta información previa sabíamos que la plataforma
.. Prefacio

prometía ser abie rta, capaz y popular. Pensamos que tales ingredientes permitirían crear
una obra interesante y útil, de modo que empezarnos a pensar en qué aspecto tendría el
libro, confiados en que la plataforma llegaría a ver la luz.
Nos convencimos (y a la editorial) de que era una buena idea y comenzamos a tra-
bajar en ella a principios de 2008. Además de los retos habituales de confeccionar un
libro, existía el obstáculo adicional de que la tecnología se encontraba en una fase de
cambios estables pero impredecibles, desde el año anterior. Básicamente, hemos escrito
dos veces este libro porque el SDK ha cambiado en varias ocasiones y porque han apa-
recido teléfonos equipados con Android, lo que ha acelerado el interés y la demanda de
la plataforma. Siempre que se producía un cambio significativo, teníamos que revisar
partes del libro y, en ocasiones, volver a escribir capítulos enteros para acomodar las
novedades de la plataforma.
y digo "teníamos" porque en la creación de este libro , Troy y yo decidimos compar-
tir la diversión con dos experimentados autores que contribuyeron con su experiencia
y entusiasmo por la plataforma. Ha sido un verdadero placer conocer y trabajar con
Charlie Collins y Robi Sen.
Mientras yo me dediqué a la parte 1 y III del libro, Charlie y Robie escribieron la
parte 11, dedicada a la creación de aplicaciones de Android. Gracias a su colaboración,
tuve la libertad de expresar mi punto de vista sobre la importancia de Android para el
espacio móvil en la primera parte del libro, para después centrarme en aplicaciones más
avanzadas.
Esperamos que disfrute la lectura de este libro y que se convierta en un recurso de
utilidad para el futuro de la plataforma Android.

Frank Belson
troducción
El libro que tiene entre manos no es un texto de iniciación ni tampoco un manual
de referencia con multitud de detalles. El texto sirve tanto para los usuarios sin expe-
riencia con Android como para programadores avezados que buscan comercializar sus
aplicaciones en Android Market. El libro abarca importantes temas iniciales como la de-
finición de Android, y la instalación y uso del entorno de desarrollo. Tras ello, avanza
a ejemplos prácticos de temas fundamentales de programación que todo programador
agradecerá tener en su estantería como referencia. La parte final del libro presenta dos
aplicaciones avanzadas, incluido un servidor basado en la Web. También se analiza una
aplicación C.
El libro está diseñado para leerse de principio a fin, ya que los capítulos se basan
unos sobre otros. No obstante, si lo que busca es una colección de ejemplos prácticos,
este manual le resultará especialmente útil, en concreto la segunda parte, en la que se
detallan los principales subsistemas y temas con ejemplos prácticos.

El público
Este libro se ha escrito tanto para programadores profesionales como para aficiona-
dos. Muchos de los conceptos se pueden comprender sin conocimientos concretos del
lenguaje Java, aunque los lectores con experiencia en Java encontrarán que Android les
resulta de mayor valor. Los lectores con conocimientos sobre programación con C, C++
o C# también podrán seguir los ejemplos.
.. Introducción

Una experiencia previa con Eclipse es aconsejable, aunque no imprescindible.


Encontrará multitud de recursos sobre Java y Eclipse para aumentar los contenidos
presentados en el libro .

Estructura
El libro se divide en tres partes. En la primera se incluye material introductorio sobre
la plataforma y el entorno de desarrollo. En la segunda se detallan los conocimientos
necesarios para crear aplicaciones de Android. En la tercera parte se presenta una apli-
cación más completa y una aplicación C de Android.

Parte 1: Fundamentos
En la primera parte del libro se presenta la plataforma Android, incluida la arquitec-
tura y la configuración del entorno de desarrollo.
El capítulo 1 describe la posición de la plataforma, y se compara con otras platafor-
mas conocidas como BlackBerry, iPhone y Windows Mobile. Tras la introducción, se
presenta la arquitectura de nivel superior de las aplicaciones de Android y el entorno
del sistema operativo.
El capítulo 2 incluye un ejercicio pormenorizado que ilustra cómo utilizar el entorno
de Android, con las herramientas y conceptos clave para crear una aplicación. Si nunca
ha utilizado Eclipse ni ha creado una aplicación de Android, este capítulo le servirá de
preparación para la siguiente parte del libro.

Parte 11: El entorno de programación


La segunda parte del libro incluye un amplio repaso de los principales temas de pro-
gramación del entorno de Android.
En el capítulo 3 se analizan los componentes principales de la interfaz gráfica de
Android, como View y Layout. También se detalla el elemento Activity. Son los
cimientos para crear pantallas y aplicaciones en la plataforma. Además, se presentan
conceptos básicos como el procesamiento de recursos externos, eventos y el ciclo vital
de una aplicación de Android.
El capítulo 4 amplía los conceptos presentados en el capítulo previo y se presenta
Inten t para demostrar la interacción entre pantallas, actividades y aplicaciones. Además,
se presenta y utiliza el elemento Servi c e, y su relación con los procesos de fondo.
En el capítulo 5 se incorporan métodos y estrategias para almacenar y recuperar
datos de forma local. Se describe el uso del sistema de archivos, bases de datos, tar-
jetas SO y entidades específicas de Android como las clases SharedPreferences y
ContentProvider. Comenzamos a combinar conceptos fundamentales con detalles
del mundo real como el procesamiento del estado de la aplicación, el uso de una base
de datos para almacenamiento duradero y SQL.
Android. Guía para desarrolladores BJI
El capítulo 6 se centra en cómo almacenar y recuperar datos sobre la red. Se inclu-
ye un ejemplo de redes antes de adentrarnos en el uso de conceptos como el de socket.
Tras ello, avanzamos en el uso de HTTP y se describen los servicios Web (como REST
y SOAP).
En el capítulo 7 se describe la telefonía en Android, desde conceptos básicos como
creación y recepción de llamadas telefónicas, hasta otros más complejos como trabajar
con SMS. Se analizan también propiedades de telefonía y clases de ayuda.
El capítulo 8 analiza el uso de notificaciones y alarmas. Vemos también cómo notifi-
car a los usuarios de distintos eventos como la recepción de un mensaje SMS así como
la forma de gestionar y definir alarmas.
En el capítulo 9 se describen los fundamentos del API Graphics de Android y cómo
trabajar con la biblioteca OpenGL ES para crear sofisticados gráficos 2D y 3D. También
se mencionan conceptos sobre animación.
El capítulo 10 analiza la compatibilidad multimedia de Android, como por ejemplo
la reproducción de elementos multimedia y el uso de la cámara y el micrófono para
grabar archivos.
El capítulo 11 presenta los servicios basados en ubicación, junto con un ejemplo de
aplicación de mapas que combina muchos de los conceptos presentados en capítulos
anteriores. Aprendemos a utilizar las API de mapas de Android, incluidos los distintos
proveedores de ubicación y propiedades disponibles, la creación y manipulación de
pantallas relacionadas con mapas y cómo trabajar con conceptos relacionados con la
ubicación a través del emulador.

Parte 111: Combinar todas las piezas


La tercera parte del libro se divide en dos capítulos, basados en los conceptos desa-
rrollados en capítulos anteriores para crear una aplicación de mayor envergadura.
El capítulo 12 ilustra una aplicación que incluye comunicaciones de servidor, al-
macenamiento duradero, desplazamiento entre varias actividades, menús y captura
de firmas.
El capítulo 13 explora el mundo de las aplicaciones C. El SDK de Android se limita
al lenguaje Java aunque se puedan escribir aplicaciones nativas para Android. En este
capítulo veremos ejemplos de creación de aplicaciones C para Android como el uso de
bibliotecas incorporadas y comunicaciones TCP para conectar una aplicación Java a
nuestra aplicación C.

Parte IV: Apéndices


Los apéndices contienen información adicional. El apéndice A es una guía detallada
para instalar el entorno de desarrollo. Junto a un capítulo anterior, proporciona toda la
información necesaria para crear una aplicación de Android. El apéndice B ilustra la
creación de una aplicación para Android Market, un elemento importante para todo el
que desee comercializar sus aplicaciones.
.. Introducción

Convenciones
Para ayudarle a sacar el mayor partido al texto y saber dónde se encuentra en cada
momento, a lo largo del libro utilizamos distintas convenciones:

• Los nombres de archivo, URL y código incluido en texto se muestran en un tipo


de letra monoespacial.
• Los menús, sub menús, opciones, cuadros de diálogo y demás elementos de la
interfaz de las aplicaciones se muestran en un tipo de letra Arial.

En estos cuadros se incluye información importante directamente relacionada con


el texto adjunto. Los trucos, sugerencias y comentarios afines relacionados con el tema
analizado se reproducen en este formato.

Código fuente
Para desarrollar los ejemplos, puede optar por introducir manualmente el código o
también puede descargar el código fuente utilizado en el sitio Web de Anaya (http: / /
www.AnayaMultimedia. es. sección Soporte técnico, opción Complementos).

Requisitos de software
El desarrollo de aplicaciones de Android se puede realizar desde un entorno Windows
XP/Vista, Mac OS X (solamente Intel) o Linux. En los apéndices encontrará una des-
cripción detallada de cómo configurar el entorno de Eclipse junto con el complemento
ADT para Eclipse.
Parte l.
Intro
.
uccron
~

a Androi

Android promete ser una plataforma tecnológica revolucionaria en el mercado, no


sólo por la funcionalidad que ofrece, sino también por cómo ha irrumpido en dicho
mercado. En esta primera parte del libro presentamos su labor como programador de
la plataforma Android de código abierto.
Comenzaremos con un análisis de la plataforma Android y de su impacto entre los
principales agentes del mercado de las tecnologías móviles. Tras ello, pasaremos al de-
sarrollo de aplicaciones para Android con ejercicios prácticos del entorno de desarrollo
Android.
1

An
Seguramente ha oído hablar de Android. Y habrá leído sobre Android. Es el momen-
to de revelar todos sus secretos.
Android es la plataforma de software de Google y la Open Handset Alliance capaz de
revolucionar el mercado de la telefonía móvil. En este capítulo presentaremos Android,
qué es y, sobre todo, qué no es. Tras leer el capítulo, sabrá cómo se construye Android,
su posición con respecto a otros productos del mercado y sus tecnologías, además de
familiarizarse con la arquitectura de aplicaciones Android. Terminaremos con una sen-
cilla aplicación para Android.
Este capítulo inicial pretende responder a preguntas básicas sobre qué es Android y
su posición en el mercado actual. Aunque se incluyen ejemplos de código, no son excesi-
vamente complejos, sino que pretenden presentar el desarrollo de aplicaciones Android
e ilustrar sus conceptos clave. Además del análisis contextual presentado en el capítulo,
el objetivo de este libro es definir las prestaciones de Android y que se sienta inspirado
para liberar el potencial de la telefonía móvil del futuro .

Presentación de Android
Android es la primera plataforma de código fuente para aplicaciones móviles con
posibilidad de adecuarse a diferentes mercados. A la hora de examinar Android, existen
diferentes aspectos técnicos y de mercado que tener en cuenta. En este primer apartado
presentaremos la plataforma y definiremos el contexto necesario para que comprenda
Android y su posición en la escena global de la telefonía móvil.
.. 1. Android

Android es un producto de Google, en concreto de la Open Handset Alliance, una


alianza formada por aproximadamente 30 organizaciones dispuesta a instaurar una te-
lefonía abierta y de mejor calidad en el mercado. En su sitio Web afirman que Android
se ha diseñado con el objetivo explícito de constituirse en la primera plataforma abierta,
integral y gratuita creada específicamente para dispositivos móviles. Como veremos en
este apartado, "abierta" es algo positivo, como "integral", pero "gratuita" es un objetivo
ambicioso. Existen muchos ejemplos de productos gratuitos en el mercado informáti-
co, como licencias gratuitas, pero con un coste de propiedad en asistencia y hardware.
y los teléfonos móviles "gratuitos" suelen incluir contratos anuales más impuestos.
Independientemente de los detalles, la aparición de Android es un acontecimiento re-
volucionario en el mercado y seguramente se convierta en un factor determinante en el
sector de la tecnología móvil.
Una vez descubiertos los responsables de Android y el objetivo básico de la Open
Handset Alliance, analizaremos la plataforma y su posición en el mercado.

La plataforma Android
Android es un entorno de software creado para dispositivos móviles. No es una
plataforma de hardware. Incluye un sistema operativo basado en Linux, una completa
interfaz de usuario, aplicaciones, bibliotecas de código, estructuras para aplicaciones,
compatibilidad multimedia y mucho más. ¡Incluso funcionalidad de teléfono móvil!
Aunque los componentes del SO subyacente se escriban en C o C++, las aplicaciones para
Android se diseñan en Java. Incluso las aplicaciones incorporadas son de Java. Excepto
los ejercicios sobre Linux que veremos en un capítulo posterior, todos los ejemplos de
código del libro se han creado en Java con el SDK de Android.
Una característica de la plataforma Android es que no existen diferencias entre las
aplicaciones incorporadas y las creadas con el SDK. Esto significa que se pueden crear
completas aplicaciones para aprovechar los recursos disponibles en el dispositivo. La
figura 1.1 muestra la relación entre Android y el hardware sobre el que se ejecuta. Puede
que lo más notable de Android sea su naturaleza de código abierto: la comunidad de
desarrolladores proporciona los elementos de los que carece. El SO basado en Linux no
incorpora un entorno sofisticado pero como la plataforma es abierta, se puede modificar.
Del mismo modo, se pueden obtener codees multimedia de terceros y no es necesario
depender de Google para disfrutar de nuevas funciones. Es el poder de una plataforma
de código abierto traducido en el mercado de la telefonía móvil.
El mercado de la tecnología móvil es muy cambiante y sus agentes tienen diferentes
objetivos. Consideremos la extraña relación entre operadores, fabricantes y distribui-
dores de software. Los operadores de telefonía móvil quieren bloquear sus redes, para
controlar y medir el tráfico. Los fabricantes de dispositivos desean diferenciarse a través
de funciones, fiabilidad y precios. Los distribuidores de software desean un acceso sin
trabas para proporcionar aplicaciones de primer nivel. Si a esto le unirnos una base de
exigentes clientes, tanto consumidores como corporaciones, adicta al teléfono gratuito y
operadores que recompensan el cambio de servicio pero no la lealtad del cliente. El mer-
cado se convierte en un abanico de ofertas confusas y también en un peligroso ejercicio
Android. Guía para desarrolladores . .

fiscal para los participantes, como sucede con el vendedor de teléfonos que descubre los
entresijos del sector y simplemente de sea salvarse de la vorágine. También han evolu-
cionado las expectativas por parte de los usuarios. La figu ra 1.2 muestra la concepción
de la tecnología mó vil y cómo ha madurado en los últimos años.

r
Entorno de software de Android

Aplicaciones personaliza das y


prede lerminadas escritas
en Java

M áqu ina v irtual Dalvik

Núcleo de Linux

[2] 0 0
0 0 0
0 0 0
c::J 0 0
Figura 1.1. Android es software. Con su núcleo de Linux como interfaz
para el hardware, se puede ejecutar en numerosos dispositivos de diversos
proveedo res de telefonía móvil. Las aplicaciones se crean en Java .

Busca Teléfono
Teléfono Organizador
Organizador Portátil
Portátil Acceso limitado a Internet
Sin acceso a Internet Reproductor de música
Reproductor de música

La madurez de la experiencia móvil

Teléfono
Portátil

<-------'
Teléfono inteligente
Acceso mínimo a Intemet
Portátil opcional Compatib ilidad con MP3

Figura 1.2. El trabajador móvil se complace con la reducción de dispositivos


que utilizar. Las funciones de los dispositivos móviles han convergido a un ritmo
elevado. Los ordenadores portátiles se han convertido en un producto opcional.
IIIII 1. Android

A lo largo del libro, siempre que haya que probar o ejecutar código en un dispositivo,
se utiliza un emulador basado en software. En un capítulo posterior encontrará más
información sobre cómo configurar y utilizar el emulador de Android.
El término plataforma se refiere a Android, el software , incluidos todos los binarios,
bibliotecas de códigoy cadenas de herramientas. Este manual se centra en la plataforma
Android. Los emuladores Android disponibles en el SDKson simplemente uno de los
muchos componentes de la plataforma.

Con este panorama, la creación de una plataforma móvil de éxito es una tarea compleja
que implica la presencia de diversas partes. Android es un proyecto ambicioso, incluso
para Google, una empresa con recursos aparentemente ilimitados. Si alguien puede re-
volucionar el mercado de los móviles, es Google y su candidato, Android.
En el siguiente apartado analizaremos los porqués de Android para definir su con-
texto y establecer su aparición en el mercado. Tras ello, analizaremos la plataforma.

El mercado de Android
Android promete tener algo para todo el mundo. Su objetivo es admitir diversos dis-
positivos de hardware, no sólo los más avanzados que suelen asociarse a los costosos
teléfonos inteligentes. Evidentemente, Android funcionará mejor en dispositivos más
potentes, en especial si tenemos en cuenta que incluye completas funciones informáticas.
La verdadera pregunta es cómo puede adaptarse a los distintos mercados y conseguir
una cuota aceptable. En este apartado veremos la posición de Android desde la pers-
pectiva de los distintos agentes existentes en el me rcado. Cuando hablamos de telefonía
móvil, hay que empezar desde la cima, con los operadores móviles.

Operadores móviles
El negocio de los operadores móviles se centra, básicamente, en conseguir contratos de
sus servicios. Los accionistas desean recuperar sus inversiones y cuesta imaginar un sector
industrial donde haya mayor inversión que en una red de tal amplitud geográfica. Para
los operadores de telefonía móvil, los teléfonos son, al mismo tiempo, un medio para sus
servicios, un atractivo para los clientes y una verdadera molestia a la hora de mantener.
La visión optimista de Android ante la respuesta de los operadores es que lo aco-
gerán con los brazos abiertos como plataforma para impulsar nuevos servicios a través
de sus redes. Los servicios de datos representan servicios premium y un gran margen
de beneficios para el operador. Si Android consigue encauzar dichos beneficios hacia el
operador, mucho mejor.
La visión pesimista es que el operador se siente amenazado por Google y por el poten-
cial de los servicios inalámbricos gratuitos impulsados por ingresos publicitarios. Otro reto
con los operadores de telefonía es que pretenden tener la última palabra sobre qué servicios
habilitar en su red. Históricamente, una de las reclamaciones de los fabricantes de teléfo-
nos era que sus dispositivos no desarrollaban todas las prestaciones que incluían debido
Android. Guía para desarrolladores ..

a la falta de disponibilidad de los operadores para admitirlas. Un gesto esperanzador es la


presencia de algunos operadores en la Open Handset Alliance. Pero basta de conjeturas;
pasemos a una comparativa de Android y los teléfonos móviles actuales del mercado.

Android frente a teléfonos tradicionales


La gran mayoría de teléfonos móviles del mercado son de formato plegable y con
funciones tradicionales. Son los que obtienen los clientes al entrar en una tienda y pedir
un teléfono gratuito. Únicamente están interesados en comunicaciones por voz y puede
que en la agenda. Incluso pueden necesitar una cámara. Muchos de estos teléfonos in-
corporan funciones adicionales como navegación Web pero debido a la escasa facilidad
de uso, apenas se emplean. La única excepción son los mensajes de texto, una aplicación
omnipresente independientemente del tipo de dispositivo. Otra categoría cada vez más
solicitada es la de servicios basados en ubicación o GPS.
El reto de Android consiste en acercarse a este mercado. Algunas de sus funciones pue-
den ignorarse para adecuarse a este hardware de nivel inferior. Una de las grandes caren-
cias de estos teléfonos es la experiencia Web. En parte se debe al tamaño de la pantalla pero
también a la tecnología de los navegadores que no suele alcanzar la completa experiencia
que ofrecen las versiones de escritorio. Android incluye el motor de navegación WebKit,
líder del mercado, que equipara la navegación de escritorio en el móvil. La figura 1.3 mues-
tra un ejemplo. Si se puede adaptar de forma eficaz a los teléfonos móviles tradicionales,
Android avanzaría considerablemente en su asentamiento en este mercado.

Figura 1.3. La tecnología de navegación que incorpora Android se basa en el motor WebKit.
.. 1. Android

El motor de navegación WebKit (http://www . webkit. org) es un proyecto de código


abierto que podemos encontrar en el navegador Safari de Macintosh y en Mobile Safari,
el navegador del iPhone. No sería exagerado afirmar que la popularidad del iPhone se
debe a su experiencia de navegación, de modo que su inclusión en Android es un gran
avance en la arquitectura de la plataforma.

En este sector del mercado, el software se divide en dos grandes grupos:


• Entorno BREW de Qualcornrn: BREW equivale, en inglés, a Entorno Inalámbrico
de Ejecución Binario. Un ejemplo de esta tecnología serían los dispositivos Get
It Now de Verizon. El reto del programador de software que aspira a acceder a
este mercado es que el listón para conseguir una aplicación para esta plataforma
está demasiado alto ya que el operador de telefonía móvil lo controla todo, con
costosas estructuras de pruebas y reparto de ingresos. La ventaja para esta plata-
forma es que el operador recauda los ingresos y los reparte al programador tras la
ventana, que suele producirse mensualmente. Pero el resto sigue siendo un reto.
El entorno de aplicaciones abiertas de Android es más accesible que BREW.
• J2ME o Java Micro Edition: Una plataforma muy conocida para estos dispositivos.
La barrera de entrada es mucho menor. Los programadores de J2ME encontrarán
un entorno similar en Android que no es una plataforma estrictamente compatible
con J2MBpero su entorno de programación Java es una ventaja adicional. Además,
es probable que en el futuro Android incluya compatibilidad con J2ME.
Los juegos, un navegador mejorado y todo lo relacionado con aplicaciones de texto o
sociales constituyen un terreno fértil para Android en este sector del mercado. Aunque
el gran público utilice los teléfonos móviles descritos en este apartado, las prestaciones
de Android resultan idóneas para los dispositivos de mayor calidad, como veremos a
continuación.

Android y teléfonos inteligentes


Los lideres del mercado en la carrera de los teléfonos inteligentes son Windows
Mobile/SmartPhone y BlackBerry, además de Symbian (muy popular fuera de Estados
Unidos), iPhone y Palmo Aunque podríamos centramos en la cuota de mercado y en las
ventajas e inconvenientes de cada plataforma, una de las principales preocupaciones es
la capacidad de una plataforma para sincronizar datos y acceder a sistemas de informa-
ción para usuarios corporativos. Las herramientas de gestión de dispositivos también
son un factor importante en el mercado empresarial. La experiencia de navegación es
mejor que con los teléfonos tradicionales, básicamente por el tamaño de las pantallas y
los métodos de entrada intuitivos, como las pantallas táctiles.
La oportunidad de Android en este mercado es su capacidad para ofrecer un mayor
rendimiento en el mismo hardware y con un coste menor de adquisición de software. El
reto al que se enfrenta Android es el mismo de Palm, escalar los muros empresariales.
Android. Gl/ía para desarrolladores ..

BlackBerry se encuentra en una posición dominante debido a sus intuitivas funciones


de correo electrónico y las plataformas de Microsoft resultan atractivas por su firme in-
tegración con los equipos de escritorio y la familiaridad para los usuarios de Windows.
Por último, el iPhone ha experimentado un éxito sin precedentes como dispositivo in-
tuitivo y versátil, con multitud de aplicaciones de software disponibles.
En el siguiente apartado nos plantearemos si Android, la plataforma móvil de código
abierto, puede triunfar como proyecto de código abierto.

Android frente a Android


Puede que el mayor desafío de todos sea el compromiso por parte de Android al
código abierto. Al provenir de Google, es probable que siempre sea un proyecto de có-
digo abierto pero para poder triunfar en el mercado de la telefonía móvil, debe vender
millones de unidades. No es el primer teléfono de código abierto pero sí el primero con
el respaldo de un gigante como Google.
El código abierto es un arma de doble filo. Por un lado, el poder de individuos y em-
presas de todo el mundo que se esfuerzan continuamente por ofrecer las funciones de-
seadas, una iniciativa que tener en cuenta, sobre todo si lo comparamos con el enfoque
tradicional y comercial del desarrollo de software. Es un tema poco novedoso, ya que
los beneficios del desarrollo de código abierto están bien documentados. El otro lado de
la ecuación es que, sin una base estable de código centralizado, Android podría perder
la masa crítica que necesita para irrumpir en el mercado de la telefonía móvil. Tomemos
Linux como alternativa al sistema operativo Windows. Linux ha disfrutado de un tremen-
do éxito; se encuentra en muchos sistemas operativos, en dispositivos como enrutadores
y conmutadores, y en multitud de plataformas móviles como Android. Existen diver-
sas distribuciones para equipos de escritorio e, irónicamente, esta variedad de opciones
es lo que ha retrasado su adopción como alternativa de escritorio a Windows. Linux es
sin duda el proyecto de código abierto de mayor éxito; como alternativa de escritorio
a Windows, se ha fragmentado, lo que ha dificultado su entrada en el mercado como
perspectiva de producto. Como ejemplo del diluido mercado de Linux, le mostramos
una relación de distribuciones:
• Ubuntu.
• openSUSE.
• Fedora (Red Hat).
• Debian.
• Mandriva (antes Mandrake).
• PCLinuxOS.
• MEPIS.
• Slackware.
• Gentoo.
• Knoppix.
lIl!II 1. Android

La lista contiene una muestra de las distribuciones Linu x de software de escritorio


más conocidas. ¿Cuánta gente conoce que utilice Linux como sistema operativo de es-
critorio principal y, en caso afirmativo, utilizan todos la misma versión? No basta con el
código abierto; Android deb e centrarse como producto y no diluirse para penetrar en el
mercado con sentido. Es el de safío clásico entre com ercialización y código abierto. Es el
ret o de Android, entre otros, ya que debe demostrar su poder y su capacidad para escala r
de sde el operador de telefonía móvil al distribuidor de softw are, e incluso al vendedor.
La fragmentación en diferentes distribuciones no supondría el éxito como teléfono móvil
de este tipo de producto de consumo. El modelo de licencia de los proyectos de código
abierto puede ser difuso. Algunas licencias de softw are son más restrictiva s que otras y
dichas restricciones son un des afío para la etiqueta de código abierto. Al mismo tiempo,
los titulares de licenci as de Android tendrán que proteger su inversión, de modo que es
un tema importante para la comercialización de Android.

licencias de Android
Android se comercializa bajo do s licencias de código abierto diferentes. El núcleo
de Linux se comercializa bajo la licencia GPL, como exige todo núcleo de SO de código
abierto. La plataforma Android, sin el núcleo, tiene una licencia ASL (Apache Software
License). Aunque ambos modelos de licencia están orientados al código abierto, la princi-
pal diferencia es que la licencia de Apache se considera más proclive al uso comercial.

Vender aplicaciones
Una plataforma móvil es útil sólo si existen aplicaciones que utilizar y disfrutar en dicha
plataforma. Para ello, la compra y venta de aplicaciones para Android es importante y
nos permite resaltar una diferencia clave entre Android e iPhone. La tienda de Apple
contiene software para el iPhone. Sin embargo, el férreo control de Apple sobre el
mercado de software par a iPhone hace que todas las aplicaciones se vendan a través de
su tienda . Esto genera una dificultad para los programadores de software, que pueden
preferir ofrecer sus aplicaciones a través de otros canales.
Al contrario del enfoque de Apple, el programador de Android disfruta de libertad
para ofrecer aplicaciones a trav és de canales tradicionales como sitios de freeuiare
y shareuiare, y diferentes mercados comerciales, incluido su propio sitio Web. Para
editores de software que deseen centrarse en la venta de dispositivos, Googleha lanzado
el Android Market. Para programadores de software que ya dispongan de títulos par a
otras plataformas como Windows Mobile, Palm o BlackBerry, mercados tradicionales
como Handango (www. han da ngo. c oro)tambiénvenden aplicacionesdeAndroid.Es
importante, ya que los nuevos consumidores pueden visitar sitos como Handango, donde
pueden haber comprado sus aplicaciones favoritas para otros dispositivos.

Algunos puristas del códi go abierto se opondrán a todo lo que no sea una apertura
total, que se comparta el códi go y que no exista com ercialización alguna. ASL intenta
equilibr ar los objetivos del código abierto con las fuerzas comerciales del mercado. Si
A ndroid . Gl/ía para desarrolladores ...

no existe un incentivo financiero para ofrece r al mercado dispositivos compatibles con


Android, nunca existirá la cantidad suficiente de dispositivos necesarios para impulsar
Android de la forma adecuada.
Hemos concluido la sección más árida del libro. A continuación nos centraremos en el
desarrollo de aplicaciones de Android. Todo an álisis técnico de un entorno de software
debe incluir una presentación de los niveles que lo componen, lo que recibe el nombre
de pila, debido a su estructura de capas. En el siguiente apartado an alizaremos los com-
ponentes de la pila de Android.

Componentes de Android
Android incluye una impresionante variedad de funciones pa ra aplicaciones mó-
viles . De hecho, si analizamos únicamente la arquitectura, sin el contexto de Android
como plataforma diseñada para entornos móviles, podríamos confundirlo con un en-
torno informático general. Los principales componentes de una plataforma informática
están presentes y se leen como un Quién es Quién de la comunidad de código abierto.
A continuación presentamos los principales componentes de Android:

• Un núcleo Linux que proporciona una capa de abstracción de hardware básica


así como servicios como gestión de procesos, memoria y sistema de archivos . En
el núcleo se implementan controladores de hardware específicos, funciones como
Wi-Fi y Bluetooth. La pila de Android tien e un diseño flexible, con diferentes
componentes opcionales qu e dependen de la disponibilidad de ha rd wa re concreto
en cada dispositiv o, como p or ejemplo pantallas táctiles, cámaras, recep tore s GPS
o acelerómetros.
• Entre las principales bibliotecas de códi go destacan las siguientes:
• Tecnología de navegador de WebKit, el mismo motor de código abierto de
Safari de Macintosh y del navegador Mobil e Safari del iPhone.
• Compatibilidad con bases de datos a través de SQLite, una base de datos SQL
sencilla de utilizar.
• Compatibilidad grafica avanzada, con 2D, 3D, animación de SGL y OpenGL
ES.
• Compatibilidad con audio y vídeo a través de OpenCore de Packet Video.
• Funciones SSL del proyecto Apache.
• Diferentes administradores de servicios para:
• Actividades y vistas.
• Telefonía.
• Windows.
.. 1. Android

• Recursos.
• Servicios basados en ubicación.
• El entorno de ejecución de Android proporciona lo siguiente:
• Paquetes Java para obtener un entorno de programación Java prácticamente
completo. No es un entorno J2ME.
• La máquina virtual Dalvik utiliza servicios del núcleo basado en Linux para
proporcionar un entorno de alojamiento para las aplicaciones Android.

Las aplicaciones básicas y las de terceros (como las creadas en este libro) se ejecutan
en la máquina virtual Dalvik, sobre los componentes anteriores. La figura 1.4 muestra
la relación entre todos estos niveles.

Aplicaciones de usuario : Contactos, teléfono, navegador, etc.

Administradores de aplicaciones: Windows, contenido,


actividades, telefonla , ubicación , notificac iones , etc.

ITiempode ejecución de Android: Java a través de la MV DalvikI


Bibliotecas : gráficos , multimedia , base de datos,
I comunicaciones , motor de navegación , etc. I
Núcleo de Linux, con controladores de dispositivos

Dispositivo de hardware con funciones concretas como


GPS, cámara , Bluetooh, etc.

Figura 1.4. La pila de Android ofrece una impresionante variedad de tecnologías y funciones .

El desarrollo de Android requiere conocimientos de programación de Java, sin duda


alguna. Para aprovechar al máximo este libro, debe desempolvar sus conocimientos
sobre ello. En Internet encontrará abundante material sobre Java , así como multitud
de libros en el mercado.

Una vez descrito el diagrama y presentados sus niveles, nos centraremos en la tec-
nología de tiempo de ejecución de Android.

los cimientos de Android


Android se basa en un núcleo de Linux y en una avanzada máquina virtual optimi-
zada para sus aplicaciones de Java. Ambas tecnologías son esenciales para Android. El
núcleo de Linux ofrece agilidad y portabilidad para aprovechar las numerosas opcio-
nes de hardware de los futuros teléfonos equipadoscon Android. El entorno Java de
Android es fundamental: hace que Android sea accesible para los programadores debido
Android. Guía para desarrolladores ..

al número de desarrolladores de software para Java y del completo entorno que ofrece
la programación con Java . Las plataformas móviles que han dependido de entornos de
programación menos accesibles se han adoptado en menor medida debido a la falta de
aplicaciones y el alejamiento de los programadores de las mismas.

El núcleo de Linux
Se preguntará por qué utilizar Linux en un teléfono. El uso de una plataforma tan
completa como Linux proporciona gran potencia y funciones a Android. El uso de una
base de código abierto desata la capacidad de individuos y empresas para impulsar la
plataforma. Es especialmente importante en el mundo de los dispositivos móviles, donde
los productos cambian con tanta rapidez. La velocidad de cambio en el mercado de la
telefonía hace parecer lento al sector de la informática general. Y, además, el núcleo de
Linux es una plataforma demostrada. En un teléfono móvil, la fiabilidad es más impor-
tante que el rendimiento, ya que se utiliza principalmente para comunicaciones por voz.
Todos los usuarios de móviles, ya sea para uso personal o empresarial, exigen fiabilidad
de voz, pero también funciones atractivas y adquieren sus dispositivos en función de
dichas funciones. Linux puede contribuir a cumplir este requisito.
Hablando de la rapidez con que aparecen accesorios para teléfonos, otra ventaja del
uso de Linux como base de Android es que proporciona un nivel de abstracción de hard-
ware lo que permite conservar los niveles superiores independientemente de los cambios
realizados en el hardware subyacente. Evidentemente, el diseño de código demanda que
las aplicaciones no fallen en caso de que falte un recurso, como por ejemplo la ausencia
de una cámara en un modelo concreto. Con la aparición de nuevos accesorios en el mer-
cado, se pueden crear controladores en el nivel de Linux para proporcionar asistencia,
como sucede en otras aplicaciones de Linux.
Las aplicaciones de usuario, así como las aplicaciones básicas de Android, se escriben
en Java y se compilan en código de bytes, que se interpretan en tiempo de ejecución por
medio de una máquina virtual.

Ejecutar la máquina virtual Oalvik


La máquina virtual Dalvik es un ejemplo de las necesidades de eficacia, el deseo de .un
entorno de programación completo e incluso el enfrentamiento de ciertas restricciones de
propiedad intelectual, dando todo ello como resultado la innovación. El entorno Java de
Android proporciona una completa plataforma de aplicaciones y resulta muy accesible
debido a la popularidad del propio lenguaje Java. Además, el rendimiento de las aplica-
ciones, en especial en entornos de memoria reducida como son los teléfonos, es impres-
cindible para el mercado de la telefonía móvil, aunque no sea el único problema.
Android no es una plataforma J2ME. Sin comentar si esto es positivo o no, existen
otras fuerzas presentes. Está el problema de la licencia de la máquina virtual de Java de
Sun Microsystems. El entorno de código de Android es java, Las aplicaciones se escri-
ben en Java y se compilan en código de Java para después traducirse a una representa-
ción diferente denominada archivos dex. Estos archivos son los equivalentes lógicos de
los códigos de bytes de Java pero permiten que Android ejecute sus aplicaciones en su
II!II 1. Android

propia máquina virtual que no depende de las licencias de Sun (aunque se podría ar-
gumentar) yen una plataforma abierta sobre la que Google, y la comunidad de código
abierto, puede mejorar los aspectos necesarios.

Nota

Todavía es pronto para afirmar si se producirá un gran enfrentamiento entre la Open


Handset Alliance y Sun sobre el uso de Java en Android. Desde la perspectiva del progra-
mador de aplicaciones móviles, Android es un entorno de Java; sin embargo, el entorno
de ejecución no es estrictamente una máquina virtual de Java, lo que explicaría las
incompatibilidades entre Android y los entornos y bibliotecas "puramente" de Java.

El dato que hay que recordar sobre la máquina virtual Dalvik es que las aplicaciones
de Android se ejecutan en su interior y que depende del núcleo de Linux para servicios
como procesadores, memoria y administración de sistemas de archivos.
Tras el análisis de las tecnologías básicas de Android, nos centraremos en el desarro-
llo de aplicaciones. En el resto del capítulo veremos la arquitectura de aplicaciones de
Android y presentaremos lID sencillo ejemplo. Si no se siente cómodo para empezar a
diseñar código, puede pasar al siguiente capítulo, en el que presentamos el entorno de
desarrollo paso a paso.

Iniciar el desarrollo de Android


En este apartado nos adentramos en el desarrollo de Android para centrarnos en un
componente importante de la plataforma y después analizar en un sentido más amplio
la creación de aplicaciones Android. Un elemento habitual del desarrollo de Android es
Intent, que describe lo que queremos hacer. Puede ser desde buscar el registro de un
contacto, abrir un sitio Web o mostrar la pantalla de confirmación de pedido. Los elemen-
tos Intent son importantes ya que no sólo facilitan la navegación de forma novedosa
como veremos a continuación, sino que también representan el aspecto más relevante
del código de Android. Si comprende Intent, comprenderá Android.

Nota
En uno de los apéndices se incluyen instrucciones para configurar el entorno de desarrollo
de Android, que utilizaremos en todos los ejemplos del libro. En un capítulo posterior se
describe detalladamente la configuración y uso de las herramientas de desarrollo.
Los ejemplos de código de este capítulo son meramente ilustrativos. Se hace referencia
a las clases presentadas sin mencionar necesariamente paquetes de Java concretos. En
capítulos posteriores adoptaremos un enfoque más riguroso para introducir paquetes
y clases específicos de Android.
Android. GIIÍa para desarrolladores lIliI
En el siguiente apartado se incluye información básica sobre la importancia de Intent
y se describe su funcionamiento. Además, se describen los principales elementos del de-
sarrollo de aplicaciones Android para crear nuestra primera aplicación completa.

Objetivos de Android
El poder de la estructura de aplicaciones de Android radica en la forma de traducir
la Web en aplicaciones móviles. No significa que la plataforma disponga de un potente
navegador y que esté limitada a JavaScript y a recursos del lado del servidor, sino que
es la base de su funcionamiento y de la interacción del usuario con el dispositivo móvil.
El poder de Internet, si lo quisiéramos reducir a una sola frase , es que todo se encuentra
a un clic de distancia. Estos clics se denominan URL (Localizador Uniforme de Recursos) o
URl (Identificador Uniforme deRecursos). El uso de VRl eficaces permite un acceso rápido
y sencillo a toda la información que necesitan los usuarios.
Además de una forma eficaz de acceso a datos, se preguntará qué relación tienen los
URl con Intent. La respuesta no es técnica sino crucial : la forma de navegar de un usua-
rio móvil en la plataforma es fundamental para su éxito comercial. Las plataformas que
reproducen la experiencia del escritorio en un dispositivo móvil son aceptadas por una
parte de los usuarios. En el mercado de la telefonia móvil, los menús complejos, la repeti-
ción de pulsaciones y clics no son bien recibidos. Este mercado, más que ningún otro, exige
facilidad de uso . Aunque un consumidor puede adquirir un dispositivo por el atractivo de
sus prestaciones, los manuales de instrucciones apenas se consultan. La facilidad de uso
de la interfaz de usuario de un entorno informático está relacionada con su penetración de
mercado. Las IV también reflejan el modelo de acceso a datos de la plataforma, de modo
que sin los modelos de navegación y datos son intuitivos, también lo será la IV. En este
apartado presentamos el concepto de Intent e IntentFil ters, el innovador mecanismo
de navegación y ejecución de Android. Estos elementos traducen el paradigma de "hacer
clic" al uso de aplicaciones móviles (y su desarrollo) para la plataforma Android.

• Intent es una declaración de necesidades.


• IntentFil t er es una declaración de capacidad e interés por ofrecer asistencia
a los que la necesitan.
• Un elemento Intent está formado por fragmentos de información que describen
la acción o servicio deseados. En este apartado veremos la acción solicitada y, de
forma genérica, los datos que acompañan a la misma.
• Un elemento IntentFil ter puede ser genérico o específico con respecto a los
elementos Intent a los que presta su servicio.

El atributo de acción de Intent suele ser un verbo, por ejemplo VIEW, PICK o EDIT.
Se definen diferentes acciones Intent como miembros de la clase Intent. Los progra-
madores de aplicaciones también pueden crear nuevas acciones. Para ver un fragmento
de información, una aplicación puede utilizar la siguiente acción Intent:
andro id .content .lntent .ACTION VIEW
lInII 1. Android

El componente de datos de In t ent se expresa como URI y puede ser cualquier frag -
mento de información, como un registro de un contacto, la ubicación de un sitio Web o
una referencia a un clip multimedia. La tabla 1.1 muestra varios ejemplos de URI .

Tabla 1.1. URI utilizados en Android .

Búsqueda de contactos c o nte n t ://con tac ts/ people


Búsqueda de mapas Geo:O ,O?q=23+Rou t e+2 06+Stan hope +NJ
Abrir navegador en un sitio Web concreto http : / / www. g o o gl e . c om/

1 n t en t Fi 1 t e r define la relación entre 1 nte nt y la aplicación. Pueden ser específicos


de la parte de datos de Intent, de la parte de acción o de ambas. IntentFil te r también
contiene un campo denominado categoría. Las categorías permiten clasificar las acciones.
Por ejemplo, la categoría CATEGORY_ LAUNCHER indica a Android que la actividad que
contiene su IntentFil ter debe ser visible en la pantalla inicial de la aplicación.
Al enviar un Intent, el sistema evalúa los elementos Activi ty y Se r v i c e dispo-
nibles, y los elementos Broadca stRe ceiver registrados (como veremos más adelan-
te) y dirige el Intent al receptor más indicado. La figura 1.5 muestra la relación entre

Pasear por tntem et ¡Busca r cualquier cosa en el mapa !


(tnle nIFilter) (Inten l Filter )

startActivity(lntent);
-: Apl icación # 2 de Andro id ( Broad caslRe ceiver)

Ver, Edita r, Exam ina r contactos (InlentFr!ter)

O 1.-- Apl ica ción # 3 de Android (Broa dca stRece iver)

startActivity(lnt ent,idenlifi er) ;


O
Ac66n o dalos perso nalizados (¡ntenIA ter)
startService(lntent);
"1 Ap licación # 4 de An droid (Broadcas tReceiver)

Bus ca r una persona


r Bu scar un a di rección
(Iolent) en el mapa
(lnt ent )

Aplicaci ón # 1 de Andro id

Intent, IntentFilt.e r y BroadcastRe ceiver.

Figura 1.5. Distribución de Intent en aplicac iones de Android, que se registran


por medio de IntentFilter, generalmente en el archivo Andro idManifest.xml.

Los IntentFil ter suelen definirse en el archivo AndroidManife st. xml de la


aplicación con la etiqueta <i nt e n t - f i l ter >. Este archivo es un descriptor de la apli-
cación, como veremos más adelante.
Android. Guía para desarrolladores lIiII
Una tarea habitual en los dispositivos móviles es buscar un contacto concreto para
iniciar una llamada, enviar un SMS o localizar una dirección postal. Imaginemos que
un usuario necesita un dato concreto, por ejemplo el registro del contacto 1234. En este
caso, la acción sería ACTION_ VIEW y los datos un identificador concreto de registro de
contacto. Para ello se crea un Intent con la acción establecida en ACTION _ VIEW y un
URI que represente la persona en cuestión.
El ejemplo de URI que utilizar con la acción android . content . Intent . ACTION_
VIEW sería el siguiente:

content://contacts/peop le/ 1234

Un ejemplo de URI para obtener la lista de todos los contactos, sería:


c ont e n t : / / c on t a c t s / p e op l e

El siguiente fragmento de código ilustra la selección de un registro de contacto:

I ntent rnylntent = new I ntent (I n t e n t. ACTION_PICK , Ur i. p a r s e (" c ont en t : / /con tac ts/
p e opl e")) ;

startActiv i t y (rnyl n tent ) ;

Este elemento Intent se evalúa y pasa al controlador más adecuado. En este caso,
el receptor sería la actividad corn. googl e. android. phone. Dialer. Sin embargo,
el receptor óptimo para este Intent podría ser una acción incluida en la misma apli-
cación personalizada de Android (la que crearemos), una aplicación incorporada como
en este caso o una aplicación de terceros para el dispositivo. Las aplicaciones pueden
recurrir a funciones de otras aplicaciones mediante la creación y entrega de un Intent
que solicite código existente para procesar el Intent en lugar de escribir código desde
cero. Una de las grandes ventajas de utilizar Intent de este modo es que se usan fre-
cuentemente las mismas ID, lo que genera familiaridad en el usuario. Es especialmen-
te importante en plataformas móviles, en las que el usuario no suele ser un experto en
tecnología ni le interesa aprender varias formas de realizar la misma tarea, como por
ejemplo buscar un contacto.
Los Intent vistos hasta el momento se denominan implícitos, y dependen de
IntentFil ter y del entorno de Android para entregar el Intent al correspondiente
receptor. También existen 1 n ten t explícitos, en los que se especifica la clase exacta con
la que procesar el Intent. Resultan muy útiles cuando sabemos exactamente qué acti-
vidad procesar con dicho Intent. Para crear un Intent explícito, utilice el constructor
Intent sobrecargado, que adopta como argumento una clase:

pub lic voi d onClick (View v I {


tr y (
s ta r tActiv ity ForRes u lt (new In t en t( v. ge t Conte x t(), Refre sh J obs.cla s s),O);
) ca tch (Exc epti on e ) {
IIIIII 1. Android

Estos ejemplos muestran cómo una aplicación Android crea un Intent y solicita su pro-
cesamiento. Del mismo modo, se puede implementar la aplicación con un Intent Fil ter,
para indicar que responde a I nte nt ya creados en el sistema y, por lo tanto, se publican
nuevas funciones en la plataforma. Este aspecto será bien recibido por los distribuidores
de software independientes, que se han ganado la vida ofreciendo programas de admi-
nistración de contactos y listas de tareas para otras plataformas móviles.
La resolución de In ten t se produce en el tiempo de ejecución, no al compilar la apli-
cación, de modo que se pueden añadir funciones de procesamiento de I nten t concretos
al dispositivo, lo que puede actualizar y mejorar las funciones incluidas en el software
original. Esta resolución también se denomina vinculación tardía.

Podemos llegar a imaginar la exclusivaexperiencia de usuario que puede ofrecerAndroid


gracias a la variedad de actividades con Inten tFil te r concretos instalados en un
dispositivo. Arquitectónicamente es posible actualizar los distintos aspectos de una
instalación de Android para ofrecer sofisticadas funciones y personalización. Aunque
puede ser una característica deseada por el usuario , también puede resultar complicado
proporcionar asistencia técnica y tener que recorrer diversos componentes y aplicaciones
para resolver un problema. Debido al potencial de complejidad añadida, este enfoque
de sistema de actualizaciones a funciones específicas debe aplicarse con precaución y
siempre teniendo en cuenta los posibles problemas asociados al mismo.

Hasta el momento nos hemos centrado en la variedad de In t ent para mostrar ele-
mentos de la interfaz de usuario. También existen Intent de control de eventos, como el
ejemplo anterior del registro de contacto. Por ejemplo, la clase In t e nt también se utiliza
para notificar la recepción de un mensaje de texto. Los 1 n t ent son elementos esenciales
de Android y se mencionan a lo largo del libro. Una vez presentados los I nte n t como
catalizadores para navegación y flujo de eventos en Android, pasaremos a un examen
más amplio del ciclo vital de las aplicaciones de Android y sus componentes básicos.

Activar Android
En este apartado utilizaremos los conocimientos sobre las clases 1 n t en t e
In tentFilter para analizar los cuatro componentes principales de las aplicaciones
Android así como su relación con el modelo de procesamiento. Se incluyen fragmentos
de código para ilustrar el de sarrollo de aplicaciones. En capítulos posteriores encontrará
ejemplos más complejos.

Una aplicación de Android puede que no incluya todos estos elementos pero tendrá al
menos uno de ellos y puede tenerlos todos.
Android. Guía para desarrolladores lIII!nII

Activity
Una aplicación puede tener una interfaz de usuario o no. Si dispone de un a, tendrá
al menos un elemento Ac t i vi ty.
La for ma más sencilla d e explicar Activi ty consiste en relacionarlo con una panta-
lla visible. Por lo gene ral, una aplicación de Android puede tener más de un elemento
Activity. Cada uno muestra una IV y responde a eventos iniciados por el sistema o
por el usuario. El elemento Act i v i t y utiliza una o va rias vistas (Vi e w) para p resen-
tar al usuario los elementos de la IV. La clase Ac t i v i ty se amplía mediante clases de
usuario, como el listado 1.1 muestra.

Listado 1.1. Elemento Activity básico de una aplicación de Android .

p a c kage c om. ms i . man n i ng .c h a p te r l ;

i mp o r t a nd r o id .app .A ctivi t y ;
i mpo r t and r o id . o s . Bun dl e;

p u b lic c lass activi ty l e x t e nds Activi t y (


@Overr i de
publ i c void onCre a te (Bu n d le i c i c l e )
s u p e r . o nCrea te (ic ic le ) ;
s e tCo ntentV i e w (R . l a yo ut . ma i n ) ;

La clase Activi t y forma parte del paquete de Java android . app, del tiempo
de ejecución de Android, que se implementa en el archi vo andr o id . j aro La clase
activi t y l amplía la clase Acti vi ty. En un capítulo poste rior encontrará más ejem-
plos de Ac t i v i t y.
Un a de las principales tareas de Ac t i v i t y es mo strar elementos de IV, que se
implementan como View y se describen en archivos XML, como veremos en un ca-
pítulo posterior . Para pasar de un elemento Act i vi t y a otro se utiliza el método
sta rtAct i vi ty () o el método s t a r t Act i vi ty ForRe sul t () cuando se necesita
una llamada sincrónica o un paradigma de resultado. El argumento de estos métodos
es I nt ent.

La clase I nte nt se utiliza con la misma forma en distintos casos. Existen I nt e nt para
ayudar a desplazarse entre actividades, como en el ejemplo anterior de ver un registro
de contacto. Las actividades son los destinos de estos tipos de I nt e nt utilizados con
los métodos startActi vi ty o s t a r t Act i v i t yFo rRe sul t . Los servicios se pued en
iniciar si se pasa In tent al método startService .
BroadcastRe ce iver reciben Intent al responder a eventos del sistem a como una
llamada entrante o la recepción de un mensaje de texto.
E!II 1. Android

Acti vi t y representa un componente de aplicación visible en Android. Con ayuda


de la clase View que veremos en un capítulo posterior, Activi ty es el tipo de aplica-
ción Android más común. El siguiente elemento de interés es Service, que se ejecuta
de fondo y no suele presentar una IV directa.

Service
Si el ciclo de vida de una aplicación es prolongado, debe incluirse en un Service.
Por ejemplo, una utilidad de sincronización de datos de fondo que se ejecute de forma
continuada debe implementarse como Service.
Al igual que Activity, Service es una clase proporcionada en el tiempo de eje-
cución de Android y que debe ampliarse, como el listado 1.2 muestra, y que envía pe-
riódicamente mensajes al registro de Android.
Listado 1.2. Ejemplo de Service en Android.
pa ckage com. ms i .manni ng.chapte r l ;

import androi d .app .Service ;


i mp o r t a ndroid .os .IBinder;
im port android.util. Log ;

publ i c c lass se rvice l exte nds Se rv ice i mplements Runnable {


p ubl i c static fin al Str i ng tag = " s ervi c el ";
private in t coun te r = O;
@Overr ide
protected void onCreate ( )
super .o nCreate ( };
Th r e a d aThread = ne w Th r e a d (th i s);
a Thread .start ( ) ;

p ub l ic voi d r un()
whil e ( t rue) {
try {
Log.i (tag , "servicel f iring # " + cou nter++ );
Th r e a d . s l e e p( l OOOO);
) catch (Exception ee ) (
Lo g . e(t a g, e e . g etMe s s a g e(» ;

@Override
pub lic IB inder o nBind ( In te nt intent ) (
return n ul l;

En este ejemplo es necesario importar el paquete android. a pp . Service, que con-


tiene la clase Se r v i ce. El ejemplo también ilustra el mecanismo de registro de Android,
muy útil para tareas de depuración. Muchos de los ejemplos del libro utilizan las funciones
de registro, como veremos en un capítulo posterior. La clase servic el amplía la clase
Android. Guía para desarrolladores EII
Service, que también implementa la interfaz Runnable para realizar su tarea principal
en un subproceso independiente. El método onCreate de la clase Service permite que
la aplicación realice tareas de inicialización. El método onBind () se analizará con mayor
detalle en un capítulo posterior, cuando veamos la comunicación entre procesos.
Los servicios se inician con el método startService (Intent) de la clase abs-
tracta Contexto De nuevo se utiliza Intent para iniciar un resultado deseado en la
plataforma.
Ahora que la aplicación dispone de una IU en una actividad y un medio para contar
con una tarea prolongada ejecutada en Service, veremos BroadcastReceiver, otra
forma de aplicación de Android dedicada al procesamiento de elementos Intent.

BroadcastReceiver
Si una aplicación desea recibir y responder a un evento global como por ejem-
plo una llamada de teléfono o un mensaje de texto entrante, debe registrarse como
BroadcastReceiver. Para ello, existen dos técnicas:

• La aplicación puede implementar un elemento <recei ver> en el archivo


AndroidManifest . xrnl, que describe el nombre de clase de BroadcastReceiver
y enumera sus IntentFil ter. Recuerde que IntentFil ter es un descriptor
del Intent que desea procesar la aplicación. Si el receptor se registra en el archivo
AndroidManifest. xml, no es necesario ejecutarlo para desencadenarlo. Cuando
se produce el evento, la aplicación se inicia automáticamente al notificar el evento.
El propio SO de Android se encarga de estas tareas de mantenimiento.
• Una aplicación se puede registrar en tiempo de ejecución a través del método
registerRecei ver de la clase Contexto

Al igual que Service, BroadcastReceiver no dispone deIU. Más importante toda-


vía, el código que se ejecuta en el método onRecei ve de BroadcastReceiver no debe
asumir operaciones de persistencia o ejecución prolongada. Si BroadcastRecei ver
requiere más que una cantidad de ejecución de código, es recomendable que dicho có-
digo solicite un servicio para completar la funcionalidad solicitada.

La clase Intent se utiliza para desencadenar BroadcastReceiver; el uso de estos


Intent es independiente de los empleados para iniciar una actividad o un servicio,
como mencionamos anteriormente.

BroadcastReceiver implementa el método abstracto onRecei ve para procesar


Intent entrantes. Los argumentos del método son Context y un Intent. El método
devuelve void, y otros métodos para pasar resultados, como setResul t, que devuelve
al invocador un código entero, un valor String y un valor Bundle, que puede incluir
diferentes objetos.
EII 1. Android

El listado 1.3 muestra un ejemplo de BroadcastReceiver desencadenado al reci-


bir un mensaje de texto .
Listado 1.3. IntentReceiver.
p a c ka g e com. msi. man ni n g . un loc ki nga ndroid ;

impor t a ndro i d .co n ten t .Co ntext ;


impor t a ndro i d.co n te n t. l n te nt;
impo r t a ndro id .con ten t . l n te ntRe ceiver ;
impor t andro id. ut il .Log ;

p ub lic c lass MySMSMa i l Box exte nds Broadc a s t Re c e ive r


publ i c stat ic fin al Stri n g tag = " MySMSMa ilBo x";

@Over r i de
pub lic vo i d o nRece ive (Co ntex t co n text , Intent inte n t ) (
Lo g.i( tag," onRe c eiv e ") ;
if (inten t . ge t Act i on{) . equ a ls{" a nd r o i d. p r o vi d e r . Tel eph o ny. SMS RECEIV ED" »
Log.i( t a g," Found ou r Even t ! " );

Analicemos algunos de los elementos del código. La clase MySMSMailBox amplía la


clase BroadcastReceiver. Este enfoque de subclase es la forma más sencilla de utilizar
Broadcas tRece i ve r. Fíjese en el nombre de la clase, MySMSMa i lBox, que se utilizará
en el archivo AndroidManifest. xml del siguiente listado de código. La variable tag
se utiliza junto al mecanismo de registro para etiquetar mensajes enviados al registro
de consola del emulador. De este modo se pueden filtrar y organizar los mensajes de
registro en la consola, como veremos en un capítulo posterior. El método onRecei ve
concentra toda la acción de BroadcastReceiver y es necesario implementarlo. Un
BroadcastReceiver puede registrar varios IntentFil ter y, por tanto, se puede
instanciar para cualquier cantidad de Intent.
Es importante procesar el Intent adecuado comprobando la acción del Intent en-
trante, como se demuestra en el código. Una vez recibido el Intent deseado, se desarro-
lla la funcionalidad. Una tarea habitual en una aplicación de recepción de mensajes SMS,
es analizar el mensaje y mostrarlo al usuario a través de una pantalla de notificación. En
este fragmento, simplemente guardamos la acción en el registro.
Para desencadenar este BroadcastReceiver para enviar y recibir el Intent, debe
enumerarse en el archivo AndroidManifest. xml, como el listado 1.4 muestra. Éste
contiene los elementos necesarios para responder a un mensaje de texto entrante.
Listado 1.4. AndroidManifest.xml.
<?xml ve rsio n = " 1. 0 11 e ncod i ng= "u tf -8 "?>
<manifes t xmlns :android= " http ://sc hemas .andro id.com/ap k/res/android "
p a c ka g e =" c om.msi. mann ing .u nl o ck i ng and r oid" >
<use s -pe rm ission androi d :name= "android. permissio n.RECEIVE _SMS " />
<a pplicat ion android :icon= "@drawab le/icon ">
<activity android :name= ".chapte rl " androi d : labe l= "@stri ng/ap p _name ">
<inten t - f i lter>
<ac t ion andro i d: na me= "andro i d. i n tent . action .MAIN " />
Android. Guía para desarrolladores &JI
<category a nd r oid : na me= " a n d ro i d . i n te n t .ca t e g o r y . LAUNCHER" />
</in tent -fil te r>
</acti vity>
<re ceiver android :name= ".MySMSMailBox " >
<inte nt -f i lter>
<ac tio n androi d:name= "android . prov ide r .Te lephony .SMS RECEIVED " />
</i nten t -fi l ter>
</ rece ive r >
</appli cat ion>
</ma ni fest>

En la plataforma Android, determinadas tareas requieren que la aplicación tenga un


privilegio designado. Para asignar a la aplicación los permisos necesarios, se utiliza la eti-
queta <uses - permiss io n>, que analizaremos más adelante. La etiqueta <r e c e i v e r>
contiene el nombre de la clase que implementa Broadc a s t Re c e i ver. En este ejemplo,
es MySMSMailBox, del paquete com. msi . ma nn ing . unl ockinga nd r oid. Fíjese en el
punto que aparece por delante del nombre, es obligatorio. Si la aplicación no se comporta
de la forma esperada, compruebe si ha incluido el punto en el archivo Andr oid. xml.
IntentFil t er se define en la etiqueta <i nte n t -fi l t er>. En este ejemplo, la acción
deseada es com. msi . mann ing. unl o ckinga ndro i d. El SOK de Android enumera las
acciones disponibles para los Inte nt estándar. Además, recuerde que las aplicaciones de
usuario pueden definir sus propios Inten t así como escucharlos. Una vez presentados
los In t e nt y las clases de Android para procesarlos, analizaremos Con ten t Prov i de r ,
el mecanismo de publicación de datos preferido de Android.

ContentProvider
Si una aplicación gestiona datos y debe mostrarlos a otras aplicaciones ejecutadas en
el entorno de Android, es necesario implementar Content Provide r. Por otra parte,
si el componente de una aplicación (Ac tivi t y, Service o Broadc a s t Re c e i v e r)
tiene que acceder a datos de otra aplicación, se utiliza el Con ten t Provide r de ésta.
Conte ntProv ide r implementa un conjunto estándar de métodos pa ra que la aplica-
ción pueda acceder a un almacén de datos, ya sea en operaciones de lectura o escritura.
Con t en tProvi der puede proporcionar datos a una actividad o a un servicio incluidos
en la misma aplicación o en otra diferente.

ProbarSM~ ; III

El emulador dispone de una serie de herramientas para manipular determinadas con-


ductas de telefonía para simular diversas condiciones, como la cobertura de la red o la
realización de llamada s. El ejemplo de este apartado ha demostrado otra función del
emulador , la recepción de un SMS.
Para enviar un SMSal emulador, use el puerto 55 54 (aunque puede variar en su sistema),
que se conectará al emulador y ejecutará el siguiente comando:
s ms sen d <nú me ro del e miso r> <c uerpo del me n s a j e de texto>

Si necesita información adicional sobre los comandos disponibles, introduzca help.


En un capítulo posterior analiza remos estas herramientas con mayor detalle.
EII 1. Android

Cont e nt Pr ov i de r puede usar cualquier mecanismo de almacenamiento de datos


disponible en la plataforma Android, como archivos, bases de datos SQL o incluso un
mapa de hash basado en memoria si no se necesita persistencia de datos. Básicamente,
Con t e nt Prov ide r es una capa de datos que proporciona abstracción pa ra sus clientes
y centraliza las rutinas de almacenamiento y recuperación. No se recomienda compartir
archivos o bases de datos directamente en Android, algo que también impide el sistema de
seguridad de Linux, que evita el acceso a archivos desde una aplicación a otra sin permisos
explícitos. Los datos almacenados en Con tent Prov i de r pueden ser tipos de datos frac-
cionales como enteros y cadenas. Los proveedores de contenido también pueden gestionar
datos binarios como imágenes. Al recuperar datos binarios, es aconsejable devolver una
cadena que represente el nombre de archivo que contiene los datos binarios. Si se devuelve
un nombre de archivo como parte de una consulta Con te nt Prov i de r, no se debe acceder
directamente al archivo, sino utilizar el método op e n l np utS treamde la clase de ayuda
ContentRe solv er. Este enfoque niega los obstáculos de procesamiento y seguridad de
Linux y normaliza todo el acceso a datos a través del ContentProvider. La figura 1.6
muestra la relación entre Cont e nt Prov i de r , almacenes de datos y sus clientes.

Aplicación 3 de Android

IActiv ity 3.1 I

Aplicación 1 de Android Aplicación 2 de Android

1 Activity 1.1 I I
Activity 2.1 I

I Activity 1.21

Conexión
Archivo virtual al
de datos almacén
remoto

Figura 1.6. El proveedor de contenidos es el nivel de datos de las aplicaciones


Android y la forma de acceder y comparti r datos en el dispositivo.

A los datos de Co n t e n t Pr ov i d e r se accede a través del URI Cont ento Un


ContentProvider lo define como cadena pública estática final. Por ejemplo, una apli-
cación puede tener un almacén de datos para gestionar hojas de datos de materiales. El
URI Content de este ContentProvider sería el siguiente:
publ ic s t ati c final Ur i CONTENT_ URI =
Uri.pa rse ( "con te nt ://com. ms i . manni ng.provider . u nlocki ngandro id/datasheets " ) ;
Android. Guía para desarrolladores lImII
Desde aquí, el acceso a Con te n t Prov i de r es similar al uso de SQL en otras pla-
taformas, aunque no se utiliza una instrucción SQL completa. Se envía una consulta a
Conten tP rovider, con las columnas deseadas y cláu sulas Whe re y Orde r By opcio-
nales. Los usuarios familiarizados con consultas con parámetros de SQL, incluso se ad-
mite la sustitución de parámetros. Los resultados se devuelven en la clase Cursor. En
un capítulo posterior encontrará más detalles sobre ContentProvider.

En cierto modo, Conte nt Provider actúa como servidor de base de datos. Aunque
una aplicación solamente podría incluir un ContentProvi der y, en esencia, ser un
servidor de base de datos, Cont e nt Pr ovi de r suele ser un componente de aplicaciones
Android de mayor tamaño que alojan al menos un elemento Activ i ty, Serv ice y/o
Broa dcas tReceive r .

Con esto concluye la breve introducción a las principales clases de aplicaciones Android.
Es importante conocer estas clases y saber cómo interactúan en el desarrollo de Android.
Conseguir qu e los componentes funcionen de forma conjunta puede ser una ardua tarea.
Por ejemplo, seguro que en alguna ocasión un producto de software no ha funcionado
correctamente en su ordenador. Puede que se haya copiado y no se haya instalado bien.
Toda plataforma de software tiene problemas de entorno, aunque varían en cada caso. Por
ejemplo, al conectarse a un recurso remoto como un servidor de bases de datos o FfP, qu é
nombre de usuario y contraseña utilizar. Lo mismo sucede con las bibliotecas necesarias
para ejecutar la aplicación. Son aspectos relacionados con el desa rrollo de software. Las
aplicaciones de Android requieren el archivo AndroidMa ni fest . xml, que combina todos
los elementos necesarios para ejecutar una aplicación en un dispositivo.

AndroidManifest.xml
En apartados anteriores presentamos los elementos comunes de una aplicación
Android. Para resumir, recordar que contiene, al menos, un elemento Act i v i t y, Service,
Broadcas t Rece i ver o Con ten t Prov i de r . Algunos muestran los In t e nt que desean
procesar a través del mecanismo In t entFil t e r. Todos estos fragmentos de información
deben combinarse para poder ejecutar la aplicación. El mecanismo que los combina es el
archivo AndroidM ani fe st. x rnl. Este archivo se encuentra en el directorio raíz de la apli-
cación y contiene todas las relaciones de tiempo de diseño y sus Intent. Estos archivos
actúan como descriptores de implementación de las aplicaciones de Android. El listado
1.5 muestra un ejemplo de un sencillo archivo And roi dManife st . x rnl.

Listado 1.5. Archivo AndroidManifest.xml de una sencilla aplicación Android .

<? x ml vers ion= " l. O" e ncoding = "utf - 8 11 ? >


<ma n i f e s t xml ns :andro i d= '' http: / /schemas. a nd roid . com/apk/res/and roid "
package= "com.ms i.manning .u nloc k i n g a n d ro i d ">
<a p p l i c a t i o n andro id : icon= "@dralia bl e / i con " >
lImI 1. A ndroid

<acti v i t y a ndro i d: na me= ".chap terl " andr oid:l a b el=" @string /app_n ame" >
<i nte n t -f i lte r>
<a c t io n a nd ro id :name= "a ndro id .i ntent .actio n.MAI N" />
<c a t e g ory android:name= "andr o id .i n ten t . ca t ego ry.LAUNCHER" />
</inte n t -f i l t er>
</ ac tivity>
</ a p p lica t io n>
</ma n i f e st>

Vemos que el elemento ma ni f es t incluye el espacio de nombres obligatorio así como


el nombre del paquete de Java que contiene esta aplicación. Dicha aplicación contiene
una única Ac tiv i t y con el nombre de clase c hap ter l. Fíjese en la sintaxis @str ing .
Siempre que se utiliza un símbolo @en un archivo AndroidManife st. xml , hace re-
ferencia a información almacenada en uno de los archivos de recurso . En este caso, el
atributo label se obtiene del recurso de cadena app_ name definido en otro punto de
la aplicación. En un cap ítulo posterior encontrará más info rmación sobre recursos. El
único elemento Acti v i ty de la aplicación contiene una definición Inten tFi l ter,
del tipo más habitual utilizado en aplicaciones Android. La acción a ndro i d . in t en t .
a c t i on. MAIN indica que es un punto de entrada a la aplicación. La categoría a nd r o i d .
intent. category.LAUNCHER añade esta Activity en la ventana de inicio (véase
la figura 1.7). Un archivo de manifiesto puede tener varios elementos Act i v ity y se
puede ver más de uno en la ventana de inicio.

Figura 1.7. Las aplicaciones se enumeran en la ventana de inicio en función de sus IntentFilter.
And roid. Guía para desarrolladores ..

Además de los elementos utilizados en este archivo de manifiesto, otras etiquetas


habituales son las siguientes:

• La etiqueta <s e r v ice> representa un servicio. Sus atributos son su clase y una
etiqueta. Un servicio también puede incluir la etiqueta <i n t e nt - fi l ter >.
• La etiqueta <r e c e i v e r > representa un elemento Broad c astRe c eiver, que
puede incluir una etiqueta explícita <i nt e n t - fil t er> o no.
• La etiqueta <us e s -pe r mi s s ion > indica a Android que esta aplicación requiere
determinados privilegios de seguridad. Por ejemplo si una aplicación tiene que
acceder a los contactos de un dispositivo, requiere la siguiente etiqueta en su
archivo Andro idMani f es t . xml :
<uses -permission a ndro id :na me=
" a n d r o i d . p e r mi s s i o n . READ_ CONTACTS" />

A lo largo del libro volveremos a mencionar el archivo And roi dMa n i fes t . xml al
hacer referencia a determinados elementos.
Una vez descrito el archivo de manifiesto, veremos cómo y dónde se ejecuta. En el
siguiente apartado an alizaremos la relación entre una aplicación Android y su tiempo
de ejecución Linux y la máquina virtual Dalvik.

Asignar aplicaciones a procesos


Las aplicaciones de Android se ejecutan en un mismo proceso Linux. Android depen-
de de Linux para la gestión de procesos y la propia aplicación se ejecuta en una instancia
de la máquina virtual Dalvik.

El entorno de Linux es completo e incluye administración de procesos. Sepueden iniciar


y cancelar aplicaciones directamente desde la consola de comandos en la aplicación
Android. Sin embargo , es una tarea de depuración del programador, no del usuario del
dispositivo Android. Aunque resulta útil para solucionar problemas de la aplicación,
no se conoce ningún teléfono móvil comercial que lo haga. En un capítulo posterior
encontrará más inform ación sobre el uso de Linux en Android.

Puede que el SO tenga que descargar o incluso cancelar una aplicación para acomo-
dar demandas de asignación de recursos. Existe una jerarquía o secuencia que el siste-
ma emplea para seleccionar la víctima de la reducción de recur sos. Las reglas generales
son las siguientes:

• Las actividades visibles y en ejecución tienen prioridad.


• Las actividades visib les pero no en ejecución son importantes ya que se han
detenido y supuestamente se van a reanudar.
.. 1. Android

• Los servicios en ejecución son la siguiente prioridad.


• Los candidatos más probables son los procesos vacíos (por ejemplo los que se abren
para funciones de rendimiento en caché) o procesos con actividades latentes.
Para finalizar el capítulo, veremos una sencilla aplicación de Android.

Una aplicación de Android


En este apartado presentaremos una sencilla aplicación de Android para ilustrar
un elemento Acti vi ty con una sola View. Activi ty recopila datos, en concreto una
dirección, y crea un Intent para localizarla. En última instancia, el Intent se mues-
tra en Google Maps. La figura 1.8 muestra una captura de la aplicación ejecutada en el
emulador. El nombre de la aplicación es Where Do You Uve.

Figura 1.8. Aplic ación de Android para ilustrar Activity y View.


Android. Guía para desarrolladores EII
Como hemos mencionado, el archivo AndroiciManife st . xml contiene los descrip-
tore s de las clases de nivel superio r de la aplicación. Ésta contiene una única Ac t i v i ty,
AWhe re DoYouLi ve. El listado 1.6 muestra el archivo Andro i ciMan ifest. xml.
Listado 1.6. AndroidManifest.xml de la aplicación Where Do You Uve.
<?x ml ve rsion= "l .O" e ncoding= llut f - 8 I'?>
<ma ni fest x mlns :andro id= "http ://schem as .andro id . com/apk/ res/and ro id "
p a cka ge = " c om. msi . ma n n i n g . u nl o c k i ng a nd r o i d " >
<app lic at ion android :icon= "@ drawable/icon ">
<activity androi d: name= " .AWhereDoYouLive " android: labe l ~ "@st ri n g/a pp_name ">
<intent -filter>
<action a ndroid :name= "and ro i d .inten t.act ion .MAIN " / >
<catego ry andro i d : name= "android .in tent .category . LAUNCHER" / >
</ i nt e nt - f i l t e r >
</activ i ty>
</ app l icat ion>
</manifest>

El único elemento Act i vi ty se implementa en el archivo AWh ereDo YouLi ve .


j ava, véa se el listado 1.7.
Listado 1.7. Implementación de la actividad en AWhereOoYouUve.java.
packag e com.msi . manning . un lock ingandro id ;

// se omi te n la s i mpo r t a c i o n e s

p ubl ic c lass AWhe reDoYo u Live extends Ac t ivit y


@Over ri de
pub l i c void onCreate (Bun d le i c i cl e)
super .o nCreate (ic ic le ) ;
s e t Co nt en t Vi e w(R. l a yout .main) ;
fin al Ed i t Te x t a ddre ss fi e l d ~ (Edi tTex t) f ind Vi ewByl d( R . i d . addre s s ) ;
fin a l Butt on b ut t o n ~ (Bu t t o n) f indViewBy ld (R . id . lau nchmap ) ;
bu tto n. s e t OnCl i c kLi s t e n e r( ne w Button .OnClickListe ne r ( ) (
pub lic v oi d onClick {View v iew ) {
try {
String address = address fie ld.ge tText ( ) . toString ( ) ;
a ddress = address . rep lace (' " ' + ') ;
In tent geolnt ent ~ new Int e nt (androi d. content .l ntent.ACTION_VIEW,
Ur i. par se ( " geo :O , O?q~ " + address ) ) ;
star tAc t ivity (geo l ntent ) ;
catch (Ex c ep ti o n e) {

}
)) ;

En este ejemplo, el método setCo nte n t View crea la ID principal, que es un dise-
ño definido en main. xml en el directorio / res/ la yo ut. La vista Edi t Te xt recopila
info rmación, en este caso una dirección. La vista Te x tEdi t es un cuadro de texto . El
método f indVi e wBy l d conecta el recurso identificado por R. i d. a ddress a una ins-
tancia de la clase Te x t Edi t.
.. 1. Android

Se conecta un objeto Bu tton al elemento l aun chma p de la IV, por medio del método
f indVi ewByI d. Al hacer clic en este botón, la aplicación obtiene la dirección introduci-
da mediante la invocación del método ge t Tex t del Edi tT e xt asociado.
Una vez recuperada la d irección de la IU, es necesario crear un I nte nt para buscar
la dirección introducida. In t en t tiene una acción VI EWy la pa rte de datos que repre-
senta la consulta de búsqueda geográfica. Por último, la aplicación solicita a Android la
ejecución de In t ent que, en última instancia, muestra la dirección en la aplicación de
mapa. La operación se realiza mediante la invocación del método s tar t Act ivi t y.
Los recursos se precompilan en una clase especial denominada R, véase el listado 1.8.
Los últimos miembros de esta clase representan elementos de la ID. No debe modifi-
car manualmente el archivo R. java, ya que se crea de forma automática siempre que
cambian los recursos subyacentes.
Listado 1.8. R.java contiene la clase R, con identificadores de elementos de la IU.
/ * AUTO -GENERATED FILE . DO NOT MODIFY.

* This c l a s s \'las a u t oma tica l l y generated by t he


* aapt too l fr om the resource d a t a i t fou nd . It
* s hou ld not be mo d i f i e d by hand .
*/

p a c kage c om. ms i. ma n n i n g . u n l o c ki n g a n d ro i d ;

p ub li c f ina l c l a s s R {
pub l i c s ta ti c f inal c l a s s attr (
)
publ i c s tatic final c la ss d r a\'lable {
publ i c s t a tic fin al i nt i c on= Ox7f0 2 000 0;

publi c s t at i c f i nal class id (


publi c s tati c fina l i n t addre ss=Ox7 fO SOOOO ;
pub li c stat ic fi nal int lau n c h map= Ox7f OSOOOl;
)
p u b l ic s t a ti c fi n a l c las s la you t {
p ub l i c sta tic fina l i nt main~ O x7f0 3 0 000;

pub li c s t a tic final class s tri n g {


pub l i c s ta t i c fi na l i n t a p p _ n a me = Ox7f 0 4 0 0 0 0 ;

En un capítulo posterior encontrará más información sobre recursos de Android.


La pantalla principal de esta aplicación se define como vista Linea rLa yout (véase
el listado 1.9). Es un diseño que contiene una etiqueta, un elemento de entrada de texto
y un botón de control.
Listado 1.9. Main.xml define los elementos de IU de la aplicación .
<?xml v ers ion= 111 .0 " e n c odi ng= "utf -8 11? >
<Li n e a r Layou t xml ns:andr o id= '' h ttp : / /s ch e mas . a ndro id . c om/ap k / r e s / a nd r o id "
a ndro i d:o r i e nt a t ion= "ver tica l ll
a n droi d: l a y o llt_\vi dth~ " fi l l_p a re n t "
Android. Guía para desarrolladores II1II
a n d roi d : layou t_he i g h t ~ " fil l _pare nt "

>
<Te xtVie l'¡
an d r oid : l a you t _ "¡i d t h=" l'Ir a p_ co n t ent "
and ro i d : l a yo ut_ he igh t= " l'Irap_ co nte n t "
a ndro i d : text="Please e n ter yo ur home a ddre ss . "
/>
<Ed i t Te x t
a ndroi d :id= "@ +id/ a dd ress "
a nd r o i d : l a yout_ ,·¡ i d t h= " fi l l _pa r e nt "
a ndro id : l a yout _he i g h t~ " l.¡r a p _ c o nt ent "
android :aut oTex t= "true "
/>
<But to n
an droid:id="@+id / lau nchmap "
a ndroid : l ayout _ l'Ii d th= "l'Irap_con t en t "
a nd r oid : l a yo ut _he ight= " I·¡rap_ conte nt "
a nd roi d : t e xt =" Shol'l Map "
/>
< T e xt V ie ~1
a ndroi d : l a yo u t _ ,.¡i d t h=" l'Ir a p_ c o nte n t "
a nd r oid: l a yo u t _ he i gh t ="I.¡r a p_ c o n t e n t "
an d r o i d :text = "Unl ock i ng And r o id, Ch ap te r l . "
/>
</ Li nea r La yo u t>

Fíjese en el uso del símbolo @ en el atributo i d de este recurso. Hace que se creen las
entradas correspondientes en la clase R a través de l archivo R . java generado automá-
ticamente. Los miembros de la clase R se utilizan en las invocaciones de fi ndVi ewB-
y I D (), como vimos anteriormente, para vincular los elementos de la interfaz a una
instancia de la clase correspondiente.
Un archivo de cadena y un icono son los recursos restan tes de la aplicación. Ellistado 1.10
muestra el archivo str i ng s . xml, que se utiliza para localizar contenidos de cadenas.

Listado 1.10. strings.xml.

<? xml version="l . O" e nc od ing="u t f- 8 '1?>


<re s ources>
<st r i ng na me ="app_ na me " >Whe r e Do You Live </stri ng>
</ re s ou r c e s >

Con esto concluye nuestra primera aplicación de Android.

Resumen
En este capítulo hemos presentado la plataforma Android y hemos visto brevemen-
te su posición en el mercado, y su presencia como novedad en el sector de los móviles.
Android es una plataforma tan novedosa que seguramente cambie y madure con la
ap arición de nuevo hardware. Las nuevas plataformas deben adap tarse para identificar
sus ventajas y mostrar los puntos débiles que mejorar. Puede que el principal reto para
lIfII 1. Android

Android sea asentarse en el sector de la telefonía móvil y convencer a los operadores de


su valor para su negocio. Afortunadamente con el respaldo de Google, Android podrá
hacerse hueco entre fabricantes y operadores.
También hemos examinado la pila de Android y su relación con Linux y Java. Con un
núcleo de Linux, Android es una plataforma magnífica, en especial para el mercado de
los móviles. Aunque el desarrollo de Android se realiza en Java, el tiempo de ejecución
recurre a la máquina virtual Dalvik, como alternativa a la de Sun. Independientemente
de la máquina virtual, se necesitan conocimientos de programación para el desarrollo
de Android, en especial para adecuar las bibliotecas de Java existentes.
Hemos descrito la clase Intent de Android. Se encarga del flujo de eventos y de
determinar el código para su procesamiento, así como de proporcionar un mecanismo
para dotar de funciones concretas a la plataforma, lo que permite a programadores de
terceros crear soluciones y productos innovadores para Android. También presenta-
mos las principales clases de la aplicación, Acti vi t y, Se r v ice , Con te n t Prov i der y
Broad ca stRe ce iver, con un ejemplo de código de cada una. Interactúan con Intent
de diferente forma pero el uso de Intent y de VRI de contenido para acceder a funcio-
nalidad y datos crean el innovador y flexible entorno de Android. A lo largo del libro
veremos la relación de Intent con estas clases de aplicación.
El archivo AndroidManife st. xml combina todos los detalles de una aplicación de
Android. Incluye la información necesaria para ejecutar la aplicación, los Intent que
puede procesar y los permisos que necesita. A lo largo del libro, haremos referencia a
este archivo al añadir y examinar nuevos elementos.
Por último, incluimos un sencillo ejemplo de aplicación de Android con una IU, un
Intent y Google Maps. Es simplemente un anticipo de lo que Android puede hacer.
En el siguiente capítulo nos adentraremos en el SDK de Android para descubrir todas
sus herramientas.
2
Entorno
e es rrollo
En este capítulo presentamos las herramientas de desarrollo de Android y le ofrece-
mos una guía práctica de uso aplicada a la creación, prueba y depuración de una apli-
cación de ejemplo. Al finalizar el capítulo, estará familiarizado con el uso de Eclipse y
el complemento de herramientas de desarrollo de Android, el SDK de Android y sus
herramientas, la ejecución de aplicaciones Android en el emulador y la depuración de
una aplicación. Con estos conocimientos, analizaremos los paquetes de Java incluidos
en el SDK para afrontar los temas de desarrollo que veremos en capítulos posteriores,
como preparación a la creación de aplicaciones de Android.
La principal tarea de un programador al enfrentarse a una nueva plataforma es com-
prender el SDK y sus distintos componentes. En primer lugar analizaremos los compo-
nentes básicos del SDK de Android, para después pasar a las herramientas incluidas
para crear y depurar una aplicación.

El SDK de Android
El SDK de Android se puede descargar de forma gratuita en Google. Lo primero que
debe hacer antes de continuar es comprobar si dispone del SDK instalado, junto a Eclipse
y el complemento Android para éste, también denominado ADT. El SDK se necesita
para crear aplicaciones de Android y Eclipse es el entorno de desarrollo preferido para
este libro. Puede descargar el SDK de Android en la dirección ht tp : / / eode . goog le .
eom/and ro i d /do wnload .html .
.. 2. Entorno de desarrollo

I}Jiruco , .
La página de descargas de Android incluye instrucciones para instalar el SDK. En uno
de los apéndices encontrará más información sobre la instalación de las herramientas
de desarrollo necesarias.

Como sucede en cualquier entorno de desarrollo, resulta muy útil familiarizarse con
las estructuras de clases, de modo que puede consultar la documentación. El SDK de
Android contiene documentación HTML, páginas con formato Javadoc que describen
los paquetes y clases disponibles. La documentación del SDK se encuentra en el direc-
torio / do c de la instalación. Debido a la naturaleza cambiante de esta plataforma, debe
estar al tanto de las novedades del SOK. En la dirección ht tp: / / code. goo g le . com/
a nd ro id /doc umen t a tio n . html encontrará la información más actualizada sobre el
SDK de Android.

Interfaz de programación de aplicaciones


El entorno de Java de Android se puede dividir en diferentes secciones. Una vez
comprenda cada una de ellas, los materiales de referencia incluidos con el SDK son una
herramienta esencial, no materiales aparentemente sin relación. Recordará que Android
no es un entorno de software estrictamente J2ME; no obstante, existen semejanzas entre
Android y otras plataformas Java. En los siguientes apartados veremos algunos de los
paquetes Java del SDK de Android y cuándo se utilizan. En capítulos posteriores encon-
trará más detalles sobre el uso de estas interfaces de programación.

Paquetes básicos de Android


Si ha programado antes en Java, reconocerá muchos de estos paquetes básicos, como
los enumerados a continuación:

• java .lang: Clases básicas del lenguaje Java.


• java. i o: Funciones de entrada/salida.
• java. net: Conexiones de red.
• java. util: Clases de utilidad. Este paquete incluye la clase Log utilizada para
escribir en LogCat.
• java. te xt: Utilidades de procesamiento de texto .
• java. ma th: Clases matemáticas y de manipulación de números.
• j avax. net: Clases de red.
• j avax. securi ty: Clases relacionadas con la seguridad.
Android. Guía para desarrolladores ..

• j avax. xml : Clases XML basadas en DOM.


• o r g . apa che. *: Clases relacionadas con HTTP.
• o r g . xml: Clases XML basadas en SAX.
Existen clases de Java adicionales. Por lo general, en el libro apenas nos centraremos
en los paquetes básicos, ya que nuestro interés principal es el desarrollo d e Android.
Dicho esto, veamos las funciones específicas d e éste incluidas en el SDK.
Los paquetes específicos de Android son fáciles de identificar ya que el nombre del
paquete comienza por a ndro id. Algunos de los más importantes son los siguientes:

• a ndro i d . a p p : Acceso al modelo de aplicaciones de Android .


• a nd ro id. content : Acceso y publicación de datos en Android.
• an dr oid . n e t : Contiene la clase Dr i , utilizada para acceder a diferentes
contenidos.
• a nd ro i d. gra p hics: Primitivas gráficas.
• andro id . ope ngl : Clases OpenGL.
• a nd ro i d . os : Acceso de nivel de sistema al entorno de Android.
• a nd ro i d. p r o v ider: Clases relacionadas con Con ten t Prov i der.
• a ndro i d . t el ephon y: Acceso a funciones de telefonía .
• andr oid . tex t : Diseño de texto.
• a ndro id . ut il : Colección de utilidades para manipulación de texto, incluido
XML.
• a ndro i d . v iew: Elementos IV.
• a ndr oid . webki t: Funciones de navegador.
• a n d ro i d. widget: Elementos IV adicionales.

Algunos de estos paquetes son fundamentales para el de sarrollo de aplicaciones


Android, como a n d roid . app, a n d ro id . vi ew y a ndro id . c on t en t oOtros se utilizan
en función del tipo de aplicación creada .

Paquetes opcionales
No todos los dispositivos Android tendrán el mismo hardware ni las mismas presta-
ciones de conectividad móvil, d e modo que algunos elementos del SDK son opcionales.
Algunos dispositivos admitirán estas funciones y otros no. Es importante que una apli-
cación se degrade con elegancia si una función no está disponible en un determinado
modelo. Los paquetes de Java que tener en cuenta son los que d ependen de hardware y
características de red concretos, como los servicios basados en ubicación que incluyen
GPS y tecnologías inalámbricas como Bluetooth, IrDA y Wi-Fi (802.11).
.. 2. Entorno de desarrollo

Ésta es una rápida introdu cción a las interfaces de programación del SDK de Android.
En cap ítulos p osteriores detallaremos las bibliotecas de clases, de modo qu e no s centra-
remos en las herramientas nece sarias para crear aplicaciones de Android.
Antes de crear una de estas aplicaciones, verem os el SDK y sus componentes en el
marco del entorno Eclipse.

Combinar las piezas


Tras instalar el SDK de Android junto al complemento ADT para Eclipse, ya podemos
explo rar el entorno de desarrollo. La figura 2.1 muestra el entorno de desarrollo típico,
incluido hardware real y el emulador de Android . Aunque no sea un a herramienta indis-
pensable p ara el desarrollo de Android, Eclips e de sempeña una importante funci ón no
sólo por proporcionar un entorno avanzado de compilación y depuración Java, sino tam-
bién porque con los ADT en Eclips e podemos gestionar y controlar prácticamente todos
los aspectos de nuestras aplicaciones Android directamente desde el lOE de Eclip se.

Entorno de desarrollo (portátil)

Herramientas de
IDE Eclipse de código abierto línea de comandos
Herramientas de transferencia
-Dlse ño de código de archivos
Sim ulación GSM
-Depuraclón
Emulador de Android
Herramientasde desarrollo
-Varías máscara s
de Android (complemento)
' SDK 'Opciones de co nectiv ida d de red
'Configuración de pe rfiles de l em ulador -Integración con Eclipse a través
'I nicio del emulador del co mplemento ADT
' Vista de procesos y sis tema de arc hivos
' Vista de regi stros

c: ~ Dispos itivo de Android


'H ardware f1sieo

Documentación del SDK

""-
-
Figura 2.1. Entorno de desarrollo para crear aplicaciones
de Android, incluido el conocido IDE Eclipse de código abierto.

Las principales características del entorno Eclips e relacionadas con el desarrollo de


Android son las siguientes:
• Un entorno avanz ado de desarrollo Java que incluye compilación de código fuente
Java , autocompletar clases e integración de [avadoc.
• Depuración de nivel de código fuente.
Android. Guía para desarrolladores lImII
• Administración e inicio de perfiles de emulador de Android.
• Servicio de Monitor izac ión de Dep uración Dalvik (DDMS):
• Vistas de su bprocesos y pilas.
• Administración de sistemas de archivos del em u lador.
• Control de redes de da tos y voz.
• Control del emulador.
• Registro de sistema y aplicaciones.

Eclipse admite el concepto de perspectiva, donde el diseño de la pa ntalla cuenta


con un conjunto de ventanas y herramientas relacionadas. Las ventanas y herramien-
tas incluidas en la perspectiva Eclipse se denominan vistas. Al desarrollar aplicaciones
An droid, hay dos pe rspectivas de Eclipse especialmente releva ntes: la perspectiva Java
y la perspectiva DDMS. Además de éstas, está disponible la perspectiva de depuración,
muy útil para depurar aplicaciones de Android. Para cambiar entre las perspectivas dis-
ponibles, utilice el menú Open Perspective (Abrir perspectiva), situado bajo el menú
Window (Ventana) en el IDE de Eclipse. A continuación veremos las carac terísticas de
las perspectivas Java y DDMS, y cómo adecuarlas al desarrollo de Android.

Perspectiva Java
En la perspectiva Java se desarrollan las aplicaciones de Android. Incluye numerosas
vistas para asistir en el proceso de desarrollo. La vista Package Explorer nos permite ver
los proyectos de Java en el espacio de trabajo. La figura 2.2 muestra este exp lorador.

@
~ Package Explorer ¡::¡'\.. B %1•• "7

• @ Chapter?
.. ~ sr c
~
.•e
ID com .arnmninqunlo ckinqandroid
III ChapterTwoJava
Chapte r'l wo
e.. onCreate(Bundle) : void
.. ~ gen [Generated Java Fi les]
• ID
p
cc m. am mninqu nlcckinqandrcid
0 Itj ·v.
• Android 15
t> ~ andro idjar . (. :\U s er5\J o 5~ \D ~ 5 Ho p\An droi d\a
~ a $S ets
... ¡B, res
l' (8. drewable
l' (O layout
t> (2:7 valúes
a l AndroidManife st.xml
~ default.properties

' \ '" = =l •
Figura 2.2. Package Explorer nos permite exam inar los elementos de los proyectos de Andro id.
2. Entorno dedesarrollo

En la perspectiva Java es donde se edita el código fuente de Java. Cada vez que guarda
el archivo de código fuente, se compila automáticamente de fondo por JDT de Eclipse.
No es necesario preocuparse por los de talles de l JDT; lo importan te es saber que funciona
de fondo para que la experiencia con Java sea integral. Si el código fuente tiene un error,
los de talles se muestran en la vis ta Problems de la perspectiva (véase la figura 2.3). Aquí
vemos un error intenciona do en el código fuente para demostrar la funcionalidad de la
vis ta Pro blems. También p uede desplazar el ratón sobre la x situada a la izquierda de la
línea que contiene el problema para ver información en pantalla sobre el mismo.

~~
·
[lI °CI'IIptuT.. o Jl'ill !.I "'-..
pa .,kalle <=. i nq . unlo ekh'lJ !lndl:o i d :
~
d 'ÍtDiC'o r l j~y~ ~ ~ e_x~t, . Il~U" r oCl::l!lt: D
o

e puhUC' 1"1 ••• Ch lOptet Tvo ItK t en d. ... .t._1Y.t.t ,Y


O!-
, D

·
L
publ.1c . t&l l o Unal. ~ H:~""'l tag .. ....:b..l't.e r2 .. ;

,.. C.. U e d
e C?/_e.[.!Jp ~
"h~ .. t h~ activ H y
"
1 tr .. t c r e ll t ",-d . ., .
i publlc .. o l d on Cre a teC f!11P.lt).!;. t e iele'
" up e e , on(rll'lO teC l c lc le j:

,~. lIetCo nt"lItVI".I ~,.. 1 l!:yo~.JUJ DI ;

·
d
:~
L inal. r:st.~G.1~~; J:II!al priee Ue l d •
f inu :r~e.lt.tY!s !

~~ ~~{>p
a=v er f tel d
(J:¡iJ_t_~~>§.G.l l!:~p.f1yJ-je!.I!.YH{R . ld.1=a1pci c"l
l!~J.l!-Y.l~_"J ,( ~ ~! ~ !".B'y.lst IR . l d . d n5 ver l ;

(~!:'-U~.n) .t. 1. ~ ! ,, ! II J.I~.l p; · ld . c .u e u.1 I1't " ) :


,
;

~~
t in al. bu t ton
b \lt t oll . lI e t OnC l 1c ltL i :Jt e ne ¡;-[ n ~ !l).l~ ~ 9n . OnCl1cltL l .. t e r.er I I

- ,
p ubll c .. o l d o l>Cl1 c kIV le " v I ,
''''' 11 f'e r'(o rn ee e rc n o n c l td:
Lo O . 1 (C hsp te r T1I'o.ta'1 , ~o r.Cl1cY. ¡(¡ v oJe.. !:!.. .. I:

11 Ch . ee e r pIl e e ! [ ~ tt... ur
"""
St r 1 ~ eee r ee r c e • ~& l p r l c e :t l eld . q <!t T .. n ( I . 1;OS t r l n 9 1) ;

L~ . I I C h.'lp t erTV'O. "=alpr ice ,. T lOelllp r l c e + - ] "1 :


- t aq,
"
,~ S tI ::lnq

l'
U
ct~ck
lI ~ 1I'e r

ee :r" e

. .",.
t t.e
(l:eal p r ll : e. l nd exot
=-,..
r-s-r
pe l e e
-» ,
l r.c l ud e ~ . -r -

~ , l:e ll l p rlce + l:lI!'al p r lCe;

íti P~A: ~ J..... dl><) ~ DtdwtbOll) 1:1 Con.oll ] ~ -"


~ U ' OU • • ,"mi!>gJ, • ottlU J
"
Duoipboll P.t1 O\11tt M l ou boo T}"Pt
O Em", (4 Iter,," )
O t«n. mll'm'ng u"'odÍl\gtt>dRl;d ,( htp~ IT....:> do tl not Lotu .d t ndnw.l.l ¡>p .Art;...ty And ,o id"1!n ,f_ fO .. pte.l 1lit ~ Arodtoid)Ml _
O The plo¡ectwlJ 1>01: bU.)1:' ....ce lU bu~d pl th il incompl et.!:. CINlotfind the d ln file roljlVllhtl g.ObjKl. Fi>lile b...&dpl th tht n tr¡ buldoll<;¡ thU p'0 jtn (hlpwl Ur,\ nvwo Jrn J'tObltm
O lmIn O'Mlt,ror.jrnhl\1 llul POontu bupbOf! Chl p!:. .2 Unln ""'" And'o;d AOT_
~ lhe t)'JItj rn l lng.Ob;u t Clnl\Otbe lt IOI.",d . lI:il il'l6 n ctt/ ,d tr t llCtd from reCJUi. ed .d l u filel Rjr n ICI'IIptorlh.t /t omJ.. 11'1 01 JrnP rob!.m

Figura 2.3. La vista Problems muestra los errores del código fuente.
Una de las principales características de la perspectiva Java de Eclipse es la integración
entre el código fuente y la vista Javadoc. Esta vista se actualiza automáticamente para
proporcionar la documentación disponible sobre una clase o método de Java selecciona-
do (véase la figura 2.4).

Nota
En este capítulo únicamente se describe una introducción al completo entorno de
Eclipse. Si necesita más información al respecto, encon trará muchos manuales sobre
la materia. Puede que el diseño de las vistas de la perspectiva actua l no sea el adecuado.
En ese caso, puede restaurar el estado de la perspectiva. La primera opción consiste en
utilizar el menú Show View del menú Window para mostrar una vista concreta . Por otra
parte, puede seleccionar el menú Reset Perspec tive para recuperar la configuración
pre determ inada de la vista.
A ndroid. Guía para desarrolladores ..

J a-Two.~• .!!1A
,p a c kage ccm. reennfnq , un l o c k l nqand l:o i d :
I
't'imp o r t andr o id . e pp , Act.1Vit.y ; D

pub1io cJ.a s s Chapt e r Tvo e x ten ds Act l v i ty (


, /:t .. Ca l l e d uhe n c he ac t1vic y 1 . f ir5 t c r eeced , ' /
OOve r ride
'"
! pub110 Yoid c ncc eece (Bundl e 1ele le ) (
í s upe r . o nCres te (telele) :
! s e t Con t e neViev ( R. l a you t.. ffla i n) ;

I )
)

U.I
@ Javadoc l::l"
anaold."I'P'Activty

Ara activlty Is a snc;;e. focused thinQ tMttheusercan do. Amost <)1 activtlesn erac:t wlh the usee, so too Activiy dess tekeselite 01aeatng a wrnow fot youil wt.m youcan place YOU' VI
,w-.oows, they can aIsobe used il otherways: as tloamg wildows(..-ia a theme wi:h wr.dowtsFloatoo set) or errbedded iiside of anolher activiy (usng~. Thete are twomethOd
• onCreat;N8ulcIe) Is where yooWiabe VOU' ac:tiviy. Most~tantJy, bere yoo YA usuaIycal setConteot:yteYdnt) wlh a layoutresce ce defnngYtu Ul, andus:f'9 fi"w:MewByl«rt
~ ~ v.t>ere youdeal Wllh the user 1eavhg \'00' acti'l'ty. MosliI'9Ortaotly, NrY change, madebythe US<f shoUd al t~ point be cClfTrite<l (usuaIy te the ConteolPl<Mder hoId
I •
l o be rJ use wih Cont:ext.st;b/'tActiytyO. al aclivty dasses rmsthevea correspondng ~ dedar ation n the.. pad:.age's And r o idHani t u t . XD.l.
1 The Adiviy dess IsMI important partcJan aookation's oyeralltecyde .
Topics covered bee:
!I
1. Acttdy lf ecyde
I
2. ConfkJJation ChanoeS
I 3. 5l:attim Adiviie s andGettrn Resu't;s

Figura 2.4. La vista Javadoc propo rciona documentación


sensible al contexto, en este caso para la clase Activity.

Además de JDT, que com p ila arc hivos Java d e código fuente, los ADT compilan au-
tomáticamente archivos esp ecíficos d e Android como los de di seño y recursos. En un
ap artad o p oster ior en con trará m ás información sobre las he rr amientas subyacen tes per o
ahora nos centrare m os en la perspecti va concreta d e An droid d el DDMS.

Perspectiva DDMS
La p erspectiva DDMS p roporciona una especie d e pane l d e control d e un d ispositiv o
And roi d en ejecución o, en nuestro cas o, un em u lador en ejecu ción. Analizaremos todos
los d etall es de la ap licación, incluido cómo crearla y cómo ejecutarla en el emulador d e
Android pero p rimero veremos el DDMS para contin u ar el análisis de las herramientas
disp onibles para el d esarrollo d e Android. La vista Devices m u es tra una ses ión de emu-
lador, con el título e rnu la to r - tcp -555 5. Esto significa que hay una con exión al emula-
d or de And roid en el p u ert o 'I'Cl?/IP 5 5 5 5 . En d icha sesión, se ejecutan cin co p rocesos.
El que n os in teresa es corn. rnann in g. unl o ckingand r o id, con el ID 616 .

Truco
A menos que pru ebe un a aplicació n P2 P, por lo general sólo te ndrá una ses ión de
emulador en ejecución. Se pu eden tener varias instancias del emulador en ejecució n
en un mism o equipo de desarrollo.
lIlII 2. Entorno de desarrollo

El registro es una herramienta esencial en el desarrollo de software, lo que nos lleva


a la vista LogCat de la perspectiva DDMS. Esta vista muestra el registro del sistema y la
aplicación en el emulador. El uso de filtro s en LogCat es muy aconsejable ya que permi-
ten reducir el ruido de todas las entradas de registro y nos permiten centrarnos en las
de nuestra aplicación. En este caso, la lista incluye cuatro entradas que coinciden con los
crite rios del filtro . En breve analizaremos el código fuente para saber cómo añ adir men-
sajes al registro. Estas entradas de registro cuentan con una columna que muestra el ID
de proceso, o PID, de la aplicación. Como esperábamos, el PID de nuestras entradas es
616 , lo que coincide con la instancia de la aplicación ejecutada en el emulador.
La vista File Explorer se muestra en la esquina superior derecha. Las aplicaciones de
usuario, es decir, las que creamos, se implementan con la extensión . apk y se almacenan
en el directorio Ida tal a p p del dispositivo Android. Esta vista también pe rmi te opera-
ciones de sistema de archivos como copiar y eliminar archivos. La figura 2.5 muestra la
eliminación de una aplicación de usuario del directorio I da ta/ a pp.

ftoli l - V = El
Si" Date: Time: Pt rmiHions Info

El (O data 2007·12·12 17:16 drwxrwx-x


I±J (O anr 2008-02,25 01:51 drwxrwxrwx
El (O app 2007·12·12 17:16 drwxrwx··x
el ApiDemos.apk 1325.. . 2007·12·12 17:15 -tw-r-vr-
G) Cnapter2.apk 12987 2008-02-25 02:43 -I W-I W-I W-
~ checkn.db 27648 2008-02'25 04: 12 ' rw-r--r"
I±J (O dalvik-ceche 2008-01'19 23:23 drwxrwxrwx
I±J (O data 2008-01-19 23:23 drwxrwx··x
I±J (O download 2008·01-19 23:23 drwxrwxrwx
I±J (O drm 2008-01-19 23:23 drwxrwxrwx
I±J (O logs 2008-01-19 23:23 drwxrwxrwx
I±J (O lost+ found 2008-02' 25 04:17 drw-rw·rw-
I±J (O mise 2007·12·12 17:13 drwxrwxrwx
I±J (O system 2008-01-19 23:23 drwxrwxrwx
~ timezone 3 2008-02'25 02:43 -rw-rw-rw-
I±J (O system 2008-02'12 01:10 drwxr-xr-x
I±J (O tmp 2008-02·25 04:17 drwxrwxrwt

Figura 2.5. Eliminaci ón de aplicaciones del emulador.

Evidentemente, la posibilidad de examinar el sistema de archivos de nuestro teléfono


móvil es una gran comodidad. Es una agradable función de desarrollo de móviles, en el que
solemos depender de crípticos mensajes emergentes para sortear los procesos de desarrollo
y depuración de aplicaciones. Con el acceso al sistema de archivos, podemos trabajar con
éstos y copiarlos a y desde nuestra plataforma de desarrollo con toda facilidad.
Además de explorar las aplicaciones en ejecución, la perspectiva DDMS proporciona
herramientas para controlar el entorno emulado. Por ejemplo, la vista Emulator Control
permite probar distintas características de conectividad de redes de voz y datos, como
por ejemp lo simular una llamada o recibir un SMS. La figura 2.6 muestra el envío de un
SMS al emulador de Android.
Android. Guía para desarrolladores lIiII
:IDEmJator. control ~~ =Ej

Te!ephony Státus.
Voke: Ihome (t] Speed: IFUIl-,"- ~ ~l
Data: Ihome !El Latency: ~...
-- ~
¡:;;Telephony ActIons
IncomilÍg nOOlber:" 19734480070 -1
e: Volee
(.' SMS
Message: Hey,Androldl Where are wegoing for lunch? ,

I ~~I
Figura 2.6. Envío de un SMS de prueba al emulador de Android.

DDMS también proporciona visibilidad y control sobre el emulador de Android y es


una herramienta muy útil para evaluar nuestras aplicaciones. Antes de pasar a la crea-
ción y prueba de aplicaciones Android, debe comprender qué sucede entre bastidores
y habilitar la funcionalidad de DDMS .

Herramientas de línea de comandos


El SDK de Android incorpora diversas herramientas de línea de comandos, locali-
zadas en el subdirectorio de herramientas de la instalación. Mientras que Eclipse y los
ADT proporcionan un gran control sobre nuestro entorno de desarrollo de Android, en
ocasiones necesitamos mayor control, en especial si tenemos en cuenta las prestaciones
y comodidad de la creación de secuencias de comandos en una plataforma de desarrollo.
Analizaremos dos de las herramientas de línea de comandos del SDK de Android.

Nota

Es aconsejable añadir el directorio tools a la ruta de búsqueda. Por ejemplo, si ha


instalado el SDK en e: \ software\google\androidsdk puede añadirlo a la ruta
mediante la siguiente operación en una ventana de línea de comandos (Windows):
set path= %pat h %;e :\softwa re\goog le\androidsdk\too ls ;

o utilizar el siguiente comando para Mac OS X YLinux:


expo rt PATH=$PA TH:/ruta a l di reetorio_de_Andro id_SDK/too ls
EII 2. Entorno de desarrollo

Herramienta de compresión de activos de Android


Seguramente se pregunte cómo se procesa el archivo main . xml y de dónde proviene
el archivo R . java, y quién comprime el archivo de aplicación en el archivo apk. Se trata
de la herramienta de compresión de activos de Android, también denominada a ap t en la
línea de comandos. Es una herramienta versátil que combina la funcionalidad de pkz ip
o j a r junto con un compilador de recursos específico de Android. En función de las op-
ciones de línea de comandos proporcionadas, aapt adopta diversas formas y nos ayuda
en las tareas de diseño. Para comprobar su funcionalidad, basta con ejecutar a apt desde
la línea de comandos sin argumentos. Se mostrará un detallado mensaje de uso.
Mientras que aapt colabora con las tareas de diseño, otra herramienta, Debug Br idge,
nos permite interactuar con el emulador en tiempo de ejecución.

Debug Bridge
La utilidad adb nos permite interactuar con el emulador de Android directamente
desde la línea de comandos. Seguramente en alguna ocasión habrá deseado desplazar-
se por el sistema de archivos de su teléfono inteligente. Ahora puede hacerlo con adb.
Funciona como aplicación cliente/servidor basada en 'I'Cl", Aunque existen un par de
procesos de fondo que se ejecutan en el equipo de desarrollo y en el emulador para ha-
bilitar la funcionalidad, lo importante es que al ejecutar adb, tenemos acceso a una ins-
tancia en ejecución del emulador de Android. A continuación le mostramos ejemplos de
uso de adb. En primer lugar, veamos si existen sesiones del emulador en ejecución:
adb d e v i c e s <r etu r n >

Este comando devuelve una lista de emuladores disponibles; por ejemplo, la figura
2.7 muestra adb tras localizar dos sesiones de emulador en ejecución.

Figura 2.7. La herramienta adb proporciona interacción


en tiempo de ejecución con el emulador Android.

Nos conectamos a la primera sesión para ver si nuestra aplicación está instalada. Para
conectarnos, se utiliza la sintaxis adb shell. Es como nos conectaríamos si tuviéramos
una única sesión activa del emulador pero al haber dos en ejecución, debemos especifi-
car un identificador para conectarnos a la sesión adecuada:
a db - d 1 she ll
A ndroid. Gl/ía paradesarrolladores 11m
La figura 2.8 muestra el sistema de archivos de Android y la búsqueda de una ins-
talación concreta, en este caso la correspondiente a este capítulo, que crearemos en el
siguiente apartado.

Figura 2.8. Uso del comando shell para examinar el sistema de archivos de Android .

Esta función resulta muy útil para eliminar un archivo concreto del sistema de ar-
chivos del emulador, terminar un proceso o interactuar con el entorno operativo del
emulador. Si descarga una aplicación de Internet, por ejemplo, puede usar el comando
adb para instalarla:

adb s hel l install un a apli c a c i ó n . ap k

Instala la aplicación unaapl icación en el emulador de Android. El archivo se copia


en el directorio /dat a/app y se puede acceder al mismo desde Android. Del mismo
modo, para eliminar una aplicación, puede ejecutar adb para hacerlo desde el emula-
dor. Por ejemplo, para eliminar la aplicación Chapter2 . apk del sistema de archivos
de un emulador en ejecución, puede ejecutar el siguiente comando desde un terminal o
la ventana de comandos de Windows:

a db s he l l r m / dat a /app / Chap t er 2 . a p k

El dominio de las herramientas de línea de comandos del SDK de Android no es un


requisito para el desarrollo de aplicaciones pero conviene saber qué herramientas están
disponibles y sus prestaciones. Si necesita ayuda con el comando aapt o adb, basta con
introducirlo en el terminal para acceder a una página de ayuda o de uso. En la documen-
tación del SDK encontrará información adicional sobre estas herramientas.

Truco

El sistema de archivos de Android es de Linux. Mientras que el comando adb shell no


proporciona un entorno de programación demasiado completo como sucede en Linux o
Mac OSX, comandos básicos como I s, ps, kilI o rmestán disponibles. Si es la primera
vez que utiliza Linux, es aconsejable aprender algunos de los comandos básicos.
.. 2. Entornode desarrollo

Otra herramienta con la que debe familiarizarse es telnet. Le permite conectarse a


un sistema remoto con una IU basada en caracteres. En este caso, el sistema remoto es
la consola del emulador. Para ello, utilice el siguiente comando:
t eln e t l o c a l h o s t 555 4

En este caso, localhost representa su equipo local de desarrollo, donde haya iniciado
el emulador, ya que depende de la dirección IP 1 27 . O• O• 1 de su equipo. Se preguntará
por qué el puerto 5554 . Si recuerda, al utilizar adb para buscar instancias del emulador
en ejecución, el resultado incluía un nombre con un número. El primer emulador se suele
encontrar en el puerto IP 5555. Independientemente del puerto que utilice, la consola
del emulador se puede encontrar en un número inferior. Por ejemplo, si el emulador se
ejecuta en el puerto 5555, la consola se encontrará en el puerto 5554.
Al utilizar una conexión telnet al emulador contamos con un medio de línea de
comandos para configurarlo mientras se ejecuta y probar funciones telefónicas como lla-
madas y mensajes de texto. Ha llegado el momento de crear una aplicación de Android
para ejecutar el entorno de desarrollo que hemos descrito.

Crear una aplicación de Android en Eclipse


Crearemos una sencilla aplicación que nos permita modificar la IU, nos proporcione
cierta lógica de aplicación y la ejecute en el emulador de Android. En capítulos posterio-
res veremos aplicaciones más complejas, en éste nos centraremos en las herramientas de
desarrollo. La creación de aplicaciones Android es similar a la de otras aplicaciones Java
en elIDE Eclipse. Para empezar, debe seleccionar File>New y como destino una apli-
cación Android. Como sucede en muchos entornos de desarrollo, Eclipse proporciona
un asistente para facilitar la creación de aplicaciones. Lo utilizaremos para comenzar a
crear la aplicación de Android.

APW
La forma más sencilla de crear una aplicación de Android consiste en utilizar los
servicios del asistente para proyectos de Android, que forma parte del complemento
ADT. El asistente facilita la definición del nombre de proyecto y ubicación, el nombre
Acti vi ty correspondiente a la clase IU principal y el nombre de la aplicación. También
es importante el nombre del paquete de Java bajo el que se crea la aplicación. Una vez
creada, resulta muy sencillo añadir nuevas clases al proyecto.

En este ejemplo, crearemos un nuevo proyecto en el espacio de trabajo de Eclipse. Se


puede utilizar el mismo asistente para importar código fuente de otro programador,
por ejemplo el de este libro. Recuerde que las pantallas pueden variar en función de la
versión de las herramientas de Android.
Android. Guía para desarrolladores mil
La figura 2.9 muestra la creación del proyecto Chapter2 con ayuda del asistente.

.. NewAndroid Proj ect -~~


1I New Android Projecl
O An SOKTarg et mu st be sp eciñed.
.gJi
Projectname: Chapter2

Con tents
,~_, Create ntw project in wcrkspece
I! O e rute project from
~ Use default location
existing seu-ce

I
Loceuon: C:/ Users/ Jose/ Des.'top/An droid/ uad/ Chaptt r2 1I B' QWse•.• I
Build Targtt

Terqet Name Ven do r Plati orm APL.


-
D An dro id Ll An droid Ope n Sc vrce Project Ll 1
El An droid LS An dtoi d Ope n Sc urce Project LS J
El Googl . APIs Goo gle Ine. 1.S J

Properties
Application neme Chapter Two

Package name: ccrn.am mninqjmlcckinqandrcid

~ C...l. Activity: ~pter Two ___


--- -~
Min SOKVeuion:
- - -

® I [i nish 11 Cancel I
Figu ra 2.9. Asist ente para crear una uneva aplicación Android, lista para su personalización .

Es aconsejable que el nombre de paquete de las aplicaciones sea exclusivoen cada una
de ellas.

Al pulsar Finish se crea la aplicación, que se compila y se puede ejecutar en el emu-


lador, sin necesidad de desarrollo adicional. Pero un proyecto vacío no sirve de nada,
así que comenzaremos a modificar nuestra aplicación de cálculo .

Código de la aplicación de Android .


El asistente se enca rga de diferentes elementos de la estructura de la aplicación, in-
cluidos los archivos Java de código fuente, los archivos de recursos predeterminados y
el archivo Androi d Man i f e st . xml. En el explorador de paquetes de Eclipse podemos
ver todos los elementos de la aplicación. A continuación le mostramos una breve des-
cripción de los elementos incluidos en la aplicación de ejemplo:
EII 2. Entorno de desarrollo

• La carpeta src contiene dos archivos de código fuente de Java que el asistente
crea de forma automática.
• Chap t e r Two . java contiene la actividad principal de la aplicación. Modificaremos
este archivo para añadir la funcionalidad de calculadora.
• R . java contiene identificadores de los distintos elementos de recursos IU de la
aplicación. Es importante que no modifique directamente este archivo, ya que se
regenera de forma automática cada vez que se modifica un recurso, y los cambios
manuales se pierden al generar la aplicación.
• Andr oid . j ar contiene las clases Java de tiempo de ejecución de Android. Es
una referencia al archivo android. j ar del 5DK.
• La carpeta res contiene todos los archivos de recursos de Android como:
• Drawabl es contiene archivos de imagen como mapas de bits e iconos. El
asistente incluye un icono de Android predeterminado (icon. png).
• La yout contiene el archivo XML main. xml , con los elementos IU de la
vista principal de la actividad. Modificaremos este archivo pero sin cambios
especialmente significativos, sólo los suficientes para la aplicación. Elementos
ID como View se analizan en un capítulo posterior. Una aplicación Android
puede tener varios archivos XML en la sección Layout.
• Value s contiene el archivo strings . xml , que se utiliza para localizar valores
de cadenas como el nombre de la aplicación y otras cadenas empleadas en la
aplicación. Contiene todas las aplicaciones del libro .
• AndroiciManife st. xml representa la información de implementación del pro-
yecto. Aunque estos archivos pueden ser muy complejos, el archivo de este capítulo
se puede ejecutar sin cambios ya que no se necesitan permisos especiales .

Ahora que sabemos lo que incluye el proyecto, veamos cómo modificar la aplicación.
Nuestro objetivo es que el usuario pueda introducir el precio de una comida y seleccionar
un botón para calcular el coste total, incluida la propina. Para ello, tendremos que modi-
ficar dos archivos, Cha p t e r Two . java y el archivo main. xml. Comenzaremos con los
cambios de ID añadiendo nuevos elementos a la vista principal, véase el listado 2.1.
Listado 2.1. Main.xml contiene elementos IU.

<?xml ver s ion='ll . O" en coding= "utf-8 "?>


<Li ne a r La yout xrnln s:a nd r oid="http: / / schernas.android.com/apk/res /android "
android : orientati on= " vert ical "
an d ro id : layout_ \·¡id th= " fi l l _parent "
a ndro id :layout_height="fil l_paren t "
>
<Te xt Vi e H
a ndroid: l ay o u t _ \'li d t h= " fi ll_parent "
android:layout_he igh t ="Hrap_conten t "
android:text ="Chapter 2 Android Ti p Ca l c u l a t or "
/>
Android. Guia para desarrolladores ..

<EditText
android :id= "@+id/mealprice "
android :layout_width= "fill_parent "
a nd roid : layo ut_height= "wrap_co nte nt "
a ndroid :a utoText= "true "
/>
<Butt on
a nd roi d :i d ="@+id/ca lcu late "
andro i d: l a yout_\oJÍdth= " \'1 rap_ co nt e n t "
a ndro id :layout_he igh t= "wrap_co nte nt "
a nd ro id: text= "Ca lc u la te Tip "
/>
<T'e x t.V d ew
and roid : id- "@ +i d/an s wer "
android : l a you t _I·¡id t h=" f i ll pare nt "
a nd ro id : l a yout_ hei ght ="¡·¡r ap_content O
a nd ro id :text= lI l1
/>

</ Linea r Layout>

El formato de esta aplicación es muy sencillo. El diseño general es vertical y lineal con
cuatro elementos. Un Te x tView estático muestra el título de la aplicación. Edi tT e xt
recopila el precio de la comida para la calculadora de propinas. El elemento Edi t Te x t
tiene un atributo de tipo a ndro i d : id con el valor mealpr i ce. Cuando un elemento
contiene este atributo, nos permite manipularlo desde el código. Para ello, añadimos el
atributo id de este elemento al archivo R . j a va como miembro exclusivo de la clase R.
Este valor se utiliza en el método findVi ewByID, véase el listado 2.2. Si un elemento
IV es estático, como Tex tV i ew, y no es necesario establecerlo ni leerlo desde el código
de la aplicación, el atributo a nd ro i d : i d no es necesario.
Se añade el botón ca lc ula t e a la vista. Este elemento también tiene un atributo
android : id ya que queremos capturar eventos de clic.
Se proporciona el Te xtVi ew an swer para mostrar el precio total, incluida la propi-
na. Este elemento también tiene un id ya que tendremos que actualizarlo en el tiempo
de ejecución.
Al gu ardar el archivo ma i n . xml, el complemento ADT lo procesa, compila los re-
cursos y genera un archivo R . jav a actualizado. Pruébelo. Modifique uno de los valo-
res id del archivo main. xml, guarde el archi vo y después abra R. java para ver las
constantes generadas. No modifique directamente el archivo R . java o se perderán los
cambios. Si realiza este experimento, no olvide volver a cambiar los valores para garan-
tizar que el proyecto se compila. Siempre que no haya errores sintácticos en mai n . xml ,
el archivo IV está terminado.

truco .-

Aunque la madurez de las her ramientas de desarrollo de Android no sea completa , los
complemento s de Eclipse disponen de editores de recursos para manipular los archivos
XML, de modo que no es necesario editarlos directamente.
2. Entorno de desarrollo

A continuación veremos el archivo Chap t e r TwD. j a v a, para implementar la fun -


cionalidad de calculadora de propinas (véa se el listado 2.2). Hemos omitido diversas
importaciones por motivos de brevedad.

Listado 2.2. ChapterTwo .java implementa la lógica de la calculadora de propinas.

pa ckage com. ma n n i n g . u n lo c k i n g a n d r oi d ;

import c om.mann ing .unlo ckingandr oid.R;


im p o rt android.app .A ct ivity;
import java.t ext. Number Format;
impo rt a ndroid.uti l .Log;
II se omiten c i e r t as im p o r t a c i o ne s
publ ic c l a s s ChapterTwo extends Acti vity
pub lic static final String tag = " Ch a p t e r 2";
@Override
p ub l i c vo id o nCr e at e (Bu n d l e i cic le)
super . onCrea te(i cicle );
se tContentView (R.layout. main) ;

f i nal EditText mea lprice fie ld =


(Ed i tTe x t) findViewBy ld (R. id.mea lprice ) ;
f i na l TextView ans wer fie ld =
(Te x t Vi e w) f indViewBy ld (R . id .answer );

f inal Button bu tto n = (But t o n) fi ndVi ew Byld (R .id .cal culate ) ;


butto n .setOnCli ckListener (new Button .O nCl ickListener ( ) (
pub li c v o i d o nC lick (View v ) {
Tr y (
II Re a l i z a r acc ió n al hacer c lic
Log .i (tag , "onC lick invoked . " ) ;
II o bte ne r preci o de la comida de l a I U
St r i n g mea lprice =
me a lpri cefield .getText() . t o S t r ing( );
Log .i(tag , "mea lprice is [ " + me al p ri c e + "JO);
S t r i n g an swe r = " 11;

II c omprobar s i e l precio incluye un s í mbo lo " S"


if (me al pr i c e. i n d e x Of(" S") == -1)
mea lprice = 11 $11 + me alp ri c e;

fl oat f mp = O.OF;
I I o b t e ne r formato de mo n e d a
NumberFormat nf =
java .te xt .N umber Format .getCurre ncy l nstan ce ();

II o b t e ne r prec io de ent rada d e l a c o mida


f mp = nf .parse (mealprice ) . f l o a t Va lu e();

II dej ar una buena propin a - > 20 %


fmp *= 1 . 2;
Log .i (tag, "Total Meal Pr ice (un f o r ma t t e d) is [" + f mp + "lO);
II aplicar f ormato al r esultado
Android. Guía para desarrolladores EII
ariswe r = " Fu ll Price, I nc l u d i ng 20 % Tip : " + nf. format ( f mp);

I I mostrar la respues ta
answerfield.setText (answer);

Log .i(tag , "onClic k complete . " ) ;


catch ( j a v a. t e x t . Pa r s e Ex c e p t i o n pe )
Log.i (tag , "Parse except ion caught " ) ;
answerfield .setText ("Fai led to parse amount? ");
c atch (Ex c e p t i o n e ) {
Log.e (tag , " Fa il e d to Ca lculate Tip :" + e.getMessage ();
e .printStackTrace ( ) ;
a nswe rfiel d.setText (e.getMessage ( ) );

}
j) ;

Examinaremos la aplicación paso a paso. Como en muchas aplicaciones de Java, esta


clase contiene una instrucción que identifica el paquete al que pertenece: com. manning .
unlockingandroid. Esta línea con el nombre del paquete se ha creado con el asistente.
Importamos la clase c om. manning . unl ockingandroid. R para acceder a las de-
finiciones utilizadas por la IV. Este paso no es obligatorio, ya que la clase R forma parte
del mismo paquete; sin embargo, conviene incluirla ya que facilita la lectura del código.
Además, la clase R incorpora determinados elementos IV, algunos de los cuales se ana-
lizarán en un apartado posterior.
Se necesitan varias importaciones para resolver los nombres de clase; la mayoría de
instrucciones de importación se han omitido por motivos de brevedad. Una de las im-
portaciones mostradas contiene la definición de la clase java. text. NumberForma t,
utilizada para aplicar formato y analizar valores de moneda.
También se importa la clase android. util. Log, que se utiliza para realizar en-
tradas en el registro. Al invocar métodos estáticos de la clase Log se añaden entradas al
registro, que se pueden ver a través de LogCat en la perspectiva DDMS. Al realizar en-
tradas en dicho registro, es aconsejable incluir un identificador consistente en un grupo
de entradas relacionadas con una cadena común, lo que normalmente se denomina eti-
queta. Podemos filtrar este valor de cadena para evitar desplazamos por cientos o miles
de entradas LogCat para encontrar mensajes de depuración o informativos.
Conectamos el elemento IV que contiene mealpri ce a una variable de nivel de clase
de tipo Edi tTe xt, mediante la invocación del método findVi ewById, al que pasamos
el identificador de mealprice, definido por la clase R generada automáticamente, de
R. java. Con esta referencia, podemos acceder a la entrada del usuario y manipular los
datos del precio de la comida introducidos por éste . Del mismo modo, conectamos el
elemento IV para mostrar la respuesta al usuario, de nuevo mediante la invocación de
findVie wById.
Para saber cuándo calcular la propina, necesitamos una referencia a Button, para
añadir un oyente de eventos. Necesitamos saber cuándo se ha pulsado el botón. Para
ello, añadimos un nuevo método OnCli ckLi stener con el nombre onCl ic k.
lIfII 2. Entorno de desarrollo

Al invocar el método o nCl ic k, añadimos las primeras entradas de registro po r medio


del método estático i () de la clase Lo g. Este método añade una entrada al registro con la
clasificación I n f orma ti on. La clase Lo g contiene métodos para añadir entradas al regis-
tro para distintos niveles, como Verbo s e, Debug, Info rm a ti on, Wa rning y Er r or.
Una vez obtenida la referencia al elemento mealpri c e, podemos obtener el texto
introducido por el usuario con el método ge t Text () de la clase EditT e xt. Como
preparación al formato del precio, obtenemos una referencia al mecanismo estático de
formato de la cantidad.
Seamos generosos y ofrezcamos una propina del 20 por 100. Tras ello, aplicamos for-
mato al precio total, incluida la propina. Seguidamente, con el método se t Text () del
elemento a ns we r f ie l d , actua l i z a rnos la fU para indicar al usuario el precio total.
Como este código puede generar un problema con datos en formato incorrecto, es
aconsejable incluir la lógica en bloques try / ca tch para evitar comportamientos inespe-
rados de la aplicación. El proyecto cuenta con archivos adicionales pero en este cap ítulo
nos centraremos en modificar la aplicación para que funcionen las opciones personali-
zadas. En cuanto dispongamos de los archivos de código fuente, Eclipse los compila de
fondo. Si se producen errores, se enumeran en la vista Problems (Problemas) de la pers-
pectiva Java y se marcan en el margen izquierdo con una x de color rojo.

Con las herramientas de línea de comandos del SDK de Android puede generar la
aplicación por lotes sin necesidad del IDE. Es un enfoque muy útil para empresas de
software con una función concreta de gestión de configuración y la necesidad de rea-
lizar generaciones automatizadas. Además de las herramientas específicas de Android,
necesitará un kit de desarrollador de Java (JDK), la versión 5.0 o superior, para generar
aplicaciones desde la línea de comandos .Aunque esta operación se escapa a los objetivos
del libro, puede consultar manuales concretos al respecto.

Siempre que no haya errores en los archivos de código, las clases y los archivos IU
se compilarán correctamente. Pero falta algo por hacer antes de ejecutar el proyecto y
probarlo en el emulador.

Generar la aplicación
Llegados a este punto, la aplicación se ha compilado y ya se puede ejecutar en el dis-
positivo. Veamos qué sucede tras la compilación. No es necesario realizar estos pasos,
ya que los ADT se encargan de todo pero es aconsejable comprender lo que sucede entre
bastidores.
Recuerde que a pesar de la dependencia de Java en tiempo de compilación, las apli-
caciones de Android no se ejecutan en una máquina virtual de Java, sino en la máquina
virtual Dalvik. Por ello, los códigos Java creados por el compilador de Eclipse deben
convertirse a formato. de x para utilizarlos en Android. El SDK contiene herramientas
para realizar este proceso pero el ADT se encarga de ello.
Android. Guía para desarrolladores ..

El SDK contiene herramientas para convertir los archivos del proyecto en un archivo
listo para su ejecución en el emulador de Android . La figura 2.10muestra el flujo genera-
lizado de archivos en el proceso de generación de Android. Si recuerda de un apartado
anterior, en tiempo de diseño se utiliza la herramienta aapt. Los archivos XML se pro-
cesan en aapt y como resultado se crea el archivo R. java; recuerde que tenemos que
hacer referencia a la clase R para identificadores de interfaz de usuario al conectar nuestro
código a la IV. Los archi vos de Java se compilan inicialmente en archivos de clase en el
entorno Java, principalmente por Eclipse y JDT. Una vez compilados, se convierten en
archivos dex para su uso en la máquina virtual Dalvik. Los archivos XML del proyecto
se convier ten en una representación binaria, no en texto como podríamos esp erar. Sin
embargo, conservan su extensión . xml en el dispositivo.

layout.xml
c:::::> Ejava

c:::::>
EJ c:::::> OO
·.class

EJ ~
/
Android- Archivo
Manilest.xml applicalion.a pk
'---__-"v
Figura 2.10. ADT utiliza herrami entas del SDK de Android para converti r archivos de códig o
fuente en un paquete listo para su ejecución en un dispositivo de Andr oid o en el emulador.

Los archivos XML con vertidos, una forma compilada de los recursos Drawab le y
Value, y el archi vo DEX (cl a s s e s . dex) se comprimen por medio de aap t en un ar-
chivo con el nombre nombrep r oye c t o . apk. El archivo resultante se puede leer con
un lector compatible con p k z i p, como WinRAR o WinZip, a j ar de Java . La figura 2.11
muestra la aplicación de este capítulo en WinRAR.
Ya podemos ejecutar la aplicación en el emulador de Android. Debe acostumbrarse
a trabajar en un entorno de emulación siempre que programe software. Hay muchas
razones para utilizar un emulador de calidad para las fases de desarrollo y pruebas.
Una es que disponer de varios dispositivos reales con planes de datos resulta muy
costoso. Uno de estos dispositivos puede costar cientos de euros. Android consegui-
rá asentarse en tre los distintos operadores y en multitud de dispositivos, cada uno
con prestaciones diferentes. Disponer de todos estos dispositivos no resulta práctico,
a menos que disponga de un enorme presupuesto, como sucede con las grandes em-
presas de software. Para el resto, basta con un par de di spositivos y el emulador de
Android. A continuación nos centraremos en las ventajas del desarrollo móvil basado
en emulador.
E!II 2. Entorno de desarrollo

1l1!l Chapter2 .a p'k.ziR·WinRAR( cop';¡;iaoo:d;:.;"=V1llu:.:::ac::.:;i6~n,):........, _ _ ~ -'~~~


Archivo Órdenes Herramienta. Favorito. O tio neo ~ }

I~
~ Añadir
....:.
m~
Ext.ruren Comprobar
~. Ver Eliminar Buscar
.
lJ)'1 tic
Asistente Información Buscar viru s Comentario SFX

Nombre "O- Tamaño Comprimido Tipo Modificado eRen


•. Pólder
assets Folder 2110712009 12:35
re. Folder 03/08/2009 12:08
J¡: src Folder 03/0 8/2009 12:08
~ .c1a ssp ath 265 156 Fichero c1a sspath 30/ 06/2009 14:13 1425F90C
~ .proj ect 844 256 Fichero project 30/06/200914:13 A9BDE611
~ AA d roid Ma njfe s ... 5B 270 XMl Document 30/06/2009 14:13 B3DA8DFC

Total 3 carpeta. y L642 byte. en 3 fichero.

Figura 2.11. El formato de archivo de la aplicación Android es compatible con pzip .

El emulador de Android
Aunque la mejor prueba de una aplicación consiste en ejecutarla en el hardware para
el que se ha diseñado, el emulador facilita considerablemente el trabajo del programador.
Al trabajar en un entorno de emulación, el ciclo de compilación, ejecución y depuración
es más rápido que al probar en un dispositivo de hardware real. Se suele tardar más en
sincronizar o copiar una aplicación en un dispositivo real que en iniciar una sesión del
emulador. Además, es más sencillo limpiar el sistema de archivos de un emulador que
realizar la misma operación de mantenimiento en un dispositivo real. La posibilidad de
programar comandos en el emulador y desde éste es una opción que tener en cuenta.
Además de ser una herramienta más rápida que el dispositivo real, el emulador debe
tener en cuenta características físicas del dispositivo, en especial el tamaño de la pantalla,
los dispositivos de entrada y la conectividad de red.

Skins
No todos los dispositivos móviles tienen el mismo equipamiento, de modo que hay
que acomodar y probar diferentes características en el entorno de emulación. El SDK de
Android incorpora un emulador con diferentes skins, que representan distintos diseños
de hardware así como orientaciones (horizontal y vertical). La figura 2.12 muestra dos
emuladores: uno en vista vertical con un teclado QWERTY oculto y otro en modo hori-
zontal con un teclado visible. En su SDK las opciones pueden variar.
No sólo debe comprender y acomodar el aspecto del dispositivo, sino también las
opciones de conectividad que ofrece. Seguramente haya probado una aplicación móvil
en una zona con excelente cobertura de datos y descubierto que la ubicación en la que
Android. Guiapara desarrolladores ..

implementa la aplicación tiene un servicio de datos escaso. La po sibilidad de probar


esta condición en el entorno de desarrollo ofrece una gran ven taja al programador.
Afortunadamente, el emulador de Android permite realizar este tipo de pruebas, como
veremos en el siguiente apartado.

Figura 2.12. El SDK de Android incluye diferentes diseños para probar varias configuraciones .

Velocidad de red
La simulación de la velocidad de la red es un elemento esencial del desarrollo de soft-
ware para mó viles . Esta función es muy útil ya que la expe riencia del usuario variará
durante el uso real, y es importante que las aplicaciones móviles se degraden con ele-
gancia ante la falta de una conexión de red fiable. El emulador de Android proporciona
un amplio conjunto de herramientas de emulación para probar distintas conexiones y
velocidades de red. La tabla 2.1 muestra las condiciones de velocid ad y latencia dispo-
nibles en el emulador.
Tabla 2.1. El emulador de Android admite diferentes opciones de velocidad de red .

Velocidad completa . (Se utiliza la conexión Ninguna


a Internet del entorno de desarrollo)
GSM GPRS
EIII 2. Entorno de desarrollo

HSCSD EDGE

GPRS UMTS

EDGE
UMTS
HSPDA

En el emulador de Android se pueden probar las principales funciones de la aplicación


en el entorno de red de máxima velocidad, ya que las pruebas se ejecutan cientos e incluso
miles de veces antes de comercializar un producto. Si tuviéramos que compilar la aplicación,
sincronizarla con el dispositivo y ejecutarla en una red inalámbrica de datos, aumentaría el
tiempo de pruebas, se reduciría el número de pruebas y se elevarían los costes asociados.
Peor todavía, los retos de las pruebas de conectividad de datos móviles harían que mini-
miz áramos las pruebas de la aplicación. Si tenemos en cuenta que la mayoría de plazos
de desarrollo de software son agresivos, cada minuto cuenta, de modo que un emulador
de calidad es muy útil para que el desarrollo de aplicaciones móviles resulte rápido yeco-
nómico. Además, conviene recordar que en un plan de comunicaciones móviles puede
haber cargos por consumo de voz y datos. Imagine tener que pagar por kilobyte en todos
los paquetes de datos descargados al probar un nuevo reproductor de audio.

Seguramente haya escuchado los términos emulador y simulador utilizados indistin-


tamente. Aunque su función es parecida, la prueba de aplicaciones sin hardware real,
no son términos idénticos. Un simulador crea un entorno de pruebas que se comporta
prácticamente igual que el entorno real; no obstante, es una aproximación de la pla-
taforma real. Esto no significa que el código dirigido a un simulador se ejecute en un
dispositivo real, ya que solamente es compatible a nivel del código fuente. El código
del simulador se crea para ejecutarse como programa de software en un equipo de
escritorio con DLL de Windows o bibliotecas de Linux que imitan las interfaces de
programación de aplicaciones CAPI) del dispositivo real. En el entorno de generación,
se suele seleccionar el tipo de CPU de destino, que suele ser x8 6/S i mu l a to r . En un
entorno de emulación, el destino de los proyectos es compatible a nivel binario. El
código funciona en el emulador yen el dispositivo real. Evidentemente, determinados
aspectos difieren, como la implementación de funciones concretas. Por ejemplo, en un
emulador la conexión de red se ejecuta a través de la tarjeta del equipo de desarrollo,
mientras que en el teléfono real lo hace sobre una conexión inalámbrica como una red
GPS, EDGE o EVDO. Es preferible utilizar emuladores ya que nos preparan con mayor
fiabilidad a la ejecución del código en dispositi vos reales . Afortunadamente, el entorno
disponible para los programadores de Android es un emulador, no un simulador.
Android. Guia para desarrolladores ..

El SDK de Android contiene un programa de línea de comandos, em ulaior, para eje-


cutar el emulador. Dispone de numerosos parámetros que nos permiten personalizar el
entorno del emulador: su aspecto y su comportamiento. Algunas de estas opciones se
mues tran en el lOE Eclipse a través de l com plemento ADT. Nos centraremos en el uso
del emulador Android para Eclipse, pero puede examinar las opciones de línea de co-
mandos disponibles ya que le serán muy ú tiles para crear aplicaciones más complejas.

Perfiles de emulador
Nuestra aplicación de ejemplo se ha compilado satisfactoriamente y el siguiente paso
consiste en ejecutarla en el emulador de Android.
Crearemos un nuevo perfil de emulador para reutilizar la configuración del en torno
de pruebas. El punto de partida es el menú Run (Ejecutar) de Eclipse (véase la figura
2.13). Puede que en su versión de Eclipse la pantalla sea diferente.

.D Javo - Eclip,. '"'"


Fil. Edit Runl Novig.t. Seerch Project Renctor Window Hefp_ _

: r3 fu Q:,
y
Run Ct~.Fll
1lí'l3
~ Debuq
~
FU f----
- - ~

i
Run History • f-
¡P Ch RunAs •
6!!l Run Configurations... _ ~
- -
~
Debuq History •
l?- O.bugAs •
Debug Configurations...
--- - -
O T09gle Bteakpoint CtJl.Sh ift . B
o T0 9g l~ Une Breekpoint
~ Q Togg le Method Bree kpo int
~ Togg le Watchpoint
&. Sldp AII Breekpoints

DI' fjc Remo ve .AJIBreakpoint s


12 AddJava Exception Breekpcint,..
G AddClass l oad Breekpoint,..
l6J AlI Refetences...
..) All Instant es... Ctrh Shift+N
~;'! Watch
lospect Ct,I. Shift. I
í. Display etrl +Shift+O
/)¡l Execute CtJl. U
Force Return A1t+Shift+f
Step Into Setectic n

q. Externallools •
Figura 2.13. Creación de una nueva configuración para probar la aplicación Android.

El objetivo es crear una nueva configu ración de inicio (véase la figura 2.14). Para ello,
seleccione la entrada Android Application en la lista de la izquierda y haga clie en
el botón New Lau nch Config uration.
EII 2. Entorno de desarrollo

• ~ Run Configur.tion s

Creete, menaqe. and ron configuratioos


~1
Android Application I

W~ x l 'B ~ . Configure launch settings from this dialog:

I ~ New launch configuration ~ [j - PreS5 the 'New' button to créet e a configuration of the select ed type.
~ ( (Ir ~ d r o i d
Application ~ - Press the 'Duplicete' button to copyth e selected configuration.
el New_configuratjon (
J~ Android JUnit Test X - Press t he 'Delete' button to remove the selected configuration.
E'll Java Applet ::!p - Press the 'Filter' button to configurefilteringoptions.
W Java Application
Jl1 JUnit • Editorview an existing configuration by selecting it.
JJ.) Task ContextTest
Configurelaunch perspective settings from the persoH tives preferent e page.

, 1- m I ,
Filter matched 7 ef 7 items

® I Run 11 Close
1

Figura 2:14. Seleccione la plantilla de ejec ución de aplicaciones Android .

Truco
Si tiene problemas para generar la aplicación, puede solucionar los errores sintácticos
que lo impiden. En Eclipse puede ver los errores marcados con una x de color rojo
junto a la línea del archivo. Si los errores persisten, asegúrese de haber configurado
correctamente el entorno de generación. En uno de los apéndices encontrará más
información al respecto .

Asignaremos un nuevo nombre a la configuración para reconocerla con facilidad.


Como en el menú habrá varias configuraciones, el nombre debe ser exclusivo y fácil de
identificar. En el ejemplo utilizamos Android Tip Calculator (véase la figura 2.15). Hay
tres fichas con opciones de configuración. En la primera puede seleccionar el proyecto
y la primera actividad que iniciar.
En la siguiente ficha puede seleccionar el skin, que incluye el diseño de la pantalla,
la velocidad de red y la latencia de ésta. Además, puede pasar parámetros de línea de
comandos al emulador (véase la figura 2.16). Al crear aplicaciones de Android, recuerde
que se pueden ejecutar en pantallas de diferentes tamaños ya que no todos los disposi-
tivos tienen las mismas características físicas . Este parámetro es muy útil para probar el
comportamiento de la aplicación con distintos tam añ os y diseños de pantalla.
A ndroid. Guía para desarrolladores

-
~ Run Config un tio n, ~
I
~
Cre ate . me naqe. en d run co ntiq urat io ns
AndroidApplication

Ci ~ x lfj ::;::' · Name: ft.ndroid Tip Calculator


Ityp e filter text I r~ Androi ~ mi T"get ) bl Common ]
1
g l Android Application
Projecu -
Q, New_configuration (
J~ Android JUnit Test
E')J Jeva Ap plet
W Java Application
Ch. pter)

l aunch Action:
(J l . unch Defau lt Activity
I BrOW5t ...
1
i'
11
J u JUnit
JU Ta s ~ ContextTest I~) Launch: Icom.manning.unlock"ingandroid.ChapterTwo ·1
lE) Do Noth ing 11

--= 1I1 ==1 ,


.. 1___

Filtermatched 1 of 1 items
I Apply 1I Reve tt I

® I Run 11 Clcse
'1
Figura 2.15. Configuración del inicio.

...
'
~ Run Configurations ~
Craate. menaq e. and run conf iguro.tion s I
AndroidApplication

Ci ~ X lfj 1f> . . Name: Android Tip Calculatcr


Itype filter text I el Andr oid 1I1 t Targ eUl.,.m Common I
q AndroidApplication
el New_configuration( Deployment Target Selectio n Mode
J~ ~ roid J Un¡t Test () Manuel
E')J Java Applet t§:, Automatic
mJava Application Select a p referred Android Virtual Device for de ploymenc
J u JUnit
J D Task Context Test AYD N. me Tarq et Nam e Platf... APllevel
0-- No AVD availa ble .. ..

l AYO M.n.ger... ¡
~

Emulator launch pa rerne t ers: - - -- - ---- -----

Networ k Speed.
~
Networklaten cy: I None .... 1
EJ Wipe User Data

- 1~. ~ I , ¡
'"
Filter met che d 7 of 1 ítems
1
App ly 1I Revert

I
I ,

Run 1I Close
,-
Figura 2.16. Selecc ión de las características operat ivas del emulador Android .
.. 2. Entorno de desarrollo

En la tercera ficha puede añadir esta configuración al menú de favoritos de Eclipse


(véase la figura 2.17). Puede seleccionar Run y/o Debug . Seleccione ambas, ya que faci-
lita el inicio al probar o depurar una aplicación.
~

l~.; R~~Configu~tion'
Craate. me na qe. and run conüquretions
~J
AndroidApplication

Cl 1m )( I El !f- ... Nemer Android Tip Calculatar


Itype filter text I El Andr oid flJ Torget (19 Comm o ~
q Android Application
Seve as -- -
le¡. New_configuration(
~ ~

J~ Android JUnit Test .-ª) l ocal file


~ Ieva Applet
mJava Appli cation
() Shared file: I 11 Browse.., 1
- - -
Ju JUnit Display in favorites menu Console Encoding
JJ:j T..k Context Test @ Default • inhented (Cp1252)
iR] § Debug i
R] O Run Ot her IISO-S359-1
·1

Standard Inputand Output


~ AJlo ( ate Console (necess ary for input)
Ll File:
I 1
1 Worb pace... 11 File Syrtem... 1I Variables...
1
O Append

~ Launch in background

. \- "'..-.... I ,
Filtermatched 7 of 1 items
I Apply 11 Revert 1

® I Run 11 Clo, e JJ
,--
Figura 2.17. Añadir la configuración de inicio al menú de herramientas.

Ya puede iniciar el emulador de Android para probar la aplicación. Para ello, selec-
cione la nueva configuración de inicio en el menú de favoritos (véase la figura 2.18).
La aplicación Android Tip Calculator se ejecutará en el emulador de Android.
Adelante, pruébela. Seguramente se pregunte qué hacer si hay un problema con el có-
digo pero no sabe dónde. Analizaremos brevemente la depuración de una aplicación
de Android.

Depuración
La depuración de una aplicación es algo sin lo que los programadores no podrían
sobrevivir y, afortunadamente, en Eclipse resulta muy sencillo depurar aplicaciones de
Android. En primer lugar debe cambiar a la perspectiva Debug de Eclipse . Recuerde
que para cambiar de perspectivas debe utilizar el submenú Ope n Perspective del menú
Android. Gl/ía para desarrolladores ..

Window. El inicio de una aplicación de Android para su depuración es tan sencillo como
su ejecución. En lugar de seleccionarla en el menú de ejecución, utilice el de depuración
(con el icono de un insecto). Recordará que al crear la configuración de inicio la añadi-
mos a ambos menús.

~ Jaw · Eclip'se
File Edit Run Source Navigate Seerch Project Refactor Window Help

: r~ · ~;1:; 1 ~ Iil : 18 t"t ¡ 'l;J. . O d¡' . : ® ll:1 G' . : ~ c9 .4 ' ·


~ Package Exp &3'- 1: Hierarchy) = El CI 1 Andr oid Tip Calculator
~
ti ~ I :. 7
Ru nAs •
• Ea Chapter2 RunConfigurations...
• ftl src Organize Favcrites...
v al com.manning,unlockingandro
fe. assets
• 10 Chapte r2.apk
C8- asset s
1) Q7 res
ti 127 src
A' AndroidManifest.xml
~ ~ re s
~ e;. drawable
~ (30 layout
t> l2:7 values
6' AndroidManifest-xml

Figura 2.18. Inicio de la aplicación de ejemplo.

La perspectiva Debug incluye funciones de depuración similares a las de otros en-


tornos de desarrollo, como la posibilidad de examinar invocaciones de métodos y valo-
res de variables. También se pueden definir puntos de interrupción si se hace doble clie
en el margen izquierdo de una línea. La figura 2.19 muestra el análisis del proyecto Tip
Calculator y los valores resultantes en la vista LogCat. El precio de la comida, incluida
la propina, todavía no se muestra en el emulador, ya que no se ha alcanzado esa línea.
Una vez completado el ciclo de generación de una aplicación de Android y después
de examinar las herramientas de desarrollo, ya podemos adentrarnos en los aspectos
fundamentales de la creación de aplicaciones de Android.

Resumen
En este cap ítulo hemos presentado el SDK de Android y hemos visto los paquetes
de Java que incluye para familiarizarnos con sus contenidos desde una perspectiva de
biblioteca de clases. Hemos descrito las principales herramientas de desarrollo de aplica-
ciones Android, incluido el lOE Eclipse y el complemento ADT, así como algunas de las
herramientas subyacentes del SDK. Durante la creación de la aplicación de este capítulo,
Android Tip Calculator, hemos visto las distintas perspectivas disponibles en Eclipse.
Hemos utilizado la perspectiva Java para desarrollar nuestra aplicación y las perspecti-
vas DDMS y Debug para interactuar con el emulador con la aplicación en ejecución. El
conocimiento de las perspectivas de Eclipse le resultará muy útil para desarrollar apli-
caciones y comprender conceptos presentados a lo largo del libro .
.. 2. Entorno de desarrollo

Hemos descrito el emulador de Eclipse y algunas de sus principales características.


Resulta muy útil para probar y validar aplicaciones de software para móviles de forma
coherente y económica.

f* __&M. ~ _ ~.:! Sutd• • _~ . '~ ......... Ht'p

'tJ t1th "


Q
Q
tI,
n - ~ "" I" ~ » 4 : " - 0 - '1.- ~ ", ~ - . "- . " • •_ ", -._

N.hod T... Cok>J_ I~"'Hk_1


.....:l~ T'l' ( .. «... ~ I"""MAf ~o';>Onl
IX .. . : ~ l .. ~ .~ ::1:'. 1 ;¡o ... ""1:I1....v....t4. i1 ~. " . ~ poontJ l

G1l@ <DO(!) I + ~ - IIl ~ ~ C

,.
. ,:::---- I ',~:==. = = = = - '11

Figura 2.19. La perspectiva Debug permite examinar línea a línea una aplicación de Android.

A partir de aquí, nos adentraremos en los elementos esenciales del SDK de Android
y del desarrollo de aplicaciones.
Part 11
u r I SD
deA r d

El SOK de Android proporciona un amplio conjunto de funciones que permiten a los


programadores crear multitud de aplicaciones. En esta segun da pa rte examinaremos las
principales partes del SOK e incluiremos ejemplos prácticos de cada capítulo.
Comenzaremos con el ciclo de vida de las aplicaciones e interfaces de usuario, para
después pasar a 1 n ten t y servicios. Examinaremos los métodos de persistencia y alma-
cenamiento disponibles y, en el mundo actual, los servicios Web y de redes.
Como la plataforma Android es un teléfono, entre otras cosas, describiremos sus
prestaciones de telefonía. A continuación, pasaremos al apartado sobre notificaciones y
alarmas. También no s detendremos en los gráficos y animaciones de Android, así como
en sus elementos multimedia.
Esta segunda parte del libro concluye con el análisis de los servicios basados en ubi-
cación disponibles para el programador de Android.
3
I traces
de usuario
Una vez presentados los componentes principales de la plataforma Android y el
entorno de desarrollo, nos centraremos en los conceptos fundamentales relacionados
con actividades, vistas y recursos. Las actividades son esenciales ya que, como vimos
en un capítulo anterior, forman las pantallas de la aplicación y desempeñan un papel
fundamental en su ciclo de vida. En lugar de que una aplicación arrebate el control del
dispositivo al usuario y a otras aplicaciones, Android presenta un ciclo de vida bien
definido para gestionar los procesos según sea necesario. Por ello, debe comprender
no sólo cómo iniciar y detener una actividad de Android, sino también cómo pausarla
y reanudarla. Las actividades están formadas por componentes secundarios denomi-
nados vistas.
Las vistas son lo que ven los usuarios y con lo que interactúan. Controlan el diseño,
proporcionan elementos de texto para etiquetas e información, botones y formularios
para entradas del usuario, y dibujan gráficos en la pantalla. También se utilizan para
registrar oyentes de eventos de la interfaz, como los de controles de pantallas táctiles.
Se utiliza una colección jerárquica de vistas para formar una actividad.
Somos los directores de la orquesta, la actividad es la sinfonía y los objetos Vi ew son
los músicos.
Siguiendo con la analogía, los músicos necesitan instrumentos, los recursos de
Android. Las vistas y otros componentes de Android utilizan cadenas, colores, estilos y
gráficos, que se compilan en formato binario y se ofrecen como recursos a las aplicacio-
nes . La clase R. java, generada automáticamente, constituye una referencia a recursos
concretos y actúa de puente entre las referencias binarias y el código fuente. Por ejemplo,
la clase R se utiliza para obtener una cadena o un color y añadirlo a una vista. La figura
3.1 muestra la relación entre actividades, vistas y recursos.
.. 3. Interfaces de usuario

Aclivity

View (eliquela
de lexlo) I[ View (enlrada de lexlo)

.
View (enlrada de selección)

View (mapa) View (imagen)

\ (

"-----
View (balón) )
./

e Recursos )
Manifest
(definición de la aplicación. activldad es, permisos. inlenl

Figura 3.1. Diagrama de la relación entre Activity, View, recursos y el manifiesto


que muestra que las actividades están formadas por vistas y éstas utilizan recursos .

Junto con los componentes que forman una aplicación (vistas, recursos yactivi-
dades), Android incluye el archivo de manifiesto presentado en un capítulo anterior,
An d roi dMa n ifes t . xrnl. Este archivo XML describe dónde empieza la aplicación, qué
permisos tiene y qué actividades incluye (así como servicios y receptores, como veremos
más adelante). Como este archivo es esencial en todas las aplicaciones de Android, lo
analizaremos detalladamente en este capítulo y lo mencionaremos a lo largo del libro .
El manifiesto permite a la plataforma iniciar y gestionar su aplicación.
Por lo general, si ha programado con ID en otras plataformas, los conceptos que ac-
tividades, vistas y recursos representan le resultarán familiares, al menos en un nivel
fundamental. Su implementación en Android es un tanto exclusiva, lo que esperamos
aclarar. Presentaremos una aplicación de ejemplo para desarrollar estos conceptos, co-
menzando con el código necesario para generar una actividad.

Crear la actividad
A lo largo del capítulo, crearemos una sencilla aplicación que permite al usuario bus-
car críticas de restaurantes en función de su ubicación y el tipo de cocina que ofrecen.
Esta aplicación, RestaurantFinder, también permite llamar, visitar el sitio Web o con-
sultar el mapa para llegar a un restaurante seleccionado. Hemos elegido esta aplicación
Android. Guia para desarrolladores lIiII
como punto de partida po r su sencillez y porque implica muchos de los aspectos de la
plataforma Android. De este modo abarcaremos numerosos conceptos y, además, tiene
una aplicación pr áctica en el teléfono.
Para crear la aplicación nece sitamos tres pantallas básica s iniciales:

• Una pantalla de crite rios donde el usuario int roduzca parámetros para buscar
restaurantes.
• Una pantalla de lista de críticas que muestre resu ltad os paginados que coincidan
con los criterios seleccionados.
• Una p ágina de detalles con la información del restaur ante seleccionado.
Recordará de un capítulo anterior que una pantalla es similar a una activ idad, de
modo que necesitamos tres clases Activity. Al finaliza r, las tres pantallas de la apli-
cación RestaurantFinder serán similares a las que la figura 3.2 muestra.

Figura 3.2. Pantallas de la aplicación RestaurantFinder,


con las actividades ReviewCriteria , ReviewList y ReviewDetail.
.. 3. Interfaces de usuario

El primer paso del análisis de actividades y vistas consiste en crear la pantalla


ReviewCriteria, para después pasar a las siguientes. En el proceso, destacaremos dife -
rentes aspectos del diseño e implementación de la IU.

Crear una clase Activity


Para crear una pantalla ampliamos la clase base android. app. Activi t y, como
hicimos en un capítulo anterior y sobrescribimos los métodos principales que define. El
listado 3.1 muestra la primera parte de la clase ReviewCri teria.
Listado 3.1. Primera mitad de la clase ReviewCriteria.
pub lic c lass ReviewCriteria e xte nds Act ivity

pr iva te s ta t ic fi na l i nt MENU GET REVIEWS Men u .FIR8T ;


pr ivate Spinner c uisine ;
private Button grabReviews;
private Edit Tex t location ;

@Override
p ub lic vo id o nCreate (Bu nd le savedl nstanceState )
s uper.onCreate (saved lnstanceState );

this.setContentV iew (R . layou t.review_cr iteria );

th is. location = (Edi t Text )


f i nd Vi e wByl d( R . id.l o c a ti on };
t h i s . c u i s in e = (Sp i nne r)
findVie wByld (R .id .cuisine } ;
this .grabReviews = (Bu tto n)
f i ndV ie wByld (R .id .ge t_rev iews_bu tto n );

Ar rayAdapte r<S tri ng> cuisi nes =


ne w ArrayAdapte r<String> (this , R . layout.spin ne r_view ,
getResources ( } .
getStri ngArray (R .array.cu isi nes » ;
c uisines .setDropDownVie wResource (
R. layout .spi nner_v iew_dropdown ) ;
thi s . c ui sin e . s e t Ad apt e r( c u i s in e s) ;

t his .g ra bRev iews . setOnC l ic kListe ner (


ne w OnClic kLi s tener ( ) {

pub lic vo id onCl ick (View v ) {


hand leGetReviews ( ) ;

}) ;

Esta clase amplía andr oid. app. Acti v i ty, que realiza diferentes acciones: asig-
na un contexto a la aplicación, ya que Acti v i t y amplía por su parte android. app .
ApplicationContext, introduce los métodos de ciclo de vida de Android; define
una estructura para iniciar y ejecutar la aplicación, y proporciona un contenedor para
añadir elementos Vi ew.
Android. GI/ía para desarrolladores .mil
Como Act i v i t y representa una interacción con el usuario, debe proporcionar com-
ponentes a la pantalla: las vistas . En nuestra clase Rev ie wCri te ria hacemos referen-
cia a tres vistas: l o ca ti on, c uis i n e y g rabRevi ew s . Lo c ati on es el tip o de vista
conocida como Edi tT e xt, un componente básico de entrada de texto. Cu isi n e es un
componente de lista de selección, denominado Spinner en Android y grab Re v iews es
un botón (Bu t t o n). Esto s elementos de vista se incluyen en una actividad con un diseño
determinado para crear una pantalla. Las vista s y los diseños se pueden defini r directa-
mente en código o un archivo de recurso XML. A lo largo de este apartado encontrará
más información sobre vistas.

Se preguntará por qué utilizamos la vista Edit Te x t para el campo de ubicación de


la actividad Revi e wCr i teria cuando Android cuenta con tecnología para obtener
este valor de la ubicación física del dispositivo (o permitir que el usuario lo seleccione
a través de un mapa, en lugar de escribirlo). Es intencionado. El objetivo es que este
primer ejemplo sea completo pero no demasiado complicado. En capítulos posteriores
veremos cómo utilizar las funciones de ubicación que incluye Android.

Tras iniciar una actividad con su s vistas necesarias, se invoca el método o n Cre a t e () .
Es uno de los métodos de ciclo vital que proporciona la clase Activ i t y . Toda actividad
sobrescribe onCr e a t e ( ) al invocar pasos de iniciali zación d e componentes, aunque no
todas las actividades sobrescriben otros métodos del ciclo de vida. En un apartado pos-
terior encontrará más información sobre esto s métodos.
Dentro del método onCr ea t e () , normalmente se asocia un archivo de di seño XML
al método s e tCon te n t View () . Decimos no rmalmente que no es imprescindible utili-
zar un archivo XML; podemos definir todo el d iseñ o y la configuración de vistas como
objetos Java dentro del código. No obstante, suele ser más sencillo utilizar un recurso de
diseño XML en cada actividad . El archivo XML define objetos Vi ew, que se disponen en
formato de árbol y se pueden configurar en la activida d.
Los detalles de diseño y vistas, definidos en XML o en el código, también se detallan
en apartados posteriores. Simplemente recordar qu e las vistas se suelen definir en XML
y después se establecen en la actividad. Las vistas que necesitan manipulación de tiempo
de ejecución, como la vinculación a datos, se pueden referencial' en el código y convertir
en los subtipos correspondientes. Las vistas estáticas, las que no hay que actualizar en
tiempo de ejecución, como las etiquetas, no se referencian en el código (aparecen en la
pantalla por formar parte del árbol View XML pero no necesitan configuración explíci-
ta en el código). Volviendo a las pantallas de la figura 3.1, comprobará que la pantalla
ReviewCriteria tiene dos etiquetas, ad emás de las tre s entradas ya mencionadas. Las eti-
quetas no aparecen en el código; se definen en el archivo review_ c r i t e r ia . x ml qu e
veremos al analizar los recursos definidos por XML.
A continuación, vinculamos datos a las vistas de lista s de selecci ón, los objetos
Spinner. Android emplea un concepto de adaptador espe cial para vincular listas que
conti enen colecciones a datos. Básicamente, Adap te r es un controlador de colección que
llmI 3. Interfaces de usuario

devuelve todos los objetos de la misma como View. Android proporciona diferentes adap-
tadores básicos: Li stAd apt e r, ArrayAda p te r , Ga l le ryAdap te r , Cur s orAdapt er y
otros muchos. También puede crear los suyos propios, una técnica que utilizaremos en un
apartado posterior. En este caso usamos Arra yAdapt er que se completa con Co nt ex t
(thi s) , un elemento Vi ew definido en un archivo XML de recursos y una matriz que
representa los datos (también definida en el archivo XML). Al crear Ar rayAd ap t e r ,
definimos Vie w como el elemento mostrado en Sp i n ne r antes de seleccionarlo; una
vez seleccionado se utiliza el elemento View definido en la lista desplegable. Una vez
definidos Ad a p te r y sus elementos Vi ew, los establecemos en el objeto Spi n ne r.
Lo último que demuestra esta actividad inicial es el primer uso explícito de proce-
samiento de eventos. Los elementos IV suelen aceptar distintos tipos de eventos, como
veremos en un apartado posterior. En este caso utilizamos OnCli ckL i sten er con el
botón, para responder al clic del botón.
Tras completar el método o nC rea t e () y con la vinculación de datos en las vistas
Spinner, disponemos de botones de menú (diferentes a las vistas But t on en pan-
talla, como veremos en breve) y acciones asociadas. El listado 3.2 muestra su imple-
mentación.

Listado 3.2. Segunda mitad de la clase Activity ReviewCriteria.

@Over r ide
pub lic boolean onCreateOpt ionsMenu (Menu menu ) (
s uper .onCreateOptionsMenu (menu ) ;
menu .add(O , RevieHCriteria.MENU_GET_REVIEWS, 0,
R. string . me n u_ g e t _ r ev i ews ) . setlcon (
android .R.draHable. ic_menu_ more );
return true¡

@Override
p ub lic boolean onMenultemSelected (int featureld , Menultem item ) (
sHitch (item .getltemld ( ))
case MENU GET REVIEWS:
handleGetRevieHs ( );
return true ;

r e t u r n super .onMenultemSelected (featureld , item) ;

private void handleGetRevieHs ()


if (! v a l i da t e ())
r e t u r n;

RestaurantFinde rApplication application


(Restaura ntF inderApplication )
getApp lication ( ) ;
application .setRevieHCriteriaCuisine (
this.cuisine.ge tSelected ltem {) .toStri ng ( ) );
application.setRev ieHCrite r iaLocation (
A ndroid. Gllía para desarrolladores II!JII
t hi s . loca t io n . g et Te x t ( ) . t o S t r i ng ( )) ;

I n t e n t intent =
new I n t en t( Co n s t a n t s . I NTENT_ ACT I ON_ VI EW_ LI ST) ;
startAct ivit y (inten t ) ;

p rivate b ool e a n va li da te ( )
boolean va lid = true ;
Str i ng Bu ilde r va l i dation Text ~ n e w St ringBui lder ( );
i f ( th i s . locat ion . g e tTe xt () == n ull ) I I
th i s . l o c a ti o n .ge t Te x t ( ) .to St r ing ( ) . equ a l s ( " " ) )
va li dat ion Text .appe nd (getResource s ( ) . ge t St r i ng(
R . stri ng . loca t io n_not_s upp lied_ message ) ) ;
va lid = false ;
}
if ( !v a li d) (
ne wAl e r t Di a l o g . Build e r l t hi s ) .
setTit le (ge tResou rces ( ) . getStr i ng (R .str l ng.a le rt l a b e l)) .
setMess age (va li da tio nText . toSt r i ng ( ) ) .
Set Posit ive Button ( " Conti n ue " ,
new a nd roi d.co nte n t . Dia loglnterfa ce .
OnClic kListe ne r ( ) (
publ i c v o i d o nC l ic k (
Di a log l n te r face di a l o g , int argl )
II b a sta co n mo s t r a r l a a lerta
)
) ) .shOl'I () ;
v a l i datio nTe xt n u l l;

retu r n v a l i d ;

Los elementos de menú de la parte inferior de las pantallas Activ i t y, véase la fi-
gura 3.2, se crean con el método onC rea teOpti onsMenu () . En este caso utilizamos el
método add () de la clase Menu para crear un único elemento Menultem. Pasamos un
ID de grupo, un ID, un orden y una referencia de recurso de texto para crear el elemento
de menú. También asignamos un icono al elemento de menú por medio del método s e -
tI con. El texto y la imagen se externalizan desde el código, por medio del concepto de
recursos de Android. El Menultem añadido duplica el botón en pantalla con la misma
etiqueta para Get Reviews.
Además de crear el elemento de menú, añadimos compatibilidad para realizar una
acción al seleccionarlo, en el método on Menu l temS e lec ted (), donde analizamos el ID
de los posibles elementos de menú con una instrucción cas e / swi tch. Cuando sabemos
que el elemento MENU_GET_ REVIEWS se ha seleccionado, invocamos el método handl e-
Ge t Re v i e ws, que añade la selección del usuario al objeto App lication y prepara la
invocación de la siguiente pantalla. Hemos incluido esta lógica en su propio método ya
que la utilizaremos en varios puntos, desde el botón en pantalla y desde Menultem.
Android utiliza internamente el objeto Appl i cation para distintos propósitos, y se
puede ampliar como hemos hecho con RestaurantFin derAppli cation (que inclu-
ye diversas variables miembro de tipo JavaBean), para almacenar información global.
II!PI 3. Interfaces de usuario

Volveremos a hacer referencia a este objeto en otras actividades para recuperar la infor-
mación almacenada. Existen varias formas de transferir un objeto entre actividades y el
uso de Appli cati on es una de ellas. Además, puede utilizar la base de datos SQLite
incorporada o implementar su propio Conte n t Prov i de r para almacenar los datos.
Encontrará más información sobre estos conceptos en un capítulo posterior. Lo importante
es que utilizamos el objeto Appl i cati on para transferir el estado entre aplicaciones.

En este caso hemos optado por usar Menu, además de los botones en pantalla. Aunque
ambos pueden funcionar en muchos casos, debe tener en cuenta si el menú, que se invoca
pulsando el botón Menu del dispositivo es adecuado para sus objetivos, o si resulta
más indicado un botón en pantalla. Por lo general, los botones en pantalla se vinculan
a elementos IV (un botón de búsqueda para formularios, por ejemplo) y los elementos
de menú deben utilizarse en acciones genéricas (envío de formularios o acciones como
crear, guardar, editar o borrar). Como toda regla necesita una excepción, si dispone
de espacio en pantalla, puede que a los usuarios les resulte más cómodo disponer de
botones en pantalla para las acciones. Lo más importante es que las decisiones de IV
deben ser coherentes. Si aplica una técnica en una pantalla, utilice el mismo enfoque
en el resto.

Una vez almacenado el estado de los criterios desencadenamos una acción con forma
de Intent. Ya vimos los Intent en un capítulo anterior y los ampliaremos más ade-
lante, pero básicamente lo que hacemos es solicitar a otra actividad que responda a la
selección de un elemento de menú por parte del usuario mediante la invocación de
startActivity (Intent intent) .

La forma más habitual de invocar una actividad consiste en utilizar el método


sta r t Act i v i ty () , pero existe otro método que utilizaremos en casos concretos:
s ta r t Ac t i v i t yForResul t () . Los dos pasan el control a una actividad diferente.
La diferencia con respecto a s t a r t Act i v i ty fo r Res ul t es que devuelve un valor
a la actividad actual cuando la actividad invocada se completa. De este modo puede
encadenar actividades y esperar respuestas (se obtienen al implementar el método
onAct i v i t yResul t ()).

Dentro del ejemplo ReviewCri teri a utilizamos AlertDial og. Antes de permitir
la invocación de la siguiente actividad, invocamos un sencillo método validate () en
el que mostramos un cuadro emergente de advertencia si el usuario no ha especificado
la ubicación. Además de ilustrar el uso general de Al ertDialog, demuestra cómo res-
ponde un botón a un evento de clic por medio de OnClickListener ().
Android. Guía para desarrolladores II!D
Hemos completado ReviewC ri ter ia, nuestra primera actividad. Una vez imple-
mentada esta clase, veremos el ciclo de vida Ac t ivi t Y de Android y su relación con
los procesos de la plataforma.

Habrá reparado en el uso del modelo Bu ilder al añadir parámetros al elemento


AlertDialog creado en la clase Rev iewCr i t e r ia. Si no está familiarizado con este
enfoque, básicam ente los métodos invocados, como Aler tDial og . setMessage () y
Alert Dialog . se t Ti t le () , devuelven una referencia a sí mismos (t.h s ), de modo
í

que podemos continuar encadenando invocaciones de métodos. Deeste modo se evitan


constructores con demasiados parámetros o la repetición de la referencia de clase en
el código. Los In t e nt también utilizan este modelo, que veremos repetidamente en
Android.

Explorar el ciclo de vida de las actividades


Todo proceso ejecu tado en la plataforma Android se incluye en una pila . Al utili zar
una actividad, el proceso del sistema en el que se aloja se sitúa en la parte superior de la
pila y el proceso anterior (el que alojara la actividad previamente en ejecución) se des -
plaza una posición. Es un concepto clave. Android trata de mantener los procesos en
ejecución todo lo posible , pero no indefinidamente, ya qu e los recursos del sistema son
limitados. Se p reguntará qu é sucede cuando comi enza a agotarse la memoria o la CPU
muestra un exceso de actividad.

Relación entre procesos y actividades


Cuando Android decide que necesita recursos, realiza una serie de pasos para redu-
cirlos (y las activ idades que alojan). Determina cuáles eliminar en funci ón de un sencillo
conjunto de prioridades:
1. El proceso que aloja la acti vidad actual es el más importante.
2. Los procesos que aloj an una actividad visible pero no la actual son los si-
guientes.
3. Los procesos qu e alojan una actividad de fondo son los siguientes.
4. Los procesos que no alojan actividades (ni ser vicios o BroadcastRece i ver),
denominados vacíos, son los últimos.

Una herramienta muy útil para desarrollo y depuración, en especial en la prioridad


de procesos, es Debug Bridge (adb), Puede ejecutar el siguiente comando para ver el
estado de los procesos en ejecución:
adb shel l dumps y s activ ity
III!!I 3. Interfaces de usuario

Este comando muestra información sobre los procesos en ejecución, como el nombre
del paquete, PIO, estado de frente o de fondo, la prioridad actual, etc.
Como un usuario puede cambiar de actividad en cualquier momento (realizar una
llamada, cambiar la orientación de la pantalla, responder a un SMS, detener la magnífica
aplicación de análisis bursátil para jugar al póquer), lo que afecta a recursos generales
del sistema, todas las clases Acti vi ty deben admitir que se detengan y se terminen en
un momento dado. Si el proceso que incluye la actividad no se encuentra de frente, se
puede terminar (decisión de la plataforma en función de recursos y prioridades).
Para administrar este entorno, las aplicaciones Android y las clases Acti vi ty que
alojan, deben diseñarse de forma diferente a la habitual. Por medio de métodos rela-
cionados con eventos que la clase Acti v i ty define, puede configurar y finalizar su
estado. Las subclases Activit y implementadas (como vimos con ReviewCr iteria
anteriormente), sobrescriben diversos métodos del ciclo vital para conseguirlo. Como
vimos en un apartado anterior, toda actividad debe implementar el método onCr ea -
te () , el punto de partida del ciclo vital. Además, la mayoría de actividades también
implementa el método onPaus e (), donde se mantienen datos y estado antes de que el
proceso abandone el ámbito.
Los métodos de ciclo vital proporcionados por la clase Activi ty se invocan en un
orden concreto en la plataforma de acuerdo a su decisión de crear y finalizar procesos.
Como programador de aplicaciones no puede controlar los procesos y depende del uso
de los métodos de ciclo vital para controlar el estado de las clases Activi ty al pasar al
frente, desplazarse al fondo y desaparecer. Es un aspecto muy significativo e inteligente
de Android. Cuando el usuario toma una decisión, las actividades se crean y se detienen
en un orden concreto mientras el sistema inicia y detiene procesos.

Ciclo de vida de las actividades


Además de onCre a te () y on Pa u s e (), Android proporciona diferentes escenarios,
cada uno correspondiente a una fase concreta del ciclo de vida de una clase Activi ty.
La figura 3.3 muestra los métodos más habituales y las fases de cada parte.
Cada método de ciclo de actividad de Android tiene una función concreta y se pre-
senta en una fase determinada del ciclo de vida.

• En la fase de frente, la actividad solamente se ve en pantalla y sobre todo lo demás


(cuando el usuario interactúa con la actividad para realizar una tarea).
• En la fase de visibilidad, la actividad está en pantalla pe ro puede que no sobre
todo lo demás ni interactuando con el usuario (por ejemplo cuando hay un cuadro
de diálogo o ventana flotante sobre la acti vidad).
• La fase de ciclo vital hace referencia a los métodos que pueden invocarse cuando
la actividad no está en pantalla, antes de que se cree o después de que termine.

La tabla 3.1 muestra información adicional sobre las fases del ciclo vital y los princi-
pales métodos de nivel superior de la clase Activi ty.
A ndroid. Guía para desarrolladores lID

onCrealeO Ciclo vital completo

'"'T'
onPauseO

ons topt) / '---- - - - - - - -'

onDeslroyO

Figura 3.3. Diagrama del ciclo de actividad de Activity.

Tabla 3.1. Principales métodos de ciclo vital de la clase Activity de Android .

Método Función

onCreate () Se invoca al crear la actividad. Aquí se realiza la configuración.


También brinda acceso a cualquier estado almacenado previa-
mente en forma de Bundl e .
o n Rest a r t () Se invoca si se reinicia la actividad , si sigue en la pila. en lugar
de iniciar una nueva .
o nSta r t ( ) Se invoca cuando la activ idad es visible en pantalla para el
usuario.
o nRes ume () Se invoca cuando la actividad comienza a interactuar con el
usuario . (Se invoca siempre. ya sea un inicio o un reinicio.)
o n Pause ( ) Se invoca al detener la actividad o al reclamar CPU u otros re-
cursos . En este método se guarda el estado para que al reiniciar
una actividad pueda comenzar con el mismo estado con el que
se finalizó .
o nS top () Se invoca para detener la actividad y pasar a una fase de invi-
sibilidad y poster iores eventos de ciclo vital.
o n Des t r o y ( ) Se invoca al eliminar una actividad de la memoria del sistema .
Se produce por invocar directamente o nFi nish ( ) o cuando el
sistema decide detener la actividad para liberar recursos .

Además de estos métodos, existen otr os más concretos.Métodos como onPos t Cr e a te


y onPo stResume no suelen ser necesarios de modo que no s los detallaremos pero debe
saber qu e existen por si nec esita este nivel de control (en el documento de Act i vi t y
encontrará más informa ción al res pecto).
ll!II 3. Interjaces de usuario

En cuanto a los principales métodos de ciclo vital que utilizará con mayor frecuencia,
es importante destacar que onPause () es la última oportunidad de limpiar y guardar
información de estado. Los procesos alojados en las clases Activity no se eliminan
hasta completar el método onPause () , pero sí después. Esto significa que el sistema
intentará ejecutar todos los métodos de ciclo vital pero si no controla los recursos (lo
que determina la plataforma), se activa una alarma y los procesos que alojen activida-
des más allá del método onPause () se eliminan en cualquier momento. Siempre que
la actividad se desplaza hacia el fondo, se invoca onPause () . Pero antes de eliminarla
definitivamente, no es seguro que se haya invocado onDestroy () (es probable pero
no siempre sucede así).
El método onPause () es sin duda donde debe guardar el estado de persistencia.
Independientemente de que sea específico de la aplicación (como preferencias del usuario)
o información global (la base de datos de contactos), debe asegurarse de atar todos los cabos
sueltos en onPause () , siempre. En un capítulo posterior veremos cómo guardar datos.

Además del estado persistente, existe otro aspecto a tener en cuenta: el estado de instan-
cias. Hace referencia al estado de la propia ID. El método onSave- InstanceState ()
Activi t y se invoca al destruir una actividad, para poder restaurar la interfaz en elfuturo.
La plataforma utiliza este método para controlar el procesamiento del estado de las vistas
en la mayoría de los casos. Por ello, no tendrá que manipularlo. Sin embargo, es impor-
tante saber que existe y que el Bundle que guarda se devuelve al método on Crea te ()
al restaurar una actividad. Si tiene que personalizar el estado de la vista, puede hacerlo si
sobrescribe este método pero no lo confunda con los métodos generales del ciclo vital.

La administración de actividades con métodos de ciclo vital, a través de procesos


principales controlados por la plataforma, permite a Android encargarse de las ope-
raciones más pesadas, decidir qué incluir en el ámbito o no, liberar a las aplicaciones
de sus cargas y garantizar un terreno de juego en perfectas condiciones. Es un aspecto
esencial de la plataforma que varía con respecto a otros entornos de desarrollo. Para
crear aplicaciones Android robustas y con capacidad de respuesta, debe prestar espe-
cial atención al ciclo vital.
Una vez analizado el ciclo vital de las actividades y después de crear nuestra primera
pantalla, nos centraremos en las vistas.

Trabajar con vistas


Aunque suene evidente, las vistas son las piezas básicas de la IV de una aplicación
de Android. Como hemos visto, las actividades contienen vistas y los objetos View re-
presentan elementos de la pantalla y se encargan de interactuar con los usuarios a tra-
vés de eventos.
Android. Guía para desarrolladores lIiiI
Toda pantalla de Android contiene un árbol jerárquico de elementos View. Estas vis-
tas adoptan diferentes formas y tamaños. Muchas de las que se necesitan diariamente las
proporciona la plataforma: elementos de texto básicos, elementos de entrada, imágenes,
botones, etc. Además, puede crear vistas compuestas y / o personalizadas en fun ción de
sus nece sidades. Las vistas se pueden incluir en una actividad (y, po r tanto, en la panta-
lla) directamente en el código o a través de un recurso XML que se amplia en tiemp o de
ejecución. En este apartado analizaremos los aspectos fun d amentales de las vistas: las
comunes proporcionadas por Android, las personalizadas que puede crear, el diseño en
relación a las vistas y el procesamiento de eventos. No analizaremos las vistas definidas
en XML, que describiremos en un apartado posterior. Comenzaremos con los elementos
View comunes proporcionados por Android, en un breve recorrido del API.

Analizar las vistas comunes


Android proporciona un amplio conjunto de objetos Viewen el paquete andro i d .
v iew. Estos objetos varían entre construcciones familiares como Ed i t Text, Spi nne r
y Te x t Vi e w hasta elementos más especializados como Ana logCloc k, Gallery,
DatePicker, TimePi cker y Vi deoView. Si necesita más información al respecto,
consulte la documentación de Android: http : / / code . g o ogle. co m/ a nd ro i d/
re fe re nce/v iew-ga l lery.html.
La figu ra 3.4 muestra el diagrama de clase de una instantánea de ni vel su perior del
API Vi e wgeneral. Dicho diagrama muestra la especialización y muchas, pero no todas,
de las clases derivadas de View. Como se deduce del diagrama (que no es completo),
el API Vi e wcuenta con diferentes clases. ViewGroup es una subclase especial de View
relacionada con el diseño, como sucede con otros elementos como Te xt Vi e w. En última
instancia todo es una vista, incluso las clases de diseño (que amplían ViewG roup).
Evidentemente, todo lo que amplíe View tiene acceso a los métodos de la clase base.
Dichos métodos le permiten realizar importantes ope raciones relacionadas con la IV,
como definir el fondo, la anchura y altu ra mínimas, establecer el relleno, configurar y
habilitar eventos (como los de clic y enfoque), definir parámetros de diseño, etc. La tabla
3.2 muestra un ejemplo de algunos de los métodos disponibles en la clase raíz View.

Tabla 3.2. Subconjunto de métodos del API View de Android.

setBackgroundColor(int color ) Establece el color de fondo .


setBackgroundDrawable(Drawable d ) Establece la imagen de fondo.
setMinimumHeight (int mi nHe i g h t) Establece la altura mínima .
setMinimumWidth(in t minWidth ) Establece la anchura mínima.
setPaddi ng (int left , i nt right, Establece el relleno.
int top , i n t bot tom)
s etC l ickab le (boolean e ) Determina si se puede hacer e1ic o no en
un elemento .
3. Interfaces de usuario

Función

s e tFo cu sab1 e( b o o1 e a n f) Determina si el elemento recibe o no el


enfoque .
setO nC1 ic kListe ne r (On C1 ickLis t e ne r 1 ) Establece un oyente que se desencadena
al producirse un evento de elle,
s etOnF ocu sChangeLi s t ener Establece un oyente que se desencadena
(OnF o cu sCha ngeLi stene r 1 ) al producirse un evento de enfoque.
se t La yo u t Pa r a ms Establece Lay ou tP ar am s (posición ,
(Vi ewGroup.La y outPar ams 1) tamaño y otros).

Figura 3.4. Diagrama de clase del API View de Android.


A ndroid. Guia para desarrolladores lImI
Además de la clase base, cada subclase Vie wsuele añadir otros métodos para mani-
pular su estado, como sucede con Te x t Vi e w. Véase la tabla 3.3.

Tabla 3.3. Métodos View adicionales de la subclase TextView.

Método Función

setGravity (i n t gravity ) Establece la alineación: superior, inferior, izquierda,


derecha y otras.
se t Height (in t h e i g h t) Define la dimensión de altura.
s e t Wi d t h (i n t wid th ) Define la dimensión de anchura.
setTypeFace (T y p e Fa c e face ) Define el tipo de letra.
s e t Te x t (Ch a r Seq ue n ce text ) Definete~o.

Mediante la combinación de los métodos de la clase base con los de subtipo, puede
configurar el diseño, relleno, enfoque, eventos, altura, anchura, colores y básicamente
todo lo que necesite. Al emp lear estos métodos en el código o sus atribu tos homólogos
del espacio de nombres android: al definir vistas en XML (como verem os más adelan-
te), puede manipular un elemento View. Cada elemento Viewutilizado tiene su propia
ruta en el API y, por ello, un determinado conjunto de métodos disponible. En ht tp: / /
code .goog l e . com/a ndro i d/ re fere nce/a nd ro id/view/Vi ew . h t mI encontrará
los detalles de todos los métodos.
Al aunar la amplia varieda d de clases con el completo conjunto de métodos dis-
ponibles de la clase base Vi ew, el API Vi e w de Android puede resultar intimidatorio.
Afortunadamente, a pesar de la impresión inicial, muchos de los conceptos implicados
son evidentes y el uso resulta m ás intuitivo al pasar de una vista a otr a (ya que son es-
pecializaciones del mismo objeto) . Aunque se podría comparar con la cabina de un 747,
una vez com ience a trabar con Android le resulta rá muy sencillo despegar.
Aunque la aplicación RestaurantFinder no utiliza muchas de las vistas enumeradas
en el diagrama, es aconsejable conocerlas, ya que muchas se utilizarán en otros ejemplos
del libro. A continuación nos centraremos en uno de los elementos Viewmás habituales,
el componente Li s tV i e w.

Utilizar listView
En la actividad ReviewLis t de nuestra aplicación, véase la figura 3.2, puede ob-
servar un tipo diferente de vista que presenta una lista desplegable de opciones para el
usuario.
Esta actividad utiliza un componente Li stVi e wpara mostrar los datos que se obtie-
nen al invocar el API de Google por medio de HTTP (lo denominaremos servicio Web
aunque técnicamente no sea SOAP ni ningún otro estándar formal) . Tras realizar la lla-
mada HTTP, al adjuntar los criterios del usuario al URL base necesario para Google,
analizamos los resultados con SAX (API sen cillo para XML) y creamos un elemento
3. Int erfaces de usuario

List de las críticas. No nos centraremos en los detalles del análisis XML ni en el uso
de la red, que veremos en capítulos posteriores, pero sí en las vistas que crearemos a
partir de los datos. Utilizaremos el elemento List resultante para completar la lista de
elementos de la pantalla.
El código del listado 3.3 muestra cómo crear y utilizar ListView para representar
esta lista de elementos entre los que escoger desde una pantalla Acti vi ty.

Listado 3.3. Primera mitad de la clase Activity ReviewList que muestra un elemento ListView.

public class ReviewList extends ListAct iv ity {

p r ivate stat ic fina l i n t MENU_CHANGE_CRI TERIA = Men u.F IRST + 1;


private static fina l int MENU_GET _NEXT_PAGE = Menu .FIRS T ;
private sta tic f ina l int NUM RESULTS PER PAGE = 8 ;

private TextView empty ;


private Prog ressDialog p rogressDialog ;
pr iva te Rev i e wAdap ter r eviewAdapte r ;
p r ivate Li s t <Re v i e w> r e v i e ws ;

priva te fi na l Hand l e r ha nd ler = new Hand l e r O {


pub1 i c void ha n d l e Me s s a g e( f in al Message ms g)
progress Dia1og .dismiss ( );
if ((re vi e ws ==null) 11 ( r ev í.e ws s í.ae t ) c O» (
empty.setTe xt ( "No Data " );
e lse {
rev iewAdapter = new ReviewAdapter (ReviewList .th is , reviews ) ;
setListAdapter ( reviewAdapter );

);

@Override
p ub lic void onC reate (Bund le savedInstanceState }
s upe r.onCreate (saved Insta nceState );
t hi s. setContentView (R .layou t . revie l'l_ list ) ;
this .empty = (Te x t Vi e w)
findVie wById (R. id.empty ) ;

Li s t Vi e w li s t Vi e w = getL istView ( ) ;
l istView.set ItemsCanFocus (fa1se );
l i s t Vi e w. s etCh o i c e Mo d e(L i s t Vi e cl . CHOI CE_ MODE S I NGLE ) ;
l i s t Vi e w. s e t Emp t y Vi e w( t h i s . e mp t y);

@Overr ide
protected vo i d onRes ume ( ) {
super .onRes ume ( ) ;
Resta uran tFinderApp1 ica tion a p p l ication =
(Re s t au r a nt Fi n d e r Ap pl i c a ti on) getApp1icat ion ( ) ;
String cr iteriaCu is ine = app licatio n .getReviewCriter iaC uis i ne ( );
String criter ia Location = application .getReviewCri te riaLocation ( ) ;

in t startFrom = getIntent ( ) . g e tIn tEx t r a(


A ndroid. Guía para desarrolladores l1li
Constants .STARTFROM_EXTRA , 1);
l o adRe vi e ws( c ri t eriaLo c a ti o n,
criter iaCui s i ne , s t a rtF rom) ;
}
II se o mite onC re a teOpt io ns Menu

ReviewList amplía ListA c ti v it y, que se utiliza para alojar un elemento


Li stView. El diseño predeterminado de Li stActivity es una lista centrada a pan-
talla completa de opciones para el usuario. El concepto de Li stVi e w es similar al de
Sp i nne r ; de hecho, son subclases de Adapt e rVi e w, véase la figura 3.4. Esto significa
que Li s tVi ew, como Sp i nne r , también utiliza un elemento Adap t e r para vincular los
datos. En este caso utilizamos una clase ReviewAda p te r personalizada. En el siguien-
te apartado encontrará más información sob re Re vi ewAda pte r. Lo importante es que
utilizamos un adaptador para Lis tVi e w(aunque personalizado) y una lista de objetos
Rev iew para completar el adaptador.
Como todavía no tenemos los datos para completar la vista, que obtendremos del ser-
vicio Web en otro subproceso, debemos incluir un Handler para obtener los datos y ac-
tualizar la IV en pasos independientes. No se preocupe por estos conceptos ya qu e tendrán
más sentido cuando veamos la segunda parte del código que el listado 3.4 muestra.
Una vez declarado Li stView y los datos, pasamos a las tareas o n Cre a te ()
habituales, incluido el uso de un diseño definido en un archivo XML de recursos. Es
significativo con respecto a ListAct i v i t y ya que un Li stVi ew con el nombre de ID
lis t es neces ario para personalizar el diseño, como hemos hecho (el nombre de ID está en
el archivo XML que verem os más adelante). Si no proporciona un diseño, puede utilizar
Li stActivi ty y Li s tV iew pero obtendrá el pred eterminado del sistema. También
definimos un elemento que utilizaremos para mostrar el mensaje No Data (Sin entradas)
si la lista carece de elementos. Además, definimos varias propiedades específicas de
Li s tVi ew, con sus métodos de personalización, como por ejemplo si los elementos de
la lista pueden o no recibir el enfoque, cuántos elementos se pueden seleccionar a la vez
y qué vista utilizar cuando la lista está vacía.
Una vez definidos los elementos Vi ew necesarios para la actividad, obtenemos los
criterios para invocar el servicio Web desde el objeto Re vi e wañadido en la aplicación
desde Rev i e wC r i te r i a Act ivity. Aquí también utilizamos un Inte nt adicional pa ra
almacenar un i nt primitivo para el número de página. Pasamos los datos de los criterios
(crit eri aLoc a ti on, cri t er i aCuis ine y s ta rtFrom) al método l o adRe v i e ws (),
que realiza la llamada al servicio Web para completar la lista de datos. Este mé todo, y
otros muchos que muestran cómo procesar con los elementos de la lista, se muestran en
la segunda mitad de la clase Re vi ewLi st, véase el listado 3.4.

Listado 3.4. Segunda mitad de la clase Activity ReviewList.

@Ove rride
pub lic b o o lean o nMe n u l t e mSe l ec t e d ( i n t f e atureld, Menul t em i tem) {
Intent intent = n ull;
switch (ite m. g e t l t e ml d( )) {
3. Interfaces de usuario

c ase MENU - GET- NEXT- PAGE:


intent = new I nte nt (Co n s t a n t s. IN TENT_ACT I ON VI EW LI ST ) ;
int ent . p utExt ra (Con stants .S TARTFROM_ EXTRA,
g etlnt ent ( ) . g e t l n t Ex t r a( Co n s t a n t s . STARTFROM_ EXTRA, 1)
+ Revi ew List .NUM_RE SULTS_ PER_PAGE) ;
s ta r t Ac t iv i t y ( i nte nt ) ;
r eturn true ;
case ME NU CHANGE CRITERIA :
i nte nt = ne", I n t en t( th i s, Revi ewCriteria. c l a s s );
s t a r t Ac t iv i t y ( i n t e n t ) ;
return tr ue ;

return s u pe r .on Me n u l t e mSe 1ec t e d ( f e a t u r e l d , i tem ) ;

@Override
prote cted v o i d o nList ltemC li ck (ListView 1 , View v ,
int po sition , l ong id ) (
RestaurantFi nde rAppl icat i on appl icat io n =
(Re s t au r a n t Fi n d e r Ap p l i c a ti o n) getApplicati on ( ) ;
app l ication . setCurrentRevi ew ( t h i s . r e v i e ws . g e t (p o s i t i on ) );

In t e n t in tent = n e w In t en t( Con s t a n t s .INTENT_ACTI ON_ VI EW_ DETAIL );


inte nt .putExt ra (Co nsta nts .S TARTFROM_EX TRA, get lnte nt ( ) . g e tl n tE x t ra(
Consta nts .S TARTFROM_EXTRA, 1») ;
s t a r t Ac t iv i t y ( i n t e n t ) ;

private void l o a d Re v i e ws (S t r i ng l o c at i on , S tri ng cu isi ne ,


in t start Fr om) (

f i na l ReviewFet cher rf = new Re v iewFet cher ( location ,


c u i s i ne , "ALL", start From,
Re viewLi st .N UM_RESUL TS PER_PAGE );

t his .prog ressDi a log =


Pr o gre s s Di al o g. s h o w( t h i s, " Work ing . .. " ,
I! Ret r iev i ng r ev e wsí v , true , fa l s e) ;

new Th r e ad() (
p ub lic v o i d r un ( ) (
r e v i e ws = r f.get Reviews ( ) ;
hand ler . se ndEmptyMessa ge (O) ;
)
) . s t a r t () ;

Esta actividad tiene un elemento de menú que permite al usuario acced er a la siguiente
página de resultados o camb iar los criterios de la lista . Para ello, implementamos el método
onMenu ltemSelec ted. Si se selecciona el elemento de menú MENU_ GET_ NEXT_ PAGE,
definimos un nuevo I n t ent p ara vo lver a abrir la pantalla con un valor s tartF rom
incrementado (y para ello utilizamos los métodos ge tE xtras () y putEx tr a s () del
I nte nt).
Android. Gl/ía para desarrolladores lID
Tras los métodos relacionados con menús, vemos el método especial onLis t l tem-
Chec k (), que se utiliza para responder cuando se hace clie en uno de los elementos de
Li s t Vi ew. En este caso usamos la posición del elemento pulsado para hacer referencia
al elemento Rev iew seleccionado por el usuario y lo establecemos en App l ica t io n
para su posterior uso en Rev ie wDeta i l Act i v i ty (que implementaremos en un ap ar-
tado posterior). Una vez definidos los datos, invocamos la siguien te actividad (incluido
el s ta rtFromadicional).
Por último, tenemos el método loadRev iews (), que se encarga de abrir críticas .
Tambi én es importante por otros motivos. Por un lado, configura una instancia de la clase
Rev i e wFe t che r qu e utilizaremos para invocar el API Google Base sobre la red y devolver
una lista de objetos Review. Tras ello, invoca el método Pr og r e s sDi al og . s how () para
mostrar al usuario la recuperación de datos. Por último, establece un nuevo subproceso en
el que se utiliza Re vi ewFet che r y se envía un mensaje vacío al Han dl er que vimos en la
primera mitad de Re v i ewLi s t. Si se fija en Handl e r, véase el listado 3.3,comprobará que
al recibir el mensaje cerramos Progre s sDi alog, completamos Adapte r con Li s t Vi e w
e invocamos setListAdapter () para actualizar la IV. El método setLis tA dap te r ( )
itera por el adaptador y muestra una vista devuelta por cada elemento.
Una vez creada y configurada la actividad, y después de utilizar Handl er para actuali-
zar el adaptador con datos, ya tenemos una segunda pantalla en la aplicación. El siguiente
paso consiste en completar el trabajo con controladores y subprocesos. No son conceptos
específicos de vistas pero conviene analizarlos ya que tendrá que utilizar estas clases para
realizar tareas de recuperación y manipulación de datos necesarios para la IV.

Multitarea con Handler y Message


Handl e r es la navaja suiza de las operaciones de mensajería y programación de
Android. Esta clase permite poner en cola tareas que ejecutar en distintos procesos y le
permite programar otras por medio de objetos Me s s a ge y Runn a b le .
La plataforma Android controla la capacidad de respuesta de las aplicaciones y cancela
las que considera que no responden. Un evento ANR (Aplicación que No Responde) se
de fine como la falta de respuesta a una entrada de usuario durante cinco segundos. (El .
usuario toca la pantalla o pulsa una tecla, y la aplicación debe responder .) Se preguntará
si esto implica que el código siempre tiene que completarse en el plazo de cinco segun-
dos . No, evidentemente, pero el subproceso principal de la IV tiene que responder en
ese plazo. Para ello, las tareas de ejecución prolongada, como la recuperación de datos
en red o de una base de datos, o cálculos complejos, deben realizarse en un subproceso
independiente.
Para ello, y para transferir los resultados al subproceso principal de la IV, se utiliza
Handl er y clases relacionadas. Al crear Handl er, se asocia a Looper, una clase que
contiene MessageQueue y procesa objetos Me s s ag e o Runnable enviados a trav és de
Handl er.
En el uso de Handl er, véase los listados 3.3 y 3.4, creamos una clase Handl er con
un constructor sin argumentos. Con este enfoque, Handl er se asocia automáticamen-
te a Loope r en el subproceso actualmente en ejecución, por lo general el de la IV. Este
lID 3. Interfaces de usuario

subproceso principal, creado por el proceso de la aplicación en ejecución, es una instan-


cia de HandlerThread, que básicamente es una especialización de Thread de Android
que proporciona un Looper. Los elementos principales implicados en esta estructura se
reproducen en el siguiente diagrama, véase la figura 3.5.

MainUIThread
. (HandlerThread)

Handler myHandler = new HandlerO (


public void handleMe ssage (Message m) (
updateUIHereO ;

);

new ThreadO (
public void runO (
doStuffO;
Message m = myHandler .obtainMessageO;
Bundle b = new Bundtet) ;
b.putString(" key", "value");
m.setData(b);
myHandler.sendMessage(m);
)
).startO;

Looper

MessageQueue

Figura 3.5. Uso de la clase Handler con subprocesos independientes


y la relación entre HandlerThread, Looper y MessageQueue.

Al implementar Handler es necesario proporcionar un método handleMessage


(Message m) ,para poder enviar mensajes. Al crear un nuevo Thread, podemos invo-
car uno de los métodos sendMessage en Handler desde el método de ejecución de ese
subproceso, como demuestran los ejemplos y el diagrama. Al invocar s e ndMe s s a ge , el
mensaje se añade a MessageQueue, que mantiene Looper.
Junto con el envío de mensajes, también podemos enviar objetos Runnabl e
directamente, y programar operaciones en diferentes momentos. Los mensajes
se envían (s e n d) y los objetos Runnable se publican (post) . Estos conceptos
aceptan métodos como sendEmpt yMessage (int what) , que ya hemos
empleado, y sendEmptyMessageAtTime (int what, long time) y
sendEmptyMessageDelayed (int what, long delay) . Una vez en la cola, el
mensaje se procesa lo antes posible (a menos que lo programe o retrase por medio del
correspondiente método send o post).
En otros ejemplos del libro encontrará más información sobre Handler y Message,
que detallaremos en determinados casos pero lo más importante es recordar que estas
clases se utilizan para comunicarse entre subprocesos y para tareas de programación.
Volviendo a nuestra aplicación RestaurantFinder y a temas orientados a vistas, el si-
guiente paso consiste en ampliar el ReviewAdapter utilizado por la vista ReviewList,
después de completarlo con datos de un mensaje. Este adaptador devuelve un objeto
View personalizado para cada elemento de datos que procesa.
Android. Guía para desarrolladores ID

Crear vistas personalizadas


Aunque por lo general puede conformarse con las vis tas proporcionadas con
Android, en determinados casos ne cesitará una vista personalizada para mostrar sus
propios objetos.
En la pantalla ReviewList utilizamos un adaptador de tipo Rev iewAdap t er para
Li s tVi ew. Es un adaptador personalizado que contiene un objeto Vi ew personalizado,
Revi ewLis tV iew, lo que nuestra actividad muestra porcada fila de datos que contiene.
El listado 3.5 muestra Ada p t er y View.
Listado 3.5. Las clases ReviewAdapter y ReviewListView.
publi c c l as s Re viewAda pter e xte nd s Ba seAdapter {

pri vate fi nal Con text co n te x t ;


private fi na l Li s t <Re v i e w> r e v i e ws ;

publ ic Rev í.ewadap t e r (Co n t e x t co nte x t , List <Rev ie l'¡> r e v í.e ws ) {


this .context c o n tex t ;
t h i s . r e v i e ws = review s ;

@Overri de
publ ic i n t g etCo u nt ( ) (
return t his .revieNs . si z e ( ) ;

@Over ri d e
p ub liC Ob j e c t g etltem( int p o s i t i on) {
r eturn thi s .re v iews. ge t(po si t i on ) ;

@Overr i d e
publi c long get l teml d( i n t pos it ion ) (
r e t u rn p o sitio n ;

@Overr ide
pub lic Vi eH getVi ew (int p o sition , Vi eH c o nv ertV ieH , Vi eHGr oup pare n t) (
Re v i ew r e v e w = t his . r e v i ew s . ge t (p o si tion ) ;
í

return neH Rev iewLi stV i eH (this .context , rev i ew .name , rev i e N.rating ) ;

pr ivate f i nal c l a s s RevieNListVieN exte nds Lin e a rLa you t (

pri va t e Te x t Vi e H name ;
pri v a t e TextView ra ti ng ;

pub lic Re v iewListVi ew (Context context , Stri ng na me, S tr ing r at i ng ) (

super (context ) ;
s e tOri enta tion (Line arLayout . VERTI CAL) ;

Linea rLayout . Layou tPa r a ms pa rams = neN Lin earLayout.La yo u tParams(


lID 3. Interfaces de usuario

ViewGroup.LayoutParams .WRAP_CONTENT,
ViewGroup.LayoutParams .WRAP_CONTENT);
params .setMargins (5, 3 , 5 , O);

this.name = new Te x t Vi e w( c o n t e xt) ;


this.name.setTe xt (name ) ;
this.name.setTextS ize (16f );
t his.name.setTextCo lor (Co lor .WHITE ) ;
t his.addView (thi s . name , params ) ;

t hi s .rating ~ new TextV iew (context ) ;


t his .rating.setText (rat i ng ) ;
this .rating.setTex tSize (16f ) ;
this.rating.se t Tex tColo r (Color.GRAY );
t his.addView (this.rati ng , params ) ;

Lo primero que vemos en ReviewAdapter es que amplía BaseAdapter, una im-


plementación de Adapter que proporciona compatibilidad básica de procesamiento de
eventos. Adapter es una interfaz del paquete android. Widget que permite vincular
datos a una vista por medio de métodos comunes. Se suele utilizar con colecciones de
datos, como observamos con Spinner y ArrayAdapter, véase el listado 3.1. Otro uso
es con CursorAdapter, que devuelve resultados de una base de datos (como veremos
en un capítulo posterior). En este caso creamos nuestro propio adaptador, para que de-
vuelva una vista personalizada.
Nuestra clase ReviewAdapter acepta dos parámetros en el constructor y establece
dichos valores en dos sencillos objetos: Con t e x t y List <Review>. Tras ello, la clase
implementa los métodos obligatorios de la interfaz Adapter que devuelven un conta-
dor, un elemento y un ID (utilizamos la posición en la colección como ID). El siguiente
método Adap t e r que implementar es el más importante: getView () . Aquí, Adapter
devuelve cualquier vista que creemos para un determinado objeto de la colección de
datos. En este método obtenemos un objeto Review concreto en función de la posición/
ID y tras ello creamos una instancia de un objeto ReviewLi stView personalizado que
devolver como View.
Por su parte, ReviewListView, que amplía LinearLayout (como veremos más
adelante), es una clase interna dentro de ReviewAdapter (que nunca utilizaremos si no
se devuelve una vista de Rev i ewAdapte r). En su interior vemos un ejemplo de configu-
ración de diseño y detalles de vista en el código, no en XML. Aquí definimos la orienta-
ción, parámetros y margen del diseño. Tras ello, completamos los objetos Te xtView que
serán secundarios de nuestra nueva vista y representan datos. Una vez configurados a
través del código, los añadimos al contenedor principal (en este caso, la clase personali-
zada ReviewListVi ew). Aquí se produce la vinculación de datos, el puente entre View
y dichos datos. Otro aspecto importante que destacar es que no sólo hemos creado una
vista personalizada, sino también una compuesta. Es decir, utilizamos sencillos objetos
View existentes en un diseño concreto para crear un nuevo tipo de vista reutilizable, que
muestra el detalle de un objeto Revi ew seleccionado en pantalla, véase la figura 3.2.
Android. ca« para desarrolladores lIfI
Nuestro objeto Rev i e wLi s t Vi e w/ aunque personalizado, es intencionadamente sen-
cillo. En muchos casos podrá crear vistas personalizadas mediante la combinación de
vistas existentes. No obstante, debe saber que puede ampliar la propia clase Vi e wy des-
pués implementar los métodos básicos necesarios. Con este enfoque, tendrá acceso a los
métodos de ciclo vital de una vista (no de una actividad como hemos visto hasta ahora).
Entre éstos destacan onMeasure () / o nLayout () / onDra w() / onVi sibil i tyChan -
ged () y otros. Aunque no necesitemos tal nivel de control, debe saber que al ampliar la
clase Viewdispone de mayores posibilidades de crear componentes personalizados.
Una vez obtenidos los datos para nuestras críticas y después de ver el adaptador y
la vista personalizada, el siguiente paso consiste en describir aspectos concretos de las
vistas, como el diseño.

Comprender el diseño
Uno de los aspectos más significativos al crear una interfaz de usuario y las panta-
llas es comprender el diseño. En Android, el diseño de pantallas se define en términos
de objetos Vi ewGr oup y LayoutParams . Vi ewGr oup es una vista que contiene otras
vistas (secundarias) y también define y brinda acceso al diseño.
En todas las pantallas, las vistas se organizan en un árbol jerárquico, de modo que
cada elemento tiene secundarios y en la raíz se encuentra ViewGroup. Todas las vistas
de la pantalla admiten diferentes atributos relacionados con el color de fondo, el color,
etc. Vimos algunos de ellos en un apartado anterior. Las dimensiones (altura y anchura),
y otras propiedades como la posición absoluta o relativa, y los márgenes, se basan en el
Layout Pa r ams que solicita la vista, y en lo que la principal puede aceptar.
La figura 3.4 muestra las principales clases de ViewGroup. La figura 3.6 muestra de
forma ampliada esta estructura de clases para ilustrar las clases internas concretas de
La youtPa rams.
Como vemos en la imagen, la clase base Vie wGroup . Layo u t Pa rams son hei ght y
width. Un tipo Abs olu t eLa yout con Abs olute La yout . La yo u t Para ms nos permite
especificar las coordenadas X e Y exactas de los objetos Vi ew secundarios.
Como alternativa al diseño absoluto, puede utilizar los subtipos Frame Layou t,
Line arLayou t y Rel ati veLayout, que admiten variantes de LayoutPa r ams deri-
vados de Vi ewGroup . MarginLa youtParams . Frame Layout permite enmarcar un
elemento secundario, como una imagen. FrameLa yout admite varios elementos se-
cundarios pero todos se ajustan a la zona superior izquierda, de modo que se solapan
en la pila. LinearLayout alinea los elementos secundarios horizontal o verticalmente.
Recordará que utilizamos Linea rLayout en el listado 3.5/ cuando creamos la vista y
LayoutParams directamente en el código. Y/ en ejemplos de actividades anteriores,
utilizamos Rela ti v e La yo u t en los archivos XML de diseño que ampliamos en el códi-
go (en un apartado posterior veremos los recursos XML). Rel ati veLayout especifica
elementos secundarios relativos entre sí (above, be low, t oLeftOf, etc.).
El contenedor es Vi ewGr oup, que admite un determinado tipo de Layout Para ms.
Los elementos View secundarios se añaden al contenedor y deben ajustarse al diseño es-
pecificado por sus principales. Un concepto clave que recordar es que aunque una vista
II1II 3. [lite/faces de usuario

secundaria tenga que ubicarse en función de los La you t Params de su principal, también
puede especificar un diseño diferente para sus propios secundarios. Este diseño ofrece
gran flexibilidad para crear prácticamente cualquier tipo de pantalla que necesite.

ViewGroup
ViewGroup.LayoutParams

f'ViewGroup.MarginLayoulParams
marginBottom
_-"1A"" marginLeft
marginRight
marginTop

FrameLayout AbsoluteLayout

FrameLayoul.LayoulParams AbsoluleLayoul.LayoutParams
gravity x (position)
y (posl üon)

LinearLayout

LinearLayoul.LayoulParams
gravity
weight

RelativeLayout

RelaliveLayout.LayoulParams
aboye
below
alignLeft
alignRight
toLeftOf
toRightOf
centerHorizontal
centerVertical

Figura 3.6. Clases ViewGroup comunes con LayoutParams y propiedades.

Para cada dimensión del diseño que la vista tiene que proporcionar, en función de
los LayoutParams de su principal, especifica uno de estos tres valores:

• Un número exacto .
• FILL PARENT.

• WRAP CONTENT.

La constante FILL_ PARENT implica ocupar el mismo espacio en la dimensión como


el principal (sin el relleno). WRAP_ CONTENT implica ocupar el espacio necesario para el
contenido (con el relleno). Así pues, un objeto View secundario solicita un tamaño y el
principal toma la decisión. En este caso, al contrario de lo que sucede en el mundo real,
los hijos escuchan a los padres, ya que no les queda otra opción.
Android. Guía para desarrolladores lID
Los elementos secun da rios realizan el seguimiento del tamaño solicitado inicialmente,
que se vuelve a calcular al añadir o eliminar elementos, pero no pueden forzar un tamaño
concreto. Por ello, estos elementos Vi e wtienen dos grupos de dimensiones, el tamaño y
la anchura que ocupar (ge t Measu r e dWid t h () y getMeasuredHeigh t () ) y el tamaño
real que consiguen tras la decisión del principal (ge t Wi d t h () y ge t He i gh t ().
El diseño se aplica en un proceso de dos pa sos: primero se realizan las mediciones,
por medio de LayoutParams, y después se añaden los objetos a la pantalla. Los com-
ponentes se dibujan en pantalla en el orden qu e ocupen en el árbol de diseño: primero
los principales y después los secundarios (si sus posiciones se solapan, los principales
pasan a ser secundarios).
El diseño es muy importante para comprender la creación de pantallas con Android.
Además de ubicar los elementos Vi e wen pantalla, nece sita comprender el enfoque y el
procesamiento de eventos para crear aplicaciones eficaces.

Enfoque
Todos los dispositivos con interfaces de usuario admiten el concepto de enfoque.
Al pasar las páginas de un libro, se fija en una concreta (o incluso en una palabra o una
letra) . Las interfaces informáticas no son diferentes. Aunque una pantalla puede contener
distintas ven tanas o elementos, sólo uno tiene el enfoque y puede responder a las en-
tradas del usuario. Un evento, como el movimiento del ratón, un clic o una tecla, puede
desplazar el enfoque a otro componente.
En Android, el enfoque lo controla la plataforma en la ma yoría de los casos. Cuando
un usuario selecciona una actividad, se invoc a y el enfoque se establece en la vista fron-
tal. Tras ello, los algoritm os internos de Andro id determinan a dónde enviar el enfoque
después, en función de eventos (botones pulsados, menús seleccionados, serv icios qu e
devuelven llamadas, etc.). Pu ed e reemplazar el comportamiento p redeterminado e indi-
car dónde quiere específicamente el enfoque por medio de los métodos de la clase View
(o sus equivalentes XML):
• nex t FocusDown.
• nex t FocusLeft.
• next FocusRight.
• next Focus Up.
Las vistas también pueden indicar un determinado tipo de enfoque, DEFAULT_ FOCUS
o WEAK_FOCUS, para establecer la prioridad de enfoque que desean, ellas mismas (prede-
terminado) o sus descendentes. Además de indicaciones como UP/ DOWN y WEAK, puede
utilizar directamente el método Vi ew. r eque s tFo cu s () para indicar que el enfoque
debe establecerse en una vista concreta en un momento determinado. La manipulación
manual del enfoque debe ser una excepción y no la regla (la lógica de la plataforma hace
normalmente lo esperado).
El enfoque cambia en función de la lógica de procesamiento de eventos con el objeto
OnFocusChangeListener y el método relaciona do s etOnFocusChangedListener () /
lo que nos lleva al universo del p rocesamiento de eventos.
lImII 3. [lite/faces de usuario

Eventos
Los eventos se utilizan para cambiar el enfoque y para otras muchas acciones. Ya
hemos implementado varios métodos onCli c kLi stener () para los botones, véase el
listado 3.2. Estas instancias se conectaron a pulsaciones de botón. Los eventos indicaban
que alguien había pulsado un botón, exactamente el mismo proceso de los eventos de
enfoque cuando anuncian o responden a eventos OnFo cusChange .
Los eventos se dividen en dos partes : el componente que genera el evento y el
componente (o componentes) que responde al mismo. Estas dos partes se denominan
Obse rva b l e y Observer en términos de diseño. La figura 3.7 muestra un diagrama
de clases de esta relación.

Observable Código fuente)


observerCollection : Colleclion<Observer> (Escuchadores)
registerObserverO : void 0..1 .1

Observer
(Escuchador
ni.
unregisterObserverO: void nolifv( : void 1I
notilvObserver(): void

I I
I Observerlmpl I
I Observeablelmol
"
--
Para observer en
observerColle ction :
notifyObserverO

Figura 3.7. Diagrama de clases del patrón de diseño Observer.

Un componente Observable permite que las instancias Observer se registren. Al


producirse un evento, Observable notifica a todos los observadores que ha pasado algo .
Tras ello, los observadores responden a dicha notificación como consideran oportuno.
En un API, se suelen utilizar interfaces para los distintos tipos de eventos.
Con respecto a un botón de Android, las dos partes se representan de esta forma:

• Ob se rva b l e: Butt on .setOn Cl ickLi st ener(On Cl i ckListene r


l i s t e ne r ) .
• Observer: listene r . onClick (View v)

Este patrón aparece en elementos View de Android ya que muchos elementos se


consideran Observabl e y permiten que otros componentes se puedan conectar yes-
cuchar eventos. Por ejemplo, la mayoría de los métodos de la clase View que empiezan
por on están relacionados con eventos: onFocusChanged (), onSizeChanged (),
onLayout () , onTou c hEve nt () y similares. Igualmente, los métodos de ciclo vital de
Activ i ty que ya hemos visto, como onCreate () y onFreeze (), están relacionados
con eventos, a otro nivel.
Los eventos se producen en la IV y en toda la plataforma. Por ejemplo, cuando se
recibe una llamada o una ubicación GPS cambia por un movimiento físico, se producen
diversas reacciones; muchos componentes pueden ser notificados al sonar el teléfono o
A ndroid. Guía para desarrolladores IEIII
cuando cambia la ubicación (no sólo uno y no sólo la IV). Las vista s admiten eventos en
distintos niveles. Cuando se produce un evento de interfaz (el usuario pulsa un botón,
se desplaza o selecciona una parte de una ventana), se comunica a la vista correspon-
diente. Por lo general, los eventos de clic, de teclado, táctiles y de enfoque son los prin-
cipales de la IV.
Un aspecto importante de View en Android es que la interfaz tiene un solo subpro-
ceso. Si invoca un método en View, tendrá que estar en el subproceso de la IV. Por esto
utilizamos Handler en el listado 3.3, para obtener datos externos al subproceso de la IV
y notificar la actualización de la vista a través del evento setMessage () .
Hemos analizado los eventos desde un punto de vista genérico para aclarar los concep-
tos, ya que no podemos abarcar todos los métodos de las API de Android. No obstante,
veremos eventos en muchos de los ejemplos del libro y el uso diario de la plataforma.
Los mencionaremos cuando resulte necesario y los detallaremos en casos concretos.
El análisis de los eventos en general y su relación con el diseño permite finalizar la
descripción de las vistas, pero nos queda un importante concepto por afrontar: los recur-
sos. Las vistas están directamente relacionadas con los recursos pero también superan
los límites de la IV. En el siguiente apartado nos centraremos en todos los aspectos de
los recursos, incluidas las vistas definidas con XML.

Utilizar recursos
Ya hemos mencionado los recursos de Android, que pr esentamos en un capítulo an-
terior. Volveremos a analizarlos con ma yor detalle para ampliar este tema y completar
la tercera y última actividad de RestaurantFinder: la pantalla ReviewDetail.
Al comenzar a trabaj ar con Android, verá muchas referencias a la clase R, qu e pre-
sentamos al principio del libro y utilizamos en los distintos ejemplos de actividades. Es
la clase de referencia de los recursos de Android. Éstos son objetos no de códi go que la
plataforma incluye automáticamente en un proyecto.
Para empezar, verem os los distintos tipos de recursos, junto con un ejemplo de cada
tipo.

Tipos de recursos admitidos


En el código fuente, los recursos se almacenan en el directorio r e s y pueden ser de
varios tipos :

• r e s / anim: Representaciones XML de animaciones de fotogramas.


• r e s/dr a wa ble:Imágenes .png, .9 .png y . j pg.
• res / layout: Representaciones XML de objetos View.
• res /values : Representaciones XML de cad ena s, colores, estilos, dimensiones
y matrices.
3. Interfaces de usuario

• r e s / xml : Archivos XML definidos por el usuario (también compilados en forma


binaria).
• res/raw: Archivos arbitrarios y sin compilar.
En Android, los recursos reciben un tratamiento especial ya que suelen compilarse
en un tipo binario eficaz (menos los que ya están compilados y los de tipo sin procesar,
que no se compilan). Animaciones, diseños y vistas, valores de cadenas y colores, y ma-
trices, se pueden definir en formato XML. Estos recursos XML se procesan después por
medio de la herramienta a apt y se compilan. Una vez compilados, se puede acced er a
los mismos en Java a través de la clase R generada automáticamente.

Hacer referencia a recursos en Java


La primera parte de la actividad Re vi ewDeta il, véase el listado 3.6, reutiliza muchos
de los elementos Activi t y vistos hasta el momento y usa diferentes subcomp onen tes
de R. java, la clase de recursos de Android.
Listado 3.6. Primera parte de ReviewDetail , que ilustra usos sencillos de la clase R.
publi c class Re vie wDetail e x t e n d s Activ ity {

private stati c final i n t MENU CALL REVIEW = Men u.F IRST + 2 ;


pr ivate static fina l int MENU MAP REVIEW Menu .FIRST + 1 ;
pr ivate s tati c final int MENU WEB REVIEW = Menu .FIRST;

p r i vate Str ing imageLink;


private String l ink;
private TextView l o c a t i o n;
private TextView name;
private TextView phone;
pr i vate TextVielol rating;
p ri va te TextView revielol;
private ImageView revielol lmage;

private Handler handler = new Handle r( )


p ubl ic vo id handleMessage (Message msg)
i f (( i ma g e Li n k ! = nu l l ) && !imageLin k .equals(" "))
t ry {
URL url = nelol URL(imageLink );
URLConnect ion conn = url .ope nConnection() ;
c onn .connec t ();
Buffe r edlnputStream bi s ~ nelol
BufferedlnputS tream(conn .getlnpu tStream () ) ;
Bi tmap bm = BitmapFa ctory .decodeS tream(bi s ) ;
b is .close () ;
r e vielollmage . setlmageBitmap(bm) ;
c a t c h ( I OEx c e p t i o n e ) {
// reg istra r o pro cesar
)
els e {
reviel.¡Image . setlmageResource (R . d r awab Le . n o _ r e view image) ;
Android. Guía paradesarrolladores lfII

);

@Override
publ ic vo id onCrea te (Bundle saved l nsta nceState )
s uper .onCreate (savedl nstanceState) ;

this .setContentView (R . layout .review_detail );

this .name =
(Te x t Vi e w) findViewBy ld (R .id.name detai l );
this . rati ng =
(Te x t Vi e w) f indViewByld (R .id.rati ng _detail ) ;
t h i s .lo c a t i o n
(Te x t Vi e w) f indViewByld (R .id. location deta i l ) ;
th is .phone =
(Te x t Vi e w) f i ndViewBy ld (R . id .phone_deta i l ) ;
th is. r ev ew =
í

(Te x t Vi e w) f i ndV iewBy ld (R .id.rev ie w d e ta i l) ;


t his.revie wlmage =
( I ma g e Vi e w) findViewBy ld (R .id.review_image );

Resta u ra ntFi nderApp l ication app licat ion =


(Re s t a u r a n t Fi nd e r Ap pl i c a t i o n ) ge t App lica t io n ( );
Review c ur re n t Review = app li c ati on . g e t Cu rr e nt Re vie w() ;

th is .l ink = curre ntRev iew . li nk ;


this . imageLi nk = curre ntRevie~¡ .imageLink;
this . name.set Text (cur rentReview. name );
th is .ra t i ng .setText (curren t Rev iew.rating );
th is .location .setText (curre ntReview. loca t io n ) ;
th is .rev iew .setText (c urre n t Re v iew.content ) ;

if (( cu r r en tRe v i e w.pho ne != n ull) && !curren tReview.phone.equa ls ( " " ) )


this .pho ne . setText (currentReview.phone );
e l se (
this.phone.setText ( "NA" ) ;

@Over ri de
p ub l ic b oo l e a n o nCreateOpt ionsMe n u (Menu menu )
s uper .onC reateOptionsMe n u (me n u ) ;
menu .add (O, ReviewDeta i l. MENU_WEB_REVIEW , 0 ,
R.str i ng . menu_web_ review ) .se t lcon (
a ndroid .R. drawab le.ic_ me n u _ i nfo_detai ls );
me nu . add( O, ReviewDetai l.MENU_MAP_REVIEW, 1 ,
R .string .menu_map _rev ie w) . s e t l c o n (
a nd ro id .R .drawable . ic_menu_ map mode ) ;
menu . a d d (O, Rev iewDet a i l . MENU_CALL_R EVIEW, 2 ,
R .str i ng . me n u ca l l _ rev iew ) . s e t l c o n(
android .R . d r awab le . ic_ me n u_ca l l ) ;
r e tu r n true ;

... veremos e l r e s t o d e es ta c lase en u n capitu lo posterior


lB 3. Interfaces de usuario

En la clase Re v i e wDe t a i l , primero definimos componentes Vi e wa los que poste-


riormente haremos referencia desde los recursos. Utilizamos un Handl e r para realizar
una llamada de red para completar Ima geV i ew en función de un URL. No está relacio-
nado con los recursos pero se incluye para completar el análisis. Los detalles de redes se
describen en un capítulo posterior. Tras Ha nd l e r , establecemos el diseño y el árbol de
Viewcon setContentView(R. layout . rev iew_ det ail ) , que se asigna a un archi-
vo XML en src/re s/ l ayo u t/rev iew_ detail . xml. También hacemos referencia a
algunos de los objetos Vi e wdel archivo de diseño directamente por medio de recursos
y sus correspondientes ID.
Las vistas definidas en XML se amplían mediante el análisis del XML e inyectando el
código correspondiente para crear los objetos, de lo que se encarga la plataforma. Todos
los métodos Vi e wy La yout Pa rams vistos hasta el momento disponen de equivalentes
en formato XML. Este enfoque de ampliación es uno de los aspectos más importantes de
los recursos relacionados con Vie w y permite reutilizarlos con gran comodidad. En el
siguiente apartado veremos el archivo de diseñ o al que hacemos referencia y las vistas
concretas que contiene.
En el código, h acem os referencia a los recursos a través de la clase R generada auto-
má ticamente. Está formada por clases internas estáticas (una por cada tip o de recurso) y
mantiene referencias a todos los recursos con forma de va lor int. Este va lor es un pun-
tero constante a un archivo de objeto a través de una tabla de recursos (que se incluye
en un archivo especial creado por la herramienta a a p t y que utiliza el archivo R).
La última referencia a recursos del listado 3.6 es para crear elementos de menú. En
cada uno, hacemos referencia a una cadena de texto de nuestros propios recursos locales y
también asignamos un icono del espacio de nombres de recursos a nd ro i d . R. drawable.
Puede cualificar los recursos de esta forma y reutilizar los iconos, imágenes, bordes, fon -
dos y demás elementos de la plataforma. Es probable que desee personalizar sus ap li-
caciones y proporcionar sus propios recursos, pero los de la plataforma siempre están
disponibles si los necesita (y resultan más coherentes para el usuario si se trata de accio-
nes bien definidas como en este ejemplo: mapa, llamada telefónica y página Web).
En los siguientes apartados veremos cómo se procesan los distintos recursos y dónde
se ubican en el código. Los primeros tip os son los relacionados con diseños y vistas.

Definir vistas y diseños con recursos XML


Com o hemos mencionado en apartados an teriores, las vistas y el diseño se pueden
definir en XML en lugar de en código de Java. De este modo resultan más sencillos de
ut ilizar, desvincular de l código y, en algunos casos, reutilizar en otros contextos .
Los archivos de recursos de vista se incluyen en el directorio r e s/ layou t . La raíz de
estos archivos XML suele ser una de las subclases de diseño Vi e wGroup que ya hemos
visto : Rel at i ve Layout , Li ne a r La you t , Fr ameLayou t, etc. Dentro de los elementos
raíz se incluyen elementos XML secundarios que representan el árbol de vistas/diseño.
Conviene mencionar que no es necesario que los recursos del directorio re s / 1a yo u t
sean diseños. Puede definir un TextView en un archivo de diseño de la misma forma
que definiría un árbol completo comenzando por Ab sol u t Layou t . Evidentemente, el
Android. Gl/ía para desarrolladores lfD
nombre de l diseño y la ruta se pueden confund ir, pero así está configurado. (Tendría
más sen tido uti lizar directorios res / la yout y res/view diferentes pero también re-
sultaría confuso.)
Puede tener todos los archivos XML de diseño o vista que necesite, de finidos en el
directo rio res/ layou t . En el código, se hace referencia a las vistas por medio del tipo
y el ID. Nuestro archivo de di seño de la pantalla Revi ewDetails, review_ detail .
xml , véase el listado 3.7, tiene una referencia en el código Ac t i v i ty como R. layout .
review_ de tail, un puntero al objeto de vista principal Rela ti ve Layout .
listado 3.7. Archivo XML de recurso s de dise ño para review_detail.x ml.
<?xml ve rsion= "l .O" e ncoding= llutf-8 ? > 11

<Relat ive Layou t


xmln s:android="h ttp : / / s c hema s .andr o id.com/ a pk / r e s / an droid "
a ndro i d : l ayo ut _ width= " f i l l_pa ren t "
a ndroid :layout_heigh t = " f i ll_parent "
a nd r o i d : g r avi t y= "ce n te r _ hori zo n t a l "
a nd r oi d : p a dd i ng= "l Opx "
a nd r o i d . s e t Ve r t i c a l Sc ro l l Ba rE na b l e d =" t r ue "
>

<I ma geVi e ¡., and r o i d : id= "@+ id/re vie,,_image "


andro id: l ayou t _,·,i d th=" lO Op x "
a nd roid :layou t _hei g ht= " l OOp x "
a nd ro id:layou t _mar g i nLe f t= " l Op x "
a n d ro id : layou t_ma r g i n Bottom~ "Spx " / >

<Te xt Vi e¡·, a nd r o id : i d=" @+i d / na me_ de t a i l "


android :layout_width="f i ll_pa ren t "
android :layout_he ight= "l'Irap_con t e nt "
an d ro id : l a yo u t _be l ol'l="@i d / r e v i e¡·,_ i ma ge "
a ndro id : layout_margi nLeft="lOpx "
an d ro i d :layout_marginBott om="Spx "
s tyle~ "@sty le/intro_blurb " />

<Te xt Vi e w android :id= " @+i d /ra t ing_label de t a i l"


andr oid : l a yout_l'Ii dth="\"rap_conten t O
android : la yo u t _ he igh t = "¡·,rap_ c on tent "
a nd r oi d : la yo u t_be low="@id/ na me_de t a i l "
a nd roi d : l ayo u t_margi nLe f t= " l Opx "
android :layout_marg inBottom="Spx"
s t y l e =" @s t yle/ l a be l "
andro id:text= "@ string /rating_label " />

s e omi t e el r e s to del ar chivo


</ Relative La you t>

En este archivo utilizamos Rela ti ve Layou t . Es el ViewGro up raíz del árbol View.
También se definen La yout Pa r ams en XML con la con vención a ndro i d : l a you t
[atribu to ] (donde [at ribu t o] hace referencia a un atributo de diseño). Junto con
el diseño, se pueden definir otros atributos relacionados con View en XML por medio de
atributos equivalentes a los métodos disponibles en código, como andro id : padding,
simi lar a se t Paddi ng () .
lfII 3. Interfaces de usuario

Tras definir el RelativeLa yout principal, se añaden los elementos Vi e wsecunda-


rios. En este caso utilizamos Ima geView y varios componentes Te xtView, cada uno con
un ID android: id="@+id/ [n ombr e] ". Al establecer un ID de esta forma, se define
una referencia int en la tabla de recursos y se le asigna el nombre especificado. De este
modo otros componentes pueden hacer referencia al ID por medio del nombre textual.
Una vez definidas las vistas como recursos, se puede utilizar el método findVi ewB-
y 1 d () de Act i v i t Y para obtener una referencia a una vista concreta por medio del
nombre. Dicha vista se puede después manipular en el código. Por ejemplo, en el listado
3.6 obtenemos r a t i ng de esta forma:
r a t i ng = (Te xt Vi e w) findViewBy ld (R .id . rating_detai l ) .

De este modo se amplía y entrega el elemento rating_deta il del listado 3.7. Las
vistas secundarias de los archivos de diseño terminan como tipo id en R. j a v a (no son
R.layout. name, sino R. id. name, aunque deben incluirse en el directorio res/l ayout).
Las propiedades del objeto View se definen en XML, incluido el diseño. Como utiliza-
mos RelativeLayout, usamos atributos que sitúan una vista con respecto a otra, como
porejemplobelowo toRightOf, por medio de la sintaxis android : layout_below="@
id/ [n ombre] . La sintaxis @id es una forma de hacer referencia a otros elementos de re-
curso desde el archivo de recursos actual. Con este enfoque podemos hacer referencia a
otros elementos definidos en el archivo actual o en otros archivos de recursos.
Algunas de las vistas representan etiquetas, que se muestran en pantalla tal cual y no
se manipulan en el código, como rating_label_detail. Las demás se completan en
tiempo de ejecución y no tienen un valor de texto, como name_detail. Los elementos con
valores conocidos, las etiquetas, se definen con referencias a cadenas externalizadas.
El mismo enfoque se aplica a los estilos, por medio de la sintaxis s t y1e = 11 @s t Y1e/
[n ombre_estilo] " . Las cadenas, estilos y colores se definen como recursos en otro
tipo de archivo de recursos.

Externalizar valores
Una técnica de programación habitual consiste en externalizar literales de cadena
del código. En Java, se utiliza Resour ceBundle o un archivo de propiedades. La ex-
ternalizacion de referencias en cadenas permite almacenar el valor de un componente y
actualizarlo independientemente del propio componente, fuera del código.
Android admite recursos de valores que se dividen en varios grupos: animaciones,
matrices, estilos, cadenas, dimensiones y colores. Cada uno de estos elementos se define
en un formato XML concreto y se muestran en el código como referencias de la clase R,
como sucede con diseños o vistas. En la aplicación RestaurantFinder utilizamos cadenas
externalizadas, véase el listado 3.8, s t r i ng s . xml.
Listado 3.8. Cadenas externalizadas de la aplicación RestaurantFinder, strings.xml.

<?xml version= "l .OT' e nc odi ng= "ut f -8 ?>


11

<r e s o urc e s>


<string name = "app_name_criter ia " >RestaurantFinder - Crite r ia </string>
<str ing name =" app_ na me_ rev iews " >Re s t a u r a nt Finde r - Rev i.ews c Zs tr ing>
Android. Guía para desarrolladores 1161
<string name~"app_name_revie\1">RestaurantFinder - Revie\1</str ing>
<string name= "app_short_name ">Resta urants</str i ng>

<string name= "menu_get_revie\1s ">Get revie\1s</stri ng>


<s tring n a me = " me nu _ \1e b _ r e v i e \1" >Ge t f ul l r e v i e \1</ s t ring >
<s tri ng name ="menu_ map_ r e vi e \1" >Map l o c a t i o n </ s t r i ng >
<s t r ing na me =" me n u_c a ll_ r e v i e \1">Call res taura nt</str i ng>
<s t r i ng n a me = "menu_ ch a nge _ crite ri a " >Ch a nge revie\1 c r i teria</s tring>
<str ing nam e= "menu_get_ne xt_page ">Get next page of r e su lt s </ s t r ing >

<stri ng name= "intro_b lurb _cr i teria ">Enter revie\1 cr i t e r i a < / s tring >
<s tring na me = " i n t r o_b l u rb_ d etail " >Re vie \1 d et ail s < / s t ring >

0 0 0 se omi te e l re s to

</re sou rce s>

Como apreciará en el ejemplo, es muy sencillo. El archivo utiliza un elemento


<s t r ing > con un atributo name para cada valor de cadena necesario. Hemos utilizado
el archivo para el nombre de la aplicación, botones de menús, etiquetas y mensajes de
validación de alertas. En Android, este formato se denomina valor sencillo. El archivo
se guarda en r e s /valu e s / s t r i ngs . xml. Además de las cadenas, tanto colores como
dimensiones se pueden definir de esta forma .
Las dimensiones se incluyen en d i mens . xml y se definen con el elemento <d i men >:
<dime nname =dimen_ name >d im en_ v a l ue </ dimen >. Las dimensiones se pueden ex-
presar en las siguientes unidades:
• Píxeles (px).
• Pulgadas (i n).
• Milímetros (mm).
• Puntos (p t).
• Píxeles de densidad independiente (dp) .
• Píxeles de escala independiente (sp) .
Los colores se pueden definir en co lo rs . xml con el elemento <c o l o r> : <co lor
name=c ol or_ name ># c ol or_ va l ue </ co lo r >. Los valores de color se expresan en
códigos RGB. Los archivos de colo res y dimensiones se almacenan en res/va l ues .
Aunque no hemos definido colores y dimensiones independientes para la aplicación,
utilizamos diversos estilos, véase el listado 3.7. Las definiciones de estilos se reprodu-
cen como muestra el listado 3.9. Aquí pasamos de un diseño de valores sencillos a una
estructura XML de estilo concreto (aunque los estilos se siguen almacenando en el di-
rectorio r e s / value s,lo que puede resultar confuso).

Listado 3.9. Recursos para definir estilos reutilizables , styles .xml.

<?xml v e r s i on="l . O" e ncodi ng = "u t f - 8 11 ? >


<reso urces>
<style nam e = " intro_b lurb " >
<item n a me = "android :tex t Si ze" >2 2 sp< /item>
IEI 3. Interfaces de usuario

<item na me =" a nd r o i d : t e x t Co l o r" >#e e 7 62 0 < / i t e m>


<item name= "android:textStyle ">bold</item>
</style>

<style name= " label ">


<item name="android :textSize ">18sp</item>
<item n ame= "and roid:textColor ">#ffffff</item>
</style>

<style name = "edit_text">


<item name= "andro id :textSize ">l6sp</item>
<item name=" a n d r o id: t e x t Co l o r" > # OOOOO O< /i t e m>
</ style>

o se o mite e l resto del archivo


• •

</resources>

El enfoque de estilos de Android es un concepto similar al uso de Hojas de Estilo


en Cascada (CSS) en HTML . Los estilos se definen en style s. xml y después se hace
referencia a los mismos desde otros recursos o código. Cada elemento <s t yl e> tiene
uno o varios elementos <i tem> secundarios que definen un parámetro concreto. Los
estilos están formados por los distintos parámetros de View: tamaños, colores, márge-
nes y demás. Resultan muy útiles ya que facilitan la reutilización y la posibilidad de
aplicar cambios en un mismo punto. Para aplicar estilos en archivos XML, se asocia un
nombre de estilo a un componente Vi ew concreto, como por ejemplo s t y1e= @s t y 1e / 11

intro blurb" (en este caso, s t y l e no tiene el prefijo android:; es un estilo local
personalizado que no proporciona la plataforma).
Los estilos también se pueden utilizar como temas. Mientras que un estilo hace refe-
rencia a un conjunto de atributos aplicados a un mismo elemento View, los temas hacen
referencian a un conjunto de atributos aplicados a toda la pantalla. Se definen con la
misma estructura <s tyl e> e <i tem> de los estilos. Para aplicar un tema basta con aso-
ciar un estilo a una actividad completa, como en andr oid: theme="@android : style /
[n ombre_ e stilo] .
Junto a estilos y temas, Android admite una estructura XML concreta para definir
matrices como recursos. Las matrices se almacenan en res/valu e s/arrays . xml y
son muy útiles para definir colecciones de valores constantes, como c u i s i ne , que pa-
samos a ArrayAdapte r, véase el listado 3.1. El listado 3.10 muestra la definición de
estas matrices en XML.

Listado 3.10. Arrays.xml para definir cuisine y rating .

<?xml vers ion= "l.OII encoding= "utf -8 "?>


<resou rces>
<array name= "cuis ines ll >
<ite m>ANY</item>
<item>American</item>
<item>Barbeque</item>
<item>Chinese</i tem>
<item>French</item>
<item>Germa n</item>
Android. GlIía para desarrolladores lfII
<i t e m> I nd i a n< / i t e m>
<i t e m> I t a l i a n< / ite m>
<i t e m>Me x i c a n</ i t e m>
<i t e m>Tha i</ i t e m>
<i t e m>Ve g e t a r i a n< /i t e m>
<i t e m>Ko s he r</ i t e m>
< / a r r a y>
</ r esou r c e s>

Las matrices se definen como recursos por medio de un elemento <a r r a y> con un atri-
buto name e incluyen varios elementos <i t e m> secunda rios para definir a los miembros
de la matriz. En el código, puede acceder a las matrices por medio de la sintaxis mo stra-
da en el listado 3.1: St r ing [] r at i ng s = getResources () . ge tS t ri ngArray (R.
array . ra t ings ) .
También se admiten archivos sin procesar y XML a través de recursos. Con los d i-
rectorios re s/ raw y r es /xml respectivamente puede incluir este tipo de archivos en
su aplicación y acceder a los mismos a través de Resources . ope nRawResou rce (in t
i d ) o Resou rces. ge tX ml (in t id).
A continuación analizaremos las animaciones, el siguiente tipo de recursos.

Animaciones
Las animaciones son más complicadas que el resto de recursos de Android pero tam-
bién las de mayor impacto visual. Android le p ermite definir animaciones que giren, se
muevan o estiren gráficos y texto. Aunque no convien e excederse con una animación
que no deje de parpadear, una p antalla de presentación o un sutil efecto animado pue-
den mejorar considerablemente su IU.
Los archivo s XML d e an im ación se almacenan en el di rectorio res /an im oPued e
habe r más de un archivo anim y como sucede con los d iseño s, se referencia a la corres-
pondiente animación por nombre/ID. Android admite cu atro tipos de animaciones:

• <a l p ha> : Define el fundido, de 0.0 a 1.0 (0.0 es transparente).


• <s c a l e> : Define el tamaño, X e Y (1.0 equivale a qu e no hay cambios).
• <t r a ns l a t e >: Define el movimiento, X e Y (porcentual o absoluto).
• <ro ta te >: Define la rotación, desde X e Y (grados).

Además, Android proporciona diversos atributos que puede utilizar con cualquier
tipo de animación:

• durat ion: Duración en milisegundos.


• s t a rtOffse t : Tiempo inicial de desplazamiento en milisegundos.
• i nte rpolato r : Se utiliza para definir la curva de velocidad de la animación.

El listado 3.11 muestra una sencilla animación que sirv e p ara escalar una vista .
III!I 3. Interfaces de usuario

Listado 3.11. Ejemplo de animación definida en un recurso XML, scaler.xml.

<?x ml vers i on="l . OII e nc o d i ng = ll u t f - 8 11?>


e sca le xmlns :android= "http :/ / sc hem a s .and r oid . c o m/apk /re s /and roi d "
and roid :fr o mXS ca le= "O.5 "
a nd roid :to XSca le = "2.0 "
andro id: fro mYSca le - "O .5 "
a nd r oid:t o YSca le= " 2 .0 "
andr oi d :piv o t X=" 5 0%"
a nd r oi d :p i v otY=" 50 %"
andro id:s ta r tO ffset= "700 "
androi d:du r a tio n="400 "
andro id : f i l l Be f ore= " fa ls e " />

En el código, puede hacer referencia a esta animación con cualquier objeto View
(TextView, por ejemplo), de esta forma:

view .startAn imat ion (An im a t i onUt il s.l o a dAn i ma ti o n (thi s, R .an im .sca ler ) ); .

De este modo se escala el elemento view hacia arriba en los ejes X e Y. Aunque de
forma predeterminada la aplicación RestaurantFinder no dispone de animaciones, para
ver su funcionamiento basta con añadir el método startAnimation a cualquier ele-
mento de vista del código y volver a abrir la aplicación. Las animaciones pueden resultar
muy útiles y las analizaremos con mayor detalle en un capítulo posterior.
Una vez completado el recorrido por los recursos de Android, abordaremos el úl-
timo aspecto de RestaurantFinder, el archivo de manifiesto AndroidManife st. xml ,
obligatorio para todas las aplicaciones de Android.

Comprender el archivo de manifiesto


Como vimos en un capítulo anterior, Android requiere un archivo de manifiesto para
todas las aplicaciones: AndroidManifest. xml. Este archivo, incluido en el directorio
raíz del proyecto, describe el contexto de la aplicación y sus actividades, servicios, re-
ceptores y/o proveedores de contenido, así como los permisos. En un capítulo posterior
encontrará más información sobre proveedores de contenidos. Por el momento, el ma-
nifiesto de nuestra aplicación RestaurantFinder, véase el listado 3.12, contiene solamen-
te <a pp lica tion >, un elemento <ac t i v i t y> por cada pantalla y varios elementos
<us e r - pe r mi s s io n>.

Listado 3.12. Archivo AndroidManifest.xml de RestaurantFinder.

<?xml ve rs i o n="l . O" encodi ng= "ut f -8 "?>


<man ifes t xml n s :and roid= "http : //schemas .android. c om/apk /res/andro id "

<app l i ca tion andro i d : icon= "@ drawa b le/restaura n t _i co n_tra ns "


a ndro id: labe l = "@s t ring /app_sho r t _ name "
Android. Guía para desarrolladores IDII
android:name= "RestaurantFinderApp lica tion "
andr oid :all owClearUs e rData="true "
android:theme= "@android:sty le/Theme.Bla ck">

<a ct ivity android:name= I'ReviewCriteria ll

android :label ~ "@st ring /app_sh ort_name">


<intent-fi lter>
<action android:name~"a ndroid .intent .action .MAIN" / >
<c a t e g o r y
android:name= "android.intent .ca tegory.LAUNCHER " / >
</intent -fi l ter>
</ac tivity>
<act i v i t y android :name ~ "ReviewLis t "
android: labe l = "@string/app_name_reviews " >
<i n t e n t - f i l t e r >
<c atego ry
android: name="and ro id .intent .category .DEFAULT " />
<action
andro id:name="com .ms i . mann ing. re staurant .VIEW_LIST " />
</ i n t e n t - f i l t e r >
< / a c t i v i t y>

<activity a ndroid :name= "ReviewDetai l "


a ndroid : label = "@ string /app_name r e vi e w" >
<intent -filter>
<catego ry
andro id :na me="a ndro id.intent .category.DEFAULT " />
<ac tion
android :name~ "c om .msi .manning.res taurant . V I EW_DETAIL" />
</intent-filte r >
</activity>

</application>

<u ses -permiss ion android:name~ "android .permission .CALL_P HONE " />
<uses-permiss io n an d r o i d: n ame~" a n dr o i d. p e rm i s s i o n .I N T E RN E T" />
</manifest>

En primer lugar definimos la declaración del elemento raíz <ma n i fes t>, que incluye
la declaración del paquete de la animación y el espacio de nombres de Android. Tras ello,
vemos el elemento <a pp l ica ti on > con los atributos de nombre e icono . No es necesario
incluir el atributo de nombre a menos que desee ampliar el objeto App lica tio n pre-
determinado de Android para proporcionar estado global a la aplicación (como hemos
hecho para almacenar el objeto Revi ew en cada pantalla). El icono también es opcional;
si no se especifica, se utiliza uno predeterminado del sistema para representar la apli-
cación en el menú principal.
Tras definir la aplicación, vemos sus elementos <a c t i v i t y> secundarios.
Evidentemente, define todas las actividades que admite la aplicación (el archivo de
manifiesto también puede utilizar recursos de Android, como sucede con @string /
nombre _ apli c a ción) . Como vimos anteriormente, el punto de partida de toda apli-
cación es Activi ty; en este caso, Act i vi t y tiene la acción <i nte nt- f i lte r> MAIN
y la categoría LAUNCHER para indicar a la plataforma cómo iniciar la aplicación desde
el menú principal del dispositivo.
IIEI 3. lllterfaces de usuario

Después de ReviewCriteria Activity, ve mos la de sign ación <a c t i v i t y> de


ReviewList . Esta actividad tamb ién incluye un <i nt en t - f i l ter > pero p ara nuestra
p ropia acción, com .msi . ma nni ng. chapter3 . VIEW_LIST. Indica a la p lataforma que
debe invocar esta actividad para este intent, como ve remos en el sig uiente cap ítulo.
Por ú ltimo, vemos un elemento <u s e r - p e r mi s s i o n>, relacionado con los intent y
que in dica a la plataforma que la aplicación necesita el p ermiso CALL_ PHONE.
La aplicación RestaurantFinder utiliz a un archivo de manifiesto básico con tres ac-
tividades y d iversos inte nts . La tabl a 3.4 muestra aunque n es un ejemplo completo,
todos los elemen tos ad mitid os en un man ifiesto.

Tabla 3.4. Elementos admitidos en AndroidManifest.xml y sus descripciones.

Posición

<ma n i fe s t> Raíz Define el paquete de la aplicación y el


espacio de nombres de Android.
<u s e s - p e r mi s s i o n > Raíz Solicita un permiso de seguridad .
<p e r mi s s i o n > Raíz Declara un permiso de seguridad .
<i n s t r ume n t a t i o n> Raíz Declara un componente de prueba .
<a p p l i c at i o n> Raíz Define una aplicación, nombre de clase ,
etiqueta, icono o tema (por manifiesto).
<a c t i v i t y> Secundario de Define una clase Ac ti vit y.
<a p p l i c a t i o n >
<i n t e n t - f i l t e r> Secundario de Declara los In tent s admitidos por
<a c t i v i t y> Activi ty.
<a c t i o n > Secundario de Acción de Inte n t .
<i n t e n t - f i l t e r >
<c ate gory > Secundario de Categoría de In t ent .
< i nte n t - f i l te r>
<d a t a > Secundario de Tipo MIME , esquema URI, autoridad URI
<i n t e n t - f i l t e r > o ruta URI de I n t e nt .
<me t a - d a t a > Secundario de Metadatos generales accesibles a través
<ac t i v i t y> de Compone ntInfo . metaDa tao
<r e c e i ve r > Raiz Define Inte ntReceiver , responde a
In t ents (también adm ite secundarios
<i nte n t - f il t er».
<s e r v i c e > Raíz Define un servicio de fondo (también
admite secundarios <i n t e n t -filte r » .
<p r o vi de r> Raíz Define Co nte n t Pr o v i d e r para gestio-
nar datos persistentes para otras aplica -
ciones.
Android. Guía para desarrolladores IDII

Resumen
Gran parte de la plataforma Android gira en torno a la interfaz de usuario y los con-
ceptos de actividades y vistas. En este capítulo hemos analizado estos conceptos y hemos
desarrollado una aplicación de ejemplo para ilustrarlos. En relación a las actividades
hemos visto los conceptos y métodos implicados, así como los eventos de ciclo vital que
utiliza la plataforma para administrarlos. Con respecto a las vistas, hemos descrito los
tipos más habituales, los atributos que definen el diseño y el aspecto, el enfoque y los
eventos.
Asimismo, hemos visto cómo procesa Android los distintos tipos de recursos, desde
los más sencillos hasta diseños complicados, matrices y animaciones, y su relación con
vistas y actividades. También hemos descrito el archivo de manifiesto de la aplicación y
cómo se combinan todas las piezas para definir una aplicación de Android.
Este capítulo constituye la base para el desarrollo general de IV de Android. Nos
adentraremos en los conceptos de las clases Intent e IntentReceiver, la capa de
comunicaciones que utilizan las actividades de Android y otros componentes. En el si-
guiente capítulo abordaremos este tema, junto con los procesos Service y el sistema
!PC (Comunicación Entre Procesos) de Android con Binder. También completaremos
la aplicación RestaurantFinder.
4
I ntent y ervice
Una aplicación típica de Android está formada por objetos Activity y View en
la interfaz, y de objetos Intent y Service subyacentes. Como vimos en un capítulo
anterior, las actividades se pueden comparar con las pantallas de la IV y las vistas son
componentes de la interfaz. Cuando un usuario interactúa con una pantalla, ésta suele
representar una tarea, como por ejemplo mostrar una lista de opciones para seleccio-
nar, recopilar información a través de formularios o mostrar gráficos y datos. Cuando
la pantalla termina su operación, pasa el testigo a otro componente para realizar la si-
guiente tarea.
En términos de Android, este paso del testigo se realiza a través de un componente
Intent. En capítulos anteriores presentarnos este concepto y vimos ejemplos de código.
En este capítulo, ampliaremos los detalles y nos centraremos en la función de Intent y
su relación con IntentFil t er. Además, terminaremos la aplicación RestaurantFinder,
completaremos el código y analizaremos las clases Intent implicadas. RestaurantFinder
utiliza internamente objetos In ten t para pasar de una actividad a otra, y también invoca
Intent desde las aplicaciones incorporadas de Android, para llamar a un restaurante,
ver su dirección en el mapa o visitar su página Web.
Tras completar la aplicación, pasaremos a la siguiente WeatherReport que nos per-
mitirá utilizar el API Yahoo! Weather para recuperar datos meteorológicos y mostrarlos,
junto con avisos, al usuario en la plataforma Android. En el análisis de esta nueva apli-
cación, utilizaremos BroadcastRec eiver y Servi ce para procesar los Intent.
BroadcastReceiver, además de utilizarse con Intent, emite a diferentes recepto-
res, no sólo para señalar una acción concreta de una actividad. Los servicios son procesos
de fondo, no pantallas de IV, pero también se invocan con In tent.
lIlI 4. intent y Service

Por último, y en relación con los servicios, veremos el mecanismo de Android para
establecer comunicaciones entre procesos (!PC) por medio de objetos Bi n d e r y AIDL
(Lenguaje de Definición de Interfaces de Android).
Android proporciona una técnica de elevado rendimiento para que los distintos
procesos se transmitan mensajes. Es importante ya qu e todas las aplicaciones se ejecu-
tan en su propio proceso aislado (por motivos de segurid ad y rendimiento, debido a la
herencia Linux de la plataforma). Para posibilitar la comunicación entre componentes
de diferentes procesos, algo habitual en los servicios, la plataforma ofrece un enfoque
IPC específico.

Trabajar con clases Intent


Las clases Intent son la red de comunicación de las aplicaciones Android. En mu-
chos aspectos, la arquitectura de Android es similar a los enfoques SOA (Arquitectura
Orientada a Servicios), ya que cada actividad realiza un tipo de invocación Int ent para
ejecutar una operación, sin saber quién será el receptor de dicho I n t ent .
En una situación ideal, no nos importa cómo se realice una determinada tarea, sino
solamente que se realice. De este modo, podemos dividir los objetivos concretos y con-
centramos en el problema a resolver, en lugar de los detalles de implementación sub-
yacentes.
Las clases Inte nt permiten la vinculación tardía, una característica que las di-
ferencia de lo habitual. Esto significa que se asignan y dirigen a un componente qu e
puede procesar una determinada tarea en tiempo d e ejecución en lugar de hacerlo en
tiempo de generación o compilación. Una actividad ind ica a la plataforma que ne-
cesita un mapa de Langtry, Texas, EE.UU., y otro componente, el que la plataforma
cons idera capaz, procesa la solicitud y devuelve el resultado. Con este enfoque, cada
componente se puede modificar, mejorar y mantener sin tener que cambiar la aplica-
ción o el sistema.
Con este concepto y las ventajas del diseño, veremos cómo se define un I n t e n t en
código, cómo se invoca desde una actividad, cómo se realiza la resolución de In te nt
con clases IntentFi l te r , así como diversos Int ent incorporados en la aplicación que
ya puede utilizar directamente.

Definir Intent
Los Intent están formados por tres fragmentos de información principales: acción,
categorías y datos, e incluyen un conjunto adicional de elementos. Una acción es una
simple cadena, como las categorías, y los datos se definen como objetos Ur i. Un objeto
Ur i es un URI genérico (de acuerdo a la definición de RFC 3986) e incluye un esquema,
una autoridad y, opcionalmente, una ruta (como veremos más adelante). La tabla 4.1
muestra los componentes de un objeto Inten t .
Android. Guia para desarrolladores ID
Tabla 4.1. Elementos Intent y su descripción .

Descripción

Extras Datos adicionales que se pasan a 1 n t en t con forma de


Bun dl e .
Componente Especifica un paquete explícito y una clase para utilízar con
In t e nt , opcional, normalmente obtenido de la acción , tipo y
categorías.
Tipo Especifica un tipo MIME explícito (no se analiza desde un URI).
Categoría Metadatos adicionales sobre el In t ent (por ejemplo, a ndroi d .
i n t e n t. cat e g ory . LAUNCHER).
Datos Datos para trabajar con lo expresado como URI (por ejemplo ,
con te n t : / / con tacts /l ).
Acción Cadena totalmente cualificada que indica la acción (por ejemplo,
a n d r o i d . in t en t. ac t i o n . MAIN).

Las definiciones de I n t e n t suelen expresar una combinación de acción, datos y at ri-


butos como una categoría. El sistem a utiliza este diseño como una esp ecie de len gu aje
para resolver qu é clases utilizar p ara satisfacer la solicitud.
Cu ando un compone nte como Act ivit y desea invocar un I n tent, puede hacerlo
de dos formas:

• Invocación implícita de Inte nt .


• Invocación explíci ta de Intent.
En un a invocación implícita de Inten t, la platafor ma determina qué componente
es el más indicad o para ejecutar In te nt, a través d e un proceso de resolución con la ac-
ción, datos y categorías. Lo veremos con ma yor detalle en un apartado posteri or. En un a
invocación explícita, el códi go especifica d irectam ente qu é componente debe procesar el
I nt en t , mediante la especificación de la clase o nombre de componente del recep tor (el
nombre del componente es un a cadena para el paquete y otra para la clase).
Para invocar un Intent de forma explícita, puede u tilizar la sintaxis I nt e nt (Cont e xt
c t x , Cl a ss c ls ) . Con este enfoque, evita el proceso de resoluc ión de Inte nt de
Android y pasa directamente una referencia de clase Ac ti v i ty o Se r v i c e para pro-
cesar el In t en t.
El listado 4.1 muestra la última parte de la actividad Re v i ewDe ta i l de la aplicación
RestaurantFinder. Mue stra varias invocaci ones implícitas de Intent .
Listado 4.1. Segunda parte de ReviewDetail , con invocaciones de Intent.

@Overri de
pub lic boolean onMenu ltemSelected ( in t feature ld , Menultem item) {
I n t e n t inten t = n ul l ;
sw itc h (it e m. g e t l teml d ( )) {
4. Inteni y Seroice

case MENU WEB REVIEW :


if ((this .link ! = null) && !this.link.equals(""»)
intent = new Intent (Intent .ACTION_VIEW ,
Uri.parse(this . link ) );
startActiv ity(intent) ;
else (
new AlertDialog.Bui lder(this)
setTitle (g e t Re s o u r c e s ()
.getString (R.string.alert_label))
. s e t Me s s a g e (R . s t r i n g . no _ l i n k_ me s s a g e )
.setPositiveButton ( IIContinue 'I,
new OnClickListener( )
public void onC lick(Dia loglnterface dialog,
int argl)
)
}) . show () ;

return true;
case MENU MAP REVIEW :
if (( t h i s .lo c a t i o n . g e t Te x t () ! = null)
&& !this.location .getText() .equals( " " ))
intent = new Intent (Intent .AC TION_VIEW ,
Uri .parse ( "geo:O ,O?q=" +
this.location .getText () .toString ( ))) ;
startActivity(intent) ;
else (
new Ale rtDia log .Bui lder (this)
.setTitle(getResou rces ( )
.getString (R .string.alert_labe l ))
.setMessage (R .string .no_location_message)
.setPos iti veButton("Continue" , new OnClickListener ()
pub lic void onClick(Dialoglnterface dialog,
int argl)
)
) ) . show () ;

return true;
case MENU CALL REVIEW:
i f ((this .phone .getText {) ! = null)
&& ! t h i s . p h o n e . g e t Te x t ( ) . e q u a l s(" ")
&& ! th i s .phone. getText () . equals ( "NA ") )
String phoneString =
parsePhone (this .phone.getText ( ) . t o S t r i n g ( ) ) ;
intent = new Intent(Intent .ACTION_CALL,
Uri.parse( "tel: " + phoneString ) );
startActivity(intent);
e lse (
new AlertDia log.Bu ilder (this )
. s e t T i t l e (g e t Re s o u r c e s ( )
. g e t S t r i n g (R . s t r i n g. a l e r t _l a b el) )
. s e t Me s s a g e (R . s t r i n g . n o_ p h o n e_me s s a g e )
.setPositiveButton ("Continue ", new OnClickListener()
public void onCl ick (Dialoglnterface dia log ,
int arg l )
)
}) . show () ;
Android. Guía para desarrolladores 1m

return true ;

retu rn super.onMenultemSelected (featureld , ite m) ;

El objeto Revi ew que la actividad Rev i ewDetail muestra al usuario contiene la


dirección y número de teléfono de un restaurante, y un enlace a su crítica en línea. Con
esta actividad, el usuario puede seleccionar a través del menú entre ver un mapa para
llegar al restaurante, llamar o ver su crítica en un navegador Web. Para permitir estas
acciones, Re v iewDe t ail utiliza aplicaciones incorporadas de Android, a través de in-
vocaciones I nt ent implícitas.
En primer lugar, se inicializa en null una instancia de la clase I ntent para poder uti -
lizarla después en los distintos menús. Tras ello, si el usuario selecciona el botón MENU_
WEB_ REVIEW, creamos una nueva instancia de la variable Intent y pasamos una acción
y datos. Para la acción, utilizamos la cons tante In tent-ACTION_VIEW, con el valor a n-
droid. app. a ction . VIEW, una cadena totalme nte cualificada que incluye el paquete
para ser exclusiva. La clase Intent dispone de numerosas constantes de este tipo para
representar acciones comunes, como I nt e nt .ACTI ON_ ED IT, Intent. ACT I ON_ INSERT
e In t ent. ACTI ON_ DELETE. Las actividades y servicios utilizan los mismos valores cuan-
do declaran su compatibilidad con un determinado Intent (y también puede reutilizar
estas constantes; en la dirección http :// c ode . googl e . com/and ro id /re fer enc e/
andro i d /co nte nt / lnte nt . html encontrará una lista completa).
Tras declarar la acción, aparecen los datos. En este caso utilizamos Uri . parse (lin k)
para especificar un Ur i (donde l in k es un URL HTTP) . El método p a r s e (String)
simplemente analiza las partes del VRI y crea un objeto Ur i , que utilizaremos más ade-
lante en el proceso de resolución. Básicamente, el tipo se pued e derivar del Uri o utilizar
el esquema, autoridad o ru ta. De este modo, el componente correcto puede responder
a la solicitud start Ac t i vi ty (I n t en t i ) Yrepresentar el recur so identificado por el
Uri . Como verá, no hemos declarado directamente ninguna actividad ni servicio para
el In te nt; simplemente decimos que queremos ver ht tp : / /unhost / unaru t a. Es la
vinculación tardía. En el caso de URL, su funcionamiento es evidente pero en Android
se aplica el mismo conc epto en otros muchos tipo s de datos (y también puede definir
los suyos propios).
El siguiente elemento de menú que procesa Rev iewDeta i l es para el caso MENU_
MAP_ REVI EW, donde se vuelve a inicializar I nt en t para usar I ntent . ACTION_VIEW, en
esta ocasió n con otro Uri analizado: " ge o: O, O? q = " + stre e t _ addre s s . Esta combi-
nación de VIEW y el esquema geo invoca otro In t en t, dentro de la aplicación de mapas.
Por último, vemos el caso MENU_ MAP_ CALL, donde se vuelve a inicializar I nte nt, para
realizar una llamada de teléfono con In t en t . ACTI ON_ CALL y el esquema t el : Ur i.
Por medio de esta s sencillas instrucciones, la aplicación utiliza invocaciones implíci-
tas para que el usuario llame o vea el mapa del restaurante seleccionado o consulte su
valoración en una pá gina Web . La figura 4.1 muestra estos botones.
Para que funcionen los botones de la act ividad ReviewDetail de la aplicación,
no hemos tenido que crear el código de la funcionalidad, sino que hemos aprovecha-
do aplicaciones existentes. Con esto completamos la aplicación RestaurantFinder, que
l!nII 4. Intent y Service

ahora permite buscar críticas, para que el usuario seleccione una en una lista, muestre
sus detalles y utilice aplicaciones incorporadas para saber más sobre el restaurante
elegido.

Figura 4.1. Botones de menú de la aplicación RestaurantFinder.

En un apartado posterior encontrará más información sobre aplicaciones incorporadas


y pares de acción y datos. A continuación nos centraremos en el proceso de resolución
de Intent, donde veremos detalles sobre acciones y datos.

Resolución de Intent
Se pueden registrar tres tipos de componentes de Android como controladores
Intent: Acti vi ty, BroadcastReceiver y Service. Suelen registrarse con la plata-
forma como destino de tipos concretos de intent con el elemento <i nt e n t - f i l ter >
del archivo AndroidManifest . xml , como hemos visto anteriormente.
Cada elemento <i n t e nt - f i l ter> se analiza en un objeto IntentFil ter. Al ins-
talar un paquete en la plataforma, se registran sus componentes, incluidos los filtros
Intent. Una vez que la plataforma tiene un registro de filtros Intent, ya sabe cómo
asignar las solicitudes Intent entrantes al correspondiente componente Activity,
BroadcastReceiver o Service.
Al solicitar un Intent, la resolución se realiza a través de los filtros registrados con
la acción, datos y categorías del Intent. Existen dos reglas básicas sobre la comparación
de Intent e IntentFilter que debe saber:

• La acción y la categoría deben coincidir.


• Si se especifica, el tipo de datos debe coincidir, o la combinación del esquema de
datos y la autoridad y ruta deben coincidir.

En los siguientes apartados analizaremos estos aspectos con mayor detalle, ya que
resultan fundamentales para comprender el funcionamiento de las clases Intent.
Android. Guía para desarrolladores l1li

Acción y categorías
Las partes corre spondientes a acciones y categorías son muy sencillas. Son objetos .
St ring, uno para la acción y puede que varios pa ra las categorías. Si no se especifica la
acción en I nt e n t Fi l t e r , se compara con la acción qu e provenga del I nte nt (todas
sirven). Con las categorías, I nte nt Fi l t e r es un superconjunto. Puede tene r categorías
adic ionales más allá de las esp ecificadas por un I nt e nt para coincidir pero al menos debe
tener las que espec ifique el I nt e n t. Además, al contrario de lo que sucede con la acción,
un In t e nt Fi l ter sin categorías coincide solamente con un I nt ent sin categorías (no se
considera comodín). Por ello, las especificaciones de acción y categoría deben coincidir.
Antes de pasar al siguiente componente, los datos, deb e saber que son opcionales.
Puede trabajar únicamente con acciones y categorías, suficiente en muchos casos. Es la
técnica empleada en la actividad Rev i e wLis t . En ese caso, se definía el I nt entFil t e r
(en el manifiesto XML), véase el listado 4.2.

Listado 4.2. Declaración de manifiesto de ReviewList con intent-fiIter.

<ac t i v i t y a ndro id :name= "ReviewList " a ndroid :labe l= "@stri ng /app_name ">
<i n t e n t - f i l t e r>
<c a t e g o ry andro i d : name= "andro id . i ntent . ca tegory .DEFAULT" / >
<a c t i o n android:na me ="com . msi. man n ing .restau ran t.V I EW_L IST " />
< / i n t e n t - f i l t e r>
</ a c tiv i t y >

Para comparar el filtro declarado en el listado 4.2, utilizamos el siguiente I nt e n t en


el código (donde Constant s . INTENT_ ACTI ON_ VIEW_ LIST es la cadena com. msi .
manning. restau r ant . VIEW_ LIST):

Intent intent = new In ten t( Con sta nts.INTENT_ ACTI ON_ VIEW_L I ST) ;
s t artAc t iv i t y {i n t e nt ) ;

La categoría DEFAULT de una actividad significa que Act i vity debe estar presente
como opción para la acción predeterminada, la pulsación del botón central , para un
determinado tipo de datos. Esto se suele especificar en un I nt en tFil t e r, pero no
suele estar presente en In t en t (el filtro sigue coincidiendo; las categorías son un
superconjunto).

Datos
Una vez resueltas la acción y las categorías, aparecen los datos de I n t e n t. Pueden
ser un tipo MIME explícito o una combinación de esquema, autoridad y ruta. En ambos
casos se derivan de Uri. El Ur i mostrado en la siguiente ilustración es un ejemp lo de
utili zación de esquema, autoridad y ruta, véase la figura 4.2.
I!EI 4. lnient y Seroice

weather:/1com.msLmanning/loc?zip=12345

TT~
esquema autoridad ruta

Figura 4.2. Partes de un Uri utilizadas en Android.

El uso de un tipo MIME explícito dentro de un Ur i tiene este aspecto:


content ://com .googl e .p rov ider . Note Pa d/ notes

Seguramente se pregunte en qué se diferencia de la combinación esquema/autori-


dad/ruta ya que muestra los mismos elementos. La respuesta es c on t e nt : 11. Indica
una sustitución de tipos en la plataforma. El tipo se define en el manifiesto del paquete
del proveedor de contenidos, como veremos más adelante.
Al definir clases IntentFil ter, establecen los límites en términos de tipo , esquema,
autoridad y ruta. El complicado proceso de resolución es el siguiente:

1. Si hay esquema pero no hay tipo , coinciden los Inten t de cualquier tipo.
2. Si hay tipo pero no hay esquema, coinciden los Intent de cualquier esquema.
3. Si no hay tipo y no hay esquema, solamente coinciden los In t en t que no tengan
dicho esquema y tipo.
4. Si se especifica una autoridad, también debe especificarse un esquema.
5. Si se especifica una ruta, también debe especificarse un esquema y una auto-
ridad.

En la mayoría de los casos, las coincidencias son muy sencillas pero como compro-
bará, con estas reglas y varios niveles de autoridades y esquemas, todo se puede com-
plicar. Para resumir la resolución de Intent, imagine que Intent e IntentFilter
son piezas diferentes de un mismo puzzle. Al invocar un Intent en una aplicación de
Android, el sistema resuelve la actividad o el servicio (o Br oadcastReceiver) para
procesar la solicitud a través del proceso de resolución por medio de acción, categorías
y datos (tipo o esquema, autoridad y ruta) proporcionados. El sistema busca en todas las
piezas del puzzle hasta que encuentra una que coincide con la indicada, para después
combinarlas y establecer la conexión de vinculación tardía.
La figura 4.3 muestra un ejemplo más elaborado de este proceso. Se define un
IntentFil ter con una acción , la categoría predeterminada y una combinación de
esquema y autoridad (sin una ruta para que coincidan todas). También se muestra un
ejemplo de Intent que coincidiría con este filtro , en este caso con un Uri que se pasa
a través de la siguiente aplicación que crearemos, WeatherReporter.
El IntentFil ter de la figura 4.3 coincide con la acción, categoría y datos (extraídos
del Uri pasado) del Intent utilizado. Este Intent y el filtro provienen de la siguiente
aplicación que crea remos, una aplicación de servicio y alertas meteorológicas, que nos
permitirá desarrollar los conceptos restantes.
A ndroid. Gllía para desarrolladores lID

IntentFilter
<inlenl-fi Iter>
<action android:name="android.inlenl.action.VIEW" />
<calegory android:name ="android.inlenl.category .DEFAULT" />
<dala android:scheme="wealher" android:hosl="co m.msi.manning" />
</inlen l-fi Iler>

Intent
intent = new Inlent(lnl enI.ACTION _VIEW •
Uri.parse("wealher:/Icom.msi.manningnoc?zip=12 345");

Figura 4.3. Coincidencia de Intent e IntentFilter con un filtro definido en XML.

Comparar un URI personalizado


El concepto subyacente de WeatherReporter, la siguiente aplicación que crearemos,
es el uso del API Yahoo! Weather para recuperar datos meteorológicos y mostrarlos al
usuario de Android. Opcionalmente, también alerta de las condiciones del tiempo en
las zonas seleccionadas po r el usuario (ya sea por la ub icación actual o el código po stal
especificado).
En este proyecto veremos cómo definir y regi strar un URI personalizado con un filtro
Intent para que cualquier aplicación pueda invocar un informe meteorológico a tra vés
de un Intent. (Al definir y publicar un I n t e n t de esta forma , otras aplicaciones pueden
utilizar nuestra aplicación). La figura 4.4 muestra la pantalla principal de la aplicación.
Para comenzar, primero abordaremos los aspectos básicos, como el archivo de mani-
fiesto. Aunque ya los hemos analizad o en capítulos anteriores, veremos los detalles con-
cretos de esta aplicación y la definición de filtro s I nten t en XML.El listado 4.3 muestra
el manifiesto de WeatherReporter.
Listado 4.3. Archivo de manifiesto de la aplicación WeatherReporter.
< ? xml ve r sion=111.0 '1 e ncoding= ll u t f - 8 11 ?>
<man i fe s t x mln s :andro id="h ttp : / /s chemas. androi d . c om/ a p k / r e s /andro id "
pa ckage= " c om .m si.manning. we a ther " >
<a p p l icat io n a n d roi d : ico n= " @d r a \'labl e/ ~leat her _ s u n_ c louds _ 1 2 0 "
androi d : l abe l=" @s t r ing /app_name "
andr oid : theme =" @a nd r o id:s t y l e /Theme . Bl a c k"
a ndro id : all owCl e arUs e rData = "true " >

<activi ty and ro i d :na me= "Repo rtVi ewSavedLocati ons "


a ndr o i d: l a b el=" @s t r ing/ app _name _ v i e w_s a v e d_l o c a t i o n s " />
4. lnient y Service

<ac t iv i ty androi d :n am e ~" Repor t S p e ci f y L o cat i o n "


a ndr o id :l abe l =
" @s t r i ng / a p p _ n a me _ s p e c i f y_ l o c a t i o n" />

<acti vi t y a nd ro i d: na me= " Re portV ie wDetai l "


and r o id: l a b el= " @s tri ng / a pp_name _v i e\1_det ail " >
<inten t - fi lte r>
<action a nd ro id : na me= " and ro i d . i n te n t .actio n .VI EW" />
<category a nd ro i d : n a me= " a ndro i d .i n te nt .ca te gor y.DEFAULT " />
<dat a a ndro id: s c heme =" weather "
andr o i d : host= " c om .m si.man n ing" />
</ i n te nt - filte r>
<i n t e n t - fi l ter>
<ac t io n a ndroid:name = "andro i d .int en t . a c ti on. VIEW" />
<data a nd r o i d: scheme-svwee t.b e r "
androi d :host= "com. msi.ma n ni n g " />
</i ntent-fi lter>
<in te n t - f i lter>
<act io n and r oid:n a me ~ "and r o id .in t e nt . a ct i o n .MAIN " />
<category a nd r oid : n ame~
" an droid .in t e n t. ca t e go r y .LAUNCHER " />
</ in t en t - fi lter >
</ activity>

<rece i ve r andr o id:n ame ~". serv i c e.Wea th e rAl e rtS erv i c eRe c e i v er " >
<in ten t - f i l t er>
<act ion androi d:n ame=
" a n d r o id . i n t e n t . a c t i o n . BOOT COMPLETED" />
</i n te nt -fil t er>
</ re ce ive r>

<s e rv i ce a nd r o i d:n ame= ".se rv ice . We ath e rA le r tS e r v ic e " />

</ap plicat ion>

<uses - pe r mi s s ion
android:name = "andr o id .pe r mi ss i on .RECEI VE_BOOT COMPLETED" />
<us e s -pe r mi ss ion
an d r oid :name ~ "a nd roi d .p ermiss i o n .AC C E SS_C OAR S E LOCATIO N" />

<uses - p e rm iss i on an droid:na m e~


" a nd r o i d . p e r mi s s i on. ACCESS FINE LOCATI ON" />
<uses - pe rrniss ion
an d ro id : na me ~

" a nd r o i d . p e r mi s s i o n . ACCESS_ LOCATI ON_ EXTRA_ COMMANDS" />


<us es - permis s ion a ndroid: n ame~ "a n dro i d . p e r m iss io n . I N T E RN ET " />

</ma nifest>

En el manifiesto se definen tres actividades. La más interesante es ReportVi e wDetail,


véase el listado 4.4. Esta actividad define varios filtros Intent que coinciden con la
misma, incluido el que denota MAIN L AUNCHER y otro con el esquema weath e r: / /
com . ms i . manning y la autoridad, véase las figuras 4.3 y 4.4. Es el URI personalizado
admitido por nuestra aplicación.
Android. Guía para desarrolladores I!II

Figura 4.4. Pantalla principal de la aplicación WeatherRepor!er.

Puede utilizar cualquier combinación de esquema, autoridad y ruta, como en este


ejemplo, o un tipo MIME explícito. En un capítulo posterior encontrará más información
sobre tipos MIME y su procesamiento.
Tras las actividades, utilizamos el elemento <r e ce i v er> del archivo de manifies-
to para hacer referencia a una clase BroadcastRe ceiver. En un apartado posterior
encontrará más información sob re esta clase, pero por el momento, sepa que también
se utiliza <i n te n t - f i l ter > para asociar un Intent, en este caso para la acción
BOOT_ COMPLETED. Con esta asociación, indicamos a la plataforma que invoque la clase
WeatherAlertServi c eReceiver una vez completada la secuencia de arranque.
En el manifiesto también tenemos una definición Service. En un apartado poste-
rior veremos cómo se genera este servicio y cómo se utiliza en la aplicación para buscar
alertas de fondo . El último elemento del manifiesto es una serie de permisos necesarios
para la aplicación.
Una vez establecida la base de la aplicación, a continuación veremos el método on S-
tart de la actividad principal, véase el listado 4.4. Aquí se analizan los datos del Uri
que coincide con el filtro Intent y se utilizan para mostrar un informe meteorológico.

Listado 4.4. Método onStar! de la actividad Repor!ViewDetail.

@Override
p ublic void onStart ( ) {
s uper.onStart ( ) ;
t h is.dbHe l pe r = n e l'l DBHelper (this ) ;
t his.dev iceZ i p = WeatherA lertService .device LocationZIP ;

if (( g e tI n t en t () . ge tDa ta () ! = n ull )
&& (g e tI n t e n t() . ge t Dat a() . g etEn c ode d Qu e r y() != nu ll )
I!II 4. lnteni y Service

&& (ge tl n t e n t( ) .getData ( ) . g etEn c odedQuery ( ) . 1e ng t h () > B) ) {


Stri ng que ryS tr ing =
getl nte nt ( ) .ge tData ( ) . getEncode dQu er y ( ) ;
t h i s . re p ort Zi p = queryS t ri ng .s u bstring (4 , 9 ) ;
this . useDeviceLoca t i o n = fa1se ;
e1se (
this .reportZ ip = th is .deviceZip ;
this .useDev iceLocation = true ;

this .saved Location = this . dbHe1per . g e t ( t h is .reportZi p ) ;


th is . deviceAler tEnab1edLocat ion =
th is . db He1pe r .get (DBHe1pe r . DEVI CE_ ALERT_ ENABLED_ZIP ) ;

if ( t h i s. u s e De v i c e Lo c a t i on) (
t his.cu r rentChec k.s etTe xt (R .s t r i ng . v i e w_ch e c kbox_c u r re nt ) ;
i f (t h i s . d e v i c e A1e r t En a b 1e dL o c ati on ! = n ul1 ) (
t h i s .cu r re ntChec k.s etCh ec ke d {t r u e ) ;
e 1s e {
t hi s .curre ntChe c k . s etChec ke d (fa1 se ) ;
}
e 1 se {
th i s . curr ent Ch e ~ k. s e t T ex t( R . s t r i ng. vi ew_c h e c kb o x_s pe cifi c);
if ( t h i s. s a v edLo c a t i o n ! = n u l l) {
if ( th i s. s a v e dL o c a t i o n. a l e r t e n a b 1e d == 1 ) (
this . cu rrentCh e ck . s e tCh e cked (tr u e );
el se {
t h i s. c u r r e n t Ch e c k. s e t Che cke d( f a1 s e);

}
}
loadReport lth i s . repo rtZ ip } ;

En este fragmento, nos centramos en analizar los datos del Uri pasados como parte
del In tent que invoca la actividad.
En primer lugar, establecemos un objeto de ayuda de base de datos. Se utilizará para
consultar una base de datos SQLite local que almacena datos de ubicación especificados
por el usuario. En un capítulo posterior encontrará más detalles al respecto.
En el método también obtenemos el código postal de la ubicación actual del disposi-
tivo a través de LocationManager en la clase Weathe rAlert Service (que de forma
predeterminada es 94102, San Francisco). Es significativo ya que el objetivo es que la
aplicación sepa dónde se encuentra. Queremos que la ubicación del dispositivo sea la
predeterminada para mostrar la información meteorológica. Cuando el usuario viaje
con el teléfono, esta ubicación se actualizará automáticamente. En un capítulo posterior
encontrará más información sobre ubicación y LocationManager. Por el momento,
recuerde que la ubicación del dispositivo se devuelve en forma de código postal.
Tras obtener la ubicación del dispositivo, pasamos al aspecto principal de obtener
datos Uri de un Intent. Analizamos el Uri pasado para obtener la quer ySt ring y
el código postal de la ubicación especificada del usuario. Si la ubicación existe, la utili-
zamos; en caso contrario, utilizamos el código postal de la ubicación del dispositivo.
A ndroid. Guía paradesarrolladores lIf.I
Una vez determinado el cód igo postal que utilizar, establecemos el estado de la casi-
lla de verificación que indica si deben habilitarse las alertas para la ubicación mostrada.
Disponemos de dos tipos de alertas: una para la ubicación del dispositivo (la que sea en
un momento dado) y otra para las ubicaciones guardadas especificadas por el usuario.
Por último, invocamos el método l oa dRe p o rt, que se utiliza para acceder al API
Yahoo! Weather y obtener datos, para después utilizar Hand l er para enviar un mensaje
y actualizar los correspondientes elementos Vi e wde la IU. Estos detalles no se muestran
en el listado, ya que nos centramos en el procesamiento de Inten t pero el patrón es el
mismo utilizado en ejemplos anter iores.
La clave de esta actividad es cómo se registra en el manifiesto para recibir Intent
de wea t he r : / / com. msi . ma nn ing y analizar la parte del URI para los d atos. Esto
permite que cualquier ap licación invoque la activid ad sin conocer má s detalles que el
URI. Es el modelo de separación de responsabilidades de la plataforma Android (la
vinculación tardía).
Una vez vistos el manifiesto y los correspondientes detalles de la clase Ac t i vi t y
principal de la aplicación WeatherReporter, así como el funcionamiento conjunto de las
clases Inten t e Inten t Fi l te r para realizar invocaciones entre componentes, veremos
algunas de las aplicaciones de Android que funcionan de forma similar. Le permiten
iniciar actividades con tan sólo p asar el URI adecuado.

Utilizar actividades proporcionadas por Android


Otra forma de comprobar la resolución de Intent en Android y cómo se utilizan los
URI consiste en explorar la compatibilidad incorporada con Ac t i vi t y. Android incluye
un conjunto de aplicaciones básicas que proporcionan acceso a través de los formatos
indicados en la siguiente relación, véase la tabla 4.2.

Tabla 4.2. Combinaciones de acción Intent y Uri en aplicaciones de Android .

I n t e n t. ACTI ON VIEW geo :latitude , longitude Abre la aplicación de mapas en


la latitud y longitud especificadas.

In ten t . ACTI ON VIEW geo : O, O?q =stre e t+add ress Abre la aplicación de mapas en
la dirección especificada.

In te nt . ACTIO N CALL t e l : phone n u mber Abre la aplicación de teléfono y


llama al número especificado.

I n te n t . ACTION DIAL t e l : phone n umbe r Abre la aplicación de teléfono y


marca (sin llamar) el número
especificado.

Intent. ACT ION DIAL voicemail : Abre la aplicación de teléfono y


marca (sin llamar) el buzón de
voz especificado.
l!nI 4. lntent y Service

In tent.ACTlüN VIEW h ttp ://web_add ress Abre la aplicación de navega-


ción por el URL especificado.
I n t e n t .ACTl üN VI EW https : //web_address Abre la aplicación de navega-
ción por el URL especificado.
I n t e n t. ACTl üN plai n_text Abre la aplicación de navegacion
WEB SEARCH y usa la búsqueda de Google .

Con las acciones y URI mostrados en la tabla 4.2 puede acceder a la aplicación de
mapas, de teléfono y de navegación. Resultan muy fáciles de invocar con el I nte n t
adecuado. Ya hemos utilizado algunas en un capítulo anterior con la aplicación
RestaurantFinder. Android también admite otra construcción, Con t en t P rov i de x, que
utiliza una forma de URI para brindar acceso a los datos. En un capítulo posterior encon-
trará más información al respecto. Al comparar las acciones y URI de las aplicaciones de
Android, comprobará que algunas utilizan un Ur i que se analiza en un tipo (contactos,
medios) y otras utilizan el esquema, o esquema y autoridad, o esquema y autoridad y
ruta, como vimos en un apartado anterior.
Una vez descritos los aspectos básicos de la resolución y los In t ent incorporados, vol-
veremos a nuestra aplicación WeatherReporter para ver el uso de Br oadcastRece i ver,
otra aplicación del concepto de I ntent.

Escuchar con receptores


Otra forma de util izar un I nt e nt consiste en enviar una emisión a un receptor inte -
resado. Una aplicación puede optar por difundir un evento por distintos motivos, por
ejemplo al recibir una llamada o un mensaje de texto . En este apartado veremos cómo
se emiten los eventos y cómo se capturan por medio de Broa dc as t Rece i ve r.
Seguiremos trabajando con la aplicación WeatherReporter del apartado anterior.
Una de las partes más importantes de la misma será la posibilidad de mostrar alertas
meteorológicas al usuario de la zona que haya indicado. Necesitaremos un proceso de
fondo que compruebe el tiempo y envíe las alertas necesarias, para lo que recurriremos
al concepto de servicio de Android. No crearemos la clase Se r vice hasta un apartado
posterior pero necesitamos una forma para ejecutar el servicio en la plataforma al ini-
ciarla, para lo que usaremos una emisión de Intent .

Ampliar el concepto de Intent


Como hemos visto, los objetos In tent se utilizan para pasar entre actividades de
una aplicación de Android. Aunque sea su principal uso, no es el único. Los i nten t
también se utilizan para difundir eventos a cualquiera de los receptores configurados
por medio de los métodos de la clase Contex t (véase la tabla 4.3).
Android. Guia para desarrolladores I:I:D
Tabla 4.3. Métodos para difundir Intent.

sendBroadcast Forma sencilla de difundir un In t e nt.


(Inte nt in t ent)
sendBroadcast ( I nte n t Emite un Int ent con una Stri ng de permiso que los
intent, Str ing receptores deben declarar para recibir la emisión .
r ece iver Permiss ion )
s e ndSt ic kyBroadcast Emite un Intent que se conservatrassu envio para que los
(I ntent i nt ent) receptores puedan obtener datos. Las aplicaciones
que lo utilicen deben declarar el permiso BROADCAST
STI CKY.
sendOrderedB r oadcast Emite una invocación de Intent en serie a receptores
(Intent intent, St r i ng individuales.
r e c ei verPermis si on)
se ndOr de r edBroa dcast Emite un I nt en t y obtiene la respuesta devuelta imple-
(Intent intent, St r i ng mentando un Br oadcastReceive r propio para la emisión
re c e i ve r Pe rmi s s ion , (que se pasa dentro) . Todos los receptores pueden ad-
Broadcas tRe c eiver juntar datos que se devuelven en Br oadc as tR e ceive r.
resultRe c e iver, Handl er Al utilizar este método, los receptores se invocan en
scheduler , int initia l Code, serie.
St r i ng initial Data,
Bundle ini t i a l Ex t ra s )

Al emitir Intent, básicamente reutilizamos el concepto de Intent para enviar un


evento de fondo . Aunque se utilice la clase Intent, se hace de forma diferente que al
invocar rutas Activi t y de frente. Un Intent emitido no invoca ninguna actividad
(aunque BroadcastReceiver puede hacerlo tras recibir el evento, si es necesario).
Otro aspecto importante es el control de los permisos. Al emitir un Intent, puede
especificar un permiso. Básicamente son declaraciones St r i ng que se pueden utilizar
al realizar una emisión que requiere que los receptores declaren el mismo permiso.
La emisión de un Intent es muy sencilla: se utiliza el objeto Con text para lanzarlo
por la conexión y los receptores interesados lo capturan. Android proporciona un con-
junto de emisiones de 1 nten t relacionadas con la plataforma que emplean este enfoque.
Al cambiar la zona horaria en la plataforma, cuando el dispositivo termina de iniciarse
o cuando se añade o elimina un paquete, por ejemplo, el sistema emite un evento por
medio de un Intent. La tabla 4.4 muestra algunos ejemplos concretos.

Tabla 4.4. Acciones de emisión proporcionadas por la plataforma Android .

ACTION TI ME TI CK Se envía cada minuto para indicar que el reloj funciona.


ACTION TIME CHANGED Se envía cuando el usuario cambia la hora del dispositivo.
1m 4. lni ent y Service

ACT ION TI MEZONE CHANGE D Se envía cuando el usuario cambia la zona horaria del
dispositivo.
ACT I ON BOOT COMPLET E D Se envía cuando termina de iniciarse la plataforma .
ACTI ON PACKAGE ADDE D Se envía al añadir un paquete a la plataforma .
ACTI ON PACKAG E REMOVED Se envía al eliminar un paquete de la plataforma.
ACT ION BATTERY CHANGED Se envía cuando cambia el nivel de la batería o el estado de
la carga.

La otra mitad de la emisión de eventos es el extremo receptor. Para registrar la re-


cepción de un Intent, debe implementar un Broadca stRe c eiver. Aquí implemen-
taremos un receptor que capture el Intent BOOT_ cOMPLETED proporcionado por la
plataforma para iniciar el servicio de alerta meteorológica que crearemos para la apli-
cación WeatherReporter.

Crear un receptor
Como el servicio de alerta meteorológica que queremos crear debe ejecutarse de
fondo siempre que se ejecute la aplicación, necesitamos una forma de iniciarlo al iniciar
la plataforma. Para ello, crearemos un Broad c a s t Receive r que escuche la emisión
del Intent BOOT cOMPLETED.
La clase base Broadc a stReceive r proporciona varios métodos que permiten ob-
tener y establecer un código de resultado, datos de resultado (con formato Str i ng) y
un Bundl e. Además, existen métodos relacionados de ciclo vital que se corresponden
a los eventos de ciclo vital de un receptor, como veremos en breve.
La asociación de un Broadca stReceiv e r a un I ntentFil t er se puede realizar en
el código o en el archivo de manifiesto XML. En XML resulta más sencillo y, por tanto, es
más habitual. Es lo que utilizamos en el listado 4.3, donde asociamos BOOT_ COMPLETEO
con la clase WeatherAlertSe r v i c eReceiver, véase el listado 4.5.

Listado 4.5. La clase WeatherAlertServiceReceiver.

p u b l ic class Wea t he r Al er t Se rvi c e Re c e i ve r e x t ends BroadcastRece ive r

@Over r ide
p ub li c vo id o n Rece ive (Co n t e xt 'c o n t e x t , Inte n t i nte n t ) (
if (i n t e n t. g e t Ac t i o n() .eq ua l s ( Inten t .A CTI ON_BOOT_COMPLETED) )
contex t .s tar t Serv ice (n e w I n te nt (conte x t ,
Wea t h e r Al e r t Se r v i c e . c l a s s) ) ;
Android. Guía para desarrolladores lB
Al crear un receptor de Intent propio, se amplía la clase Broadea stRe ee i ver pro-
porcionada por Android y se implementa el método abstracto onReee i v e (Con t ex t e ,
I nt e nt i). En este método iniciamos WeatherAl ertServi ee. La clase Se rviee,
que crearemos a continuación, se inicia por medio del método Servi e e (I n te n t i ,
Bundl e b ).
Recuerde que las instancias de clases receptoras tienen un ciclo vital muy breve y
específico. Al completar el método onReeeive (Context e, I nt ent i ) , la instancia y
el proceso que ha invocado el receptor dejan de ser necesarios y el sistema puede cance-
larlos. Por ello, no puede realizar operaciones asíncronas en un Broade a stRe e ei ve r ,
como por ejemplo vincular a un servicio o mostrar un cuadro de diálogo. Puede iniciar
un servicio, como en este caso, y dejar que se ejecute de fondo. (La vinculación de un
servicio no es lo mismo que iniciarlo, como veremos en breve.)
Después de que el receptor inicie WeatherAle rtServi e e, que se ejecuta de fondo
y avisa a los usuarios de las condiciones meteorológicas por medio de una alerta basada
en Notifieation, nos adentraremos en el concepto de servicios de Android.

Crear un servicio
En una aplicación típica de Android, se crean clases Aetivi t y y se pasa de una pan-
talla a otra por medio de invocaciones Intent. Es el enfoque presentado en un capítulo
anterior y empleado en el resto. Funciona con aplicaciones de pantallas pero no para
procesos que se ejecutan de fondo, para lo que necesitamos un servicio.
El servicio con el que trabajaremos es Weat he rAlertS e rviee, al que enviamos una
solicitud Intent en Weathe rA lertServi e e Re e eiver en el listado 4.4. Este servi-
cio envía una alerta al usuario cuando las condiciones meteorológicas son adversas en
la zona de interés que haya indicado. La alerta se muestra en cualquier aplicación, en
forma de Not ifiea ti on, por medio del servicio de fondo . La figura 4.5 muestra las
notificaciones enviadas.
Un aspecto esencial de las clases Servi ee de Android es su naturaleza dual, es decir,
tienen una doble vida.

Naturaleza dual de un servicio


En Android, un servicio realiza dos funciones: ejecutar una tarea de fondo o mostrar
un objeto remoto para IPC (Comunicación Entre Procesos). Analizaremos ambas funcio-
nes de forma individual. Aunque crearemos instancias Servi ee diferentes para cada
función, también puede crear un servicio que realice ambas al mismo tiempo.
Una tarea de fondo suele ser un proceso que no implica una interacción directa del
usuario o ningún tipo de IV. Evidentemente, es perfecto para el servicio meteorológico.
En lo que respecta a mostrar objetos remotos para IPC, veremos cómo funciona y por
qué es necesario, cuando creemos otro servicio que muestra la creación y exposición de
un objeto remoto.
lB 4. Int ent y Service

I Severe Weather Alert!


San Francisco, CA

Figura 4.5. Alerta basada en Notification que muest ra WeatherAlertService al usuario .

Como ya hemos mencionado, un servicio se p uede iniciar, vinc ular o las dos cosas .
El inicio de un servicio está relaciona do con el aspecto de tarea de fondo. Una vez ini-
ciado/ el servicio se ejecuta hasta que se detiene explícitamente. La vinculación de un
servicio implica el uso de un objeto Se r v i c e Conne c ti on para conectar y obtener una
referencia remota.
A continuación nos centraremos en la creación de Wea the rAlertSe rvice, que rea-
liza el primer tipo de función y habilita las comprobaciones de fon do de la aplicación.

Crear un servicio de tarea de fondo


El servicio de tarea de fondo Wea therAl ertServi c e, que se inicia al ejecutar el
dispositivo a través de Br oa d c a s t Re c eiver como vim os anteriormente, se reproduce
a continuación (véase el listado 4.6).
Listado 4.6. Clases WeatherAlertService, utilizada para registrar ubicaciones y enviar alertas.
p ublic c la ss WeatherAlertService extends Se rv ice

pr ivate static fi n al St r i ng LOC - "L OC" ;


pr iva te sta t ic fin al Stri ng ZI P = " ZI P";
p r i v ate sta t ic fin al long ALERT_QUI ET_PERIOD = 1 0 0 00 ;
private stat ic final long ALERT_POL L_ INTERVAL = 15 0 0 0 ;

p u b lic sta tic String deviceLocationZ IP = " 9 4 1 0 2 " ;

private Time r timer ;


private DBHelpe r dbHelper ;
pr ivate No t i fi c a t i o nMa na g e r nm;

private TimerTask task = new TimerTas k()


Android. Guía para desarrolladores lIiI
p u b lic vo i d r un()
Li st <Locati on > l o cati o ns = dbH elpe r.ge t AIIAlertE nabl ed ( ) ;
fo r (Lo c a t i o n l o c : l o ca t io ns ) {
Weathe rRecord r eco r d = l oadRe c o rd( l o c.z ip);
i f (r e c o r d. i s Se v e r e ( }) {
i f ( (l oc .lasta lert +
WeatherAlertService .ALERT_QUIET PERI OD)
< System .currentTimeMillis (»
l o c .l a s t al e r t = System. curren tT imeMi llis ( ) ;
d b Hel p e r . up d a t e ( l oc ) ;
sendNo tifi cation (l oc . zip , record );

. .. se omite el b loque de alerta de la ub ica c i ó n de l dispositivo

j;

private Handler h andl e r = new Handler( )


p ublic vo id h and leMessage (Message msg ) (
not if yFromHandler( (St ring ) msg.get Data ( )
. g e t (We a t h e r Al e r t Se r v i c e . LOC) , (St r i ng) msg .getData ()
. g e t( We a t h e r Al e r t Se r v i c e . ZI P»;

};

@Overri de
publ ic vo id onCreate ( )
th is . dbHelper = new DBHelper (thi s);
this .t ime r = new Ti mer ( ) ;
this.time r.schedule (this .task , 5000 ,
WeatherA lertServ ice .A LERT_POLL_INTERVAL);
this .nm = (No t i f i c a t i o nMa n a g e r)
getSystemServi ce( Context .NOTIF I CATION_SERVI CE) ;

. .. se omite onStart con LocationManager y Lo cat ion Listener \

@Override
public void o nDestroy ( )
s uper .onDest roy () ;
th i s . dbHe l p er . c l e a n u p();

@Override
p u b l i c IB inder o nBi n d ( I n t e n t intent)
return nul l;

pr ivate Weathe rRecord l oa d Re c o r d( St r i n g z i p )


fin al YWeatherFetcher ywh = new YWeatherFetcher (zip , true );
r e t u r n ywh , getWeather () ;

priva t e vo id notifyF romHandler (Str ing locat ion , String z ip ) {


leII 4. lnient y Service

Ur i uri = Uri .parse ( "weather :// com. msi .ma nn ing /loc?z ip=" + z i p ) ;
I n t e n t inte nt = new Inte nt ( In ten t.ACT ION_V I EW , uri );
Pendingln tent p end i n g l n t e n t =
Pending lnte nt .getActiv i ty (th is , I ntent . FLAG_ACTIV ITY_NEW_TASK,
i nte n t ,Pendingln t e n t . FLAG_ ONE_ SHOT) ;
fi na l No t i f ica t i o n n =
n e w Notifi c at i on (R .dra wa b l e . s evere _ we ather_ 24 ,
"Se v e r e We a ther Al ert ! 'I,
Sys tem.cu r re n t Ti meMi l l is ( » ;
n .setLatestEven t ln fo ( t h is , " Se v e r e We athe r Aler t ! " ,
loca tion , pend i nglnten t ) ;
t hi s . nm. n o t i f y (I n tege r .pa r se l n t(zip), n );

p r i vate vo id se n d Notifi ca tion (Str ing z i p , WeatherRe c o r d reco r d ) {


Message mes s age = Mess age .ob tai n ( ) ;
Bundl e b und le = n e w Bu nd l e () ;
bundl e . pu tS t r ing (We a therAl e rt Servi c e .Z IP , zip ) ;
b und le . p u tS t r i n g (We athe r Aler t Serv i c e . LOC, r e c o r d . ge t Ci t y ()
+ " , " + r e c o r d . g e t Re g i o n ( ) ;
me s s a g e . se tDa ta (b u n dle ) ;
t h i s .handl e r. s endMe s s a g e(me s s a ge );

Lo primero que destacar en la clase WeatherAlertServ i ce es que amplía Service.


Es el mismo enfoque empleado con actividades y receptores: ampliar la clase base, im-
plementar los métodos abstractos y reemplazar los métodos de ciclo vital necesarios.
Tras la clase, se define una serie de variables miembro. Las primeras son constantes
que rep resentan intervalos para consultar las condiciones meteorológicas adversas y pe-
riodos de calma. Este aspecto es significativo ya que hemos definido un umbral muy bajo
durante el desarrollo, y las alertas de condiciones meteorológicas adversas se mostrarán
a menudo en el emulador. En la fase de producción lo reduciremos a 6 ó 12 horas.
Tras ello, utilizamos la variable TimerTa sk para realizar la consulta y obtener las
ubicaciones guardadas po r el usuario para las alertas, a travé s de una invocación de base
de datos. En un capítulo posterior encontrará más información al respecto.
Una vez obtenidas las ubicaciones guardadas, las analizamos y abrimos el informe
meteorológico. Si muestra condiciones meteorológicas adversas, actualizamos la hora
del último campo de alerta e invocamos un método de ayuda para iniciar el envío de
Not i f icat io n. Después de procesar las ubicaciones guardadas por el usuario, obtene-
mos la ubicación del dispositivo de la base de datos por medio de una designación espe-
cial de códigos postales. El proceso de obtener y enviar la alerta se rep ite con la ubicación
actual del dispositivo, al contrario de lo que sucede con las ubicaciones guardadas, si el
usuario ha habilitado esta función. La ubicación del dispositivo se obtiene a través de
Loca t io nMa na ge r . En este ejemplo hemos omitido los detalles, pero encontrará más
información al respecto en capítulos po steriores.
Tras configurar TimerTask, disponemos de una variable miembro Handl e r , que
utilizaremos posteriormente para recibir un objeto Me ssage enviado desde un subpro-
ceso no relacionado con la ID. En este caso, al recibir el mensaje, invocamos un método
de ayuda que crea una instancia de No tif i c ati on y la muestra.
Android. Guia para desarrolladores lID
Llegamos a los métodos de ciclo vital Service que hemos reemplazado, comen-
zando por onCreate. En su interior definimos el objeto de ayuda de base de datos y
un NotificationManager. Tras onCreate, vemos onDestroy, donde limpiamos
la conexión a la base de datos. Las clases Service disponen de estos métodos de ciclo
de vida para que podamos controlar la asignación de recursos, al igual que sucedía con
las clases Acti vi ty.
Tras los métodos de ciclo vital, implementamos el método onBind. Devuelve
IBinder, que es lo que suelen utilizar para comunicarse componentes que invocan mé-
todos en Service. Las clases Service, como mencionamos antes, ejecutan procesos de
fondo y se vinculan para habilitar IPe. Nuestro servicio de alertas solamente realiza una
tarea de fondo, no habilita IPC basada en IBinder /Binder. Por ello, esta clase devuelve
null para onBind. En un apartado posterior encontrará más información al respecto.
Tras ello vemos las implementaciones de los métodos de ayuda. En primer lugar
loadRecord, donde invocamos el API Yahoo! Weather a través de YWea therFetcher.
Seguidamente, sendNoti f i ca tion, que define un mensaje con detalles de ubicación para
pasar al Handler que declaramos antes. La forma de utilizar el controlador en este método
garantiza que el tiempo de procesamiento para obtener datos meteorológicos no se man-
tiene en el proceso principal de la ID. Por último, vemos el método notifyFromHandler
que se invoca desde Handler y desencadena una notificación con objetos Intent que se
vuelven a invocar en WeatherReporter si el usuario hace clic en la notificación.

En la aplicación de ejemplo, iniciamos un servicio y dejamos que se ejecute de fondo.


Nuestro servicio se ha diseñado para dejar una huella mínima (obtención de los datos)
pero por lo general, no se aconseja el uso de servicios de ejecución prolongada. Si no
los necesita, asegúrese de detenerlos al salir de la aplicación. Si necesita un servicio de
ejecución prolongada, puede permitir que el usuario lo utilice o no (una preferencia).
En este sentido, los servicios resultan un tanto paradójicos: son tareas de fondo pero
no significa que deban durar eternamente. En el foro de desarrolladores de Android
encontrará más información al respecto.

Una vez analizados los servicios, después de crear una clase Service e iniciar un ser-
vicio a través de Broadcas tRecei ve r, detallaremos el proceso IPC en Android y otras
características de los servicios, como el inicio frente a la vinculación y el ciclo vital.

Realizar comunicación entre procesos


La comunicación entre aplicaciones de distintos procesos es posible en Android gra-
cias a un enfoque IPC concreto. Es necesario, ya que cada aplicación de la plataforma se
ejecuta en su propio proceso y dichos procesos se separan intencionadamente unos de
otros. Para poder pasar mensajes y objetos entre procesos, es necesario utilizar la ruta
IPC de Android.
lImI 4. lnteni y Service

Para comenzar el análisis de esta ruta, crearemos una sencilla aplicación para exami-
nar la forma de generar una interfaz remota con AIDL, para después conectarnos a la
misma mediante un proxy que mostraremos a través de un servicio (la otra función de
Service). Por el camino, ampliaremos los conceptos de IBinder y Binder empleados
para pasar mensajes y tipos durante IPe.

lenguaje de definición de interfaces de Android


Android proporciona su propio lenguaje de definición de interfaces que puede uti-
lizar para crear archivos IDL. Estos archivos son la entrada de la herramienta aidl, in-
cluida en Android, y que sirve para generar una interfaz remota y clases Stub internas
que pueden, a su vez, utilizar para crear un objeto de acceso remoto.
Los archivos AIDL tienen una sintaxis especial que le permite definir métodos, con
tipos devueltos y parámetros (no puede definir campos estáticos, como en una interfaz
convencional de Java). En la sintaxis básica de AIDL se define el paquete, importaciones
y la interfaz, como en Java (véase el listado 4.7).
Listado 4.7. Ejemplo de archivo .aidl de lenguaje de definición de interfaces.

package com. ms i . mann i ng . b i nde r ;

in t e rfa ce I Simpl e Ma t hS e r vi ce (
in t a dd(i n t a , i nt b);
i n t s u bt ract (i n t a , i nt b);
St r i ng echo (i n Str i ng i n p u t ) ;

En AIDL, las construcciones correspondientes al paquete, las instrucciones de impor-


tación (en este ejemplo no hay) y la interfaz, son muy sencillas, similares a las conven-
cionales de Java. Para definir métodos, debe especificar una etiqueta direccional para
todos los tipos no primitivos con cada parámetro (in, out o inout). Solamente se per-
miten primitivos como in y, por tanto, se procesan de forma predeterminada como in
(y no necesitan la etiqueta). La plataforma utiliza esta etiqueta direccional para generar
el código necesario para las instancias de la interfaz entre límites IPe. Por motivos de
rendimiento, es aconsejable utilizar una única dirección siempre que sea posible.
En este caso hemos declarado la interfaz ISimpleMathService que incluye méto-
dos para sumar, restar y reproducir una cadena. Es un ejemplo muy simple pero sirve
para ilustrar el enfoque.
Al utilizar AIDL también debe recordar que solamente se admiten determinados
tipos, los incluidos en la siguiente relación (véase la tabla 4.5).

Tabla 4.5. Tipos permitidos en rDL.

Primitivos de Java boolean, by te, short, int, No


float,double,long,char.
Android. Guíapara desarrolladores lIiI

String java .lang .String . No


Cha rSequence jav a .l an g. Char Se q uen c e. No
Li st Puede ser genérico; todos los tipos No
utilizados en la colección deben ser
permitidos en IDL. En última
instancia se implementa como
Ar rayList .
Map Puede ser genérico; todos los tipos No
utilizados en la colección deben ser
permitidos en IDL. En última
instancia se implementa como
Ha s h Map .
Otras interfaces AIDL Cualquier otro tipo de interfaz Sí
generada por AID L.
Objetos Pa rce lable Objetos que implementa n la Sí
interfaz Parce lab le de Android.

Una vez definidos los métodos de la interfaz con los tipos y parámetros que devolver
y etiquetas direccionales en formato AIDL, se invoca la herramienta a i d l para generar
una interfaz de Java que representa la especificación AIDL. Puede invocar [ANDROID_
HOME] / t ool s / aidl desde la línea de comandos para ver las opciones y sintaxis de esta
herramienta. Por lo general, basta con apuntarla a los archivos. aidl para que emita una
interfaz Java del mismo nombre. Si utiliza el complemento de Eclipse, invoca automáti-
camente la herramienta aidl (reconoce archivos. aidl e invoca dicha herramienta).
La interfaz generada por AIDL incluye la clase abstracta estática interna Stub que
amplía Bi nde r e implementa la interfaz de clases externas. Esta clase St ub representa
el lado local de la interfaz remota. También incluye un método a s In te r f a c e (IBi nde r
binde r ) que devuelve una versión remota del tipo de interfaz. Los invocadores pueden
utilizarlo para acceder al objeto remoto, desde el que invocar métodos remotos. El proceso
AIDL genera una clase Prox y (otra clase in terna, en esta ocasión dentro de Stub) que se
utiliza para realizar las conexiones y devolver a los invocadores desde el método a s In-
t erface. La figura 4.6 muestra el diagrama de esta relación local/rem ota de IPe.
Una vez obtenidas todas las partes implicadas, debe crear una clase concreta que
amplíe St ub e implemente la interfaz. Tras ello, puede mostrar dicha interfaz a los in-
vocadores a través de un servicio.

Mostrar una interfaz remota


La combinación de todas las partes vistas hasta el momento es el punto en que se
muestra la interfaz remota a través de un servicio. En términos de Android, la exposición
de una interfaz remota a través de un servicio se denomina publicación.
4. Intent y Servíce

Archivo A IDL

IWeatherAlertService.aidl

Interfaz de Java generada


IWeat herAlertServ ice .java

Stub está tico interno


IWeatherAle rtServiee .Stub

Proxy est ático interno yl


IWeatherAlertService.Stub .Proxy
I

IWeatherAl ertServ ice


addAle rtLocation{String z íp'

/
/

Objeto LO CAL
Stub
\/ Objeto REMaTE
Proxy
El invocador utiliza "aslnterface"
Stub.as lnterfaceO dev uelve para obte ner una referencia
el objeto REMa TE (Proxy) a un obje to remoto -
Se devu elve Proxy
\. onTransactO ) transactü )
~IWeath erAlertService.Stub.Proxy
IWeathe rAlertServiee.Stub
IWeatherAlertService aslnterface(IBinder b) JWeatherAlertServiee asl nterface( IBinder b)
IBinder asBinderO IBinder as Binde rO
i boolean onTransacl{int code , Pareel da la, boolean onTransacl{int code , Parcel data ,
Parcel rep ly, int flag,,) Parcel reply, int flags)

Figura 4.6. Diagrama del proceso AIDL de Android.

Para publicar una in terfaz remota debe crear una clase que amplíe Se rvice y devuel-
va un I Bi nde r a través del mé todo onBind (Intent intent ) . El IBinder devuel-
to es lo qu e los clien tes u tilizarán para acceder a un determ inado objeto remoto. Como
vimos anteriormente, la clase Stub generada por AIDL (que a su vez am plía Binder)
se u tiliza para ampliar y devolver una implementació n de una in terfaz remota . Es lo
que se devuelve de l mé todo onBind de una clase Se r v i c e , y la forma de mostrar una
interfaz remota a todos los procesos que se puedan vincular a un servicio. El listado 4.8
m uestra el proceso, donde imp leme ntamos y pub licam os el ISimpleMathServi c e
creado en el apartado an terior.

Listado 4.8. Implementación de Service que muestra un objeto remoto IBinder.

pub l i c c la ss Simp l eMa t hService ex tends Service (

p r i vate fin a l ISimpleMathService.Stub b inde r =


new ISimp leMa t hService . St u b ( ) (
pub li c int add (i nt a , i nt b) {
re tu r n a + b;
Android. Guía para desarrolladores lIImI
)
p ub l i c i nt subtract ( i n t a , in t b ) (
return a - b ;
)
pub lic St ring echo (S t r i ng input ) (
r e t ur n "e c ho ,. + input ;

};

@Ove r ri de
pu b lic IBinder o nBind ( I n te nt int ent ) (
retu r n t his . binde r ;

Se necesita una instancia concreta de la interfaz de Java generada por AIDL para
devolver un I Binde r a los invocadores que se vinculen a Se r v i c e. Para ello, se im-
plementa la clase Stub generada por la herramienta ai dl. Esta clase, como antes, im-
plementa la interfaz AIDL y amplía Binde r . Una vez establecid o IBinde r, se devuelve
desde el método o nBi nd.
Ahora que sabemos dónde puede conectarse un vinculador a un servicio y obtener
una referencia a un objeto remoto, completaremos la conexión mediante la vinculación
a un servicio desde una actividad.

Vincularse a un servicio
Cuando se vincula una clase Acti v i ty a un servicio, por medio del método
Con tex t . b ind Serv i c e ( I nt e n t i, Se rv i c eCo n nec tio n co nn e ct i on , i n t
flags ) , se utiliza el objeto Se rvi ceCon ne cti on pasado para enviar d iversas retro-
llam adas, desde el servicio a la actividad. Cuando el servicio se completa, se produce
una retrollamada esp ecialmen te significativa, que adop ta la forma del método on Se r -
v i c eConne cted (Comp o ne n t Na me c las sName , IBinder b inder ) . La plat afor-
ma iny ecta automáticamente el resultado IB inder o nBi nd (del ser vicio vincu lado)
al método, de modo que el objeto pasa a estar disponible para el invocador. El listado
4.9 muestra este código.
Listado 4.9. Vinculación a un servicio desde una actividad .
p ub l ic c l a s s Ac t i v i t yEx a mp l e e xte nds Act i v ity

private I Si mpl eMa t h Se r v i c e s erv ice ;


p r i vate b o o le a n b ou nd;

. . . Se omi t e n las de c la rac i o ne s d e e lemento s Vi ew

pr iva t e Serv i ceCo nn ect i o n co n nect ion ~ new Se r v i c e Conn e ct i o n ( )


p ub l i c vo id onS e rviceCon nected (Compone nt Name c lassName ,
IB inder iservice ) (
s ervice = I Si mpl e Ma t h Se r v i c e . S t u b . a s I n t e r f a c e( i s e r v i c e};
To a s t . makeT ex t (Ac t i vityE x amp l e . t h i s ,
" c o nn e c t e d to Se rvice " , To a st . LENGTH_ SHORT) . show () ;
bound = t rue;
4. Intent y Seroice

)
publi c voi d o nServiceDisconnected (Co mponentName className ) (
serv ice = null ;
Toast .makeText (Activ ityExample .this ,
" disco nnected from Service " , To a s t . LENGTH SHORT) . s h o w ( );
bound = f a l s e ;

j ;

@Overri de
p u bl i c vo id o nCrea te (Bu ndle i c i c l e ) (
.. . Se omi te l a e xte nsió n de e leme ntos View

thi s .addBu tton. s etOnCli ckL i s t en e r( ne w OnClick Liste ner ( )


publ i c vo i d o nCl ick (View v ) {
try (
int result = s ervice.add (
I nte g er . p a r s eln t (i nputa .ge t Tex t{) .toString ( ) } ,
Inte g er . p a r s eln t( i npu t b. ge t Tex t{) .toString ( ) } );
ou t p u t.setText (String. va lueOf (resu l t ) } ;
catch (De adOb j e c t Ex c e pti on e ) (
Log .e( lIAc t iv ity Examp l e ", I'e r r o r" , e ) ;
catch (Remote Ex cep t i o n e )
Lo g . e(" Ac t i v i t y Ex ample ", I'e r r or ", e);

)
}) ;

Se omite subtra c tBu t t on , s i mi l a r a addButton

@Override
p u bl i c void o nS tart ( }
s u pe r .onStart ( ) ;
i r (! b ound) {
thi s . b i nd Serv i c e(
ne w Inten t (Ac tivi t yE xam p l e .this,
Simpl eMathSe rvice .class } ,
connect ion ,
Co ntext. BI ND_AUTO_CREATE ) ;

@Over r i d e
p u b li c vo i d onPaus e ( )
s u p e r.o nPa use ( ) ;
i r (b o u n d) (
bo und = f a l s e;
th is .u nbi ndServ ice (conne ct io n ) ;

Para utilizar I Simp l e Ma thServi ce definido en AIDL, declaramos una variable del
tipo de interfaz generado por Java. Junto con esta variable se rvice, incluimos boolean
para realizar el seguimiento del estado actual de la vinculación .
Android. Gui« para desarrolladores lmI
Tras ello vemos el objeto ServieeConnee tion, fundamental para el proceso de
vinculación. Se utiliza con métodos Con text para vincular y desvincular. Al vincular
un servicio, se ejecuta la retrollamada onServieeConneeted, en la que se devuelve la
referencia remota a IBinder y se puede asignar al tipo remoto. Después de esta retro-
llamada, se ejecuta onServieeDiseonneeted al desvincular un servicio.
Una vez establecida la conexión y con IBinder en su sitio, se puede utilizar para
realizar las operaciones qu e define. En este caso utili zamos los métodos add, s ub t raet
y echo creados en el listado 4.7.
Con esta clase vemos los métodos Aetiv i ty de ciclo vital. En onSta r t establecemos
la vinculación por medio de b i ndSe rv iee y en on Pa use utilizamos unbi nd Se r v i ee.
Un servicio iniciado pero sin vincular se puede limpiar para liberar recursos del sistema.
Si no los desvincula, utilizará recursos innecesariamente.
Un servicio, como veremos más adelante, se invoca por medio de un In t en t. Como
antes, se puede utilizar invocación implícita o explícita. Cualquier aplicación (con los
permisos adecuados) puede invocar un servicio y vincularse al mismo, y devolver el
IBinder para realizar las operaciones; no es necesario que sea una actividad de la misma
aplicación del servicio (así se comunican las aplicaciones de procesos diferentes).
De este modo llegamos a la diferencia entre iniciar y vincular un servicio, y las im-
plicaciones de ambas opciones.

Iniciar frente a vincular


En Android, los servicios realizan dos funciones y puede utilizarlos como hemos
visto de dos formas correspondientes:

• Inicio: Context . startServ iee (I n t e n t se rv iee , Bundle b ) .


• Vinculación: Context . b i ndServi ee (Intent serviee , Se rvieeConneetion
e , int flag ) .

Al iniciar un servicio se indica a la plataforma que lo abra de fondo y lo manten-


ga en ejecución, sin una conexión concreta a otra activid ad o aplicación. Utilizamos
We a the r Repor t Se r vi e e de esta forma para ejecutarlo de fondo y emitir alertas me -
teorológicas. La vinculación a un servicio, como en el caso de ISimple Ma thServi e e,
nos permite acceder a un objeto remoto e invocar métodos definidos desde una activi-
dad. Como ya hemos comentado, como todas las aplicaciones de Android se ejecutan
en su propio proceso, el uso de un servicio vinculado (que devuelve IBinde r a través
de ServieeCo nnee t io n) nos permite transferir datos en tre procesos.
La transferencia de objetos entre límites de procesos es una operación complicad a,
motivo por el que los proce sos AIDL tienen tantos elementos. Afortunadamente, no es
necesario lidiar con los detalle s internos, basta con seguir una sencilla técnica para crear
y utilizar objetos remotos:
1. Defina su interfaz por medio de AIDL, con un archivo [NOMBRE INTERFAZ ] .
ai dl, (véase el listado 4.7).
lID 4. lntent y Service

2. Genere una interfaz Java para el archivo . a i d l (operación automática en


Eclipse) .
3. Amplíe la clase [NOMBRE_INTERFAZ] . St ub generada e implemente sus propios
métodos (véase el listado 4.8).
4. Muestre la interfaz a los clientes por medio de un servicio y el método
onBind (in ten ti) del servicio (véase el listado 4.8).
5. Vincule el servicio con Se rv i ceCo nnec t io n para acceder al objeto remoto, y
utilícelo (véase el listado 4.9).

Otro aspecto importante del concepto de servicio es el ciclo vital, que depende de si
el servicio está vinculado, iniciado o ambos.

Ciclo vital de servicios


Junto con el ciclo vital general de las aplicaciones y el de las actividades que presen-
tamos en capítulos anteriores, los servicios disponen de sus propias fases de procesos.
Las partes del ciclo vital de los servicios que se invoquen dependen del uso de los mis-
mos: iniciado, vinculado o ambos.

Ciclo vital de servicios iniciados


Si un servicio se inicia por medio de Cont ex t . s t a r t Se rv ice (Intent serv ice ,
Bundle b) , (véase el listado 4.5), se ejecuta de fondo independientemente de que esté vin-
culado o no. En este caso, si es necesario, se invoca el método onCr e a te () del servicio, y
después el método onSta rt (in t id, Bundl e arg s) . Si se inicia el servicio más de una
vez, se invoca repetidamente el método o nS t a rt (int id, Bundle a r g s ) pero no se
crean instancias adicionales del servicio (sólo se necesita una invocación para detenerlo).
El servicio sigue ejecutándose de fondo hasta que el método Con t e xt . s topService ()
lo detiene explícitamente o lo hace su propio método s t op Se l f () . Recuerde que la
plataforma puede detener servicios si necesita recursos, de modo que la aplicación debe
saber reaccionar si llega a pasar (reiniciar automáticamente un servicio, funcionar sin
el servicio, etc .).

Ciclo vital de servicios vinculados


Si una actividad se vincula a un servicio por medio de Conte xt . bindSe r vi ce (Intent
servi ce, Se rvi ceConnecti on c o nnec t io n , int flag s), véase el listado 4.9, se
ejecuta mientras se mantenga la conexión. Una actividad establece la conexión por medio
de Context y también se encarga de cerrarla.
Cuando un servicio se vincula de esta forma pero no se inicia, se invoca su método
onCreate () pero no se utiliza onStart (int id, Bundle args). En estos casos, la
plataforma puede detener el servicio y limpiarlo cuando deje de estar vinculado.
Android. Cuia para desarrolladores 1m

Ciclo vital de servicios iniciados y vinculados


Si un servicio se inicia y se vincula, algo permitido, básicamente se ejecuta de fondo,
como los servicios iniciados. La única diferencia es el ciclo vital en sí. Debido al inicio y la
vinculación, se invocan tanto onStart (int id, Bundle args) como onCreate () .

Limpiar al detener un servicio


Cuando se detiene un servicio, ya sea explícitamente después de haberlo iniciado o
implícitamente cuando no hay más conexiones vinculadas (y no se ha iniciado), se invoca
el método onDestroy () . En su interior, todos los servicios deben realizar la limpieza
final y detener todos los subprocesos activos. Una vez analizada la implementación de
Service, su utilización en inicio y vinculación, y sus ciclos vitales, nos centraremos en
los detalles de los tipos de datos remotos que utilizar con IPC e IDL de Android.

Binder y Parcelable
La interfaz IBinder es la base del protocolo remoto de Android. Como hemos visto,
no se implementa directamente, sino que se suele utilizar AIDL para generar una inter-
faz que contiene una implementación Stub Binder.
La clave de IBinder e Binder en IPC, una vez definidas e implementadas las in-
terfaces, es el método IBinder. transact () y el correspondiente método Binder.
onTransact () . Aunque no se suela trabajar con estos métodos internos directamente,
son la base del proceso remoto. Los métodos definidos con AIDL se procesan de forma
asíncrona a través del proceso de transacciones (lo que permite la misma semántica que
si el método fuera local).
Todos los objetos pasados a través de los métodos de la interfaz definida con AIDL,
utilizan el proceso de transacciones. Estos objetos deben ser Parcelable para poder
incluirlos dentro de Parcel y desplazarlos por la barra de procesos locales y remo-
tos en los métodos de transacción Binder. Solamente tendrá que preocuparse de
Parcelable cuando desee enviar un objeto personalizado a través de IPe. Si utiliza los
tipos predeterminados permitidos en los archivos de definición de su interfaz (s tr ing,
CharSequence, List y Map) todo se controla de forma automática. Si tiene que utilizar
algo adicional, tendrá que implementar Parcelable.
La documentación de Android describe los métodos que debe implementar para
crear una clase Parcelable. Lo más importante es crear un archivo. aidl para cada
interfaz Parcelable. Estos archivos. a idl son diferentes a los empleados para definir
clases Binder, que no debe generar desde la herramienta aidl. Si intenta utilizarla, no
funcionará, como está previsto. La documentación indica que estos archivos se utilizan
como un encabezado en C, de modo que la herramienta no los procesa.
Además, al crear sus propios tipos Parcelable, asegúrese de que realmente los
necesita. La transferencia de objetos complejos por IPC en un entorno incrustado es una
costosa operación y debe evitarse siempre que sea posible (sin mencionar que la creación
manual de estos tipos es muy tediosa).
Con esto termina el análisis del uso de Intent y Service en Android.
II'!!I 4. lnieni y Service

Resumen
En este capítulo hemos cubierto gran parte del territorio de Android. Primero nos
hemos centrado en la abstracción de Inten t, su definición, su resolución con obje-
tos IntentFilter y los controladores de Int ent proporcionados por la platafor-
ma. También hemos visto la invocación explícita e implícita de Intent, y las razones
para seleccionar una u otra. Durante esta descripción hemos finalizado la aplicación
RestaurantFinder.
Tras ver los conceptos básicos de las clases Intent, hemos pasado a una nueva apli-
cación, WeatherReport. En el ámbito de esta aplicación hemos explorado el concepto
de Broadca stRe ceiver y de servicio de Android. Utilizamos el receptor para iniciar
el servicio y lo diseñamos para enviar alertas de condiciones meteorológicas adversas.
Junto con los detalles de implementación de Se rv ice vimos la diferencia entre iniciar
y vincular servicios, y las partes del sistema IPe de Android, que utiliza procesos IDL.
A través del análisis de estos componentes en diferentes ejemplos prácticos se habrá
formado la base de estos conceptos. En el siguiente capítulo avanzaremos a partir de
dicha base y veremos distintos medios proporcionados por Android para obtener y al-
macenar datos, como preferencias, el sistema de archivos, bases de datos y la creación
de Con t en t Prov i de r.
5
Almacenar
y recu rar datos
Siempre que se desarrolla software, una de las construcciones más habituales a las
que enfrentarse es un medio para almacenar y recuperar datos. Después de todo, los
datos son la clave. Aunque existen diversas formas de transferir datos entre lenguajes y
tecnologías, las formas para mantenerlos son escasas: estructuras de memoria, el sistema
de archivos, bases de datos y servicios de red.
Como sucede con otras tecnologías, Android cuenta con sus propios conceptos para
obtener y compartir datos entre aplicaciones, aunque en última instancia dichos con-
ceptos se implementan mediante enfoques convencionales (en la mayoría de los casos).
Android proporciona acceso al sistema de archivos, admite una base de datos relacional
a través de SQLite e incluye un objeto SharedPreference s y un sistema de preferen-
cias para almacenar pares de clave y valor en aplicaciones.
En este capítulo analizaremos cada uno de los mecanismos locales relacionados con
datos (y las opciones de red en un capítulo posterior). Comenzaremos con las preferen-
cias y crearemos una aplicación para aplicar dichos conceptos. Tras ello, crearemos otra
aplicación para examinar el uso del sistema de archivos para almacenar datos, tanto in-
ternos de nuestra aplicación como externos por medio de la compatibilidad con tarjetas
SO de la plataforma. Seguidamente veremos cómo crear una base de datos y acceder
a la misma. Para ello, examinaremos parte del código y los conceptos de la aplicación
WeatherReporter, que utiliza SQLite.
Además de los elementos básicos, Android incluye su propia construcción que permite
a las aplicaciones compartir datos a través de un enfoque basado en el URI denominado
ContentProvider. Esta técnica combina otros conceptos de Android, como el estilo ba-
sado en el VRI de Intent y el conjunto de resultados Cursor de SQLite, para que los
lImI 5. Almacenar y recuperar datos

datos sean accesibles entre aplicaciones. Para demostrar su funcionamiento, crearemos


otra aplicación que utiliza proveedores incorporados y veremos los pasos necesarios
para crear un Conte nt Provi der propio.
Comenzaremos con la forma de almacenamiento y recuperación de datos más sen-
cilla de Android, las preferencias.

Utilizar preferencias
Al pasar de una actividad a otra, resulta de gran utilidad poder disponer de un es-
tado global de la aplicación en un objeto Sha re d Pre f eren c e s . Veremos cómo confi-
gurar datos en un objeto de preferencias y cómo recuperarlos posteriormente. Además,
aprenderemos a crear preferencias privadas de la aplicación o accesibles para otras apli-
caciones del mismo dispositivo.

Trabajar con SharedPreferences


Puede acceder a un objeto Sh a r e d Pre f e r e n c es a través del contexto desde el que
trabaje. Muchas clases de Android tienen una referencia a Con t e x t o se amplían desde
Contexto Por ejemplo, tanto Activi t y como Se r v ice amplían Con t ex t o
Conte xt incluye un método getShar edPref erence s (String name, int
ac ce ssMode) que le permite obtener un controlador de preferencias. El nombre especi-
ficado indica el archivo que contiene las preferencias que le interesan. Si no existe dicho
archivo al intentar obtener las citadas preferencias, se crea uno automáticamente con el
nombre pasado. El modo de acceso hace referencia a los permisos que desea otorgar.
El listado 5.1 muestra un ejemplo de actividad que ilustra cómo se permite al usuario
añadir entradas y después almacenar esos datos a través de objetos Sha r e dPr e f e r e nces
con distintos modos de acceso .

Listado 5.1. Almacenar SharedPreferences con distintos modos .

packagecom. msi .manning .chapter5.prefs ;

/ / se omi ten l a s importaciones

p ublic class Sha redPrefTest l nput exte nds Activ ity {


pub l i c s ta t ic f i n al St r ing PREFS_PR IVATE = "PRE F S_ PR I VATE " ;
p u bl i c s ta tic f i n a l Str ing PREFS_ WORL D_ READ = " PRE FS_ WORL D_ READABLE ";
publ i c static final String PREFS_WORLD_WRITE = " PRE FS_WORLD_ WRITABLE";
pub lic s tatic final String PREFS_WORLD_READ_WRITE =
" PRE FS_I'IORLD_READABLE_WRI TABLE" ;

public sta t ic final String KEY_PR IVA TE ~ " KEY_ PRI VATE " ;
public sta t ic f in al String KEY_WORLD_READ = " KEY_WORLD_READ " ;
public sta t ic f in al Str ing KEY_WORLD_WRI TE ~ " KEY_ WORLD_WRITE " ;
p ublic s ta ti c f in al St r ing KEY_WORLD_READ _WRI TE ~
Android. Guíapara desarrolladores lID

. se a mi t en l a s d ec lara c io nes de e lementos v e w


í

p ri v ate Shared Pre fere nces prefsPriva te ;


pr ivateSh are dPreferencespref sWor l dRead ;
pri v a t e Sha redPrefere nces p re fs Worl d Wri te ;
privateS ha re dP refe re ncespre f s Wor l dRead Wr i te ;

@Over r i de
pub l icvoido nCreate (Bu nd l e ic ic le )

. . v i a w i n f l a t ion a mi tte d fo r br e vi ty

t his . b u t to n .setOnCl ic k Liste ne r (nel'lOnC l ic k Liste ne r ( )


p ubl i c voi d o nC l ick ( f i n a l Vi e w v )
b o ol e an va l id = v a l i da te () ;
i f (v alid) (
p r ef sPr i vat e =
get Sh a r ed Pref eren c e s (
Share dPre fTe st lnput.PREFS_PRI VATE,
Cont ext.MODE_PRI VATE) ;
p r e f s wo r Ldkead =
ge t SharedPrefe r en ces (
S ha re d Pr e f Tes t l npu t . PREFS_ WORLD READ,
Context.MODE_WORLD_READABLE);
prefsWor ldWrite =
g etSharedPre ferenc es (
Sha redPref Testlnpu t .PREFS WORLD WRITE ,
Conte xt .MODE_WORLD_WRITEABLE );
pref sWor ldReadWrit e =
g etSharedPreferen c e s (
Sh a red Pre f Test l n p u t .PREFS WORLD READ WRITE ,
Con t e x t. MO DE- WORLD- READABLE
+ Con t e xt .MODE_WORLD_WRITEABLE);

Edito rpref s Pri vateEdi t or=


p refsPri v ate.edit (};
Edito rpre f sWorl d ReadEdit o r =
pref sWorldRead .edit ( ) ;
Edit o r prefsWor ldWrit eEditor =
prefsWorldWr it e. edit( );
Edit orprefsWor ldReadWriteEdi t or=
p ref sWorldReadWr i te .edit() ;

pref s Pri va t e Edit or .put String (


Sha red Pr e f Test l n p ut . KEY_PRIVATE ,
input Pri v ate .getTe xt . toStrin g ( ));
pref sWorldReadEdit or .putString (
Share d Pre f Tes t l n p ut .KEY_ WORLD_ READ,
inputWorl dRe ad . getText( ) . t o St ri ng());

prefsWor ldWriteEd i t or.pu tString(


SharedPrefTest lnput . KEY_WORLD_WRITE,
inputWorl dWrit e .getText ( ) . t o S t r i n g () ) ;
prefsWo r ldReadWriteEdit or .putSt rin g(
IB!II 5. Almacenar y recuperar datos

Sh a r e dP r e fT e s tl npu t . KEY_WORLD_ READ_WRI TE,


inputWorldReadWrite .getTe xt ( ) .to St ri ng());

prefs PrivateEditor.comrnit ( ) ;
prefsWorl dRea dEd i to r .comrnit ( );
prefsWorl dWriteEdito r .comrni t ( );
p re fs Worl dRe a dWrite Ed i to r .comrni t ( ) ;

I nten t inte nt =
ne wln t en t( Sh are d Pr efTe s t l npu t .this,
SharedPre f Tes tOu t put.class ) ;
startAct iv i ty (i ntent );

}
}) ;

. se omi te va l i date

Tras conseguir una variable SharedPreferences, puede asignar una referencia


a través de Context oEn cada objeto SharedPreferences obtenido, utilizamos un
valor constante diferente para el modo de acceso y en algunos casos incluso añadimos
modos (éstos son de tipo int). Dichos modos especifican si las preferencias deben ser
privadas, si todos las pueden leer, escribir o una combinación.
A continuación, obtenemos un control Edi tor para iniciar la manipulación de valo-
res. Con Edi tor puede establecer tipos String, boolean, floa t, int, y long como
pares de clave y valor. Este conjunto limitado de tipos puede ser restrictivo, motivo por
el que un capítulo anterior ampliamos Context para almacenar el estado de la aplica-
ción en un objeto complejo en lugar de utilizar preferencias. Incluso con esta restricción,
las preferencias son adecuadas y muy sencillas de utilizar.
Una vez almacenados los datos en Edi tor, lo que crea un elemento Mapen memoria,
tendrá que invocar cornrnit () para guardarlos en el archivo de preferencias. Una vez
confirmados los datos, obtenerlos de un objeto SharedPreferences resulta mucho
más fácil que almacenarlos. El listado 5.2 muestra una actividad de la misma aplicación
(el mismo paquete) que obtiene y muestra los datos almacenados en el listado 5.1.

Listado 5.2. Obtener datos SharedPreferences almacenados en la misma aplicación.

pac kagecom.msi . manni ng .chapte r5 . prefs ;

/ / se omi ten l a s imp o r t a ci on e s

public c lass Sha redPre f TestOutput e xte nd s Activity {

. .. se omi t en las declaraciones de variab les de e l e men tos v iew

p r i vate Sh aredPrefe r enc e sprefs Pri v ate ;


pri vate Sha r e d Pr efe rences prefsWo rldRe a d ;
priva te SharedP re f erence s pre f sWorldWri t e ;
Android. Guía para desarrolladores IDII
private SharectPrefere nces prefsWorldReadWr ite;

. . . se omi te onC reate

@Override
public void onStart ( )
super .onStart( ) ;
th is .prefsPrivate =
getSharedPreferences (SharedPrefTestl nput .PREFS PRIVATE ,
Context .MODE_PRIVATE );
this.prefsWor ldRead=
getSharedPrefe rences (SharedPre fTest l np ut .PREFS _WORLD_READ,
Context .MODE_WORLD_READABLE ) ;
this .prefsWorldWrite =
getSharedPreferences(SharedPrefTestl nput.PREFS_WORLD_WRITE ,
Context.MODE_WORLD_WRI TEABLE) ;
this . p r ef s Wo rl d Re adWr i t e =

getSharedPreferences(
Share dPrefTestlnput .PREFS_WORLD_READ WRI TE,
Context . MODE - WORLD- READABLE
+Context. MODE_WORLD_WRITEABLE ) ;

t his.outputP rivate.se t Text (this .pre fsP r ivate.ge tString (


Shared PrefTes tl nput.KEY_PRIVATE , " NA" ));
th is .outputWorldRead.setText (th is .prefsWorldRead .getS t ring (
Shared PrefTest lnput .KEY_WORLD_READ, "NA")) ;
t his .out putWor ldWrite .setText (this .prefsWo r l dWri te .getS tring (
S haredP re f Te st lnput.KEY_WORLD_ WR I TE , "NA"));
this .outputWo r ldReadWrite . setText ( t hi s .pre f s Wo r l dR e a d Wr ite.getString (
SharedPrefTestlnput .KEY_WORLD_READ_WRITE ,
"NA") } ;

Para obtener valores Sh a r e d Pr e f e r e n ce s previamente almacenados, volve-


mos a declarar variables y a asignar referencias. Tras ello, utilizamos métodos como
getString (String key, String d efaul t).
Como puede apreciar, la configuración y obtención de preferencias es muy sencilla.
El único inconveniente son los modos de acceso, que analizaremos a continuación.

Permisos de acceso a preferencias


Puede abrir o crear Sh a re d Pre f e r e n c e s con cualquier combinación de constantes
de modo Co n tex t oComo estos valores son tipos int, se pueden sumar, como hicimos
en los ejemplos anteriores, para combinar permisos. Las constantes de modo admitidas
son las siguientes:

• Co n t ex t .MODE PRIVATE (valor O).


• Con t ex t . MODE_ WORLD_ REA DA BL E (valor 1).
• Co n tex t. MODE_ WORL D_ WRI TEABL E (valor 2).
liD 5. Almacenar y recuperar datos

Estos modos le permiten precisar quién puede acceder a cada preferencia. Si nos fi-
jamos en el sistema de archivos del emulador, tras crear objetos Shared Pre fe r e nces
(que, a su vez, crean archivos XML para mantener los datos), vemos que funciona con
un sistema de archivos basado en Linux.
La figura 5.1 muestra la vista File Explorer de Eclipse; además de los permisos de nivel
Linux de los archivos XML Sha re d Pre fe r e nces creados en el listado 5.1 (originados
automáticamente al utilizar Sha re d Pre fe re nces ).

I Y- (C, com .msl .mannlng.cha~ter5.~~re::.¡f~s


'-." 2008-03-12 13:40 d rwxrwx- - x _ _ I
=""-'~~_~-::.:¡:.::......:::.:~~:...;..::~
T lC7 sharedprefs 2008-03 -12 13:41 drwxrwx - -x
~ PREFS_PRIVATE.xml 114 2008 -03 -12 13:41 - rw- rw- ---
~ PREF5.WORLD.READA8LE.xml 117 2008 -03 -12 13:41 - rw- rw- r--
~ PREFS.WORLD.READA8LLWRITA8LE.xml 126 2008 -03 -12 13:41 - rw- rw· rw·
~ PREFS.WORLD.WRITABLE.xml 119 2008 -03 -12 13:41 - rw- rw- -w-
~ lC7 com.olher.mannlng.chapterS.prefs 2008 -03·12 13:42 drwxrwx - -x
~ lC7 download 2008 -03 -12 13:37 drwxrwxrwx

Figura 5.1. La vista File Explorer de Android muestra permisos de archivos de preferencias .

Cada archivo (o directorio) tiene un tipo y tres conjuntos de permisos representados


por la notación drwxrwxrwx. El primer carácter indica el tipo [d equivale a directorio,
- a un tipo de archivo convencional, y también se puede utilizar el tipo para representar
enlaces simbólicos y otros elementos). Tras el tipo, los tres conjuntos de rwx representan
permisos de lectura, escritura o ejecución para el usuario, grupo y otros, en ese orden. De
modo que al ver esta notación se puede saber a qué archivos puede acceder el usuario
propietario de los mismos, el grupo al que pertenecen o por otros.

ce

Nota .
." ."~

Los permisos de directorio pueden resultar confusos. Lo importante es que los directo-
rios de cada paquete se crean con el permiso x de otros. Esto significa que cualquiera
puede buscar y enumerar los archivos de este directorio. Esto, a su vez, significa que
los paquetes de Android tienen acceso de nivel de directorio a los archivos del resto , de
modo que el acceso de nivel de archivo determina los permisos de archivos.

Los archivos XML Share dP r e fe r e nc e s se incluyen en la ruta / d a tos/ da tos/


NOMBRE_ PAQUETE/ s ha r e d_pre f s del sistema de archivos. Toda aplicación o paquete
(cada archivo. apk) tiene su propio ID de usuario (a menos que utilice s h are Us e r I D en
el manifiesto para compartir el ID de usuario, aunque es una excepción especial). Cuando
una aplicación crea archivos (incluido SharedPreferen ce s), son propiedad del ID de
usuario de esa aplicación. Para permitir que otras aplicaciones accedan a estos archi-
vos, es necesario establecer los demás permisos (como se indica en la figura 5.2, donde
nuestros archivos de preferencias carecen de permisos externos, uno de los archivos se
puede leer, otro se puede leer y escribir y uno sólo se puede escribir).
Android. Guía para desarrolladores 1&1
La parte más complicada de obtener acceso a los archivos de una aplicación desde
otra diferente incluso con los permisos adecuados es la ruta inicial. La ruta se construye
a partir de Co ntexto Por ello, para obtener los archivos de otra aplicación debe cono-
cer y utilizar el contexto de la misma. El listado 5.3 muestra un ejemplo en el que obte-
nemos SharedPre fere nces establecidas en el listado 5.1 pero en esta ocasión de sd e
otra ap licación.

Listado 5.3. Obtener datos SharedPreferences almacenados en otra aplicac ión.

packagecom .other .manning .chapter5.prefs;

. . . se omiten l a s importac iones

public c l a s s SharedPrefTestOtherOutput extends Acti vi ty {

. se omiten las dec laraciones de constantes y v a ri a b l e s


. se omi te onCrea te

@Override
public void onStart ()
super .onStart() ;
Context ot herAppsContext = null ;
try {
otherApps Context =
createPa ckageContext ( "com .msi .manning.chapter5.prefs " ,
Context.MODE_WORLD_WRI TEABLE) ;
} catch (Name No tFo u n d Ex c e p t i o n e ) {
/ / registrar o procesar

this. prefsPrivate =
otherAppsContext.getSharedPreferences (
SharedPrefTestOtherOutp ut .PRE FS _PR IVATE , O) ;
th i s . p refsWor l dRead =
otherAppsContext .getSharedPreferences (
SharedPrefTestOtherOutput.PREFS_WORLD_READ , O) ;
t h i s . p r e f s Wo r l d Wr i te =
otherAppsContext .getSharedPreferences (
SharedP refTestOtherOutp ut.PREFS_WORLD_WRITE , O);
this . p r e f s Wo rl d Re a d Wr i t e =
o t h e r Ap p s Co n t e x t . g e t S h a r e d Pr e f e r e n c e s (
SharedPrefTestOtherOutput .PREFS_WORLD_READ WRITE , O);

this. outputPrivate .setText(


t h i s . p r e f s Pr i v a t e . g e t S t r i n g (
SharedP refTestOtherOutput.KEY_PRIVATE, " NA" ) );
th is . o utputWor ldRead .setText (
t his .prefsWorldRead.getString (
SharedPrefTestOtherOutput . KEY_ WORLD READ, " NA" ) ) ;
this .outputWorldWrite.setText (
this.p refsWorldWr ite .getString (
SharedPrefTestOtherOutput.KEY_WORLD_WRITE, " NA" ));
this.outputWorldReadWr ite .setText (
lB 5. Almacenar y recuperar datos

t h is. p refsWor l dRe a d Wri te .getS tr ing (


Sh ar edPrefTe s t Ot h erOut put . KEY_WORLD_READ_ WRI TE , " NA" ) ) ;

Para obtener datos SharedPreferen ces definidos por una aplicación desde otra de
un paquete diferente, es necesario utilizar el método createPa ckageContext (String
context Name, int mode) . Una vez obtenida una referencia al contexto de la otra apli-
cación, podemos utilizar los mismos nombres para los objetos SharedPreferences
creados por la otra aplicación (es necesario conocer los nombres) para acceder a dichas
preferencias.
Con estos ejemplos disponemos de una aplicación que establece y obtiene
SharedPreferences, y una segunda aplicación que obtiene las preferencias defini-
das por la primera. La imagen mostrada a continuación, (véase la figura 5.2) ilustra su
aspecto (donde NAson las preferencias a las que no hemos podido acceder desde la se-
gunda aplicación debido a los permisos).

Figura 5.2. Dos aplicaciones independientes


en las que se definen y obtienen SharedPreferences.
Android. Guía para desarrolladores lfD
La forma de almacenar Shar e dP r efer e nces en archivos XML en el sistema de ar-
chivos de Android y el uso de permisos nos lleva al siguiente método de almacenamiento
y recuperación de datos, el propio sistema de archivos.

Utilizar el sistema de archivos


Como habrá comprobado, Android disp one de un sistema de archivos basado en
Linux y que admite permisos basados en modos. Existen varias formas de acceder a este
sistema de archivos. Puede crear y leer archivos desde aplicaciones, acceder a archivos
sin procesar incluidos como recursos y trabajar con archivos XML personalizados de
compilación especial. En este apartado analizaremos todos estos enfoques.

Crear arch ivos


En Android resulta muy sencillo crear archivos y almacenarlos en el sistema de ar-
chivos bajo la ruta de datos de la aplicación con la que esté trabajando. El listado S.4
muestra cómo obtener un control File Output St r e am y cómo escribir en el mismo
para crear un archivo.

Listado 5.4. Creación de un archivo en Android a partir de una actividad.

publieelassCreateFileextendsAet ivity {

pr ivate EditText ereateI np ut ;


pr ivate But t on erea teButton ;

@Overri de
p ublie void onCreate (Bund l e i ei e l e)
super.onCreate (ieiel e l;
thi s . s e t Con t e n t Vi ew (R.layout . e r e a t e file ) ;

this .ereateInput=
(Ed i t Te x t) this. fin dViewById (R.id .ereate_ input ) ;
this . e reateButton =
(Butt on) t his .findViewById (R . i d .ereate_butto n ) ;

t h i s . e r e a t e But t on. s e t OnCl i e kLi s t e ner( ne wOnCl i e kLi ste ne r()


publie vo id onC lie k (f i nal Vielv v ) {
Fi leOutputSt ream fos = nul l ;
try {
f o s = openFileOu tpu t ( " fil e name . t xt " ,
Context .MODE_PRIVATE );
f os . wr i t e (e r e a t eI np u t . getTe x t ( ) .toString () . ge t Byt e s( ) ) ;
} e a teh (Fi l eNo tFo un d Ex e e p t i on e l {
Log. e (" Cr e a t e Fi l e", e. getLoealizedMessage () ) ;
} eate h (I OExe e p t i o n e )

Log . e ( " Cr e a t e Fi l e " , e . ge t Loe a l izedMessag e () ) ;


lIflI 5. Almacenar y recuperar datos

} f inally {
if ( f o s !=null )
try {
fos.fl ush ( ) ;
f o s . cl o s e();
) ca tch ( IOException e)
/ / e l i mi n ar

}
s tartAct ivity (
newln ten t (CreateFile .this , ReadFi le .c lass ) ) ;
}
}) ;

Android proporciona un método de e o n t e x t para obtener una referencia a


FileOutputStream, openFileOutput (String name, int mode). Con este méto-
do puede crear un flujo en un archivo que, en última instancia, se almacenará en la ruta
datos/datos/ [NOMBRE_PAQUETE] /archi v os /archivo. nombre de la plataforma.
Tras ello, puede escribir en el mismo como si se tratara de Java convencional. Cuando
termine con el flujo puede vaciarlo y cerrarlo para realizar las tareas de limpieza.
La lectura de un archivo desde el contexto de una aplicación (es decir, la ruta del paque-
te de la aplicación) es también muy sencilla, como veremos en el siguiente apartado.

Acceder a arch ivos


Al igual que openFileOutput, el contexto también dispone del método
op e nFi 1e 1 npu t, que se puede utilizar para acceder a un archivo del sistema de archivos
y leerlo, véase el listado 5.5.

Listado 5.5. Acceso a un archivo existente en Android desde una actividad .

public c lass Re a dFil e extends Ac tiv ity {

private Te x t Vi e ¡·¡ r eadOutput ;


private Button gotoReadResource ;

@Override
public void o nCreate (Bu n dl e i ci c l e)
super .onCrea t e (icic le ) ;
this .setContentView (R.layout .read_fi le ) ;

th is . readOutput =
(Te x t Vi e w) t h is . findVie wByld (R . i d. read_o utp ut ) ;

Fi le lnputStream f i s = n u l l ;
try {
fis = th i s . openFi le lnput {" f i l e n ame. txt" ) ;
byte [ ] r e a d e r ~ new byte [ f i s. available () ] ;
Android. Guía para desarrolladores 1&1
wh i l,e ( f i s . r e a d( r e a d e r ) ! =- l) ( )
this .readOutput.setText (newString (reader ) ) ;
) catch ( I OEx c e p t i o n e ) {
Lo g . e ( "R eadFil e " , e . getMessage () , e );
} f i na lly {
i f ( f i s !=nu ll ) {
try {
f i s . c lase () ;
) ca tch ( I OEx c e p ti o n e )
/ / elimi nar

. . se omite g o t a n e x t Act iv it y a través de s tar tAc tiv ity

La obtención de Fil elnput Stre am para leer en un archivo del sistema de archivos
es lo contrario a obtener un FileOutputStream. Se utiliza ope n Fi l e l np ut (String
name, int mode) para obtener el flujo y después se lee el archivo como si fuera Java
convencional (en este caso completamos la matriz de bytes reader). Cuando termine,
tendrá que cerrar el flujo para evitar malgastar recursos.
Con openFileOutpu t y o p e n Fi l e l np u t puede escribir y leer en cualquier archivo
del directorio de archivos del paquete de aplicaciones con el que trabaje. Además, como
sucede con los modos de acceso y los permisos, puede accede r a archivos de diferentes
aplicaciones si cuenta con los permisos adecuados y si conoce la ruta completa al archivo
(ya conoce el paquete para establecer la ruta desde el conte xto de la otra apl icación) .

Aunque sea una excepción, en ocasiones puede resultar útil establecer el ID de


usuario con el que se ejecuta una aplicación (aunque en la mayoría de los casos
basta con que la plataforma seleccione un ID exclusivo). Por ejemplo, si tiene varias
aplicaciones que necesitan almacenar datos ent re todas pero también quiere que no
se pueda acceder a los datos desde fuera de este grupo de aplicaciones, puede definir
los permisos como privados y compartir el ID de usuario para permitir el acceso.
Puede permitir un UID compartido con el atributo s haredUser I D del manifiesto:
Andro id:s haredUse r ld= "S u I D" .

Además de crear archivos desde la aplicación, puede transferirlos a la plataforma por


medio de la herramienta a a db que vimos anteriormente. Opcionalmente, puede incluir
estos archivos en el directorio de su aplicación, para después leerlos como si fueran ar-
chivos convencionales. Recuerde que no necesita esta ope ración si no es para objetivos
de desarrollo. Lo que hará será crear y leer archivos desde la aplicación o trabajar con
archivos incluidos en una aplicación como recursos sin procesar, como veremos a con-
tinuación.
1&1 5. Almacenar y recuperar datos

Archivos como recursos sin procesar


Si dese a inclu ir archivos sin procesar en su aplicación, puede hacerlo con el directo-
rio re s/raw. En un capítulo anterior analizamos los recursos pero sin adentrarnos en
los archivos sin procesar, para agrupar aquí este enfoque de almacenamiento y acceso
a datos. Al guardar un archivo en r es / ra w, la plataforma no lo compila, sino que está
disponible como recurso sin procesar, véase el listado 5.6.
Listado 5.6. Acceso a un archivo sin procesar desde res/raw .
pub l ic c lass ReadRaHResource Fi le extends Activity {

private Tex tVieH readOutput;


private But ton go toReadXMLReso urce ;

@Override
pub lic vo id o nCreate (Bu ndl e i c i cl e)
super .onCreate (ic icle );
t h is .se tCo nte n tV ieH (R. layout.read_rawresource_fi le ) ;

thi s. readO u tp ut =
(Te x t Vi e H) t his .findVie HBy ld (R .id .readraH res_o utput );

Reso urces r e s o u r c e s = t h is. getRe sou rces () ;


InputStream is = nul l;
try {
i s = resour ces . o pe nRawResource (R. raw . people ) ;
b yte [1 r e ade r = ne w byt e [ i s . available () J;
while ( i s .re ad(re ader) ! =-l) ()
t h is. r e adOu t put. s e t Te x t(n eH S t r i n g( r e a d e r)) ;
) catch ( IOExc e p t i o n e ) {
Lo g. e (" Re a d Ra HRe s ou r c eF il e", e. getMessage () r e ) ;
) f ina lly {
i f (i s ! = n u ll ) {
t ry {
is . c l o s e () ;
) catc h (I OExc e p t i on e )
/ / e limina r

. . se o mi te g ot o ne xt Ac ti vi ty a través d e s t a r t Ac t i vi ty

La obtención de recursos sin procesar es similar a la de archivos. Se obtiene un con-


trol a InputStream, que después puede utilizar para asignar una referencia. Se invoca
Context. getResource s () para obtener la referencia Res ources del contexto de la
aplicación actual y, tras ello, se invoca openRawResource (int id) para vincular al
objeto concreto. El id se obtiene automáticamente de la clase R si incluye el activo en el
directorio res/ raw. No es necesario que los recursos sin procesar sean archivos de texto,
aunque sea lo que utilicemos aquí. Pueden ser imágenes, documentos, etc.
Android. Guia para desarrolladores 1&1
El aspecto significativo de los recursos sin procesar es que la plataforma no los com-
pila previamente y que pueden hacer referencia a cualquier tipo de archivo. El último
tipo de recurso de archivo que veremos es res/xml, que la plataforma compila en un
eficaz tipo binario al que se accede de forma especial.

Recursos de archivos XMl


Al hablar de recursos XML en Android, la terminología puede resultar confusa. Se
debe a que éstos pueden ser recursos generales definidos en XML, como archivos de di-
seño, estilos, matrices, o pueden ser archivos XML concretos de r es/xml.
En este apartado veremos los archivos XML de re s / xml, que reciben un tratamien-
to diferente a otros recursos de Android. Se diferencian de los archivos sin procesar en
que no se utiliza un flujo para acceder a los mismos ya que se compilan en formato bi-
nario al implementarse y se distinguen de los demás recursos en que pueden tener la
estructura XML que desee.
Para ilustrar este concepto utilizaremos un archivo XML que define varios elementos
<p e r s o n> y utiliza atributos para firstname y lastname: people. xml. Obtendremos
este recurso y mostraremos sus elementos en la pantalla (véase la figura 5.3).

Figura 5.3. Actividad ReadXMLResourceFile creada


en el listado 5.8, para leer un archivo de recurso res/xml.

El listado 5.7 muestra el archivo de datos para este proceso, que guardamos en res /
xml.

Listado 5.7. Archivo XML personalizado incluido en res/xml.

<people>
<pe rsonf irstname ="John " lastname =" Ford " />
<personfirstname = " Alfred " lastname= "Hitchcock "/>
<p erson firstna me= "S tanley " La s t name-vvKub r ck " />
í

<perso n fi rs tname= "Wes " last na me= "Anderson " />


</peop le>
5. Almacenar y recuperar datos

Una vez guardado el archivo, la plataforma lo obtiene automáticamente (si utili za


Eclipse) y lo compila en un activo de código fuente . Posteriormente podemos acced er
a este activo si analizamos el formato XML binario admitido por Android, véase el lis-
tado 5.8.
Listado 5.8. Acceso a un recurso XML compilado de res/xml.

pub lic c l a ss ReadXMLRe s our ceFi le ex tends Activ ity {

priva te TextVie\. readOutput ;

@Override
publi c void o nCreate (Bu nd l e i c i c l e)
s uper .onCreate (ici cle );
t h is .se tContentVieH (R . layout. r e a d_ xml r e s o u r c e _ f i l e ) ;

t h is . r e a d Ou t p u t = (Te x t Vi e H)
th is . fi n dV ieHById (R .id.readxm l res output ) ;

Xml Pul l Parser parse r = t h i s . getResou rces () . ge t Xml (R . x ml . peop le ) ;


St ringBu ffer s b = new St r ingBuffer () ;

try {
wh Le (p ars e r . ne xt () ! = XmlPullParse r . END DOCUMENT )
í

Str i ng n a me = p a r s e r . g e t Na me () ;
Str i ng fi rst = n u ll ;
St r ing l a s t = nu ll ;
i f ( {n a me ! = n u ll ) &&name . e qual s (" pe rson " ))
int size = parse r . g e tAttributeCoun t () ;
fo r (i n t i = O; i <s ize ; i++ ) {
Str ing attrName =
pars er .ge tAt tributeName (i ) ;
St r ing attrValue =
p a r s e r. g e t At t r i b u t e Val u e( i) ;
if « (a t t r Na me != null )
&&attrName .eq uals ( " f irs tname " ) )
first = at t rValue ;
} e l se if « a t t r Name ! = n ull )
&& at trName . equa ls (" l a s t n a me " ) )
la st = a t trValue ;

}
i f ((firs t != null ) && ( l a s t ! = n ull )) {
s b .appe nd (la st + " , " + f i r st + " \n") ;

}
th is . readOu tpu t .setText (sb . toStr ing ( ) ) ;
} ca t ch (Ex c e p t i o n e ) (
Lo g . e( " Re a d XMLRe s o u r c e Fi l e " , e .getMessage ( ) , e ) ;

. . se omi t e g oto n ex t Ac ti vi t y a t r a v é s de star tAct i v ity


A ndro id. Guia para desarrolladores lmJI

Para procesar un recurso XML binario se utiliza XmlP u l l Pa r s e r. Esta clase puede
recorrer el árbol XML al estilo SAX. El analizador proporciona un tipo de evento repre-
sentado por un i nt por cada elemento que detecta, como DOCDEC L, COMMENT, START_
DOCUMENT, START_ TAG, END_ TAG, END_ DOCUMENT, etc. Con el método nex t () puede
recuperar el valor del tipo de evento y compararlo con las constantes de evento de la clase.
Cada elemento detectado tiene un nombre, un valor de texto y un conjunto de atribu-
tos opcionales. Puede recorrer el documento por medio del attr i buteCoun t de cada
elemento y obtener el nombre y el valor. En este ejemplo recorremos los nodos de un
archivo XML basado en recursos por medio de un analizador; en un capítulo posterior
encontrará más información al respecto. Además del almacenamiento local de archivos
en el sistema de archivos del dispositivo, dispone de otra opción más indicada para de-
terminados tipos de contenidos, como es escribir en una tarjeta SO externa.

Almacenamiento externo a través de una tarjeta SD


Una de las ventajas de la plataforma Android con respecto a otros dispositivos si-
milares de la competencia es que ofrece acceso a una tarjeta de memoria SO. En última
instancia, puede que no todos los dispositivos de Android tengan una tarjeta SO pero si
la incluyen, la plataforma la admite y facilita su utilización.

Para utilizar una imagen de tarjeta SD en el emulador de Android, primero tiene que
utilizar la herramienta mksdcard para configurar el archivo de imagen SD (este ejecu-
table se encuentra en el directorio tools del SDK). Una vez creado el archivo, inicie el
emulador con la opción - s dc a r d <r ut a a archivo > para montar la imagen SD.

El uso de tarjetas SO tiene sentido si trabaja con archivos de gran tamaño o no dispo-
ne de un acceso seguro permanente a determinados archivos. Evidentemente, si trabaja
con imágenes, archivos de audio o similares, le interesa almacenarlos en la tarjeta SO. El
sistema de archivos interno se almacena en la memoria del sistema, muy limitada en los
dispositivos móviles, por lo que no suele ser aconsejable realizar fotografías en el propio
dispositivo si dispone de otras opciones (como una tarjeta SO). Por otra parte, con datos
de aplicaciones especializadas que deben ser permanentes y de acceso seguro, conviene
utilizar el sistema de archivos interno (o una base de datos interna).
La tarjeta SO no es permanente (el usuario puede extraerla) y la compatibilidad SO
en la mayoría de dispositivos, como dispositivos Android, admite el sistema de archivos
FAT. Debe recordarlo para no olvidar que la tarjeta SO carece de los modos de acceso y
los permisos del sistema de archivos de Linux.
El uso de la tarjeta SO es muy sencillo. Se puede utilizar j a va. io. Fil e y objetos
relacionados para crear y leer (y eliminar) archivos de la ruta / s dca r d (siempre que esté
disponible, lo que puede comprobar con métodos File estándar). El listado 5.9 muestra
un ejemplo de comprobación de la ruta / s dcard, de creación de un subdirectorio en su
interior y después escribir y leer datos de archivo en dicha ubicación.
5. Almacenar y recuperar datos

Listado 5.9 . Uso de técnicas java .io.File estándar con una tarjeta SO.
public class ReadWriteSDCard File extends Activity {

private TextView readOutput;

@Override
public void onCreate (Bu n d l e icic le )
super.onCreate (icicle);
this .setContentView (R.layout .read_ write sdcard file ) ;

t his . r e a d Ou t p u t = (Te x t Vi e w)
th is .findViewByld (R. id.readwritesd_output ) ;

St ring fil e Na me = " t e s t f il e-"


+ S y ste m. currentTimeMi ll is () + " . txt " ;
Fi le sdDi r = n e w Fil e ( " / s d c ard/ ") ;
if (s d Dir . e x i s t s () && sdD ir.ca nWri te () ) {
Fil e u a dDi r = ne w Fil e ( sdD ir . g etAbs ol u t ePa th ()
+ " /unl o c k i ng _ a n d r o i d" ) ;
u a d Di r . mk d i r() ;
i f (u a d Di r . e x is ts () & & uad Di r . c a nWr i t e () ) {
Fi l e f ile = new Fi le (u a d Di r. getAbs o l u t ePath ()
+ " /" + fi l e Name ) ;
t ry {
fi l e . c r e a t eNe wFi l e( ) ;
) ca tch ( I OEx c e p t i o n e ) (
/ / r e g i s t r a r o p r o c e s ar

i f ( f i l e . e x i s t s () && file.ca nWr ite ()


FileOu tputStream fos ~ nu l l ;
try {
fos = n ew Fi leOutputStream ( fil e) ;
fos.write ( "I fear you speak upon the r a c k ,"
+ " wh e r e men enfo r c ed d o s p e a k"
+ " a n y t h i n g . " . getBy tes () ) ;
) catch {Fil e No t Fo u n d Ex c e p ti o n e ) (
Log . e (Re a d Wr i t e S DCa r d Fi l e . LOGTAG, " ERROR", e ) ;
) catch {I OExc ep ti on e ) (
Lo g . e (Re a d Wr i t e S DCa r dFi l e . LOGTAG, " ERROR" , e ) ;
) finall y {
if ( f o s ! = n u ll) {
try (
f o s. fl u sh () ;
f o s . cl o s e ( ) ;
) catch ( I OEx c e p t i o n e )
/ / elimin ar

)
) el se {
/ / regist rar o p rocesar - error a l esc r i b ir e n el arch ivo
)
} el se {
/ / r e g i s t r a r o procesar -
/ / no se puede escribir en /sdca r d/un locking_andro id
Android. Guia para desarrolladores lID
} e ls e {
Log.e ( "R eadWri te SDCardFi l e.LOGTAG" ,
" ERROR /s d c a r d p ath n ot availab l e ( d i d yo u c r e a t e "
+ " a n SD i mage Hi th t he mksdc a rd too l , "
+ " and start e mulator wi t.h - sdc a r d "
+ <p a t h_ to_ f i l e> optio n? " ) ;

Fil e r File =
new Fi l e ( " / s d c a r d / u n l o c k i n g andro i d/ " + f il eName) ;
i f ( r Fil e . e x i s ts () && r Fil e . canRea d () )
File l np u t Stream fi s = n ull ;
try {
fis = ne w Fi l e l npu tS tream ( rFil e) ;
b yte l l r e ader = new b y t e [ fi s . a vailab le () l ;
wh Le (f i s. r e a d( r eader) !=-l) {
í

}
thi s. r e adOut pu t. s etTe x t(ne H S tring(r e a d e r»;
) catch (I OEx c e p t i on e) {
/ / reg istrar o procesar
} fina lly {
i f (f i s !=null) {
t ry {
fi s . clo s e ( );
) cat c h (I OEx c e pti on e )
/ / e l i mi nar

}
} e lse {
t h is . readOu t put .setTe x t (
" Unabl e t o r e ad /Hr i te sdc a rd f il e, see l ogca t o u t p u t " ) ;

Lo primero que hacemos en la clase ReadWr i teSOCardFile es establecer un nombre


de archivo para el archivo que queremos crear. Para ello , añadimos un sello de tiempo
para crear un archivo exclusivo cada vez que se ejecute la aplicación. Tras ello, creamos
una referencia a un objeto File en el directorio / sdcard. Después creamos una refe-
rencia File a un nuevo subdirectorio, / s dc a r d / u n l oc k i n g _ andro i d (en Java, tanto
archivos como directorios se pueden representar por el objeto File). Seguidamente in-
vocamos mkdi r () para garantizar que se crea el subdirectorio.
Una vez definida la estructura, seguimos un patrón similar con el archivo. Creamos
una instancia de un objeto File e invocamos c r e a t e Fi l e () para crear un archivo en
el sistema de archivos. Seguidamente, sabemos que dicho archivo existe y que podemos
escribir en él (recuerde que la tarjeta SD tiene permisos de escritura de forma pred eter-
minada por utilizar el sistema de archivos FAT), por medio de Fil elnputStream.
Trascrearel archivo y añadirle datos, creamos otro objeto File con la ruta completa para
leer los datos. Podríamos utilizar el mismo objeto File generado al crear el archivo pero
para el ejemplo hemos optado por comenzar con un nuevo File. Con la referencia File,
creamos Fil e Output Stream y leemos los datos que almacenamos anteriormente .
mi 5. Almacenar y recuperar datos

Como puede apreciar, trabajar con archivos en la tarjeta SD es muy similar a j a v a.


i o. Fil e . Se necesita código de Java para crear una solución robusta, con permisos,
comprobación de errores y registro de actividades pero resulta sencillo y completo. Si
tiene que procesar muchos objetos Fil e, probablemente deba crear sencillas utilidades
locales para incluir las tareas habituales y no tener que repetirlas continuamente (abrir
archivos, escribir, crearlos, etc.). Puede utilizar el paquete cornmons . i o de Apache, que
incluye una clase Fil eUtils que se encarga de este tipo de tareas y de otras muchas.
El ejemplo de la tarjeta SD completa este apartado, donde hemos visto distintas formas
de almacenar datos de archivo en la plataforma Android. Si tiene elementos estáticos pre-
definidos, puede usar re s/ raw. Si tiene archivos XML, puede usar r es/xml. También
puede trabajar directamente con el sistema de archivos y crear, modificar y recuperar
datos en archivos (en el sistema de archivos local o en la tarjeta SD si está disponible).
Otra forma de trabajar con datos, más adecuada en determinadas situaciones (como para
compartir datos relacionales entre plataformas) consiste en utilizar una base de datos.

Almacenar datos en una base de datos


Una de las ventajas de la plataforma Android es la incorporación de una base de
datos relacional. SQLite no tiene todas las funciones de los productos de base de datos
cliente/servidor comerciales pero ofrece todo lo necesario para almacenamiento local
de datos, a la vez que resulta rápido y sencillo.
En este apartado trabajaremos con el sistema de base de datos SQLite incorpora-
do para crear y consultar una base de datos, y actualizar y trabajar con la herramienta
s ql i t e 3 de Debug Bridge. Lo haremos en el contexto de la aplicación WeatherReporter,
que utiliza la base de datos para almacenar las ubicaciones guardadas por el usuario y
conservar las preferencias de cada ubicación. La figura 5.4 muestra los datos guardados
entre los que el usuario puede elegir; cuando selecciona una ubicación, se recuperan los
datos de la base de datos y se muestra un informe meteorológico.
Comenzaremos con la creación de la base de datos utilizada por WeatherReporter.

Crear y acceder a una base de datos


Para utilizar SQLite debe disponer de ciertos conocimientos generales sobre SQL. Si
necesita desempolvar los comandos básicos, como CREATE, INSERT, UPDATE, DELETE
Y SELECT, le aconsejamos que consulte la documentación de SQLite (http: //www .
s q l i t e . org/lang . html).
En nuestro ejemplo, pasaremos directamente a crear una clase de ayuda de base de
datos para la aplicación. Creamos una clase de ayuda para que los detalles relacionados
con la creación y actualización de la base de datos, de apertura y cierre de conexiones,
y de ejecución de consultas se incluyan en un mismo punto y no se repitan en el código
de la aplicación. De este modo, nuestras clases Activity y Se r v ice podrán utilizar
sencillos métodos get e insert, con objetos específicos que representen el modelo, o
Android. Guía para desarrolladores lID
Colle ctions en lugar de abstracciones específicas de base de datos (como el objeto
Curso r de Android qu e representa un conjunto de resultados de consulta). Ima gine que
esta clase es una Capa de Acceso de Datos (DAL) en miniatura.

Figura 5.4. Pantalla Saved Locations de WeatherReporter,


que obtiene datos de una base de datos SOLite.

El listado 5.10 muestra la prim era parte de la clase de ayuda DBHel per, qu e incluye
varias clases internas qu e verem os más adelante.

Listado 5.10. Parte de la clase DBHelper con la clase interna DBOpenHelper.

pub lic c lass DBHelper (

public sta t i c final Str ing DEVICE_ALERT_ ENABLED_ ZI P = " DAEZ9 9" ;
pub lic s t at i c fin al String DB_ NAME ~ " ~I_a l e r t" ;
pub lic s t a tic f i na l String DB_TABLE = "I·,_ a l e r t _l o c " ;
p u b lic s ta t ic fi na l int DB_VERS ION = 3 ;

private sta tic fina l String CLASSNAME = DBHe l p e r . class. getSimpleName ( ) ;


private static f i n al Str ing [J COLS = riew String [ J
{ "_id", " z i p" ; " c i t y " , " r e g i o n ", " 1 a s t a l e r t" , " a Le r t e nab Led" }¡

pr ivate SQLiteDatabase db ;
p r ivate f i n a l DBOpe n Helper dbOpen Helper ;

pub lic static class Location {


public long id ;
public long l a s t a l e r t ;
II!II 5. Almacenar y recuperar datos

p u bl ic int a le rtenabled ;
p ubli c St ring z i p ;
p ub lic S tring ci t y ;
pub l ic String region ;

. . . se omiten los co nstructo res d e Locat io n y toString

p rivate static c lass DBOpenHel per exten ds


SQLi teOpe nHe l per (

priva te s ta t ic f i na l S t r i n g DB_CREATE = " CREATE TABLE "


+ DBHe lpe r . DB_TABLE
+ " (_ i d I NTEGER PRl MARY KEY, z i p TEXT UNI QUE NOT NULL, "
+ " c i t y TEXT , reg i o n TEXT, lasta lert I NTEGER, "
+ " al e r t e n a b l e d IN TEGER) ; " ;

p u b l ic DBOpenH el pe r (Contex t contex t , St ri n g dbNa me, i n t v ersi on ) {


s u p e r ( c o nte x t, DBHelper. DB_NAME, nu ll, DBHe lper . DB_VERSION) ;

@Ove r r i d e
publi c void onCr ea t e (SQLi te Da tab a s e db )
try {
db . e x e c SQL (DBOpenH elpe r.DB_CRE ATE l;
l c atch (SQLEx c e p ti on e l {
Lo g . e (Con st an t s .LOGTAG, DBHelper. CLASSNAME , e l ;

@Ov e r r i d e
public void o nOp e n (SQLi t e Da t a b a s e db )
s u p e r .on Ope n {db l ;

@Over r i d e
p u b l ic vo i d o n Up g r a d e (SQLi t e Da t a b a s e db , in t o ldVersio n ,
int n e wve rs on ) {
í

db . execSQL (" DROP TABLE IF EXI STS " + DBHe lpe r . DB_ TABLE ) ;
this .o n Create (db ) ;

En la clase OBHelper tenemos varias constantes que definen importantes valores es-
táticos relacionados con la base de datos con la que queremos trabajar, como el nombre, la
versión y un nombre de tabla. Tras ello vemos algunas de las partes principales de la clase
de ayuda que hemos creado para la aplicación WeatherReporter, las clases internas.
La primera clase interna es un sencillo bean Lo cati on que se utiliza para represen-
tar la ubicación seleccionada por el usuario para guardar. Intencionadamente, esta clase
carece de mecanismos de acceso y mutadores, ya que añaden sobrecarga y no los nece-
sitamos para la aplicación (no la mostraremos). La segunda clase interna es una imple-
mentación SQLi t e Op e nHelpe r.
Android. GI/ía para desarrolladores lIiI
Nuestra clase interna DBOpe nHel per amplía SQLi teOpen He lper, que es una
clase proporcionada por Android para ayudar a la creación, actualización y apertura de
bases de datos. En esta clase incluimos una cadena que representa la consulta CREATE
que utilizaremos para crear la tabla de la base de datos; muestra las columnas exactas
y los tipos que tendrá nuestra tabla. Los tipos de datos empleados son muy sencillos:
utilizaremos principalmente I NTEGER y TEXT (si necesita información adicional sobre
otros tipos admitid os por SQLite, consulte la documentación en h ttp: //www . sqli te .
org / dat a t ype 3 . ht ml). Además, en DBOpenHelpe r impl ementamos varios métodos de
retrollamada SQLi teOpenHelpe r , en especial onCreate y onUpgrade (también se admite
onOpen pero no lo utilizaremos). Explicaremos el funcionamiento de estas retrol1amadas
y la utilidad de la clase en la segunda parte de DBHelpe r (la clase externa).

Listado 5.11. Parte de la clase DBHelper que muestra métodos de utilidad .

publi c DBHelper (Con tex t c ontext ) (


t his. clbOpenHe lpe r = new DBOpen Helpe r (c o n t e x t, " WR_ DATA", 1 ) ;
this.establi s hDb ( ) ;

pri va t e v oid establi shDb ()


i f (thi s. clb == n u ll) (
t his . clb = thi s .clbOpenH elper . g etWri t ableDa t aba s e () ;

p u b li c v oid cl e anup () {
i f (this . clb ! = n ull ) (
this .clb .clos e( ) ;
this. clb = n ull;

p ubli c v o i d ins e r t (Lo c a t i o n l ocati on)


ContentVal u e s va l ues = new Co nte n t Va lu e s () ;
v alues .pu t ( " zip ",locat i o n . zip );
values . put ( " c i t y", location . c ity ) ;
v alues .put( "regi on" , l ocation .region ) ;
va l ue s . p u t ( " la s t a ler t " , l o c a t ion . l a sta le r t ) ;
v a l ue s . p u t ( " a l e r te na b l e d " , l o c a tion . ale r te na b l e d ) ;
t his .db. in s ert (DBHelper .DB_TAB LE , null , v a l ues ) ;

public void upd a t e ( Lo c a ti on l o c a t i on)


Co n t e n t Va l u e s v alue s = new Con tentValues () ;
va lues . put (" zip " , l oca ti on . zip ) ;
values .put (" c i t y", locatio n . c i ty ) ;
values .put ( "region " , location .region );
v a l ue s . p u t (" lastalert " , locati on .lastalert ) ;
values . pu t ( "al ertenabled " , location . a lertenabled) ;
th is . clb . up da t e (DBH elper . DB_TABLE, v a l u es , " _ i d =" + l o c a ti on . i d , null );

p ub li c void delete ( l o ng i d )
wr.r.'II 5. Almacenar y recuperar datos
~

t h i s , db. delete (DBHe l p e r . DB_ TABLE , " _ id= " + id , nul l ) ;

publie void delete (Str ing zip ) {


t h i s . db. de le te (DBHe l p e r . DB_ TABLE, " z i p = '" + zip + " , ", n ul l ) ;

p ub lie Loeation get (S t ring zi p)


Cursor e = null ;
Lo e a ti o n l o e a t i o n = null ;
try {
e=this .db .query (true , DBHe l per .DB TABLE , DBHelper.COLS ,
"z i p = t 11 + zip + 11 f" I null , nul l , nu l l , nu ll ,
n ull ) ;
i f (e . g e t Coun t{) > O) (
e . moveTo First ( ) ;
l o e at i on = rie w Lo e a t i on () ;
l o e a t i on . id = e . get Long (O) ;
l o e a ti on . zip = e. getSt r i ng ( 1) ;
loeation . ci ty = e. getS tr ing (2) ;
l o e a t i o n . regio n = e . getS tring ( 3) ;
loeation . l a s t a l e r t = e . g et Long ( 4) ;
loeat io n . ale rtenab led = e . getln t (5) ;
)
} eateh (SQLEx e ep ti on e ) (
Log.v (Co ns tan ts . LOGTAG, DBHel per .CLASSNAME, e ) ;
fin all y {
i f (e ! = null && ! e. isClosed () ) (
e .elose ();

return l o c a ti o n¡

publ ie Li s t <Lo e a ti o n > getAll ()


Ar rayList<Loeation> ret = riew ArrayList<Loeation> () ;
Cur sor e = null;
try (
e = t his . db . q u e r y (DBHelp e r. DB_T ABLE , DBHelper. COLS , nu l l ,
nu l l , nul l , nu l l , n u l l );
i nt num R0\1S = e. getCount ( ) ;
e . moveToFirst ( ) ;
fo r (in t i = O; i < numxows r ++ i)
Loeation loea tion = new Loeation () ;
l o e at i o n . id = e . getLong (O) ;
loeation. zip = e. getStri ng ( 1) ;
loeation. ei ty = e . getStr i ng (2) ;
loeatio n . r e gi on = e . getString (3 ) ;
l o e a t i on . las t a le r t = e . get Long (4) ;
l oea t i on . a le r t enab l ed = e . get l nt ( 5) ;
if ( ! l o e a ti o n .z i p . e quals( DBHelpe r . DEVI CE_ ALERT ENABLED ZIP » )
ret. add (loeation ) ;

e .moveToNext ( ) ;
}
} eateh (SQLEx e e p t i o n e )
Android. Guía para desarrolladores 1m
Log. v (Con stan t s .LOGTAG , DBHe l p e r .C LASS NAME , e ) ;
} f inal1y {
i f (c ! = nu l1 && ! c. i s Cl o s e d())
c . c l ase () ;

retu rn r e t;

. . . se omi te getA l lA le rtEn ab led

Nuestra clase DBHelper contiene una referencia de variable miembro a un objeto


SQLiteDatabase, véase el listado 5.10. Este objeto es el motor de bases de datos de
Android y se utiliza para abrir conexiones, ejecutar elementos SQL y otras operaciones.
Tras ello, se crea una instancia de la clase interna DBOpenHelper en el constructor.
Seguidamente, se utiliza dbOpenHelper, dentro del método establishDb si la refe-
rencia db es null, para invocar openDatabase con el contexto, nombre y versión de la
base de datos actuales. De este modo se establece db como instancia de SQLi teDatabase
a través de DBOpenHelper.
Aunque puede abrir una conexión de base de datos directamente, el uso de open in-
voca las retrollamadas proporcionadas y facilita el proceso. Con esta técnica, al intentar
abrir la conexión a la base de datos, se crea o actualiza automáticamente (o se devuelve),
si es necesario, a través de DBOpenHelper. Aunque el uso de DBOpenHelper impli-
ca pasos adicionales, una vez aplicado resulta muy útil para modificar la estructura de
tablas (basta con incrementar la versión y hacer lo necesario en la retrollamada onUp-
grade; sin esto, sería necesario modificar y/o eliminar manualmente y volver a crear la
estructura existente). Otro elemento importante que proporcionar en una clase de ayuda
como ésta es un método cleanup. Lo utilizan los invocadores para llamarlo cuando se
detienen, para cerrar conexiones y liberar recursos.
Tras el método cleanup vemos los métodos SQL que encapsulan las operaciones
proporcionadas por la clase de ayuda. En esta clase contamos con métodos para aña-
dir, actualizar, eliminar y obtener datos. También tenemos los métodos especializados
get y get all, en los que vemos cómo se utiliza el objeto db para ejecutar consultas.
La clase SQLi teDa tabase dispone de numerosos métodos como insert, upda te y
delete, y proporciona acceso query directo que devuelve un Cursor sobre un con-
junto de resultados.

I ·~~AS.~S de datos corrpaquetes prívadoe .... " '. [ -' .. i . .•

Al contrario de lo que sucede con SharedPreferences como vimos anteriormente,


una base de datos no puede ser WORLD_ READABLE. A las bases de datos solamente
puede acceder el paquete en el que se hayan creado, de modo que sólo puede acceder
el proceso que lo haya creado. Si tiene que pasar datos entre procesos, puede utilizar
AIDL/Binder, o crear un ContentProvider (como veremos en breve) pero no puede
utilizar una base de datos directamente entre procesos y paquetes.
l1mII 5. Almacenary recllperar datos

Por lo general, se obtienen grandes resultados con los pasos básicos relacionados con
la clase SQLi t eDa tab a s e, como hemos visto, y le pe rmite crear un mecanismo de al-
macenamiento de datos útil y rápido para sus aplicaciones Android. El último aspecto
relacionado con bases de datos que veremos es la he rramienta s q l i te3, que le permite
manipular datos externos a una aplicación.

Utilizar la herramienta sqlite3


Al crear una base de datos para una aplicación en Android, los archivos de la misma
se crean en /datos/datos/ [NOMBRE_PAQUETE] /ba s e de datos/db. nombre. Estos
archivos son SQLite, pero existe una forma de manipular, volcar, restaurar y trabajar con
bases de datos a través de los mismos por medio de la herramienta s q l i t e 3.
Puede acceder a esta herramienta a través de la línea de comandos si ejecuta los si-
guientes (no olvide utilizar el nombre del paquete, ya que en el ejemplo empleamos el
de la aplicación WeatherReporter):
cd [ANDROID_HOME J/tools
aclb she ll
sql ite3/data/data/com .ms i .ma nni ng .chapter4/databases/w_alert .db

Una vez en la ventana de símbolo del sistema (con el signo #), puede ejecutar comandos
sqlite 3, como . help para empezar. (En h ttp://www . s q l i te. or g / sqli t e . html
encontrará más información al respecto). Desde la herramienta puede ejecutar coman-
dos básicos como SELECT o INSERT,u otros más avanzados para tablas como CREATE
o ALTER. Esta herramienta resulta muy útil para operaciones básicas y de resolución de
problemas, y para volcar y abrir datos (. dump y . l oad, respectivamente). Como otras
herramientas SQL de línea de comandos, se necesita tiempo para familiarizarse con su
formato pero no hay mejor forma de crear copias de seguridad de los datos o abrirlos. (Si
necesita estas funciones, en la mayoría de los casos el desarrollo para móviles no exige
bases de datos de gran tamaño. Recuerde que esta herramienta sólo está disponible a
través de la consola de desarrollo y no para abrir datos en una aplicación real).
Ahora que sabemos cómo utilizar la compatibilidad de Android con SQLite, para
crear y acceder a tablas, almacenar datos o investigar bases de datos con las herra-
mientas proporcionadas por la consola, el siguiente paso consiste en crear y utilizar
Con t e n t Prov i de r , el último aspecto de procesamiento de datos en la plataforma.

Trabajar con clases ContentProvider


En Android se utiliza Con t e nt Pr ov ide r para compartir datos entre aplicaciones.
Ya hemos analizado el hecho de que cada aplicación se ejecuta en su propio proceso
(normalmente) y que los archivos y datos que almacena no son accesibles para otras
aplicaciones de forma predeterminada. También hemos explicado cómo conseguir que
preferencias y archivos estén disponibles entre aplicaciones gracias a los permisos correc-
tos y al contexto y ruta de cada aplicación. No obstante, es una solución limitada para
Android. Guía para desarrolladores lmI
aplicaciones relacionadas que ya conocen sus correspondientes detalles. Por el contrario,
con Con tentProvide r pued e publicar y mostrar un determinado tipo de datos qu e
otras aplicaciones utilicen para consultar, añadir, actualizar y eliminar, sin necesidad de
qu e conozcan con ant elación las rutas o recursos, o qu ién proporciona el contenido.
El ejemplo convencional de ContentProvi der en Android es una lista de contactos,
la lista de nombres, direcciones y teléfonos almacenada en el teléfono. Puede acceder
a estos datos desde cualquier aplicación si utiliza un URI concreto, content : / / con -
tacts/ people / , y un a serie de métodos proporcionados por las clases Activ i ty y
Conten tRes o l v e r para recuperar y almacenar datos. En un apartado posterior encon-
trará más información sobre Con te n tReso l ver. Otro concepto relacionado con datos
de Con ten tProvi der es Cu rs or, el mismo objeto que utilizamos anteriormente para
trabajar con conjuntos de resultados de bases de datos SQLite. Cu rsor también lo de-
vuelven los métodos de consulta que veremos en breve.

La devolución de un Cursor es una de las características de Conte nt Provider. La


exposición de Cursor a través de ContentP r ovide r es una abstracción un tanto
descuidada y resta coherencia al API, como veremos en breve. Cursor forma parte del
paquete a nd roi d. da t abase, lo que implica que trabajamos con registros de base de
datos y nos vincula a determinados conceptos de dichas bases al obtener resultados.
Pero el concepto que subyace a ContentProvider es su supuesta independencia de la
base de datos, lo que quier e decir que debería poder implementar Conten t Provider
sin utilizar una base de datos para obtener y almacen ar datos (la documentación actual
de Android es contradictoria; por un lado afirma que se puede no utilizar una base de
datos y por otro lo niega). En la actualidad, independientemente de méritos y deméritos,
tendrá que aprender a tr abajar con resultados basado s en Cursor y construcciones
SQLcuando utilice invocaciones ContentProvider.

En este apartado crearemos v a ri as a p li caci ones de ejemplo para ilustrar


Con te n t Provider desde todos los ángulos. Primero crearemos una aplicación bas a-
da en una única actividad, Provi derExp lore r , que utiliza la base de datos de contac-
tos incorporada para consultar, añadir, actualizar y eliminar datos. Tras ello, crearemos
otra aplicación que implementa su propio Co nte nt Provide r e incluye una actividad
de tipo explorador para manipular los datos. Además, analizaremos otros proveedores
incorporados de la plataforma.
La aplicación Pr ovi de rE xpl ore r tendrá una pantalla de gran tamaño en la qu e
podemos desplazarnos (véase la figura 5.5). Recuerde que nos centraremos en una ac-
tividad, para mostrar todas las operaciones Conte n tProvi der en el mismo punto, en
lugar de en aspectos estéticos o de facilidad de uso (la aplicación es intencionadamente
ho rrible, al menos esta vez).
Para comenzar, analizaremos la sintaxis de URI y las combinaciones y rutas uti-
lizadas para realiza r d ist intas operaciones con las clases Con tentProv ider y
Conte ntResol ver.
IIPI 5. Almacenar y recuperardatos

Figura 5.5. Aplicación ProviderExplorer que utiliza el ContentProvider de contactos.

Representaciones URI y manipulación de registros


Todo ContentProvider debe mostrar un CONTENT_ URI exclusivo que se utiliza
para identificar el tipo de contenido que procesa. Este URI se utiliza de do s formas para
consultar los datos, singular o plural (véase la tabla 5.1).
Tabla 5.1. Variaciones de URI de ContentProvider para distintas funciones.

content ://contacts/ Devuelve una lista (Li s t) de todas las personas del proveedor
people/ registrado para procesar content: / / contacts
content ://contacts/ Devuelve o manipula una única persona con ellO 1 del proveedor
people/l registrado para procesar content : / / contacts

El concepto de URI aparece independientemente de que consulte datos, los añada o


elimine, como veremos en breve. Para familiarizam os con el proceso, veremos los mé-
todos básicos de manipulación de datos CRUD y su relación con la base de datos de
contactos y sus respectivos URJ.
Android.Guía para desarrolladores lB
Analizaremos cada tarea para destacar los detalles: crear, leer, actualizar y eliminar.
Para hacerlo de forma concisa, crearemos una única actividad que se encarga de todas
estas acciones en el ejemplo Provide r Exp l ore r. En apartados posteriores analizare-
mos distintas partes de la actividad para centramos en cada tarea .
Lo primero será configurar la estructura del proveedor de contactos, en la primera parte
del siguiente listado, el inicio de la clase Pr oviderExplorer. (Véase el listado 5.12).
Listado 5.12. Inicio de la actividad para definir las clases internas necesarias.
public class Prov iderExp lorer ex tends Ac t ivi ty {

private EditText addName ;


private Ed i t Te x t addPhoneNumber ;
private Ed itTex t editName ;
pri vate Ed i tText edi t Ph o ne Numbe r ;
p r ivate Button a ddCo nta c t ;
p r i va t e Bu tt o n e di t Co n t act ;

pr iva t e l o n g co n tac t l d ;

priva t e c lass Co ntac t {


p ub lic lon g id ;
publ i c String name ;
p u b li c S t r i n g ph on eNumb e r ;

public Contact ( l o n g i d , String name , String phoneNumber)


thi s. id = id ;
t his . na me = name ;
t his. pho n eNu mbe r = p h o ne Numbe r ;

@Over r i d e
p ubli c S tr ing toStr i n g ()
r e t u r n th is . n ame + " \ n" + t his . pho n eNu mber ;

private clas s Conta ctBut ton extends Bu tton {


p ub li c Co ntact contact;

public Con tactButton (Co ntex t ctx, Contact co n tact )


super (ctx) ;
th i s . contact = contact ;

@Ove rr i de
publ i c voi d o nC rea te (Bu n d l e icic le )
super . onC r e ate (ici cl e ) ;
th is . s e t ContentVi ew (R . l a you t .p r o vider_e xpl o r e r);

this . addName = (Ed i tTe xt ) this . findVi ewByld {R . id . a dd_ na me ) ;


t his . a d d Ph o n e Nu mbe r =
(Ed i t Te x t) this .fi ndViewByld (R. id .add_phone_number ) ;
this . e d i t Name =
(Edi tTe xt ) t hi s . fi ndVi ew Byl d(R .id . e dit_na me);
this. e d i tPh oneN umber =
5. Almacenar y recuperar datos

(Edi t Te x t) this . f i nd v i ewñy Ld (R. id . edit_phone_number ) ;

t his . addContaet ~
(Bu tt o n) this . f i ndv i.e wís yLd (R. id . a dd_eontaet_button ) ;
this .addContae t .setOnCl iekListener (nel'lOnC l iek Listener ( )
pub li e vo i d o nCliek (fi na l v ew v ) (
í

ProviderExplorer.this.addCo ntaet ( );
}
)) ;
this. edi tContaet =
(Bu tto n) th is . f í.nd v.i. e wñy Ld (R. id . edit_eontae t_bu tton ) ;
this .editContaet .setOnCliekLis te ner (neI'lOnC liekLi stener ( )
publie void onClie k ( fin al Vi.ew v ) {
Prov iderExplorer .this.edi tContae t ( ) ;
}
)) ;

Para comenzar la actividad ProviderExplorer creamos una sencilla clase interna


para representar un registro Co nt ac t (no es una representación completa pero captura
los campos que nos interesan). Tras ello, incluimos otra clase interna para representar
ContactButton, clase que amplía Button e incluye una referencia a un determina-
do contacto. Después de establecer los botones Add y Edit, creamos implementaciones
OnClickLi stener anónimas que invocan los correspondientes métodos add y edi t
al hacer clic en un botón.
Con esto terminan las tareas de configuración de ProviderExplorer. A continua-
ción implementaremos el método onStart, que añade más botones dinámicamente
para completar y eliminar datos (véase el listado 5.13).
Listado 5.13. Parte onStart de la actividad ProviderExplorer.
@Override
p ubli e void onStart () {
super.onStart ( ) ;
List<Contaet> eontaets = t his. getContae ts () ;

Li near Layout . Layou tParams params =


neI'l LinearLayout.LayoutParams (200 ,
androi d .viel'l.VieI'lGroup . LayoutParams .WRAP_CONTENT ) ;

i f (e on t a ets ! = nu ll ) {
Li nearLayout e di tLayout ~
(Li ne a r La yo u t)
this .findVieI'lByld (R .id.edit_ buttons layout );
LinearLayoutdeleteLayout=
(Li ne a r La you t)
this. fi ndVieI'lByl d (R. id .de lete_but tons_layout ) ;
params .setMarg ins (1 0, O, O, O) ;

for (Co nt a e t e : eontaets ) {

ContaetButton eontaetEditButton =
neI'lContaetButton (t his, e ) ;
eontaetEditButton .setText (e.toStr ing ( ) ) ;
Android. Guía para desarrolladores lI'mI
editLayout .addView (eo ntaetEditButton , pa rams ) ;
eontaetEditButton.setOnCliekListener (newOnCliekLis tener ( )
publie vo id o nCliek (f in a l View v ) (
ContaetButton view = (Co n t a e t But t o n) v ;
editName .setText (v iew.eo ntaet .name) ;
edit PhoneNumber .setText (view .eontaet .phoneNumber );
eontaet ld = v e w, eo ntaet . id ;
í

}
)) ;

ContaetButtoneontaetDe le teButto n=
ne wContaetButton ( t h i s, e ) ;
contaet DeleteButto n . setTe xt (" De l e t e " + e. name ) ;
de le teLayo u t.addView (eontaetDe lete Button , params ) ;
eontaetDe leteButton .setOnCliek Listener (newOnC liekListener ( )
pub lie void o nCliek ( f i na l View v ) {
ContaetButto n view = (Co nt a e t Butto n ) v ;
eontaetl d = v e w, eon taet. i d ;
í

de leteContact ( ) ;
)
}) ;
}
} e lse {
Li ne a r La yo ut l a yo ut =
(Linea r La yo ut )
t h is .findVie wByld (R . i d.edit_b utto ns _ la yo ut ) ;
'I'ex t v i ew empty = ne w Te xtVi e \'¡ (t h i s ) ;
e mpty. setText (" No e u r r ent eon tae ts " ) ;
l a you t . a ddVi e w( emp t y , params ) ;

El método onSta r t () invoca el método ge tC on ta c t s que (véase el listado 5.14)


devuelve una lista de los objetos Contac t actuales de la base de datos de contados de
Android. Una vez obtenidos los contados, iteramos por los mismos y creamos dinámica-
mente un diseño en el código para edi t y del e te, respectivamente. Seguidamente, crea-
mos objetos de vista, incluido Con t ac t Bu t t on para completar un formulario de edición
y un botón para eliminar un contado. Tras ello, cada botón se añade manualmente a su
correspondiente LinearLayout al que hemos hecho referencia a través de R. j a va.
Una vez añadido el método o nSt a r t , tenemos una vista para mostrar todos los con-
tados actuales y todos los botones, estáticos y dinámicos, necesarios para añadir, editar
y eliminar datos de contados. Seguidamente, implementamos los métodos para realizar
estas acciones, para lo que utilizamos Cant en tRe s a l v e r y otras clases relacionadas.
Inicialmente tendremos que completar la pantalla de contados actuales para lo que
necesitamos consultar (leer) datos.

Consultar datos
La clase Activ i t y dispone de un método managedQuery que se utiliza para invo-
car clases Con te nt Prov i de r registradas. Al crear nuestro propio Conte nt Prov i d e r
en un apartado posterior, veremos cómo se registran proveedores en la plataforma; por
lmI 5. Almacenar y recuperar datos

el momento, nos centraremos en la invocación de métodos existentes. Cada proveedor


deb e publicar el CONTENT_ URI que admite. Para consultar el proveedor de contactos,
(véase el listado 5.14), necesitamos sab er este URI y obtener un Cursor mediante la in-
vocación de managedQue ry.

Listado 5.14. Detalles de consulta para ContentProvider en la actividad ProviderExplorer.

p r i v a t e Li s t <Co n t a c t >ge t Con t ac t s{)


List<Co ntact> resu l ts = null ;
long i d= OL;
S t r ing na me = nu l l ;
S t r ing phoneN umbe r = null ;
S tr i ng [ J p r ojection = new String [ )
( Con t ac ts. Peop le ._10 ,
Con t ac ts. People . NAME,
Co n t a c ts. People. NUMBER J;
Con t e n t Re s o l ve r r eso lver= t h is. getCo n t e n t Reso lver {) ;
Cursor cur = re s o lve r . q u ery (Con t a c t s. Pe op l e . CONTENT_ URI ,
pro j ec t ion , nul l, nu ll ,
Co nt ac ts . Peop le .O EFAULT_SO RT_OROER) ;
while (c u r . moveToNex t ()) {
i f ( r e s u l t s == n ull ) {
r e ~ult s = new Arra yL i s t <Co n t a ct > () ;
)
id = c u r , ge t Lon g (c u r . ge t Col umnlnd e x (Ba s e Column s. _ I D) ) ;
na me = cu r , getS t ri ng (c u r . getCo lumn lndex ( Pe o p l e Col umn s. NAME ) ) ;
p honeNumber =
cu r .getString(cur.getColumnln dex {PhonesColumns .NUMBER) );
results . add (ne w Contact (id , name , p honetiumbe rj j r

return r e s u l t s ¡

En realidad, la base de datos de contactos de Android está formada por varios tipos
de datos. Un contacto inclu ye detalles de una persona (nombre, empresa, fotografía, etc.),
uno o varios números de teléfono (cada uno con un número, tipo, etiqueta y demás) e
información adicional. Con te ntProvide r suele proporcionar todos los detalles del URI
y los tipos que admite como constantes en una clase. En el paquete android . provider
se incluye una clase Cont ac t s correspondiente al proveedor de contactos. Esta clase
cuenta con clases internas anidadas que representan People y Phones . A su vez, éstas
contienen clases internas con constantes que representan campos o columnas de datos
para cada tipo . Esta estructura de clases internas puede resultar complicada de asumir
pero simplemente recuerde que los datos Contac ts acaban en varias tablas y que los
datos que debe consultar y manipular provienen de las clases internas de cada tipo.
Las columnas que utilizaremos para establecer y obtener datos se definen en estas
clases . Únicamente trabajaremos con la parte correspondiente a individuos y teléfonos.
Primero creamos una proyección de las columnas que de volver como matriz String .
Tras ello, obtenemos una referencia a ContentResolver, que nos permite obtener un
objeto Cur so r . Este objeto representa las filas de los datos de vueltos, por los que itera-
mos para crear los objetos de los contactos.
Android. Guía para desarrolladores la

Para obtener una referencia Cursor también puede utilizar el método managedQuery
de la clase Acti vi ty. Un Cursor gestionado se limpia automáticamente al detener la
actividad y también se reinicia al iniciarla. En las instancias de Cursor la plataforma
mantiene su estado junto con el ciclo vital de Acti vi ty, muy útil en la mayoría de los
casos. Si simplemente necesita recuperar datos de una actividad, es mejor utilizar un
Cursor gestionado con Con ten tRe solve r . (En el último ejemplo no 10 utilizaremos,
ya que necesitamos algo más que recuperar datos y queremos centrarnos en los com-
ponentes provider/resolver).

El método query de la clase ContentResolver también le permite pasar argumentos


adicionales para limitar los resultados. En concreto, donde pasamos null, null, (véase
el listado 5.14), podemos pasar un filtro para limitar las filas que devolver en forma de
cláusula WHERE y objetos opcionales de sustitución para dicha cláusula (inyectados en
?). Es-un uso convencional de SQL, muy sencillo de utilizar. El inconveniente aparece
si no se utiliza una base de datos para ContentProvider. Aunque sea posible, tendrá
que procesar instrucciones SQL en la implementación del proveedor y necesita que todo
el que utilice el proveedor también 10 haga. Después de ver cómo consultar datos para
devolver resultados, aprenderemos a añadir nuevos datos: una fila.

Añadir datos
El listado 5.15 muestra el siguiente fragmento de la clase ProviderExplorer, el
método addContent. Se utiliza para añadir elementos de formulario a la actividad e
insertar una nueva fila de datos en las tablas relacionadas con contactos.
Listado 5.15. Detalles de inserción para ContentProvider en la actividad ProviderExplorer.
pr iva tevo ida ddContact ( ) {
Conten tReso lverreso lve r = t h is .getContentResolver ( ) ;
Co nte n tVa l uesva l ues= newCo ntentVa lues ( );

va l ues. p ut (Co ntacts. Peop le . NAME ,


th i s .addName . g etTe xt() .toStr i ng ( ) ) ;
Ur i personUri =
Co n t a c t s. Pe o p le . c r eat ePe r s onlnMyCon t a ct sG ro u p(
reso lver , val ues ) ;

v a lues . clea r ( ) ;
Ur i pho ne Ur i = Uri.withAppende d Pa t h (p er s o nUr i,
Co n tac ts . People.Phones .CONTENT_ DIR ECTORY ) ;
va lues . p ut (Co ntacts .Phones . TYPE , Phone s .TY PE_MOBILE ) ;
va lu es. put (Co ntacts . Phones .N UMBER,
t his. a ddPho neNumbe r . ge t Text ( ) . t o S t r i ng());

r e s ol v er.in s er t( p h o ne Ur i, va l ue s ) ;

th is . s t a r t Ac t ivi ty ( new l nte n t (t his , Pro viderExpl o r er . cl a s s));


lJmI 5. Almacenar y recuperar datos

Lo primero que vemos en el método a ddContact es la referencia Conte ntRe so l ver


y el uso de un objeto Conte ntVal ues para asignar nombres de columna a valores. Es
un tipo de objeto específico de Android. Una vez definidas las variables, utilizamos el
método de ayuda especial c r ea t e Pe r s on l nMyCont actsGr oup de la clase Contacts .
Pe ople para añadir un registro y devolver Uri. Este método utiliza Reso l ver y, entre
bastidores, realiza la inserción. La estructura de la clase Contacts dispone de diversos
métodos de ayuda (consulte la documentación) y que permiten reducir la cantidad de
código necesario para realizar determinadas tareas como añadir un contacto al grupo My
Contacts (el predeterminado que muestra el teléfono en la aplicación de contactos).
Tras crear un nuevo registro People, añadimos datos al Uri existente para crear
un registro de teléfono asociado a la misma persona. Es una característica del API. Por
lo general puede añadir o ampliar un Uri existente para acceder a distintos aspectos
de la estructura de datos. Una vez obtenido el Uri y después de establecer y actuali-
zar el objeto de valores, añadimos directamente un registro de teléfono, con el método
Cont e nt Reso l v e r insert (no el de ayuda).
Tras añadir los datos, veremos cómo actualizar datos ya existentes.

Actualizar datos
Para actualizar una fila de datos primero debe obtener una referencia de fila Cur s o r
y utilizar los métodos de actualización de Curs or (véase el listado 5.16).

Listado 5.16. Detalles de actualización para ContentProvider en la actividad ProviderExplorer.

pri va te void editContact () (


ContentResol ver resolver ~ this . getCon te ntReso lver ( ) ;
ContentValues va l ues = new ContentVa lues ( ) ;

Ur i personUri = Contacts . People. CONTENT_URI . b u i l dUp o n ()


. ap pe nd Pa t h( Lo ng . t oStr ing( t h i s . c o nt a c t I d)) . b ui l d ( ) ;

va lues .put (Contacts .People .NAME,


this .editNarne .getText ( ) . t oSt ri ng()) ;
resolver.update(personUri, values , nul l , nu l l );

values.clea r ( ) ;
Uri p ho neUr i = Ur i . w .i, thApp e nde d Pa th (p e r s on Ur i,
Contacts. People. Pho nes. CONTENT_DIREC TORY + " / 1 " ) ;
va lu es .put (Contacts.Phones .NUMBER,
t his .editPhoneNumber .getTex t () .to St r i ng ();
resolver . upda te (pho ne Uri , val ues , n ul l , null) ;

t his .startActivity (neH Inten t (this , Provi derExplorer . class ) );

Al actualizar datos, comenzamos con Pe ople . CONTENT_ URI y le añadimos una


ruta ID concreta por medio de UriBuilder. Es una clase de ayuda que utiliza el pa-
trón Builder para que -pueda construir y acceder a los componentes de un objeto Uri .
Seguidamente, actualizamos los valores e invocamos re solve r . updat e para realizar la
Android. Guia para desarrolladores lImI
actualización. Corno puede apreciar, el proceso de actualización con ContentResolver
es muy similar al de creación, con la excepción de que el método update le permite
pasar una cláusula WHERE y objetos de sustitución (estilo SQL).
En este ejemplo, después de actualizar el nombre de la persona, necesitarnos obtener
el Ur i correcto para actualizar también su registro de teléfono. Para ello añadimos da tos
de ruta Uri adicionales a un objeto que ya tenernos, que adjuntarnos al ID específico
deseado. En otras circunstancias, sería necesario determinar qué registro telefónico del
contacto hay que actualizar (en este caso hemos utilizado el ID 1 para resumir).
Aunque únicamente actualizarnos un registro en función de un URI concreto, recuer-
de que puede actualizar un grupo de registros si utiliza la forma no específica del URI y
la clausula WHERE. Por último, tendremos que implementar el método delete.

Eliminar datos
Para eliminar datos volveremos al objeto Con ten tRe solver utilizado para añadir
datos. En esta ocasión invocaremos el método delete, véase el listado 5.17.

Listado 5.17. Detalles de delete para ContentProvider en la actividad ProviderExplorer.

pri v a t e v o id d el e t e Con ta ct() {


Ur i p er sonUri = Con tacts. Pe o pl e . CONTENT_ URI ;
personUri=personUri . b ui ldUpon ( ) .
appendPath (Long .toString (contact ld ) ) .bui ld ( ) ;
getCo nt e ntResolve r ( ) . del e t e(pe r s onU ri, nu l l, nu l l);

s tartActiv ity (new l nte nt (t his , Prov iderExp lo rer .clas s ) ) ;

El concepto de eliminación es muy similar, una vez comprendido el resto del proceso.
Volvernos a utilizar el enfoque UriBuilder para configurar un Uri para un registro
concreto y después obtenernos una referencia ContentRe sol ver, en esta ocasión con
la invocación del método delete.

Al utilizar Conten tProvider, que por definición es accesible desde cualquier aplica-
ción del sistema, y realizar una consulta, únicamente se obtiene el estado actual de los
datos. Éstos podrían cambiar tras la invocación de modo que se preguntará cómo estar
al tanto. Para notificar el cambio de Cur s or , puede utilizarelAPI ContentObserver.
Admite una serie de retrollamadas que se invocan al cambiar los datos. Cursor dispone
de métodos regist er y unregister para objetos ContentObserver.

Tras analizar el funcionamiento del proveedor de contenidos, le aconsejarnos que


consulte el paquete android. provider en [avadocs, que enumera proveedores adi-
cionales. El siguiente paso será la creación de un ContentProvider.
e!I 5. Almacenar y recuperar datos

Crear ContentProvider
En este apartado crearemos un proveedor que se encargue de las responsabilidades
de datos de un objeto Widg et genérico, muy sencillo con un nombre, tipo, categoría y
miembros adicionales, e intencionadamente genérico para poder centramos en el cómo
y no en el por qué. (Los motivos para implementar un proveedor en la vida rea l son
muchos; para este ejemplo, nuestro tipo será el mítico Widge t ).
Para crear un Con te nt Provider, debe ampliar esta clase e implementar los méto-
dos abstractos necesarios, como veremos en breve. Antes, conviene definir una clase de
constantes de proveedor que defina CONTENT_ URI Y MIME_ TYPE para el proveedor.
Además, puede añadir los nombres de columna en una clase (o utilizar varias clases in-
ternas anidadas como en el sistema de contactos, aunque emplearemos un enfoque más
sencillo de entender).

Definir Content_Uri y M IM E_TYPE


El listado 5.18 muestra cómo hemos definido las constantes necesarias para nuestro
tipo Widget, como requisito previo a la ampliación de la clase ContentProvider para
un proveedor personalizado.

Listado 5.18. Constantes Widget Provid er, incluidas columnas y URI.

pub l i c f i n al c l ass Widget i mplement s Base Co l u mns (

p u bli c st a t ic fi n a l String MIME_DIR_ PREF IX =


"v nd . a n d r o i d. c u r s o r . d i r 't;
pub li c stat i c fina l S t ring MIME_ITEM_PREF IX =
"v nd. a nd r o i d . c u r s o r . i t e m";
pub l i c sta tic fi na l St r i n g MIME_ ITEM ~ " v n d. ms i. l<idget ";
publi c s t a tic fi na l S t r i n g MIME_TY PE_S INGLE =
MIME_ITEM_PREFIX + " 1" + MIME_ITEM ;
publi c sta t i c final Stri ng MIME_TY PE_ MULTIPLE =
MIME_D IR_PREFIX + " 1" + MI ME_ I TEM;

publi c sta tic final Stri ng AUTHORITY =


" c om.ms i. ma n n i n g . c h a p t e r 5. Wi d g e t";
publi c static final Str i ng PATH_SI NGLE = "\-lÍdg e t s / #" ;
pub lic static f i n a l Stri ng PATH_MULTI PLE ~ "w .í dqe t s "r
public static f inal Uri CONTENT_URI =
Uri. parse (" c o n t en t : 11" + AUTHORI TY + " 1" + PATH_MULTIPLE) ;

publi c sta ti c fina l Str ing DEFAULT_SORT _OR DER = " u p d a t e d DESC";

p u bl ic sta ti c fina l S t ri ng NAME = " n a me";


p u b l ic sta tic f i n a l S t r i ng TYPE = " t yp e";
publics tatic f i n alStrin gCATEGORY ~ "categor y " ;

pub li c sta t ic fina l St r i ng CREATED = " c r e a t e d" ;


publi c sta ti c fin a l St r i n g UPDATED = " u p d a t e d";
Android. Guín para desarrolladores B!III
En primer lugar ampliamos la clase Ba s e Co lumns de Android. De este modo nues-
tra clase dispone de constantes base como _ I D. Tras ello, definimos el prefijo MIME_
TYPE para un conjunto de varios y un único elemento. Se detalla en la documentación
de Android; la convención es que v nd . and r oid. cu r so r . di r representa varios ele-
mentos y vnd . a ndr o id . c u rs or . i tem rep resenta uno solo. Después, definimos un
elemento MIME concreto y lo combinamos con las rutas anteriores para crear dos re-
presentaciones MI ME_ TYPE.
Una vez abordados los detalles MIME, definimos la autoridad y la ruta para los ele-
mentos que se utilizarán en los invocadores CONTENT_ URI que pasaremos al proveedor.
El URI de múltiples elementos es, en última instancia, desde donde se inician los invo-
cadores y el que publicaremos (desde aquí pueden adjuntar elementos concretos).
A continuación definimos nombres de columna que representan los tipos de variable
del objeto Widge t , que también se usan en campos de la tabla de base de datos. Los in-
vocadores utilizan estas constantes para obtener y establecer campos concretos, lo qu e
nos lleva a la siguiente parte del proceso, la ampliación de Conte ntPr ovi de r.

Ampliar ContentProvider
El listado 5.19 muestra el inicio de la implementación de Cont e nt Pr ov ide r. En
esta sección de la clase realizamos tareas de mantenimiento relacionadas con la base de
datos que utilizaremos y el URI admitido.

Listado 5.19. Primera parte del ContentProvider WidgetProvider.

p u bl ic c l a ss Wi d g e t Pro v ide r e x t e n ds Co n t e n t Pro vi d er (

p r í v a t e s ta tic final St ring CLASSNAME =


Widget Provi der .cla s s .getSimpleName ( ) ;
pr ivat e s t a t ic f inal in t WIDGETS = 1 ;
private sta tic f inal i n t WI DGET = 2 ;
p ub l i c s t at ic fi na l Stri ng DB_ NAME = "w idget s _db " ;
p ub l i c s t a t i c f i nal String DB_ TABLE ~ " \1id g e t" ;
p u bli c stat i c fina l i nt DB_ VERSION= 1;

priva te s t a tic UriMa tche r URI _MATCHER = n u ll ;


p r i v a t e s t a t i c Ha shMa p <S t ri n g , Stri ng> PROJECTION_MAP;

priv a t e SQLiteDatabase db;

sta tic {
Wid ge tProv ider . URI _ MATCHER = new UriMat cher (Uri Ma t c h e r. NO_MATCH) ;
WidgetProvider . URI_MATC HER.addURI (Widge t .AUTHORI TY,
Widget.PATH_MULTI PLE , Widget Provider .WIDGETS ) ;
Widget Provide r . URI_MATCHER .addURI (Widget . AUTHORITY ,
Widget .PATH_S INGLE , WidgetProvider.WIDGET ) ;

WidgetProvider . PROJ ECTI ON_MA P = n e w Ha shMap <S t r i n g , String > ( ) ;


Wi d g e t Pr o v i d e r . PROJ ECTI ON_ MA P . p u t (Ba s e Co l u mn s . _ I D, " _ i d") ;
WidgetProvider . PROJECT IO N_MAP . p u t (Wi d g e t. NAME , " n a me ") ;
WidgetPr ov ider. PROJECTIO N_MAP . p u t (Wi d g et . TYPE , " t yp e") ;
5. Almacenar y recuperar datos

WidgetProvider .PROJECTION_MAP .put {Widget.CATEGORY , " c a t e g o r y ") ;


WidgetP rov ider . PROJECTION_MAP . p u t (Widg e t . CREATED , "created" ) ;
Widget P rovide r . PROJECTION_MA P . p ut (Wi d g e t . UPDATED, " u p d a t e d");

pr i v a te s tati c cl a s sDBOpe nHe lper ex t endsSQLi t e Op enHe lpe r (


pr i v ate sta t i c f ina l S t r i n g DB_ CREATE ~ " CREATE TABLE "
+Wi d ge tPr o v ide r . DB_ TABLE
+ " (_i d INTEGER PRl MARY KEY, n a me TEXT UNIQ UE NOT NULL , "
+ " t yp e TEXT, c a t e go ry TEXT , update d I NTEGER, c r e a t e d "
+ " I NTEGER) ; " ;

public DBOpenH elper (Co n t e x t context) (


s uper (c o n t e x t , Widget Provider . DB_NAME, n ul l,
Widg e t Pr o v i d e r .DB_ VERSI ON) ;

@Over r i d e
p ub l i c v oid o n Cre a te (SQLi t e Da t a b a s e db )
try (
d b .exe cSQL (DBOpen Helpe r .DB _ CREATE ) ;
) ca tch ( SQLEx c e p t i o n e ) {
/ / r egi s t r a r o p r o ce sar

@Over r ide
publi c void onOpe n (SQLite Databa s e db )
}

@Override
pub l i c vo i d onUpg rade (SQLi t e Da t a b a s e db , in t ol dVersio n ,
int newve r s on ) (
í

db. e xe c SQL ( " DROP TABLE I F EXI STS "


+ Widg et Pr o vider .D B_T ABLE ) ;
this .o nC rea te {db ) ;

@Override
p u b l icbooleanonCreate ( ) {
DBOpenHelper dbHe lper = n ew DBOp e nHelp e r (t h i s . ge tContext () ) ;
t his . db = dbHelper . getWri t a bleDatabase () ;

i f ( t h i s . d b == n u ll )
re t u r n f a l se ;
) e lse {
ret urn t r ue ;

@Override
p u blic S tring ge tType (Uri uri ) {
s Hi tc h (Wi d g e t P r o v i d e r . URI _ MATCHER . ma t c h (u r i))
c ase WI DGETS :
ret ur n Wi dget . MI ME_TYPE_MULT I P LE ;
Android. Guía para desarrolladores BiD
case WIDGET:
re turn WidgeLMIME_TYPE_S IN GLE;
default :
t h row n ew I l legalArgumentException (" Unk n o ¡.¡n URI " + uri ) ;

Nuestro proveedor amplía ContentProvider, que define los métodos que imple-
mentar. Tras ello utilizamos varias constantes de base de datos para definir el nombre
y la tabla que utilizar. Incluimos Ur i Ma tche r , que utilizaremos para comparar tipos,
y un mapa de proyección para los nombres de campos.
Incluimos una referencia a un objeto SQLi teDa tabase, en el que almacenaremos y
recuperaremos los datos procesados por el proveedor. Esta base de datos se crea, abre
y actualiza con SQLiteHel pe r en una clase interna. Ya hemos utilizado este modelo
antes, cuando trabajamos directamente con la base de datos (véase el listado 5.14). En el
método onCre a te de nuestro proveedor se utiliza el modelo de ayuda para definir la
referencia a dicha base de datos.
Tras la configuración, llegamos al primer método que implementar, getT yp e . El
proveedor lo utiliza para resolver los Uri pasados y determinar si se admite y, en caso
afirmativo, qué tipo de datos solicita la invocación actual (un elemento concreto o el
conjunto completo). Devolvemos la cadena MIME_Typ e en función de las constantes
definidas en la clase Widg et. A continuación veremos los métodos que implementar
para satisfacer las exigencias de Conte n t Provi der. (Véase el listado 5.20). Se corres-
ponden a actividades CRUD utilizadas con el proveedor de contactos en la sección an-
terior: qu er y, ins ert, upd a t e y dele te.
Listado 5.20. Segunda parte de del ContentProvider WidgetProvider.
@Overri de
p ubl ic Cursor que ry (Ur i ur i , S tr ing [ ] p roject ion ,
String select ion , String l l select io nA rgs ,
St r ing so rtO rder ) {
SQLi teQuer yBuilde r q u e r yB u i l d er = new SQLi teQue ryBui lder ( ) ;
S tr ing orde rBy = null ;

sw í, tch (Wi dge tP r o v i d e r. URI _ MATCHER . ma tch (u r i )) {


case WIDGETS :
queryBu i lde r . se t Ta b les (Widge t Provi d e r .D B_ TABLE );
q ueryB uilder.se tP ro ject ionMa p (WidgetProv i der .PROJECTION_MAP) ;
brea k ;
case WI DGET :
queryBui lde r .setTab les (Widget Provider. DB_ TABLE) ;
q ueryBui lder.appe ndWhere ( "_id= "
+ u r i . g e t Pat h Se gme n t s( ) . g e t ( l)) ;
b reak ;
de fa u lt:
t h r ow n e w I llegalArgumentException (" Un kn o ¡.¡n URI " + uri ) ;

if (T e x tUt il s . i s Emp t y (s o r t Or d e r )) {
orderB y = Widget . DEFAULT_ SORT_ ORDER;
} else {
5. Almacenar y recuperar datos

o rde r By = so rtOrder ;

Cur so r e = queryBuil der. q ue r y ( thi s . db, projection ,


selec tio n , se lectionArgs , n ull , n u Ll, ,
o rderBy ) ;
c.setNotifica tionUri (
t his . getCo n t ex t() . g e t Co n t e n t Re s o l v e r ( ) , ur i ) ;
re t urn e ;

@Override
pub l i c Ur i insert (Ur i ur i , Co n te n t Val ue s initial Va lue s)
l o n g r owld = OL;
Co nte ntVa l u es va l ues = n ull ;

i f ( in iti al Va l ue s ! = null ) {
values = new Conten t Value s (initi alValu e s ) ;
) e l se {
va lues = n ew Conte ntValues () ;

if (Wid g et Pr o vi d e r . URI _ MATCHER . ma t c h (u r i) ! =


Widge tPro vider . WID GETS ) (
throw new I llegalArgument Exception ("Unknown URI " + uri ) ;

Long n ow = System . cu r r en tT i meMill is () ;

. . . s e omi ten l o s va lo res p redete rm i na dos

rowld = th is. db . i nser t (Wid g e t Pr o v i de r . DB_ TABLE, " ~Iidg e t_h a c k " ,

v a lue s ) ;

i f (ro wl d > O) {
Uri re su l t = Conte ntUris . wí.t hAp pe n d e d ld (Widg e t . CONTENT_ URI ,
r-owL d ) ;
th is . getCo ntext ( ) . g e t Co n t e n t Re s o l v e r() . no t i f yCh a n g e ( r e s u l t ,
null ) ;
r e tu r n result ;

th r o w ne w SQLEx cepti on (" Fa i l e d t o insert r o w in t o " + uri) ;

@Ove rr ide
p ub li c in t updat e (Ur i u r i , Co n te n tVa l u e s value s , Stri n g s e l ect io n ,
S t ri n g [ 1 s e l ectio nArgs ) {
int cou n t = O;
s w t c h (Wi d g e t Pr o v i d e r . URI _ MATCHER . ma t c h (u r i) ) (
í

case WIDGETS :
cou n t = t his . d b. update (Widge tPro vider . DB_ TABLE , value s ,
s e l ect ion , se lec tio nArgs ) ;
brea k ;
ca se WIDGET :
S t ring segment = ur i . g et Pa t h Se g ments () . g e t (1) ;
String whe re = ti,,;
Android. Guía para desarrolladores lImI
if ( ! Te x t Ut il s. i s Emp t y( s el e c t i o n) )
where = 11 AND (" + se lection + ") ";
}
c ount = thi s. db. update (Wi d g e t Pr o v i d er. DB_ TABLE, v a l ues ,
"_id=" + segment + whe r e , s e lec t i o n Ar g s ) ;
bre ak ;
d e f aul t:
t hrow new Ill e g al Ar gum en t Ex c e p ti on (" Unk nOlm URI " + uri ) ;
)
t hi s. g e t Con t e x t() . g e t Co nt e n t Re s olv e r() .noti f yCha nge (uri , null);
r e t u r n cou n t ;

@Over r ide
pub li c i nt de lete (
Ur i uri , St ri ng se lec ti o n, St r i ng l l se lect ionA rg s )
int coun t ;

s wi t c h (Widg etP r o vi d e r.URI_MATCHER.match(uri)) (


c a s e WIDGETS :
c oun t = this . db . d elet e (Wi d g e tPro vide r. DB_T ABLE, se l ec t ion ,
se l ect i o nAr g s ) ;
break ;
case WIDGET:
Str ing se g ment = ur i .getPa t h Se gment s () . g et (1);
String whe re = 1111 ;
i f ( ! Te xtUtil s .i sEmpt y( s el e c ti on))
wher e = ti AND (" + se l e ction + ") n ;
}
count = t h i s .db . de lete (W i d getP r o vide r. DB_T ABLE,
"_id=" + segment + whe r e , s e l e c t i o n Ar g s ) ;
break;
de f ault:
throw ne w 1 lle g alArgum entE x c ept i on ("U n k no wn URI " + u r i ) ;
)
thi s .getCon t e x t() . g e t Content Re s o l v er() . n otifyChange( uri, null);
re turn c o u n t;

En la última parte de la clase WidgetProvider vemos la implementación de los


métodos ContentProvider. Son los mismos métodos pero un proveedor diferente
que invocamos en el ejemplo ProviderExplorer anterior.
Primero utilizamos SQLQueryBuilder dentro del método de consulta para añadir
el mapa de proyección y las cláusulas SQL, junto al URI correcto en función de la com-
paración, antes de realizar la consulta y obtener el Cursor que devolver.
Al final del método de consulta utilizamos el método setNotificationUri para
establecer el Uri devuelto en el que observar los cambios. Es un mecanismo basado en
eventos que se puede utilizar para realizar el seguimiento de los cambios de los elemen-
tos de datos en Cursor, independientemente de cómo se produzcan.
Seguidamente vemos el método insert donde se valida el objeto ContentValues
y se completa con valores predeterminados si no está presente. Una vez listos los valo-
res, invocamos el método insert de la base de datos. Tras ello, se utiliza otro sistema
&mi 5. Almacenar y recuperar datos

de notificación, en esta ocasión para ContentRes olve r. Como hemos realizado un


cambio de datos, informamos de lo sucedido a Conte ntRe s olver para actualizar los
escuchadores registrados.
Tras completar el método in sert, llegamos a los métodos de actualización y elimi-
nación. Repiten muchos de los conceptos ya empleados. Primero comparan el Uri pa-
sado en un único elemento o en el conjunto, para después invocar los correspondientes
métodos de actualización y eliminación. Como antes, al final de estos métodos notifica-
mos a los escuchadores de los cambios en los datos.
Para completar la clase implementamos los métodos de proveedor necesarios. Este
proveedor, que ahora tiene el tipo de datos Widget, se puede utilizar desde cualquier
aplicación para consultar, añadir, actualizar o eliminar datos, una vez registrado como
proveedor en la plataforma, como veremos a continuación.

Manifiestos de proveedor

Para que la plataforma sepa qué contenido ofrecen los proveedores y qué tipo de datos
representan, deben definirse en un archivo de manifiesto de aplicación e instalarse en la
plataforma. El listado 5.21 muestra el manifiesto de nuestro proveedor.

Listado 5.21. Archivo AndroidManifest.xml de WidgetProvider.

<?xmlversion= ll1 .0 1'encoding ="utf-81 1?>


<ma n i f e s t xml n s : a n d r o i d = " h t t p :// s c h e ma s . a n d r oi d .co m/ a p k / r e s/ a nd r o i d "
package= " com. ms i. manning .chapterS . widget " >
<a p p l ica t i o n a n d r o i d : i c o n = " @d r a wa b l e/ i c o n "
andro id : labe l = "@str ing /app_ short_name " >
<a c ti vi t y a n dro i d : n ame = " .Wi d g e t Ex p l o r e r "
android : label= "@s tring/app_name " >
< i n t e n t - f i l t e r>
<a c t i o n android: name= "a ndroid . i n t e n t . a ction . MA I N" />
<c a t e g o r y a n d ro i d : n a me = " a n d r oi d . i n t e n t. c a t e go r y. LAUNCHER" />
< / in t en t - f il t e r>
</ activ i t y>

<p r ovi d e r a ndr oid : name = "WidgetProvider"


andro id:au thorit i es=
" c om.ms i. ma n ni ng. c h a p t e r S . Wid g e t" / >
</ a p p l i c a t i o n>
< / mani fest>

La parte más importante del manifiesto relacionada con proveedores de contenidos


es el elemento <p r ov i de r> . Se utiliza para definir la clase que implementa el provee-
dor y para asociar una determinada autoridad a dicha clase.
Un proyecto completo capaz de añadir, recuperar, actualizar y eliminar registros
nos ha permitido concluir el análisis del uso y creación de clases Con t e n t Pr ovi de r.
y con ello hemos demostrado muchas de las formas de almacenar y recuperar datos en
la plataforma Android.
A ndroid. Guía para desarrolladores Ba
.'
propiedades adicionales de manifiestos ContentProvider -" .
Las propiedades de ContentP rov i de r, que se pueden configurar en el manifiesto,
permiten configurar distintos parámetros adicionales como permisos concretos, orden
de inicialización, multiprocesamiento, etc. Mientras que la mayoría de implementa-
ciones Conten tProv ide r no requieren estos detalles , es aconsejable conocerlos. Si
necesita más información al respecto, consulte la página de documentación de Android
h t t p: / / c ode. goog l e. c om/ a nd r oi d / r e f e r e nc e / an d r oi d / R. s t yl e abl e.
html - Andr oidManifestProvider .

Resumen
Desde un sencillo mecanismo Share dP re fere nces que guarda da tos en archivos
hasta el almacenamiento de archi vos, bases de datos y el concepto de Con tentProvi der,
Android ofrece una amp lia gama de formas para que las aplicaciones puedan recuperar
y almacenar datos.
Como hemos visto en este capítulo, algunos de los medios presentados se pueden
utilizar entre aplicaciones y procesos, y otros no . SharedPre f e rences se puede crear
con un modo de permisos, para que sea privado, o se puede compartir globalmente con
permisos de lectura y escritura, o de sólo lectura.
Las preferencias se almacenan en sencillos archivos XML en una ruta específica del
dispositivo, como otros recursos que puede crear y leer personalmente. El sistema de
archivos, es muy útil para controlar determinados niveles del estado local de la aplica-
ción y mantener datos pero no para objetivos más amplios.
Tras el acceso al sistema de archi vos, el siguiente ni vel de almacenamiento que ofre-
ce Android es un sistema de bases de datos relacionales basado en SQLite. Es ligero,
rápido y muy completo pero como hemos visto, únicamente permite mantener datos
locales en una misma aplicación. Además de almacenar y recupera r datos locales, puede
utilizar una base de datos pero tendrá que mostrar una interfaz a través de un servicio
o Conte nt Provider. Los proveedores, analizados en este capítulo, muestran tipos de
datos y operaciones a tra vés de un enfoque ba sado en URI.
También hemos examinado las distintas rutas de datos disponibles para una aplic ación
Android. H emos utilizado aplicaciones concretas con p referencias y el sistem a de archi-
vos, y hemos ahondado en la aplicación WeatherReporter de un cap ítulo anterior, apli-
cación que utiliza una base de datos SQLite para acceder a los datos y conservarlos.
Al ampliar los horizontes de Android más allá de los datos y los conceptos básicos
presentados en capítulos anteriores, como vistas, I nt e nt y ser vicios, en el siguiente ca-
pítulo pasaremos al uso de redes. Analizaremos los fundamentos y las API de red que
proporciona Android, además de ampliar los conceptos de datos para incluir las redes
como fuente de datos.
• •
s ser lel s
We
Todo operador móvil admite redes de voz y datos de varios tipos . La parte más in-
teresante de los dispositivos de Android es la red de datos, junto con la posibilidad de
vincular los datos de una red a distintas aplicaciones. Estas aplicaciones se pueden ge-
nerar con el enfoque basado en Intent y Service descrito en capítulos anteriores.
Dicho enfoque combina intent incorporados (o personalizados) como navegación Web,
con acceso a componentes de hardware como un subsistema gráfico 3D, un receptor
GPS, una cámara, almacenamiento extraíble, etc. Esta combinación de plataforma abier-
ta, opciones de hardware, arquitectura de software y acceso a redes de datos es lo que
hace que Android resulte tan atractivo.
Con esto no afirmamos que la red de voz no sea importante (como veremos en un
capítulo posterior) pero al hablar de redes nos centraremos en los datos.
En términos de la red de datos, Android proporciona acceso de distintas formas:
redes móviles IP (Protocolo de Internet), Wi-Fi y Bluetooth. En este caso nos centrare-
mos en comunicar aplicaciones de Android con redes IP, a través de distintos enfoques.
Analizaremos los fundamentos de las redes y después pasaremos a los aspectos concretos
de Android al analizar la comunicación con protocolos de nivel superior como HTTP.
Android ofrece parte de los paquetes java. net y org. apache. httpclient para
admitir redes básicas. Otros paquetes relacionados como android. net se encargan de
detalles de red internos y propiedades de conectividad general. Veremos todos estos
paquetes en los distintos ejemplos sobre redes del capítulo.
En términos de propiedades de conectividad, utilizaremos la clase
Connectivi tyManager para determinar cuándo está activa la conexión de red y de
qué tipo es (móvil o Wi-Fi). A partir de aquí, utilizaremos la red de distintas formas con
las aplicaciones de ejemplo.
lJl!I 6. Redes y servicios Web

En este capítulo sobre redes no nos adentraremos en los detalles relacionados con las
API Bluetooth o Wi-Fi de Android. Bluetooth es una tecnología importante para redes
inalámbricas entre dispositivos pero las API relacionadas con Android no están toda-
vía completas (incluso en el SDK 1.0). Los dispositivos Android admiten Bluetooth pero
de forma limitada y no está disponible en el emulador. Wi-Fi, por su parte, no cuenta
con el correspondiente API y tampoco con una capa de emulación. Como el emulador
no distingue el tipo de red utilizada y desconoce todo lo relacionado con Bluetooth y
Wi-Fi, y como creemos que la importancia radica en cómo se utiliza la red, no analiza-
remos estas API. Si necesita más información al respecto, consulte la documentación de
Android(http:// c ode.googl e. c om/androi d/re fe r enc e/ android/net/wi f i/
package- surnmar y. h tml).
En la aplicación de este capítulo, NetworkExplorer, veremos formas de comunicarnos
con la red en Android e incluiremos diversas utilidades. En última instancia, esta aplica-
ción tendrá varias pantallas para diferentes técnicas de red, véase la figura 6.1.

Figura 6.1. La aplicación NetworkExplorer que crearemos para analizar las redes.
Android. Guía para desarrolladores &11I
Tras analizar la red IP general y su relación con Android, veremos cómo convertir
el servidor en un API más robusto por medio de servicios Web. Trabajaremos con XML
sobre HTTP (POX) y REST (Transferencia de Estado de Representación). Además, des-
cribiremos el protocolo SOAP (Protocolo de Acceso Sencillo a Objetos). Analizaremos
las ventajas e inconvenientes de los distintos enfoques y las razones para elegir uno u
otro para un cliente Android.
Antes de adentrarnos en los detalles de aplicaciones Android en red, repasaremos
los conceptos básicos. Si ya dispone de sólidos conocimientos sobre redes puede pasar
al siguiente apartado pero es importante contar con esta base para el futuro.

Redes
Un grupo de equipos interconectados es una red. Con el tiempo, las redes han pasado
de ser algo disponible únicamente para gobiernos y grandes empresas a convertirse en
la sorprendente Internet. Aunque el concepto es sencillo, permitir la comunicación entre
ordenadores, las redes implican cierta tecnología avanzada. No abordaremos todos los
detalles pero sí los conceptos básicos de las redes generales.

Fundamentos sobre redes


En la mayoría de los casos, las API utilizadas para programar aplicaciones Android
abstraen los detalles de red subyacentes. Lo que es positivo. Las API y los protocolos de
red se han diseñado para que nos centremos en las aplicaciones y no nos preocupemos
por enrutadores y entrega de paquetes.
No obstante, conviene conocer el funcionamiento de una red para mejorar el dise-
ño y la resolución de errores de nuestras aplicaciones. Para ello, analizaremos algunos
de los conceptos generales de redes, con parada en TCP /IP (Protocolo de Control de
Transmisiones/Protocolo de Internet). Comenzaremos con nodos, capas y protocolos.

Nodos
El concepto básico de una red es que los datos se envíen entre dispositivos conecta-
dos con determinadas direcciones. Las conexiones se pueden realizar a través de cables,
ondas de radio, etc. Cada dispositivo con dirección se denomina nodo. Un nodo puede
ser una centralita, un PC o cualquier otro dispositivo con una pila de red y conectividad,
como un dispositivo manual de Android.

Capas y protocolos
Los protocolos son un conjunto de reglas de comunicación predefinidas y acordadas.
Suelen situarse unos sobre otros ya que asumen distintos niveles de responsabilidad.
Por ejemplo, en la pila TCP /IP, utilizada en la mayoría de tráfico Web de todo tipo y
con Android, las capas principales son las siguientes:
lIfI 6. Redes y servicios Web

• La capa de enlace, que incluye protocolos de resolución de direcciones de dispo-


sitivos físicos como ARP, RARP Yotros.
• La capa de Internet, que incluye el propio IP, con varias versiones, y los protocolos
ping, ICMP, entre otros.
• La capa de transporte, donde encontramos distintos protocolos de entrega como
TCPyUDP.
• La capa de aplicaciones, que incluye protocolos conocidos como HTTP, FTP,
SMTP, IMAP, POP, DNS, SSH YSOAP.
Las capas son una abstracción de los distintos niveles de una pila de protocolos de red.
El nivel más bajo, la capa de enlace, se preocupa de dispositivos y direcciones físicos. El si-
guiente nivel, la capa de Internet, se preocupa de direcciones y detalles de datos generales.
Tras ésta, la capa de transporte se encarga de los detalles de entrega. Por último, los proto-
colos de capa de aplicaciones de nivel superior utilizan la capa situada debajo y son espe-
cíficos de cada aplicación para enviar archivos y correo electrónico o ver páginas Web.

IP
IP se encarga del sistema de direcciones y de la entrada de datos en pequeños frag-
mentos denominados paquetes. Los paquetes, conocidos en términos IP como datagra-
mas, definen la cantidad de datos de cada fragmento, los límites de carga e información
de encabezados, etc. Las direcciones IP indican de dónde proviene cada paquete (su
origen) ya dónde se dirige (su destino).
Las direcciones IP tienen distintos tamaños en función de la versión del protocolo uti-
lizado pero sin duda el más habitual actualmente es el de 32 bits . Las direcciones IP de
32 bits (IPv4) se suelen escribir en una notación decimal que separa los 32 bits en cuatro
secciones, cada una para representar 8 bits (octeto) como por ejemplo 74.125.45.100.
Determinadas clases de direcciones IP tienen un papel y un significado especiales.
Por ejemplo, 127 siempre identifica una dirección local del equipo; esta clase no se co-
munica con otros dispositivos (solamente se puede utilizar dentro de un equipo). Las
direcciones que empiezan por 10 ó 192 se pueden comunicar con otros dispositivos del
mismo segmento de red local pero no con otros segmentos. Todas las direcciones de un
segmento de red concreto deben ser exclusivas para evitar conflictos.
El enrutamiento de paquetes en una red IP, cómo recorren la red y pasan de un seg-
mento a otro, se realiza en enrutadores. Los enrutadores se comunican entre sí por medio
de direcciones IP y otra información relacionada con IP.

rcr y UDP
TCP Y UDP son distintos tipos de protocolos de entrega que se suelen utilizar con
TCP /IP. TCP es fiable y UDP es más directo. Esto quiere decir que TCP incluye datos
adicionales para garantizar el orden de los paquetes y para enviar un acuse de recibo
(como sucede con el correo certificado tradicional). UDP, por su parte, no proporciona
orden ni acuse de recibo (es como una carta normal, más barata y rápida de enviar pero
no sabemos si el destinatario la recibirá).
Android. Guía para desarrolladores lIfJII

Protocolos de aplicaciones
Después de enviar y entregar un paquete, la aplicación toma el control. Para enviar
un mensaje de correo, por ejemplo, SMTP define un riguroso conjunto de procedimien-
tos. Es necesario saludar y presentarse de una forma concreta; después proporcionar
información de origen y destino, seguida por un mensaje en un formato concreto. Del
mismo modo, HTTP define el conjunto de reglas para Internet, qué métodos se permi-
ten (GET, POST, PUT, DELETE) y el funcionamiento general del sistema de solicitudes y
respuestas entre cliente y servidor.
Al trabajar con Android y API relacionadas con Java en general, no es necesario aden-
trarse en los detalles de nivel inferior pero sí conocer sus principales diferencias para la
resolución de problemas, así como tener conocimientos sobre direcciones IP. Además,
es aconsejable conocer el funcionamiento de clientes y servidores, y cómo se establecen
las conexiones a través de puertos.

Clientes y servidores
Todo el que haya utilizado un navegador Web está familiarizado con el modelo infor-
mático de cliente/servidor. Los datos, en cualquier formato, se almacenan en un potente
servidor centralizado. Los clientes se conectan a dicho servidor a través de un protocolo
concreto (como HTTP) para recuperar los datos y utilizarlos.
Este patrón es evidentemente mucho más antiguo que la Web y se ha aplicado prác-
ticamente en todo desde terminales conectadas a sistemas centrales hasta modernas
aplicaciones de escritorio que se conectan a un servidor para realizar parte de sus ope-
raciones (como iTunes, básicamente un organizador y reproductor multimedia pero que
también tiene una tienda donde los clientes se conectan a un servidor para conseguir
nuevos contenidos). En cualquier caso, el concepto es el mismo: el cliente realiza una
solicitud al servidor y éste responde. Es el mismo modelo empleado por la mayoría de
aplicaciones Android, al menos de las que usan un servidor (por lo general, las aplica-
ciones Android suelen acabar como cliente).
Para procesar varias solicitudes cliente, para distintos propósitos, que una dirección
IP reciba simultáneamente, los sistemas operativos de servidor modernos recurren al
concepto de puertos. Los puertos no son físicos; son una representación de una deter-
minada zona de memoria del ordenador. Un servidor puede escuchar en varios puertos
designados en una misma dirección; por ejemplo, un puerto para enviar correo electró-
nico, otro para el tráfico Web, dos para la transferencia de archivos, etc. Todo equipo
con una dirección IP también admite miles de puertos para habilitar múltiples conver-
saciones simultáneas. Los puertos se dividen en tres categorías:

• Puertos conocidos: Del Dal1D23.


• Puertos registrados: Del1D24 al 49151.
• Puertos dinámicos y/o privados: Del 49152 al 65535.
lID 6. Redes y servicios Web

Los puertos conocidos se publican y son simplemente eso, conocidos. HTIP es el puer-
to 80 (y HTTPS, el puerto 443), FTP utiliza los puertos 20 (control) y 21 (datos), SSH es el
puerto 22, SMTP es el puerto 25, etc. Tras éstos, los puertos registrados siguen estan do
controlados y publicados pero para funciones más concretas. Suelen utilizarse para una
determinada aplicación o emp resa; por ejemplo, MySQL es el puerto 3306 (de forma pre-
determinada). Si necesita más información al respecto, consulte el documento de números
de puerto ICANN (h t t p : / / www. iana . org /assignments /port -numbe rs ).
Los puertos dinámicos o privados no están regi strados intencionadamente ya que los
utiliza la pila TCP /IP para facilitar las comunicaciones. Se registran dinámicamente en
cada ordenador y se utilizan en la conversación. El puerto dinámico 49500, por ejemplo,
se puede utilizar para en via r una solicitud a un servidor Web y procesar la resp uesta
po sterior. Una vez terminada la conversación, el puerto se reclama y se puede reutilizar
localmente para otra transferencia de datos.
Así pues, clientes y servidores se comunican como los nodos con direcciones, por
medio de puertos en una red que admite varios protocolos. Los protocolos implicados
en Android se basan en la red IP en la que participa la plataforma. Antes de crear una
ap licación Android cliente/servidor completa con la red, es necesario determinar el es-
tado de la conexión.

Comprobar el estado de la red


Android ofrece multitud de utilidades para determinar la configuración de los dispo-
sitivos y el estado de di versos servicios, incluida la red. No rmalmente se utiliza la clase
Conne c t i v i tyManage r para determinar si ha y conectividad de red y para sab er cam-
bios en la misma. El listado 6.1 muestra parte de la actividad principal de la aplicación
NetworkExplore r, para ilustrar el uso básico de Conne c t ivi tyManager.

Listado 6.1. Método on8tate de la actividad principal de NetworkExplorer.

@Override
public void onStart ()
s upe r .onStart ( ) ;

Con nec t iv i tyManager cMgr = (Co n ne cti v i t yMa n a g e r)


this . get Sy s temService (Co ntext .CONNECTIV I TY_S ERVIC E );
Ne tworkl n f o n e t l n f o = c Mg r . g e tA ctiv eNetwor kl nfo() ;
t h is.s t atus .setText (netlnfo.toSt r ing ( ) ) ;

Este breve ejemplo demuestra que se puede acceder a Conne ctivi tyMa nager a tra-
vés del método ge tS ys temSe rvice del contexto si pasamos la constante CONNECTI VY
SERVI CE. Una vez conseguido, podemos obtener información de red a través dd
objeto Netw or klnfo. La figura 6.2 muestra el resultado de vuelto por el método toS -
tr i ng de este objeto . Evidentemente, no es habitual mostrar el resultado de St ring
de Netwo rk lnfo pero le permite saber rápidamente lo que está disponible. Es más
Android. Guía para desarrolladores lID
frecu ente utilizar los métodos i s Ava i l a b l e o isConnected (que devuelven un valor
bo olean) o consultar di rectamente Network lnfo . Sta t e po r medio del método get -
State. Netw orklnfo . State es una en umque define el estado de la conexión y sus
posibles valores son CONNECTED, CONNECTING, DISCONNECTED y DISCONNECTI NG.
El objeto Network ln fo también permite acceder a información más detallada pero por
lo general basta con la información básica (a menos que sea un caso especial como la
creación de una aplicación de administración del estado de la red).

Figura 6.2. Resultado del método toString de Networklnfo.

Una vez que sabemos que estamos conectados, ya sea a través de móvil o Wi-Fi, po-
demos usa r la red IP. Para nuestra aplicación NetworkExplorer, comenzaremos con la
conexión IP más rudimentaria, un socket, hasta llegar a HTTP y los servicios Web.

Comunicarse con un socket de servidor


Un socket de servidor es un flujo en el que puede leer o escribir bytes sin procesar,
en una dirección IP y puerto conc retos. De este modo puede centrarse en los dato s sin
preocuparse de tipos de medios, tamaños de paquetes, etc. Es otra abstracción d e red
qu e pretende facilitar la labor del programador. La filosofía de los socket de que todo
debe parecer E/S de archi vos pa ra el programador, proviene de la familia de estándares
POSIX, que han adoptado la ma yoría de sistemas operativos actuales.
Antes de pasar a los niv eles superiores de la comunicación por red, comenzaremos
con un socket sin procesar. Para ello necesitamos un servidor que escuche en un puerto
concreto. El código Ech oSe r v e r (véase el listado 6.2) se encarga de este aspecto. No es
una clase específica de Android, sino un servidor simplificado que se puede ejecutar en
cualquier equipo anfitrión con Java . Posteriormente nos conecta remos al mismo desde
un cliente Android.
Listado 6.2. Servidor echo para ilustrar el uso de socket.
public f inal class Ec h o Se r v e r e x t end s Thread (

priva te sta t i c fina l int PORT = 8889 ;

p r ivate Ec h o Se r v e r () {)

public sta tic void mai n (Stri ng args l l ) {


6. Redes y servicios Web

EchoServer echoServer = new EchoServer () ;


i f (e cho Serv e r ! = nu ll) {
echoServer .start() ;

public voi d run ()


try {
Serve rSoc ke t server = new ServerSocket (PORT, 1) ;

whi le ( t ru e) {
Socket c lient = server . accept () ;
System.out .p ri nt l n ( " Cl ien tconnected " ) ;

wh ile ( t r u e) {

Bu f f e r edRe a d e r reade r =
new Bu f f e r e d Re ade r (n e w I npu t S t r e a mRe a d er (
c l ie n t.ge t l nputStream ( » ) ;
System. o u t . p r i n t l n (" Re a d f rom c lie nt " ) ;
Str i ng tex t Li ne = r e a d e r . readLi ne () + " \ n" ;

if (t e xtLin e. e q ua ls Ig noreCase (" EXIT \n"» {


System . out .pri nt ln ("EXIT i nvoked , c losi ng cl ient " ) ;
brea k;

BufferedWrite r wr i ter ~ new Buffere dWriter (


new Outp utStreamWri ter (
c l ient .getOut putStream ( » ) ;
System. o ut. p r i nt l n ( " Echo i n put to c l ien t" ) ;
write r .wri te (!lECHO fr om s erv er: 11
+ textLi ne , O, textLi ne . l eng th () + 1 8) ;
wr i t e r . fl u s h() ;
)
c l ient. c lose () ;
)
) c a tch ( I OEx c e p ti on e)
System . err .println (e ) ;

La clase EchoServer es básicamente E/S de Java . Amplía Thread e implementa run,


de modo que cada cliente conectado se puede procesar en su propio contexto. Tras ello uti-
lizamos Se r v e r So c ke t para escuchar en un puerto definido. Cada cliente es, por tanto,
una implementación de Socke t . La entrada del cliente se añade a BufferedReader del
que se lee cada línea. La única consideración de este sencillo servidor es que si la entrada
es EXIT, acaba el bucle y sale . Si la entrada no solicita la salida, el servidor reproduce la
entrada en el OutputStream del cliente por medio de BufferedWri te r.
Es una representación muy básica del funcionamiento de un servidor. Procesa las en-
tradas, normalmente en un subproceso independiente, y después responde al cliente en
función de las mismas. Para probar el servidor antes de utilizar Android, puede acceder
por medio de t elnet al puerto especificado (una vez ejecutado el servidor) e introducir
una entrada; si todo funciona correctamente, reproducirá el resultado.
Android. Guía para desarrolladores lIfI
Para ejecutar el servidor debe invocarlo localmente con Java. Tiene un método
principal, de modo que se ejecuta independientemente; comience desde la línea de co-
mandos o desde el lOE . Recuerde que al conectarse a un servidor desde el emu lad or,
debe conectarse a la dirección IP del host en el que ejecute el proceso del ser vidor; no
127.0.0.1. El emulador se considera a sí mismo 127.0.0.1, así que debe utilizar la direc-
ción del host al intentar conectarse desde Android. (Para averiguar la d irección IP del
equipo en el que se encuentre, introduzca if c o n f ig en Linux o Mac, o ipconfi g
en Windows.)
La parte correspondiente al cliente del ejemplo es donde comienza NetworkExplorer,
con el método ca l lSocket de la actividad S i mp leSoc ket, véase el listado 6.3.

Listado 6.3. Cliente de Android que invoca un recurso de servidor.

pu bli c c l a ss Si mp leSoc ket extends Activ ity {

. 0. Se omi t e n las dec lara c i o nes de variabl es View

@Override
p ub li c vo i d o nCr e a t e (fina l Bundle ic i c l e )
super .onCreate (i cic le) ;
t h i s . setCon t entView (R. layout os impl e_socket) ;

. o. Se omite la a mpliación de Vi ew

th is . s ocketBut t on osetOnCli ckListener (neH OnCl ic kListe ne r ( )

public void onC li c k (fina l View v )


so cketOutputosetText( " ") ;
St ring ou t p ut = callSocket (
ipAddress .getText() .toString() ,
p o rt .ge tText () otoSt ring() ,
s ocketlnput.getText () ot oStr ing(» ;
so cketOutput. setText( output ) ;
)
}) ;

p rivateStringcallSocket (Stri ngip, St ringport, St rings ocketData)


So cketsocket= nul l;
BufferedWri ter wr i t er = null ;
Buffe redReader reader = null;
String o u t p ut ~ nul l ;
try {
socket = new Socke t (i p, I nt e ge r opa r selnt (po r t) ) ;
wri ter = new BufferedWri ter (
new Ou tputStreamW ri ter (
soc ketogetOutputSt ream(») ;
reade r ~ new BufferedReader (
newlnputStreamRead er (
s o c ke t . ge t l np u tS t re a m( » ) ;

String i np u t = socketData;
Hriter .H r ite( inpu t+ " \n" , 0 , .í. np u t Lenqt.h t ) + 1 ) ;
i
BrII 6. Redes y servicios Web

wr te r . flu sh () ;
í

output = reade r . r e a d Li ne ( ) ;
th i s . s o cke t Ou t p u t .se tTe x t (ou t pu t) ;

/ / envia r EXIT y ce rrar


¡.¡riter .¡.¡r i te ( " EXI T\n " , 0 , 5 ) ;
wr i t e r f Lu s h f ) ;
c

. . . Se omi t e n captu ras y c ierres d e l e c t o r, es cri t er y sec ket


. . . Se omi te o nC rea te

return o utpu t ;

En este caso utilizamos el método onCre a te para invocar un método de ayuda


privado call So cket y establecer el resultado en Te xtVi ew. Dentro del método
c a l l Soc ke t creamos un Soc ke t para representar el lado cliente de la conexión y
establecemos el componente de escritura de la entrada y un lector para el resultado. Una
vez abordadas las tareas de mantenimiento, escribimos en el socket, que se comunica con
el servidor, y obtenemos el valor de resultado que devolver.
Un socket es probablemente el uso de red de nivel inferior en Android. Aunque ofrece
una gran abstracción, debemos encargamos de muchos de los detalles (en concreto del
lado del servidor, subprocesos y cola). En muchos casos tendrá que utilizar un socket (si
el lado del servidor ya existe), pero las soluciones de nivel superior como HTTP ofrecen
numerosas ventajas.

Trabajar con HTTP


Como vimos en el apartado anterior, puede utilizar un socket para transferir datos IP con
Android. Es un enfoque importante que hay que tener en cuenta para disponer de dicha
opción y conocer los detalles sub yacentes. No obstante, puede evitar esta técnica siempre
que pueda y aprovechar en su lugar productos de servidor ya existentes para enviar sus
datos. La forma más habitual de hacerlo consiste en utilizar un servidor Web con HITP.
A continuación veremos cómo realizar solicitudes HTTP desde un cliente Android
y enviarlas a un servidor HTTP . Permitiremos que el servidor HTTP procese todos los
detalles y nos centraremos en la aplicación Android cliente.
El protocolo HTTP es complejo. Si no está familiarizado con su uso o necesita más in-
formación al respecto, puede consultar RFC (encontrará el de la versión 1.1 en h t tp: / /
www.w3. o r g/ Pro toco ls/ rf c 2 616/ rfc2 616. html). Para resumir, diremos que es
un protocolo sin estado, con diferentes métodos para que los usuarios puedan realizar
solicitudes a servidores que, a su vez, devuelven respuestas. La Web se basa en HTTP.
Más allá de los conceptos básicos, existen formas de pasar datos entre solicitudes y res-
puestas, y de autenticarse con servidores. En este apartado volveremos a utilizar algu-
nos de los métodos y conceptos más habituales para comunicamos con recursos de red
desde aplicaciones Android.
Android. Guía para desarrolladores IIEID
Para empezar, recuperaremos datos con solicitu des GET de HTfP a un a sencilla página
Web por medio del API estándar java. net oTras ello, utilizaremos el API HttpClie nt
incluido en Android. También crearemos una clase de ayuda, HttpRequ e stHelper,
para simplificar el proceso y encapsular los detalles. Esta clase, y el API de red de
Apache en general, ofrece ciertas ventajas para desarrollar una red prop ia con java .
net, como veremos en breve. Una vez definida la clase de ayuda, la utilizaremos para
realizar solici tu des HTT P y HTTPS adicionales, tant o GET como POST, y analizaremos
la autentica ción desde un punto de vis ta básico. Nuestra primera solicitud HTIP será
una invocación GET con HttpU rl Conne cti on .

HTTP Yjava.net
El mé todo de solicitud HTTP más básico es GET . En este tipo de solicitu d, los d atos
enviados se incrustan en el URL d e la cade na de con sulta. La siguien te clase de la aplica -
ción Netw orkExplorer, (véase el listado 6.4), incluye una activi da d que lo demuestra.
Listado 6.4. Actividad SimpleGet para ilustrar java.net.UrIConnection.
p u b lic c las s Simpl e Get ex t e nd s Act i v i ty (

. . . Se omite n ot r as p a r t e s d e o nCreate

th is .getButto n . se t OnCl i ckLi s t e n e r (n ew OnCl i c kL i s t e n er ( )


p ubl ic void onC lic k (v e w v ) (
í

g etOu t pu t .se tTex t ( " " ) ;


S tr i n g ou tp ut =
getHt tpResponse (get lnput . getText ( ) .toString () ) ;
i f (o utpu t != n u ll ) (
getOutput.s etText (output ) ;

}
)) ;
};

p ri vate St ri n g ge tHttpRe spon s e (S t r i n g l o ca t i on )


S t ring r esu l t = nu l l ;
URL url = nu l l ;

try {
url = n ew URL ( l o c a ti on ) ;
} c a tc h (Malfo r me dU RLEx c ep t i o n e)
/ / r e gis t r ar y/o p r oce s ar
}
i f (u rl ! = nu ll ) {
try (
Ht t p URLCo n n e ct i o n u r l Conn =
(Ht tp URLCo nne c t i o n) u rl . ope n Con n e ct i on ( );
Bu f fer edRe a d e r i n =
new Bu f f e r e d Re a d e r (
new Inpu t S tre a mRe ader (
urlCo nn .getlnputStream ( ) } ) ;
llIiI 6. Redes y servicios Web

String i n pu t Li ne;

in t l in e Co u n t = o; / / limi tar línea s de ej emp l o


whí. Le ( (li n e Co un t < 1 0)
s s ((i n p u tL i ne = i n. r e a dL i ne()) != nu ll) ) (
l i n e Co u n t ++;
re s ult += " \n" + i np utLi ne;

in . c l o s e () ;
u rl Con n. d i s c o nn e c t();

} c a t c h (I OEx c e p ti o n e ) (
/ / r e g i s t r a r y /o procesar
}
} else {
/ / r e g i st r a r y/o p r o c e s a r

ret urn res ul t ;

Para obtener una respuesta HTTP y mostrar las primeras líneas en nuestra clase
SimpleGet, invocamos un método getHttpResponse. En este método construimos
un objeto java. ne t . URL, que se encarga automáticamente de muchos de los detalles,
y después abrimos una conexión a un servidor por medio de HttpUrlConnecti on.
Seguidamente utilizamos BufferedReader para leer datos de la conexión una línea
por vez. Recuerde que mientras lo hacemos, utilizamos el mismo subproceso de la IU y,
por tanto, la bloqueamos, lo que no es aconsejable. Únicamente lo hacemos para ilustrar
el funcionamiento de la red; en un apartado posterior veremos cómo utilizar un subpro-
ceso independiente. Una vez obtenidos los datos, los añadimos a la cadena de resultado
devuelta por el método y cerramos el lector y la conexión. Gracias a la sencilla compati-
bilidad de Android con java. net, podemos acceder a los recursos de red HTTP .
Esta forma de comunicación con HTTP es muy sencilla pero se puede complicar si ne-
cesitamos algo más que recuperar datos y, como hemos indicado, el bloqueo que genera
esta técnica no es aconsejable. Podemos solucionar algunos de estos problemas si utiliza-
mos subprocesos independientes y realizamos su seguimiento, y si creamos nuestra pro-
pia estructura de API para cada solicitud H1TP, pero no será necesario. Afortunadamente,
Android proporciona otro conjunto de API en la biblioteca HttpClient de Apache que
abstraen las clases de java. net y que ofrece una mayor compatibilidad con HTTPpara
solucionar el problema de los subprocesos independientes.

HTTP con HttpClient


Para comenzar con Ht tpCl ien t, veremos algunas de las clases principales para reali-
zar solicitudes HTTP de métodos GET y POST. Nos centraremos en realizar solicitudes de
red en un Thread independiente de la IU, con una combinación de Re sponseHandler
de Apache y Handler de Android (para diferentes propósitos). El listado 6.5 muestra
el primer ejemplo del API HttpClient.
Android. Guía para desarrolladores IDI
Listado 6.5. HttpClient de Apache con Handlerde Android y ResponseHandler de Apache.

private fina l Handler handler = new Handler () {


pub l ic void han d l eMe s s a g e (Me s s a g e ms g)
progressDialog .dismiss ( ) ;
String bundleResult =
msg.get Data ( ) .getS tring ( "RESPONSE");
output.setText (bu nd leRes ult ) ;

j;

.. . Se omi te onCrea te

private void performRequest ()


fi na l ResponseHandler<String> respo nse Handler =
n e w Re s p on s eHa nd l e r <S t r i ng >() {
pub l icS tring ha ndleResponse (HttpResponse response )
StatusLine st atus = response . getStat usLine ( ) ;
HttpEnt i tyentity =respo nse .getEntity () ;
String resu lt = nul l;
try {
resul t = StringUti ls. i n pu t S tre amTo S t r i n g (
ent ity .ge tContent ( ) ) ;
Mess age me s s a g e = handler . obtainMessage () ;
Bu n d l e bu ndl e = n ew Bu ndl e () ;
b undle. p utSt r i ng ("R ES PONSE" , resu lt);
message.setData (bu ndle );
h a ndl e r . s en dMe s s a g e (me s s a g e } ;
J catch (IOEx c e p ti o n e ) {
/ / r e gi st r a r y/o procesar

return resu lt ;

J;
this .progressD ia log =
ProgressDialog. show (this , " wo r k i n g . . . " ,
" p e r f o r mi n g HTTP r e q ue s t " ) ;

ne w Th read ( ) {
p ub l ic voi d r un ()
t ry (
Defau l tH ttpC lient client = ne w De fau ltHttpClient () ;
HttpGet h t t pMe thod =
ne w HttpGet (
urlChoose r. getSe lectedltem ( ) . t o S t r i n g ( ) ) ;
c l ie nt .execute(
http Me tho d, responseHandler ) ;
} catch (Cli en t Prot o c o lEx c e p t i o n e ) {
/ / re gistra r y/o procesar
} catch ( I OEx c e p ti on e ) {
/ / reg istrar y/o procesar

j
} . s t a r t () ;
&lI 6. Redes y servicios Web

Lo primero que hacemos en el ejemplo HttpCl ient es crear un Handl er para en-
viar mensajes a y desde otros subprocesos. Es la misma técnica em pleada en ejemplos
anteriores y qu e permite enviar objetos Message a las tareas de fondo para conec tarse al
subproceso principal de la IV. Esta clase se puede utilizar con solicitudes Ht tpClí en t de
HTIP para pasar como punto de retrollamada. Al completar una solicitud HTIP realiza-
da por Ht t pClient, invoca el mé todo onResponse (si se utiliza Res ponseHandl er).
Al recibir la respuesta, obtenemos la carga por medio de Http Ent i ty, devuelto por
el API. De este modo, se puede realizar la llamada HTIP de forma asíncrona, y no te-
nemos que bloquear ni esperar a que se ejecute la solicitud y se com plete. La figura 6.3
muestra la relación entre la solicitud, la respuesta, Handl er y Res ponseHand le r , y
los diferentes subprocesos.

Subproceso no IU - solicitud de red

HllpClien t de Apache
Solicitud HTIP
execute(mélodo . responseHand ler)

ResponseHandler de Apache
Respuesta HTIP
handleResponse(hllpResponse)

HandJer de Andro id

sendMes sage(mensaje)
onMessage(mensaje)

Subproceso IU - Actualizaciones UI

Figura 6.3. Diagrama de la relación entre HttpClient, ResponseHandler y Handler de Android.

Después de ana lizar el funcionamiento de HttpCli ent y de este enfo que básico, a
con tinuación aña di remos algunos de los detalles en una clase de ay uda para poder in-
vocarla repe tida men te sin tener qu e rep etir el cód igo de configuración .

Crear una clase de ayuda HTTP y HTTPS


La siguien te actividad de la ap licación NetworkExplorer, véase el listado 6.6, es más
sencilla y centrada en An droid que las an teriores clases relacionadas con HTIP. Se con-
sigue gracias a la clase de ayuda que mencionarnos antes, que oculta parte de la com -
plejidad de l pro ceso (examinaremos la clase de ayuda después de ver la primera clase
que la utiliza).
Android. Guía para desarrolladores mil
Listado 6.6. Uso de HttpClient de Apache a través de HttpRequestHelper.

publi c class ApacheHTTPViaHe lper ex tends Activity (

. .. Se omi ten otras var ia b les mi e mb r o

p r ivate fina l Han dler ha ndler = new Ha n dl e r ()


p ub lic void hand leMessage (Me s s a g e ms g) {
prog ressD ialog .dismiss ( ) ;
String bu nd leRes ul t = ms g . getData () . getStr ing ( " RES PONSE") ;
outp ut .setText (bu n d leResult ) ;

);

@Over ride
public vo i d onCrea te ( f i n al Bundle icicle )
super .o nCreate ( icicle ) ;

. . . Se o mite n l a a mpliac ió n y co nfigurac ión d e vis tas

this.butto n .setOnC l ickListener ( ne¡.¡O nC lick Listene r ( )


public vo id onC lick ( fi n al Vie¡.¡v ) (
o utput .se t Tex t ( " " ) ;
pe r for mRe ques t (
u rl Ch o o s er .ge t Sel e c t e d l t em() .toString ( ) ) ;
)
l);
);

. . . Se omi te onPause

private voi d performRequest ( S t ring u r l )

f inal ResponseHand ler<String> responseHandler =


HTTPRequestHe lper .getResponseHandler lnstance (
th is .handler ) ;

t his . progressDia log =


ProgressD ia log.sho¡.¡(th is , "¡.¡orking . . . ",
" p e r f o r mi n g HTTP request " ) ;

riew Thr ea d () (
p ub lic vo i d r un ( )
HTTPRe qu e s t Help e r h el p er = new
HTTPRe que s tH e l p e r( r e spons eHandl e r) ;
he lpe r.performGet (url , n u ll , nu l l , nu ll ) ;
)
) . sta r t () ;

En primer lugar, creamos un Ha n d l e r , desd e el que actualizamos Te xtVi e w en


función de los datos de Message . Posteriormente en el código, en el método onC reate,
invocamos un método pe r for rnRequest local al pulsar el botón go y pasamo s una
cadena que representa el URL.
6. Redes y servicios Web

Dentro del método p erformReque st, utilizamos un método estático para devolver
HttpClient Re s po nseHa nd le r , al que pasamos nuestro Handl er de Android. Más
adelante veremos la clase de ayuda para saber cómo funciona, pero por el momento re-
cuerde que el método estático crea Re s po nseHa nd le r por nosotros. Una vez terminada
la instancia Re s p on s eHandl er, creamos una instancia de Ht tpReque s tH elpe r y la
utilizamos para realizar una sencilla invocación GET de HTIP (en la que solamente pasa-
mos la cadena del URL). Como en el ejemplo anterior, al completar la solicitud, se ejecuta
Re spon s eHa ndler en el método onRe s pons e , y se envía un mensaje a Handl er para
completar el proceso. La actividad del listado 6.6 es sencilla, asíncrona y no bloquea el
subproceso de la IU. HttpCl ient se encarga de las tareas pesadas y de la configuración
de HttpRequestHelper. La primera parte de la clase HttpReque stHelper,quedes-
cribiremos en tres secciones, se rep ro duce a continuación (véase el listado 6.7).

Listado 6.7. Primera parte de la clase HUpRequestHelper.


public class HTTPRequestHe lper (

private static final int POST_TYPE = 1;


private sta tic fi na l int GET_TYPE = 2;
private stat ic fi nal String CONTENT_TYPE = " Co n t e n t - Ty p e";
public static fina l String MIME_FORM_ENCODED =
" a p pl i c a t i o n / x-wwl<- f o r m- u rl e n c o d e d";
public sta tic final String MIME_TEXT_ PLAI N = "text/plain" ;

private f ina l ResponseHandler<String> responseHandler ;

publicHTTPRequest He lper (Response Handle r<String>respo nseHandler )


this. responseHandler = responseHand le r ;

pub lic void performGet (String u r l , String user, String pass,


fi n al Map<String , String> addi tionalHeaders ) (.
performRequest (null , url , user , pass ,
addi tiona lHeaders, n ull , HTTPRequestHe lper.GET_TYPE ) ;

public void performPost (S t rin g content Type , String url ,


Stri ng user, String pass ,
Map<Str ing, String>additionalHeaders,
Map<String, Str i ng> params ) {
performRequest (c o n t en tT ype, u r I , use r, pass,
additiona lHeaders, params , HTTPRequestHelper . POST_TYPE ) ;

p ublic void performPost (S t r i n g u rl, String user , String pass ,


Map<String , String>additiona lHeaders ,
Map<String , String> params ) (
performRequest (HTTPRequestHelper.MIME_FORM ENCODED ,
url, user, pass ,
additiona lHeaders , params , HTTPRequestHe lper.POST_TYPE ) ;

pri vate void performRequest (


Androíd. Gl/ía para desarrolladores &D
Stri ngcontentType ,
Str ing url ,
String user,
String pass ,
Map <String , String>headers,
Map<String , String> pa rams ,
int requestType ) {

DefaultHttpClient client = riew Defaul tHttpC lient () ;

if (( u s e r !=null ) && (p a s s ! = n u l l)) (


client.getCredentia lsProvider ( ) .setCredentials (
AuthScope .ANY,
nel'lUse rnamePass l'lo rdCredent ials(user , pass) ) ;

fi nal Map<Stri ng , Stri ng>se ndHeaders=


n e l'lH a s h Ma p <S t r ing, St ring> ( ) ;
i f ((he a d e r s ! = n ull) & & (h e a d e rs . size () > O) )
se n dHeade rs.putAl l (heade rs ) ;
}
if (requestType == HTTPRequ estHelper .POST_TYPE ) {
sendHeaders .put(HTT PRequestHelper.CONTENT_TYPE , contentType );
}
if (s en d He a d e r s. s ize () > O) {
c lient.addReques t lnte rce ptor (
n ev HttpReques tlnte rcep tor {)
public void process (
f inal Ht t pR e q u e s t reques t , f ina l Ht t p Co n t e x t conte xt)
t hrol'ls HttpExcep t io n , I OEx c e p t i o n {
f or (S t ring key : s e n d He a d e r s . keySet ()) {
i f (! req uest . contains Header (key)) (
request .addHeader ( key ,
sendHeaders .get (key ) ) ;
}

}) ;

.. . E j e c u c i ó n de POST y GET en e l l i s t a d o 6.8

El primer aspecto que destacar en la clase HttpRequestHelper es que se necesita


un Respons eHandl er para pasarlo como parte del constructor. Se utilizará al invocar
la solicitud HttpClient en última instancia. Tras el constructor, vemos un método pú-
blico relacionado con GET de HTTP y varios métodos públicos relacionados con POST.
Todos estos métodos son envoltorios del método privado p erformRequest que puede
procesar todas las opciones HTTP. El método performRequest admite un valor de
encabezado de tipo de contenido, URL, nombre de usuario y contraseña, un objeto Map
de encabezados adicionales, un objeto Map similar de parámetros de solicitud y un tipo
de método de solicitud. Dentro del método pe rformRequest se crea una instancia de
Defaul tHttpClient. Tras ello, comprobamos si están presentes los parámetros de
DI 6. Redes y servicios Web

métodos de usuario y contraseña, y en caso afirmativo, establecemos las credenciales de


solicitud con un tipo Usern amePas swordCredentials (HttpClien t admite varios
tipos de credenciales, como puede comprobar en la documentación de Java) . Al mismo
tiempo que establecemos las credenciales, definimos AuthScope. El ámbito representa
el servidor, puerto, reino de autenticación y esquema de autenticación a los que se apli-
can las credenciales proporcionadas.
Puede establecerlo todo con el nivel de detalle que desee; en este caso utilizamos el
ámbito ANY predeterminado que coincide con todo. Lo que no hemos establecido es el
sistema de autenticación que utilizar. HttpClient admite varios sistemas, incluida
autenticación básica, de compendio y un sistema NTLM específico de Windows. La
autenticación básica, una sencilla combinación de nombre de usuario y contraseña del
servidor, es la predeterminada. (Además, en caso de que sea necesario, puede utilizar
inicio de sesión de formularios para autenticación basada en éstos; basta con remitir el
formulario necesario y obtener el ID de sesión y demás.)
Tras configurar la seguridad, utilizamos HttpRequestlnterceptor para añadir
encabezados HTTP. Los encabezados son pares de nombre y valor, de modo que resulta
muy sencillo. Una vez definidas las propiedades que se aplican independientemente del
tipo de método de solicitud, añadimos parámetros adicionales específicos del método.
El listado 6.8, la segunda parte de la clase de ayuda, muestra los parámetros específicos
de POST y GET, Y el método de ejecución.
Listado 6.8. Segunda parte de la clase HttpRequestHelper.

if (r equ e s t Typ e == HTTPRe qu e s t Helpe r .POST_TY PE)


Ht t p Po s tme tho d= n ew HttpPo s t(u rl) ;
Li s t <Na me Va lu e Pai r > nvps = null;
if (( p a r a ms !=null) && (p a r a ms. s i z e() >0 ) )
nvps = new Ar rayList<NameValuePair> () ;
for (S t ri n g k ey : params . keySet ()) {
n v p s. a dd (n e l1 Ba s i c Name Valu e Pa i r (ke y ,
p aram s . g e t( k e y)));

)
i f (nvps ! =nu ll )
tr y {
method . set Ent ity (
n e w UrlEn c ode dFormEn t i ty (nv p s, HTTP.UTF_ 8) ) ;
} cat c h (Un s uppor t e dE n c o di n gE x c e p t i on e ) (
/ / re g istr ar y/o pr o c e s a r

exec ute (cli e n t, meth od) ;

} e lse i f (requ e stT yp e == HTTPRe qu e s tH elp e r . GET_T YPE)


HttpGe tme thod = n e w Ht tpGet(u rl) ;
execu te (c lient , metho d);

pr i v ate v o id e x e cut e(H t t p Cl i e nt cli e n t, HttpRequestBase method )


Android. Guía para desarrolladores mi
Basic HttpRes ponseerrorRe sponse=
neH BasicHttpRespo nse (
new Protoco lVersio n ( " HTT P_ ERROR", 1, 1 ) ,
5 00 , " ERROR") ;
t ry {
c 1 i e n t . e x e c u t e (me t ho d , th i s . r e spo n s e Hand l e r ) ;
} catc h (Ex c e p t i o n e ) {
erro rResponse .setReason Phrase (e .ge t Mes sage ( ) ) ;
try (
t h i s . res po nse Ha nd 1er . ha n d leRespo n s e (erro rRes po ns e ) ;
) catch (Ex c e p t i o n ex ) (
/ / r e g i s t r a r y / o p r o c es ar

Cuando la solicitud especificada es de tipo POST, creamos un objeto Ht tpPos t para


procesarla. Tras ello, añadimos parámetros de solicitud POST, otro conjunto de pares
de nombre y valor, y que se crean con el objeto Basi cNameValuePair. Ya podemos
realizar la solicitud, por medio del método de ejecución privado local con el objeto del
método y el cliente.
El método de ejecución establece un controlador de respuesta de error (queremos
devolver una respuesta, se produzca o no un error, de modo que lo configuramos en un
caso) e incluye el método HttpClient ex e c u te, que requiere un objeto de método (en
nuestro caso, POST o GET, preestablecido) y un Re spo n s eHeader como entrada. Si no
se genera una excepción al invocar Ht t p C 1 i en t e xe c u te, todo ha salido correctamente
y los detalles de la respuesta se añaden a Re sponseH andl er. Si se produce una excep-
ción, completamos el controlador de errores y lo pasamos a ResponseHandl er.
Invocamos el método de ejecución privado local con los detalles establecidos para
una solicitud POST o GE T. El método GET se procesa de forma similar a POST, pero no
establecemos parámetros (con las solicitudes GET se esperan parámetros codificados en
el propio URL). Actualmente, la clase solamente admite POST y GET (lo que abarca el
98 por 100 de las solicitudes que necesitaremos habitualmente) pero se podría ampliar
para admitir otros tipos de métodos HTIP.
La última parte de la clase de ayuda, véase el listado 6.9, nos lleva al primer ejemplo,
y muestra exactamente lo que devuelve el método getRe spons eHandlerln stan c e
(la construcción de la clase de ayuda requiere un Res po nseHa n d le r y este método
devuelve uno predeterminado).
Listado 6.9. Parte final de la clase HttpRequestHelper.

publ i c s tat ic Re spon s eHandl er<Str ing >


g etRespo n s e Han d le r l ns t a n c e ( f i na l Hand ler ha nd 1e r )
fi na l Re s ponse Ha n d l e r <S t r i n g> r espons eHandl er =
ne w Re sponseHandl e r <Stri ng> () {

p ubli c String hand l e Re spons e (f i n a l HttpResp on se r e s pon s e )


Mes s age mes sage = h a n d l e r . ob tain Messag e ( ) ;
Bund l e bu nd le = n e w Bundl e () ;
S t a t us Li ne s t a t us = re s p on s e . getStat u sLine () ;
HttpEntity e n t i t y = r e s p o n s e . g e tEnti ty () ;
l!liI 6. Redes y servicios Web

Str ing r e s ul t = nu l l;
i f (e n t it y != nu ll ) {
try (
resu lt = St r ingUtils. inputStreamToString (
e nt ity .getContent ( » ;
bund le .putStr ing (
" RES PONSE", res ul t ) ;
message.set Data (bu ndle) ;
handler.se ndMessage (message ) ;
) catch ( I OEx c e p t i o n e ) {
bundle.putString ( "
RESPONSE" , " Error - " + e . getMessage () } ;
message .se tData (b u n d le ) ;
h andl er. s endMe s s a g e(me s s a g e) ;
)
} e lse {
b undle.pu tSt r i ng (" RES PONSE", " Err o r -"
+ r e spon s e . g e t S tatu sLi n e() . g e t Re a s onPhra s e(»;
me s s a g e. s e t Da t a( b undl e) ;
ha nd ler .sendMessage (message );

ret urn result ;

};
r etu r n r e s p on s e Handl er;

Al analizar el método getRespons eHandlerlnstance de la clase de ayuda, recuerde


que aunque resulte de gran utilidad, es totalmente opcional. Puede utilizar dicha clase sin
este método. Para ello, construya su propio ResponseHandler y páselo al constructor
de ayuda, un caso perfectamente plausible. El método g etRespon seHandlerlnstance
crea un ResponseHandl er predeterminado que se conecta a Handler a través de un
parámetro y analiza la respuesta como String. La cadena de respuesta se devuelve al
invocador por medio del modelo Handler Bundle y Mes sage que hemos utilizado
anteriormente para transferir mensajes entre subprocesos de pantallas de Android.
Una vez abordados los detalles de Ht tpReques tHandler y después de analizar su
uso básico, nos centraremos en aplicaciones más elaboradas de esta clase en el contexto
de los servicios Web.

Servicios Web
El término servicios Web tiene distintos significados en función de su origen y su
público de destino. Para algunos es un difuso término de marketing sin definir; para
otros, un conjunto rígido y específico de protocolos y estándares. Lo abordaremos como
concepto general, sin definirlo pero tampoco sin dejarlo sin definir.
Los servicios Web son un medio para mostrar un API a través de un punto de red
tecnológicamente neutral. Permiten invocar un método remoto o una operación no vin-
culada a una plataforma o distribuidor específico, y obtener un resultado. Según esta
Android. Guía para desarrolladores mi
definición, se incluye POX sobre la red POX, al igual que RESTy SOAP, y prácticamente
todos los métodos de exposición de operaciones y datos en red de forma neutral. POX,
RESTy SOAP son sin duda los servicios Web más conocidos y los que analizaremos en
este apartado. Cada uno proporciona unas directrices generales para acceder a datos y
exponer operaciones, y cada uno es más riguroso al respecto que el anterior. POX bá-
sicamente muestra fragmentos de XML sobre la red, normalmente a través de HTTP.
RESTes más detallado y recurre al concepto de recursos para definir datos que después
manipula con distintos métodos HTTP con un enfoque similar a URL (semejante el sis-
tema Inten t de Android, que ya hemos visto en capítulos anteriores). SOAP es el más
formal de todos e impone estrictas reglas sobre tipos de datos, mecanismos de trans-
porte y seguridad.
Todos estos enfoques ofrecen ventajas y desventajas, diferencias que se amplifican
en una plataforma móvil como Android. Aunque no podamos describir todos sus deta-
lles, veremos las principales diferencias de cada uno. Examinaremos el uso del enfoque
POX para devolver mensajes recientes del API deUcio. LIS, para después utilizar RESTcon
el API Google Data AtomPub. Para empezar, abordaremos el que seguramente sea el
tipo de servicio Web con mayor presencia en la red y el que más verá al conectarse con
aplicaciones de Android: POX.

POX con HTTP y XML


Para trabajar con POX realizaremos llamadas de red al conocido sitio de marcadores
sociales del.icio.us. Especificaremos un nombre de usuario y una contraseña para iniciar
sesión en un recurso HTTPS y devolver una lista de mensajes recientes o ma rcadores.
Este servicio devuelve datos XML sin procesar, que después analizaremos en una clase
de estilo JavaBean y la mostraremos en la pantalla (véase la figura 6.4).
El listado 6.10 muestra el inicio de sesión en del.icio.us y el código de la actividad POST
de HTTPS en nuestra aplicación NetworkExplorer.

Listado 6.10. API POX HTIPS del.icio .us con autenticación de una actividad .

p ub lic class Del i ci ou s Re c en tPo s t s extends Activ ity (

priva te s t a t i c fi nal String CLASSTAG =


Del ic iousRece ntPosts .class .getS i mpleName ( ) ;
private static fi na l Stri ng URL_GET_POS TS_RECENT =
"h t tp s : / / a p i. d el . i ci o.u s / vl /po st s / r e c en t ?";

. . . Se omi t e n l a s declaraciones de variables miembro de u s e r , pass , output y b utton


(Vi s t a s)

private f i na l Ha nd l e r h a ndl e r = n e w Ha ndl e r {) {


pub l ic void handleMessage ( f i n a l Message msg )
progressD ialog.dismiss ( );
String bundleResul t = ms g. getData ( ) . getString ( "RESPONSE ") ;
output.setText (parseXMLResult (bundleResult));
6. Redes y servicios Web

};

@Override
public void onC r eate ( f i n a l Bundle icicle )
super . onCreate (icicle );
this.setCon tentVieN (R .layout .deli cious_posts ) ;

.. . Se omite la amp liación de l a s v istas

this .button .setOnCl ickListe ne r (neI10nClickListener ( )


public void onClick ( fi n a l Vi ew v} {
o utput.setText ( " " );
p e r f o r mRe q u e s t (u s e r . g e t Te x t () .toString( ) ,
pass .getText( ) . t o S t r i n g ( ) ) ;
)
)) ;
);

.. . Se o mi te onPause

pri va te void performRequest (S t r i n g u s e r, Str ing pas s ) {


this . progressDialog = Progress Dialog . ShON ( t hi s ,
" wo r k í.nq . . . " , " p e r f o r mi n g HTTP p o st to d el. icio . us " ) ;

fi nalRespo nseHand ler<Str i ng> response Handle r=


HTTPRe que s tH el p e r . g e t Re s p o n s e Handl e rl n s t a n c e( t hi s . h a n d l e r);

new Th read () {
p ublic void ru n ( } {
HTTPRequest Helper h e l p e r =
n e NHT TPRe q u e s t He l p e r( r e sp on s eH andl e r);
helpe r. pe r fo rmPost (URL_GET _ POSTS_RECENT ,
u s e r, pass , null , nul l ) ;
)
) . star t () ;

privateStringparseXMLResult(String xmlString)
StringBuilder res ult = riew Stri ngBu i lde r () ;
try {
SAXParserFactory sp f ~ SAXPa rse rFactory . new Iris t.ance () ;
SAXPa rse r sp = spf . nel·,SAXParser ( ) ;
XMLReader xr ~ sp . getXMLReader () ;
De l icious Handler h a n d l e r = new Delicious Ha nd le r () ;
xr .setConte ntHandler (handl er ) ;
x r .parse (neH l nputSource(neNStri ngReade r (xmlStr ing ) ) ) ;

Li s t <De l i c i o u s Po s t > posts = h a n dl e r. ge tPosts () ;


for (Del i c i o u sPo s t p : pos ts } {
res ult .append ("\n " + p .getHref ( ) ) ;
)
} c a tch (Ex c e p t i o n e ) {
/ / registrar y/o p ro cesar

retu r n resul t . toString ( ) ;


Android. GlIía para desarrolladores II1II

Figura 6.4. Pantalla de mensajes recientes de del.icio .us en la aplicación NetworkExplorer.

Para utilizar un servicio pax, primero necesitamos cierta información sobre el mismo,
comenzando por el URL. Para invocar el servicio del. icio. us tendremos que utilizar
Handler para actualizar la ID y el Ht tpRequestHelper del apartado anterior. En este
ejemplo hay menos líneas de código que si no utilizáramos la clase de ayuda (se repeti-
rían en las distintas clases Activi ty). Con la instancia de la clase de ayuda, invocamos
el método pe r f o rrnReque s t con un nombre de usuario y una contraseña. Este método,
a través de la clase de ayuda, inicia sesión en del. icio. us y devuelve un fragmento
XML que representa los últimos elementos marcados como favoritos. Para convertir el
XML sin procesar en tipos útiles, incluimos un método parseXMLResult. El análisis
de XML es un aspecto que analizaremos en un capítulo posterior pero diremos que re-
corremos la estructura XML con un analizador y devolvemos nuestros propios bean de
datos DeliciousPost para cada registro. Es todo lo necesario para utilizar pax para
leer sobre HTIP5. El principio arquitectónico RE5T se construye sobre la inclusión de
XML en HTIP, utilizando pax y ampliándolo, como veremos a continuación.

REST
Durante el análisis de RE5T también intentaremos hacer referencia a otros concep-
tos importantes para el desarrollo de Android: utilizar las distintas API Google Data
(http://code.google . corn/apis/gdatal). Ya hemos utilizado API GDATA en la
&El 6. Redes y servicios Web

aplicación RestaurantFinder de un capítulo anterior, pero no realizamos autenticación


y no vimos los detalles de redes o REST. Por ello, aquí realizaremos dos nuevas tareas:
autenticar y recuperar un elemento ClientLogin de Google, y recuperar los datos de
contacto de un usuario concreto. Recuerde que aunque estemos trabajando con los API
GDATA en profundidad, utilizaremos un API de estilo REST.
Los conceptos principales con REST son la especificación de recursos en forma de
URI y el uso de distintos protocolos para realizar diferentes acciones. El protocolo de
publicación de Atom (AtomPub) define un protocolo de estilo REST y las API GDATA
son una implementación de AtomPub (con algunas extensiones de Google). Como ya
hemos mencionado, el enfoque de Int ent de la plataforma Android es muy similar a
REST. Un URI como co n t e n t : / / c o ntac t s / 1 tiene estilo REST. Incluye una ruta que
identifica el tipo de datos y un recurso concreto (el número de contacto 1).
Sin embargo, este URI no dice qué hacer con el contacto 1. En términos de REST,para
ello se utiliza el método del protocolo. Para HITP, REST utiliza distintos métodos para
realizar diversas tareas: POST (crear, actualizar o, en casos especiales, eliminar), GET
(leer), PUT (crear, reemplazar) y DELETE (eliminar). Las implementaciones RESTHTTP
utilizan todos los tipos de métodos HTTP y recursos para crear APL
En el mundo real no encontrará demasiadas implementaciones REST. Es más habi-
tual ver API con estilo REST. Eso significa API que normalmente no utilizan el método
DELETE de HTTP (muchos servidores, proxy y demás tienen problemas con DELE TE) y
sobrecargan los métodos GET y POST más habituales con distintos URL para cada tarea
(mediante la codificación de un fragmento para indicar el propósito del URL, o como
encabezado o parámetro, en lugar de depender estrictamente del método). De hecho,
aunque se suelan denominar REST a las API GDATA, técnicamente sólo tienen estilo
REST, no son REST. No es algo negativo; el objetivo es la facilidad de uso del API en
lugar de la pureza del modelo. No obstante, REST es una arquitectura o estilo muy co-
nocido, debido a su sencillez y a sus prestaciones.
El listado 6.11 muestra un ejemplo que se centra en los aspectos de red de la auten-
ticación con GDATA para obtener un elemento Cli entLogin y posteriormente utili-
zarlo con una solicitud de estilo REST para obtener datos de contactos incluyendo una
dirección de correo electrónico como recurso.
Listado 6.11. Uso del API AtomPub de Google Contacts con autenticación.

public c lass GoogleClientLogin extends Activi ty {

p r ivate static fi na l String URL_ GET_ GTOKEN =


" h t tps : / / www . g o o gl e. c o m/ a c c o un t s / Cl i e n t Lo g in" ;
p ri v a t e stat ic fi n al String URL_GE T_CONTACTS_PRE F IX =
"ht t p : / / www. g o o gl e . c om/mB/ f e e d s / c onta c t s /" ;
private sta tic f inal Str ing URL_GET _ CONTACTS_SUFFIX = " / f u ll" ;
priva te sta ti c f inal String GTOKEN_ AUTH_ HEADER_ NAME = " Au t h o r i z a t i o n";
pr iva te static f ina l St r ing GTOKEN_ AUTH_ HEADER_ VALUE_PREFI X =
" Go o g l e Lo g in a ut h= " ;
pr ivate static f i na l St r i ng PARAM_ACCOUNT_ TYPE = " a c c oun tT y p e" ;
p rivate stat ic fi n al Stri ng PARAM_ACCOUNT_ TYPE_VALUE =
"HOSTED_ OR_ GOOGLE " ;
pr i vate sta t ic fin al Stri ng PARAM_ EMAIL = " Email";
Android. Guía para desarrolladores mi
pri vate static f inal S t r ing PARAM_ PAS SWD = " Pa s s wd " ;
p ri v at e sta tic final St ring PARAM_SERVICE = " s e r v i c e " ;
p rivate sta ti c fin al S tr ing PARAM_ SE RVICE_ VALUE = " cp ";
p rivate static final String PARAM_SOURCE = " s ou r c e" ;
priva te stati c fin al S t r i n g PARAM_SOURCE_VALUE =
" ma n n i n g- u nl o c k i n g An d r o i d- l . O" ;

privateStringtokenValue ;

. .. Se omi ten las de clarac i one s de Vi e \·,

privat e f inal Handl e r tokenHandler = new Handler ()

publi c vo i d hand leMessage (final Message msg) {


progre s sD ia log.dis mi ss ( );
St r ing bundleResul t = ms g . g etData ( ) . getStr ing ( "RESPONSE " ) ;
String authToken = bund leRes u lt;
authToken = a uthToken . substring (authToken . indexOf (" Au t h =")
+ 5 , authToken .length () ) . t rim () ;
tokenVal ue = authToken ;
GtokenText .setText (authToken );

};

priva te final Hand le r c o n t a c ts Hand l e r =


n e w Ha n d l e r () (
public void handleMessage ( f i n al Message msg)
progressDialog .dismiss( );
String bundleResul t = msg. getData () . g etString ( "RESPONSE ") ;
output.s etText (bundleResult ) ;

j;

. . . Se omi ten onCreate y onPause

privatevoidge tToken (Stringemail, Stri ngpass) {


finalRespon seHandler <String >respon s eHandler =
HTTPRequest Helper .getResponseHandlerlnstance (
th is .to ken Handle r ) ;

t his . pr ogressDia log = ProgressDia log. Sh0\1 (t hi s,


" wo r k i nq . . . ", "getti ng Goog le ClientLogin t o ke n " ) ;

new Thread () {
publi c void run ( )
Ha s hMa p <Str i n g , String> pa rams =
newHashMap<Str ing, String> ( } ;
params . p ut (GoogleC l ientLogin .PARAM_ACCOUNT_TYPE ,
Goog leC lientLogin .PARAM_ACCOUNT_TYP E_VALUE ) ;
params .put (Goog leClientLogin .PARAM_EMAIL , e ma i l );
params . p u t (GoogleCl ientLogin .PARAM_PA SSWD, pas s);
params .put (Goog leC lie ntLogin .PARAM_SERV ICE,
GoogleC lien tLogin.PARAM_S ERVICE_VALUE );
params . put (GoogleC l ient Log in . PARAM_SOURCE ,
Goog leClientLogin .PARAM_SOUR CE_ VALUE);

HTTPRequest Helper h e l p e r =
6. Redes y servicios Web

newHTTPRequestHe lper (responseHandler )¡


helper .performP ost( HTTPRequestHe lper.M IME_FORM_ENCODED ,
Goog leClientLogin.URL_GET_GTOKEN,
nu l l , nul l , null, params ) ¡
)
) .start () ¡

pr i va t e voidge tCo ntacts (St r i ng ema il, Stringtoken ) (


f i na lRespo nseHa nd ler<String>respo nseHandler=
HTTPRequestHe lper.getResponse Hand ler lnstance (
this .contacts Hand le r ) ¡

this. progress Dialog = Progress Dia log . s h ow (thi s,


" wo r k i nq . . . " , "getti ng Goog le Contacts " ) ¡

new Th r e a d () (
p ub lic vo id r un ()
Ha s hMap<St r i ng , String> headers =
new Ha s hMap <St r i ng, Stri ng > () ¡
he a de r s. pu t( Googl e Cl i en t Log in . GTOKEN_AUTH_HEADER_ NAME,
Goog leC lient Log i n.GTOKEN_AUT H_HEADER_VALUE_ PREFIX
+ toke n ) ¡

Stri ng e ncEmai l = e mai l ¡


try {
encEmail = URLEncoder . encode (en cEma i l,
"UTF- 8 11 ) ;

} c atch (Uns upp o r t e dEn c odin gExc ep t i on e )


/ / registrar y/o proc e s ar
}
String ur l =
Goog leClien tLogin . URL_GET_CONTACTS_PREFIX + encEmail
+ GoogleC l ientLogin . URL_GET_CONTACTS_S UFFIX ¡

HTTPRequestHelper helper = new


HTTPReque s tHe l p e r(r e s p o ns e Ha ndl e r)¡
help e r. pe rform Ge t( u r l, nu l l , null , headers ) ¡
}
} . start () ¡

Tras las numerosas constantes que representan distintos valores St r i n g que uti-
lizaremos con los servicios GDATA, vemos varias instancias Handl er, comenzando
por t okenHandler. Este controlador actualiza TextView cuando recibe un mensa-
je, como en los ejemplos anteriores, y actualiza una variable miembro t okenValue,
que no es de la IV, para su uso en otras partes del código. El siguiente controlador es
eo n t ae t s Ha ndl e r , que se utiliza para actualizar la IV tras solicitar los contactos.
Además de los controladores, vemos el método g etToken, que incluye todos los pa-
rámetros necesarios para obtener un elemento ClientLogin de los servidores GDATA
(http://eode . g oogle. eom/apis/gdata/auth. html). Tras la configuración para
obtener el elemento, podemos realizar una solicitud POST a través de la clase de ayuda.
Android. Gllía para desarrolladores &a
Una vez abordados los detalles del elemento, vemos el método getContacts. Utiliza
como encabezado el elemento obtenido a través del método anterior. Puede almacenar
este método en cache y utilizarlo en posteriores solicitudes (no es necesario obtener
siempre el elemento). Tras ello aparece el código para la dirección de correo electrónico
del URL de API de contactos y realizamos una solicitud GET para los datos, de nuevo
con HttpRequestHelper.
Con este enfoque realizamos varias invocaciones de red (una como HTTPS para ob-
tener el elemento y otra como HTTP para obtener datos) por medio de la clase de ayuda
anterior. Cuando el API GDATA devuelve los resultados, analizamos el bloque XML y
actualizamos la IV.

Aunque aquí hemos incluido un ejemplo ClientLogin que funciona, también hemos
pasado por alto una parte importante, CAPTCRA. Google puede exigir CAPTCRA con
el enfoque Cl i e nt Log i n, aunque opcionalmente. Para admitir ClientLogin de
forma completa, debe procesar esa respuesta y mostrar el CAPTCHA al usuario, para
después devolver la solicitud con el valor de CAPTCRA introducido por el usuario. En
la documentación de GDATA encontrará más detalles al respecto.

Una vez analizadas la redes de estilo REST, solamente nos queda SOAP. Es un tema
muy habitual al hablar de redes de dispositivos móviles pero en ocasiones el bosque no
deja ver los árboles.

SOAP O no SOA~ esa es la cuestión


SOAP es un potente protocolo que tiene muchos usos. Aunque se puede utilizar en
un pequeño dispositivo incrustado como un teléfono inteligente, independientemente
de la plataforma, no es recomendable. En el entorno de recursos limitados de Android,
la pregunta es qué se debe hacer, más que si se puede hacer o no .
Seguramente muchos programadores experimentados, que llevarán años utilizando
SOAP en otros dispositivos, no estén de acuerdo. Esperen a la explicación. Las princi-
pales características de SOAP son su compatibilidad con tipos (a través de esquemas
XML), transacciones, su seguridad y encriptación, su compatibilidad con mensajes y
todos los estándares relacionados con WS-*. Estos elementos son fundamentales en
muchos entornos informáticos orientados a servidores, sean o no empresariales. Y estos
elementos añaden sobrecarga, en especial en un pequeño dispositivo incrustado. De
hecho, en muchos casos en los que se utiliza SOAP en dispositivos incrustados, los
usuarios ignoran las funciones avanzadas, y utilizan XML sencillo con la correspon-
diente sobrecarga. En un dispositivo incrustado se obtiene mejor rendimiento y un
diseño más sencillo por medio de una arquitectura de estilo REST o POX, y evitando
la sobrecarga de SOAP .
&II 6. Redes y servicios Web

No obstante, en ocasiones tiene sentido investigar el uso de SOAP directamente con


Android. Si tiene que comunicarse con servicios SOAP existentes de los que no tiene el
control, SOAP podría tener sentido. Por otra parte, si ya dispone de clientes J2ME para
servicios SOAP existentes, podrá transferirlos en determinados casos. Pero en ambos en-
foques, las ventajas son para el programador, no para el rendimiento del usuario. Incluso
al trabajar con servicios SOAP existen tes, recuerde que puede escribir un proxy de estilo
POXjREST para servicios SOAP en el lado del servidor e invocarlo desde Android, en
lugar de utilizar SOAP directamente.
Si piensa que SOAP es la opción adecuada, puede utilizar alguno de los puertos del
conjunto de herramientas kSOAP (http://ks oap2. sourc eforge . net/), especial-
mente diseñadas para SOAP en un dispositivo Java incrustado. No obstante, recuerde
que incluso la documentación de kSOAP afirma que SOAP añade una sobrecarga signi-
ficativa a los servicios Web que puede afectar a los dispositivos móviles. Si tiene control
total sobre el cliente y el servidor, una arquitectura basada en REST resulta más ade-
cuada. Además, puede crear su propio analizador para servicios SOAP sencillos que no
utilicen funciones avanzadas y aplicar un enfoque POX que incluya las partes XML de
SOAP necesarias (siempre puede crear las suyas propias, incluso con SOAP).
Sin embargo, nuestra respuesta es que no utilice SOAP en Android, aunque pueda. Este
. análisis de SOAP completa la descripción general de los servicios Web y de la red.

Resumen
Iniciamos este capítulo con un repaso de los conceptos básicos sobre redes, desde
nodos y direcciones a capas y protocolos. Con esta información, vimos detalles relacio-
nados con la forma de obtener información sobre el estado de la red y distintas formas
de trabajar con las prestaciones de redes IP de la plataforma.
En términos de redes, hemos visto cómo usar socket básicos y el paquete java. neto
También hemos examinado el API Ht t pCl i en t de Apache. HITP es uno de los recursos
de red más habituales e importantes disponibles para Android. El uso de HttpClient
nos ha permitido analizar los distintos tipos de solicitudes, parámetros, encabezados,
autenticación y demás. Además, nos hemos adentrado en los conceptos de POX y REST,
y ligeramente SOAP, ya que todos utilizan HTIP como mecanismo de transporte.
Una vez vistas muchas de las posibilidades de una red y sabiendo lo que puede hacer
con API del lado del servidor y su integración con Android, abordaremos otro impor-
tante aspecto del universo de Android: la telefonía.
7
Telefonía
Con un dispositivo de Android puede navegar por la Web, almacenar y recuperar
datos de forma local, acceder a redes e información sobre ubicaciones, utilizar distintos
tipos de aplicaciones y, atento, realizar llamadas telefónicas.
Después de todo lo que hemos visto, uno de los componentes fundamentales de la
plataforma es el teléfono móvil. Dispone de servicios para marcar, recibir llamadas, en-
viar y recibir mensajes de texto y multimedia, etc. Con Android, la ventaja añadida es
que todos estos elementos son accesibles para los programadores a tra vés de sencillas
API y aplicaciones incorporadas que utilizan In t ent y servicios. Puede utilizar la com-
patibilidad con telefonía de Android y combinarla e incluirla en sus propias aplicaciones
(como hemos visto en ejemplos anteriores).
En este capítulo analizaremos los conceptos básicos de la telefonía y los términos re-
lacionados con los dispositivos móviles. Pasaremos después a paquetes básicos de tele-
fonía de Android, para centramos en el procesamiento de llamadas con acciones Inten t
incorporadas y veremos las clases TelephonyMana ger y Phon e StateLi stener. Las
acciones Intent son las que utilizará a diario para iniciar llamadas en sus aplicacio-
nes . TelephonyManage r, por otra parte, no tiene relación con las llamadas sino que
permite recuperar todo tipo de datos relacionados con la telefonía, como el estado de
la red de voz, el número del dispositivo o los detalles de la tarjeta SIM . Por medio de
TelephonyManager podemos añadir PhoneSta teListener, que indica si cambia el
estado de la llamada o de la red.
Una vez presentadas las API básicas de telefonía, pasaremos a trabajar con otra fun-
ción habitual de los teléfonos móviles: el envío y recepción de mensajes SMS. Android
proporciona Intent y aplicaciones para procesar mensajes SMS así como API que le
permiten enviar y recibir mensajes SMS.
eIiII 7. Telefonía

También veremos las funciones del emulador para enviar llamadas de prueba y / o
mensajes para probar sus aplicaciones.
Volveremos a utilizar una aplicación de ejemplo para ilustrar los conceptos de este
capítulo. Crearemos la aplicación TelephonyExplorer para mostrar cómo marcar desde
el teléfono, obtener información de estado del teléfono y el servicio, añadir oyentes al
estado del teléfono y trabajar con SMS. Nuestra aplicación TelephonyExplorer tiene va-
rias pantallas básicas, véase la figura 7.1.

Figura 7.1. Pantalla principal de TelephonyExplorer con todas


las actividades relacionadas que realiza la aplicación.

TelephonyExplorer, como puede apreciar en la imagen, no es especialmente estética


ni demasiado práctica, aunque sirve para aprender los conceptos y los detalles de las
API. Esta aplicación nos sirve para ver las API relacionadas con la telefonía, al tiempo
que resulta sencilla. Antes de empezar a diseñar TelephonyExplorer, aclararemos el con-
cepto de telefonía y su terminología.

Información y términos sobre telefonía


Puede que la información básica sobre telefonía no sea nueva para los programadores
de móviles con experiencia (si es su caso, puede pasar al siguiente apartado) pero es impor-
tante aclarar términos y establecer las bases para los que no conocen dichos conceptos.
Android. Gllía para desarrolladores II1II
En primer lugar, telefonía es un término genérico que hace referencia a los detalles rela-
cionados con las comunicaciones electrónicas de voz a través de redes telefónicas. Nuestro
ámbito será, evidentemente, la red de telefonía móvil en la que participan los dispositivos
de Android, en concreto la red GSM (Sistema Global para Comunicaciones Móviles).

El término teléfono significa habla a distancia . Proviene del griego tele, que significa
lejos, y fonos, que significa discurso.

GSM es una red de teléfonos móviles. Los dispositivos se comunican por ondas de
radio y frecuencias concretas por medio de repetidores telefónicos. Esto significa que
el estándar GSM debe definir diferentes aspectos como identidades para dispositivos y
móviles, así como todas las reglas para establecer la comunicación.
No nos adentraremos en los detalles de GSM pero conviene recordar que es el es-
tándar empleado por Android para admitir llamadas de voz, y el más utilizado por los
operadores y dispositivos de todo el mundo. Todos los dispositivos GSM utilizan una
tarjeta SIM para almacenar la configuración de la red y del usuario.
Una tarjeta SIM es una pequeña tarjeta inteligente, extraíble y segura. Todos los dis-
positivos que operan en una red GSM disponen de identificadores exclusivos, que se
almacenan en la tarjeta SIM:

• ICCID (ID de Tarjeta de Circuito Integrado): Identifica la tarjeta SIM (también


denominado Número de Serie SIM o SSN).
• IMEI (Identidad de Equipamiento Móvil Internacional): Identifica un dispositivo
móvil. (El número suele aparecer impreso bajo la batería.)
• IMSI (Identidad Internacional del Suscriptor Móvil): Identifica al suscriptor (y
a la red a la que pertenece).
• LAI (Identidad de Área de Ubicación): Identifica la región del proveedor de red
del dispositivo.
• KI (Clave de Autenticación): En esta red se utiliza una clave de 128 bits para
autenticar la tarjeta SIM.

Estos números son importantes para validar y autenticar una tarjeta SIM, al disposi-
tivo en que se encuentra y al suscriptor en la red (y entre redes si fuera necesario).
Junto con el almacenamiento de identificadores y claves de autenticación, las tarje-
tas SIM permiten guardar contactos y mensajes SMS. Es muy útil para los usuarios, ya
que pueden extraer sus tarjetas y disponer de sus datos de contactos y mensajes en otros
dispositivos. En la actualidad no existen API públicas para interactuar con la tarjeta
SIM directamente en el dispositivo Android, aunque puede que aparezcan en el futuro.
(Actualmente, la plataforma controla la interacción SIM y los programadores pueden
obtener acceso de sólo lectura a través de las API de telefonía.)
ePI 7. Telefonía

La base para trabajar con los paquetes de telefonía para Android es así de breve y
sencilla. No olvide que estamos trabajando con una red GSM y que puede encontrar
términos como IMSI e IMEI, almacenados en la SIM. Para acceder a esta información y
realizar otras operaciones, se util iza la clase TelephonyManager.

Acceder a información sobre telefonía


Android proporciona una clase de administración que proporciona información sobre
muchos detalles del dispositivo relacionados con la telefonía. Por medio de esta clase,
TelephonyManager, puede acceder a muchas de las propiedades GSM/SIM mencio-
nadas anteriormente y puede obtener información de estado de red y actualizaciones.
Para que las aplicaciones sepan cuándo está disponible un servicio y cuándo se inician
llamadas, se mantienen activas y finalizan, se añade un oyente de eventos al teléfono,
PhoneListener, a través del administrador.
A continuación examinaremos distintas partes de la aplicación de ejemplo
TelephoneManager para analizar estas clases y conceptos. Para empezar, obtendremos
una instancia de TelephonyManager y la utilizaremos para consultar información de
telefonía.

Recuperar propiedades de telefonía


El paquete android . telephony contiene la clase TelephonyManager, así como
detalles de toda la información que ofrece. En este ejemplo obtendremos y mostraremos
.un pequeño subconjunto de dicha información para ilustrar este enfoque. La primera
actividad, no la pantalla principal, de la aplicación es una sencilla pantalla que muestra
parte de la información que podemos obtener a través de TelephonyManager (véase
la figura 7.2).
La clase TelephonyManager constituye el centro de información de los datos rela-
cionados con telefonía de Android. El listado 7.1 muestra cómo obtener una referencia
a esta clase y utilizarla para recuperar datos (como los ilustrados en la figura 7.2).

Listado 7.1. Obtener una referencia TelephonyManager y utilizarla para recuperar datos.

/ / .. . se omite e l inic io de l a c lase

fi na l Tel e p h on yMana g er te l Mgr =


(Tel e pho n yMana g e r) th is .getSystemService (
Context .TELEPHONY_SERV ICE);

/ / . .. se omite e l método onCreate yo t ros

publ ic String getTelephonyOverviel1 (


Tel e phon y Man a g e r telMgr ) (

in t c a llS tate = te lMgr . getCa llState () ;


Android. Guía para desarrolladores mi
String callStateString = " NA" ;
sw i t ch ( c a ll S t a t e) {
case TelephonyManager .CALL_STATE_IDLE :
cal lStateString = " I DLE";
break ;
case TelephonyManager .CALL_STATE_OFFHOOK :
callStateString = " OFFHOOK" ;
break;
case Telepho nyManager .CALL_STATE_RINGING :
callStateString = " RI NGI NG" ;
break ;

Gs mCel l Locationcell Location-


(Gs mCe l l Lo c a t i o n) t e lM g r . g etCe llLo c a t i o n ( ) ;
String cel lLoca tionStri ng =
ce ll Location . g e tLa c () + " " + ce l lLoca tion. g e t Ci d ( ) ;

St ri ng d e vi c eId = telMgr . g et De v i c e Id () ;
S tr ing d e vi c e So f tl1a r e Ve r s ion =
t elMg r . g e tD e v i c e So ftHa r e Ver s i on ( ) ;

S tri ng l i n el Nu mbe r = t e lMg r . g e t Li nel Number () ;

S tri n g n e t wo rkCount r yI s o - t e lMg r . g e tNe t Ho r kCoun t r y I s o () ;

Stri ng n e t wo r kOp e ra t o r = t elMgr . g etNe two rkOp era t o r ( ) ;


Str i ng n e t Ho rkOp era t orName = t elMgr . getNe t workOpera tor Name ( ) ;

i nt phoneType = telMgr . getPhoneType ( ) ;


String phoneTypeStri ng = " NA" ;
sw i t ch (phoneType ) {
case TelephonyManager.PHONE_TYPE_GSM:
phoneTypeString = " GSM";
break ;
case Te l e p h o n y Ma n a g e r . PHONE_ TYPE_NONE :
p honeTypeSt r ing = " NONE";
break ;

S tr i ng s i mCou n t ryIso = telMg r . g etS imCo u n tr y I s o ( ) ;


S tr i ng s i mOpera to r = telMgr . ge t SimOp era t or ( ) ;
S tring simOperatorName = te l Mg r . g etSimOpera t o rN ame () ;
St r i ng s imSerial Nu mber = telMgr . g et S im Se r i a lNumbe r () ;
S t r i ng s i mSu bsc r i berI d = te l Mg r . g et Sub s c r ibe r I d () ;
int s i mState = te l Mgr . getS i mSt a t e () ;
S t ri ng simSta teStr i ng = "NA" ;
switch (s i mS t a t el {
case Tel e ph o n yMa n a g e r . S I M_ STATE_ ABSENT :
s i mStateString = " ABSENT" ;
bre ak ;
case Te lephonyManager .SIM_S TATE NETWORK LOCKED:
simS tateStri ng = " NETWORK_LOCKED" ;
break ;
/ / . . . se omi ten otros estados S I M
)
ea 7. Telefonía

StringBui lder sb = riew StringBui lder () ;


sb . a ppe n d (" te lMgr - ") ;
s b. a p pend (" \nca llState = " + ca llSta teSt ri ng ) ;

/ / . .. se ami ten el resto de e lementos añadi dos

r e tu rn sb. toStri ng () ;

Figura 7.2. Metainformacián de red sobre el dispositivo


y el teléfono obtenida de la clase TelephonyManager.

Se utiliza Context de Android, a través del método getSystemService con una


constante, para obtener una instancia de la clase TelephonyManager. Tras ello, se
utiliza para obtener la información necesaria. En este caso hemos creado un método de
ayuda para obtener datos del administrador y devolverlos como cadena para mostrarla
posteriormente en la pantalla.
El administrador le permite acceder a datos de estado del teléfono, como por ejem-
plo si hayo no una llamada en curso, información de ubicación, el ID del dispositivo y
la versión del software, el número de teléfono registrado en el usuario o SIM actuales, y
otros muchos detalles SIM como el ID del suscriptor (IMSI). Existen otras propiedades
adicionales que no utilizamos en este ejemplo (consulte la documentación).
Fíjese en otro detalle no incluido en el listado. Para que la clase funcione, es necesario
establecer el pe rmiso READ_ PHONE_ STATE en el manifiesto (sin su presencia, se generan
excepciones de seguridad al intentar leer datos desde el administrador). En un apartado
posterior veremos los permisos relacionados con la telefonía.
Este control a la información telefónica, incluidos metadatos sobre el dispositivo, red
y tarjeta SIM, es uno de los principales objetos de la clase Telephon yManager. El otro
es permitir que se añada un PhoneStateLi stener.
Android. Guía para desarrolladores ea
Obtener información de estado del teléfono
Evidentemente, un teléfono muestra diferentes estados. Los más habituales son en
reposo, en una llamada o en proceso de iniciar una llamada. Al crear aplicaciones para
un dispositivo móvil, en ocasiones no sólo debe conocer el estado actual del teléfono sino
también saber cuándo cambia. Para estos casos puede adjuntar un oyente al teléfono y
suscribirse para recibir notificaciones de los cambios publicados. En Android, se utiliza
PhoneStateListener, que se añade al teléfono a través de TelephonyManager. El
listado 7.2 muestra un ejemplo de uso de ambas clases.
Listado 7.2. Añadir un PhoneStateListener a través de TelephonyManager.
@Overr ide
pub lic vo id onStar t ()
s upe r .onSta r t (} ;

fi na l Telephon yManager te l Mgr =


(Telephon yManager)
t his.ge tSys t e mService (
Con t ex t.TELEPHONY_S ERV ICE ) ;

Phon eSt a t eLi s t enerphon e StateLi stene r=


new Phon e StateLi s t ener () {
pu bli c v oid on Call State Changed(
int stat e , St r i ng i ncomi ng Number ) (
telMg rOu t pu t .s e t Te x t (ge t Te lephonyOve rvie w(te l Mgr » ;

};
t e lMg r . l is te n (p ho neSta te Lis te ne r,
Phon e Sta t eListener .LI STEN_CALL_ STATE);

St r i ng t ele ph o n yOverv i e¡.¡= thi s . getTel epho nyOverv ie w(t elMg r) ;


thi s .telMgrOutput. s etTe x t( t el e p ho n yOv e r vie w) ;

Para comenzar a trabajar con PhoneStateListener, necesita una instancia de


TelephonyManager para poder asignar posteriormente el oyente. PhoneSta teListener
es una interfaz, de modo que debe crear una implementación, con el método obligato-
rio onCallStateChanged, para poder utilizarla. Una vez obtenida la instancia de
PhoneStateListener (una implementación propia de la interfaz), se asigna al admi-
nistrador por medio del método listen.
En el ejemplo del listado 7.2, escuchamos todos los cambios PhoneS ta teLi s t ener.
LISTEN- CALL- STATE del estado del teléfono. Es un valor constante de una lista de es-
tados disponibles que puede ver en la clase PhoneStateListener. Puede utilizarlo
como valor al asignar un oyente al método listen, como hemos hecho aquí, o puede
combinar varios valores. Si se produce un cambio de estado, restablecemos los detalles
en pantalla con el método getTelephonyOverview que utilizamos inicialmente para
establecer el estado inicial en el listado 7.1. La acción realizada se define en el método
onCallSta teChanged de PhoneStateListener. Puede filtrar este método (además
de los tipos de eventos que escuche) en función del int state pasado.
&ni 7. Telefonía

Para ver cómo cambian los valores del ejemplo mientras trabaja con el emulador,
puede utilizar las herramientas del SDK para enviar llamadas entrantes o mensajes de
texto y cambiar el estado de la conexión de voz. El emulador incluye un simulador de
módem GSM que puede manipular con el comando g s m desde la consola. La figura 7.3
muestra una sesión de ejemplo. Si necesita más información al respecto, consulte la do -
cumentación que encontrará en (http:/ /code.googl e. c om/andro id/reference /
e mu lato r. h t ml- t e l e p h o n y ).

ccollins@crotalus:/opt/android/tools$ telnet localhost 5554


Trying 127.0 .0.1 . . •
Connected to localhost.
Escape character is 'AJ'.
Android Console: type 'help' for a list of commands
OK
gsm
allows you to change GS~I-related settings , or to make a new inbound phone call
available sub-commands:
list list current phone calls
can create inbound phone call
busy close waiting outbound call as busy
hold change the state of an oubtound call to 'held'
accept change the state of an outbound call to 'active'
cancel disconnect an inbound or outbound phone call
data modify data connection state
voice modify voice connection state
status display GSM status

Figura 7.3. Sesión de consola de Android que ilustra


el comando gsm y los subcomandos disponibles.

Una vez completados muchos de los principales detalles, a continuación analizaremos


usos básicos de las API de telefonía. Examinaremos la forma de interceptar llamadas, el
uso de clases de utilidad de telefonía y cómo realizar llamadas desde sus aplicaciones.

Interactuar con el teléfono


En el desarrollo diario, uno de los objetivos será interactuar con el teléfono. Esta inte-
racción puede ser tan simple como marcar a través de inient incorporados o interceptar
llamadas para modificarlas de alguna forma. En este apartado analizaremos estas tareas
básicas y examinaremos algunas de las utilidades de números de teléfono que propor-
ciona Android. Una de las operaciones más habituales no implica directamente las API
de telefonía: realizar llamadas a través de los inient incorporados.

Utilizar Intent para realizar llamadas


Como vimos en un capítulo anterior, basta con utilizar la acción Intent. ACTION_
CALL y tel : Uri para invocar la aplicación incorporada de marcado y realizar una
llamada. Este enfoque invoca la aplicación de marcado, la completa con el número de
teléfono proporcionado (obtenido de Uri) e inicia la llamada.
Android. Guía para desarrolladores efI
Junto con esta acción, también puede invocar la aplicación con la acción Intent.
ACTION_ DIAL,que de nuevo completa la aplicación con el número proporcionado pero
se detiene después de iniciar la llamada. El listado 7.3 muestra ambas técnicas.
Listado 7.3. Uso de acciones Intent para marcar y llamar
con la aplicación de marcado incorporada.

dialintent = (Bu t t o n ) findView By ld (R . id . d i ali n t e n t _ bu tton) ;


dia l i n te n t. se tO nC l ic k Lis te ner (n e w OnClickLi st e ne r ( ) {
p ub lic void o nC lic k (Vi e w v ) (
I n t en t i nt e nt =
n e w In t en t ( I n t e n t. DI AL_ ACTI ON,
Uri. p a r se (" t e l: " + NUMBER ) ) ;
s ta r tAct ivity ( inte nt ) ;
)
}) ;

ca ll i n te n t ~ (Bu t t o n) find Vi e \·¡By ld (R. i d . c a ll i nte n t_bu t t on ) ;


c a llint ent. setOnC l ic k Liste ne r (n e w OnC l ic k Lis t e ner ( ) {
p ublic vo i d onC li c k (Vi e w v ) {
In t ent i n t e nt =
n ew l ntent (In t ent . CALL_ ACTION ,
Ur i . p a r s e ( "te l : " + NUMBER) );
sta r t Act iv i t y ( i n te n t ) ;
}
}) ;

Llegado a este punto, ya hemos visto el uso de I n tent y el diseño de la plataforma


Android . En el listado 7.3 volvemos a utilizar este diseño para realizar llamadas a nú-
meros concretos.
La realización de llamadas a través de los intent incorporados es muy sencilla, como
hemos visto en ejemplos anteriores. Básicamente necesita definir la acción que realizar,
ya sea completando el marcador con ACTION_ DIALo completándolo e iniciando la lla-
mada con ACTI ON_ CALL. En cualquier caso, debe especificar el número de teléfono que
utilizar por medio de Intent Uri.
Otro aspecto relación con las llamadas que tener en cuenta son los permisos. El mani-
fiesto de la aplicación debe incluir los permisos correctos para poder acceder y modificar
el estado del teléfono o interceptar llamadas (como veremos en un apa rtado posterior).
La tabla 7.1 muestra los principales permisos relacionados con el teléfono y su función
(si necesita más información al respecto, consulte la documentación de Android, en
http:/ /c ode . googl e . com/a nd ro i d / deve l / securi t y. html).
Tabla 7.1. Permisos del manifiesto relacionados con el teléfono y su func ión.

l;unciOñ

and roid . permiss io n . READ_ Permite a la aplicación leer el estado del teléfono .
PHON E STATE

android . permiss io n. MODIFY_ Permite a la aplicación modificar el estado del teléfono.


PHONE STATE
ea 7. Telefonía

l~n-~Íi r~(i) [¡tl[j,,fr·tJ


" .
a ndroid .permissio n. Inicia una llamada de teléfono sin la confirmación del
CALL PHONE usuario .
and roid . pe r mission .C ALL_ Llama cualquier número , incluido el de emergencias, sin
PRIV I LEGED confirmación en el marcador.
and roid.permission . Permite a la aplicación recibir difusión de las llamadas
PROC ESS OUTGO I NG CALLS salientes y modificar.

El marcado desde una aplicación de Android es muy sencillo. El procesamiento in-


corporado a través de In t ent y la aplicación de marcado hace que resulte prácticamente
trivial. Puede utilizar la clase PhoneNumberUtils para analizar y validar cadenas de
número de teléfono.

Utilidades relacionadas con números de teléfono


Las aplicaciones ejecutadas en dispositivos mó viles que admiten telefonía disponen
de numerosas cadenas de formato para números de teléfono. Afortunadamente, el SDK
de Android incluye una clase de utilidad para mitigar los riesgos asociados a esta tarea
y estandarizar el proceso: PhoneNumberUtils.
La clase PhoneNumb erUtil s se puede utilizar para analizar datos String en nú-
meros de teléfono, analizar dígitos alfabéticos del teclado en números y determinar
otras propiedades de dichos números (como si son globales o localizados). El listado 7.4
muestra un ejemplo.

Listado 7.4. Trabajar con la clase PhoneNumberUtils.

pr i vate Te xt Vi e l'/ pnOutput ;


pri vate EditText pn lnput;
pri va t e Edi tTe x t pn l nPl a celnput ;
pri va te Bu t t on p nFo r mat ;

this.pnFormat. s etOnCli c kListe ner(newOnCli ckListener ()


p ub lic void o nCli c k (Vi e wv ) (
Str ing phoneNumbe r = PhoneNumbe rUti l s. for ma tN umber (
pn lnpu t.ge t Text ( ) . t oS t r i ng();
p honeNumber = PhoneNumberUt i ls . conve rtKeypad Letters To Digits (
pn lnpu t.getText ( ) . t oSt ring()) ;

St ringBu i lder res u lt = new StringBu ilder () ;


r es u lt .append (phoneNumbe r ) ;
res ult . ap pe nd (" \n i s Gl ob al - "
+Phon e Numbe r Ut il s. i sGl ob alPhon eNumbe r (p ho ne Numbe r)) ;
Android. Guia para desarrolladores lID
re su lt .append ( " \ni sEmergen cy - "
+Pho neNumb erU t i ls . isEmerge n cyNumber (ph oneNumbe r ) ) ;

p nOutput .setText (res u lt .to St ring ( ) ) ;


p n I np ut .setText ( " " );
}
)) ;

La clase PhoneNumberUtils dispone de diversos métodos estáticos de ayuda para


analizar números de teléfono y el más sencillo de todos es f o rmatNumber. Este méto-
do adopta una cadena como entrada y utiliza la configuración regional para devolver
un número de teléfono con formato (existen otros métodos para aplicar formatos a un
número con la configuración regional especificada, para analizar distintos segmentos
de un número, etc.). El análisis de un número se puede combinar con otro método muy
útil, co nvert Keypa dLette r s ToDi gi ts, para convertir las letras del teclado alfabético
en número. El método de conversión no funciona a menos que ya reconozca el formato
de un número de teléfono, de modo que en este caso es importante ejecutar primero el
método de formato.
Junto a estos métodos básicos, también puede comprobar propiedades de una cade-
na numérica, por ejemplo si el número es global y si representa una llamada de emer-
gencia.
Una forma ad icional de aplicar formato a un número de teléfono para cualquier ele-
mento Editable, como Edi tTe xt (o Te xtVi ew), esla sobrecarga f orma t Numbe r. Este
método actualiza el Edi tText pasado al invocarse. El listado 7.5 muestra un ejemplo.

Listado 7.5. Utilizar formato Editable View a través de PhoneNumberUtils.

thi s.pnInPla ce Input.setOnFo cu sChangeListe ne r (


new OnFo c u s Ch a n ge Lis t e ne r () {
publi c vo id onFocusChange (V.iew v , b o ol ean b) {
if (v , equals (pn I nPlaceInput) && (b == fa l se) )
Ph oneNumbe rUtil s.formatNumber(
pnInPlace Input .ge tText( ) ,
PhoneNumberUt ils .FORMAT_NANP) ;

}
}) ;

El editor se puede combinar con una actualización dinámica con distintas técnicas ;
una consiste en realizar automáticamente la actualización cuando se aleje el enfoque de
un campo del número de teléfono (curiosamente, la edición no proporciona automáti-
camente la conversión de carácter alfabético a número). Para ello, hemos implementa-
do un OnFocu sCha nge Li stene r. Dentro del método on FocusCha ng e, que filtra el
elemento Vi ew adecuado, invocamos la sobrecarga f orma tNumber, en la que pasamos
el correspondiente elemento Edi t a ble y el estilo de formato que utilizar. En este caso,
NANP equivale al plan de numeración estadounidense, que incluye un prefijo opcional
y un número telefónico de siete dígitos.
Además de las utilidades de números de teléfono y la realización de llamadas, puede
que también necesite interceptarlas.
&iI 7. Telefonía

Interceptar llamadas
Existen muchos motivos para interceptar llamadas. Por ejemplo, puede qu e necesite
crea r una aplicación que detecte llamadas entrantes y cambie el tono de llamada o utili-
ce otras alertas en función del qu e llame. O puede crea r una aplic ación que capture las
salientes y las cancele en función de determinados criterios.
La intercepción de llamadas salientes se admite en la versión actual del SDK de
Android, pero no sucede lo mismo con las entrantes. Los usuarios pueden cambiar el tono
de llamada y ot ras opciones de sus contactos pero siempre de acuerdo a las aplicaciones
incorporadas y no es una opción disponible como desarrollador por medio de las APL
Debido a las limitaciones del API, no s centraremos en la intercepción de una llamada
saliente, véase el listado 7.6.
Listado 7.6. Captura y cancelación de una llamada saliente.
pub l i c c las s OutgoingCa l lRecei ver ex t e nd s BroadcastReceiver {
public s t a t i c final String ABORT_ PHONE_NUMBER = "1 2 312312 34" ;
pr i vate stati c f ina l String OUTGOING_CALL_ACTION =
"a nd r o i d. i n t en t. a c t i on . NEW_ OUTGOI NG_ CALL";
private sta ti c f ina l String I NTENT_PHONE_NUMBER =
"a nd r o i d. i n t e n t . e xtra .PHONE_ NUMBER";
@Override
pub lic void onRe ceive (Co ntext c o n t e x t , Intent i n t e nt)
if (i n t e nt. ge t Ac t i o n( ) . e qua l s (
OutgoingCal lReceiver.OUTGO ING_CALL_ACTION ) )
St r i ng phoneNumber =
intent .getExtras () . ge t St r i ng (I NTENT_ PHONE_ NUMBER) ;
i f ( (phoneNumber ! = null)
&&pho ne Numbe r . e q ua ls (
Ou t g oi ngCa l l Re c e i ve r . ABORT_ PHONE_ NUMBER) )
Toa s t .ma ke Te x t( c o nt e x t ,
"NEW_ OUTGOING_ CALL i nte r c e p t e d to number "
+ " 123-123-1 23 4 - aborting c a l l " ,

Toa st .LENGTH_LONG ) . s how( ) ;


t his.abo rtBroadcast () ;

Lo primero que hacemos para interceptar una llamada saliente es ampliar


Broadc a s t Re c eive r. El receptor define varias constantes, una de ellas para la acción
NEW_OUTGO I NG_ CALL y otra para los datos del número de teléfono, PHONE_ NUMBE R.
Para Br oadc a stRe c eiver es necesario implementar el método onRece i ve. En
este método filtramos la acción Inte n t deseada, an dr oid. inte nt . ac ti on. NEW_
OUTGO I NG_CALL, para después obtener los datos In t ent con la clave del número de
teléfono. Si el número coincide, enviamos una alerta Toa st a la IV y cancelamos la lla-
mada saliente mediante la invocación del método a bo r tB ro a dc a s t.
Android. Guia para desarrolladores mil
Además de marcar, aplicar formato a números e interceptar llamadas, otro aspecto im-
portante de la compatibilidad con telefonía de Android es el envío y recepción de SMS.

Trabajar con mensajes: SMS


SMS es un importante medio de comunicación para los dispositivos móviles. Se uti-
liza para enviar sencillos mensajes de texto y pequeñas cantidades de datos. Android
incluye una aplicación SMS que permite a los usuarios ver mensajes SMS recibidos
y env iarlos (incluida la respuesta a los recibidos). Junto con esta compatibilidad y el
ContentProv ider relacionado para interactuar con el sistema incorporado, el SDK
proporciona API para que los programadores puedan enviar y recibir mensaj es me-
diante programación. Para explorar esta compatibilidad, analizaremos ambas caras de
la moneda, el envío y la recepción. La sencilla pantalla de la siguiente imagen muestra
la actividad relacionada con SMS que crearemos en la aplicación TelephonyManager
(véase la figura 7.4). Pa ra comenzar, enviaremos mensajes SMS mediante la compatibi-
lidad proporcionada por SmsManager.

Figura 7.4. Actividad que envía mensajes SMS


y ejemplo de alerta de recepción de mensajes ...

Enviar mensajes SMS


El subpaquete a nd ro i d . te lep ho ny . gsm contiene las clases SmsManager y
SmsMessage. SmsManage r se utiliza para definir muchas constantes relacionadas con
SMS y contiene los métodos sendDa taMess age, se ndMult ipa rtTextMessage y
sendTextMes sage.
7. Telefon ía

El listado 7.7 muestra un ejemplo de la aplicación Tel ephon yMan ager qu e utiliza
el administrador 5M5 para enviar un sencillo mensaje de texto.

Listado 7.7. Uso de SmsManager para enviar mensajes SMS.

/ / .. . se omi te e l inicio de l a c lase

private Bu t ton smsSe nd;


pri va te SmsManager smsManager;

@Override
pub l ic vo id onCreate (Bundl e i c i cle)

super .onCreate (icicle );


this .setContentView (R . layout .smsexamp le ) ;

/ / . .. se omi te la amp l iación de otros e lemen tos de l a vista onCreate

th is. smsSend = (Butto n) f nd v ewüy I d (R. id . s mssend_butto n ) ;


í í

t his . smsManager = SmsManage r . getDefau l t () ;

finalPendinglntentsent l nte nt=


Pend ing lntent.getActivity (
this, O, new Intent (th is,
SmsSendChec k .c l ass ), O);

this .smsSend.setOnCl ickListener (n el1 0 nCl i c kLi s t e ner ( )


p ublic void onC lick (Vi e wv ) (
String dest = s ms l np ut Des t.getTex t () . toS tri ng ();
i f (Pho ne Numbe r Ut il s .
isWeIIFormedSmsAddress(dest » (
smsManager .sendTextMessage (
sms lnput Dest .getText ( ) .toSt ring , null,
s msl nputTex t .getText ( ) . t oString(),
sentl nten t , null) ;
Toa s t. ma ke Te xt( SmsE xa mpl e . t hi s ,
" 5M5 message se n t ",
Toa s t. LENGTH_LONG) .show () ;
) e lse (
Toast .makeText (SmsExamp le .th is,
" SMS desti na tio n i nva l i d - try agai n " ,
Toas t. LENGTH_ LONG ) . s ho l1() ;

)
)) ;

Lo primero que necesitamos para trabajar con mensajes 5M5 es obtener una ins-
tancia de SmsManage r , por medio del método estático getDefaul t. Posteriormente
utilizaremos el administrador para enviar el mensaje. Pero antes, es necesario crear
Pendi nglntent (que utilizaremos como parámetro en el método de envío).
Android. Guía para desarrolladores mi
.".~-~~~ .'-0,
~ ·~ ~.U.nt).l~~~~·~~~~~.rit?~~~:f:~:, :~;~:-j::-
~_. : -: ~· . -
Pendinglntent es una especificación de un Intent futuro . Básicamente es una forma
de pasar un I nte nt futuro a otra aplicación y permitir que ejecute ese 1 nt en t como si
tuviera los mismos permisos que su aplicación, independientemente de que siga activa
al invocar el Intent. Recuerde el ciclo vital de las actividades y la lógica de procesos
independientes que utiliza la plataforma. Pendi ng 1 nt en t permite que las aplicaciones
funcionen más allá con un determinado I nt en t. Incluso tras cancelar la aplicación
propietaria de Pen din glnte nt, és te se puede seguir ejecutando después.

Pendingln t en t puede especificar el elemento Activi t y, Br oadca st o Se r v ice


que necesite. En nuestro caso, utilizamos el método getActivity, que denota una ac-
tividad, y después el contexto, código de solicitud (que no se utiliza), Intent e indica-
dores adicionales. Indican si es necesario crear una nueva instancia de la actividad (o
Bro a dc a s t o Serv ice) a la que se hace referencia en caso de que no exista.
Una vez obtenido Pe ndi ng l n te nt, comprobamos que la dirección de destino sea
válida p ara SMS (con otro método de Ph on eNumbe rUtil s) y enviamos el mensaje por
medio del método se nt Te x t Me s s a ge del administrador.
Este método de en vío acepta varios parámetros, uno de los cuales puede resultar
confuso. La firma de este método es la siguiente:
se ndDataMessage (St ri ng de st i n a t i on Ad d r e s s, Stri ng s c Address , s hort
des tinat ionPort , b yt e[] data , Pending Intentse n t I nte n t , Pe nd ing I n t en t
de l ive ry I nte nt )

de stinati onAddr e s s es el número de teléfono al que enviar el mensaje. El pa rá-


metro scAddre s s es el complicado. No es la dirección de origen, sino que indica la di-
rección del centro de serv icios internos en la red, que en la mayoría de los casos se deja
en null (para utilizar el valor predeterminado). de s tin a ti onPo rt es el puerto; dat a
es la carga del mensaje y, por último, se n t l n t en t y de l i ver ylntent son instancias
Pendi nglntent independientes que se ejecutan cuando se envía y se recibe satisfac-
toriamente el mensaje.
Al igual que los permisos para el teléfono, las tareas relacionadas con SMS también
requieren permisos, véase la tabla 7.2.
Tabla 7.2. Permisos del manifiesto relacionados con SMS y sus funciones.

android . permis sion .RECE IVE_SMS Permite que la aplicación controle los mensajes
SMS entrantes .
a ndr oid .permi s s i on.READ_SMS Permite que la aplicación lea mensajes SMS.
android .permi s s i on.SEND_SMS Permite que la aplicación envíe mensajes SMS.
android . pe r missi on . WRI TE_SMS Escribe mensajes SMS en el proveedor SMS
incorporado (no está relacionado directamente
con el envío de mensajes).
mi 7. Telefonía

Además de en viar mensajes de texto y datos mediante este modelo bási co, puede
crear un Br oadcastRe c ei v er relacionado con SMS para recib ir mensajes 5MS en-
trantes.

Recibir mensajes SMS


La recepción de un mensaje 5M5 mediante programación se realiza a través de una di-
fusión en la plataforma Android. Para ilustrarlo en nuestra ap licación TelephonyExplorer,
volveremos a implementar un receptor, véase el listado 7.8.

Listado 7.8. Creación de un BroadcastReceiver relacionado con SMS.

pub lic class SmsReceiver e x te n d s Br o a d c a s t Re c e i v e r (


p ub lic static fina l String SMSRECEIV ED = " SMS R" ;
private static fina l String SMS_REC_ACTION =
"android .provider. Te lephony. SMS_RECEIVED" ;

@Overr ide
pub lic void onRecei ve ( f Co n t e x t context , Intent intent )

if (intent .getAction () .
equals (SmsReceiver.SMS REC_ACTION ) )
StringBui lder sb = new Stri ngBuilder () ;

Bundle bund le = intent . get Extras () ;


i f (b undl e ! = null ) (
Obj ect [] pdus = (Obj ec t [ ] ) bund le . get ( "pdus " ) ;
for (Object pdu : pdus ) (
SmsMessage smsMessage =
SmsMessage .createFromPdu ( (byte[ ]) pdu );
s b . append (" b o d y - "
+smsMes sage .getDisplayMessageBody ( »;

Toast.makeText (context, " SMS RECEIVED - "


+ sb. toString () , Toast . LENGTH_LONG ) . show ( ) ;

Para reaccionar a un mensaje 5M5 entrante, volvem os a crea r Broa d ca stRe c e i v e r


ampliando la clase. Nuestro receptor define una constante local para la acción Intent
que desea capturar, en este caso andro i d. p rov i de r . Tel ephon y. SMS_ RECEI VED.
Una vez configurada la clase, filtramos la acción deseada en el método onRece i v e
y obtenemos los datos 5M5 del Intent Bundl e con la clave p du s . PDU (Unidad de
Datos de Protocolo) es el término que describe el paquete de datos en mensajes 5M5.
En este caso, la plataforma utiliza la clave pdus (lo hemos descubierto por ensayo y
error, al obtener el conjunto de claves del Bundle e iterando por el mismo). Por cada
objeto pdu construimos un Sms Me ssage convirtiendo los datos en una matriz de bytes.
Android. Guia para desarrolladores 1m
Seguidamente podemos trabajar con los métodos de esa clase, como por ejemplo ge -
t Dis p layMessageBody. Con el en vío y recepción de mensajes SMS completamos la
descripci ón de las API de telefonía.

Resumen
En nuestro viaje po r las API de telefonía de Android hemo s abordado temas impor-
tantes. Comenzamos con un a bre ve descripción de d iversos términos y de las API es-
pecíficas de Android.
Con las API v im os cómo acceder a información d e telefonía por medio de
Te l eph onyManage r, incluidos datos del dispositivo y la tarjeta SIM, y del estado del
teléfono. También vimos cómo conectamos a Ph one Sta t eL is t en er para obtener ac-
tualizaciones al cambiar el estado del teléfono y reaccionar a dichos eventos.
Además de recuperar datos, hemos aprendido a marcar con las acciones e Int e n t
incorporados, a interceptar llamadas salientes y a utilizar la clase Ph oneNumbe rUtil s
de distintas formas. Tras ver los usos de voz estándar, nos centramos en los mensa-
jes SMS. Enviamos y recibimos mensajes SMS con ayuda de las clases SmsManag er y
SmsMe ssage .
En el siguiente capítulo nos centraremos en las notificaciones y alertas de la plata-
forma Android.
8
Notificaciones
y alarmas
En la actualidad, se espera que los teléfonos móviles no sólo sean teléfonos, sino tam-
bién asistentes personales, cámaras, reproductores de música y vídeo, clientes de men-
sajería instantánea y prácticamente todo lo demás que puede hacer un ordenador. Con
todas estas aplicaciones en un teléfono, se necesita un sistema de notificación para el
usuario, para captar su atención o realizar una acción en respuesta a un SMS, un nuevo
mensaje de voz o una alarma que recuerde una cita próxima.
En este capítulo veremos cómo utilizar BroadcastReceiver y AlarmManager de
Android para notificar este tipo de eventos a los usuarios. Veremos qué es Toast,
Notifi cation, cómo utilizar NotificationManager y cómo mostrar una notificación
al usuario o desencadenar otra acción . También aprenderemos a crear una alarma y a
utilizar AlarrnManager para programar eventos Al arm. Antes de adentramos en el fun-
cionamiento de las notificaciones, crearemos una sencilla aplicación de ejemplo.

Presentación de Toast
En nuestro ejemplo crearemos una sencilla clase Receive r que escucha un mensaje
de texto SMS y, cuando llega, muestra brevemente al usuario un mensaje emergente, de-
nominado Toa st, que contiene el contenido del mensaje. Toa s t es un sencillo mensaje
no persistente diseñado para indicar al usuario que se ha producido un evento. Resultan
perfectos para avisar al usuario de una llamada entrante, la recepción de un SMS o un
correo electrónico, o de cualquier otro evento.
Para ver las aplicaciones de Toast, crearemos un sencillo ejemplo. En primer lugar
debe crear un nuevo proyecto con el nombre SMSNotifyExample en Eclipse. Puede
utilizar el nombre de paquete que desee, pero en este ejemplo utilizaremos c om. ms i .
l1mI 8. Notificaciones y alarmas

ma nning. c h a p te r8 . Una vez creado el proyecto, modificaremos AndroidMa ni fe s t .


xml. Tendrá que añ adir etiquetas para que su archivo se parezca al siguiente (véase el
listado 8.1).
Listado 8.1. AndroidManifest.xml para SMSNotifyExample.

<?xml v e r s i o n= "1. O" encoding= "u tf -8 "?>


<mani fest xml ns :andro i d=" ht tp://sc he mas . andro id .com/ap k/res/android "
pac ka ge= "com. msi . manni ng .cha pter8 ">
<uses - pe r miss ion a ndro i d : na me= "a nd roid.pe r miss io n . RECEIVE_ SMS " />
<a pp l icat ion a nd ro i d : i c o n ~"@ dr a \'lab l e / c h at " >
<activ i t ya nd ro i d : name= " .SMSNot ifyActivit y "
an d ro i d : l a be l= " @string/app _ na me">
<i nten t -fi l te r>
<act ion android : na me= "a nd r oid . i nten t. ac tion . MA I N" />
<ca tego ry a nd ro i d: name ="android. i ntent .category. LAUNC HER" />
</ i ntent - fi l ter>
</ ac tiv ity>
<re ce ive r a nd r o id : n ame > SMSNot ifyExa mp le ">
? •

<i nt e nt - f i l t e r>
<act i on a nd r oid : na me =" a nd ro i d . p r ovider . Te l e ph o ny . SMS_ RECEIV ED" />
</i n t e nt -fi l t e r>
</ rece iver>
</app l ica t io n>
</ man ifest>

El archivo Andro idManifest. xml necesita permisos de usuario específicos para


permitir mensajes 5M5 entrantes. El modelo de seguridad predeterminado de Android
no asocia permisos a las aplicaciones, de modo que éstas no pueden hacer nada para
dañar al dispositivo ni a sus datos. Para proporcionar permisos de Android debe utili-
zar uno o varios permisos. En un capítulo posterior encontrará más información sobre
el modelo de seguridad de Android.
En la siguiente parte del archivo Andr oidManif est. x ml definimos
SMSNot ifyActi v i ty, nuestra actividad, y la clase SMSNotifyExample, que ac-
tuará como receptor. Tras ello, creamos una sencilla clase Act ivit y con el nombre
SMSNotifyActi vi t y (véase el listado 8.2).
Listado 8.2. Actividad SMS para la clase SMSNotifyExample.

p ub lic class SMSNoti fyExa mp leAc tiv ity e xte nds Act iv i t y {

@Ove r r i de
p ubl i c vo i d o nCrea t e (Bun d l e icic l e )
s up e r. o nCre a t e (ic i cle ) ;
se tC o n t entVie\'l (R . l a yo u t .ma in ) ;

Como puede apreciar, el listado es muy sencillo ya que prácticamente no haremos


nada con la actividad del primer ejemplo. Más adelante ampliaremos esta clase. Por el
momento, creamos la clase Receiver, que escucha el mensaje 5M5 y desencadena un
evento. El listado 8.3 muestra el código de nuestra clase SMSNotifyExample.
Android. Guia para desarrolladores mi
Listado 8.3. Sencillo IntentReceiver SMS o

p ubli c c lass SMSNotifyExamp le extends BroadcastRecei v e r {

private static final String LOG_TAG = " SMS Re c e i v e r";

pub l i c s t a tic fina l int NOTIF ICATION_ ID _ RECEIVED = Ox122l ;

sta tic final String ACTION = "android.provider . Telephony . SMS_ RECE IVED ";

p ublic void onRece iveInten t (Co n t e x t context, Intent in tent ) {

if (intent .getActio n ( ) .equals(SMSNotifyExample .ACTION ))


Str ingBui lde r sb = n e w StringBuilder () ;

Bundle bundle ~ inte nt. getExtras () ;


i f (bu ndle! = null ) {

Obj ect [J pdusObj = (Ob j ec t [ ] ) bund le. get (" p d u s") ;


SmsMessage [] messages = n e w SmsMessage [ p d u s Ob j . Te n q t.h ] ;

for (Sms Me s s a g e curren tMessage : messages) (


sb . append (" Re c e i v e d SMS\nFrom: ") ;
s b .append (currentMessage .getDisp layOriginatingAddress( )) ;
sb.append ( "\n- - - -Message - - - -\ n ");
sb .append (cu r re n tMessage.getDisplayMessageBody ( ) ) ;

}
Log i (SMSNo ti f y Ex a mp l e. LOG_TAG, "[ SMSAp p] onReceiveIntent: " + sb ) ;
o

To a s t . ma ke Te x t (context , sb . toStri ng () r To a s t LENGTH_ LONG) . show () ;


o

@Override
p ub l ic void o nReceive (Co n t e x t context , Intent i n t e n t)

El listado 8.3 es muy sencillo. Ampliamos la clase SMSN o tif yExample con
Br oadca stRecei v e r, lo que permite a la clase recibir clases Intent oTras ello, crea-
mos una cadena para almacenar la acción que desencadena el sistema al recibir un SMS.
Después creamos un sencillo método para notificar al usuario que se ha recibido un men-
saje SMS y lo analizamos para mostrar quién lo ha en viado y su contenido. Por último,
utilizamos Toa st para proporcionar un rápido mensaje al usuario.
Las clases Toast son sencillos mensajes transitorios que muestran información al usua-
rio sin interrumpir su tarea. En nuestro código, combinamos dos métodos con la forma
makeText (Context context , CharS que nce t ext , i nt dura t i on) . s ho w() , donde
el primero contiene una vista de texto para el usuario y el segundo método, s ho w() , mues-
tra el mensaje. Toa st le permite definir una vista concreta con se tV iew, pero en nuestro
ejemplo mostraremos la predeterminada, la barra de estado de Android,
EmI 8. Notificaciones y alarmas

Después de cortar y pegar el código, lo demás se compila automáticamente y podrá


ejecutar la aplicación (véase la figura 8.1).

Figura 8.1. Sencillo ejemplo de Toast, SMSNotifiyExample, en el emulador.

Para probar la aplicación, seleccione la opción DDMS en Eclipse. En el campo Telephony


Actions (Acciones de telefonía), introduzca un número, por ejemplo 17035551429. Seleccione
SMS, introduzca un mensaje en el campo Message y pulse Send. El mensaje se enviará
al emulador, que debería responder en la consola de Eclipse. Aparecerá un mensaje en la
barra de estado de Android en la parte superior de la pantalla, véase la figura 8.2.
Una vez creado este sencillo ejemplo y después de ver cómo mostrar un breve men-
saje al recibir un SMS y cómo utilizar el emulador para crear un SMS, aprenderemos a
crear un mensaje más duradero que también sirva para configurar LEO, reproducir un
sonido o algo similar, para indicar al usuario que se ha producido un evento.

Introducir notificaciones
En el apartado anterior vimos lo sencillo que resulta crear un mensaje rápido para
indicar al usuario que ha recibido un mensaje SMS. En el siguiente apartado, apren-
deremos a crear una notificación que no sólo aparezca en la barra de estado sino que
también se conserve en una zona de notificación hasta que el usuario la borre. Para ello
utilizaremos la clase Notifi cati on, que nos permite realizar tareas más complejas
que las ofrecidas por Toast.
Android. Guía para desarrolladores mil

~m u la tor· 55S4 Onfine


syslemJ Hoets s 54 '\\
corn.android.pbcne 9.1 '%
android.process.shered 98 "'SA
com.qccqte.prccess.sh 132 'l.
com.msi.manning.cha¡ 166 ~
com.android.mms 172 ~

=Ej

~=:=;
· Isp.e<t ~
L.::."-'---_ _ ·--'I l.,.ncy. ~
Telephcny Actions
70:O-:
Incom;ng number. -::I:O-: 3S:O-:
SS:-::
1S:-::
1S- - - -
f:) Voice:
' ~ ' SM S

Helio Android how at e:


)'ou1

Figura 8.2. Ejemplo de mensaje Toast generado a partir de un mensaje SMS.

En Android, una notificación puede adoptar diversas formas, desde un mensaje


eme rgente hasta un LED que parpadea o una vibración, pero todas ellas comienzan y se
representan por medio de la clase Notifi c ati on. Esta clase define cómo representar
la notificación al usuario y tiene tres con structores, un método público y varios campos.
La tabla 8.1 muestra la clase.
Tabla 8.1. Campos Notification.

Público in t l e dARGB Color del LEO de notificación.


Público in t l e d OffMS Milisegundos que permanece
apagado el LEO entre destellos.
Público i nt l e d OnMS Milisegundos que permanece en-
cendido el LEO entre destellos.
Público ContentURI soun d Sonido para reproducir.
Público Remo teViews contentView Vista que mostrar al seleccionar
statusBar lcon en la barra de
estado .
Público CharSequence statusBarBa l loon Te xt Texto que mostrar al seleccionar
statusBar lcon en la barra de
estado.
llfI 8. Notificaciones y alarmas

Público Pe ndinglntent con te n t l n t en t Intent que ejecutar al hacer clic


en el icono .
Público i nt ico n ID de recurso del elemento que
utilizar como icono en la barra de
estado.
Público Cha r Se que nce ti ckerText Texto que se desplaza por la ven-
tana al añadir este elemento a la
barra de estado.
Público l on g[] vib rate Patrón de vibración.

Como puede apreciar, la clase Notificat ion cuenta con varios campos para des-
cribirlas distintas formas de notificación al usuario. Para utilizar Not i f i ca t i on, basta
con ejecutar este código:
No ti fi c at i on no t i f = ne wNotifica tion (
contex t , / / contexto de l a a p l i c ac ión
i con , / / i c on o de la ba rra de e s tado
t i c ke tTe x t, / / texto que mo s t r a r
whe n, / / mar c a de t iempo de la noti f icació n
Title, / / titu lo de l a no ti f i c a ció n
Te xtBody, //detal lesquemos t rare n lanotificación
con t en t lntent , //conte nt ln te nt
a pp l ntent ); / / i nt ent del a aplic a ción

Para enviar la notificación, introduzca lo siguiente:


nm.no t ify( St r ing, No t i f i c a ti on);

donde nm es la referencia a Not i f i ca t i o nMa n a g e r . A continuación modificaremos


el ejemplo anterior para cambiarlo de notificación Toast a tilla notificación en la barra
de estado. Pero antes, añadiremos iconos al directorio de recursos. En este ejemplo utili-
zaremos los iconos chat. png e i n c omi n g . png. Basta con soltarlos sobre el directorio
res/drawable para que Eclipse los registre automáticamente en la clase R.
En primer lugar editamos la clase SMS No t i f y Ac t i v i t y para que al invocar
Activity, de tecte la notificación pasada desde Notifi cationManager. Una vez
ejecutada la actividad, SMSNotifyActi v i ty puede cancelarla. El listado 8.4 muestra
el código necesario para la nueva clase SMSNotifyActivity.
Listado 8.4. Ejemplo de 8M8NotifyActivity.

p ublic c lass SMSNotifyAc ti vi ty extends Acti v i ty 1

publicvoido nCrea te lBund l eicicle )


s up er .onCreate (ic icle );
setCo ntentView (R. layout . mai n ) ;

Not i fi cationManager nm = (No t i f i c at i onMan a ge r )


Android. Guía para desarrolladores mil
g et SystemService (NOTIFICAT ION_SERVICE ) ;
nm .ca ncel (R .string . app _ name);

Como puede apreciar, únicamente hemos utilizado Notifi c a ti onManage r para


buscar la notificación y después el método c a nce l () para cancelarla. Podríamos rea-
lizar otras operaciones, como por ejemplo definir una vista personalizada, pero es suf i-
cien te con lo que tenemos.
Seguidamente modificamos SMSNoti f yExample para eliminar la notificación Toa s t
y admitir una notificación en la barra de estado. El listado 8.5 muestra los cambios ne-
cesarios.
Listado 8.5. Actualización de SMSNotifyExample.java.
public c lass SMSNotifyExample extends BroadcastRece i ver {

private sta tic fina l String LOG TAG = "SMSRece i ver ";
pub lic s t a ti c final i n t NOT I FICATION I D RECEIVED = Ox122l;
static fi nal St ring ACTION = " a n d r o i d .provider . Te lephony . SMS_ RECEI VED" ;
p rivate CharSequen ce tickerMessage = null;

publ i c void onReceivelntent (Co ntext context , Intent intent )

Notificat ionManager nm = (No t i f i c a t i o nMa n a g e r)


c ont ext .getSystemS ervice (Con text . NOTIFICATION_SERVICE );
if (intent.getAction() .equal s(SMSNoti f yExampl e.ACTION})

StringBu ilder s b = rie w St ringBui lder ( ) ;

Bundle b undl e = i n t e n t . getExtras () ;


i f (bundle !=null ) {
Obj e ct [1 pdusOb j = (Obj ect ( ] ) bundle . get ( "pdus") ;
SmsMe ssage [ ] me ssage s = n e w SmsMes sage (pdusOb j . le n g t h ] ;

for (Sms Me s s a g e c u r r e n t Mess a ge : messages ) {


sb . append ( "Received compres s e d SMS\nFrom : ") ;
sb .appe nd(currentMessage .getDi splayOriginatingAddre ss( )} ;
sb .append( " \n-- --Message--- -\n") ;
sb .append (currentMessage .getDispla yMe ssageB ody( ) );

Log . i (SMSNotifyE xample .LOG_TAG , "( SMSAp p] onReceivelntent : " +sb) ;


abortB roadcast() ;

Intent i = new Intent (conte xt , SMSNotifyActivity . class ) ;


c o n t e x t. s t art Ac t i v i t y ( i ) ;

CharSequence appName = "SMSNot i fyExample ";


th is . tickerMessage = sb . toString ( ) ;
Long theWhe n = Syst em. c urrentT i meMi l l i s () ;

Pend ing ln tent.getBr oadcast ((Context ) appName , O, i, O);


mi 8. Notificaciones y alarmas

No t i f i c a t i o n notif = new Notif i catio n (


R . drawable.incoming ,
th i s.tickerMessage,
theWhe n ) ;

noti f.vibrate=ne ~l1ong [ l ( 1 0 0, 2 5 0, 1 0 0, 500 } ;


nm .not ify{R . string .a lert_me ssage , n o t i f) ;

@Override
p u b lic vo id o nReceive (Co n t e x t co ntext , In t e nt in t en t)

El primer cambio es añadir la llamada a tickerMessage, que almacenará el men-


saje SMS que mostrar en la barra de notificaciones. Añadimos estos campos detrás de
la variable Action:
p r ivate CharSeque nce tic kerMe ssage = n ull;

Tras ello, creamos un Intent Appli ca ti on, que se mostrará al hacer clic en la ban-
deja de entrada de SMS.En este ejemplo no realiza acción alguna pero lo necesitamos para
crear la notificación. Podríamos mostrarlo en un editor o en cualquier otra pantalla.
Una vez configurado el I ntent Application, podemos generar la notificación.
Para que el código resulte más sencillo, hemos añadido comentarios junto a los atributos
de Notification en el listado 8.5:
No t i f i cati o n notif = new No t i f i c a t i on (
R. d r awab Le , i n c omi n g , / / icono de la barra d e estado
tic kerMessage , / / texto q ue most rar
t heWhe n
) ;
nm .no ti f y( R .str in g . a pp_n a me, notif );

En la última línea utilizamos el método notify () de NotificationManager para


enviar la notificación a la aplicación.
Si ejecuta dicha aplicación, abre DDMS y pasa un mensaje SMS como hicimos an-
teriormente, verá que la nueva notificación aparece en la barra de estado. El mensaje
muestra las líneas durante un breve intervalo hasta que se ve la totalidad del mensaje.
Verá un nuevo icono en la barra de estado que indica la presencia de un nuevo mensaje
SMS (véase la figura 8.3).
Tras enviar el mensaje, puede hacer clic en el icono New Messages para acceder a una
barra desplegable. Haga clicen ella y arrástrela hacia la parte inferior de la pantalla. Se abri-
rá una vista predeterminada del buzón de entrada SMS de Android (véase la figura 8.4).
Podría realizar otras muchas tareas con este ejemplo como crear una ID mejorada
o añadir funciones al buzón de entrada. Incluso la aplicación podría reproducir un so-
nido al recibir un mensaje pero hemos analizado todo lo necesario para empezar a tra-
bajar con notificaciones. En el siguiente apartado describiremos un pariente cercano de
Notification,Alarm.
Android. Guía paradesarrolladores

DOMS · ~~&<~ W_
Fiie ( 4it N~ate. ~rdI P~tct Ortl ~~~ Run WindQw

¡ r:l • I\i. 51 ¡ ti I} ¡~ .~ • Q ¡ (:. té ¡ cf ' • ¡ ti •


IiID "";" , ¡:¡ :0; 1- e l~ l li v ~l:I
N,me
Android
• [J ~I~o,¿~ _ Online 09_ r1
S)'Stf.fT\.Proc:ess 52 ~ S6OO/8J01)
conundroiciphone 88 ~ 8001
I[J New messages IIndroid.p'OUsuh.ttd 93 ~ 8601
2 unread messages. corr... ndroicimms 112 ~ 860<
<om.google.Pf'oceu .sh 127 "'!. 860S
com.ms i.nvn ning.ch.l' 161 ~ 860S
<onundroiul"mdoc: 168 '1. 8609
In droid.proctss.mf:d i. 178 ~ 8610
,.._ _,: ; 11" ~

Tol' l'honyS" ""


Vo;cc lhome ·Isp.... ~
Dote lhom< · 1 l..ttency. I Non~ .1
Ttl tp hony Actions -===:- _
1ncoming numbf:r. 70135S1419
O Voice
~ SMS
Mes~e: HelioAndtoid how areyou doing1

tout!on Cont'oo
M. nu. ! GPX ICML

Figura 8.3. Utilización del DDMS de Android para enviar un mensaje SMS a la aplicación .

Figura 8.4. Buzón de entrada SMS desplegado con contentlntent y applntent.


&1 8. Notificaciones y alarmas

Alarmas
En Android, las alarmas le permiten programar la ejecución de la aplicación en el
futuro. Se pueden utilizar para diferentes aplicaciones, como por ejemplo para notificar
al usuario de una cita o para tareas más sofisticadas como iniciar la aplicación, hacer
que busque actualizaciones de software y después se apague. Para funcionar, la alarma
registra un Intent y, en el momento programado, lo emite. Android inicia automática-
mente la aplicación seleccionada incluso aunque el dispositivo esté inactivo.
Android gestiona todas las alarmas de forma similar a NotificationManager,
a través de una clase AlarrnManager, que dispone de cuatro métodos: c a nc e l , set,
setRepeating y setTimeZone (véase la tabla 8.2).
Tabla 8.2. Métodos públicos de AlarmManager.

void cancel (PendingI n ten t in ten t) . Elimina las alarmas en las que coincida
Intent .
voi d s e t (int t ype, l ong t r i g g e r At Ti me , Pendinglntent operat i o n) .
Define una alarma.
void setRepeating (int type, l o n g triggerAtTime, long interval,
PendingIntent opera t ion ) . Define una alarma repetida .
voi d s e tTimeZone (String Time Zone) . Define la zona horaria de Alarm.

Las instancias de AlarmManager se crean de forma indirecta, como sucede con


NotificationManageratravésdeContext.getSystemService( Conte xt.ALARM
SERVI CE) . La configuración de alarmas es muy sencilla. En el siguiente ejemplo crearemos
una sencilla aplicación que establece una alarma al pulsar un botón; al desencadenar Alarm,
devuelve un sencillo Toast para informamos de que se ha ejecutado dicha alarma.

Ejemplo de alarma
En el siguiente ejemplo crearemos un proyecto de alarma, SimpleAlarm, con el paquete
com. msi. manning . c ha p t e r 8 . simpleAlarm, el nombre de aplicación SimpleAlarm
y el nombre de actividad GenerateAlarm. Utilizaremos otro icono de código abie rto.
Cambie el nombre del icono por clock y añádalo al directorio r es/drawable del pro-
yecto al crearlo. Seguidamente, modificaremos AndroidMani fe s t . xml para incluir un
receptor con el nombre AlarmReceiver, véase el listado 8.6.
Listado 8.6. AndroidManifest.xml
< ? xmlve r sion= I' 1 . O" e n cod i ng = "u t f - 8 11 ?>
<ma n i f es t xml n s : a n d ro i d = '' h t t p : / / s c h e ma s . a n d r o i d .com/ a p k I r e s / a nd r o i d "
pa c ka g e=" com.ms i .manning. chap t er8.simpl eAlarm" >
<a p p l i c a t i o n a n d r o i d : i c o n = " @d r a wa b l e / c l o c k ">
<a c t i v i t y android: name =" . GenerateAl a rm"
Android.Guíapara desarrolladores DI
a ndroid :label="@string/app_name" >
<i n t e n t - f i l t e r>
<a c t i on android: name="android. intent. action. MAIN" / >
<category android: name="android. intent. category. LAUNCHER" / >
</ i n t e n t - f i l t e r>
</ a c t i v i t y>
<receiver android: name=" . AlarrnReceiver" android : process=" : remote" / >
</ a pp li c a t i on >
</manifest>

Modifique el archivo string. xml en el directorio de valores y añada dos nuevas


cadenas:
<s t r i n g name= "set_alarm_text">Set Alarm</str ing>
<s t r i ng name="alarm_ mes sage">Alarm Fired</ string>

Utilizaremos esta cadena como valor del botón. Seguidamente, añadimos un nuevo
botón, como se indica a continuación:
<Bu t t o n android: id="@+id/set_alarm_button"
android:layout_width="wrap_content"
android :layout_height="wrap_content"
android:text="@string/set_alarm_text" >
<r e q ue s t Fo c u s / >
</ Bu t t o n>

Ya podemos crear una nueva clase que utilizar como receptor para la notificación
generada por Alarm. En este caso generaremos una notificación de tipo Toast para
indicar al usuario que se ha desencadenado Alarm. Como se indica a continuación, la
nueva clase espera la difusión de Alarm a AlarmReceiver y después genera Toast
(véase el listado 8.7).
Listado 8.7. AlarmReceiver.java.
public class AlarmReceiver extends BroadcastReceiver {

public void onReceivelntent (Context context, Intent intent) (


Toast.makeText( context, R.string.app_name, Toast.LENGTH_SHORT) .show();

@Override
public void onReceive (Context context, Intent intent)

Seguidamente modificamos la clase SimpleAlarm para crear un botón que invoque


la clase interna setAlarm. En setAlarm creamos un método onClick para programar
la alarma, invocar el Intent y desencadenar Toast. El listado 8.8 muestra el aspecto
final de la clase.
Listado 8.8. SimpleAlarm.java.
public class GenerateAlarm extends Activity {

ToastmToast;

@Override
8. Notificaciones y alarmas

protected void o nCrea te (Bundl e i ci c l e)


super . o n Cr e a t e ( i c i c l e ) ;
setCon tentV iew (R. l a you t . main ) ;
Butto n bu tt on = (Bu t t o n) t í.ndv.í e wa y rd (R. id . se t al arm_but t on ) ;
b u t t on .setOnCl i ck Listen er( t h i s . mOne Sh otL i st e n e r );

private OnC l ic k Listene r mOne Sho tLi stene r = riew OnCl ic k Listene r ( )

public void onC lic k (Vi e w v ) (

Inte n t inte n t = n e w In t en t (Ge n e ra t e Al a rm . this , Ala rm Rece ive r. class ) ;

PendingIn t ent a pp Intent =


Pe ndi ng I nte nt . g e t Broa dcas t (Ge n e r a t e Al a r m. t hi s , O, inten t, O) ;

Ca l e n d a r c a le n d a r = Ca l end a r . g etIn stan ce ( ) ;


c a l e n da r .se t Ti me I nMil l is (Sys t e m. c u r r e n t Ti meMi l l is ( » ) ;
c a l e n d a r . a d d (Ca l e n dar . SECOND,3 0 ) ;

Al armM anager a m = (Al armMan ager ) ge t S y st e mSe r v i ce (ALARM_ SERVICE ) ;


am . se t (Ala rmManager.RTC_ WAKEUP , ca len d ar . getTi me I n Mi l l is ( ) ,
a pp Inten t);

if (Gene r a t e Al a r m. thi s . mTo a s t ! = null)


Genera t eA larm.this .m Toast .canc e l( ) ;

Genera teAlarm . t h i s . mTo as t = Toast . ma k e Te x t (Ge n e r a teAl a rm . this ,


R .string .ala r m_mes sage, To a s t. LENGTH_LONG);
Ge n e r a teA la r m. t h i s. mTo as t.sho w ( ) ;

i.

Corno puede apreciar, es una clase muy sencilla. Primero crearnos un botón pa ra des-
encadenar la alarma. Tras ello, crearnos una clase interna para mOn e Sh otLi sten er y,
después, el Inten t que ejecutar al saltar la alarma. En el siguiente fragmento de códi-
go utilizarnos la clase Ca l e n da r para calcular el número de milisegundos desde que se
pulsa el botón, que utilizaremos para establecer Alarm.
Ya hemos realizado las acciones previas neces arias para crear y establecer la alarma.
Para ello, primero crearnos Al a rrnManager y después invocamos su método se t ()
para establecer Ala r m. En las siguientes líneas apreciará más detalles de lo que sucede
en la aplicación:

Al a r mManage r am = (AlarmManag e r) getSys t e mSe rv ic e (ALARM_ SERVI CE) ;


am . s e t (AlarmManag er. RTC_WAKEUP, ca le ndar . ge t Ti me I nMi l l is ( ) , int e nt ) ;

Aquí se crea y establece Ala rm por medio de ge tSy ste mSe rvice pa ra crear
Ala rrnMan ager. El primer parámetro pasado al método s et () es RTC_ WAKEUP, un
entero que representa el tipo de alarma que establecer. AlarrnManag e r admite cuatro
tipos de alarma, véase la tabla 8.3.
Android. Guín pnra desarrolladores mil
Tabla 8.3. Tipos de alarma de AlarmManager.

ELAPSE D REALT IME Hora de la alarma en Sys temCl ock . e lapsedRea l t i me ()


(desde el arranque , incluido el tiempo de suspensión).
ELA PSE D REALT I ME WAKEUP Hora de la alarma en SystemClock. e lapsedRea l time ( )
(desde el arranque , incluido el tiempo de suspensión) ,
reanuda el dispositivo al desencadenarse .
RTC Hora de la alarma en S y stem . c urr e n tT i me Mi ll is ()
(hora UTC).
RTC WAKEUP Hora de la alarma en Sy stem . c urr e n tTi me Mil l is ()
(hora UTC), reanuda el dispositivo al desencadenarse .

Como puede apreciar, existen diversos tipos de alarmas que puede utilizar en fun-
ción de sus necesidades. Por ejemplo, RTC_ WAKEUP, establece la hora de la alarma en
milisegundos y cuando se activa, reanuda el dispositivo, al contrario de lo que sucede
con RTC, que no lo hace.
El siguiente parámetro que pasamos al método es la cantidad de tiempo en milise-
gundos que debe pasar antes de poder desencadenar la alarma. Lo establecem os por
medio de:
Ca l enda r c a le nda r = Ca len dar . g etlnstance ( ) ;
ca l enda r.setTime l nMi l lis (System.cu rre ntT i meMi l lis ( ) ) ;
c alendar . a dd (Cale n da r.S ECOND,30 ) ;

El último parámetro es el In t en t que difundir, In t en t Re c e i v e r. Si genera la


aplicación y la ejecuta en el emulador, obtendrá un resultado similar al ilustrado en la
imagen (véase la figu ra 8.5). Al hacer clic en el botón Set Alarm se establece la alarma y
transcurridos 30 segundos verá un resultado similar al ilustrado en la sigu ien te pantalla,
que muestra el mensaje Toa s t.
Como habrá comprobado, la creación de una alarma en Android resulta muy sencillo
pero tendría más sentido que ésta desencadenara una notificación en la barra de estado.
Para ello, tendremos que añadir un No ti fi c a tio nManage r y generar una notificación.
Hemos creado un nuevo método que añadir al listado 8.8, s ho wNot i f icat ion, que
acepta cuatro parámetros y crea la notificación:
privatevoi dshowNoti f ication (intstatus BarlconID ,
int sta tusBarTex t I D, int detai ledText ID , boo lean showr.conon y ) í

I nte n t c ont en t l n t e n t = n e wl n t e n t( t h i s , SetAlarm .class) ;


Pending l n te nt theapp lntent = Pendinglntent. getBroadcast (S e t Al arm. this ,
o, conte nt lntent , O) ;
CharSe q u ence fr o m = " Al a r m Manager " ;
Ch arSeque nce me ss a g e = " Th e Alarm wa s fired ";

St r ing tickerTex t = show j conon j y ? null : t h is. getString (s t a t u s Ba r TextI D) ;


No tif i ca ti o nn o t i f= neI-l No t i fi c a t i o n( s t a t u s Ba r l c o nID , tickerText ,
System. cu r re ntTimeMi l lis ( ) );
&!II 8. Notificaciones y alarmas

no t i f . s e tLa t e s t Eve nt l n f o (th i s , fr om, me s s a ge, theappln te nt);

nm.notify (YOURAPP_NOTIF ICATION_ID , not i f);

Figura 8.5. Ejemplo de la aplicación SimpleAlarm ejecutada en el emulador.

Gran parte del código es similar al código de SMSNotifyExample. Para añadirlo a


Simpl eAlarm, cambie el listado 8.8 con las modificaciones mostradas en el siguiente
(véase el listado 8.9),donde hemos importado Notificatio n yNotificationManager,
añadido las variables privadas nmy Appli c ati onID, e invocado s h o wNo t i fica t io n
después de Toast.
Listado 8.9. SetAlarm .java.
pub lic c lass SetAlarm extends Activi ty {

pr ivate Notif i cationManager nm;


Toast mToast;

@Overr ide
protected void o nCreate (Bundl e icic le )
super .onCreate (icicle);
Android. Guía paradesarrolladores &JI
setCon te ntV ie w(R. la yo ut . ma i n ) ;

t h is. nm = (Not if icationManager )


getSy stemSe r vi ce(Contex t .NOTIFI CAT ION_SERVICE ) ;

But t on button ~ (But t o n) f indViewByld (R. i d. set_a larm_butt on) ;


button . s e tOnCli ckListener (this .mOneShotL i stener ) ;

privatevoidshowNotifica ti on (in tsta tusBar lconI D, ints tatu sBarTextID , in t


detailedTextID , b oo l e a n showj c o no n L y) {

Intent c on te nt l nt e nt = new Intent (t h i s, SetAlarm. class ) ;


Pend inglntent theapp l nte nt = Pendinglntent. getB roadcast (Se t Al a r m. this, O,
cont en t l ntent , O) ;
Cha rS equen ce from = "Al a rm Manager " ;
CharSequen ce me s sage = " The Al arm was fi r ed" ;

String ticke rText = shovn conon.l y ? null : this . getString (s tatusBar Te xtID ) ;
Notification not i f = new Notification (s t a t u s Ba rl c o nID, ticke rT ext ,
System .currentT imeMi ll is ( » ;

not if.setLatestEventlnfo (this , from , message , theappln tent) ;

t h i s . nm. no t i f y (t h i s . YOURAPP_ NOTI FI CATI ON_I D, noti f);

p rivate OnCl i c kLi s t e ne r mOneShotListener ~ new OnClickListener ( )

public void onClic k (Vi e l1v) {

Intent inte nt = new In tent (SetAla rm . this , AlarmRecei ver. class ) ;

Pendinglntentapplntent=Pendingln tent .getBroadcast (SetAla rm .this , O,


i nte nt , O) ;

Calendar calenda r ~ Ca l e ndar . getlnstance () ;


c a l e nda r . s e t Time l nMil l i s (Sy s t e m.curre ntTime Mi l l is ( ) ;
ca lendar.add(Ca le ndar. SECON D, 30 );

AlarmManager am = (Al a r mMa na ge r)


ge t Syste mSe r vic e (Co n t e x t . ALARM_S ERVICE) ;
am.set (AlarmManage r .RTC_WAKEUP , calenda r .getTimelnMil lis () ,
app lnte nt ) ;

shoI1Notification (R .dral1abl e .alarm, R.string.a l arm_me ssage ,


R.string .alarm_message , fals e ) ;

};

Si ejecuta el código y pulsa Set Alann, verá Al arm Notifi c ati on en la barra de
estado (véase la figura 8.6). Puede editar este código para añadir parámetros de fecha y
hora, para mostrar distintos Int ent al hacer clic en los iconos, etc.
&iI 8. Notificaciones y alarmas

Figura 8.6. Alarm Notification en la barra de estado .

Como hemos visto en este ejemplo, en Android las alarmas y Al a r rnMa n age r son
muy sencillas y podrá integrar estos elementos sin dificultad en sus aplicaciones.

Resumen
En este capítulo hemos visto dos elementos diferentes pero relacionados: Noti fi cation
y Al armo Hemos aprendido a usar Not i f i c a t i o nMa n a g e r para generar notificaciones y
la clase No t i f i c a t i on para p resentar una notificación al usuario por medio de un sencillo
ejemplo que la muestra al recibir mensajes SMS en la bandeja de entrada. También hemos
visto como establecer una alarma para iniciar una aplicación o realizar una acción en el
futuro, como activar el estado del sistema. Por último, hemos aprendido a desencadenar
una notificación a partir de una alarma. Aunque el código de estos sencillos ejemplos le
ofrece una parte de lo que puede hacer con notificaciones y alarmas, su uso es ilimitado.
Ahora que ya sabe cómo trabajar con las clases Notifi ca t i on y Alarm, pasaremos
a analizar gráficos y animaciones. Aprenderemos los métodos básicos para generar grá-
ficos en Android, para crear sencillas animaciones e incluso cómo trabajar con OpenGL
para generar sorprendentes gráficos 3D.
9
Gráficos
• •
ya I a 10 es
Una de las principales características de Android que ya habrá detectado es que re-
sulta mucho más sencillo desarrollar aplicaciones de Android que para otras platafor-
mas de aplicaciones móviles. Destaca especialmente en la creación de ID visualmente
atractivas aunque exista un límite en cuanto a lo que se puede hacer con los elementos
IU tradicionales, corno los mencionados en un capítulo anterior. En este capítulo creare-
mos gráficos con el API Graphic de Android, desarrollaremos animaciones y veremos su
compatibilidad con el estándar OpenGL (en h ttp://www.ornnigsoft. com/Andro id/
ADC/r e a drne . htrnl encontrará ejemplos de la plataforma gráfica de Android).
Si ya ha trabajado con gráficos en Java, es probable que el API Graphics y el funcio-
namiento de los gráficos en Android le resulten familiares.

Dibujar gráficos en Android


En este apartado analizaremos las prestaciones gráficas de Android junto a ejemplos de
cómo crear sencillas formas 20. Utilizaremos el paquete android . graphi cs (ht tp : / /
code.google .com/android/reference/android/graphics/packag e-
surnmary. h trn I ), que contiene todas las clases de nivel inferior y las herramientas
necesarias para crear gráficos. Este paquete admite mapas de bits (que almacenan
píxeles), lienzos (donde dibujar), primitivas (corno rectángulos o texto) y pintura (para
añadir colores y estilos) .
Para ilustrar el dibujo de una forma básica veamos un sencillo ejemplo, donde dibu-
jaremos un rectángulo (véase el listado 9.1).
&1 9. Gráficos y animaciones

Listado 9.1. Ejemplo de forma.

package com .msi.mann ing .chapter9 .SimpleShape ;

p ublic clas s Si mpleS hape exte nds Ac tiv ity (

@Override
protected void onCreate (Bu nd le i cic le ) (
s u per .onCreate (ic ic le ) ;
setConten tV iew (ne w S i mpleView (this ) );

private sta tic c la ss SimpleVie w ex te nds View {


p riva t e ShapeDrawab le mDrawab le
new ShapeD rawab le ( ) ;

p ub lic S i mpleView (Co ntex t con text )


super (context );
set Focusab le (true ) ;
t hi s . mDr a wabl e =
n ew Shape Drawab le (new Re ctShap e ( ) ) ;
th i s. mDrawabl e .getPa i n t() .setCol o r(O x FF FFOO OO);

@Over ride
p ro tec ted void on Dr aw (Ca nvas ca nvas ) (

int x = 10 ;
i n t y = 10;
in t wi d th = 30 0;
int h eight = 5 0;
t h i s .mDr awab l e. s etBounds(x, y , x + wid th , y + he ight) ;
t h is . mDrawab le .dra w (ca nvas ) ;
y += h ei ght + 5;

Dibujar una nueva forma resulta muy sencillo. Primero es necesario importar el pa-
quete, incluidos los gráficos, y después ShapeDrawable, que nos permite añadir formas
al dibujo, y después formas, que incluye diversas formas genéricas como RectShape,
que utilizaremos en este caso. Tras ello, creamos una vista y después un nuevo elemento
ShapeDrawable para añadir Drawable. Seguidamente podemos asignar formas. En
nuestro código utilizamos RectShape pero podríamos haber utilizado OvalShape,
PathShape, RectShape, RoundRectShape o Shape. A continuación, utilizamos el
método onDraw () para dibujar Drawable en el lienzo. Por último, utilizamos el mé-
todo setBounds () de Drawable para definir el contorno (un rectángulo) en el que
dibujarlo con el método draw () . Al ejecutar el listado 9.1, apreciará un sencillo rectán-
gulo (véase la figura 9.1).
También puede utilizar XML para realizar la misma operación. Android le permite
definir formas para dibujar en un archivo de recurso XML.
Android. GI/ía para desarrolladores &iI

Figura 9.1. Sencillo rectángulo dibujado con el API Graphics de Android.

Dibujar con XMl


Con Android puede crear sencillos dibujos con un archiv o XML. Para ello, basta con
crear uno o varios objetos Dr a wa bl e, que se definen como archivo XML en el direc-
torio res / drawab le. El listado 9.2 muestra el código XML necesario para dibujar un
sencillo rectángulo.

Listado 9.2. simplerectangle.xml.

<?xml version= " l. O" e ncod ing =" ut f -8 If ? >


<s ha pe xmln s :andr oid="http: / / s chema s .andro id . com/ ap k/ r e s / android" >
<s o l i d andr o i d : c o l o r =" ' FFOOO OFF" / >
</s ha p e>

Con las formas XML de Android, la predeterminada es el rectángulo, pero puede


cambiarla por medio d e la etique ta type si selecciona el valor de un óvalo, rectángulo,
línea o arco. Para utilizar esta forma XML debe hacer referencia a la misma en el dis eño ,
véase el listado 9.3.
9. Gráficos y animaciones

Listado 9.3. xmllayout.xml.

<?xml version="l .O" encoding= "ut f -8 ?> 11

<Sc r o l l Vi e l< xml ns:android= ''http ://schemas .android .com/apk/res/android"


a nd r o i d : l a yo u t_l< i d t h=" f i l l _p a r e nt "
android :layout_height= "l<rap_content ">
<Li ne a r La yo ut
android :orientation= "vertica l "
android :layout_l<idth= " f ill_pa r e nt "
android : layout_height= "14rap_ cont en t " >
<I ma ge Vi e l< android :layout_l<i dth= "fill_parent "
andro id :layout_heigh t= "SOdip "
andro i d :s rc= "@dr a l<able /simple rec ta ng le " / >

Tras ello, basta con crear un a sencilla activi da d y añadir la IV en una co n te n t Vi e w,


véase el listad o 9.4.

Listado 9.4. XMLDraw.java .

pub l i c c la ss XMLDral< ex te nd s Acti vity {

@Overr i de
p ub l ic vo id o nCreate (Bund le i c i c l e ) (
s upe r .onCrea te (icicle ) ;
se tContentViel« R.layou t .xmld r al<ab le ) ;

Si ejecuta este código, dibujará un sencillo rectángulo. Puede realizar dibujos o formas
más complejas si apila u ordena los elementos d rawable XML, y puede incluir todas las
formas que necesite en función de l espacio. Podría modificar el archivo xml dr a wab 1 e .
xml (véase el listad o 9.5) para añadir varias formas y apiladas verticalmen te.

Listado 9.5. xmldrawable.xml.

<?xml vers ion= "l. O" e ncoding= "ut f -8 11 ? >

<Sc r o l l Vi e \4 xmlns :android='' ht tp ://sche mas . a nd roi d .com/ap k/res/ a nd roi d "
a nd r oid: la you t _l<idt h= " fill_pare nt "
a nd r oid: la y o u t_h e i g h t=" ~¡ r a p_c o n t e n t " >

<Li ne a r La yo ut
a nd ro i d :or ientation= "vertica l "
an d roid :la you t_l<id t h=" f ill_pa r ent "
an droi d : l ayout_height= "l<r ap_con t e n t ">
<Ima geVie\4 a nd ro i d : layou t _l<i d t h= " f i l l_pare nt "
a nd ro id : layout_ he igh t = "SOd i p "
a nd ro i d :src= " @dral<ab le/shape_ l " />
<Lma qe v í.e w an d roid : layout_ I<i d t h=" fil l _p a ren t "
androi d : l a yo ut_ he i gh t = "l<r a p_ c o n ten t "
an dro i d : s r c =" @d r a\4a b l e / l ine " />
<I ma ge Vi e \4
android : layout_,·¡idt h= " fill _pare nt "
Android. GI/ía para desarrolladores &D
andro id : layou t _ height= "50di p "
a nd ro i d :src= "@drawable/s hape_2 " />
< I ma g e Vi e w
and ro i d : layout_ \ü d th= " f i ll_paren t "
a n d ro i d : layout_hei ght= "5 0di p "
a nd ro i d :s rc= "@drawab le/s hape_5 " />
< / Li n e a r La y o u t>
</Scro l lV i e w>

Por último, debe añadir las formas de los listados 9.6, 9.7, 9.8 Y9.9 a la carp eta r e s /
drawable.

Listado 9.6. shape1 .xml.

< ? x ml vers ion= " 1.0 " encodi ng = "ut f -8 11 ?>


<s hape x ml n s :an d r o id= '' h t t p : / /s chemas . andr o id . c om/ ap k / r e s / andr o id "
type= "o va l ll
>
<so l i d andr oid : c ol o r = "IOOOO OOOO " / >
<p a d d i n g a nd r oid : l eft= "lO s p " android:top= "4 s p "
a ndro i d : r i g h t= " l Osp " a n d ro i d : bo t tom~ " 4 s p " / >
<s t r o k e a n dro i d :width= " l dp " andro i d :color= "IFFFFFF FF " / >
</s h a p e>

En el listado 9.6 utilizamos un óvalo. Hemos añadido la etiqueta paddi ng, que nos
permite definir el relleno o espacio entre los objetos de la IV. También util izamos la eti-
queta s t roke, que nos permite definir el estilo de la línea que forma el borde del óvalo
(véase el listado 9.7).

Listado 9.7. shape2 .xml.

<?xml vers ion="l .O" encodi ng= "ut f -8 ?> 11

<s h a p e xmln s : a n d r o i d ='' h t t p : / / s c h e ma s . a nd r o i d . c om/ a p k /re s / a nd r o i d " >


<so l id androi d : c ol or=" I FF OOOOFF " / >
<s t ro ke a n d r o id :w id th= "4dp " andr oid :co l or= " IFFFF FFFF "
android: da shWidth = " ldp " a ndroi d : d a s h Gap= " 2d p " / >
<pad di n g and ro id : le f t = "7dp " a n d ro i d :to p = " 7d p "
andro id : righ t= " 7dp " a ndroid:bo ttom=" 7dp " / >
<co r ne r s a ndro i d: radiu s= "4dp " />
</sh a pe>

Con esta forma se genera otro rectángulo pero, en esta ocasion (véase el listado
9.8) utilizamos la etiqueta co r ne r s para crear esquinas redondeadas con el atributo
an dro id : radiu s.

Listado 9.8. shape3 .xml.

<?xml v er s i on = "l . O" e ncoding= "ut f -B " ? >


<s ha p e x ml n s : a ndro i d= '' ht tp : / / s c h emas .andr oid .com/ a p k / r e s /a nd r oid "
t ype= "o v a l ">
<g r a d i e n t androi d : s t a r tCol or= "I FF FFOOOO " a ndroid :endColo r = "180 FFOOFF "
a nd ro i d :an g le= "2 70 "/ >
EmII 9. Gráficos y animaciones

<pad d i ng a ndroi d :l e f t - " 7dp " a ndro id:top- " 7dp "
a nd roi d :right= "7 dp " an d roid :bottom- " 7dp " />
<cor ne rs and r o i d :ra d i u s - " 8 d p " />
</s hap e>

En el listado 9.9 creamos una forma de tipo line con una etiqueta s iz e con el atri -
buto andr oid: h ei ght, que nos permite describir el número de píxeles utilizados en
la vertical para el tamaño de la línea .

Listado 9.9. line.xml.

<?xml v e rs i o n= "l . O" en c oding= "utf - 8 11 ?>


<s h a p e xml n s:a ndr o id=http : / /s chema s . a ndroid . c om/ ap k /re s /a nd r o id
type = "1 i ne " '>
<s o l i d android : c o l or= "# FFF FF FFF" / >
<s t r o ke a ndroid : widt h= " ldp " android : co lo r="# FFFFF FFF "
a ndroid: d a s hWidth = " ldp " a ndro id :dashGap =" 2dp " />
<p a d d i ng a ndro i d:l e f t ="l d p" a nd ro i d : top = "2 5d p "
a ndroid:righ t = " l dp " android :botto m=" 25dp " />

<s i z e andr oid : height= " 2 3dp " />


</ s ha p e>

Si lo ejecuta, obtendrá el resultado reproducido en la siguiente imagen (véase la fi-


gura 9.2). Como puede comprobar, en Android resulta muy sencillo dibujar y permite
diseñar mediante programación prácticamente cualquier forma. En el siguiente apartado
analizaremos las prestaciones de animación de Android.

Animaciones
Si una imagen vale más que mil palabras, una animación debe valer millones. Android
admite diferentes métodos de animación, como por ejemplo a través de XML como
vimos en un capítulo anterior, con el API Graphics o con OpenGL ES. En este apartado
crearemos una sencilla animación de una pelota que rebota por medio de la técnica de
animación por fotogramas de Android.
Android le permite crear sencillas animaciones mostrando un conjunto de imágenes
repetidas para generar sensación de movimiento. Para ello, establece cada imagen como
recurso drawable; tras ello, las imágenes se muestran una detrás de otra en el fondo de
un elemento Vi e w. Para utilizar esta función, debe definir un conjunto de recursos en
un archivo XML y después invocar AnimationDrawable. run ().
Para ilustrar este método de creación de animación, primero debe descargar las imá-
genes del capítulo. Se trata de seis representaciones de una pelota dando botes. Tras ello,
cree un proyecto con el nombre XMLanimation. Cree un nuevo directorio / anim bajo
el directorio / res de recursos. Añada todas las imágenes del capítulo al directorio /
drawable . Seguidamente, cree un archivo XML con el nombre Simple_animation.
xml con el siguiente código (véase el listado 9.10).
Android. GI/ía para desarrolladores mi

Figura 9.2. Distintas formas dibujadas con XML.

Listado 9.10. Simple_animation.xml.


<?xml version= 1I1. 0 " e ncoding= lI u t f - 8 "? >
<animation-list xmlns :android=http://schemas .a ndroid .com/apk/res/android
i d= "selected " android :oneshot= "false " >
< item android :drawable-"@drawab le/bal l l " android :duration= "50 " />
<item androi d :drawable- "@drawable/ba l 12 " a ndroid :duration= "5 0 " />
<item androi d :drawable= "@drawable/bal13 " android :duration= "5 0 " />
<item android :drawa b le= "@drawable/bal14 " a ndro id : d uratio n= "50 " />
< item androi d :drawable= "@drawable/bal15 " a ndro id : duration- "50 " />
<item android :drawable= "@drawable/ba l 16 " a ndroid:duration- "50 " />
</anima t io n- l ist>

El archivo XML define la lista de imágenes que mostrar para la animación. La eti-
queta <a n i ma t i o n - lis t> contiene las etiquetas de los dos atributos drawabl e, que
describe la ruta a la imagen, y dura ti on indica la duración de la imagen en nanose-
gundos. Una vez creado el archivo XML de la animación, edite el archivo main . xml
(véase el listado 9.11).
Listado 9.11. main.xml.
<?xml version= '11 .0 '1 e ncoding= "utf -8 "?>
<LinearLayout x mlns:a ndroid= "http://schemas .android .com/apk/res/android "
android:orientation= "vertical "
android: layout_l'lidth= " f i l l _parent "
9. Gráficos y animaciones

android: layout_height= "fil l _pa re nt "


>
<ImageViel< android :id= "@+ id/si mple_anim "
a ndroid : layout_l< i dth=" l<ra p_co ntent "
andro id :layout_he igh t ="l'Irap_con te nt "
android :gravity="cente r"
a ndro id: layout_cente rHorizontal= "true "
/>
<TextViel<
a ndro id: layout_,üd t h= " fill pa rent "
andro id:layout_height= "l<rap_content "
andro id :text="He l lo World, XMLAn i matio n"
/>
</ LinearLayout>

Únicamente hemos añadido una etiqueta ImageViewque define el diseño de ImageView.


Por último, creamos el código para ejecutar la animación (véase el listado 9.12).
Listado 9.12. xmlanimation.java.
p ub l ic c lass XMLAn i mat io n exte nds Acti vi t y (

@Override
pub lic vo id o nCreate {Bund l e i ci cle)
supe r .onCreate (ic ic le ) ;
setConte ntV i el«R . l ayout .main ) ;
Ima ge Vi el'l i mg =
(Ima ge Vi e l<) f in dVi eI'lByl d( R. i d . simple a ni m) ;
img .se tBackground (R.a n i m.s i mp le_a n i mat ion ) ;

MyAnimationRout ine ma r =
nel< MyAnimatio nRout ine ( ) ;
MyAn ima t i on Ro uti ne 2 mar2 =
ne l'l MyAni matio nRoutine2 ( ) ;
Time r t = ne l< Ti mer (fa lse ) ;
t.schedu le (mar , 100 ) ;
Time r t2 = ne l'l Time r (fa lse ) ;
t2 .schedu le (mar2 , 5000);

class MyAnima t i on Rou tine extends Ti me rT a s k {

@Overr ide
public void r un () (
ImageViel< i mg = (Ima ge Vi e l'l) fi ndVi e I<Byl d( R. i d. s i mpl e anim ) ;
Ani matio nDral<able frameAni mation = (Anima ti o nDr a l<a ble)
i mg.getBackgro und ( );
frameAn i mat ion.s tart ( ) ;

c lass MyAn i mat ionRoutine2 exte nds TimerTask (

@Overr ide
p ubl ic void run ( ) (
I mag eViel< img = ( I ma ge Vi el<) f i ndVi e I'lByld( R. i d. s impl e_ a nim);
Android. Guía para desarrolladores f1D
Anima t ionDrawable fra meAnima t ion (An i ma ti o n Dr a wa b l e)
im g . g e t Ba c kg r ound();
frameAni ma tion.st op ( ) ;

El listado 9.12 puede resultarle ligeramente confuso debido al uso de las clases
TimerTas k. Como no podemos controlar la animación desde el método OnCrea te, deb e-
mos crear dos subclases que invoquen los métodos start y stop de Ani mat i onDr awab l e.
Por ello, la primera subclase, MyAnima ti onRo utine, amplía TimerTask e invoca el mé-
todo f ram eAnima tio n . start () de Anima t i on Dr a wabl e vinculado a Ima ge Vi ew de
fondo. Si ejecuta el proyecto, obtendrá un resultado similar al siguiente (véase la figura 9.3).

Figura 9.3. Uso de Animation XML de Android para hacer botar una bola.

Como puede apreciar, la creación de una animación con XML es muy sencilla. Puede
crear animaciones de cierta complejidad pero para hacerlo mediante programación, nece-
sita las funciones gráficas 2D y 3D de Android, como ver emos en el siguiente ap artado.

Crear una animación mediante programación


En el apartado anterior utilizamos las prestaciones de animación por fotogramas
para, básicamente, mostrar una serie de imágenes en bucle y generar sensación de mo-
vimien to. En el siguiente apartado, animaremos mediante programación un globo para
desplazarlo por la pantalla.
mi 9. Gráficos y animaciones

Para ello, animaremos un archi vo gráfico (PNG) con una bola que parece rebotar den-
tro de la ventana de Android. Crearemos un subproceso para ejecutar la animación y un
elemento Handler para comunicar mensajes al programa que reflejen el estado de la
animación. Posteriormente utilizaremos este enfoque en el apartado sobre OpenGL ES.
Es una técnica básica para afrontar aplicaciones gráficas y animaciones más complejas.

Anim ar recursos
En este apartado veremos una técnica de animación muy sencilla que utiliza una ima-
gen vinculada a un muelle que se mueve por la pantalla. Para empezar, cree un nuevo
proyecto con el nombre bouncing ball y una BounceActivi t y. Puede copiar y pegar
el siguiente código (véase el listado 9.13) para el archivo Activi ty. j ava.

Listado 9.13. BounceActivity.java.

publ ic c lass BounceAc tivity extends Act iv ity (

p rotected s tatic final i nt GUI UPDATEI DENTIFIER Oxl 0 l ;

Th r e ad myRe fres h Thread = n ull;


BounceVie w myBo unceView = n u ll;

Hand le r myGUI Upd ateH a ndl e r = new Handl er() (


pub l ic voi d h a nd l e Me s s a g e( Me s s a g e ms g ) {
sw i tch (ms g . wha t) (
case Boun c e Ac t i vi t y. GUIU PDATEI DENTIFIE R :
myBoun c e Vi e w. i nv al i d a t e {);
b r e ak;

s u per . ha ndleMessage (ms g ) ;

j;
@Over r i de
p ub l ic voi d onCrea te {Bun d le ic ic le ) (
super .onCreate (ic ic le ) ;
t his . req uestWindowFea t u re (W i ndow .FEATURE_NO_TITLE ) ;

this .myBounceV iew = new Bo unceVieH (this ) ;


this .setConte ntVieH (th is .myBounceVieH);

ne H Th read (neH RefreshRunner( )) . s t a r t() ;

c lass Ref res hRunner i mplements Runnab le (

p ub lic void ru n ( ) (
Hh i le (!Th re a d . c u r r e n t Th r e a d() . i sI n t e r r u p t e d(»)

Message me s s a g e = neH Message ( ) ;


message .what = BounceAc tivity .GUI UPDATEIDENTIF IER ;
Bou nc e Ac ti vi t y . th i s. myGUI Up d a t e Ha ndl er . s endMe s s a g e(me s s a g e);
try {
Android. Guía para desarrolladores IZa
Th r ead . s l e ep (lOO l ;
c atc h (I n t e r r u p t e d Ex c e p ti o n e ) {
Thread . c u rren t Thre ad ( ) . i n t e r ru p t () ;

En el listado, primero importamos las clases Handler y Message, para después


crear un identificador exclusivo que nos permita enviar un mensaje al programa para
actualizar la vista en el proceso principal. Para ello tendremos que mandar un mensaje
indicando a dicho proceso que actualice la vista cada vez que el proceso secundario haya
terminado de dibujar la bola. Como el sistema puede generar diferentes mensajes, nece-
sitamos garantizar la exclusividad del nuestro en el controlador, para lo que creamos un
identificador exclusivo con el nombre GUIUPDATEIDENTIFIER. Tras ello, creamos el
controlador para procesar nuestros mensajes para actualizar la vista principal. Handler
nos permite enviar y procesar clases Message y objetos Runnable asociados a la cola
de mensajes del proceso. Los controladores se asocian a un único proceso y a su cola de
mensajes. Utilizaremos el controlador para que los objetos ejecutados en un proceso co-
muniquen cambios de estado al programa que los ha generado o viceversa.

Si necesita información adicional sobre el procesamiento de solicitudes de ejecución


prolongada, visite http://devel oper . android. c om/referen ce/androidl
app / Act ivi t y . ht ml .

Creamos un elemento Vi ew y el nuevo subproceso. Por último, creamos una clase


interna RefreshRun ner que implemente Ru nn a b l e, que se ejecuta a menos que algo
interrumpa el subproceso, momento en que se envía un mensaje a Handler para invocar
su método invalida te (). Este método invalida la vista y fuerza una actualización.
A continuación tendremos que crear el código para nuestra animación y un elemento
Vi ew. Utilizaremos la imagen de un globo. También puede utilizar cualquier otro archivo
PNG. El objetivo es utilizar el logotipo de Android como fondo, incluido en las descargas
del código fuente. Guarde las imágenes en re s I drawable. Tras ello, cree un archivo de
Java con el nombre Bou nceView y copie el siguiente código (véase el listado 9.14).

Listado 9.14. BounceView.java.

p u b l ic c lass Bou nc e Vi ew e xtends View (

prot e ct ed Dr a wab l e my Spr ite ;


p r o t e ct ed Poi n t mySpr itePo s = ne w Point( O,O);

p r ot e cted en um Hor i z on talDirecti on (L EFT, RI GHT )


p rote c ted e nu m Vert i c alDi recti on ( UP, DOWN)
9. Gráficos y an imaciones

prote cted Hori zon t alDi r e cti on rnyXDire c t i o n

Horiz onta lDirection.R IGHT;

p rotected VerticalDirection rnyYDirect i on Vertica lDirection.UP;

pub l ic Boun ceV iew (Context co nt ex t ) (


s uper (c o n t e x t) ;

t h i s . s e t Ba c kg r o und( th i s. ge t Re s ou r c e s() . ge t Dr a wa bl e( R. d r a wa ble . a nd r o i d));


t h is.rnySpri te =
t h is .getResources ( ) . ge tD r a wa ble( R. d r a wa b l e. wo rl d );

@Ove r ri de
protec ted void onDra w(Canv as ca nvas ) (

this .rnySp r ite .setBounds (this .rnySpritePos .x ,


t his. rnySpri tePos .y ,
t h is . rnySp ri tePos.x + 50 , this . rnySpr i tePos .y + 50 ) ;

if (rnySp rite Pos. x >= thi s . ge tWi d th() -


rnySp r i t e . ge tB ou nd s() . wi d th ( ) ) (
thi s .rnyXDire c t i o n = Hori zontalD i re cti o n .LEFT;
e ls e i f (rnySpr i t eP o s . x <= O) (
t h is. rnyX Direction = Hor i zo nt alDire c ti on . RI GHT;

i f (rnySp r i t e Po s . y >= this.get Height ( ) -


rnySp r i t e .getB oun d s() . he i gh t ( ) ) (
t his . rnyYDire ct io n = VerticalD irection . UP;
e lse if (rnySp r iteP o s . y <= O) {
this.rnyYDi r e c t i o n = Vert ica l Directio n.DOWN;

i f (th i s. rnyXDire c t i o n ==
Hori zontal Dire c t i on. RI GHT) {
t h i s .mySp r itePos . x += 1 0;
e lse {
t h is . rnySpri tePos .x - = 10 ;

if (t hi s .rnyYDire c t i o n ==
Vertica lD irectio n.DOWN )
th i s .rnySp r iteP os . y += 10 ;
e lse (
t his . rnySpr itePos .y 10 ;

t his . rnySprite . draw (ca nvas ) ;

En el listado 9.14 se realiza todo el trabajo de la animación de la imagen. Primero


creamos Drawable para almacenar la imagen del globo y Point, que utilizaremos para
ubicar y realizar el seguimiento del globo al animarlo. Tras ello, creamos enumeraciones
Android. Guia para desarrolladores BD
para almacenar valores direccionales verticales y horizontales, que utilizaremos para rea-
lizar el seguimiento del movimiento del globo. Después, asignamos el globo a la variable
mySpr i t e y establecemos el logotipo de Android como fondo de la animación.
Una vez terminada la configuración, creamos un nuevo objeto Vi e w y establecemos
los límites de Drawab l e . Seguidamente, creamos la lógica condicional que detecta si el
globo intenta salir de la pantalla; si comienza a salir de ésta, cambiamos su d irección .
Tras ello, proporcionamos una sencilla lógica condicional para mantener el mov imiento
en la misma dirección si no ha detectado los límites de Vi ew. Por último, dibujamos el
globo con el método d raw. Si compila y ejecuta el proyecto, verá cómo el globo bota por
delante del logotipo de Android, véase la figura 9.4.

Figura 9.4. Sencilla animación de un globo que bota por delante del logotipo de Android .

Aunque esta sencilla animación no es especialmente emocionante, podría aplicar los


conceptos básicos (límites, desplazamiento por elementos dra wabl e, detección de cam-
bios, subprocesos, etc.) para crear algo similar al juego Lunar Lander de Google o incluso
una sencilla versión de Asteroids. Si desea mayor potencia gráfica y trabajar con objetos 3D
para crear juegos o animaciones sofisticadas, lea el siguiente apartado sobre OpenGL ES.

Presentación de OpenGl para sistemas incrustados


Una de las características más interesantes de la plataforma Android es su compati-
bilidad con OpenGL para sistemas incrustados (OpenGL ES). Es la versi ón para siste-
mas incrustados del conocido estándar OpenGL, que define un API multiplataforma y
multilenguaje para gráficos informáticos. El API OpenGL ES no admite la totalidad del
IEID 9. Gráficos y animaciones

API OpenGL y se ha reducido considerablemente para poder ejecutarla en diferentes


teléfonos móviles, PDA, consolas de videojuegos y otros sistemas incrustados. OpenGL
ES es un desarrollo original del consorcio Kronos Group y en http://www . kh ro no s.
org/opengle s/ puede encontrar la última versión del estándar.
OpenGL ES es un API fantástica para gráficos 20 y 3D, en especial para aplicacio-
nes con abundancia de gráficos como juegos, simulaciones y visualizaciones, y todo
tipo de animaciones. Como Android también admite aceleración de hardware 3D, los
programadores pueden crear aplicaciones gráficas dirigidas a hardware con acelera-
dores 3D.
Como OpenGL y OpenGL ES son temas tan amplios con libros dedicados al respecto,
únicamente abordaremos los fundamentos de trabajar con OpenGL ES y Android. Si
necesita información más detallada, consulte la especificación y el tutorial de OpenGL
ES en http://www.zeu scmd .com/tut orials/opengles/ index. php. Tras leer
este apartado sobre la compatibilidad con OpenGL ES de Android, dispondrá de in-
formación suficiente para realizar un análisis más profundo de OpenGL ES así como
transferir su código de otros lenguajes (como los ejemplos del tutorial) a la estructura
Android. Si ya tiene conocimientos de OpenGL u OpenGL ES, los comandos le re-
sultarán familiares y podrá concentrarse en los aspectos concretos relacionados con
Android.
Dicho esto, aplicaremos los fundamentos de OpenGL ES para crear un elemento
OpenGLCon te xt y después una ventana en la que poder dibujar. Para utilizar OpenGL
ES con Android, siga los pasos descritos a continuación:

1. Cree una subclase View personalizada.


2. Obtenga un elemento handle a OpenGLCon text, para acceder a la funcionalidad
OpenGL ES de Android.
3. En el método onDraw () de View, utilice handle y después sus métodos para
realizar las funciones GL.

Tras realizar estos pasos, crearemos una clase que utilice Android para crear una su-
perficie en blanco sobre la que dibujar. En el siguien te apartado utilizaremos comandos
de OpenGL ES para dibujar un cuadrado y después un cubo animado en la superficie.
Para comenzar, abra un nuevo proyecto con el nombre OpenGLSquare y cree una acti-
vidad con el nombre OpenGLSquare, véase el listado 9.15.

Listado 9.15. OpenGLSquare.java.

p ub l ic c lass Sq ua reAc tivity extends Act ivity {

@Overr i de
p ub l ic voi d o nCre ate (Bu ndle icic le ) (
s upe r .onCreate (ic ic le );
setContentView (ne w Dr a wing Su rfa c e Vi e w( t h i s)) ;

c la ss Drawi ngSu r f aceView ex ten ds SurfaceVie w impl e men t s


Android. Guía para desarrolladores lIfImI
Su rfaee Hol d er .Ca ll baek [

pub lie Su r f aee Hol de r mHol d e r ;

p ublie Dr awi ng Th read mThr e ad;

publ i e Dr awingSurfa e e Vi e w( Con tex t e) {


super (e );
init () ;

publi e vo i d i ni t() {
mHolder = getHolder ( ) ;
mHol d e r. a d dCa l lbaek (th i s ) ;
mHol d e r.se t Ty pe (S ur f aee Hol der . SURFACE_TY PE GPU);

publi e vo i d s urfae eCre a ted {Su r faee Hol d er holde r) {


mThread = ne w DrawingTh read();
mThread . sta rt() ;

publi e v o id surfae eDe s tro yed( Su rfa eeH olde r h olde r) {


mThread . waitForExit();
mThread = n u l l ;

publi e vo i d surfaee Changed{ SurfaeeHo lder h older,


i n t f ormat, int w, i n t h) {
mThr ead . onWindowRe si z e{ w, h ) ;

e l a ss DrawingThre ad extends Thread (


b oolean stop;
in t \-l j
in t h ;

b o olean e ha ng e d true;

DrawingTh r ead ()
super();
stop = fa lse;
w O;
h = O;

@Override
p u b li e vo i d run() {
EGL1 0 egl = (EGL10)EGLCon text.getEGL();
EGLDi splay dpy =
e gl .eglGetDi splay(EGL10 . EGL_DEFAULT_DISPLAY);
int[ ] ver si on = n ew int[ 2 ];
egl .egllnitialize{dpy , ve r s ion ) ;
int[ ] eo n fig Sp ee = {
EGL10 .EGL_RED_SIZE , 5,
EGL10 .EGL_ GREEN_ SIZE , 6,
EGL10.EGL_BLUE_SIZE, 5,
9. Gráfi cos y animaciones

EGL10.EGL_DEPTH_ SIZE, 16 ,
EGL10.EGL NONE
);
EGLConf ig [] co nfigs = new EGLCon f i g[ l];
i nt [ ] n um_ c on fi g = ne w i nt [ l ] ;
egl.eg IC hooseCo nfig (d py , conf igSpec , conf igs , 1 , num_co nfig ) ;
EGLConfig config = c o n f i g s [ O] ;

EGLCon t e x t context = eg l.eg IC reateContext(dpy , c o nfig ,


EGL10 . EGL_ NO_CONTEXT , n u l l ) ;
EGLSurface surface = n u l l;
GL10 gl = n u ll ;

whil e ( ! s to p )
int W, H;
boo lean u p d a t e d;
synchronized ( t his ) {
updated = this .cha nged ;
W = t h is .w ;
H = t h is . h ;
t his .cha nge d f al s e;
}
if (upd ate d) {

i f (su r fa c e ! = null) {
eg l .egI MakeC urrent (dpy ,
EGL10.EGL NO_SURFACE, EGL1 0 . EGL_ NO_SURFACE, EGL10 . EGL NO CONTEXT ) ;
e gl . e gIDe s t r o y Surf a c e( dpy, s u rface ) ;
)

su rface
egl .egICreateWindowSurface (dpy , co n f ig, mHol de r , n ull);
eg l .egIMakeCurrent (dpy, s u r face, su rface, conte xt ) ;
g l = (GL10) co ntext .getGL ( ) ;

gl . gI Di s abI e(GLl O. GL_ DITHE R) ;

g l . g I Hi n t (GLlO.GL_ PERSPECTIV E_CORRECTIO N_H I NT,


GLlO .GL_ FASTEST) ;

gl .gICI e a r Col or(l , 1, 1 , 1 ) ;


g l .gIEn ab l e (GLlO .G L_CULL_FACE ) ;
gl.g IS hadeMode l (GLlO.GL_S MOOTH) ;
g l .g I Enab Ie (GLl O.G L_D EPTH_ TEST ) ;
g l . g IViewport (O, O, W, H) ;
fI oa t ratio = ( fI oa t) W / H;
g l .gIMatr ixMode (GLl O.G L_PROJECTIO N);
g l .gILoad ldentity ( ) ;
gl . g IFr u s turnf(- r ati o, r a t i o, -1, 1, 1 , 1 0) ;
}

d rawFr a me (gl) ;

e g l .egIS wap Bu f fe rs (dpy , s urface };

if (egl . e gI Ge tError() ==
Android. GI/ía paradesarrolladores mil
EGLII .E GL_ CONTEXT_L OST ) (
Con t e xt e = ge tCo n tex t ( ) ;
if (e inst ane e of Aeti vity)
( (Aeti vit y) e ) . finish () ;

egl . eg I Make Curre nt (dpy , EGLIO .EGL_NO_SURFACE, EGLI O. EGL NO


SURFACE ,EGLIO .EGL_N O_CONTEXT) ;
egl.egIDestroySu rfa ee (dpy , surfa ee ) ;
egl .eg IDes troyCo n text (dpy , e on tex t ) ;
egl .egITerminate (dpy ) ;

p ub lie void onWi ndowRes i z e (int w, in t h) {


s y nehron ized (t hi s ) {
t h s . « = w;
í

t his . h = h ;
t h is.eha nged true ;

publie v o i d wai t Fo rE x i t ( )
th i s . s t o p = true ;
try {
j oin () ;
ea t e h (I n t e r r up t e dEx e e p t i o n e x ) {

pr i v a te voi d d rawFrame (GLIO g l )


II realiz ar aqui e l dibu jo .
)

El listado 9.15 genera una ventana blanca vacía. Básicamente es código para dibujar
y gestionar visualizaciones OpenGL ES. En primer lugar importamos las clases nece sa-
rias. Después implementamos una clase interna, que procesa todas las operaciones de
gestión de una superficie, como su creación, los cambios o su eliminación. Ampliamos
la clase Su r f aceVie we implementamos la interfaz Sur face Hol de r , que no s permite
obtener información de Android cuando cambia la superficie, por ejemplo al cambiarla
de tamaño. En Andro id todo debe hacerse de forma asíncrona, no podemos gestionar
superficies directamente.
A continuación creamos un subproceso p ara realizar el dibujo y un método i n i t
que utili za el método ge tH ol de r de Su rf aceView para acceder a SurfaceView y
añadir una retrollamada a trav és del método addCal lBack. Ya podemos implem entar
surfaceC re ated, surfaceChanged y sur faceDes t royed, métodos d e la clase
Ca l l b a c k que se desencadenan en función del cambio de estado de Surface.
&PI 9. Gráficos y animaciones

Una vez implementados los métodos Ca l lbac k, creamos un subproceso para reali-
zar el dibujo. Antes de poder dibujar, hay que crear un Con t e x t OpenGL Es y después
crear un control a la superficie, para poder utilizar el método de Con te xt para actuar en
la superficie a través del control. Ya podemos dibujar, aunque en el método drawFrame
no se realiza ninguna acción .
Si ahora ejecuta el código, solamente obtendría una ventana vacía pero lo que hemos
generado hasta el momento aparece, de alguna u otra forma, en cualquier aplicación
OpenGL ES que cree en Android. Por lo general se divide el código para incluir una clase
Acti vi ty al principio, otra clase que implemente la vista personalizada, otra que im-
plemente Su r f ace Ho l de r y Callback, y todos los métodos para detectar cambios en
la superficie así como el dibujo de los gráficos en el subproceso, y por último el código
que represente los gráficos. En el siguiente apartado veremos cómo dibujar un cuadrado
en la superficie y cómo crear un cubo animado.

Dibujar formas en OpenGl ES


En el siguiente ejemplo utilizaremos OpenGL ES para crear un sencillo dibujo, un
rectángulo, con primitivas OpenGL, que básicamente son píxeles, polígonos y triángulos.
Al dibujar el cuadrado dispondremos de una primitiva denominada GL_Triangle_
Strip, que acepta tres vértices (los puntos X, Y Y Z de una matriz de vértices) y dibuja
un triángulo. Los dos últimos vértices se convierten en los dos primeros del siguiente
triángulo, y el siguiente vértice de la matriz es el punto final. Esto se repite con todos
los vértices que incluya la matriz y genera el siguiente resultado (véase la figura 9.5),
donde vemos dos triángulos.
OpenGL admite un conjunto de primitivas, véase la tabla 9.1, con las que puede crear
desde sencillas formas geométricas como rectángulos hasta modelos 3D de personajes
animados.
Tabla 9.1. Primitivas OpenGL y sus descripciones.
:-.- • ';, _ - s

.íhr:r~:8?8J l:J1IIuJ11'n -ilbT-l1r':'1r;'ll - :


' .
GL POINT S Añade un punto en cada vértice.
GL LINE S Dibuja una línea por cada par de vértices proporcionados.
GL LINE STRI P Dibuja un conjunto continuo de líneas. Tras el primer vértice ,
traza una línea entre los vértices sucesivos y el anterior.
GL LINE LOOP Igual que GL_ LINE_ STRI P con la excepción de que también
conecta los vértices inicial y final.
GL TRIANGLES Por cada conjunto de tres vértices, dibuja un triángulo con las
esquinas especificadas por las coordenadas de los vértices .
GL TRIANGLE STRI P Tras los dos primeros vértices , cada vértice posterior utiliza los
dos anteriores para dibujar un triángulo.
GL TRIANGLE FAN Tras los dos primeros vértices, cada vértice posterior utiliza el
anterior y el primero para dibujar un triángulo. Se utiliza para
dibujar formas cónicas.
Android. Guia para desarrolladores mi

3 4
0.75

Triángulo 2

0.5

Triángulo 1
0.25

1t>- X- - - - - - - - - - - - - - - -

0.25 0.5 0.75

0.25

0.5

0.75

Figura 9.5. Dibujo de dos triángulos a partir de una matriz de vértices.

El listado 9.16 muestra cómo utilizamos una matriz de vértice para definir un cuadra-
do que pintar en la superficie. Para utilizar el código, añádalo directamente por debajo
del comentario / / r e ali zar a q u í e l di buj o .

Listado 9.16. OpenGLSquare.java.

g l .glC lear (GLl O.GL_COLOR_ BUFFER_BIT


GLIO .GL_DEPTH_BUFFER_BIT );

fl oat [ ] s q u a r e = ne w fl oa t[]

O. 2 Sf , O. 2 Sf , O.O f ,
O. 7 Sf , O. 2 Sf, O.Of ,
O. 2 Sf , O.7 Sf, O.Of ,
mi 9. Gráficos y animaciones

0 .7Sf , 0 .7Sf, O.Of} ;

FloatBuffe r s qua r e Bu f f;

Byt eBu ff e r bb =
Byt eB uf f e r . a l l o c a teD ire ct (squ a r e. l e ng t h * 4) ;
bb . o rder (By t eO r d e r.nat iveOrder ( » ;
squa re Buff = bb. a sFl oa tBuffe r ( ) ;
squa reBuff . pu t (square );
squa reBuff .posi tio n ( O) ;

g l .glMatrixMode (GLlO .GL_PRO JECT ION) ;


gl .glLoadldentity ( ) ;
GLU.gluOrtho2D(gl , 0 .Of ,1.2 f ,0.Of ,1 .Of );

g l . gl Ver t e x Po i n t e r( 3 , GLIO. GL_FLOAT, O, squareBuff ) ;


g l.g l EnableCl ientSta te (GLlO .G L_VERTEX_ARRAY) ;

gl .gl Cl e ar(GL lO .G L_ COLOR_BUFFER_BIT ) ;


gl .gl Col o r 4f(O, 1 ,1, 1 ) ;
g l.gl DrawAr ray s (GLIO.GL_TR I ANGLE_STRI P, O, 4 ) ;

Este código contiene multitud de comandos OpenGL. Lo primero que hacemos es


borrar la pantalla con glC Iea r , operación que debe hacer antes de cada dibujo. Tras
ello, creamos la matriz que representa el conjunto de vértices que componen nuestro
cuadrado. Como mencionamos antes, utilizaremos la primitiva OpenGL GL_ TRANGLE_
STRIP para crear el rectángulo mostrado en la figura 9.5, donde el primer conjunto de
vértices (puntos 1, 2 Y 3) forma el primer triángulo. El último vértice representa el tercer
vértice (punto 4) del segundo triángulo, que reutiliza los dos últimos vértices, 2 y 3, del
primer triángulo como sus dos primeros para crear el triángulo descrito por los puntos
2,3 Y 4. Para que lo comprenda mejor, OpenGL selecciona un tri ángulo y lo gira por la
hipotenusa. Crearemos un búfer para almacenar los datos del cuadrado. También indi-
camos al sistema que utilizaremos GL_ PROJECTION para nuestro modo de matriz, que
simplemente es un tipo de transformación de matriz que se aplica a todos los puntos de
la pila de ma trices .
Las siguientes tar eas están relacionadas con la configuración. Abrimos la matriz de
identidad y utilizamos el comando gl u Ort h o 2D (GLIO g l , flo a t Le f t , f Lo a t,
rig h t, f Loa t; b ottom, f Loa t; t o p ) para definir los planos de recorte asignados a
las esquinas inferior izquierda y superior derecha de la ventana. Ya podemos empezar
a dibujar la imagen. Para ello, primero utilizamos el método glVert e xPoin t e r (in t
si ze, int t ype, int stride, pointe r to array) que indica la ubicación
de los vértices del triángulo. El método tiene cuatro atributos: s i ze, typ e , s t r i de
y p o in t e r. Si ze especifica el número de coordenadas por vértice (por ejemplo, una
forma 2D puede ignorar el eje Z y utilizar solamente dos coordenadas por vértice),
typ e define el tipo de datos a utilizar (GL_ BYTE, GL_SHORT, GL_ FLOAT, etc.), stri-
de especifica el desplazamiento entre vértices consecutivos (cuántos valores sin utilizar
existen entre el final del vértice actual y el inicio del siguiente) y p o in t e r es una refe-
rencia a la matriz. Aunque la mayor parte del dibujo en OpenGL se realiza con distintos
tipos de matrices como la matriz de vértices, para ahorrar recursos del sistema todas
Android. Guía para desarrolladores BB
están des habilitadas de forma predeterminada. Para habilitarlas utilizamos el comando
OpenGLglEnable ClientState (arra y t yp e) que acepta un tipo de ma triz, en este
caso GL- VERTE X- ARRAY.
Por último u tilizamos la funció n glDrawAr r a ys para representar las matrices en
las primitivas OpenGL y crear el dibujo. La funció n gl Dr awAr rays (rnode , f i rst,
coun t ) tiene tres atributos: rnode indica la primitiva que representar, como GL_
TRI ANGLE_S TRI P; fir s t es el índice inicial de la matriz, que establecemos en O ya
que queremos representar todos los vértices de la matriz y count especifica el número
de índices que representar, en este caso 4.
Si ejecuta el código, verá un sencillo rectángulo azul sobre una superficie blanca
(véase la figura 9.6). No es especialmente apasionante pero la mayor parte del código
se util iza en muchos proyectos OpenGL. En el siguiente ejemp lo crearemos un cubo 3D
con diferentes colores en cada cara y lo giraremos en el espacio.
v:

~
Square

Figura 9.6. Cuadrado dibujado en nuestra superficie con OpenGL ES.

Formas y superficies tridimensionales con OpenGl ES


En este apartado utilizaremos gran parte del código an terior pero lo ampliaremos
para crear un cubo 3D giratorio. Aprenderemos a aña dir perspectiva a los gráficos para
generar sensación de profundidad. En OpenGL, la profundidad utiliza un búfer de
profundidad, que contiene un valor comprendido entre O y 1 para cada píxel. El valor
EIII 9. Gráficos y animaciones

representa la distancia percibida entre los objetos y nuestro punto de vista, de modo que
al comparar los valores de profundidad de dos objetos, el valor más próximo a Oparece
situarse en la parte frontal de la pantalla. Para emplear profundidad en nuestro progra-
ma, primero hay que habilitar el búfer de profundidad pasando GL_ DEPTH_TEST al
método glEnabIe. Tras ello, utilizamos glDepthFunc para definir la comparación de
los valores. En nuestro ejemplo utilizaremos GL_ EQUAL, definido a continuación (véase
la tabla 9.2), que indica al sistema que muestre objetos por delante de otros si su valor
de profundidad es menor.

Tabla 9.2. Indicadores para determinar la comparación de los valores del búfer de profundidad .

GL NEVER Nunca pasa.

GL LESS Pasa si el valor de profundidad entrante es menor que el valor


almacenado.

GL_EQUAL Pasa si el valor de profundidad entrante es igual al valor alma-


cenado.

GL_LEQUAL Pasa si el valor de profundidad entrante es menor o igual que


el valor almacenado.

GL GREATER Pasa si el valor de profundidad entrante es mayor que el valor


almacenado.

GL_NOTEQUAL Pasa si el valor de profundidad entrante no es igual que el valor


almacenado.

GL_GEQUAL Pasa si el valor de profundidad entrante es mayor o igual que


el valor almacenado.

GL ALWAYS Siempre pasa.

Al dibujar una primitiva, se realiza la prueba de profundidad. Si el valor supera dicha


prueba, el valor de color entrante sustituye al actual.
El valor predeterminado es GL_ LESS. Queremos que supere la prueba si los valores
también son iguales. De ese modo, los objetos con el mismo valor z se muestran en fun-
ción del orden en que se dibujen. Pasamos GL_ LEQUAL a la función.
Un factor importante para mantener la sensación de profundidad es la perspectiva.
En OpenGL, una perspectiva se suele representar por un punto de vista con planos
de recorte lejanos y cercanos, y planos superior, inferior, izquierdo y derecho, donde
los objetos más próximos al plano lejano parecen más pequeños,como se muestra en
la figura 9.7.
OpenGL proporciona la función gluPerspective (GLIO gl, fIoat fovy,
fIoat aspect, fIoat zNear, fIoat zFar) con cinco parámetros (véase la tabla
9.3) para crear perspectivas con facilidad.
Android. Guiapara desarrolladores BiI

F
R

Figu ra 9.7. En OpenGL, una perspectiva está formada por un punto de vista y planos
de recorte lejano (F), cercano (N), superior (T), inferior (B), izquierdo (L) y derecho (R).

Tabla 9.3 . Parámetros de la función gluPerspective.

gl Interfaz GL 10.
f ov y Campo de ángulo de visión, en grados, en la dirección Y.
aspect La proporción de aspecto que determina el campo de visión en
la dirección X. La proporción de aspecto es la proporción de X
(anchura) a Y (altura ).
zNear Distancia desde el espectador al plano de recorte cercano,
siempre positiva .
z Far Distancia desde el espectador al plano de recorte lejano, siempre
positiva .

Para ilustrar la profundidad y la p erspectiva, crearemos el proyecto Op enGLCube y


copiarem os el código del listado 9.15 en OpenGLCubeAct i v i t y.
Tras ello, añad imos do s variables al código (véase el listado 9.17) al inicio de la clase
interna Dr a wSu r f a c e Vi e w.

Listado 9.17 . OpenGLCubeActivity.java.

c lass DrawingSur faceV iew ex tends SurfaceV iew implements


Su r f ace Holder .Ca llback {
p ublic Su r face Holder mHolder;

f loa t xro t O.Of;


floa t y rot O.O f;

Usaremos las varia bles xrot e yrot más ade lan te para controlar la rotación del cubo.
A continua ción, por delante del mé todo draw Frame, aña da un nuevo método con el
nombre make FloatBu ffer, véase el listado 9.18.
9. Gráficos y animaciones

Listado 9.18. OpenGLCubeActivity. java .


pr otected Fl oatBuffe r makeF l oatBuffer (fl o at [ ] arr) (
ByteBuffer bb = ByteBuffer .allocateDirect (arr .length*4 );
bb .order (ByteOrder .nativ eOrde r( ) ) ;
FloatBuffer fb = b b. a s Floa t Bu f fe r ( ) ;
fb.p u t (arr ) ;
fb .positi on (O) ;
return fb ;

Este búfer es prácticamente idéntico al del listado 9.16, pero lo hemos abstraído del
método drawFrame para poder centramos en el código necesario para representar y
animar el cubo. A continuación, copie el siguiente código (véase el listado 9.19) al méto-
do d r a wFr a me.
Listado 9.19 . OpenGLCubeActivity.java.
pr ivate v oid drawFrame (GLIO g l , i nt wl , int h l ) (

f loat my cub e[] =


II FRENTE
-O.Sf , -O .Sf , O.Sf,
O. Sf , - O. Sf, O.Sf ,
- O. Sf, O.Sf , O.Sf ,
O.Sf , O.Sf , O.Sf ,
II TRASERA
- O. S f, -O .Sf, -O . Sf ,
- O. S f, O.Sf , - O. Sf ,
O.Sf , - O. S f , - O. Sf ,
O. Sf , O. Sf , - O. Sf,
I I I ZQUIERDA
- O. Sf , - O. Sf, O.Sf ,
- O. S f , O.Sf , O.Sf,
- O. S f, -O . Sf , -O. Sf ,
- O. S f , O.Sf, - O. Sf,
I I DERECHA
O.Sf , - O. S f , - O.Sf ,
O. Sf , O.Sf, - O. Sf ,
O. Sf, - O. S f , O.Sf,
O.Sf, O.Sf , O.Sf ,
II ARRIBA
- O. Sf, O.S f , O.Sf,
O.Sf, O.Sf, O. Sf ,
-O . Sf , O.Sf , -O .Sf ,
O. Sf, O.Sf, - O. S f ,
I I ABAJO
- O. Sf, -O .Sf , O.Sf,
- O. Sf, -O.Sf , - O. Sf,
O.Sf , - O. Sf, O. Sf,
O.Sf, - O. Sf, -O.Sf ,
j;

Fl oatBuffer c ubeBuf f ;

c u b e Bu f f = makeFl oatBuffer (mycube) ;


Android. Guía para desarrolladores lID
gl.glEnab1e(GL10.GL_DEPTH_TEST);
gl.glEnab1e(GL10 .G L_CULL_FACE ) ;
gl.g lDepthFunc (GL10.GL_LEQUAL) ;
gl .g lC1earDepthf ( 1 .0f );

gl .g lMatrixMode(GL10 .G L_PROJECTION ) ;
gl .glLoad Identity ( );
g l .glV iewpor t (0 ,0 ,w1,h1 ) ;
GLU.g1uPerspective (g l , 4S . 0f, « f1 o a t) w1) / h 1, lf , 1 00 f);

g l.g lMatrixMode (GL10.GL_MODELVIEW) ;


g l.g l Lo a dIdentity ( );
GLU.g1 u LookAt (g l, O, O, 3 , O, O, o, O, 1, O);

g l .glS hadeMode 1(GL1 0.GL_SMOOTH);

g l.g lVertexPoin ter (3 , GL10.GL FLOAT, O, cubeBuf f ) ;


gl.g l Enab1eC 1 ie n tSta te (GL10 .G L_VERTEX_ARRAY);

gl .glRo tatef (x rot , 1, O, O) ;


g l . g lRo tatef(yro t, O, 1, O) ;

g1.g1Co 1or4 f (1. Of , o, o, 1. Of ) ;


g l.g l DrawAr rays (GL10.GL_ TRIA NGLE_S TRI P , o, 4 ) ;
g l .g l DrawArrays (GL10 .G L_ TRIANGLE_S TRIP , 4 , 4 ) ;

g1.g 1Co 1or4f (0, 1. 0 f , O, 1. 0 f) ;


gl .glDrawArrays (GL10 .G L_ TRIANGLE_S TRIP , 8 , 4 );
g l.g lDrawArrays (GL1 0 .G L_ TRIA NGLE_STR IP , 12 , 4);

g 1.g1Co 1or4f (0 , O, 1. 0f, 1.0f ) ;


g l .glDrawArrays(GL 10 .GL_TR IANGLE STRIP, 1 6, 4);
gl .gl DrawAr rays (GL10 .G L_TR IA NGLE_S TRIP, 20 , 4 ) ;

xrot += 1. Of ;
yrot += O.S f ;

No hay grandes novedades en el código. Primero describimos los vértices de un


cubo, que se crea de la misma forma que el rectángulo del listado 9.16 (con triángulos).
Tras ello, definimos el búfer para los vértices, habilitamos la función de profundidad y
la de perspectiva para generar sensación de profundidad. Pasamos 45 . Of (45 grados)
a gluPerspective , para definir un punto de vista más natural.
AcontinuaciónutilizamoslafunciónGLU .gluLo okAt( GLlO gl , fl oat e yeX,
fI oat eyeY, fI oat eye Z, fIoat cent erX, fIoat center Y, fI oat cen-
terZ, fI oat up X, f Loa t; upY, f l. oat; upZ) para desplazar la posición de la
vista sin tener que modificar directamente la matriz de proyección. Una vez establecida
la posición de vista, activamos el suavizado del modelo y giramos el cubo en los ejes X
e Y. Tras ello, dibujamos las caras del cubo e incrementamos la rotación para que en la
siguiente iteración del dibujo, el cubo se dibuje en un ángulo ligeramente distinto. Si
ejecuta el código, verá un cubo 3D giratorio (véase la figura 9.8).
DI 9. Gráficos y animaciones

Figura 9.8. Cubo 3D giratorio en el espacio.

Puede experimentar con el valor f o v y para comprobar cómo afectan los cambios de
ángulo al cubo.

Resumen
En este capítulo hemos descrito someramente algunos de los temas relacionados con
las completas funciones gráficas de Android, como dibujos, animaciones y la implemen-
tación de Android del estándar OpenGL ES. Gráficos y visualizaciones constituyen un
tema amplio y complejo, pero como Android utiliza est ándares abiertos y bien defini-
dos, además de admitir un API excelente para gráficos, le resultará muy sencillo utilizar
la documentación de Android, el API y otros recursos para desarrollar desde un nuevo
programa de dibujo a juegos complejos.
En el siguiente capítulo pasaremos de los gráficos a trabajar con diversos medios.
Aprenderemos a utilizar audio y vídeo para sentar la base necesaria para crear comple-
tas aplicaciones multimedia.
10
ultimedia
En la actualidad, la gente utiliza los teléfonos móviles para todo menos para llamar,
desde chatear, navegar por la Web, escuchar música e incluso ver la televisión en direc-
to. Los teléfonos modernos deben ser compatibles con funciones multimedia para que
se consideren útiles. En este capítulo veremos cómo utilizar Android para reproducir
archivos de audio, ver vídeos, realizar fotografías e incluso grabar sonido.
Android admite funciones multimedia gracias al sistema multimedia de código abierto
OpenCaRE de PacketVideo Corporation. apenCaRE constituye la base de los servicios
de medios de Android, que ofrece en una sencilla API.
Analizaremos la arquitectura y servicios apenCaRE, para después utilizarlos a tra-
vés del API MediaPlayer de Android para reproducir archivos de audio, realizar foto-
grafías, reproducir vídeos y, por último, grabar vídeo y audio desde el emulador. Para
comenzar, nos adentraremos en la arquitectura multimedia de apenCaRE.

Multimedia y OpenCORE
Como la base de la plataforma multimedia de Android es OpenCaRE de PacketVideo,
en este apartado analizaremos la arquitectura y los servicios de OpenCaRE. Se trata de una
plataforma Java multimedia de código abierto que admite los siguientes elementos:

• Interfaces de terceros y codees de media de hardware, dispositivos de entrada y


salida, y directivas de contenido.
DI 10. Multimedia

• Reproducción de medios, envío en directo, d escargas y reproducción progresiva,


incluidos contenedores 3GPP, MPEG-4, AAC y MP3.
• Codificadores y descodificadores de vídeo e imagen, corno MPEG-4, H.263, AVC
(H.264) y JPEG.
• Codees de discurso corno AMR-NB y AMR-WB.
• Codees de audio corno MP3, AAC y AAC+ .
• Grabación de medios corno 3GPP, MPEG-4 YJPEG.
• Telefonía de vídeo basada en el estándar 324-M.
• Estructura de pruebas PV para garantizar la estabilidad; herramientas de creación
de perfiles para uso de memoria y CPU .

OpenCaRE proporciona esta funcionalidad en un conjunto determinado de servicios,


reproducidos en el siguiente diagrama (véase la figura 10.1).
.------- r-
Stteamlng fonnalosde FOImalOsde
Oesauga
3Gpp
3GPP """"",- ....,,,,,lo<
ASF 3GPP
- KTPP
Rea l f-- IMotion 3CPP
Fastrack MPEG4
RTPIRTSP MC
IMotion 'Motion
SOP MlR
MC
'--- '----
.------- r -
MlR
MP3
2-Way Campa"" emisión ASF
NÚCLEO CSVTC
SIP ova RN
3GPP VTC MPEG-2 WAV
3032 4... f-- f--
PushTo Sys lenu
H2 45
Me<fia FlUTE
Administrador de H.223
Base de datos Listas de Sha .. MecrsaFlO
directivasde reproducción '--- '----
multimedia
Oesaxl ificador contenido DRM
de video
MPE~/H _263
AVC
WMV9
Real Video
I Motores mulUmedla
I
1-- -
OeS<O<f<fica<lo<
de audio
Codee',
Cod<fica<lo<
Formatosde datos WMA
RealAud"1O
MP3
."""'"
AMR(NB,WB)
de video
AAC, HE-ACC.
1-- G.1t 1 Codeede imagen

~
MPEG4 Codeede video Codeede audIo HE-M CV2 G.726 JPEG
H~63 11 G.729
AVC

I
Codlfi<ado<
de audio
MC

Figura 10.1. Servicios y arquitectura de OpenCORE.

El SDK actual de Android no admite grabación de vídeo a través del API. Depende de
cada fabricante de teléfonos.

Corno puede apreciar en la imagen, la arquitectura de apenCaRE admite multimedia


y diversos codees. En el siguiente apartado nos adentraremos en estos aspectos y utiliza-
remos el API de Android pa ra reproducir archivos de audio.
Android. Guía paradesarrolladores lIiJI

Reproducir audio
Probablemente una de las necesidades multimedia básicas de un teléfono móvil sea
la capacidad para reproducir archivos de audio, ya sean nuevos tonos de llamada, MP3
o notas de sonido. El reproductor Media Player de Android es muy fácil de utilizar. A
nivel superior, sólo tiene que seguir los pasos descritos a continuación para reproducir
un archivo MP3:
1. Almacene el archivo MP3 en el directorio re s!rawdel proyecto (también puede
utilizar un URI para acceder a los archivos de la red o a través de Internet).
2. Cree una nueva instancia de Me d iaPlayer y haga referencia al MP3 mediante
la invocación de MediaPlayer. cre a t e () .
3. Invoque los métodos pre p a r e () y start () de Medi aPla yer.
Utilizaremos un ejemplo para ilustrar este sencillo proceso. En primer lugar creamos
un nuevo proyecto MediaPlayer Example con la actividad MediaPlayerActivi ty, y
una nueva carp eta raw bajo res! para almacenar los MP3. En este ejemplo utilizare-
mos un tono de llamada del juego Halo 3, que se obtiene de Me diaPlaye r. crea te.
Descargue la melodía de Halo 3 (o cualquier otro MP3) y gu árdela en el directorio r a w.
Tras ello, cree un sencillo Button para el reproductor de música, véase el listado 10.1.
Listado 10.1. main .xml para MediaPlayer Example.
<?xml ve rs i o n="l . O" e nc o di ng = 'lut f - 8 ? >
11

<Li ne a r La yo ut xmln s : android=''http:/ /schemas . a nd r o i d. com/apk/re s/andro id"


android : orientation= "vertica l "
android : layou t_width= " fi l l _pare nt"
android :layout_height= "fill_parent "
>
<Te x t Vi e w
a nd roid : layout_width="fill_paren t"
android : layout_h eight="wrap_conte nt "
a ndroid: text= "Simple Media Playe r "
/>

<Bu t t o n a nd r o i d : i d=" @+i d / p l a ys o ng "


a ndroid : l a you t _ "lÍ d t h=" fi ll_pare nt "
a ndroid :layout_heigh t="wrap_conte nt "
android : text= "Ha lo 3 Theme So ng "
/>
</ Li ne a r La you t>

Seguidamente tendremos que completar la clase MediaPlaye rAct i v i t y (véase el


listado 10.2).
Listado 10.2. MediaPlayerActivity.java.
pub lic c l a s s MediaPlayerAct ivity e x t e nds Acti vity {
@Override
pub lic v o i d o nCreate (Bundle i c i cle)
super.o nCreate (icicle );
lIiII 10. Multimedia

setCo ntentV iew (R. layout .main ) ;


But t on mybu tt on = (Bu t t on ) f i.nd v i ewü y Ld (R. i d. p layso ng ) ; 1
mybu t t on . s e t OnCl i c kLi s t e ne r (ne wBu t t on . OnCl i c kLi s t e ne r( )

publi c vo i d o nClick (Vi e "' v ) (


Me d i a Pl aye r mp =
MediaP la ye r .create (Medi aP l a ye r Acti vi t y .th i s, R.ra w.hal o theme ) ;
mp. sta rt{) ;
mp. setOnComple t i onLis t ene r (n e v OnCompletio nListe ner () {
p ub lic vo i d onCo mp le t ion (Me di a Pl a ye r arg O) (

) ;

) ;

Como puede apreciar, la reproducción de MP3 es muy sencilla. En el listado 10.2


hemos utilizado una vista creada en el listado 10.1 y asignamos un botón, p l a ys ong,
a mybutton, que después vinculamos a setOnClickListener () . Dentro del oyente
creamos la instancia MediaPlayer, por medio del método c r e a t e (Context c ontext ,
int re sourceid) , que simplemente adopta nuestro contexto y un ID de recurso para
los MP3. Por último, establecemos s e t OnComp le t i onLi s t e ne r, que realiza una tarea
al finalizar. Por el momento no hacemos nada pero puede cambiar el estado de un botón
o notificar al usuario que la canción se ha terminado y preguntarle si desea reproducir
otra diferente. En caso afirmativo, debe utilizar este método.
La figura 10.2 muestra el resultado que obtendrá si compila la aplicación y la ejecu-
ta. Haga die en el botón para escuchar la melodía de Halo 3 en el emulador a través de
sus altavoces. También puede controlar el volumen de la reproducción por medio de
los controles situados en el lateral del emulador.
A continuación veremos cómo rep roducir un archivo de vídeo.

Reproducir vídeo
La reproducción de vídeo es ligeramente más complicada que la de audio con el API
Media Player, ya que se necesita una superficie de visión en la que reproducir el vídeo.
Android dispone de VideoView para realizar esta tarea y se puede utilizar en cualquier
administrador de diseño. Además, ofrece diversas opciones de visualización, como las de
escala y polarización. Para iniciar la reproducción de vídeo creamos un nuevo proyecto,
Simple Video Player, junto al diseño mostrado a continuación (véase el listado 10.3).
Listado 10.3. main.xml: IU para Simple Video Player.
<? xml ve r s í.o rr- l . 0 11 e ncod ing = "ut f - 8 11 ? >
v

c Li ne ar Layo u txml ns :and ro i d= " h t tp://schemas. a ndro i d . com/ap k/res/ a nd roi d "
an d r oi d:o r i e nt a t ion="ve r t ica l "
a ndr oid : l a you t _ \1id th=" f i ll_pa r en t"
Android. ca« para desarrolladores lIiiI
and ro i d: layo u t_he ight= " f i l l _ pa r e nt "
>

<Vi deoView a nd ro i d : i d = "@+id/ v i de o"


android : l a y o u t _\.¡i d t h~" 3 2 0 p x"
and r oi d :l a you t _hei ght=" 240px "
/>
<But ton a ndro i d : i d = " @+i d/ p l a yvi d eo "
a ndro i d : te xt= " Pl a y Vi deo "
and ro i d : layout_height= "fil l _parent "
andr oid:layout_width= "fi l l _parent "
andr o id :paddi ngR ig h t ~ "4p x "

andro i d :enab l ed= "fals e "


/>
</ Li nea r La yo u t>

Figura 10.2. Ejemplo de reproductor.

En la actualidad, el emulador tiene problemas para reproducir contenido de vídeo en


determinados equipos y sistemas operativos. No se sorprenda si la reproducción de
vídeo o audio no ofrece la calidad esperada.

En el listado 10.3 simplemente hemos añadido VideoView y un botón para iniciar


la reproducción del vídeo.
mi 10. Multimedia

A continuación crearemos una clase para reproducirlo. Además de VideoView,


incluimos un botón que, al pulsarlo, muestre el panel VideoView, denominado
MediaController. De forma predeterminada se sitúa en la parte inferior de VideoView
y muestra la posición actual en el clip de vídeo. Además incluye botones para detener,
rebobinar y avanzar el vídeo (véase el listado 10.4).

Listado 10.4. SimpleVideo.java.


publ ie e l ass Simp l eVi de o extends Aeti vit y {

pr iva t e Vi deoVi e wmyVideo;


pri va te MediaController me;

@Ove rri de
publi e vo i d o nCr e a t e (Bundl e i e i e l e )
super.onCreate(ieiele);
getWindow() . s e t Fo r ma t (Pi xe l Fo r ma t . TRANSLUCENT) ;
s e t Con t e ntVi e w(R. l a yo u t . ma i n ) ;
ButtonbPlayVideo=(Button)findViewByld(R . id .playvide o);

bPlayVide o. s etOnCliekListener(newView .OnCliekLi stener( )


publ i e v oid o nClie k (View view) {
Si mp l e Vi de o . t hi s . me . s how () ; }
}) ;
this.myVideo=(Vide oView}findViewByld(R.id.video);
this.myVideo.setVideoPath("sdcard/te st.mp4");
thi s.mc=newMediaController(this) ;
thi s.mc. setMediaPlayer(myVideo);
thi s.myVideo.setMediaController{mc);
this.myVideo.requestFocus();

En primer lugar creamos una ventana translúcida necesaria para SurfaceView.


Añadimos un botón a VideoView e indicamos a Android que añadaMediaController
sobre VideoView, por medio del método show (). Tras ello, hacemos referencia a
VideoView y utilizamos su método setVideoPath para que busque en una tarjeta SO
(sdcard) nuestro archivo MP4 de prueba. Por último, configuramos MediaController
y utilizamos setMediaController () para realizar una retrollamada a VideoView
para notificar que ha concluido la reproducción del vídeo.
Antes de poder ejecutar la aplicación, es necesario definir sdcard en el emulador
(consulte un capítulo anterior). En primer lugar, cree una nueva imagen de tarjeta SO:
mksdcard 51 2Mmysdcard

Pulse Intro. Se crea una imagen FAT32 de 512 MB con el nombre mysdca rd para que
pueda abrirla en el emulador. Para ello, utilice lo siguiente:
emulat or - s dc a rd mysdcard

Envíe el archivo test. mp4 a la imagen de disco. Una vez enviado, puede iniciar la
aplicación SimpleVideo si ejecuta el proyecto desde el lOE con el emulador activo.
Obtendrá un resultado similar al siguiente (véase la figura 10.3).
Android. Guia para desarrolladores EmI

Figura 10.3. Reproducción de un vídeo MP4 en el emulador de Android.

Como puede apreciar, las clases VideoView y MediaPlayer simplifican el uso de ar-
chivos de vídeo. Cuando trabaje con este tipo de archivos, recuerde que el emulador tiene
problemas con archivos superiores a 1 MB, aunque no sucede en el teléfono Gl actual.

- ~- - - -- - - - - -- - - - -
1-"'" r ...
l ~lse'::.J
. _ .....

De forma predeterminada, Gt solamente admite los formatos MP4 y 3GP. Puede uti-
lizar un conversor para convertir vídeos de otros formatos a estos estándares. Cuando
aumente la presencia de Android en el mercado, aparecerán actualizaciones y los
reproductores serán compatibles con mayor cantidad de formatos.

Después de ver lo sencillo que resulta reproducir medios con el API Media Player de
Android, aprenderemos a utilizar la cámara y el micrófono incorporados en el teléfono
para capturar imágenes o audio.

Captu rar med ¡OS


La utilización de un teléfono móvil para realizar fotografías, grabar, filmar vídeos cortos,
etc., son algunas de las funciones que se esperan en este tipo de dispositivos. En este apar-
tado no sólo veremos cómo capturar medios desde el micrófono y la cámara, sino también
cómo escribir estos archivos en la imagen de tarjeta SO creada anteriormente.
lDiI 10. Multimedia

Para empezar, utilizaremos la clase Ca me r a de Android para capturar imágenes y


guardarlas en un archivo.

Comprender la cámara
Una función muy importante en los teléfonos móviles modernos es su capacidad para
realizar fotografías o incluso vídeo por medio de una cámara incorporada. Algunos mo-
delos permiten incluso utilizar el micrófono de la cámara para capturar audio. Android,
evidentemente, admite todas estas funciones y ofrece distintas formas de interactuar con
la cámara. En este apartado veremos cómo interactuar con ella y realizar fotografías. En
el siguiente, utilizaremos la cámara para grabar vídeo y guardarlo en una tarjeta SO.
Crearemos un nuevo proyecto llamado SimpleCamera para ilustrar cómo conectar
la cámara del teléfono para capturar imágenes. En este proyecto utilizaremos la clase
Ca mer a (http:/ / c ode.google. c om/android/r ef er ence/andr oid/hard-
wa re /C a mer a. h tml) para vincular la cámara del emulador (o de un teléfono) a un
objeto Vie w. La mayor parte del código que crearemos permite mostrar la entrada
de la cámara pero la acción de realizar la fotografía se condensa en un único método:
takePicture(Camera.ShutterCallbackshutter, Camera.PictureCallback
r aw, Came r a . PictureCallback j peg) . Cuenta con tres retrollamadas que le per-
miten controlar la forma de realizar la fotografía. Antes de avanzar con la clase Ca me r a
y aprender a utilizar la cámara, crearemos un proyecto. Crearemos dos clases y, como
la clase principal es extensa, la dividiremos en dos secciones. El listado 10.5 muestra la
primera sección, Ca me r a Exa mp l e . java.

El emulador de Android no permite la conexión a dispositivos de cámara en su equipo


como webcam, de modo que todas las imágenes mostrarán una especie de tablero de
ajedrez como el ilustrado en la figura lOA. Puede conectar una cámara Web y obtener
imágenes yvídeo en directo, pero para ello necesita realizar ciertas modificaciones.Puede
encontrar un excelente ejemplo en el sitio Web de Tom Gibara, donde ofrece un proyecto
de código abierto para obtener imágenes en directo de una cámara Web (http : / / www •
t omgi bara . co m/android/ came r a - s ource). Es posible que en futuras versiones del
SDK, el emulador admita conexiones a cámaras del hardware en el que se ejecuta.

Listado 10.5. CameraExample.java.


p ublic c lass SimpleCamera ex tends Act ivi ty i mpleme n ts Sur face Holder. Ca l lback (

p rivate Camera camera ;


p rivate boo lean isPreviewRunning = false ;
private S imp leDateFormat ti meS tampFo rma t = new
Simp le DateFormat("yyyyMMddHHmmssSS ");

private Su rfaceView su rf aceV iew ;


Android. Guía para desarrolladores l1li
private SurfaeeHolder surfaeeHolder ;
pri vate Uri targe tResouree ~ Media . EXTERNAL_ CONTENT UR I ;

pub lie void onCrea te (Bund l e ieiele) {


supe r .onCreate (ie ie le ) ;
Log. e (ge t Cl a s s () . ge t Simp l e Na me(), " o nCr e a t e");
getW i ndow ( ) .set Format (Pixe l Format. TRANS LUCENT);
setContentView (R .layout.ma i n ) ;
sur faeeV iew = (Su r f a e e Vi e w) fi ndViewByl d (R. i d. surfaee ) ;
surfaeeHol de r=surfaeeView. getHolder ( );
s urfaeeHo lde r .addCall b aek (t his) ;
surfaeeHol de r .setType (SurfaeeHo lder .SURFACE_ TYPE_PUSH_BUFFERS) ;

@Over r i de
publ i e b ool e a n o nCreateOptionsMe nu (a nd r o i d. vi ew. Me nu me nu )

Me nu l t e m i t em =m e nu. add( O, O, O, "ViewPho tos? " ) ;


i t em. s e t OnMenul t emCli e kLi s te ne r( ne w
Men ul t e m. OnMenu ltemC liekListener () {

pu blie b oo l e a n o nMenu I temCliek (Me nul t e m i tem)


In t en t i nten t = rie w In t en t (Intent. ACTI ON_VI EW,
SimpleCamera .this . ta r ge tRes ou ree ) ;
star t Aet ivity (int ent );
return tru e;
}
j) ;
return t rue;

@Override
p r o t e eted vo i d on Re s t o r el n sta ne e Stat e( Bun d l e s a vedl ns t an ee State)
super .onRestorelnsta neeS ta te (saved l n s taneeS tate );

Camera . Piet u reCallbaek mPietureCa llbaekRaH = ne w


Camera. Pietu reCallbae k () (
pu b l i e voidon PietureTake n (b yt e[] d at a, Camera e )
SimpleC a mera .this .eamera. star tP revieH ( );

j;

Camera . Shu tterCa l l b aek mSh utte r Callb a e k = new Camera . Sh utterCallbae k ()

Pub lie void onShutter () { }


}
j ;

Este listado es muy sencillo. Primero definimos variables para gestionar surface-
View y configurar la vista. Tras ello, creamos un sencillo menú y una opción de menú
sobre la superficie para que el usuario haga die en el botón MENU del teléfono mien-
tras se ejecuta la aplicación . De este modo, se abre el buscador de imágenes de Android
y el usuario puede ver las fotografías de la cámara. Seguidamente creamos el primer
10.Multimedia

Pi ctureCallba ck, que se invoca al realizar una fotografía. Esta primera retrollamada
captura el único método de Pi cture Callba ck, o n Pictu re Ta ke n (b yt e [] dat a,
Ca me ra camer a ) ,para obtener los datos de imagen directamente de la cámara. Después
creamos Shu t te rCa l l b ack, que se puede utilizar con su método onS hutter () para
reproducir un sonido pero en este caso no lo invocamos. El listado 10.6 muestra la con-
tinuación del código de Ca meraExa mp l e . jav a.

Listado 10.6. Continuación de CameraExample.java .

@Over r i de
public boolean onxeypown ( i n t keyCode , KeyEvent event)
ImageCaptureCa llback camDemo = nu ll;
if ( ke yCo d e == KeyEve nt . KEYCODE_ DPAD_ CENTER) {
try {
St ring f i lename = this. timeStampFormat. format (new Date () ) ;
ContentValues va lues = new ContentValues () ;
va lues .put (MediaCo lumns . TITLE , filename );
va lues.put (ImageCo lumns .DESCRIPTION ,
"Ima g e from Android Emulator") ;
Uri uri = getContentReso lver () . insert (
Media .EXTERNAL_CONTENT_URI , va l ues);
camDemo = new I ma g e Ca p tu r e Ca l l b a c k (
getContentReso lver () .openOutputStream (uri )) ;
) catch (E x c e p t i o n ex ) (
)
)
if ( ke yCo d e == KeyEvent.KEYCODE_BACK ) {
return super . o n Ke y Do wn( k e y Co d e, event) ;

if (keyCode = = KeyEvent.KEYCODE_DPAD_C ENTER)


this. c a me r a. t a ke Pi c tu r e (this .mSh u t t e r Callb a c k ,
this . mPi c t u r e Ca l l b a c kRa w, this.camDemo ) ;
return true;

return false ;

@Override
protected void onRes ume ( ) {
Log . e (getClass () . getSimp leName ( ) , "onResume ") ;
super. o n Re s ume {) ;

@Override
protected void onSave InstanceState (Bu n dl e outState)
super . o nSa v eIn s t an c e S t a t e( o u t S t a t e) ;

@Override
protected void o nStop ( )
[
super. o nS t o p () ;
Android. Guía para desarrolladores lID

public void surfaceChanged (Su r f a c e Hold e r h o lder , int forma t , int w, int h )
if (this . isPreviewRunning ) {
this . c a me r a . s t o p Pr e vi e w() ;

Camera . Parameters p = this. camera . get Parameters () ;


p . setPrevie wSize (\1, h ) ;
th is .camera .setParameters (p ) ;
t h is .camera .setPreviewDisplay (ho lder );
t h is .camera .startPreview () ;
this . i s Pr e v i e \1Ru n n i ng = trua ;

public void s ur faceC reate d (Su rfa c eHolde r h o l d e r)


this. camera = Camera . open ( ) ;

public void su rfaceDe stroyed {Sur fa c e Hol d er ho l der )


this. c a me r a . s t o p Pr e vi ew{) ;
this. i s Pre vi e \1Ru n n i ng = falsa ;
this. c ame r a. r e l e a s e() ;

El listado 10.6 es más complicado que el listado 10.5 aunque gran parte del código
permite gestionar la superficie para la vista previa de la cámara. Como puede apreciar,
la primera línea es el inicio de la implementación del método onKeyDown, que comprue-
ba si se ha liberado la tecla central del teclado. En caso afirmativo, definimos la creación
de un archivo y, por medio de Irnage CaptureCallba ck, que definiremos en un lis-
tado posterior, creamos OutputStrearn para escribir los datos de imagen, incluyendo
no sólo la imagen sino también el nombre de archivo y otros metadatos. Seguidamente
invocamos el método takePicture () y le pasamos tres retrollamadas, rnShutterCa-
llback, rnPicture CallbackRaw y carnDerno, donde rnPictureCallbac kRaw es la
imagen sin procesar y c arnDerno escribe la imagen en un archivo de la tarjeta SD, véase
el listado 10.7.

Listado 10.7. ImageCaptureCallback.java.

p ublic c lass I mag e Ca p tu r e Call b a ck i mp l e ments Pic ture Callb a c k {

pr ivateOutputStreamf ilou tputStream;

p ub lic Ima g e Capt u reCallb a c k (Outpu t St r e a m fi loutputSt ream)


th is . filo u tputStream = f i l o u t pu t St r e am ;

p ub lic void o nPicture Taken (b y t e [] da ta, Camera came ra )


try (
t his .fi loutputStream . write (data ) ;
t his .fi lou tput Stream .flush ();
th is .fi loutputStream.close () ;
lID 10. Multimedia

} catc h (Exc e p t i o n ex ) (
ex . p r i nt StackTrace ( ) ;

En este listado, la clase implementa la interfaz Pi ctureCallback y proporciona dos


métodos. El constructor crea un flujo en el que escribir los datos y el segundo método,
onPi ctureTaken, acepta datos binarios y los escribe como JPEG en la tarjeta SD. Si
gene ra este proyecto e inicia el emulador con la imagen de tarjeta SD creada en un apar-
tado anterior, obtendrá un resultado similar al siguiente (véase la figura 10.4) al iniciar
la aplicación SimpleCamera desde el menú de Android. Si se fija en la imagen, verá un
extraño fondo de cuadros blancos y negros con un cuadro gris móvil. Es un patrón de
prueba generado por el emulador para simular una retransmisión en directo ya que el
emulador no la ha obtenido de la cámara.

-.. --
-.-.----.--.-. El fill1mO 10:00 PM

. "_.--.
SlmpleCamera

. ... --
-_-. -- . '
.

.
1 __

~
- -- 1

Figura 10.4. Patrón de prueba de la cámara del emulador


mostrado en la aplicación SimpleCamera.

Si hace clic en el botón central del emulador, la aplicación realiza una fotografía. Para
verla, haga clic en el botón MENU, que muestra un menú en la ventana de vista de la
cámara con una única opción, View Pictures. Al seleccionarla, accederá al explorador de
imágenes de Android, y verá marcadores de posición de imágenes de Android que re-
presentan el número de capturas de la cámara. También verá los archivos JPEG escritos
en la tarjeta SD si abre DDMS en Eclipse y se desplaza hasta sdcard>bin>dcim>Camera.
La figura 10.5 muestra un ejemplo.
Android. Guía para desarrolladores lID
el FileExplorer ¡;:¡
Name
(C. dala
(C. sdcard
(C. dcim
(C. Camera
~ 1228874159048.jpg
~ 1228874160509.jpg
(C. system

Figura 10.5. El emulador de Android muestra marcadores


de posición para todas las fotografías realizadas.

Como puede apreciar, trabajar con la cámara en Android no resulta especialmente


complicado. Para ver el comportamiento de una cámara real, tendrá que probar en un
dispositivo hasta que el emulador permita conectarse a una cámara de su ordenador.
Pero esto no debe impedir que desarrolle aplicaciones para cámara y muchas de las apli-
caciones existentes de Android ya las utilizan, desde juegos a una aplicación que utiliza
una imagen de su rostro para desbloquear el teléfono. Después de ver el funcionamiento
de la clase Camera en Android, aprenderemos a capturar o grabar audio desde el mi-
crófono de la cámara. En el siguiente apartado analizaremos la clase MediaRecorder
y guardaremos las grabaciones en la tarjeta SO.

Capturar audio
Vamos a utilizar el micrófono incorporado para grabar audio. En este apartado uti-
lizaremos el ejemplo MediaRecorder de la lista de programadores de Android de
Google, que puede encontrar en http://groups.google.com/group/android-
developers/files . Hemos actualizado ligeramente el código mostrado.

Al cierre de esta edición, Google Android SDK 1 no permite capturar audio desde el
emulador a través del ordenador pero es probable que en futuras versiones del SDK
se pueda hacer.

En Android, la grabación de audio o vídeo sigue el mismo proceso:


1. Cree una instancia de android. media. MediaRecorder.
2. Cree una instancia de android.content.ContentValues y añada propie-
dades como TITLE, TIMESTAMP y MIME_ TYPE .
IJII 10. Multimedia

3. Cree una ruta p ara alm acenar los datos por medio de an d r o i d. co ntent .
Cont entRe so l ve r .
4. Para definir una vista p re via en u n a superficie de vi sualización, utilice
MediaRec order. setP r eviewDi spl a y() .
5. Establezca el origen del audio, con MediaRecorde r . se t Audio Source () .
6. Establezca el formato de archi vo de salida con MediaReco rde r . se tO utpu t
Format () .
7. Establezca la codificación del audio, con Me di a Re c o rd er . s e t Au di o
Encoder () .
8. Utilice p repare () y sta rt () para preparar e iniciar las grabaciones.
9. Utilice stop () y r el ea s e () para detener y limpiar el proceso de grabación.

Aunque la grabación no es un proceso especialmente complejo, sin duda resulta


más complicado que el de reproducción. Para comprender el funcionamiento de la
clase MediaRecorder, analizaremos una aplicación. Cree una nueva con el nombre
SoundRecordingDemo. Seguidamente, modifique el archivo And r oidMani fe s t . xml
y añada la siguiente línea:
<use s - pe rm iss io n android : n a me =" a n d ro i d . permissi on . RECORD_ AUDIO " />

De este modo la aplicación puede reg istrar los archivos de audio y reproducirlos.
Tras ello, cree la siguiente clase (véase el listado 10.8).

Listado 10.8. SoundRecordingdemo.java.

public class SoundRe c o rdingDemo extends Act i v i ty {

MediaRe co rder mRe c o r d e r;


File mSamp l eFil e = null;
static final St r i n g SAMPLE_ PREF I X = "re c ord ing " ;
static final String SAMPLE_E XTENSION = ". mp 3";

private static final St ring TAG="SoundRe c ordingDemo";

@Override
publ ic void o n Cre a t e (Bundle s aved I n stanceState )
super. onCr e a t el s a v e dI n s t a n c e S t ate ) ;
s e tCo n te ntVi ew{R . lay o u t.mai n ) ;
this. mRec order = new MediaRe corde r () ;

Butto n sta rtRecording = (Button ) f i ndViewById (R . id . s ta r t re cord i n g ) ;


Butto n st opRecording = (But t on) f indViewById (R. id . s t o p r e c o r d i n g ) ;

s t a r t Re c o r d i ng. s e tOnCl ic k Lis t e n e r {n e w Vi e w.O nCl ic k Li s t e ner ( ) {

public void onCli ck (Vi ew v )


s t a rtRe c o r d i n g ( ) ;
Android. Guía para desarrolladores IJII
I
)) ;

stopRecording .setOnClickLi ste ner (newV ie w.OnClickLi ste ner ( ) (

public void o nClic k (Vi e w v )


s top Reco rd i ng ( ) ;
addToDB( ) ;

1) ;

protected void addToDB () {


Conte ntVa l ues va l ues = new ContentVa l ues (3) ;
long c u r r e nt = Sys tem. c urrentTimeMi l li s () ;

va l ues .put {MediaCo lumns . TI TLE, " t e s t _ au d i o");


va l ues . pu t (Me d i a Columns . DATE_ADDED, {int) (cu r r ent / 1000 ) );
va l ue s . pu t (Me d i a Co l umn s. MI ME_ TYPE, "au d i o / mp 3");
va l ue s . p u t (Me d i a Co l umn s . DATA, mSampleFile.ge t Abs olu t eP ath()) ;
Conte ntReso l ve r contentReso l ver = getCo n te ntReso l ve r () ;

Ur i base = Med i a St o r e . Aud i o .Media. EXTERNAL_CONTENT_URI;


Uri ne wUri = co nte n tReso lver . i ns er t (b a s e, va l ues ) ;

se nd Broadcast (ne w I nten t ( I n t en t . ACTION_MEDI A_SCANNER_SCAN_FI LE, newUri)) ;

protected void s ta r t Record i ng () (


this .mRecorde r = new Medi aRe c order () ;
this.mRe c order.setAudi oSource(Medi aRe c o rder. Audi oSou r c e. MI C);
this .mRe c order.se t OutputForma t( Medi a Re c o rde r. OutputFo rma t.THREE GPP ) ;
this.mRe c o r der. s e tAudi oEnc od e r( Me d i aRe c or de r .Audi oEnc ode r. AMR_NB) ;
this.mRe c o r de r. s e t Out p ut Fil e(this. mSa mpl eFile . ge t Abs olu t e Pa th()I ;
this.mRe c order. p r epa r e() ;
this. mRe c order. s t art();
if (th i s . mSampleFil e == null)
File samp le Dir = Environment . g e tExte rn al Stora ge Dire c t or y () ;

try {
this . mSampleFile = Fil e. c r e a t e Te mp Fi l e (
So undReco rdi ng Demo. SAMPLE_ PREFIX ,
So un dReco rdi ng Demo . SAMPLE_ EXTENS I ON, sampleDi r ) ;
I catch (IOE xc eption e l {
Log. e (TAG, " s dc ard access er ror " ) ;
return ;

protected void s topReco r d i ng ()


this. mRe c order.s t op();
this.mRe c order.relea s e();
lID 10. Multimedia

Como puede apreciar, la primera parte del código consiste en crear los botones y oyen-
tes de botón para iniciar y detene r la grabación. El primer eleme nto en que debe fijarse
es el método addTo DB (), en el que establecemos todos los metadatos del archivo de
audio que guardar, incluido el título, fecha y tipo de archivo. Seguidamente invocamos
I nt ent ACT ION_ MEDI A_SCANNER_SCAN_FI LE para notificar a las aplicaciones que
se ha creado un nuevo archivo de audio. De este modo podemos utilizar Music PIayer
para buscar nuevos archivos en una lista de reproducción y reproducirlos.
Tras finalizar el método a d dT o DB, creamos el método startRecord ing, que crea
un nuevo Medi a Re c o r d er. Como indicamos al inicio de este apartado, establecemos el
origen del audio, que es el micrófono, definimos el formato de salida como THREE_ GP P,
establecemos el tipo de codificador en AMR _ NB Yla ruta del archivo de salida para escri-
bir el archivo. Después utilizamos los métodos p r epa r e ( ) y s tart () para habilitar
la grabación de audio.
Por último creamos el método stopReco rd i ng () para impedir que Medi aRe c orde r
grabe audio. Para ello utilizamos los métodos s top () y r el ea s e () . Si genera esta apli-
cación y ejecuta el emulador con la imagen de tarjeta SD de un apartado anterior, podrá
iniciar la aplicación desde Eclipse y pulsar el botón Start Recording. Transcurridos unos
segundos, pulse el botón Stop Recording y abra DDMS; podrá desplazarse hasta la car-
peta sdc a r d y ver las grabaciones, véase la figura 10.6.

(O data
(O sdeard
(O dcim
~ l<eordingl3065.mp3
~ reeording58398.mp
(O system

Archivo de audio
generado por
Sound Recording
Demo grabada en
la sdcard

Figura 10.6. Ejemplo de archivos de audio guardados en la imagen de tarjeta SO del emulador.

Si reproduce música en el sistema de audio de su equipo, el emulador de Android la


detecta y la graba directamente desde el búfer de audio (no desde el micrófono). Puede
comprobarlo si abre Music Player de Android y selecciona Playlists>Recently Added.
Reproducirá el archivo grabado y podrá escuchar lo que estuviera reproduciendo en su
equipo en ese momento. Aunque en la actualidad Android solamente le permite grabar
Android. Guía para desarrolladores lID
audio, Google tiene pensado añadir compatibilidad para grabar vídeo. También se uti-
lizará la clase MediaRec o rder para grabar vídeo proveniente de la cámara, como se
hace con el audio.

Resumen
En este capítulo hemos visto cómo utiliza el SOK de Android las funciones multime-
dia y cómo puede reproducir, guardar y grabar vídeo y sonido. También hemos anali-
zado las distintas opciones que ofrece MediaPlayer de Android para los programadores,
desde un reproductor de vídeo incorporado hasta su amplia compatibilidad con forma-
tos, codificaciones y estándares.
También hemos aprendido a interactuar con otros dispositivos de hardware conec-
tados al teléfono, como micrófonos y cámaras. Hemos utilizado el SOK para crear una
imagen de tarjeta SO para simular dichas tarjetas en el emulador y hemos usado la apli-
cación MediaRecorder para grabar audio y guardarlo en la tarjeta SO.
Aunque al cierre de esta edición el SOK y el emulador de Android no proporcionen
una forma perfecta de interactuar con una cámara Web o un micrófono en su plataforma
de desarrollo, puede crear verdaderas aplicaciones multimedia con el SOK, como hacen
algunos proveedores en sus plataformas telefónicas. Android le ofrece en la actualidad
todo lo necesario para crear aplicaciones completas y atractivas, y su compatibilidad con
estándares abiertos y de la industria garantiza que las aplicaciones se pueden utilizar en
una amplia variedad de teléfonos.
En el siguiente capítulo aprenderemos a utilizar los servicios de ubicación de Android
para interactuar con GPS y mapas. Al combinarlo con lo visto en este capítulo, podrá
crear su propia aplicación GPS que no sólo proporciona instrucciones por voz, sino que
también puede responder a comandos de voz.
11
Ubicación
Un dispositivo móvil con completas funciones de ubicación es muy potente. La com-
binación de detección de ubicaciones con acceso a datos de red está cambiando el secto r
y aquí es donde sobresale Android. No es la única plataforma que ofrece estas prestacio-
nes, evidentemente, pero se distingue del resto por una estructura de API de ubicación
fáciles de utilizar (Google Maps) y por su naturaleza de código abierto.
Desde consultas directas de red hasta triangulación con torres e incluso GPS, un
dispositivo Android tiene acceso a distintos tipos de LocationProvider que puede
utilizar para acceder a datos de ubicación. Los diferentes proveedores ofrecen una com-
binación de mediciones relacionadas con la ubicación, que incluyen latitud y longitud,
velocidad, localización y altitud.
GPS es el proveedor de ubicación más conocido con el que trabajaremos en la plata-
forma Android, ya que es la opción más completa y precisa. No obstante, algunos dispo-
sitivos pueden incluir o no receptores GPS, o puede que la señal GPS no esté disponible.
En esos casos, la plataforma Android le permite salir con elegancia y consultar otros
proveedores si falla la primera opción. Puede configurar los proveedores disponibles y
conectarse a los mismos por medio de la clase Loc at i onMan age r.
La detección de ubicación abre un nuevo universo de posibilidades para el desarrollo
de aplicaciones. Comenzamos a apreciar lo que los programadores pueden hacer con in-
formación de ubicación en tiempo real y un acceso a datos de red cada vez más rápido y
fiable. En este capítulo crearemos una aplicación que combina detección de ubicación con
datos de la NOAA (Administración Nacional Oceánica y Atmosférica Estadounidense).
En concreto, nos conectaremos al NDBC (Centro Nacional de Datos de Boyas) para re-
cuperar datos de boyas situadas en la costa estadounidense (y algunos barcos NOAA). Sí,
hemos dicho datos sobre boyas. Gracias al sistema NOAA-NDBC, que consulta sensores
lB 11. Ubicación

instalados en boyas y ofrece los datos en suministros RSS, podemos recuperar información,
en función de la ubicación actual, y mostrar datos sobre condiciones meteorológicas como
la velocidad del viento, la altura de las olas o la temperatura. (Aunque no analizaremos
detalles no relacionados con la ubicación, como el uso de HTTP para obtener los datos RSS,
la descarga correspondiente a este capítulo incluye el código completo de la aplicación).
La aplicación, que llamaremos Wind and Waves, dispone de varias pantallas principales,
incluida una pantalla MapActivi t y de Android con MapView. Estos componentes se
utilizan para mostrar y manipular información de datos, véase la figura 11.1.

Figura 11.1. Pantallas de la aplicación Wind and Waves.

Es evidente que el acceso a datos de boyas tiene un público de destino limitado, prin-
cipalmente para usos marítimos (y sólo con boyas fijas en America del Norte y diversos
barcos que utilizar como puntos de datos mundiales) pero el objetivo es ilustrar la am-
plia gama de posibilidades disponibles y conseguir algo exclusivo. Además de esta ex-
clusividad, esperamos que la aplicación resulte interesante y que utilice muchas de las
prestaciones de Android relacionadas con los servicios de localización.
Android. Guía para desarroll