Está en la página 1de 453

INTRODUCCIÓN

ÍNDICE
1.1 INTRODUCCIÓN .............................................................. 3
1.1.1 Qué es Android .................................................................3
1.1.2 Proyecto libre (Open Source) ..........................................3
1.1.3 Su historia ..........................................................................3
1.1.4 Inconvenientes de Android..............................................4

1.2 QUÉ ES ECLIPSE ............................................................. 5


1.2.1 El Consorcio Eclipse ........................................................5
1.2.2 Instalación de Java Developmente Kit (JDK)................6
1.2.3 Instalación de Eclipse ......................................................7
1.2.4 Instalación de las librerías de Android...........................9
1.2.5 Añadir versiones y componentes de Android ............19
1.2.6 Definición del dispositivo virtual de Android ..............25
2
Introducción

1.1 INTRODUCCIÓN

1.1.1 Qué es Android

Android es un sistema operativo, inicialmente diseñado para teléfonos móviles como


los sistemas operativos iOS (Apple), Symbian (Nokia) y Blackberry OS.

En la actualidad, este sistema operativo se instala no sólo en móviles, sino también en


múltiples dispositivos, como tabletas, GPS, televisores, discos duros multimedia, mini
ordenadores, etcétera. Incluso se ha instalado en microondas y lavadoras.

Está basado en Linux, que es un núcleo de sistema operativo libre, gratuito y


multiplataforma.

Este sistema operativo permite programar aplicaciones empleando una variación de


Java llamada Dalvik, y proporciona todas las interfaces necesarias para desarrollar fácilmente
aplicaciones que acceden a las funciones del teléfono (como el GPS, las llamadas, la agenda,
etcétera) utilizando el lenguaje de programación Java.

Su sencillez principalmente, junto a la existencia de herramientas de programación


gratuitas, es la causa de que existan cientos de miles de aplicaciones disponibles, que
extienden la funcionalidad de los dispositivos y mejoran la experiencia del usuario.

1.1.2 Proyecto libre (Open Source)

Una de las características más importantes de este sistema operativo reside en que es
completamente libre. Es decir, ni para programar en este sistema ni para incluirlo en un
teléfono hay que pagar nada. Por esta razón, es muy popular entre los fabricantes de teléfonos
y desarrolladores, ya que los costes para lanzar un teléfono o una aplicación son muy bajos.

Cualquier programador puede descargarse el código fuente, inspeccionarlo,


compilarlo e incluso modificarlo.

1.1.3 Su historia

Android era un sistema operativo para móviles prácticamente desconocido hasta que
en 2005 lo compró Google.

En noviembre de 2007 se creó la Open Handset Alliance, que agrupó a muchos


fabricantes de teléfonos móviles, procesadores y Google. Este año se lanzó la primera versión
de Android, junto con el SDK (del inglés, Software Development Kit, que significa Kit del
desarrollo de software) para que los programadores empezaran a crear sus aplicaciones
para este sistema operativo.

3
El despegue del sistema operativo fue lento porque se lanzó antes el sistema operativo
que el primer terminal móvil, aunque rápidamente se ha colocado como el sistema operativo
de móviles más vendido del mundo.

En febrero de 2011 se anunció la versión 3.0 de Android, cuyo nombre en clave es


Honeycomb, que está optimizada para tabletas en lugar de para teléfonos móviles.

Versiones disponibles:

Las versiones de Android reciben nombre de postres en inglés. En cada versión el


postre elegido empieza por una letra distinta siguiendo un orden alfabético:

C: Cupcake (v1.5), magdalena glaseada.


D: Donut (v1.6), rosquilla.
E: Éclair (v2.0/v2.1), pastel francés conocido en España como pepito.
F: Froyo (v2.2), (abreviatura de «frozen yogurt») yogur helado.
G: Gingerbread (v2.3), pan de jengibre.
H: Honeycomb (v3.0/v3.1), panal.
I: IceCream Sandwich (4.0), sandwich de helado.
J: Jelly Bean (¿¿??), gomitas de gelatina)

En el siguiente enlace puedes encontrar una descripción de la funcionalidad que


incluye cada versión de Android.

1.1.4 Inconvenientes de Android

Android ha sido criticado muchas veces por la fragmentación que sufren sus
terminales (con versiones distintas de Android), ya que las actualizaciones no se despliegan
automáticamente en estos terminales una vez que Google lanza una nueva versión. Cada
fabricante debe crear su propia versión. Sin embargo, esa situación cambiará ya que los
fabricantes se han comprometido a aplicar actualizaciones al menos durante los 18 meses
siguientes desde que empiezan a vender un terminal en el mercado.

Además, actualmente Google tiene la intención de unificar la funcionalidad entre las


versiones del sistema operativo para tabletas y móviles en la versión 4.0.

Disponer del código fuente del sistema operativo no significa que se pueda tener
siempre la última versión de Android en un determinado móvil, ya que el código para soportar
el hardware de cada fabricante normalmente no es público; así que faltaría un trozo básico del
firmware (controladores) para poder hacerlo funcionar en dicho terminal.

Hay que tener en cuenta que las nuevas versiones de Android suelen requerir más
recursos, por lo que, en los modelos más antiguos, no se puede instalar la última versión por
insuficiente memoria RAM, velocidad de procesador, etcétera.

4
Introducción

1.2 QUÉ ES ECLIPSE

Eclipse es un entorno de software multi-lenguaje de


programación que incluye un entorno de desarrollo integrado (IDE).
Inicialmente, se diseñó pensando principalmente en el lenguaje de
programación Java y se puede utilizar para desarrollar aplicaciones en
este lenguaje.

En la web oficial de Eclipse (www.eclipse.org), se define como “An IDE for everything
and nothing in particular” (un IDE para todo y para nada en particular). Eclipse es, en realidad,
un armazón (workbench) sobre el que se pueden instalar herramientas de desarrollo para
cualquier lenguaje, mediante la implementación de los plugins adecuados. El término plugin
procede del inglés to plug, que significa enchufar. Es un software que permite cambiar,
mejorar o agregar funcionalidades.

La arquitectura de plugins de Eclipse permite, además de integrar diversos lenguajes


sobre un mismo IDE, introducir otras aplicaciones accesorias que pueden resultar útiles
durante el proceso de desarrollo, tales como herramientas UML (modelado de objetos),
editores visuales de interfaces, ayuda en línea para librerías, etcétera.

Usando distintas librerías es posible servirse de este entorno de desarrollo para otros
lenguajes de programación, como Ada, C, C + +, COBOL, Perl, Delphi, PHP, Python, R. Ruby,
Scala, Clojure y Scheme.

A menudo el IDE Eclipse añade un apellido a su nombre cuando se usa para


programar otro lenguaje. Por ejemplo, se llama Eclipse ADT (Ada Development Toolkit) para
Ada, Eclipse CDT para C / C + +, Eclipse JDT para Java y Eclipse PDT para PHP.

Esta lista de lenguajes aumenta con los años, ya que este IDE se está convirtiendo en
el entorno de desarrollo de muchos programadores por su simplicidad y facilidad de uso.

1.2.1 El Consorcio Eclipse

En su origen, el Proyecto Eclipse era un proyecto de desarrollo OpenSource,


desarrollado y mantenido en su totalidad por IBM. Bajo la dirección de IBM, se fundó el
Consorcio Eclipse, al cual se unieron algunas empresas importantes como Rational, HP o
Borland.

Desde el año 2004, el Consorcio Eclipse es independiente de IBM y entre otras


empresas, está integrado por HP, QNX, IBM, Intel, SAP, Fujitsu, Hitachi, Novell, Oracle, Palm,
Ericsson y RedHat, además de por algunas universidades e institutos tecnológicos.

5
1.2.2 Instalación de Java Developmente Kit (JDK)

Es muy importante tener en cuenta que, para poder ejecutar el entorno de desarrollo
Eclipse y las librerías de Android, es necesario tener instaladas en el ordenador las librerías de
desarrollo de Java. Aunque ya está disponible la versión 1.7, para poder compilar
aplicaciones Android, es necesario instalar la versión 6 de Java (también conocida como
Java 1.6).

Podemos descargar la versión correcta del JDK de Java en:

http://java.sun.com/javase/downloads

6
Introducción

Nota: en el caso de Linux o Mac, es posible también instalar Java usando los
programas habituales del sistema operativo que permiten la actualización de paquetes.

Nota: si vas a instalar Eclipse y las librerías de Android en Linux, lee las notas que se
encuentran en “Preguntas y Respuestas” de esta Introducción en la mesa del curso.

1.2.3 Instalación de Eclipse

La instalación es muy sencilla. Simplemente accedemos a la página web:

http://www.eclipse.org/downloads/

En esta página seleccionamos el tipo de Sistema Operativo donde vamos a instalar


Eclipse y descargamos el archivo "Eclipse Classic 3.7".

7
Hay que tener en cuenta que debemos descargar la versión 32 bits o 64 bits en función
del sistema operativo de que dispongamos.

En el caso de Windows podemos ver el tipo de sistema operativo haciendo clic con el
botón derecho del ratón en el icono "Equipo" o Mi PC del Escritorio y haciendo clic de nuevo
en "Propiedades":

En el caso de Linux, desde la línea de comandos podemos ejecutar el siguiente


comando para saber si el sistema operativo es de 64bits:

$ uname -m

x86_64

En el caso de Apple Mac, utiliza el Perfil de Sistema para determinar si estás utilizando
un kernel de 64 bits.

1. En el menú Apple ( ), selecciona Acerca de este Mac y a continuación, haz clic en


"Más información":

8
Introducción

2. En el panel "Contenido", selecciona "Software".

3. Si Extensiones y kernel de 64 bits está configurada como Sí, estás utilizando un kernel
de 64 bits.

Cuando hayamos descargado el fichero correspondiente, lo copiamos a un directorio


o carpeta del ordenador y descomprimimos este fichero.

Es recomendable usar un directorio sencillo que podamos recordar fácilmente, por


ejemplo C:\cursos_Mentor\eclipse. Además, es muy importante que los nombres de los
directorios no contengan espacios, pues Eclipse puede mostrar errores y no funcionar
correctamente.

Una vez descomprimido el fichero, Eclipse está listo para ser utilizado; no es necesario
hacer ninguna operación adicional.

Recomendamos hacer un acceso directo en el Escritorio del ordenador para arrancar


rápidamente el entorno de programación Eclipse.

1.2.4 Instalación de las librerías de Android

A continuación, debemos instalar el Paquete de Desarrollo de iniciación (en inglés,


SDK Starter Package) de Android. Este paquete no incluye las librerías de desarrollo
completas, sino que únicamente es el núcleo del SDK que se utiliza para descargar el resto de
los componentes SDK, como la última plataforma Android.

9
Para descargar el fichero necesario, accedemos a la página de descarga del SDK de
Android y nos bajamos la versión que corresponda en función del sistema operativo donde
vayamos a instalarlo. Recomendamos que hay que descargar el archivo .zip, ya que con éste
la instalación es más sencilla y rápida:

Cuando hayas descargado la versión .zip o .tgz, descomprímelo en el disco duro.


Recomendamos que conviene usar el directorio C:\cursos_Mentor\Android\android-sdk-
windows.

Si usas otro directorio, toma nota del mismo, ya que, más adelante, tendrás que usar
el nombre de este directorio para acabar de configurar el plugin de Android en Eclipse.

Ahora vamos a instalar las librerías necesarias en Eclipse. Estas librerías se denominan
Android Development Tools (ADT). Para ello, arrancamos Eclipse haciendo doble clic sobre
el acceso directo que hemos creado anteriormente. A continuación, Eclipse pedirá que
seleccionemos el "workspace", es decir, el directorio donde queremos guardar los proyectos.

10
Introducción

Seleccionaremos un directorio sencillo y fácil de recordar.

Importante: Recomendamos usar el directorio C:\cursos_Mentor\Android\proyectos como


carpeta personal

Finalmente hacemos clic en OK para abrir Eclipse:

Ahora vamos a configurar las preferencias de la versión de Java en Eclipse para


compilar los proyectos de Android. Para ello, hacemos clic en la opción del menú “Window->
Preferences...”, hacemos clic en el panel izquierdo sobre “Java->Installed JREs” y
seleccionamos “jre6” en el campo “Installed JREs”:

Si no podemos seleccionar "jre6" debemos usar el botón "Add" para añadir las
librerías del JRE 6. Por ejemplo, en Windows, se pueden encontrar en el directorio:
"C:\Program Files\Java\jre6".

11
Para finalizar, en esta ventana hay que seleccionar la versión de Java utilizada para
compilar los proyectos de Android. Para ello hacemos clic en “Java->Compiler” y elegimos
“1.6” en el campo “Compiler compliance settings”:

Si no hemos seleccionado la versión 6 de JRE en el paso anterior aparecerá el


siguiente mensaje de error en Eclipse:

12
Introducción

Importante: Es necesario disponer de conexión a Internet para poder continuar con los
siguientes pasos y poder descargar las librerías necesarias.

A continuación, seleccionamos en el menú Help -> Install New Software...

En el cuadro de diálogo que aparecerá, introducimos la dirección del sitio de descarga


de las librerías ADT:

https://dl-ssl.google.com/android/eclipse/

Y pulsamos la tecla Intro. A continuación, marcamos todas las opciones tal y como se
muestra en la siguiente captura de pantalla:

Es muy Importante comprobar la versión de Java. Si no, no se instalará bien el software de


Android.

13
Hacemos clic en el botón "Next".

Nota: este proceso puede llevar un rato en función de la conexión a Internet y de la


potencia del ordenador que tengamos.

Después, aparecerá la siguiente ventana:

Hacemos clic de nuevo en "Next", seleccionamos "I accept..." en el acuerdo de


licencia y hacemos clic en "Finish":

14
Introducción

A continuación, se instalará el software necesario de Android:

Se mostrará este aviso de seguridad y pulsaremos en "OK" para continuar la


instalación:

Al acabar la instalación, es necesario reiniciar Eclipse. Para ello haremos clic en


"Restart":

15
Al arrancar de nuevo Eclipse ya dispondremos de las librerías necesarias para
empezar a trabajar con Android:

Podemos hacer clic en la X de la pestaña "Welcome" para acceder al entorno de


desarrollo:

La primera vez que accedemos al entorno Eclipse, aparece la siguiente ventana en la


que debemos indicar dónde hemos instalado el SDK de Android:

16
Introducción

Hacemos clic en el botón “Browse...” e indicamos el directorio donde hemos instalado


el SDK de Android. Si has seguido las instrucciones de Windows, el directorio por
recomendado para descomprimir el archivo del SDK es
“C:\cursos_Mentor\Android\android-sdk-windows”. Pulsamos el botón “OK” para finalizar
la configuración.

A continuación, aparece otra ventana solicitando nuestra conformidad para enviar


estadísticas de uso del SDK de Android. No es necesario hacerlo si no lo deseamos:

17
Pulsamos el botón “Finish” para finalizar la configuración. A continuación, aparece el
siguiente mensaje indicando que no hemos instalado ninguna versión del sistema operativo
Android:

Pulsamos el botón “OK”, en el siguiente paso, instalamos la versión de Android sobre


la que vamos a trabajar en este curso.

En el caso de que no aparezca la ventana que permite indicar a Eclipse dónde se


encuentra el SDK de Android, podemos hacerlo manualmente. Para ello, hacemos clic en la
opción del menú “Window-> Preferences...” y seleccionamos “Android” en el panel
izquierdo:

18
Introducción

Para acabar, pulsamos el botón “OK”.

1.2.5 Añadir versiones y componentes de Android

El último paso en la configuración de las librerías de Android es descargar e instalar


los componentes esenciales del SDK para el entorno de desarrollo.

El SDK utiliza una estructura modular que separa las distintas versiones de Android,
complementos, herramientas, ejemplos y la documentación en un único paquete que se puede
instalar por separado. Para desarrollar una aplicación en Android, es necesario descargar, al
menos, una versión. En este curso vamos a usar la versión 2.3, por ser la más extendida en
el momento de redacción de la documentación. No obstante, vamos a emplear sentencias
compatibles y recompilables en otras versiones.

Para añadir esta versión hay que hacer clic en la opción “Android SDK Manager” del
menú principal “Window” de Eclipse:

19
Se abrirá la ventana siguiente:

Para instalar la versión 2.3.3, seleccionamos los paquetes que se muestran en la


siguiente ventana:

20
Introducción

Nota: la revisión de las versiones de Android puede ser superior cuando al alumno o
alumna instale el SDK.

Una vez hemos pulsado el botón “Install 6 packages”, aparece esta ventana y
seleccionamos la opción “Accept All” y, después, hacemos clic en “Install”:

El instalador tarda un rato (10-20 minutos) en descargar e instalar los paquetes:

21
Para acabar, reiniciamos el ADB (Android Debug Bridge):

La instalación ha finalizado correctamente:

Ahora vamos a ver la estructura que tiene el SDK de Android. Para ello, abrimos el
explorador en el directorio “C:\cursos_Mentor\Android\android-sdk-windows” o en el
directorio donde lo hayamos descomprimido. La siguiente tabla describe los subdiretorios que
contiene esta carpeta:

22
Introducción

NOMBRE
DESCRIPCIÓN
CARPETA

Contiene los paquetes “add-on” del SDK de Android que permiten desarrollar
add-ons/ aplicaciones usando librerías externas disponibles para algunos dispositivos
o terminales.

Documentación completa en formato HTML, incluyendo la Guía del


docs/ desarrollador y la guía de la API. Para leer la documentación, puedes abrir el
fichero offline.html en un navegador Web.

Contiene las herramientas de desarrollo comunes del SDK que se actualizan


platform-
con cada nueva versión de Android, tales como el ADB (Android Debug
tools/
Bridge), así como otras herramientas que no se suelen utilizar directamente.

Contiene las versiones de Android con las que se puede desarrollar


platforms/ aplicaciones en Eclipse. Cada versión se encuentra en un directorio
independiente.

Directorio de la plataforma de la versión correspondiente, por ejemplo,


"Android-10". Todos los directorios de la versión de Android contienen un
<platform>/ conjunto similar de archivos y la misma estructura de subdirectorios.
Además, también incluye la librería de Android (android.jar) que se utiliza
para compilar aplicaciones con esta versión de Android.

samples/ Contiene los ejemplos de código para esa versión específica de Android.

Contiene el conjunto de herramientas de desarrollo y creación de perfiles que


tools/ son independientes de la versión de Android, como el emulador, el SDK de
Android y AVD Manager, DDMS (Dalvik Debug Monitor Server), etcétera.

SDK
Archivo que explica cómo realizar la configuración inicial del SDK de Android.
Readme.txt

Aplicación que inicia el SDK de Android y la herramienta AVD de gestión de


SDK paquetes. Sólo disponible en Windows.
Manager.exe

23
Finalmente, vamos a incluir el directorio donde hemos instalado las librerías de
Android en el PATH del sistema operativo. En concreto, vamos a incluir los directorios tools y
platform-tools.

Si has usado el directorio recomendado, los subdirectorios son:

C:\cursos_Mentor\Android\android-sdk-windows\tools
y
C:\cursos_Mentor\Android\android-sdk-windows\platform-tools

En Windows, se puede hacer esto accediendo al “Panel de control”, haciendo clic en


el icono “Sistema”, seleccionando la pestaña “Opciones avanzadas” y haciendo clic en el
botón “Variables de entorno”. A continuación, añadiremos los directorios anteriores a la
variable PATH de la siguiente ventana:

En Windows 7 se puede acceder a la ventana anterior abriendo el “Panel de control”,


haciendo clic en el icono “Sistema y Seguridad”, después en "Sistema" y, para acabar, en la
opción "Configuración avanzada del sistema" para acceder a la ventana anterior:

24
Introducción

Escribiendo el comando path en una ventana de comandos de Windows podemos ver


si se hemos modificado bien esta variable global del sistema:

Nota: en el resultado de este comando debemos ver el directorio de instalación de


Android. Es muy importante que se muestre tal como aparece en la ventana anterior: con
punto y coma al final del directorio SDK y sin espacios entre el punto y coma del
directorio anterior.

En OS X (Mac) y Linux, puedes agregar la ruta a la variable PATH con el comando SET
o estableciendo la variable correspondiente en un script de inicio.

1.2.6 Definición del dispositivo virtual de Android

Para poder hacer pruebas de las aplicaciones Android que desarrollemos sin
necesidad de disponer de un teléfono Android, el SDK incluye la posibilidad de definir un
Dispositivo Virtual de Android (en inglés, AVD, Android Virtual Device). Este dispositivo
emula un terminal con Android instalado.

Para definir el AVD, hacemos clic en la opción “Android AVD Manager” del menú principal
“Window” de Eclipse:

25
Aparecerá la siguiente ventana:

Hacemos clic en el botón “New” de la ventana anterior y la completamos como se


muestra en la siguiente ventana:

26
Introducción

La opción “Snapshot-> Enabled” permite guardar el estado del dispositivo de manera


que todos los cambios que hagamos, como cambiar la configuración de Android o instalar
aplicaciones, queden guardados. Así, la próxima vez que accedamos al emulador, se recupera
automáticamente el último estado.

Importante: En el curso hemos creado un dispositivo virtual que no guarda el estado porque
puede producir problemas de ejecución con Eclipse. En todo caso, el alumno o alumna puede
usar la opción “Edit” del AVD cuando crea necesario que los últimos cambios sean
almacenados para la siguiente sesión de trabajo

Para acabar, basta con hacer clic en “Create AVD”:

27
En esta Introducción puedes encontrar el vídeo “Cómo instalar Eclipse y el plugin
Android”, que muestra de manera visual los pasos seguidos en las explicaciones anteriores

28
INTRODUCCIÓN AL ENTORNO
ANDROID

ÍNDICE
1.1 INTRODUCCIÓN AL ENTORNO DE ANDROID ....................  31
1.1.1 Introducción ...................................................................................31
1.1.2 Características de Android...........................................................31
1.1.3 Arquitectura de Android ...............................................................33
1.1.4 Creación de un proyecto por líneas de comando ....................35

1.2 CONCEPTOS DE LAS APLICACIONES ANDROID ............... 37


1.2.1 Características de las aplicaciones Android.......................... 37
1.2.2 Componentes de las aplicaciones......................................... 37

1.3 CÓMO CREAR UN PROYECTO ANDROID ........................... 40


1.3.1 Un vistazo general al IDE de Eclipse ..................................... 40
1.3.1.1 Editores .................................................................................41
1.3.1.2 Vistas ................................................................................ …..41
1.3.1.3 Barras de Herramientas principal y secundarias ............43
1.3.1.4 Perspectivas .........................................................................43
1.3.2 Cómo crear un proyecto Android ...............................................46
1.3.2.1 Creación de un nuevo proyecto ........................................47
1.3.2.2 Nombre de la actividad .......................................................49
1.3.2.3 Descripción de los ficheros por defecto del proyecto....49
1.3.2.4 Ejecución del proyecto Android ........................................53
1.3.2.5 Cómo usar el emulador de Android (AVD) .......................56

1.4 CÓMO CREAR LA PRIMERA APLICACIÓN CON ANDROID63


1.4.1 Cambio en la Interfaz de usuario con Layout .........................66

1.5 DISEÑO DE LA INTERFAZ DE USUARIO .............................. 70


1.5.1 Cómo diseñar la interfaz de usuario mediante Vistas ..............70
1.5.2 Vistas disponibles de Android .....................................................71
2
Introducción al entorno Android

1.1 INTRODUCCIÓN AL ENTORNO DE ANDROID

1.1.1 Introducción

En esta Unidad vamos a explicar las características y la arquitectura de Android.


Además, describiremos el entorno de desarrollo Eclipse y crearemos nuestro primer
proyecto Android.

También, detallaremos los ficheros básicos que componen un proyecto Android.

Finalmente, usaremos Paneles de diseño (Layout) y Componentes (View) para


diseñar la interfaz de usuario en ejemplos de aplicaciones de Android.

1.1.2 Características de Android

A continuación se muetra un resumen de las características más importantes:

Diseñado para
El sistema operativo es compatible con pantallas VGA (y mayores),
dispositivo
gráficos 2D y gráficos 3D presentes en muchos teléfonos tradicionales.
pequeños

Dispone de la base de datos ligera SQLite donde se almacenan los


Almacenamiento
datos de las aplicaciones.

Android soporta las siguientes tecnologías de conectividad: GSM/EDGE,


IDEN, CDMA, EV-DO, UMTS, Bluetooth, Wi-Fi, LTE y WiMAX. Algunas
Conectividad
son muy populares en los teléfonos actuales y otras se están
desarrollando.

Mensajería Se pueden usar tanto SMS como MMS.

El navegador web incluido en Android está basado en el motor del


Navegador web navegador de código abierto WebKit. Este navegador es muy eficiente y
permite cargar las páginas Web rápidamente.

Aunque las aplicaciones se escriben en el lenguaje Java, no hay una


Máquina Virtual de Java en el sistema operativo para ejecutar el código.
Este código Java se compila en un ejecutable Dalvik y se ejecuta en la
Máquina Virtual Dalvik. Dalvik es una máquina virtual especializada,
Soporte de Java
diseñada específicamente para Android y optimizada para dispositivos
móviles que funcionan con batería y que tienen memoria y procesador
limitados. Es posible incluir las librerías J2ME nativas de Java mediante
aplicaciones de terceros, como J2ME MIDP Runner.

31
Android soporta los siguientes formatos multimedia: WebM, H.263,
H.264 (en 3GP o MP4), MPEG-4 SP, AMR, AMR-WB (en un contenedor
Soporte multimedia
3GP), AAC, HE-AAC (en contenedores MP4 o 3GP), MP3, MIDI, Ogg
Vorbis, WAV, JPEG, PNG, GIF y BMP.

Soporte para Android soporta los siguientes formatos de streaming: RTP/RTSP,


streaming descarga progresiva de HTML (tag <video> de HTML5). Adobe Flash
(distribución en Streaming (RTMP) es soportado mediante la instalación de Adobe Flash
Internet) Player, pero sólo para algunos terminales.

Android puede manejar cámaras de fotos, de vídeo, pantallas táctiles,


Soporte para
GPS, acelerómetros, giroscopios, magnetómetros, sensores de
hardware adicional
proximidad y de presión, termómetro, aceleración 2D y 3D.

El entorno de desarrollo es Eclipse 3.7 y el plugin de Herramientas de


Entorno de
Desarrollo de Android (ADT) que incluye un emulador de dispositivos,
desarrollo
herramientas de depuración y análisis de rendimiento.

Market El Android Market es un catálogo de aplicaciones gratuitas y de pago


(Mercado de que pueden ser descargadas e instaladas desde los propios dispositivos
aplicaciones) Android.

Android tiene soporte nativo para pantallas multi-táctiles que permiten


Multi-táctil
manejar la pantalla táctil con más de 1 dedo.

Bluetooth En la versión 2.2 de Android se incluye la funcionalidad completa.

Videollamada Android incluye la posibilidad de videollamada a través de Google Talk.

Existe la multitarea real de aplicaciones, es decir, las aplicaciones que


Multitarea no se están ejecutando en primer plano reciben ciclos de reloj del
procesador para actualizar su estado.

Características Es posible dar órdenes de voz al terminal. Por ejemplo, la búsqueda en


basadas en voz Google a través de la voz ya estaba disponible desde la primera versión.

Android incluye la compartición de la conexión a Internet (en inglés,


Tethering
tethering), que permite usar el teléfono como un punto de acceso
(compartición de
inalámbrico, de manera que un ordenador puede usar la conexión 3G
conexión a Internet)
del móvil Android.

32
Introducción al entorno Android

1.1.3 Arquitectura de Android

Los componentes principales de la arquitectura del sistema operativo Android son los
siguientes:

 Aplicaciones: todas las aplicaciones están escritas en lenguaje de programación


Java. Las aplicaciones incluidas por defecto son un cliente de correo electrónico,
programa de SMS, calendario, mapas, navegador, contactos, etcétera.

Todas las aplicaciones de Android usan el siguiente conjunto de servicios y sistemas:

o Un conjunto de componentes (Views) que se usan para crear las interfaces de


usuario. Por ejemplo, botones, listas, tablas, cajas de texto, etcetera.

o Proveedores de contenidos (Content Providers) que permiten a las


aplicaciones acceder a la información de otras aplicaciones (por ejemplo, los
Contactos del teléfono) o compartir datos entre ellas.

o Gestor de recursos (Resource Manager), que permite acceder a recursos que


no sean del código fuente, tales como textos de internacionalización, imágenes
y ficheros de estilos (layout).

o Gestor de notificaciones (Notification Manager), que permite a todas las


aplicaciones mostrar alertas en la barra de estado de Android.

o Gestor de actividades (Activity Manager), que controla el ciclo de vida de la


aplicación.

 Marco de desarrollo de aplicaciones: los programadores tienen acceso completo a


las mismas APIs (librerías) del Framework (marco de desarrollo) utilizadas por las
aplicaciones base. La arquitectura está diseñada para simplificar la reutilización de
componentes, es decir, cualquier aplicación puede publicar sus capacidades y
cualquier otra aplicación puede hacer uso de estas capacidades.

 Librerías: Android incluye también un conjunto de librerías de C/C++ usadas por


varios componentes del sistema. Entre ellas, se encuentran: System C library
(implementación de la librería C estándar), librerías de medios, bibliotecas de gráficos,
3D y SQLite, entre otras. El programador puede hacer uso de estas librerías.

 Runtime (ejecutable) de Android: Android también incluye un conjunto de librerías


base que proporcionan la mayor parte de las funciones del lenguaje Java. Cada
aplicación Android ejecuta un proceso con instancia individual de la máquina virtual
Dalvik.

 Núcleo Linux: Android está basado en Linux para los servicios base del sistema,
como seguridad, gestión de memoria, procesos y controladores.

33
DIAGRAMA DE LA ARQUITECTURA ANDROID

La utilidad de línea de comandos genera automáticamente todos los archivos


necesarios para crear un proyecto Android; incluso permite crear un proyecto para Eclipse.

Android usa Java como lenguaje base para el desarrollo de las aplicaciones. Por lo tanto,
hace uso de los Paquetes Java (Package en inglés). Estos paquetes son contenedores de
clases que permiten agrupar las distintas partes de un programa cuya funcionalidad tienen
elementos comunes.

El uso de paquetes proporciona las siguientes ventajas:

o Agrupamiento de clases con características comunes

o Reutilización de código

o Mayor seguridad al existir niveles de acceso

34
Introducción al entorno Android

1.1.4 Creación de un proyecto por líneas de comando

Usando la línea de comandos vamos a crear un proyecto Android. Es importante usar


el directorio de trabajo que hemos creado anteriormente con Eclipse:
C:\cursos_Mentor\Android\proyectos.

Desde este directorio, debemos ejecutar el siguiente comando:

android create project --package es.mentor.eje1.unidad1.bienvenido --activity

Bienvenido --target android-10 --path unidad1.eje1.bienvenido

para crear los ficheros básicos de un proyecto Android. Fíjate en qué la orden anterior es una
única línea que debes ejecutar en la línea de comandos de tu sistema operativo.

C:\>cd C:\cursos_Mentor\Android\proyectos

C:\cursos_Mentor\Android\proyectos> android create project --package


es.mentor.unidad1.eje1.bienvenido --activity Bienvenido --target android-10 --path
bienvenido

Created project directory: C:\cursos_Mentor\Android\proyectos\bienvenido

Created directory
C:\cursos_Mentor\Android\proyectos\bienvenido\src\es\mentor\eje1\unidad1\bienvenido

Added file
C:\cursos_Mentor\Android\proyectos\bienvenido\src\es\mentor\eje1\unidad1\bienvenido\Bienv
enido.java

Created directory C:\cursos_Mentor\Android\proyectos\bienvenido\res

Created directory C:\cursos_Mentor\Android\proyectos\bienvenido\bin

Created directory C:\cursos_Mentor\Android\proyectos\bienvenido\libs

Created directory C:\cursos_Mentor\Android\proyectos\bienvenido\res\values

Added file C:\cursos_Mentor\Android\proyectos\bienvenido\res\values\strings.xml

Created directory C:\cursos_Mentor\Android\proyectos\bienvenido\res\layout

Added file C:\cursos_Mentor\Android\proyectos\bienvenido\res\layout\main.xml

Created directory C:\cursos_Mentor\Android\proyectos\bienvenido\res\drawable-hdpi

Created directory C:\cursos_Mentor\Android\proyectos\bienvenido\res\drawable-mdpi

Created directory C:\cursos_Mentor\Android\proyectos\bienvenido\res\drawable-ldpi

Added file C:\cursos_Mentor\Android\proyectos\bienvenido\AndroidManifest.xml

Added file C:\cursos_Mentor\Android\proyectos\bienvenido\build.xml

Added file C:\cursos_Mentor\Android\proyectos\bienvenido\proguard.cfg

C:\cursos_Mentor\Android\proyectos>

35
Ahora no vamos a examinar en detalle los ficheros que hemos creado y que conjuntamente
forman el proyecto Android. En el siguiente apartado de esta Unidad los detallaremos con un
ejemplo real en Eclipse.

Este script genera los siguientes directorios y archivos principales:

 /src: en este directorio es donde se almacenan los archivos de código fuente Java
(con extensión .java).

 /assets: en este directorio se guardan los recursos que utiliza la aplicación.

 /res: es el directorio principal de recursos (resources). Aquí guardaremos imágenes o


archivos multimedia que utilice nuestra aplicación.

 /res/drawable-Xdpi: son los directorios de recursos gráficos o imágenes que utilizará


nuestra aplicación con los nombres drawable-hdpi, drawable-mdpi y drawable-ldpi;
en ellos se almacenan las imágenes dependiendo de la densidad de puntos por
pulgada que tenga el dispositivo en el que se ejecute la aplicación.

 /res/layout: en Android hay que separar el código Java de la aplicación y la interfaz


gráfica. En este directorio es donde colocaremos los archivos xml que definen las
vistas que utilizará la aplicación.

 /res/values: de igual forma que separamos el código Java y la interfaz gráfica,


Android separa también las cadenas constantes de texto (Internacionalización de la
aplicación), las matrices, la paleta de colores, etcétera.

 AndroidManifest.xml: es el archivo de configuración de la aplicación en el que se


define lo que puede hacer nuestra aplicación, es decir, en él informamos al sistema
operativo de las capacidades que tiene esta aplicación. En este archivo también
indicaremos las actividades o servicios que ejecutará nuestra aplicación y los permisos
de seguridad especiales necesarios si va a acceder a recursos compartidos del
sistema, como por ejemplo el acceso al listado de contactos, empleo del GPS o la
posibilidad de enviar mensajes SMS.

 default.properties: fichero de proyecto para Eclipse.

 Nota: una vez analizado los ficheros básicos del proyecto, podríamos importarlo en
Eclipse. No obstante, por simplificación, no lo vamos a hacer, pues es más sencillo
crear un proyecto nuevo directamente desde Eclipse.

Así pues, borramos el directorio C:\cursos_Mentor\Android\proyectos\bienvenido,


para crear este mismo proyecto desde Eclipse directamente.

36
Introducción al entorno Android

1.2 CONCEPTOS DE LAS APLICACIONES ANDROID

1.2.1 Características de las aplicaciones Android

Las Aplicaciones Android se forman con uno o más de los siguientes componentes:
actividades, servicios, proveedores de contenidos y receptores de mensajes. Cada
componente tiene una funcionalidad diferente en la aplicación; incluso la aplicación puede
activar cada uno de los componentes de forma individual; es más, otras aplicaciones también
los pueden activar.

El archivo de manifestación (manifest) indica todos los componentes que usa la


aplicación; en él también deben declararse todos los requisitos necesarios de la misma como,
por ejemplo, la versión mínima de Android, las configuración mínima de hardware, etcétera.

El código que no es de la aplicación, como las imágenes, cadenas de


internacionalización, diseño de la interfaz de usuario, otros recursos, etcétera, puede incluir
distintas configuraciones en función del idioma del teléfono o diseños de la interfaz del usuario
en función de los diferentes tamaños de pantalla.

Las aplicaciones de Android están escritas en el lenguaje de programación Java. Las


herramientas de SDK de Android compilan este código, junto con sus datos y los archivos de
recursos, en un paquete Android. Este archivo tiene la extensión .apk y lo utiliza Android para
instalar la aplicación.

Una vez instalada una aplicación en un dispositivo, Android la aloja en su propia caja
de arena (sandbox). Sandbox, palabra del inglés que significa caja de arena (Sand+box), es
un sistema informático que aísla los procesos; de esta manera se pueden ejecutar
aplicaciones de forma independiente. Se utiliza para evitar la corrupción de datos del sistema
donde éstos se ejecutan.

El sistema Android aplica el principio de “privilegios mínimos” (recuerda que Android


se basa en el kernel de Linux). Es decir, cada aplicación, por defecto, sólo tiene acceso a los
componentes que necesita para hacer su trabajo y nada más. Esto crea un entorno muy
seguro en el que una aplicación no puede acceder a las partes de un sistema para las que no
tiene permiso.

1.2.2 Componentes de las aplicaciones

Los Componentes de las aplicaciones son los elementos esenciales de una aplicación
Android. Cada componente es un punto diferente de entrada por el que el sistema operativo
puede interaccionar con la aplicación. No todos los componentes son verdaderos puntos de
entrada, sino que algunos dependen unos de otros, aunque cada uno exista como una entidad
separada en Android y desempeñe un papel específico que define el comportamiento general
de la aplicación.
37
Existen los siguientes tipos de componentes de la aplicación:

 Actividades (Activities): una actividad representa una pantalla única con una interfaz
de usuario. Por ejemplo, una aplicación de correo electrónico puede tener una
actividad que muestra una lista de correo electrónico nuevo, otra actividad que
compone un correo y otra actividad que lee los mensajes. Aunque las actividades
trabajan conjuntamente para dar la sensación de una única aplicación, cada una de
ellas es independiente de las otras. Por lo tanto, otra aplicación externa diferente
podría iniciar cualquiera de estas actividades (si la aplicación de correo electrónico lo
permite). Por ejemplo, una aplicación que gestiona los contactos podría iniciar la
actividad que compone nuevos mensajes de correo indicando como destinatario del
mensaje al contacto seleccionado en la primera aplicación.

Una actividad se implementa a partir de la clase Java Activity. Más adelante veremos
cómo se usa.

Puedes pensar en una actividad de Android como si fuera una ventana en una
aplicación de escritorio o una página HTML en una aplicación Web. Android está
diseñado para cargar muchas actividades pequeñas, por lo que se permite al usuario
abrir nuevas actividades y pulsar el botón “Atrás” para ir a un estado anterior, al igual
que se hace en un navegador web.

 Servicios (Services): un servicio es un componente que se ejecuta en segundo plano


y que realiza operaciones cada cierto tiempo. Un servicio no proporciona una interfaz
gráfica al usuario. Por ejemplo, un servicio puede reproducir música en segundo plano
mientras el usuario está en otra aplicación, o puede obtener información de Internet sin
la interacción del usuario. Otros componentes, como una actividad, pueden iniciar un
servicio e interactuar con él si es necesario.

Un servicio se implementa a partir de la clase Java Service. Más adelante veremos


cómo se usa.

 Proveedores de contenidos (Content providers): un proveedor de contenidos


maneja el conjunto de datos compartido de la aplicación. Puede almacenar
información en el sistema de archivos, en una base de datos SQLite, en Internet o en
cualquier otro lugar de almacenamiento permanente al que la aplicación tenga acceso.
A través del proveedor de contenidos, otras aplicaciones pueden consultar e incluso
modificar los datos (si el proveedor de contenidos lo permite). Por ejemplo, Android
proporciona un proveedor de contenidos que gestiona la información de los contactos
del teléfono. Por lo tanto, cualquier aplicación, con los permisos adecuados, puede
hacer una consulta al proveedor de contenido de los contactos
(ContactsContract.Data) para leer y escribir información sobre una persona en
particular.

38
Introducción al entorno Android

Los proveedores de contenidos se utilizan también para escribir y leer datos que son
privados de la aplicación y no se comparten. Por ejemplo, una aplicación de Notas
puede utilizar un proveedor de contenidos para guardar las notas.

Un proveedor de contenidos se implementa a partir de la clase ContentProvider y


debe implementar un conjunto estándar de métodos (API) que permiten a otras
aplicaciones interaccionar con él. Más adelante veremos cómo se usa.

 Receptores de mensajes (Broadcast receivers): un receptor de mensajes responde


a mensajes difundidos (broadcast) a todos los elementos del sistema. Por ejemplo, un
mensaje puede anunciar que la pantalla se ha apagado, la batería está descargada o
que se ha capturado una foto. Las aplicaciones también pueden emitir este tipo de
mensajes para, por ejemplo, indicar a otras aplicaciones que ciertos datos ya han sido
descargados en el dispositivo y están disponibles para ser utilizados. Aunque estos
receptores de mensajes no muestran información en la interfaz del usuario, sí que
pueden crear una notificación en la barra de estado (la barra que aparece arriba en
Android) para alertar al usuario cuando se produce este tipo de mensajes.

Un receptor de mensajes se implementa a partir de la clase BroadcastReceiver y


cada mensaje emitido es un objeto del tipo Intent (Intención). Más adelante veremos
cómo se usa.

 Componentes de la pantalla de inicio (Widgets): estos componentes visuales se


usan principalmente en la Pantalla de inicio (HomeScreen) de Android para mostrar
información que se actualiza periódicamente como, por ejemplo, un reloj, la previsión
del tiempo, etcétera. Al tratarse de programación avanzada no los estudiaremos en
este curso de iniciación a Android.

 Otros componentes de Android son las Carpetas animadas (Live Folders) y los
Fondos de pantalla animados (Live Wallpapers) en la Pantalla de Inicio. Las
carpetas animadas permiten a Android mostrar información en la pantalla inicial sin
necesidad de lanzar la aplicación correspondiente. Igualmente, al tratarse de
programación avanzada, no los estudiaremos en este curso de iniciación a Android.

Un aspecto único del diseño del sistema Android es que cualquier aplicación puede iniciar un
componente de otra aplicación. Por ejemplo, si es necesario para la aplicación abierta
capturar una imagen con la cámara de fotos, seguramente ya exista otra aplicación que hace
eso y que la aplicación inicial puede reutilizar en lugar de desarrollar el código necesario para
capturar la foto. Únicamente hay que iniciar la actividad de la aplicación de la cámara de fotos
y capturar la imagen. La sensación del usuario es como si la cámara formara parte de la
aplicación inicial.

Cuando el sistema arranca un componente, éste inicia un proceso (si no está ya en


ejecución) para que la aplicación cree las instancias de las clases necesarias del componente.

39
Por ejemplo, si una aplicación inicia la actividad de la aplicación de la cámara que hace fotos,
la actividad se ejecuta en el proceso que pertenece a la aplicación de la cámara, no en el
proceso de la aplicación original que ha hecho la llamada a la otra aplicación. Por lo tanto, a
diferencia de otros sistemas operativos, las aplicaciones de Android no tienen un punto de
entrada único (no hay función main()).

Debido a que el sistema ejecuta cada aplicación en un proceso independiente con


permisos restringidos, ésta no puede activar directamente un componente de otra aplicación,
sino que es el sistema operativo Android el encargado de hacerlo. Por lo tanto, para activar un
componente de otra aplicación es necesario enviar un mensaje al sistema que especifica su
intención (clase Intent) de iniciar un componente en particular. Por lo tanto, el sistema
operativo es el encargado de activar el componente solicitado.

1.3 CÓMO CREAR UN PROYECTO ANDROID

1.3.1 Un vistazo general al IDE de Eclipse

Antes de crear el primer proyecto de Android, vamos a echar un primer vistazo al


entorno de desarrollo de Eclipse para conocer sus características básicas, la forma en que
organiza el proyecto y las herramientas adicionales que ofrece.

La primera vez que se ejecuta Eclipse se puede ver una pantalla muy similar a la que
se muestra a continuación.

40
Introducción al entorno Android

1.3.1.1 Editores

La ventana principal (la más grande) se llama “Editor”. El Editor es el espacio donde se
escribe el código fuente de los programas que estamos desarrollando.

Es posible tener varios ficheros de código fuente abiertos a la vez, apilados uno encima
de otro. En la parte superior de la ventana del Editor se muestran las pestañas que permiten
acceder a cada uno de los ficheros abiertos (o bien cerrarlos directamente).

Editor

1.3.1.2 Vistas

Además del Editor, existe un segundo tipo de ventanas “secundarias”, que se llaman
Vistas.

Las Vistas son ventanas auxiliares para mostrar información, introducir datos, etcétera.
Las Vistas se usan con múltiples propósitos, desde navegar por un árbol de directorios, hasta
mostrar el contenido de una consulta SQL.

41
Vistas

En función de las librerías de desarrollo (Android, GWT, Java, Delphi...) se definen


Editores propios y todas las Vistas necesarias.

En la ventana anterior están abiertas dos Vistas:

 La Vista vertical de la izquierda muestra el árbol de directorios de los proyectos


con los ficheros del mismo.

 La Vista horizontal inferior muestra una pequeña “agenda” de tareas pendientes


que pueden ser introducidas por el usuario, de forma directa, o por Eclipse, en
función de determinados eventos (compilación del proyecto, depuración de
código, etcétera).

Si deseamos cambiar las Vistas, se puede usar la opción “Show View” en el menú de la
pestaña “Window”.

42
Introducción al entorno Android

1.3.1.3 Barras de Herramientas principal y secundarias

La barra de herramientas principal contiene los accesos directos a las operaciones más
comunes, como abrir y guardar archivos. Además, también es posible ejecutar herramientas
externas y tareas relacionadas con el Editor activo, como ejecutar un programa, depurar el
código fuente, etcétera.

Además de la barra de herramientas principal (imagen anterior), cada Vista puede tener
su propia barra de herramientas secundaria.

1.3.1.4 Perspectivas

Una Perspectiva es un conjunto de ventanas (Editores y Vistas) agrupadas que


simplifican el desarrollo de un proyecto.

Al seleccionar una Perspectiva se carga una configuración guardada de las Vistas y


Editores de nuestro entorno de desarrollo Eclipse.

Por ejemplo, existe una Perspectiva "Java Browsing" que facilita el desarrollo de
aplicaciones Java y que incluye, además del Editor, Vistas para navegar por las clases, los
paquetes, etcétera.

Se puede cambiar la perspectiva activa utilizando la opción “Open Perspective” del


menú de Windows. Desde este mismo menú también es posible definir Perspectivas
personalizadas.

43
También existe un botón en la barra de herramientas principal para cambiar de
Perspectiva:

Si el alumno tiene dudas sobre el uso avanzado de Eclipse, en Internet existen muchos
tutoriales que indican cómo utilizarlo.

Además, es posible usar el menú "Help" o la tecla [F1] para solicitar ayuda.
Desgraciadamente, a día de hoy, esta ayuda sólo se encuentra en inglés.

Importante: para importar en Eclipse el código fuente de los ejemplos del curso hay que
usar la opción del menú principal: File -> Import.

44
Introducción al entorno Android

Después, hay que marcar Existing Proyects into Workspace en la ventana emergente y
pulsar en el botón “Next”:

Finalmente, seleccionamos el directorio de trabajo donde debemos haber copiado


previamente los ficheros con el código fuente de los ejemplos:
“C:\cursos_Mentor\Android\proyectos” y hacemos clic en “Finish”:

45
Nota: en esta Unidad 1 puedes encontrar el vídeo “Cómo cargar los ejemplos en Eclipse”,
que muestra cómo se importan los ficheros con el código fuente de los proyectos que son los
ejemplos del curso.

Importante: en el apartado “Problemas al cargar proyectos de Android” de Preguntas y


Respuestas (FAQ) de la Unidad 1 puedes encontrar soluciones a los problemas que ocurren
al importar el código fuente de los ejemplos del curso.

1.3.2 Cómo crear un proyecto Android

A continuación, vamos a describir cómo crear un proyecto usando Eclipse y las librerías de
Android que hemos instalado con anterioridad.

Se trata del primer proyecto que el alumno va a crear, por lo que es muy importante prestar
atención a los pasos seguidos, ya que los proyectos siguientes se generan de manera similar.

Así pues, arrancamos Eclipse.

46
Introducción al entorno Android

1.3.2.1 Creación de un nuevo proyecto

En el menú de Eclipse hacemos clic en File->New->Project:

También podemos hacer clic en el botón del menú de herramientas de Eclipse


haciendo clic en la opción “Open a wizard to help create a new Android project”:

A continuación, aparece una nueva ventana en la que escribimos el nombre de proyecto


"unidad1.eje1.bienvenido", "es.mentor.unidad1.eje1.bienvenido" para el paquete de java
(Package) y “Bienvenido” para Activity.

El resto de opciones las dejamos como aparecen en la siguiente captura de pantalla:

47
  

Pulsamos el botón “Finish” para crear los ficheros del proyecto.

A continuación, describimos los apartados que genera un proyecto Android:

 Project Name: como su nombre indica es el nombre del proyecto Eclipse;


corresponde con el nombre del directorio que contiene los ficheros del
proyecto.

 Build Target: indica la versión del Android SDK que vamos a usar para compilar
la aplicación. Por ejemplo, si seleccionas Android 2.3, la aplicación se compilará
para funcionar en esta versión de Android y las siguientes. La versión
seleccionada aquí no tiene que coincidir con la versión del Emulador de Android
(AVD), ya que las aplicaciones de Android están diseñadas de manera que se

48
Introducción al entorno Android

ejecutan en la plataforma en la que se desarrollaron y en todas las versiones


superiores de Android. Por ejemplo, una aplicación que de desarrolló para la
versión 2.1 se ejecutará bien en la versión 2.3.3. Al contrario no funciona.

 Application Name: es el nombre de la aplicación que aparece en el icono del


Escritorio de Android. Es el texto que ve el usuario del teléfono.

 Package Name: es el nombre del paquete Java en el que se almacena todo el


código fuente de la aplicación.

 Create Activity: define el nombre de la Actividad.

Importante: El nombre del paquete debe ser único en relación con todos los paquetes
instalados en Android. Por esta razón, es importante utilizar el estándar de dominio para
nombrar los paquetes de las aplicaciones. En el ejemplo anterior se utiliza el nombre de
paquete "es.mentor". A la hora de desarrollar tus propias aplicaciones y distribuirlas en el
Android Market (Mercado de aplicaciones Android) debes utilizar nombres propios de
paquetes.

En ningún caso debes utilizar el nombre “es.mentor” para distribuir aplicaciones en el Android
Market, pues sólo es válido para los ejemplos del curso.

1.3.2.2 Nombre de la actividad

Importante: El nombre de la actividad no puede incluir tildes, la letra “ñ”, ni caracteres raros.

1.3.2.3 Descripción de los ficheros por defecto del proyecto

Para ver los ficheros del proyecto Android creados por Eclipse, en la barra lateral
Package Explorer, desplegamos las entradas haciendo clic en los ficheros marcados con
flechas rojas de los diferentes paquetes:

49
A continuación, vamos a explicar la estructura y contenido de los ficheros del proyecto.

Carpeta/src/

Contiene todo el código fuente de la aplicación, código de la interfaz gráfica, clases


auxiliares, etcétera. Inicialmente, Eclipse crea el código básico de la Actividad (Activity)
principal de la aplicación, debajo del paquete Java definido.

Carpeta /res/

Contiene todos los ficheros de recursos necesarios para el proyecto: imágenes, vídeos,
cadenas de texto (para internacionalización de la aplicación), etcétera. Los diferentes tipos de
recursos se deben distribuir entre las siguientes subcarpetas:

 /res/drawable-X/: contiene las imágenes de la aplicación. Se divide en /drawable-


ldpi, /drawable-mdpi y /drawable-hdpi para utilizar diferentes recursos dependiendo
de la resolución del dispositivo.
 /res/layout/: contiene los ficheros de definición de las diferentes pantallas de la
interfaz gráfica. Se puede usar la carpeta /layout y /layout-land para definir los
diferentes diseños en función de la orientación del dispositivo.
 /res/anim/. alberga la definición de las animaciones utilizadas por la aplicación.
 /res/menu/: contiene la definición de los menús de la aplicación.
 /res/values/: contiene otros recursos de la aplicación como, por ejemplo, cadenas de
texto (strings.xml), estilos (styles.xml), colores (colors.xml), etcétera.
 /res/xml/: contiene los ficheros XML utilizados por la aplicación.
50
Introducción al entorno Android

 /res/raw/: contiene los recursos adicionales, normalmente en diferente formato a


XML, que no se incluyan en el resto de carpetas de recursos.

Carpeta /gen/

Reúne una serie de elementos de código generados automáticamente al compilar el


proyecto. Cada vez que compilamos el proyecto, Android genera una serie de ficheros fuente
Java dirigidos al control de recursos de la aplicación.

El archivo más importante es el que se puede observar en la imagen anterior, el fichero


R.java que define la clase Java denominada R.

Esta clase R contiene un conjunto de constantes con los ID de todos los recursos de la
aplicación incluidos en la carpeta /res/, de forma que el programador pueda acceder
fácilmente a estos recursos desde el código fuente a través de esta clase. Así, por ejemplo, la
constante R.drawable.icon define el ID de la imagen “icon.png” contenida en la
carpeta /res/drawable/. Veamos como ejemplo la clase R creada por defecto para el
proyecto nuevo:

/* AUTO-GENERATED FILE. DO NOT MODIFY.


*
* This class was automatically generated by the
* aapt tool from the resource data it found. It
* should not be modified by hand.
*/

package es.mentor.unidad1.eje1.bienvenido;

public final class R {


public static final class attr {
}
public static final class drawable {
public static final int icon=0x7f020000;
}
public static final class layout {
public static final int main=0x7f030000;
}
public static final class string {

public static final int app_name=0x7f040001;


public static final int hello=0x7f040000;
}

Importante: Esta clase la crea automáticamente Android por lo que no debemos modificarla.

51
Carpeta /assets/

Alberga el resto de ficheros auxiliares necesarios para que aplicación funcione, como
los ficheros de configuración, de datos, etcétera.

La diferencia entre los recursos incluidos en la carpeta /res/raw/ y los incluidos en la


carpeta /assets/ es que para los primeros se generará un ID en la clase R y se deberá acceder
a ellos usando un método de esta clase. Sin embargo, para los segundos no se generarán un
ID y se puede acceder a ellos por su ruta como a cualquier otro fichero del sistema.
Aplicaremos unos u otros según las necesidades de nuestra aplicación.

Carpeta /bin/

Es el directorio donde se guarda la aplicación una vez se ha compilado.

Carpeta /libs/

Es el directorio donde se almacenan las librerías de tipo JAR que amplían las
funcionalidades de la aplicación.

Fichero AndroidManifest.xml:

Contiene la definición en formato XML de las características principales de la aplicación,


como, por ejemplo, su identificación (nombre, versión, icono, etcétera), sus componentes
(Actividades, Mensajes, Servicios, etcétera) o los permisos necesarios para su ejecución.
Veremos más adelante otros detalles de este fichero.

Importante: Haciendo doble clic sobre estos ficheros podemos abrirlos en el Editor de
Eclipse. Es importante que el alumno se familiarice con este entorno de desarrollo y pruebe
las distintas opciones del mismo.

Al abrir los distintos ficheros veremos la siguiente ventana:

52
Introducción al entorno Android

1.3.2.4 Ejecución del proyecto Android

Una vez hemos creado el proyecto, vamos a explicar cómo ejecutamos esta aplicación
de prueba con Eclipse y el emulador de Android (AVD: Android Virtual Device).

Para ello, hacemos clic en el botón "Ejecutar" de la barra de herramientas principal

o en la opción "Run" de menú “Run”. También disponemos del atajo del teclado
[Ctrl+F11]

53
Si no aparece ningún problema de compilación, entonces aparecerá la siguiente
ventana:

Eclipse inicia el emulador de Android AVD en el ordenador que estés utilizando, para
que puedas probar el proyecto que has desarrollado. Ten en cuenta que el dispositivo
virtual tarda un rato en cargar cada vez que lo inicias. Hay que tener un poco de paciencia
hasta que parezca la ventana de inicio.

54
Introducción al entorno Android

En la consola de Eclipse puedes ir viendo el progreso de todo el proceso. Eclipse instala


automáticamente la nueva aplicación en el AVD y la ejecuta:

Cuando accedemos por primera vez al emulador, aparece la pantalla de bienvenida con
el terminal bloqueado:

Para desbloquear la pantalla hay que arrastrar el icono "candado" con el ratón hacia la
derecha.

Una vez desbloqueado el AVD, podemos ver el aspecto de la aplicación instalada:

55
Importante: En general, cada vez que modifiquemos el código fuente y deseemos probar de
nuevo nuestro proyecto no es necesario parar el emulador de aplicaciones y arrancarlo de
nuevo; simplemente hacemos clic de nuevo en el botón “Run” y Eclipse compilará, reinstalará
y ejecutará la aplicación modificada.

Una vez hayamos acabado de probar nuestro proyecto, es necesario parar el emulador de
Android.

Atención: En el Emulador de Android es posible probar varios proyectos a la vez.

Nota: En esta Unidad 1 puedes encontrar el vídeo “Cómo ejecutar un proyecto Android”, que
muestra cómo usar Eclipse para compilar y ejecutar los proyectos que son los ejemplos del
curso.

1.3.2.5 Cómo usar el emulador de Android (AVD)

Como puedes observar, el Emulador de Android simula un teléfono con botones


(lado derecho de la ventana). Si ya sabes utilizar este tipo de teléfonos no tendrás ningún
problema en manejar el emulador como si fuera un teléfono más.

Si no conoces Android, lee las siguientes instrucciones para ver cómo se maneja.
Cambiaremos el idioma del sistema operativo.
56
Introducción al entorno Android

El botón “Volver a atrás”, permite cerrar la aplicación y volver al “Escritorio” de Android:

En la pantalla que aparece a continuación, debemos desbloquear el dispositivo virtual.


Para ello, arrastramos con el ratón la barra que tiene un candado dibujado hacia la derecha:

Después, aparece la pantalla denominada “Pantalla Inicial” (en inglés se denomina


Home Screen), podemos acceder a todas las actividades instaladas haciendo clic en el icono
marcado con una flecha roja en la imagen siguiente:

57
Si lo hacemos, veremos las aplicaciones instaladas. A esta pantalla se la denomina
Pantalla de lanzamiento (en inglés se denomina Launcher Screen):

58
Introducción al entorno Android

Para movernos en esta pantalla, podemos usar el ratón como si fuera el dedo de tu
mano. Es decir, para ver los iconos que están abajo hay que hacer clic en la pantalla y, sin
soltar el botón del ratón, arrastrar la ventana hacia arriba:

Arrastrar hacia abajo


con el ratón

Haciendo clic con el ratón sobre el icono de una de las aplicaciones, el emulador la
ejecutará.

A continuación, vamos a modificar el idioma del sistema operativo. Para ello, haciendo
clic en el icono “Settings” aparece la siguiente pantalla:

Arrastrar hacia abajo


con el ratón

59
Desplazando con el ratón hacia arriba esta ventana hacemos clic en “Language &
keyboard”:

En la siguiente pantalla hacemos clic en “Select language”:

60
Introducción al entorno Android

Para acabar, desplazamos de nuevo la pantalla hacia arriba hasta que veamos el idioma
en el que deseamos configurar Android:

Arrastrar hacia abajo


con el ratón

Hacemos clic sobre el idioma correspondiente y el sistema operativo queda


configurado:

61
Después, debemos desmarcar la opción “Japanese IME” de esta pantalla:

Si usamos el botón “Volver atrás”, veremos que el idioma del sistema operativo ha
cambiado en la Pantalla Inicial (Home Screen):

62
Introducción al entorno Android

En el apartado “Uso del emulador de Android” de la Unidad 2 puedes encontrar una


descripción más ampliada y detallada del AVD.

1.4 CÓMO CREAR LA PRIMERA APLICACIÓN CON ANDROID

A continuación, vamos a explicar cómo crear un proyecto sencillo usando Eclipse y las
librerías Android.

Vamos a partir del proyecto de ejemplo que hemos creado en el punto anterior.

El primer proyecto Android consiste en una pantalla muy sencilla que muestra un
mensaje de bienvenida.

En la barra lateral Package Explorer de Eclipse, desplegamos las entradas haciendo


clic en las flechas de los diferentes paquetes.

Si abrimos el fichero BienvenidoActivity.java, veremos el código fuente de la aplicación


Android:

63
package es.mentor.unidad1.eje1.bienvenido;

import android.app.Activity;

import android.os.Bundle;

import android.widget.TextView;

public class BienvenidoActivity extends Activity {

/** Método que se llama cuando se crea una actividad. */

public void onCreate(Bundle savedInstanceState) {

// Llamamos al método de la clase superior (Activity)

super.onCreate(savedInstanceState);

// Establecemos los contenidos de la Intefaz de usuario

// de forma “programada”.

TextView tv = new TextView(this);

tv.setText("¡Bienvenido al curso de Android de Mentor!");

setContentView(tv);

// Descomentar la siguiente sentencia para usar los layout en

// el diseño de la Interfaz Usuario. Si lo haces, debes

// comentar las 3 sentencias anteriores.

// setContentView(R.layout.main);

Fíjate en que la clase principal BienvenidoActivity de la aplicación se basa en la clase


Activity de Android.

Una actividad (Activity) es el componente de la aplicación que realiza acciones. Una


aplicación puede tener muchas actividades, si bien el usuario sólo interactúa con ellas de una
en una. Android llama al método onCreate() cuando una actividad se inicia. En este método se
lleva a cabo toda la inicialización de variables y configuración de la interfaz de usuario. Una
actividad no está obligada a tener una interfaz de usuario, aunque generalmente la suele tener.

En la Unidad 2 veremos en detalle todos los métodos disponibles en esta clase


básica de Android.
64
Introducción al entorno Android

La interfaz de usuario de Android se compone de vistas (Views). Una vista es un


objeto que define el diseño de la interfaz de usuario. como un botón, una imagen, una etiqueta
de texto, etcétera. Cada uno de estos objetos se hereda de la clase principal View. En este
ejemplo hemos utilizado la subclase TextView, que crea una etiqueta de texto.

En el ejemplo se crea una etiqueta TextView en el constructor de la Actividad. Para


crear esta etiqueta es necesario pasar como parámetro una instancia del Contexto (Context)
de la aplicación Android. Un Contexto es un identificador del sistema que sirve para tener
acceso a recursos, a preferencias, a bases de datos, etcétera, de la aplicación. La clase
Actividad se hereda de la clase Contexto; por lo tanto, se puede pasar esta Actividad como el
Contexto de la aplicación escribiendo this.

Con el método setText() establecemos el texto contenido en la etiqueta.

Para acabar, usamos el método setContentView() para indicar a la Actividad el contenido de


la interfaz de usuario.

Si ejecutas la aplicación en Eclipse deberás ver la siguiente ventana en el emulador:

65
Nota:

Al ejecutar varias veces una aplicación desde Eclipse puede ocurrir que aparezcan los
siguientes mensajes de error en la consola:

Estos mensajes de error:

[2011‐11‐20 09:18:15 ‐ unidad1.eje1.bienvenido] Application already deployed. 
No need to reinstall. 

[2011‐11‐20  09:18:15  ‐  unidad1.eje1.bienvenido]  Starting  activity 


es.mentor.unidad1.eje1.bienvenido.BienvenidoActivity on device emulator‐5554 

[2011‐11‐20 09:18:16 ‐ unidad1.eje1.bienvenido] ActivityManager: Starting: 
Intent { act=android.intent.action.MAIN 
cat=[android.intent.category.LAUNCHER] 
cmp=es.mentor.unidad1.eje1.bienvenido/.BienvenidoActivity} 

[2011‐11‐20 09:18:16 ‐ unidad1.eje1.bienvenido] ActivityManager: Warning: 
Activity not started, its current task has been brought to the front 

Indican únicamente que no se ha modificado el código fuente y que la aplicación se muestra


de nuevo en el primer plano de la pantalla del dispositivo virtual.

1.4.1 Cambio en la Interfaz de usuario con Layout

Los Layout son elementos no visibles que establecen cómo se distribuyen en la interfaz
del usuario los componentes (widgets) que incluyamos en su interior. Podemos pensar en
estos elementos como paneles donde vamos incorporando, de forma diseñada, los
componentes con los que interacciona el usuario.

Nota: La clase Layout se hereda, como el resto de componente, de la clase Vista. A lo


largo del curso nos referimos a los componentes de Android como Vistas (Views) o como
Widgets, tanto si son visibles (botones, texto, menús….) como si son elementos de diseño
(layout).

En el ejemplo anterior hemos utilizado un diseño de interfaz de usuario "programado",


es decir, se construye esta interfaz con sentencias Java en el código fuente. Si ya has
desarrollado interfaces de esta manera, sabrás que pequeños cambios en su diseño pueden
dar lugar a grandes modificaciones en el código fuente.

66
Introducción al entorno Android

Al ser Android un lenguaje nuevo, permite desarrollar interfaces usando archivos de


diseño (Layout) XML. La forma más fácil de explicar este concepto es mostrar un ejemplo. El
fichero res/layout/main.xml define el diseño de la interfaz del usuario:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

<TextView

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/bienvenido"

/>
</LinearLayout>

La estructura general de un archivo de diseño de interfaz XML de Android es simple. Se


trata de un árbol de elementos XML, donde cada nodo es el nombre de una clase Vista (en
este ejemplo, usamos las clases LinearLayout y TextView). Puedes utilizar el nombre de
cualquier clase de tipo Vista (View) de Android o, incluso, una clase Vista personalizada por
el programador.

El Layout LinearLayout apila secuencialmente todos sus elementos hijos de forma


horizontal o vertical. En el apartado siguiente veremos diferentes tipos de paneles de diseño y
sus características.

Esta estructura XML hace que sea más fácil y rápido crear las interfaces de usuario.
Este modelo se basa en el modelo de desarrollo web, donde se separa la presentación
(interfaz de usuario) de la lógica de la aplicación (encargada de leer y escribir la información).

En el ejemplo de XML anterior sólo hay un elemento Vista: TextView, que tiene tres
atributos y un elemento de diseño Layout: LinearLayout, que tiene cuatro atributos. A
continuación, mostramos una descripción de los atributos:

67
Atributo Descripción

Esta declaración indica que vamos a usar el espacio de nombres (la


terminología) de Android para referirnos a los atributos que se definen
xmlns:android
a continuación.

Asigna un identificador único para el elemento correspondiente. Este


identificador sirve para poder acceder al componente desde el código
android:id fuente o desde las declaraciones de otros elementos en este archivo
XML.

Define el largo que debe ocupar la Vista. En este caso, indicamos que
android:layout_width el TextView debe ocupar toda la pantalla con "fill_parent”.

Similar al atributo anterior, en este caso, se refiere al ancho de la Vista.


android:layout_height

Establece el texto que la Vista TextView debe mostrar. En este


android:text ejemplo se utiliza una cadena que se establece en el archivo
res/values/strings.xml.

El título de la aplicación “Unidad1 - Ejemplo 1: Bienvenido” y la frase "¡Bienvenido al


curso de Android de Mentor!", que aparecen en el área del usuario, se definen en el fichero
res/values/strings.xml.

El SDK de Android permite definir los ficheros de tipo XML de dos formas: a través de
un editor visual o directamente en el archivo XML. Se puede cambiar entre las dos formas
haciendo clic en la pestaña de la parte inferior de la ventana. Por ejemplo, en el Package
Explorer, seleccionamos res/layout/main.xml y hacemos clic en “Graphical Layout”:

68
Introducción al entorno Android

En esta ventana podemos diseñar visualmente la pantalla de la aplicación Android


arrastrando con el ratón los componentes que aparecen en el apartador “Palette”.

Si en el Package Explorer seleccionamos res/layout/strings.xml y hacemos clic en


“Resources”:

Usando en esta ventana el botón “Add” podemos añadir visualmente los diferentes
tipos de recursos de Android.

Para crear la primera aplicación hemos usado componentes (Widgets o Vistas) usuales
de Android. En el siguiente apartado de teoría explicaremos en detalle el tipo de componentes
disponibles por defecto y cómo usarlos para diseñar las pantallas que servirán de interfaz
gráfica al usuario.

Fichero AndroidManifest.xml: contiene la definición en formato XML de las


características principales de la aplicación, como su identificación (nombre, versión, icono,
etcétera), sus componentes (Actividades, Mensajes, Servicios, etcétera) o los permisos
necesarios para su ejecución. Más adelante veremos otros detalles de este fichero.

En este fichero hay que declarar la Actividad para que Android tenga acceso a la misma.
Si abres el archive manifest, verás que existe el siguiente elemento <activity>:

69
<?xml version="1.0" encoding="utf‐8"?> 

<manifest xmlns:android="http://schemas.android.com/apk/res/android" 

... 

<activity android:name=".BienvenidoActivity" 

                  android:label="@string/app_name"> 

... 

</manifest> 

En este fichero se pueden definir varios atributos para establecer la etiqueta de la


actividad, su icono o el tema de estilo de la interfaz de usuario. El único atributo obligatorio es
android:name, que especifica el nombre de clase de la actividad. Es importante usar siempre
el mismo nombre de Actividad ya que otra aplicación puede iniciarla.

Importante: Aunque el código fuente de este ejemplo se encuentra disponible en la carpeta


de ejemplos de esta unidad, es fundamental que crees este proyecto Android desde el
principio para entender la secuencia de pasos dados y los ficheros necesarios.

Además, si no has usado nunca el entorno de desarrollo Eclipse - Android, adquirirás soltura
utilizándolo.

1.5 DISEÑO DE LA INTERFAZ DE USUARIO

1.5.1 Cómo diseñar la interfaz de usuario mediante Vistas

Una de las características más importante de Android es la posibilidad de usar


componentes gráficos dinámicos y reutilizables (en inglés se denominan Views).

Mediante el SDK de Android, el programador puede utilizar clases prediseñadas para


implementar elementos y comportamientos en la interfaz del usuario, que, de otra forma, éste
tendría que crear desde cero, tales como botones, cuadros de edición complejos, arrastrar y
soltar, o menús en árbol.

Nota: Como en la literatura inglesa de Android se habla genéricamente de Vista (View) para
referirse a estos componentes visuales y en Internet siempre aparecen referencias a esta
palabra, vamos a usar esta nomenclatura a partir de ahora.

Es importante no confundir los widgets (componentes o Vistas) que usamos al desarrollar las
interfaces de usuario en Android con “Widget de la pantalla principal” (Screen Home), que son
pequeñas aplicaciones que el usuario del teléfono puede añadir a esta pantalla, tales como un
calendario dinámico, previsión meteorológica, etcétera.

70
Introducción al entorno Android

1.5.2 Vistas disponibles de Android

Construir interfaces de usuario en las aplicaciones de Android es muy sencillo y rápido


gracias a que podemos utilizar Vistas.

Como hemos visto en el apartado anterior, las Vistas visibles deben situarse dentro de
otro tipo de Vista denominada Layout (Panel de diseño).

Estos paneles de diseño (Layout) de Android se usan para diseñar la interfaz gráfica del
usuario de la aplicación. Estos paneles se usan para separar simbólicamente el área de la
aplicación. Dentro de estos paneles se incluye la mayoría de las Vistas, como botones,
cuadros de texto, etcétera. Además, dentro de un panel se pueden incluir otros paneles
para hacer diseños complejos.

Nota: Cuando se describan los métodos más importantes de cada Vista (View), sólo se
incluirán aquéllas que no se hayan señalado con anterioridad o se invoquen con diferentes
argumentos.

Además, el entorno de Eclipse dispone de una ventana emergente de ayuda que, al


escribir código fuente, muestra los diferentes métodos disponibles para esa clase. De esta
forma, evitamos errores de codificación y el desarrollo de las aplicaciones web es mucho más
rápido y eficiente.

En la siguiente imagen mostramos el aspecto que tiene esta ventana de ayuda.

Con el atajo de teclado [CTRL+BARRA_ESPACIADORA] podemos acceder a esta


ventana de ayuda emergente.

71
Tipos de paneles (Layout)

Panel Marco (FrameLayout)

Éste es el panel más sencillo de todos los Layouts de Android. Un panel FrameLayout
coloca todos sus componentes hijos alineados pegados a su esquina superior izquierda de
forma que cada componente nuevo añadido oculta por el componente anterior. Por esto, se
suele utilizar para mostrar un único control en su interior, a modo de contenedor (placeholder)
sencillo para un único elemento, por ejemplo, una imagen.

Los componentes incluidos en un FrameLayout pueden establecer las propiedades


android:layout_width y android:layout_height, que pueden establecerse con los valores:

 fill_parent para que el componente hijo tenga la dimensión del layout que lo
contiene.

 wrap_content para que el componente hijo ocupe el tamaño de su contenido.

Ejemplo

<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<EditText android:id="@+id/TextoNombre"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</FrameLayout>

72
Introducción al entorno Android

Panel Lineal (LinearLayout)

Vertical y Horizontal

El panel LinearLayout apila todos sus componentes hijos de forma horizontal o


vertical, según se establezca la propiedad android:orientation con el valor “vertical” u
“horizontal”.

De igual forma que en un FrameLayout, se pueden establecer las propiedades


android:layout_width y android:layout_height. Además, existe la propiedad
android:layout_weight, en el caso de un panel LinearLayout, que permite establecer las
dimensiones de los componentes contenidos proporcionales entre ellos.

Ejemplo

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">

<EditText android:id="@+id/TextoDato1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />

<EditText android:id="@+id/TextoDato2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="2" />

</LinearLayout>

Si incluimos en un panel vertical dos cuadros de texto (EditText) y en uno de ellos


establecemos un layout_weight=”1 y en el otro un layout_weight=”2 , conseguiremos que
toda la superficie del panel esté ocupada por los dos cuadros de texto y, además, que el
segundo sea el doble (relación entre sus propiedades weight) de alto que el primero.

73
Panel Tabla (TableLayout)

El panel TableLayout permite distribuir todos sus componentes hijos como si se


tratara de una tabla mediante filas y columnas.

La estructura de la tabla se define de manera similar a una tabla en formato HTML, es


decir, indicando las filas que compondrán la tabla (objetos TableRow) y las columnas de
cada una de ellas.

Por norma general, el ancho de cada columna corresponde al ancho del mayor
componente de dicha columna, pero existen una serie de propiedades pueden modificar este
comportamiento:

 android:stretchColumns: indica el número de columna que se expande para


ocupar el espacio libre que dejan el resto de columnas a la derecha de la
pantalla.

 android:shrinkColumns: indica las columnas que se pueden contraer para


dejar espacio al resto de columnas de lado derecho de la pantalla.

 android:collapseColumns: indica las columnas de la tabla que se pueden


ocultar completamente.

Todas estas propiedades del TableLayout pueden establecerse con una lista
de índices de las columnas separados por comas, por ejemplo:
android:stretchColumns=”1,2,3 o un asterisco para indicar que se debe
aplicar a todas las columnas, de esta forma: android:stretchColumns=”*”.

 android:layout_span: una celda determinada puede ocupar el espacio de


varias columnas de la tabla (análogo al atributo colspan de HTML) del
componente concreto que ocupa dicho espacio.

Ejemplo

<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="1">

<TableRow>
<TextView android:text="Celda 1.1">
<TextView android:text="Celda 1.2">
</TableRow>

<TableRow>

74
Introducción al entorno Android

<TextView android:text="Celda 2.1">


<TextView android:text="Celda 2.2">
</TableRow>

<TableRow>
<TextView android:text="Celda 3" android:layout_span="2" />
</TableRow>

</TableLayout>

Desde Eclipse puedes abrir el proyecto Ejemplo 2 (Panel tabla) de la Unidad 1. Estudia
el código fuente y ejecútalo para mostrar en el emulador el resultado del programa anterior,
en el que hemos utilizado el Layout TableLayout.

Panel Relativo (RelativeLayout)

El panel RelativeLayout permite especificar la posición de cada componente de forma


relativa a su elemento padre o a cualquier otro elemento incluido en el propio layout. Así, al
incluir un nuevo componente X podemos indicar, por ejemplo, que debe situarse debajo del
componente Y y alineado a la derecha del layout padre (el que lo contiene).

Un panel RelativeLayout dispone de múltiples propiedades para colocar cada


componente. Las principales son:

Posición relativa a otro control:

 android:layout_above: arriba.
 android:layout_below: debajo.
 android:layout_toLeftOf: a la izquierda de.
 android:layout_toRightOf: a la derecha de.
 android:layout_alignLeft: alinear a la izquierda.
 android:layout_alignRight: alinear a la derecha.
 android:layout_alignTop: alinear arriba.
 android:layout_alignBottom: alinear abajo.
 android:layout_alignBaseline: alinear en la base.

Posición relativa al layout padre:

 android:layout_alignParentLeft: alinear a la izquierda.


 android:layout_alignParentRight: alinear a la derecha.
 android:layout_alignParentTop: alinear arriba.
 android:layout_alignParentBottom: alinear abajo.
 android:layout_centerHorizontal: alinear horizontalmente al
75
centro.
 android:layout_centerVertical: alinear verticalmente al centro.
 android:layout_centerInParent: centrar.

Opciones de margen (también disponibles en el resto de layouts):

 android:layout_margin: establece el margen.


 android:layout_marginBottom: establece el margen inferior.
 android:layout_marginTop: establece el margen superior.
 android:layout_marginLeft: establece el margen izquierdo.
 android:layout_marginRight: establece el margen derecho.

Opciones de espaciado o padding (también disponibles en el resto de layouts):

 android:padding: establece la separación entre componentes.


 android:paddingBottom: establece la separación inferior.
 android:paddingTop: establece la separación superior.
 android:paddingLeft: establece la separación izquierda.
 android:paddingRight: establece la separación derecha.

Ejemplo

<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<EditText android:id="@+id/TextoNombre"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />

<Button android:id="@+id/BtnAceptar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/TextoNombre"
android:layout_alignParentRight="true" />

</RelativeLayout>

En este ejemplo, el botón BtnAceptar se coloca debajo del cuadro de texto


TextoNombre (android:layout_below=”@id/TxtNombre”) y alineado a la derecha del layout
padre (android:layout_alignParentRight=”true”); además, se establece un margen a su
izquierda de 10 pixeles (android:layout_marginLeft=”10px”).

76
Introducción al entorno Android

Panel Marco (FrameLayout

El panel FrameLayout permite superponer en el área de la pantalla varios


componentes hijos. Por lo general, este panel debe contener pocos componentes hijos, ya
que puede ser difícil organizarlos sin que se superpongan unos con otros en los diferentes
tamaños de pantalla de los teléfonos. El atributo más importante de los componentes hijos
que contiene es layout_gravity, que permite controlar su posición relativa.

Ejemplo

<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="center"
android:src="@drawable/imagen" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dip"
android:layout_gravity="center_horizontal|bottom"
android:padding="12dip"
android:background="#AA000000"
android:textColor="#ffffffff"
android:text="Imagen" />

</FrameLayout>

En este ejemplo, mostramos una imagen que ocupa todo el área de la pantalla usando
el atributo android:scaleType="center" y una etiqueta superpuesta en la parte inferior de la
pantalla con el atributo android:layout_gravity="center_horizontal|bottom".

77
Componentes Básicos

Como hemos comentado anteriormente, los interfaces de las aplicaciones de usuario en


Android se construyen usando componentes o Vistas que están contenidas en paneles de
diseño (layout). Los componentes permiten al usuario interaccionar con la aplicación. Los
paneles ordenan la posición de estos elementos en la interfaz del usuario.

A continuación, vamos a mostrar los componentes básicos.

Botones

Los botones se usan para que el usuario interactúe con la aplicación Web:

El SDK de Android proporciona tres tipos de botones: el botón clásico (Button), el de


tipo on/off (ToggleButton) y el que puede albergar una imagen (ImageButton).

 El componente Button es el botón básico de Android. En el ejemplo siguiente


definimos un botón con el texto “Haz clic aquí” asignando la propiedad android:text.

<Button android:text="Haz clic aquí"

android:id="@+id/boton" android:layout_height="wrap_content"

android:layout_width="135dp">

</Button>

Además, podemos utilizar otras propiedades, como el color de fondo


(android:background), el estilo de la fuente (android:typeface), el color de fuente
(android:textcolor), el tamaño de la fuente (android:textSize), etcétera.

 El componente ToggleButton es un tipo de botón que puede encontrarse en dos


estados: pulsado (ON) o no_pulsado (OFF). En este caso, en lugar de definir un único
texto, podemos establecer dos en función del estado asignando las propiedades
android:textOn y android:textoOff, respectivamente. Veamos un ejemplo a
continuación:

<ToggleButton android:id="@+id/toggleButton"

android:text="ToggleButton" android:layout_width="88dp"

android:textOn="Encendido"

android:textOff="Apagado"

android:layout_height="match_parent">

78
Introducción al entorno Android

</ToggleButton>

 El componente ImageButton es un botón que muestra una imagen en lugar de un


texto asignando la propiedad android:src. Normalmente, indicamos esta propiedad
usando el descriptor de alguna imagen que hayamos copiado en la carpeta
/res/drawable. Así, por ejemplo, en nuestro caso hemos incluido la imagen “stop.png”, a
la que hacemos referencia en “@drawable/ok“.

<ImageButton android:layout_height="wrap_content"

android:src="@drawable/stop" android:layout_width="wrap_content"

android:id="@+id/imageButton">

</ImageButton>

Los botones disponen de eventos que se puede capturar. El más común es el evento
onClick. En la Unidad 2 veremos qué son los Eventos y los Listerners, si bien en este
apartado se incluyen algunos ejemplos para que la teoría sea consistente.

Para definir la lógica de este evento hay que definir un nuevo objeto
View.OnClickListener() y asociarlo al botón mediante el método setOnClickListener(). La
forma de hacerlo es la siguiente:

final Button btnBoton1 = (Button)findViewById(R.id.boton);

btnBoton1.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View arg0)

lblEtiqueta.setText("¡Has pulsado el Botón!");

});

En el caso del botón de tipo ToggleButton suele ser más útil conocer el estado en el
que está el botón tras ser pulsado. Para esto, se usa el método isChecked(). En el siguiente
ejemplo se comprueba el estado del botón después de ser pulsado y se realizan diferentes
acciones según su resultado:

79
final ToggleButton btnBoton2 =
(ToggleButton)findViewById(R.id.toggleButton);

btnBoton2.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View arg0)

if(btnBoton2.isChecked())

lblEtiqueta.setText("Botón Encendido");

else

lblEtiqueta.setText("Botón Apagado");

});

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Componentes básicos) de la Unidad 1.


Estudia el código fuente y ejecútalo para mostrar en el emulador el resultado del programa
anterior.

Etiqueta (TextView)

La etiqueta (o TextView en inglés) permite mostrar un determinado texto al usuario. El


texto se establece mediante la propiedad android:text. Además de esta propiedad, se puede
cambiar el formato del texto usando las siguientes propiedades: android:background (color
de fondo), android:textColor (color del texto), android:textSize (tamaño de la fuente) y
android:typeface (estilo del texto: negrita, cursiva). Fíjate en el código del siguiente ejemplo:

<TextView android:id="@+id/texto1" android:text="Texto Plano"

android:textSize="30dp"

android:layout_width="wrap_content"

android:layout_height="wrap_content" />

<TextView android:id="@+id/texto2" android:text="Fuente Serif"

android:layout_width="wrap_content"

80
Introducción al entorno Android

android:layout_height="wrap_content"

android:textSize="25dp"

android:typeface="serif" />

<TextView android:id="@+id/texto3" android:text="Negrita"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textStyle="bold" />

<TextView android:id="@+id/texto4" android:text="Cursiva"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textSize="40dp"

android:textColor="#FF0000"

android:textStyle="italic" />

Además, podemos modificar estas propiedades desde nuestro código Java usando los
métodos getText() para recuperar el texto de una etiqueta, setText() para actualizar el texto y
setBackgroundColor() para cambiar el color de fondo. Por ejemplo, así:

// Buscamos la etiqueta con el id texto1

final TextView lblEtiqueta = (TextView)findViewById(R.id.texto1);

String texto = lblEtiqueta.getText().toString();

texto += " abc";

lblEtiqueta.setText(texto);

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Componentes básicos) de la Unidad 1.


Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del programa
anterior, en el que hemos utilizado el componente TextWiew

Imagen (ImageView)

La imagen (o ImageView en inglés), como su propio nombre indica, permite mostrar


imágenes en la aplicación. La propiedad más útil es android:src y permite establecer la
imagen que se muestra. De nuevo, lo usual es indicar como origen de la imagen el
identificador de un recurso de la carpeta /res/drawable. Además de esta propiedad, existen

81
otras, como las destinadas a establecer el tamaño máximo que puede ocupar la imagen:
android:maxWidth y android:maxHeight. Fíjate en el código del siguiente ejemplo:

<ImageView android:id="@+id/ImgFoto"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/icon" />

En la lógica de la aplicación, podemos establecer la imagen mediante el método


setImageResorce():

ImageView img= (ImageView)findViewById(R.id.ImgFoto);

img.setImageResource(R.drawable.icon);

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Componentes básicos) de la


Unidad 1. Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del
programa anterior, en el que hemos utilizado el componente ImageView.

Cuadro de Texto (Text Edit)

El Cuadro de Texto (o EditText en inglés) es el componente de edición de texto de


Android que permite la introducción y edición de texto al usuario. Su propiedad más
importante es android:text, que establece el texto que contiene. Fíjate en el código del
siguiente ejemplo:

<EditText android:id="@+id/editTexto"

android:layout_width="match_parent"

android:layout_height="wrap_content">

También es posible recuperar y establecer este texto mediante los métodos getText() y
setText(nuevoTexto) respectivamente:

final EditText txtTexto = (EditText)findViewById(R.id.editTexto);

82
Introducción al entorno Android

texto = txtTexto.getText().toString();

txtTexto.setText("Esto es un texto");

En el código fuente anterior hemos hecho un cambio de formato usando el método


toString() sobre el resultado de getText(). El método getText() no devuelve una cadena
(String), sino un objeto de tipo Editable (tipo Spanned, algo así como una cadena de
caracteres en la que podemos insertar etiquetas)

Es decir, el componente EditText permite editar texto plano y texto enriquecido o con
formato; por eso hemos tenido que usar un método para cambiar la cadena perdiendo el
formato enriquecido.

Para poder obtener el texto con el formato correspondiente, podemos usar la clase
Html de Android, que dispone de los métodos para convertir cualquier objeto de tipo
Spanned en su representación HTML equivalente. Veamos cómo funciona:

//Obtiene el texto del componente con etiquetas de formato HTML

String aux2 = Html.toHtml(txtTexto.getText());

La sentencia anterior devolvería una cadena de texto como ésta

“<p>Esto es una <b>prueba</b>.</p>”.

También es posible realizar la operación opuesta, es decir, establecer en un cuadro de


texto (EditText) o en una etiqueta (TextView) un texto en formato HTML. Para ello, se utiliza
el método Html.fromHtml(String) así:

//Asigna el texto con formato HTML

txtTexto.setText(

Html.fromHtml("<p>Esto es una <b>prueba</b>.</p>"),

BufferType.SPANNABLE);

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Componentes básicos) de la Unidad 1.


Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del programa
anterior, en el que hemos utilizado el componente EditText.

83
Cuadro de Selección (CheckBox)

La caja de selección (o CheckBox en inglés) permite al usuario marcar o desmarcar


opciones en una aplicación. La forma de definirlo en la interfaz y los métodos disponibles
para manipularlos son análogos a los ya comentados para el componente ToggleButton.
Fíjate en el código del siguiente ejemplo:

<LinearLayout android:id="@+id/linearLayout4"

android:orientation="vertical" android:layout_width="154dp"

android:layout_height="wrap_content">

<CheckBox android:id="@+id/check1" android:layout_width="wrap_content"

android:layout_height="wrap_content" android:text="Android" />

<CheckBox android:id="@+id/check2" android:layout_width="wrap_content"

android:layout_height="wrap_content" android:text="iPhone" />

</LinearLayout>

<Button android:id="@+id/botonCB" android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Confirmar Selección"

android:layout_gravity="center_vertical" />

Respecto a la personalización de estilo del componente, podemos emplear casi todas


las opciones del componente TextView comentadas anteriormente.

En el código de la aplicación podemos utilizar los métodos isChecked() para conocer


el estado del componente y setChecked(boolean) para establecer un estado en concreto.

En cuanto a los posibles eventos interesantes que puede lanzar este componente, el
más interesante es onCheckedChanged que notifica que la selección ha cambiado. Por
ejemplo, así:

CheckBox.OnCheckedChangeListener CBCambioListener = new

CheckBox.OnCheckedChangeListener() {

public void onCheckedChanged(CompoundButton buttonView,

boolean isChecked)

84
Introducción al entorno Android

if (isChecked) {

txtTexto.setText("¡Checkbox "+ buttonView.getText() +

" marcado!");

else {

txtTexto.setText("¡Checkbox "+ buttonView.getText() +

" desmarcado!");

};

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Componentes básicos) de la


Unidad 1. Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del
programa anterior, en el que hemos utilizado el componente Checkbox.

Botón de radio (RadioButton)

El botón de radio (o RadioButton en inglés) permite elegir una única opción de un


grupo de opciones, es decir, si se marca una de ellas se desmarcará automáticamente la
anterior. En Android, los botones RadioButton se agrupan dentro de un elemento
RadioGroup. Veamos un ejemplo de cómo definir un grupo de botones RadioButton en la
interfaz:

<RadioGroup android:id="@+id/gruporb" android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent">

<RadioButton android:id="@+id/radio1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

85
android:text="Opción 1" />

<RadioButton android:id="@+id/radio2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Opción 2" />

</RadioGroup>

En primer lugar, hemos establecido la orientación (vertical u horizontal) como hicimos


con el componente LinearLayout. Después, hemos añadido todos los componentes
RadioButton necesarios, indicando su ID mediante la propiedad android:id y su texto
mediante la propiedad android:text.

Una vez definida la interfaz, podemos manipular los componentes desde el código java
haciendo uso de los diferentes métodos del componente RadioGroup como, por ejemplo:

 check(id): selecciona una opción determinada mediante su ID.

 clearCheck():desmarca la opción seleccionada.

 getCheckedRadioButtonId():devuelve el ID de la opción seleccionada o -1 si


no hay ninguna marcada.

En cuanto a los eventos iniciados por este componente, como los CheckBox, el más
útil es el que informa de los cambios en el elemento seleccionado onCheckedChange. Fíjate
en el siguiente ejemplo:

    // Definimos el evento OnCheckedChange 

final RadioGroup rg = (RadioGroup)findViewById(R.id.gruporb);

rg.setOnCheckedChangeListener(

new RadioGroup.OnCheckedChangeListener() {

public void onCheckedChanged(RadioGroup grupo, int checkedId) {

// Obtenemos el RadioButton que está seleccionado usando

// el ID marcado checkedId

final RadioButton rb = (RadioButton) findViewById(checkedId);

86
Introducción al entorno Android

txtTexto.setText("RadioButton seleccionado: " + rb.getText());

});

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Componentes básicos) de la


Unidad 1. Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del
programa anterior, en el que hemos utilizado el componente

Si ejecutas en Eclipse este Ejemplo 3, verás que se muestra la siguiente aplicación en


el Emulador:

87
 Android es un sistema operativo, inicialmente diseñado para teléfonos móviles
con sistemas operativos iOS (Apple), Symbian (Nokia) y Blackberry OS.

 Android, basado en Linux, es un sistema operativo libre y gratuito.

 Los componentes principales de la arquitectura del sistema operativo Android


son las Aplicaciones, el Marco de desarrollo (SDK), las Librerías de Android, el
Runtime (ejecutable) y el Núcleo de Linux.

 Todas las aplicaciones de Android usan el siguiente conjunto de servicios y


sistemas:

o Un conjunto de componentes (Views)

o Proveedores de contenidos (Content Providers)

o Gestor de recursos (Resource Manager)

o Gestor de notificaciones (Notification Manager)

o Gestor de actividades (Activity Manager)

 Android usa Java como lenguaje base para el desarrollo de las aplicaciones; por lo
tanto, emplea Paquetes Java (Package en inglés). Estos paquetes son
contenedores de clases que permiten agrupar las distintas partes de un programa
cuya funcionalidad tienen elementos comunes.

 El nombre de los paquetes debe ser único en relación con los paquetes
instalados en Android. Por esta razón, es importante utilizar el estándar de dominio
“com.dominio…” para nombrarlos.

 Los ficheros que contengan el código fuente de las actividades del alumno han de
guardarse en una carpeta personal. Recomendamos usar el directorio
C:\cursos_Mentor\Android\proyectos para este fin.

 Es muy importante conocer la estructura de directorios y el nombre de los


ficheros que componen un proyecto Android.

88
Introducción al entorno Android

 Para comprobar que una aplicación funciona, hay que hacer clic en la opción "Run"
de menú "Run" de Eclipse (Atajo del teclado [Ctrl+F11]); después, arrancará el
Emulador (AVD) de Android para ver el resultado de su ejecución.

 Un Dispositivo Virtual de Android (en inglés, AVD, Android Virtual Device) emula
un terminal instalado con Android en el que podemos probar las aplicaciones
desarrolladas.

 Todas las sentencias de Android (Java) deben acabar con ;.

 Las sentencias o instrucciones compuestas contienen varias sentencias simples y


deben estar incluidas entre los signos { y }. Generalmente, una sentencia
compuesta está integrada por sentencias simples de un bucle o de la declaración
de una función que deben ejecutarse como un bloque.

 Para poder seguir mejor el flujo de un programa y ver más intuitivamente su


código, conviene indentar (adentrar unos espacios) las sentencias que están
incluidas dentro de una estructura. En Eclipse podemos usar el atajo de teclado
[CTRL+I] para hacerlo automáticamente.

 Los comentarios ayudan mucho a comprender un programa. Los que sólo


ocupan una línea deben ir precedidos de los signos //. Si el texto ocupa más de una
línea, hay que incluirlo entre los signos /* y */.

 Android dispone de todas las variables, funciones, expresiones y operadores


más usuales de Java.

 Una de las características más importante de Android es la posibilidad de usar


componentes gráficos dinámicos y reutilizables (en inglés se denominan Views).

 Los paneles en Android se usan para diseñar (en inglés se denomina layout) la
interfaz gráfica del usuario de la aplicación.

 El entorno de Eclipse ayuda al programador mostrando una ventana emergente


de ayuda al escribir el código fuente. En ella se proponen los diferentes métodos
disponibles para esa clase. Disponemos también del atajo de teclado
[CTRL+BARRA_ESPACIADORA].

89
 Las Vistas visibles están contenidos en los paneles y permiten interaccionar al
usuario con la aplicación.

 Android define detectores de eventos (Listeners) que, asociados a un


componente, permiten controlar la interacción del usuario sobre éste: clic del ratón,
escribir en el teclado, etcétera. En la Unidad 2 se tratan en detalle estos detectores.

 En Android como en cualquier lenguaje las expresiones constituyen uno de los


asuntos más importantes de la programación, pues intervienen en todas las
sentencias y están integradas por todos los elementos de un lenguaje informático.

90
DISEÑO DEL INTERFAZ DE
USUARIO

ÍNDICE
2.1 ACTIVIDADES - ANDROID .................................................. 93
2.1.1 Introducción..............................................................................93
2.1.2 Creación de una actividad......................................................93
2.1.3 Ciclo de vida de una actividad ..............................................94
2.1.4 Cómo se implementa el ciclo de vida de una actividad ....94

2.2 EVENTOS Y LISTENERS ...................................................... 100


2.2.1 Gestionando los eventos del usuario ............................. 100
2.2.2 Uso de los Event Listeners ............................................. 100
2.2.3 Gestores de Eventos (Event Handlers)........................... 104
2.2.4 Modo táctil de pantalla ................................................... 105
2.2.5 Controlando la Vista con el foco activo.......................... 106

2.3 USO DEL EMULADOR DE ANDROID ................................. 106


2.3.1 Teclado del emulador ..................................................... 108
2.3.2 Cómo introducir tildes con el Teclado del Emulador ..... 110
2.3.3 Limitaciones del Emulador ............................................. 111
2.3.4 Tamaño ventana emulador ............................................. 111
2.3.5 Otras opciones del Emulador ......................................... 112
2.3.6 Cómo configurar las opciones del Emulador ................. 113

2.4 COMPONENTES AVANZADOS ........................................... 114


2.4.1 Qué son los Adaptadores de Android (adapters) ........... 114

2.5 COMPONENTES PERSONALIZADOS................................ 127


2.5.1 Diseño de componentes personalizados ....................... 127
2.5.2 Cómo crear un componente extendido ......................... 127
2.5.3 Cómo combinar varios componentes para crear uno
compuesto ........................................................................... 130
2
Diseño del interfaz de usuario

2.1 ACTIVIDADES - ANDROID

2.1.1 Introducción

Una Actividad (Activity) es un componente de Android que ofrece una pantalla


con la que los usuarios pueden interactuar con la aplicación, como marcar el teléfono,
sacar una foto, enviar un correo electrónico o ver un mapa. Cada Actividad tiene asociada una
ventana en la que se dibuja la interfaz de usuario. Normalmente, esta ventana ocupa toda la
pantalla, aunque puede ser menor que ésta o flotar sobre otras ventanas.

Por lo general, una aplicación de Android consta de múltiples actividades que están
más o menos ligadas entre sí. Habitualmente, se define una actividad "principal", que es la que
se presenta al usuario cuando se inicia la aplicación por primera vez. Una actividad puede
iniciar otra actividad con el fin de realizar diferentes operaciones. Cada vez que comienza una
nueva actividad, la actividad anterior se detiene y la envía a una pila de retroceso ("back
stack"). Esta pila usa el mecanismo de cola LIFO ("last in, first out"), por lo que, cuando el
usuario pulsa la tecla “Volver atrás” del móvil, se extrae de la pila la actividad anterior
(destruyéndose la pila) y se reanuda. En la Unidad 3 veremos en detalle cómo funciona esta
pila.

Cuando una actividad se para porque se inicia una nueva actividad, se le notifica este
cambio de estado a través de los métodos de llamada callback del ciclo de vida de la
actividad.

Una función de llamada (en inglés callback) es una función que se remite a Android
cuando se inicia una Actividad, para que el sistema operativo la “llame” durante la ejecución
de esta Actividad.

Existen varios métodos de llamada callback que una actividad puede recibir debido a
un cambio en su estado; por ejemplo, cuando el sistema crea la Actividad, cuando se reactiva
o cuando se destruye. El programador puede aprovechar estos métodos para ejecutar
sentencias específicas apropiadas para el cambio de estado. Por ejemplo, cuando una
Actividad se suspende es recomendable liberar de la memoria todos los objetos grandes.
Cuando la actividad se reanuda, se puede volver a reservar los recursos necesarios y
continuar con las acciones que se interrumpieron. Estos cambios de estado forman parte
del ciclo de vida de la actividad.

2.1.2 Creación de una actividad

Para crear una Actividad debemos usar la clase Activity de Android. En la subclase
creada es necesario implementar los métodos callback que el sistema puede invocar cuando
hay un cambio en su ciclo de vida: la actividad se está creando, se detiene, se reanuda o se
destruye. Los dos métodos callback más importantes son:

93
 onCreate(): es obligatorio implementar este método ya que el sistema lo invoca
cuando crea su actividad. Dentro de su implementación debemos iniciar los
componentes esenciales de la actividad, como dibujar la interfaz de usuario
empleado la función setContentView().

 onPause(): el sistema llama a este método cuando el usuario detiene la actividad,


aunque no significa que la actividad se destruya. Aquí es donde el programador debe
guardar todos los cambios que deben persistir en la siguiente sesión del usuario, ya
que éste podría no volver a la Actividad y que ésta se destruyera.

Es posible utilizar otros métodos de tipo callback para proporcionar una experiencia de
usuario fluida entre actividades y manejar el paso de una a otra. Lo veremos más adelante en
este apartado.

2.1.3 Ciclo de vida de una actividad

Una Actividad puede mantenerse en tres estados:

 Resumed: la actividad está en el primer plano de la pantalla y el usuario la está


utilizando. Este estado también se denomina "running".

 Paused: la actividad es visible, pero hay otra actividad en primer plano que tiene el
foco. La actividad pausada sigue “viva” ya que se mantiene en memoria y conserva
toda la información de estado, si bien el sistema operativo puede eliminarla en caso
de memoria disponible muy baja.

 Stopped: la actividad se oculta completamente por una nueva actividad (la actividad
anterior se ejecuta en "background"). Una actividad detenida también se mantiene
en memoria con toda la información de estado. Sin embargo, el usuario ya no la ve
visible y el sistema operativo puede eliminarla cuando se necesita memoria para otra
tarea.

Si una actividad está “pausada” o detenida, el sistema puede eliminarla de la memoria


invocando el método finish() de la Actividad o, simplemente, puede acabar (“kill”) con el
proceso. Si se quiere abrir de nuevo esta actividad, después de haber finalizado, debe ser
creada otra vez desde el principio

2.1.4 Cómo se implementa el ciclo de vida de una actividad

Cuando una actividad cambia entre los diferentes estados descritos anteriormente, el
sistema operativo le notifica el cambio mediante diferentes métodos callback. El programador
puede usar todos estos métodos callback para ejecutar las órdenes apropiadas. El ejemplo
siguiente incluye la estructura de cada uno de estos métodos fundamentales del ciclo de vida
de una Actividad:

94
Diseño del interfaz de usuario

// Definimos el evento callback onCreate de la Actividad

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

// Usamos la clase Toast que muestra durante 1 segundo un

// mensaje pequeño al usuario

Toast.makeText(this, "Se ejecuta el método onCreate", 1).show();

// Definimos el evento callback onPause de la Actividad

@Override

protected void onPause() {

super.onPause();

// Aquí deberíamos guardar la información para la siguiente sesión

Toast.makeText(this, "Se ejecuta el método onPause", 1).show();

// Definimos el evento callback onRestart de la Actividad

@Override 

protected void onRestart() {

super.onRestart();

Toast.makeText(this, "Se ejecuta el método onRestart", 1).show();

// Definimos el evento callback onResume de la Actividad

@Override

protected void onResume() {

super.onResume();

Toast.makeText(this, "Se ejecuta el método onResume", 1).show();

// Definimos el evento callback onStart de la Actividad

@Override

protected void onStart() {

super.onStart();
95
  // Aquí deberíamos leer los datos de la última sesión para seguir la

// aplicación donde la dejó el usuario

Toast.makeText(this, "Se ejecuta el método onStart", 1).show();

// Definimos el evento callback onDestroy de la Actividad

@Override

protected void onDestroy() {

super.onDestroy();

Toast.makeText(this, "Se ejecuta el método onDestroy", 1).show();

// Definimos el evento callback onStop de la Actividad

@Override

protected void onStop() {

super.onStop();

Toast.makeText(this, "Se ejecuta el método onStop", 1).show();

IMPORTANTE: la implementación de estos métodos siempre debe incluir la llamada al


método de la clase superior (superclase) antes de ejecutar cualquier otra sentencia, de la
forma siguiente: super.onStop();.

A continuación, vamos a ver el ciclo de vida de una Actividad siguiendo sus estados y
los métodos callback que desencadena cada uno de ellos:

 El ciclo de vida de una Actividad ocurre entre las llamadas a los métodos onCreate()
y OnDestroy(). En el primer método la Actividad debe realizar la reserva de memoria,
el diseño de la interfaz de usuario y recuperar el estado de la sesión anterior. En el
segundo método, hay que liberar todos los recursos usados con anterioridad.

 El ciclo de vida "visible" de una Actividad ocurre entre las llamadas a los métodos
OnStart() y OnStop(). Durante este tiempo el usuario puede ver e interactuar con la
pantalla de la Actividad. El sistema invoca el método OnStop()cuando se inicia una
nueva actividad y la actual ya no está visible al usuario. Entre estos dos métodos, el
programador debe definir y destruir respectivamente los recursos necesarios para
mostrar la Actividad para el usuario.

96
Diseño del interfaz de usuario

 El ciclo de vida "en ejecución" de una Actividad sucede entre las llamadas a los
métodos OnResume() y OnPause(). Durante este tiempo la Actividad se ejecuta en
primer plano y tiene el foco del usuario. A menudo, el sistema operativo invoca estos
métodos, por ejemplo, cuando el teléfono se queda en modo espera o cuando
aparece una ventana con un mensaje de la aplicación. Por esto, es conveniente incluir
en estos métodos pocas y sencillas sentencias para que la aplicación no se pare
cuando el usuario intenta hacer operaciones con ella.

A continuación, se muestra un esquema visual de los cambios de estado posibles


dentro del ciclo de vida de una Actividad. Los rectángulos representan los métodos callback
que el sistema operativo puede invocar en las transiciones entre los estados de la Actividad.

97
Resumen de los métodos callback del ciclo de vida de una Actividad

Siguiente
Método Descripción Kill
método

Se invoca cuando la Actividad se crea por primera


vez. Aquí es donde se reserva la memoria
onCreate() No onStart()
necesaria, se crea la interfaz de usuario, se
recupera el estado de la sesión anterior, etcétera.

Se invoca cuando la Actividad está parada, justo


onRestart() No onStart()
antes de iniciarse de nuevo.

Se invoca justo antes de que el usuario pueda ver


la Actividad.
onResume()
onStart() A continuación, se puede invocar el método No o
onStop()
onResume() si la Actividad vuelve al primer plano
o onStop() si se oculta.

onResume() Se invoca justo antes de que el usuario comience a No onPause()


interactuar con la Actividad.

Se invoca cuando el sistema está a punto de


comenzar otra Actividad. Es recomendable usar
este método para confirmar con el usuario si quiere
guardar los cambios, desactivar animaciones y
cualquier código que consuma CPU, etcétera. onResume()
onPause() Estas sentencias deben ser muy rápidas porque la Sí o
nueva Actividad no se inicia hasta que finaliza este onStop()
método.

A continuación, se puede invocar el método


onResume() si la Actividad vuelve al primer plano
o onStop() si se oculta.

98
Diseño del interfaz de usuario

Siguiente
Método Descripción Kill
método

Se invoca cuando la actividad ya no es visible al


usuario. Esto puede ocurrir porque se vaya a
destruir la Actividad o porque otra Actividad
(existente o nueva) se ha reanudado y vuelve al onRestart()
onStop() primer plano. Sí o
onDestroy()
A continuación, se puede invocar el método
onRestart() si la Actividad vuelve al primer plano o
onDestroy() si se destruye.

Se invoca antes de que se destruya una Actividad;


se trata, pues, del último método.

onDestroy() El sistema operativo puede invocar este método Sí ninguno


porque el usuario decide finalizar la aplicación
(método finish())o porque es necesaria memoria
libre. Se puede distinguir entre estos dos
escenarios con el método isFinishing().

La columna "Kill" de esta tabla indica si el sistema puede matar (kill) el proceso de la
Actividad cuando finaliza la ejecución del método sin ejecutar ninguna sentencia más de la
Actividad. Hay tres métodos así: onPause(), onStop() y onDestroy().

onPause() es el método que se ejecuta siempre en caso de que el sistema operativo


mate una Actividad. Sin embargo, no es posible asegurar que el sistema invoque los métodos
OnStop() y OnDestroy() porque se haya ejecutado onPause() y la Actividad haya acabado.
Por lo tanto, se debe utilizar el método onPause() para guardar los datos importantes y
persistentes de la Actividad. No obstante, hay que ser selectivo sobre qué información debe
guardarse durante onPause(), ya que este método bloquea el inicio de una nueva Actividad y
el usuario podría notar que el teléfono se enlentece.

Los métodos que se han marcado con "No" en la columna "Kill" están, a priori,
protegidos. El sistema operativo sólo los "mata" en una situación de inestabilidad con falta de
memoria.

En la Unidad 3 veremos cómo usar esos eventos para guardar el estado de una
Actividad y poder recuperarlo cuando ésta se reinicia.

Desde Eclipse puedes abrir el proyecto Ejemplo 1 (Actividades) de la Unidad 2. Estudia el

99
código fuente y ejecútalo para mostrar en el emulador una aplicación en la que hemos
utilizado los métodos del ciclo de vida de una Actividad.

2.2 EVENTOS Y LISTENERS

2.2.1 Gestionando los eventos del usuario

Hasta ahora, en el curso, hemos creado elementos de la interfaz de usuario utilizando


los componentes disponibles en Android. En este apartado vamos a explicar cómo se gestiona
la interacción del usuario sobre la interfaz de la aplicación.

Como muchos otros entornos de desarrollo, Android está basado en Controladores


de Eventos (Event Handlers). Es decir, se ejecuta un determinado código en respuesta a
algún evento que ocurra. Normalmente, estos eventos se activan cuando el usuario interactúa
con la interfaz de la aplicación.

En Android el programador puede capturar los eventos específicos del objeto Vista
(View) con la que el usuario interactúa y ejecutar sentencias.

Por ejemplo, cuando el usuario toca con el dedo una Vista (por ejemplo, un botón), el
sistema operativo invoca el método onTouchEvent() de ese objeto. Para interceptar este
método deberíamos extender (crear una nueva clase heredada del botón) la clase y reemplazar
el código del método de la clase original. Sin embargo, extender cada objeto Vista para
gestionar un evento no es práctico. Por esta razón, Android dispone en todas las clases View
de una colección de interfaces con funciones callback que se pueden utilizar con mucha más
facilidad. Estas interfaces, que se denominan “Escuchadores de eventos” (Event listeners),
permiten controlar la interacción del usuario con la interfaz de usuario.

Esto no quiere decir que no podamos extender una clase Vista para crear una clase
nueva que herede el comportamiento de la clase anterior y redefinir los eventos de la misma
directamente.

2.2.2 Uso de los Event Listeners

Un Event Listener es una interfaz de la clase Vista (View) que contiene un único
método de tipo callback. Android invoca estos métodos cuando la Vista detecta que el usuario
está provocando un tipo concreto de interacción con este elemento de la interfaz de usuario.

Existen los siguientes métodos callback:

 onClick(): de View.OnClickListener. Este método se invoca cuando el usuario toca


un elemento con un dedo (modo contacto), hace clic con la bola de navegación
(TrackBall) del dispositivo o presiona la tecla "Intro" estando en un objeto.
100
Diseño del interfaz de usuario

 onLongClick(): de View.OnLongClickListener. Este método se invoca cuando el


usuario toca y mantiene el dedo sobre un elemento (modo de contacto), hace clic sin
soltar con la bola de navegación (TrackBall) del dispositivo o presiona la tecla "Intro"
durante un segundo estando en un elemento.

 onFocusChange(): de View.OnFocusChangeListener. Se invoca cuando el usuario


mueve el cursor hacia una Vista o se aleja de ésta utilizando la bola de navegación
(Trackball) o usando las teclas de navegación.

 onKey(): de View.OnKeyListener. Se invoca cuando el usuario se centra en un


elemento y presiona o libera una tecla del dispositivo.

 onTouch(): de View.OnTouchListener. Se invoca cuando el usuario realiza una


acción de tipo contacto con el dedo como presionar o soltar o cualquier gesto de
movimiento en la pantalla dentro de la Vista.

 onCreateContextMenu(): de View.OnCreateContextMenuListener. Se invoca


cuando se crea un menú contextual como resultado de una "pulsación larga" sobre
un elemento. En la Unidad 3 veremos cómo se usa este tipo de menú.

El siguiente ejemplo muestra cómo especificar los métodos de los eventos sobre un
EditText:

// Definimos el evento Change del EditText

texto.addTextChangedListener(new TextWatcher() {

  // Método que se lanza antes de cambiar el texto

  public void beforeTextChanged(CharSequence s, int start, int count,

int after) {

resultado1.setText("Texto antes de cambiar: "+s.toString());

  // Método que se lanza cuando el texto cambia

  public void onTextChanged(CharSequence s, int start, int before,

int count) {

resultado2.setText("Texto cambiado: "+s.toString());

  // Método que se lanza cuando el texto cambia. La diferencia con el

// método anterior es que la variable s es modificable

  public void afterTextChanged(Editable s) { 

    // En este evento no hacemos nada 

101
  } 

}); // end onChange EditText

En el código anterior fíjate de qué manera se define el Listener de los cambios del
texto de una Vista de tipo EditText. Dentro de este Listener establecemos los métodos que
vamos a gestionar (escuchar): beforeTextChanged, onTextChanged y afterTextChanged.

También es posible definir un método común en toda la Actividad y asignarlo a las


Vistas en el fichero de diseño de la interfaz del usuario res\layout\main.xml así:

<!-- En el botón definimos el método miClickHandler para indicar mediante el

diseño qué método debe invocar Android cuando el usuario haga clic sobre

él -->

<Button android:id="@+id/boton" android:layout_height="wrap_content"

android:text="@string/calcular" android:onClick="miClickHandler"
android:layout_gravity="center_horizontal"
android:layout_width="104dp">

</Button>

A continuación, se muestra la implementación del método anterior:

// Método onClick que invoca el botón "Calcular"

// Este método se define a nivel de Actividad y es común a todas sus vistas

public void miClickHandler(View view) {

  // Debemos ver la Vista (botón) que ha invocado el método 

  switch (view.getId()) { 

  case R.id.boton: // Si se trata del botón "Calcular"

// Vemos qué tipo de cálculo debemos hacer

  RadioButton kilometrosButton = (RadioButton)


findViewById(R.id.radio0); 

   // Si no se ha escrito nada mostramos un mensaje de error 

     if (texto.getText().length() == 0) {

Toast.makeText(this, "Por favor, introduce un número",


Toast.LENGTH_LONG).show();

  return;

102
Diseño del interfaz de usuario

  // Definimos el formato del número resultante

// Es importante tener bien configurado el AVD para que el


// separador decimal sea la coma ","

     DecimalFormat formatoNumero = new DecimalFormat("0.00");

     // Obtenemos el nº en formato float 

     float inputValue = Float.parseFloat(texto.getText().toString()); 

     // Convertimos a la unidad correspondiente

     if (kilometrosButton.isChecked()) {

distancia.setText("Kms son " +


formatoNumero.format(inputValue*0.6214) + " Millas");

} else {

distancia.setText("Millas son " +


formatoNumero.format(inputValue*1.6093) + " Kms");

break;

  } // end switch 

} // end miClickHandler

Fíjate en que la función callback miClickHandler() no devuelve ningún resultado


(void); sin embargo, otros métodos deben devolver un resultado lógico (boolean) para finalizar
su ejecución. A continuación vemos en qué consiste cada evento:

 onLongClick(): este método devuelve “true” para indicar que se han llevado a cabo
las operaciones necesarias para manejar el evento clic, por lo que ya no debe
lanzarse cualquier otro método de tipo “clic”. En caso contrario, si el método
devuelve el valor “false”, Android puede invocar a continuación otro método diferente
de tipo “clic”.

 onKey(): este método devuelve “true” o "false" para avisar si se han llevado a cabo
las operaciones necesarias para manejar el evento de teclado, por lo que ya no debe
lanzarse cualquier otro método de tipo “teclado”.

 onTouch(): en este método ocurre como en los dos casos anteriores, según devuelva
"true" o "false" para señalar si Android debe invocar los siguientes métodos.

Recuerda que los eventos de tipo teclado siempre afectan a la Vista que está activa en
ese momento.

103
Desde Eclipse puedes abrir el proyecto Ejemplo 2 (Eventos) de la Unidad 2. Estudia el
código fuente y ejecútalo para mostrar en el emulador una aplicación en la que hemos
utilizado varios métodos de eventos de Android.

Si ejecutas el ejemplo, verás que la aplicación tiene el aspecto siguiente:

2.2.3 Gestores de Eventos (Event Handlers)

En otro apartado de esta Unidad vamos a explicar cómo crear un componente (Vista)
personalizado. En este caso es posible redefinir varios métodos de tipo callback utilizados
como controladores (handlers) de eventos. A continuación vamos a hacer un resumen de los
métodos que podemos redefinir en una clase heredada de una Vista de Android:

 onKeyDown(int, KeyEvent): se invoca cuando el usuario usa el teclado del


dispositivo.

 onKeyUp(int, KeyEvent): se invoca cuando el usuario suelta una tecla.

 onTrackballEvent(MotionEvent): se invoca cuando el usuario utiliza la bola


de navegación (trackball) del dispositivo.

104
Diseño del interfaz de usuario

 onTouchEvent(MotionEvent): se invoca cuando el usuario toca con el dedo


sobre la pantalla del dispositivo.

 onFocusChanged(boolean, int, Rect): se invoca cuando una Vista recibe o


pierde el foco del usuario.

Hay otros métodos que, aunque no forman parte de la clase Vista, debes conocer, ya
que permiten gestionar eventos de otros componentes de Android. Son los siguientes:

 Activity.dispatchTouchEvent(MotionEvent): permite que podamos gestionar


el evento que se invoca cuando el usuario toca la pantalla a nivel de Actividad
antes de que llegue a alguna Vista de la interfaz de usuario.

 ViewGroup.onInterceptTouchEvent(MotionEvent): permite a la Vista


ViewGroup (es un conjunto de Layout) gestionar eventos antes de que se
remitan a los hijos que contiene.

 ViewParent.requestDisallowInterceptTouchEvent(boolean): podemos usar


este método para indicar que la Vista no debe interceptar los eventos de tipo
"toque" de pantalla.

2.2.4 Modo táctil de pantalla

Cuando un usuario interacciona con la interfaz de una aplicación Android usando las
teclas del dispositivo o una bola de navegación (trackball) es necesario marcar las Vistas como
activas (tienen el foco sobre ellas) coloreándolas, para que el usuario sepa que puede
utilizarlas. Sin embargo, si el dispositivo tiene una pantalla táctil, el usuario puede interactuar
con la interfaz utilizando su propios dedos. En este último caso, ya no es necesario resaltar las
Vistas. Este modo de interacción se llama "modo táctil".

Cuando un usuario toca la pantalla táctil, el dispositivo cambia a modo táctil, de


manera que sólo las Vistas en las que el método isFocusableInTouchMode() devuelve "true"
pueden recibir el foco, como ocurre, por ejemplo, en los EditText. Otras Vistas, como los
botones, no pueden recibir el foco de la aplicación, sino simplemente inician el método clic
cuando se presiona sobre ellos.

Cada vez que un usuario pulsa una tecla o desplaza la bola de navegación (trackball),
el dispositivo sale del modo táctil y busca una Vista donde activar de nuevo el foco de la
aplicación. Así el usuario puede volver a interactuar con la interfaz de usuario sin tocar la
pantalla.

El estado de modo táctil se mantiene a lo largo de todo el sistema operativo (todas las
ventanas y actividades). Para consultar el estado actual, se puede usar el método
isInTouchMode(). De esta forma se puede saber si el dispositivo se encuentra en modo táctil.

105
2.2.5 Controlando la Vista con el foco activo

El sistema Android cambia la Vista activa (con foco) en respuesta a la interacción del
usuario. Las Vistas indican la posibilidad de recibir el foco a través del método isFocusable().
Para establecer si una Vista puede recibir el foco, hay que utilizar el método setFocusable().
Como hemos dicho, en el modo táctil, podemos usar el método isFocusableInTouchMode() y
establecer si una Vista puede recibir el foco con setFocusableInTouchMode().

El cambio de foco automático que hace Android se basa en un algoritmo que busca la
Vista vecina más cercana en una dirección. Normalmente, este algoritmo establecido por
defecto no es el esperado por el usuario. Cuando esto ocurre, es posible definir explícitamente
en el archivo de diseño res\layout\main.xml cómo se debe cambiar el foco con los siguientes
atributos: nextFocusDown, nextFocusLeft, nextFocusRight y nextFocusUp indicando el id
de la Vista al que se debe saltar. Por ejemplo:

<LinearLayout
android:orientation="vertical"
... >
<Button android:id="@+id/arriba"
android:nextFocusUp="@+id/abajo"
... />
<Button android:id="@+id/abajo"
android:nextFocusDown="@+id/arriba"
... />
</LinearLayout>

Por lo general, en el diseño vertical anterior, intentar ir hacia arriba desde el primer
botón no cambia el foco de la aplicación. Lo mismo ocurre con el segundo botón al intentar ir
hacia abajo. Sin embargo, al haber definido en el botón superior el nextFocusUp para pasar al
segundo botón, el foco cambia al botón de abajo y viceversa.

Si queremos indicar que una Vista que por defecto no recibe el foco de la aplicación
pueda recibirlo, podemos usar el atributo XML de la Vista android:focusable al diseño la
interfaz de usuario. También podemos usar el atributo android:focusableInTouchMode
cuando se trate del modo táctil.

También desde el código Java podemos usar el método requestFocus() para indicar
al sistema que una Vista debe tener el foco.

2.3 USO DEL EMULADOR DE ANDROID

Como ya hemos visto, el entorno de desarrollo de Android incluye un emulador


virtual para poder ejecutar en un ordenador las aplicaciones que desarrollamos. El emulador
106
Diseño del interfaz de usuario

permite desarrollar y probar las aplicaciones de Android sin necesidad de disponer de un


dispositivo físico. Este Emulador tiene esta apariencia:

El emulador de Android imita todas las características de hardware y software de un


dispositivo móvil físico, exceptuando que no puede realizar llamadas a teléfono reales.
Dispone de teclas de navegación y control sobre las que se puede hacer clic con el ratón.
También muestra una pantalla en la que aparece la aplicación.

Para poder diseñar y probar aplicaciones con mayor facilidad, el emulador de Android
utiliza dispositivos virtuales (AVD: Android Virtual Device). Estos AVDs permiten establecer
algunos aspectos del hardware (memoria) y software (versión de Android) del teléfono virtual.

Como el emulador es un sistema operativo completo de Android, la aplicación que


desarrollamos puede utilizar los servicios, otras aplicaciones, acceder a la red, reproducir
audio y vídeo, almacenar y recuperar datos, hacer notificaciones, etcétera.

Además, el emulador incluye la capacidad de depuración y de simulación de


interrupciones de las aplicaciones (por ejemplo, cuando llega un mensaje SMS o una llamada
telefónica) para simular los efectos en estado de latencia.

En la Unidad 1 ya hemos visto cómo se inicia este emulador de Android; ahora vamos
a estudiar cómo se utiliza.
107
NOTA: es importante que el dispositivo virtual se encuentre configurado en el idioma Español
para que todos los ejemplos del curso funcionen correctamente. Se explica cómo hacerlo en
el apartado “Cómo crear un proyecto Android” de la Unidad 1.

2.3.1 Teclado del emulador

Se puede interactuar con el emulador como si se tratara de un dispositivo Android


real. Para usar el modo táctil de la pantalla hay que usar el puntero del ratón y para escribir
con las teclas del dispositivo hay que utilizar las teclas que aparecen a la derecha del
emulador.

Cuando accedemos por primera vez al emulador, aparece la pantalla de bienvenida del
terminal bloqueado:

Para desbloquear la pantalla hay que arrastrar con el ratón el icono "candado" hacia la
derecha.

La tabla siguiente resume las relaciones entre las teclas del emulador y las teclas del
teclado de tu ordenador:

Tecla del Emulador Tecla del ordenador Función

INICIO Se vuelve a la pantalla


Inicio

108
Diseño del interfaz de usuario

principal de Android.

Muestra un menú
desplegable con las
F2 o RePág
Menú (tecla izquierda) opciones de la pantalla
actual.

Vuelve a la pantalla anterior.


ESC
Volver

F3 Llamar por teléfono.


Llamar

Acabar una llamada.


F4
Colgar

Buscar información.
F5
Buscar

F7
Botón encender/apagar

BLOQUE_NUM_MAS (+),
Subir volumen Ctrl+5

BLOQUE_NUM_MENOS (-)
Bajar volumen Ctrl+F6

Cámara Ctrl+BLOQUE_NUM_5, Ctrl+F3 Inicia la cámara de fotos.

BLOQUE_NUM_7, Cambia la orientación del


Ctrl+F11 móvil de horizontal
Cambio orientación
BLOQUE_NUM_9, (landscape) a vertical
Ctrl+F12 (portrait).

Habilita y deshabilita la
conexión de datos del
F8 móvil.
Conexión datos

El emulador ocupa la
Pantalla completa Alt+Intro pantalla completa del
monitor del ordenador.

Inicia el modo de
F6 navegación con bola
Bola navegación
(trackball)

Entra momentáneamente en Mientras tengamos pulsada


Suprimir
modo bola navegación. la tecla Suprimir

109
Arrastrar izq/arriba/dcha/abajo BLOQUE_NUM_4/8/6/2

Ten en cuenta que para usar las teclas del bloque numérico es necesario desactivar el
bloqueo numérico en tu ordenador.

NOTA: es recomendable familiarizarse con el emulador de Android usando las funciones del
dispositivo virtual e ir visitando las diferentes opciones del mismo así como utilizar los atajos
de teclado de la tabla anterior.

 
2.3.2 Cómo introducir tildes con el Teclado del Emulador

Para que se puedan introducir tildes en las cajas de texto de las aplicaciones es muy
importante que el dispositivo virtual se encuentre configurado en el idioma Español. En el
apartado “Cómo crear un proyecto Android” de la Unidad 1 se explica cómo hacerlo.

Para que podamos introducir tildes en las cajas de texto, hay que pulsar un rato con el
ratón en la vocal correspondiente:

Pulsar un rato

También podemos usar de la misma forma el teclado externo del emulador:

110
Diseño del interfaz de usuario

Pulsar un rato

Atención: también es posible pulsar un rato en la tecla de la vocal del teclado del ordenador
donde estés trabajando para poder escribir la tilde correspondiente.

 
2.3.3 Limitaciones del Emulador

 No se pueden enviar ni recibir llamadas de teléfono reales, aunque se pueden simular.


 No se pueden usar conexiones por USB.
 No se puede utilizar la cámara de fotos/vídeo.
 No se pueden conectar unos auriculares al dispositivo virtual.
 No se puede conectar a una red de telefonía.
 No se puede establecer la carga de la batería.
 No se puede detectar cuándo se inserta o se quita la tarjeta de memoria SD.
 No se pueden establecer conexiones por Bluetooth.

2.3.4 Tamaño ventana emulador

Es posible cambiar el tamaño de la ventana de AVD del Emulador para que se vea
correctamente en la pantalla de tu ordenador. Para ellos hacemos clic en el menú de Eclipse
“Android SDK and AVD Manager”:

111
Después, hacemos clic en el botón “Start”:

En esta ventana podemos ver la escala (Scale) que se aplicará a la ventana del
emulador (un número entre 0,1 y 3). Para ello, podemos especificar la densidad del monitor del
ordenador en puntos por pulgada (Monitor DPI) y el tamaño en pulgadas de la pantalla del
dispositivo Android (Screen Size).

Para arrancar el emulador basta con hacer clic en el botón “Launch”.

2.3.5 Otras opciones del Emulador

Aunque el Emulador tiene muchas posibles opciones configurables para simular casi
todos los casos posibles de un dispositivo real, vamos a describir las que son más
importantes o usaremos más adelante en el curso:

 Geo Localización: la localización geográfica por GPS es un servicio muy útil


en los dispositivos de Android.

112
Diseño del interfaz de usuario

 Encendido / Apagado dispositivo: es posible simular el encendido, apagado


y bloqueo de la pantalla del teléfono. Incluso se puede simular el estado de la
batería del móvil: cargando/descargando.

 Llamada de teléfono: el emulador de Android incluye un módem virtual GSM


que permite simular las funciones de telefonía. Por ejemplo, se pueden simular
llamadas entrantes del teléfono y establecer conexiones de datos. Lo que no
podemos usar es el audio en esta versión del Emulador.

 Mensajes cortos SMS: se pueden crear mensajes SMS y dirigirlos al


dispositivo virtual de Android. Es decir, se simula la recepción de mensajes
cortos.

2.3.6 Cómo configurar las opciones del Emulador

En Eclipse se incluye una herramienta para depurar de aplicaciones Android que se


llama "Dalvik Debug Monitor Server" (DDMS). DDMS funciona tanto con un emulador de
Android como con un teléfono real conectado por cable USB. Esta herramienta también se
puede utilizar para configurar alguna de las opciones anteriores.

Para acceder a esta herramienta en Eclipse hay que hacer clic en la opción del menú
principal: Window -> Open Perspective -> DDMS.

Una vez abierta la perspectiva de DDMS, en la pestaña "Emulator Control", podemos


encontrar los campos necesarios para configurar estas opciones:

113
Nota: no todas las opciones posibles del Emulador están incluidas en la ventana anterior;
algunas deben configurarse mediante órdenes en la línea de comandos del sistema operativo
de tu ordenador.

 
En la Unidad 8 veremos cómo usar esta ventana para depurar aplicaciones Android.

2.4 COMPONENTES AVANZADOS

En la Unidad 1 ya hemos indicado que la interfaz de usuario en Android se construye


usando componentes o Vistas que están contenidas en paneles de diseño (layout). Los
componentes permiten interaccionar con el usuario con la aplicación. Los paneles ordenan la
posición de estos elementos en la interfaz del usuario.

Android dispone de diversos componentes que permiten al usuario seleccionar una


opción dentro de una lista de posibilidades, tales como listas desplegables (Spinner), listas
fijas (ListView), tablas (GridView). Además, existen otros componentes específicos de la
plataforma, como las galerías de imágenes (Gallery).

Este apartado está dedicado a los componentes de tipo selección y vamos a describir
un elemento importante y común a todos ellos: los adaptadores.

2.4.1 Qué son los Adaptadores de Android (adapters)

Un Adaptador (Adapter) es un objeto que permite definir el modelo de datos que usan
todos los componentes de selección de forma unificada. Es decir, todos los componentes de
selección acceden a los datos que contienen a través de un adaptador.

El Adaptador, además de suministrar datos a los componentes visuales, también es


responsable de generar las vistas específicas que se muestran dentro del componente de
selección. Por ejemplo, si cada opción de una lista estuviera formada por una imagen y varias
etiquetas, el Adaptador se encarga de generar el contenido de todas estas “opciones”
diseñadas.

Android dispone de varios tipos de adaptadores sencillos, aunque es posible extender


fácilmente mediante herencia su funcionalidad para mejorarlos según las necesidades de la
aplicación. Los más comunes son los siguientes:

 ArrayAdapter: es el más sencillo de todos los adaptadores. Suministra datos


a un componente de selección mediante una matriz de objetos de cualquier
tipo.

114
Diseño del interfaz de usuario

 SimpleAdapter: se usa para definir las opciones de un componente de


selección con los datos de un fichero XML.

 SimpleCursorAdapter: se utiliza para obtener las opciones de un componente


de selección de la respuesta de una consulta a una base de datos.

A continuación vemos cómo crear un Adaptador de tipo ArrayAdapter:

// Matriz con las opciones del Spinner 

final String[] datos = new String[]{"Opción 1","Opción 2","Opción 3",


"Opción 4","Opción 5"};

// Adaptador que usamos para indicar al Spinner dónde obtiene las opciones

ArrayAdapter<String> adaptador =
new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, datos);

 
 

Para crear el adaptador usamos tres parámetros:

1. El contexto se refiere normalmente a la actividad donde se crea el adaptador.

2. El ID de la Vista que va a mostrar la opción seleccionada por el usuario. En


este caso, usamos un ID de diseño predefinido por Android
(android.R.layout.simple_spinner_item) y formado por un componente
TextView. Podríamos haber escrito cualquier ID de un componente de la
interfaz de usuario del proyecto.

3. La matriz de los datos que definen las opciones de la lista.

De esta forma, ya hemos creado el adaptador para mostrar las opciones


seleccionables y sólo hay que asignarlo al componente de selección adecuado.

Lista desplegable (Spinner)

El componente Spinner permite al usuario seleccionar una única opción de un listado


desplegable. En el ejemplo siguiente definimos una Lista desplegable:

Ejemplo

<Spinner android:id="@+id/ListadoOpciones" 

         android:prompt="@string/selecciona" 

         android:drawSelectorOnTop="true" 

115
         android:layout_width="fill_parent" 

         android:layout_height="wrap_content" /> 

Como en otros componentes de Android, podemos utilizar otras propiedades como el


color de fondo (android:background), el estilo de la fuente (android:typeface), el color de
fuente (android:textcolor), el tamaño de la fuente (android:textSize), etcétera.

Para enlazar el adaptador que define las opciones de este tipo de listado y tratar el
evento que ocurre cuando el usuario selecciona una de las opciones disponibles, escribimos
las siguientes sentencias:

//Indicamos el tipo de Spinner dropdown

adaptador.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);

// Establecemos el adaptador en el Spinner

listadoOpciones.setAdapter(adaptador);

// Definimos el evento setOnItemSelected

listadoOpciones.setOnItemSelectedListener(
new AdapterView.OnItemSelectedListener() {

@Override

  // Si se selecciona una opción, mostramos un mensaje y


// actualizamos la etiqueta 

  public void onItemSelected(AdapterView<?> adapterView, View view,


int position, long id) {

Toast.makeText(getBaseContext(), datos[position], 1).show();

resultado.setText("Opción seleccionada: " + (position+1));

   // Si no se selecciona nada limpiamos la etiqueta. En este tipo de


// componentes siempre se selecciona una opción por lo que no se verá
// este evento. 

public void onNothingSelected(AdapterView<?> adapterView) {

resultado.setText("");

});

116
Diseño del interfaz de usuario

Para cambiar el aspecto de las opciones de la lista emergente hay que usar el método
setDropDownViewResource(ID_layout). En este caso hemos utilizado el diseño predefinido
de Android para las listas desplegables android.R.layout.simple_spinner_dropdown_item.
Esto provoca que el diseño de la selección y el listado desplegable sean diferentes (fíjate en el
cambio de color):

El evento de una Lista desplegable normalmente es onItemSelected, que se invoca


cuando el usuario selecciona una opción de la lista. El manejo del evento es similar a otros
componentes usando el método setOnItemSelectedListener(). En este evento definimos dos
métodos: el primero de ellos es onItemSelected y se invoca cada vez que cambia la
selección de la lista desplegable; y el segundo es onNothingSelected, que se invoca cuando
no hay ninguna opción seleccionada (esto ocurre si el adaptador no tiene datos).

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Lista desplegable) de la Unidad 2.


Estudia el código fuente y ejecútalo para mostrar en el emulador el resultado del programa
anterior

117
Lista de selección (List View)

La Lista de selección (o ListView en inglés) permite al usuario hacer clic sobre una lista
de opciones seleccionables. Estas opciones se muestran directamente sobre el propio
componente; por lo tanto, no se trata de una lista emergente como el Spinner. Si no se
visualizan todas las opciones disponibles en la pantalla del dispositivo porque no caben, se
puede desplazar el listado con el dedo o los botones de navegación.

En el ejemplo siguiente vamos a usar un Adaptador particularizado para que dibuje las
opciones del menú personalizadas.

Fíjate en el código del siguiente ejemplo que define un ListView en el fichero main.xml
de layout de la aplicación:

Ejemplo

<ListView android:id="@+id/ListaOpciones"

android:layout_width="wrap_content"

android:layout_height="wrap_content" />

Para enlazar el adaptador que define las opciones de este tipo de listado y tratar el
evento que ocurre cuando el usuario selecciona una de las opciones disponibles, escribimos
las siguientes sentencias:

// En este caso definimos la matriz de opciones usando la clase Opcion

private Opcion[] datos = new Opcion[32];

... 

// Definimos 32 Opciones en el ListView

for(int i=1; i<=32; i++)

datos[i-1] = new Opcion("Opción " + i, "Ésta es la opción " + i);

// Usamos un adaptador para dibujar las opciones de la lista

AdaptadorOpciones adaptador = new AdaptadorOpciones(this);

// Establecemos el adaptador del Listview

listaOpciones.setAdapter(adaptador);

118
Diseño del interfaz de usuario

// Definimos el evento setOnItemClick

listaOpciones.setOnItemClickListener(new AdapterView.OnItemClickListener() {

@Override

  // Si se hace clic sobre una opción, mostramos un mensaje 

public void onItemClick(AdapterView<?> adapterView, View view,


int position, long id) {

Toast.makeText(getBaseContext(), "Has hecho clic en '" +


datos[position].getTitulo()+"'", 1).show();

});

Fíjate que en este caso hemos utilizado un nuevo tipo de Adaptador:


AdaptadorOpciones, que personaliza el diseño de las opciones de la lista. Este Adaptador se
define con el siguiente código:

// Definimos el Adaptador que dibuja la opciones del ListView

class AdaptadorOpciones extends ArrayAdapter<Opcion> {

Activity contexto;

  // Contructor del adaptador usando el contexto de la aplicación actual 

  AdaptadorOpciones(Activity contexto) { 

    // Llamamos al constructor de la clase superior 

  super(contexto, R.layout.listitem_opcion, datos);

this.contexto = contexto;

// Método que dibuja la Vista de cada Opción

// Se invoca cada vez que haya que mostrar un elemento de la lista.

  public View getView(int position, View convertView, ViewGroup parent)

  // Vista que Android indica como reutilizable  

View item = convertView;

// Esta variable se usa para almacenar un objeto dentro

// de la Vista que dibuja la opción

119
VistaTag vistaTag;

// Si Android indica que no hay una Vista reutilizable para


//la opción, la definimos, inflamos el diseño que se define
// en el fichero listitem_opcion.xml y establecemos su contenido

If (item == null)

// Usamos un Inflater para inflar el diseño

// Ahora tenemos una Vista que se asocia al elemento

LayoutInflater inflater = contexto.getLayoutInflater();

// Definimos en la vista de vuelta el tipo de diseño

item = inflater.inflate(R.layout.listitem_opcion, null);

// Definimos el objeto que vamos a almacenar en el


//nuevo elemento 

vistaTag = new VistaTag();

// Obtenemos los punteros a las etiquetas recién infladas

vistaTag.titulo = (TextView)item.findViewById(R.id.LblTitulo);

vistaTag.subtitulo =
(TextView)item.findViewById(R.id.LblSubTitulo);

// Guardamos el objeto en el elemento

item.setTag(vistaTag);

else

// Si estamos reutilizando una Vista, recuperamos el


// objeto interno

vistaTag = (VistaTag)item.getTag();

// Cargamos las opciones de la matriz de datos

vistaTag.titulo.setText(datos[position].getTitulo());

vistaTag.subtitulo.setText(datos[position].getSubtitulo());

// Devolvemos la Vista (nueva o reutilizada) que dibuja


// la opción

  return(item); 

} // end class AdaptadorOpciones

120
Diseño del interfaz de usuario

El método más importante de un Adaptador es getView() ya que es el encargado de


mostrar los elementos de la lista.

Lo primero que debe hacer este método es “inflar” el diseño layout XML que hemos
definido en el fichero res\layout\listitem_opcion.xml. Para ello, hemos utilizando la clase
LayoutInflater, que crea la estructura de diseño de los objetos mediante el método
inflate(id_layout). Cada vez que es necesario mostrar un elemento de la lista en la pantalla,
Android invoca este método para diseñarlo, incluso cuando ya se ha mostrado el elemento y
se ha ocultado en la pantalla al desplazar la lista.

Esto produce que, dependiendo del tamaño de la lista y de la complejidad del layout,
se creen y destruyan muchos objetos aumentando el uso de la CPU y dela memoria y, al final,
provocando un mayor consumo de batería.

Sin embargo, Android permite reutilizar una Vista que ya hayamos “inflado” con
anterioridad y que ya no haga falta por algún motivo; por ejemplo, porque el elemento
correspondiente de la lista haya desaparecido de la pantalla al desplazar el listado. Así, se
crean únicamente los objetos necesarios que se pueden visualizar en la pantalla del teléfono.

Además, todos los componentes de Android tienen una propiedad denominada Tag,
que puede almacenar dentro cualquier tipo de objeto. Para asignar y recuperar el objeto
almacenado hay que emplear los métodos setTag() y getTag() respectivamente.

Esta facilidad de los objetos de Android permite que almacenemos dentro cada Vista
convertView las etiquetas que dibujan el diseño de la opción del listado. Para ello, vamos a
definir una nueva clase VistaTag donde almacenamos las etiquetas TextView que hemos
“inflado” para dicho elemento, de forma que, posteriormente, podamos recuperarlo
fácilmente y cambiar su contenido.

Por lo tanto, la clase VistaTag sólo contiene una referencia a cada uno de los
componentes del diseño que hay que manipular, en este caso, las dos etiquetas de texto.
Definimos esta clase de la siguiente forma:

// Clase que se usa para almacenar las 2 etiquetas de tipo TextView


// de una opción

static class VistaTag {

TextView titulo;

TextView subtitulo;

121
Desde Eclipse puedes abrir el proyecto Ejemplo 4 (Lista de selección) de la Unidad 2.
Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior,
en el que hemos utilizado el componente ListView.

Lista de rejilla (Grid View)

La Lista de rejilla (o GridView en inglés) permite mostrar al usuario un conjunto de


opciones seleccionables distribuidas como una tabla dividida en filas y columnas. Sus
propiedades más importantes son:

 android:numColumns: establece el número de columnas de la tabla.


También podemos escribir “auto_fit” si queremos que este número sea
calculado por el propio sistema operativo.

 android:columnWidth: establece el ancho de las columnas de la tabla.

 android:horizontalSpacing: establece el espacio horizontal entre celdas.

 android:verticalSpacing: establece el espacio vertical entre celdas.

 android:stretchMode: establece qué hacer con el espacio horizontal


sobrante. Si se fija el valor “columnWidth”, este espacio será ocupado a
partes iguales por las columnas de la tabla y si se fija el valor
“spacingWidth”, será ocupado a partes iguales por los espacios entre
celdas.

Fíjate en el código de ejemplo que define un GridView en el fichero main.xml de


layout de la aplicación:

Ejemplo

<GridView android:id="@+id/GridOpciones"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:numColumns="auto_fit"
android:columnWidth="80px"
android:horizontalSpacing="5px"
android:verticalSpacing="10px"
android:stretchMode="columnWidth" />

Como en los anteriores ejemplos, para enlazar el adaptador que define las opciones de
este tipo de listado escribimos las siguientes sentencias:

122
Diseño del interfaz de usuario

ArrayAdapter<String> adaptador =

new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, datos);

final GridView gridOpciones = (GridView)findViewById(R.id.GridOpciones);

gridOpciones.setAdapter(adaptador);

Además, también es posible gestionar el evento que ocurre cuando el usuario


selecciona una de las opciones del listado.

Cuadro de texto con sugerencias (AutoComplete TextView)

El Cuadro de texto con sugerencias (o AutoComplet TextView en inglés) permite al


usuario la introducción y edición de texto para que lo vea el usuario. Mientras el usuario
escribe texto, este componente propone posibles frases. Su propiedad más importante es
android:completionThreshold, que indica el número de caracteres mínimo que debe escribir
el usuario para que se propongan sugerencias.

Fíjate en el código de ejemplo que define un AutoCompleteTextView en el fichero


main.xml de layout de la aplicación:

Ejemplo

<AutoCompleteTextView android:id="@+id/miautocomplete"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:completionThreshold="1"

/>

Como en los anteriores ejemplos, para enlazar el adaptador que define las opciones de
este tipo de listado escribimos las siguientes sentencias:

123
// Debemos implemetar los método de TextWatcher para poder detectar los

// eventos de abajo

public class mesesActivity extends Activity implements TextWatcher {

private AutoCompleteTextView miAutoComplete;

@Override

public void onCreate(Bundle savedInstanceState) {

String meses[]={

"Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio",


"Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"};

...

miAutoComplete =
(AutoCompleteTextView)findViewById(R.id.miautocomplete);

ArrayAdapter<String> adaptador = new ArrayAdapter<String>(this,


android.R.layout.simple_dropdown_item_1line, meses);

miAutoComplete.addTextChangedListener(this);

miAutoComplete.setAdapter(adaptador);

@Override

public void beforeTextChanged(CharSequence s, int start, int count,

int after) {

   // Aquí escribimos el código que se ejecuta antes de que el texto cambie 

  } 

@Override

public void onTextChanged(CharSequence s, int start, int before, int count)


{

  // Aquí escribimos el código que se ejecuta cuando el texto ha


cambiado 

124
Diseño del interfaz de usuario

@Override

public void afterTextChanged(Editable s) {

  // Aquí escribimos el código que se ejecuta después de que el texto


cambie 

Además, dispone de las mismas propiedades y eventos que un componente EditText.

Actividad con lista de selección (List Activity)

Una Actividad con lista de selección (en inglés, ListActivity) es una actividad heredada
de la clase Activity que ya incluye el componente de selección ListView.

El componente ListActivity tiene un diseño predeterminado que consiste en una lista


de selección que ocupa toda la pantalla del dispositivo. Sin embargo, es posible personalizar
el diseño de esta lista usando la función setContentView() en el evento OnCreate() de la
Actividad. Para poder rediseñar esta lista de selección es obligatorio que, en el fichero
main.xml de layout de la aplicación, se defina el componente ListView con el id
@+id/android:list.

Opcionalmente, la pantalla de la aplicación puede contener otro objeto que se mostrará


cuando la lista de selección esté vacía. Este componente que se muestra cuando la lista está
vacía debe tener el id android:id/empty.

El código siguiente muestra un diseño personalizado poco agraciado de la pantalla.


Tiene una lista con un fondo verde, rojo y un escueto mensaje "sin datos".

Fíjate en el código de ejemplo que define este componente en el fichero main.xml de


layout de la aplicación:

Ejemplo

<ListView

android:id="@+id/android:list"

android:layout_width="wrap_content"

android:layout_height="wrap_content"/>

125
<TextView android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000"
android:text="No hay datos"/>

Para cambiar el diseño de las opciones del ListView interno de la actividad ListActivity
hay que definir un nuevo fichero XML de diseño dentro de la carpeta res\layout. Por ejemplo,
el fichero se puede llamar fila.xml y contener el siguiente código:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical">

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

android:textSize="16sp"

android:textStyle="bold"

android:layout_width="match_parent"

android:layout_height="wrap_content"/>

</LinearLayout>

El código anterior crea un diseño de una etiqueta en negrita para definir cada opción
del listado.

Para acabar, vamos a cargar datos en el objeto ListView de la actividad ListActivity


usando un Adaptador de tipo matriz en el fichero Java correspondiente de la aplicación:

public class MiListado extends ListActivity {

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

 
126
Diseño del interfaz de usuario

   

   // Creamos una matriz de datos que usaremos en la ListActivity 

String[] nombres = new String[] { "Firefox", "Internet Explorer",


"Opera", "Chrome", "Safari" };

     // Definimos el adaptador del ListView contenido en el ListActivity.


// Hay que indicar el diseño de la opción (R.layout.fila) y el campo
// que debemos rellenar (R.id.texto)

 
this.setListAdapter(new ArrayAdapter<String>(this, R.layout.fila,

R.id.texto, nombres)); 
}

En el ejemplo anterior podríamos haber utilizado un matriz de objetos en lugar de


cadena y extender el Adaptador para dibujar las opciones del listado con el método
getView(), tal y como hemos visto en el Ejemplo 4 de esta Unidad.

2.5 COMPONENTES PERSONALIZADOS

2.5.1 Diseño de componentes personalizados

Hasta ahora en el curso hemos estudiado los componentes básicos que proporciona
Android. Usando estos componentes podemos diseñar interfaces de usuario. A veces, la
funcionalidad de la aplicación requiere emplear componentes diseñados por el programador.

Como en muchos lenguajes de programación, Android permite crear componentes


personalizados de las siguientes maneras:

1. Extendiendo la funcionalidad de un componente ya existente.


2. Combinando varios componentes para formar otro compuesto.
3. Diseñando desde cero un nuevo componente.

A continuación, vamos a ver las formas 1 y 2 de crear componentes personalizados.

2.5.2 Cómo crear un componente extendido

A continuación, vamos a ver cómo podemos crear un nuevo componente usando un


componente básico ya existente de Android. A modo de ejemplo, vamos a extender la Vista

127
EditText (cuadro de texto) para que muestre el número de caracteres que contiene a medida
que se escribe en él.

Se trata de simular un editor de mensajes cortos SMS del propio sistema operativo que
nos avisa del número de caracteres que contiene el mensaje. En nuestro caso, como resultado
obtendremos un componente como el que se muestra en la siguiente imagen:

Podemos observar en la esquina superior derecha del cuadro de texto el número de


caracteres del mensaje de texto introducido, que se actualiza a medida que modificamos el
texto.

Lo primero que hay que hacer es crear una nueva clase Java que extienda el
componente que utilizamos de partida como base, en este caso EditText. El código de esta
nueva subclase es éste:

// El componente EditTextExtendido se extiende de EditText

public class EditTextExtendido extends EditText

private Paint pNegro;

private Paint pBlanco;

  // Hay que reescribiremos siempre los constructores heredados.  

  // En este caso son tres 

  public EditTextExtendido(Context context, AttributeSet attrs, int defStyle){

super(context, attrs, defStyle);

pinceles();

   public EditTextExtendido(Context context, AttributeSet attrs) {

super(context, attrs);

128
Diseño del interfaz de usuario

pinceles();

public EditTextExtendido(Context context) {

super(context);

pinceles();

   // Función que inicia las pinceles que usamos para pintar el cuadradito negro 

   private void pinceles()

pNegro = new Paint(Paint.ANTI_ALIAS_FLAG);

pNegro.setColor(Color.BLACK);

pNegro.setStyle(Style.FILL);

pBlanco = new Paint(Paint.ANTI_ALIAS_FLAG);

pBlanco.setColor(Color.WHITE);

   // Para modificar el aspecto del EditText hay que reescribir este método

   @Override

public void onDraw(Canvas canvas)

  //Invocamos al método de la superclase (EditText) 

  super.onDraw(canvas); 

  //Dibujamos el fondo negro del contador en la parte de arriba derecha 

  canvas.drawRect(this.getWidth()-30, 5, this.getWidth()-5, 20, pNegro);

  //Dibujamos el número de caracteres sobre el contador 

canvas.drawText("" + this.getText().toString().length(),
this.getWidth()-28, 17, pBlanco);

Es importante reescribir siempre los tres constructores heredados.

Para modificar el aspecto del componente incluyendo el contador de caracteres


tendremos hay que reescribir el método onDraw() que Android invoca cada vez que hay que
dibujar el componente en la pantalla del dispositivo. Este método tiene el parámetro Canvas

129
que es el “lienzo” sobre el que podemos dibujar todos los elementos extra necesarios en el
componente.

La clase Canvas proporciona varios métodos para dibujar líneas, rectángulos, elipses,
texto, imágenes, etcétera, sobre el espacio ocupado por el componente. En este caso
únicamente vamos a dibujar un rectángulo que sirve de fondo para el contador y el texto con
el número de caracteres actual que ha escrito el usuario.

Para dibujar el gráfico es necesario definir dos “pinceles” (clase Paint). El primero
permite pintar de color negro y con relleno sólido, y el segundo pinta de color blanco. Como
sólo es necesario crear estos pinceles una vez, los hemos definido como atributos de la clase
y los inicializamos en los tres constructores del componente.

Finalmente, dibujamos el fondo y el texto del contador mediante los métodos


drawRect() y drawText()del objeto Canvas usando posiciones relativas al espacio ocupado
por el componente.

Para añadir el nuevo componente a la interfaz de nuestra aplicación hay que incluirlo en
fichero res\layout\main.xml que define el diseño de la ventana como cualquier otro
componente, teniendo en cuenta que debemos hacer referencia a él con el nombre completo
de la nueva clase creada: es.mentor.unidad2.eje5.edittextext.EditTextExtendido. El fichero
tiene este aspecto:

<es.mentor.unidad2.eje5.edittextext.EditTextExtendido

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:layout_marginTop="3dip"

android:layout_weight="0.25"/>

Desde Eclipse puedes abrir el proyecto Ejemplo 5 (EditText Extendido) de la Unidad 2.


Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del programa
anterior, en el que hemos utilizado un componente extendido.

2.5.3 Cómo combinar varios componentes para crear uno compuesto

Android permite la creación de componentes compuestos a partir de varios


componentes estándar combinando la funcionalidad de todos ellos en un único componente
reutilizable en otras aplicaciones.

130
Diseño del interfaz de usuario

A modo de ejemplo, vamos a crear un componente de identificación (login) formado por


varios componentes estándar de Android. Además, en este componente compuesto hemos
definido un nuevo evento personalizado.

Lo primero que hemos hecho es diseñar la interfaz del componente compuesto a partir
de componentes estándar de Android: etiquetas, cuadros de texto y un botón. Para ello,
definimos un nuevo layout XML en la carpeta \res\layout con el nombre
“componente_login.xml“. En este fichero vamos a establecer la estructura típica de una
pantalla que muestra una ventana de login. El fichero es el siguiente:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"
android:layout_height="wrap_content"

android:orientation="vertical" android:padding="10dip">

<TextView android:id="@+id/TextView01" android:layout_width="wrap_content"


android:layout_height="wrap_content" android:text="Usuario:"
android:textStyle="bold" />

<EditText android:id="@+id/TextoUsuario"

android:layout_height="wrap_content"
android:layout_width="fill_parent" />

<TextView android:id="@+id/TextView02" android:layout_width="wrap_content"

android:layout_height="wrap_content" android:text="Contraseña:"

android:textStyle="bold" />

<EditText android:id="@+id/TextoPassword"

android:layout_height="wrap_content"
android:layout_width="fill_parent" />

<Button android:id="@+id/BotonAceptar" android:paddingLeft="20dip"

android:layout_gravity="center_horizontal"
android:layout_height="wrap_content"

android:text="Entrar" android:paddingRight="20dip"

android:layout_width="104dp"></Button>

<TextView android:id="@+id/LabelMensaje" android:layout_width="wrap_content"

android:layout_height="wrap_content" android:paddingLeft="10dip"

android:textStyle="bold" />

</LinearLayout>

131
A continuación, creamos la clase Java asociada a este componente compuesto donde
se define toda su funcionalidad. Como el diseño está basado en la clase LinearLayout, el
nuevo componente debe heredar también esta clase Java de Android. Redefiniremos además
los dos constructores básicos:

public class ComponenteLogin extends LinearLayout

  // Componentes que forman el componente compuesto 

  private EditText textoUsuario;

private EditText textoPassword;

private Button botonLogin;

private TextView labelMensaje;

  // Evento que se invoca cuando el usuario pulsa el botón Entrar 

  private OnLoginListener listener; 

  // Constructor por defecto 

public ComponenteLogin(Context context) {

super(context);

inicializar();

  // Constructor que define el texto del botón de acceso 

  public ComponenteLogin(Context context, AttributeSet attrs) {

super(context, attrs);

inicializar();

    // Procesamos los atributos XML personalizados 

TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.ComponenteLogin);

String textoBoton = a.getString(R.styleable.ComponenteLogin_texto_boton);

    // Escribimos el testo del botón 

    botonLogin.setText(textoBoton); 

    // Liberamos memoria 

a.recycle();

 
132
Diseño del interfaz de usuario

  // Este método se usa para dibujar (inflar) el componente compuesto

  private void inicializar()

    // Utilizamos el diseño layout 'componente_login' como interfaz del


// componente. Primero obtener el inflater

    String infService = Context.LAYOUT_INFLATER_SERVICE;

LayoutInflater li =
(LayoutInflater)getContext().getSystemService(infService);

    // Inflamos el componente compuesto definido en el XML 

    li.inflate(R.layout.componente_login, this, true); 

    // Obtenemos las referencias a los distintos componentes internos 

textoUsuario = (EditText)findViewById(R.id.TextoUsuario);

textoPassword = (EditText)findViewById(R.id.TextoPassword);

botonLogin = (Button)findViewById(R.id.BotonAceptar);

labelMensaje = (TextView)findViewById(R.id.LabelMensaje);

    //Asociamos los eventos necesarios

asignarEventos();

  // Establece el listener del componente compuesto 

public void setOnLoginListener(OnLoginListener l)

listener = l;

  // Define un nuevo evento para el componente compuesto 

  private void asignarEventos()

    // Cuando el usuario hace clic sobre el botón Entrar

// entonces se lanza el evento onLogin con los datos

// de las cajas de texto

botonLogin.setOnClickListener(new OnClickListener()

@Override

public void onClick(View v) {

133
listener.onLogin(textoUsuario.getText().toString(),
textoPassword.getText().toString());

});

  // Permite escribir la etiqueta con el resultado del Login 

  public void setMensaje(String msg)

labelMensaje.setText(msg);

Como se puede observar en las sentencias anteriores, el método inicializar() es el


encargado de "inflar" el diseño XML que hemos definido, de obtener las referencias a todos
los componentes internos y deasignar el evento necesario. Además, se incluye también un
método que permite modificar el texto de la etiqueta que muestra un mensaje con el resultado
del login.

Finalmente, hemos implementado un evento que permite responder cuando el usuario


de la aplicación hace clic en el botón "Entrar". Para concretar los detalles de dicho evento,
definimos una interfaz Java con su listener. Esta interfaz sólo tiene el método onLogin() que
devuelve los dos datos introducidos por el usuario (usuario y contraseña). La interfaz es muy
sencilla:

public interface OnLoginListener

void onLogin(String usuario, String password);

Una interfaz en Java es una colección de métodos abstractos y propiedades. En ella se


especifica qué se debe hacer, pero no su implementación. En la clase de la Actividad es
donde vamos a implementar esta interfaz que describe la lógica del comportamiento de los
métodos:

134
Diseño del interfaz de usuario

// Definimos lo que hace el evento onLogin  

compLogin.setOnLoginListener(new OnLoginListener()

@Override

public void onLogin(String usuario, String password)

    //Validamos el usuario y la contraseña 

    if (usuario.equals("admin") && password.equals("admin"))

compLogin.setMensaje("¡El usuario es correcto!");

else

compLogin.setMensaje("Error: el usuario o la contraseña no son


correctos.");

});

Ahora ya podemos definir el nuevo componente en una actividad como si se tratase de


cualquier otro componente predefinido por Android. En el fichero main.xml que define la
interfaz de usuario basta con hacer referencia a él utilizando la ruta completa del paquete
Java, que en este caso sería es.mentor.unidad2.eje6.logincompuesto:

<LinearLayout

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:sgo="http://schemas.android.com/apk/res/es.mentor.unidad2.eje6.logincomp
uesto"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:padding="10dip" >

<es.mentor.unidad2.eje6.logincompuesto.ComponenteLogin
android:id="@+id/ComponenteLogin"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:background="#0000AA"

135
sgo:texto_boton="Entrar" />

</LinearLayout>

Como el componente compuesto es heredado de un LinearLayout, podemos utilizar


cualquier atributo permitido en dicho componente; en este caso hemos establecido, por
ejemplo, los atributos layout_width, layout_height y background. Además, vamos a definir
también atributos xml exclusivos para este componente compuesto. Como ejemplo, definimos
el atributo texto_boton que permite establecer el texto del botón de la ventana desde el el
fichero anterior de diseño layout xml.

Para ello, hay que declarar un nuevo atributo y asociarlo al componente


ComponenteLogin. Esto se define en el fichero \res\values\attrs.xml, que incluye la etiqueta
<declare-styleable> asociada al ComponenteLogin donde indicaremos el nombre (name) y
el tipo (format) del nuevo atributo:

<resources>

<declare-styleable name="ComponenteLogin">

<attr name="boton_text" format="string"/>

</declare-styleable>

</resources>

Este atributo se usa en el segundo constructor de la clase ComponenteLogin mediante


el método obtainStyledAttributes() del contexto de la aplicación. Obtenemos el valor del
nuevo atributo definido (que es su ID formado por la concatenación del nombre del
componente y el nombre del atributo, en nuestro caso ComponenteLogin_texto_boton) y
modificaremos el texto por defecto del botón con el nuevo texto.

Desde Eclipse puedes abrir el proyecto Ejemplo 6 (Login compuesto) de la Unidad 2.


Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del programa
anterior, en el que hemos utilizado un componente extendido.

Si ejecutas este ejemplo verás que el aspecto que presenta es el siguiente:

136
Diseño del interfaz de usuario

Ten en cuenta que en el código de esta aplicación únicamente se da por bueno el


usuario y contraseña admin/admin.

137
 Una Actividad (Activity) es un componente de Android que ofrece una pantalla
con la que los usuarios pueden interactuar en una aplicación.

 Una aplicación de Android consta de múltiples actividades que están más o


menos ligadas entre sí.

 Una función de llamada (en inglés callback) es una función que se remite a Android
cuando se inicia una Actividad para que el sistema operativo la “llame” durante la
ejecución de esta Actividad.

 El ciclo de vida de una Actividad de Android está compuesto de tres estados:


Resumed, Paused y Stopped.

 Como muchos otros entornos de desarrollo, la interacción con el usuario en


Android está basada en Controladores de Eventos (Event Handlers).

 En Android el programador puede capturar los eventos específicos del objeto


Vista (View) con la que el usuario interactúa y ejecutar sentencias.

 Un Event Listener es una interfaz de la clase Vista (View) que contiene un único
método de tipo callback que Android invoca cuando detecta que el usuario está
provocando un tipo concreto de interacción con este elemento de la interfaz de
usuario.

 Es posible redefinir métodos de tipo callback utilizados, como controladores


(handlers) de eventos para ampliar las funcionalidades de las Vistas.

 El modo táctil permite al usuario interactuar con la interfaz utilizando su propios


dedos.

 El entorno de desarrollo de Android incluye un emulador virtual para poder


ejecutar en un ordenador las aplicaciones que desarrollamos sin necesidad de
disponer de un dispositivo físico.

 Un Adaptador (Adapter) es un objeto que permite definir el modelo de datos que


usan todos los componentes de selección de forma unificada.

138
Diseño del interfaz de usuario

 Android permite crear componentes personalizados de las siguientes maneras:

 Extendiendo la funcionalidad de un componente ya existente

 Combinando varios componentes para formar otro compuesto

 Diseñando desde cero un nuevo componente

139
140
Unidad de Aprendizaje 9

EMÁS
FECTOS DE TRANSICIÓN
INFORMACIÓN SOBREY
ANANDROID
IMACIÓN

ÍNDICE
3.1 INTRODUCCIÓN .......................................................... 143
3.1.1 Introducción ...................................................................143
3.1.2 Gestión del botón “Hacia atrás” de Android ............143
3.1.3 Definición de una tarea en los proyectos Android...145

3.2 GUARDAR Y RECUPERAR EL ESTADO DE UNA


ACTIVIDAD.................................................................... 147

3.3 PROCESOS EN HILOS EN ANDROID ........................ 154


3.3.1 Procesos ........................................................................154
3.3.2 Ciclo de vida de los procesos ....................................155
3.3.3 Hilos de ejecución en Android ....................................157

3.4 HILOS EN SEGUNDO PLANO..................................... 158


3.4.1 Utilización de tareas asíncronas con la clase
AsyncTask ..............................................................................159

3.5 MENÚS DE ANDROID ................................................. 162


3.5.1 Ejemplo de Menú Principal y Submenú ....................162
3.5.2 Ejemplo de Menú Contextual......................................167
3.5.3 Ventanas de diálogo en Android ................................172
3.5.3.1 Ventanas de diálogo con mensaje ....................174
3.5.3.2 Ventanas de diálogo con botones ....................174
3.5.3.3 Ventanas de diálogo con selección ..................176
3.5.3.4 Ventanas de diálogo personalizada..................177
Más información sobre Android

3.1 INTRODUCCIÓN

3.1.1 Introducción

En esta Unidad vamos a explicar cómo funciona la tecla “Retroceso” de los


dispositivos Android. Además, veremos cómo almacenar el estado de una Actividad de
Android.

También, detallaremos el uso de Hilos en Android.

Finalmente, usaremos Menús y Ventana de diálogo para mejorar la interfaz de


usuario con ejemplos de aplicaciones de Android.

3.1.2 Gestión del botón “Hacia atrás” de Android

Como ya hemos visto, una aplicación puede contener múltiples actividades. El


programador desarrolla una actividad para que el usuario realice un tipo específico de acción.
Además, una actividad puede arrancar otras actividades. Por ejemplo, una aplicación de
correo electrónico puede tener una Actividad que muestre una lista con los mensajes nuevos.
Así, cuando el usuario seleccione un correo electrónico, se puede abrir una nueva actividad
para mostrar su contenido.

Una Actividad también puede arrancar las actividades que definen otras
aplicaciones. Por ejemplo, si una aplicación tiene que enviar un correo electrónico, puede usar
el objeto “Intención” (en inglés, Intent) de Android para realizar el envío incluyendo alguna
información, como la dirección de correo electrónico del destinatario y un mensaje.

En la Unidad 5 veremos cómo se usan estas Intenciones ("Intents").

Para que se pueda hacer esto, hay que declarar la Actividad que envía mensajes para
que admita este tipo de “intenciones”. En este caso, la “intención” es enviar un correo
electrónico, por lo que la aplicación disponible para crear correos electrónicos se inicia (si
hubiera varias Actividades que tuvieran la misma “intención”, el sistema permite al usuario
seleccionar la que desea utilizar). Finalmente, cuando se envía el mensaje, la actividad inicial
se reanuda y “parece” que la Actividad de correo electrónico forma parte de la aplicación
general. A pesar de que las actividades pueden pertenecer a diferentes aplicaciones, Android
mantiene la sensación de que se trata de una única aplicación juntándolas en una sola Tarea
(Task).

Una Tarea es un conjunto de actividades con las que un usuario interactúa. Android
organiza las actividades en una pila de ejecución (en inglés stack) donde se van apilando las
actividades que el usuario va invocando. Cada Tarea tiene asociada su propia pila de
ejecución independiente.

La ventana siguiente muestra la pantalla principal del dispositivo (HOME):

143
Cuando el usuario toca un icono de esta pantalla, se inicia una tarea asociada a la
aplicación. Si es la primera vez que arrancamos la aplicación, el sistema operativo crea una
nueva tarea y su Actividad principal ("main") queda almacenada en primer lugar en la pila de
Android.

Cuando esta Actividad principal arranca otra, la nueva Actividad se añade a la parte
superior de la pila y pasa a primer plano. La Actividad anterior permanece en la pila y se
detiene manteniendo el estado actual de la interfaz de usuario. Si el usuario pulsa la tecla de

retroceso del teléfono, la Actividad actual se quita de la parte superior de la pila, se


destruye y se reanuda la Actividad anterior en el estado que estuviera. Las actividades en la
pila no se pueden reorganizar, se trata de una pila de tipo "último en entrar, primero en salir"
("last in, first out").

En el siguiente esquema visualizamos cómo cambia la pila de Android al ir abriendo


Actividades y al pulsar el botón "Volver Atrás":

144
Más información sobre Android

Si el usuario continúa presionando "Volver atrás", se extraerá una a una cada actividad
de la pila mostrando la anterior, hasta que aparezca la pantalla de inicio u otra aplicación que
se haya iniciado antes. Cuando esto ocurre, la tarea se destruye.

Una tarea es una unidad compuesta de actividades que puede pasar a un segundo
plano cuando los usuarios inician una nueva tarea o van a la pantalla de inicio. Cuando una
tarea se encuentra en segundo plano, todas sus actividades se detienen, aunque su pila de
ejecución se mantiene intacta hasta que el usuario decida que vuelva al "primer plano".

Android denomina a esta manera de guardar el estado de las aplicaciones Multitarea


(Multitasking), si bien no se trata de ejecución simultánea de las aplicaciones, sino de la
posibilidad de seguir en el estado donde la dejamos cuando ejecutemos otra aplicación y
volvamos a la inicial.

Nota: es posible mantener muchas tareas en segundo plano a la vez; sin embargo, si el
sistema operativo necesita memoria, puede destruirlas perdiendo sus estados de ejecución.

3.1.3 Definición de una tarea en los proyectos Android

Si abrimos el fichero AndroidManifest.xml del Ejemplo 1 de la Unidad 1, veremos que


contiene las siguientes etiquetas:

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

        <activity android:name=".BienvenidoActivity"

android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

Aquí podemos establecer una Actividad como punto de entrada de la tarea


escribiendo el "android.intent.action.MAIN" como la acción principal. En este caso, la
Actividad .BienvenidoActivity está definida como principal.

145
Nota: debido a la nomenclatura de Android, es obligatorio escribir un punto al principio del
nombre.

Aunque no es lo normal, es posible definir varios puntos de entrada en un mismo


proyecto.

Un punto de entrada hace que Android cree un icono y una etiqueta en la pantalla de
inicio para la actividad correspondiente, ofreciendo a los usuarios una forma de iniciar la
actividad.

La categoría de la Actividad android:name="android.intent.category.LAUNCHER es


muy importante, ya que permite a los usuarios volver a una tarea que está en segundo plano
desde el Lanzador de actividades. Para llegar a este Lanzador debemos hacer clic en el botón:

A continuación aparecen todas las aplicaciones instaladas:

También es posible ver las aplicaciones recientes pulsando un par de segundos el

botón "Inicio" para acceder a las ejecutadas anteriormente:

146
Más información sobre Android

Por esta razón, es muy importante marcar la actividad principal con la categoría
"android.intent.category.LAUNCHER", para que el usuario pueda volver a una aplicación
que se encuentre en segundo plano y que ya no es visible.

3.2 GUARDAR Y RECUPERAR EL ESTADO DE UNA ACTIVIDAD


La configuración de un teléfono puede cambiar mientras un usuario ejecuta una
aplicación. Por ejemplo, la orientación de la pantalla (vertical/horizontal), la disponibilidad de
teclado, el idioma, etcétera. Cuando este cambio se produce, Android reinicia la Actividad
usando el método OnDestroy() e inmediatamente seguido por onCreate(). Este
comportamiento de reinicio está diseñado para que la aplicación se adapte a la nueva
configuración de forma automática, por ejemplo, cuando cambia la posición de los
componentes.

La mejor manera de manejar un cambio de configuración para preservar el estado de


la aplicación es usar los métodos onSaveInstanceState() y onCreate().

En la Unidad 2 ya hemos estudiado lo que es una Actividad y su ciclo de vida.

En este apartado vamos a describir lo que hace Android cuando una Actividad se
destruye o se detiene, cómo se conserva su estado en la memoria del sistema operativo,
incluyendo todos los cambios que haya hecho el usuario en la interfaz introduciendo
información, seleccionando opciones, etcétera.

En el siguiente esquema se muestra los eventos y estados por los que pasa una
Actividad cuando se detiene o se destruye.

147
Como se puede ver en el esquema anterior, una Actividad recupera su estado de
ejecución si se detiene y vuelve a primer plano; o si se destruye y se vuelve a crear.

Para almacenar la información importante sobre el estado de ejecución de una


Actividad podemos usar el método de llamada (callback) de Android onSaveInstanceState() y
luego restaurar esta información cuando el sistema vuelva a crear la Actividad.

El sistema llama a este método justo antes de que la Actividad se destruya y le pasa
como parámetro un objeto de tipo Bundle. Este objeto Bundle es donde podemos almacenar
la información del estado de la Actividad como pareja nombre-valor, utilizando el método
putString(). De esta manera, si Android mata el proceso de la Actividad y el usuario vuelve a
ejecutar la misma Actividad, el sistema pasa el objeto Bundle almacenado anteriormente
como parámetro en el método onCreate(), para que pueda restaurar el estado de ejecución. Si
no hay información sobre este estado, el objeto Bundle es nulo.

Nota: no hay garantías de que Android invoque siempre el método onSaveInstanceState()


justo antes de que la Actividad se destruya, ya que hay casos en los que no es necesario
guardar el estado de ejecución; por ejemplo, cuando el usuario sale de la aplicación usando la
tecla “Volver atrás” , que indica que desea salir de la Actividad.

Sin embargo, incluso si no lo hacemos en el método onSaveInstanceState(), la


aplicación restaura por defecto automáticamente parte del estado de la Actividad. En

148
Más información sobre Android

concreto, la implementación por defecto de este método recorre cada componente de la


interfaz de usuario, guardando y restaurando automáticamente estos componentes. Por
ejemplo, se almacena automáticamente el texto contenido en un componente de tipo EditText
o la selección en un componente CheckBox. Lo único que debemos hacer para que Android
guarde el estado de los componentes es proporcionar un identificador único (atributo
android:id) para cada uno de ellos. Si un componente no tiene un identificador, entonces no
se guarda su estado.

También es posible indicar de forma explícita que no se almacene automáticamente el


estado de un componente de dos formas: en el diseño de la interfaz de usuario con el atributo
android:saveEnabled establecido a "false", o usando el método del componente
setSaveEnabled(). Por lo general, no se debe desactivar esta opción, salvo que debamos
proteger cierta información (contraseñas) o que al restaurar el estado de la interfaz de usuario
haya que mostrar la pantalla de manera diferente.

Aunque el método onSaveInstanceState() guarda la información útil del estado de la


interfaz de usuario, puede ser necesario guardar información adicional, como la orientación del
dispositivo (horizontal o vertical). Podemos usar este método y la variable Bundle para
almacenar esta información extra. Ten en cuenta que siempre debemos invocar el método de
la clase madre antes de guardar el resto de información:

 
super.onCreate(estadoAlmacenado); 

Muy importante: debido a que Android no garantiza que siempre se invoque el método
onSaveInstanceState(), debe usarse únicamente para almacenar el estado transitorio de la
Actividad, es decir, la interfaz de usuario. Nunca debe emplearse para almacenar datos
persistentes con preferencias del usuario o datos de la aplicación. En la Unidad 4 veremos
cómo se implementa esta funcionalidad.

A continuación, vamos a describir el Ejemplo 1 de esta Unidad para que queden más
claros los conceptos anteriores.

En el fichero res\layout\main.xml hemos definido la siguiente interfaz de usuario:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout android:id="@+id/mainLayout"

android:layout_width="fill_parent" android:layout_height="fill_parent"

android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">

149
<TextView android:id="@+id/nombre" android:layout_width="wrap_content"

android:layout_height="wrap_content" android:text="Introduce el usuario:"

android:layout_marginLeft="3dip" android:layout_marginTop="10dip">

</TextView>

<EditText android:id="@+id/nombreEditText"

android:layout_width="fill_parent" android:layout_height="wrap_content"

android:textSize="18sp" android:layout_marginLeft="3dip"

android:layout_marginRight="3dip" android:layout_marginTop="3dip">

</EditText>

<TextView android:id="@+id/password" android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="Introduce tu contraseña (no se guardará):"

android:layout_marginLeft="3dip" android:layout_marginTop="10dip">

</TextView>

<!-- En este EditText no definimos el ID. Así indicamos a Android que no

queremos que guarde el estado de este componente -->

<EditText android:id="@+id/passwordEditText"

android:layout_width="fill_parent" android:layout_height="wrap_content"

android:textSize="18sp" android:layout_marginLeft="3dip"

android:layout_marginRight="3dip" android:layout_marginTop="3dip"

android:saveEnabled="false">

</EditText>

</LinearLayout>

Se trata de una sencilla pantalla de autentificación de usuario. Fíjate que hemos


definido el atributo android:saveEnabled="false" para que no se almacene el campo
contraseña.

Después, hemos definido la Actividad así:

150
Más información sobre Android

public class GuardarestadoactividadActivity extends Activity {

@Override

protected void onCreate(Bundle estadoAlmacenado) {

// Recuperamos el estado de la Actividad

super.onCreate(estadoAlmacenado);

setContentView(R.layout.main);

if (estadoAlmacenado != null) {

// Recuperamos una variable que se almacena con el estado

Toast.makeText(this, "Evento onCreate(). Se recupera variable


almacenada: " +
estadoAlmacenado.getString("VARIABLE_ALMACENADA"),
Toast.LENGTH_LONG).show();

@Override

protected void onSaveInstanceState(Bundle estado) {

// Guardamos el estado de la Actividad (sus componentes)

super.onSaveInstanceState(estado);

// Añadimos una variable que se almacena con el estado

estado.putString("VARIABLE_ALMACENADA", "Texto guardado");

Toast.makeText(this, "El estado de la Actividad se ha guardado",

Toast.LENGTH_LONG).show();

@Override

protected void onDestroy() {

super.onDestroy();

Toast.makeText(this, "El sistema ha terminado la Actividad",


Toast.LENGTH_LONG).show();

En el código anterior hemos usado los métodos putString() y getString() del objeto
Bundle para almacenar el contenido de una variable extra en el estado de la Actividad.

151
Desde Eclipse puedes abrir el proyecto Ejemplo 1 (Guardar estado de una actividad) de la
Unidad 3. Estudia el código fuente y ejecútalo para mostrar en el emulador una aplicación en
la que guardamos el estado de ejecución de la Actividad

Para entender mejor la funcionalidad que almacena el estado de la Actividad, inicia


este ejemplo en el emulador. Verás la siguiente pantalla:

A continuación, introduce algún texto en los campos que aparecen. Después, haz clic

en el botón "INICIO" del emulador. Aparecerá el mensaje "El estado de la Actividad se ha


guardado" en la pantalla y desaparecerá la aplicación:

Si volvemos a ejecutar la aplicación, veremos que podemos ver tanto los datos tanto
de la caja de texto usuario como de la contraseña. Esto ocurre porque Android no ha
destruido la Actividad.

152
Más información sobre Android

Para que Android pare (kill) la Actividad cuando la dejamos, hay que configurar las
herramientas de desarrollo del dispositivo conocidas como "Dev-Tools". Para acceder a
estas herramientas hay que hacer clic en el icono "Dev Tools" que aparece en el lanzador de
aplicaciones. Después, hacemos clic en la opción "Development Settings" y, finalmente,
marcamos la opción "Immediately destroy activities":

Si ahora ejecutas de nuevo la aplicación y pulsas otra vez el botón "INICIO", verás el
mensaje "El sistema ha terminado la Actividad", que indica que la aplicación se destruye:

Si vuelves a abrir de nuevo la aplicación, observarás que únicamente se recupera el


campo nombre de usuario.

Importante: si haces clic en el botón ATRÁS del emulador, no se guarda el estado de la


actividad, ya que Android considera que el usuario desea finalizar la ejecución de la
aplicación. En este caso, todos los valores de los componentes se perderán.

153
3.3 PROCESOS EN HILOS EN ANDROID
Un hilo es una característica de la informática que permite a una aplicación realizar
varias tareas a la vez (concurrentemente). Esencialmente, un hilo es una tarea que se ejecuta
en paralelo con otra tarea. Todos los hilos que se ejecutan a la vez pueden compartir una
serie de recursos, tales como la memoria, los archivos abiertos, etcétera. Esta técnica permite
simplificar el diseño de una aplicación que puede así llevar a cabo distintas funciones
simultáneamente.

Cuando un usuario ejecuta una aplicación en Android se inicia un nuevo proceso de


Linux con un único hilo de ejecución. Por defecto, todos los componentes de una misma
aplicación se ejecutan en el mismo hilo principal (en inglés se denomina "main thread"). A
este hilo también se lo denomina hilo de la interfaz de usuario (en inglés, "UI thread").

Si se inicia una Actividad de una aplicación que ya se está ejecutando y, por lo tanto,
ya existe un proceso asociado, entonces la nueva Actividad se inicia dentro de ese proceso y
utiliza el mismo hilo de ejecución. Sin embargo, es posible diseñar aplicaciones que ejecuten
sus diferentes componentes de la aplicación en procesos separados y el programador puede
crear subprocesos adicionales para cualquier proceso principal.

En este apartado vamos a estudiar cómo funcionan los procesos e hilos en una
aplicación de Android.

3.3.1 Procesos

Ya hemos comentado que, por defecto, todos los componentes de una misma
aplicación se ejecutan en el mismo proceso. La mayoría de las aplicaciones deben ejecutarse
de esta manera. Sin embargo, a veces, es necesario controlar en qué proceso se ejecuta un
componente en particular. Para hacer esto, debemos modificar el archivo
AndroidManifest.xml del proyecto de Android.

En este fichero se definen con etiquetas los componentes de la aplicación:

 Actividad: etiqueta <activity>

 Servicio: etiqueta <service>

 Broadcast receiver: etiqueta <receiver>

 Content provider: etiqueta <provider>

Dentro de estas etiquetas se puede definir el atributo android:process que establece


el proceso que ejecuta ese componente. Se puede establecer este atributo de manera que
algunos componentes se ejecutan en su propio proceso y otros lo comparten. Incluso también
es posible configurar componentes de aplicaciones diferentes para que compartan el mismo

154
Más información sobre Android

proceso, siempre y cuando estas aplicaciones compartan el mismo ID de usuario de Linux y


estén firmadas con los mismos certificados del desarrollador.

Además, también podemos usar el atributo android:process dentro del elemento


<application> para definir el valor por defecto del proceso que aplica a todos sus
componentes.

Android podría matar un proceso en algún momento si queda poca memoria en el


teléfono y el usuario solicita que se ejecuten nuevos procesos al iniciar otra aplicación. Los
componentes de la aplicación del proceso destruido desaparecen y Android debe volver a
crear el proceso si la aplicación se reinicia.

Cuando el sistema Android necesita matar algún proceso para liberar memoria para
otro, tiene en cuenta su importancia en relación con el usuario. Por ejemplo, se termina antes
un proceso que alberga actividades que ya no son visibles en la pantalla que otro donde sus
actividades son visibles. Por lo tanto, la decisión de terminar un proceso depende del estado
de los componentes que se ejecutan en ese proceso. Las reglas que emplea Android para
acabar con un proceso se discuten a continuación.

3.3.2 Ciclo de vida de los procesos

El sistema operativo Android trata siempre de mantener el proceso de una


aplicación el mayor tiempo posible en memoria. Sin embargo, a veces, es necesario eliminar
procesos antiguos para liberar memoria e iniciar nuevos procesos. Para determinar qué
procesos se deben eliminar, el sistema ordena jerárquicamente por importancia cada proceso
basándose en qué componentes se están ejecutando dentro de él y su estado. Los procesos
de menor importancia se eliminan primero, después los de importancia media y así
sucesivamente.

Hay cinco niveles en la jerarquía de importancia. La siguiente lista muestra los


diferentes tipos de procesos por orden de importancia (el primer proceso es más importante y
se destruye el último):

1. Proceso en primer plano (Foreground): es un proceso en el que se ejecutan


componentes que está usando el usuario en ese momento. Por lo general, sólo hay
unos pocos procesos en primer plano en un momento dado. Sólo se eliminan
como último recurso, cuando la memoria del teléfono es tan baja que no puede
continuar con la aplicación actual y la pantalla deja de hacer caso al usuario.

2. Proceso visible: es un proceso que no tiene ningún componente en primer plano,


pero puede afectar a lo que el usuario ve en la pantalla. Esto puede ocurrir, por
ejemplo, si la actividad en primer plano abre una ventana de diálogo que permite
ver a la la actividad anterior detrás de ella.

155
3. Proceso de servicio: es un proceso que ejecuta un servicio que se ha iniciado con
el método startService() y no corresponde a ninguna de las dos categorías
anteriores. Aunque los procesos de servicio no están relacionados directamente
con lo que ve el usuario, por lo general, desempeñan tareas importantes para el
usuario, como reproducir música o descargar de datos de Internet.

4. Procesos en segundo plano (Background): es un proceso que mantiene una


actividad que no se muestra al usuario (ocurre cuando Android invoca el método
OnStop() de la actividad). Estos procesos no tienen un impacto directo sobre la
experiencia del usuario y el sistema puede eliminarlos en cualquier momento para
liberarmemoria.
Por lo general, hay muchos procesos ejecutándose en segundo plano, por lo que
se reordenan siguiendo el esquema "recientemente menos utilizado" (del inglés,
LRU: Least Recently Used) que asegura que se el último proceso utilizado por el
usuario se elimina al final.

5. Si una actividad utiliza los métodos de su ciclo de vida de forma correcta


guardando su estado actual y Android decide matarla, el usuario no notará
nada ya que Android recuperará su estado.

6. Proceso vacío: es un proceso que no tiene ningún componente de la aplicación


activa. La única razón para mantener este tipo de proceso en memoria es para
fines de precarga en la caché y mejorar el tiempo de arranque la próxima vez que
se ejecute.

Android asigna siempre a un proceso el nivel más alto en base a la importancia de sus
componentes internos. Por ejemplo, si un proceso contiene un servicio y una Actividad visible,
el proceso se clasifica como un proceso visible, no como un proceso de servicio.

Además, la clasificación de un proceso puede aumentar debido a que otros procesos


dependen de él. Por ejemplo, un proceso que da servicio a otro proceso no puede ser
clasificado nunca con un rango inferior al proceso al que da servicio.

Hemos visto que un proceso que ejecuta un Servicio se clasifica con mayor prioridad
que un proceso con una Actividad en segundo plano. Si una actividad inicia una operación que
dura mucho, es recomendable crear un servicio para esa operación, en lugar de crear un
subproceso, especialmente si la operación va a durar más que la propia actividad. Por
ejemplo, si una actividad tiene que subir una foto a un sitio Web, es mejor iniciar un servicio
para llevar a cabo esta tarea, así la carga pueda continuar en segundo plano incluso si el
usuario sale de la actividad. El uso de servicios garantiza de que la operación tendrá, por lo
menos, la prioridad de un proceso de servicio, independientemente de lo que ocurra con la
actividad.

Por esta misma razón, los receptores de mensajes (broadcast receivers) deben
emplear servicios en lugar de tareas con un hilo de ejecución.

156
Más información sobre Android

3.3.3 Hilos de ejecución en Android

Cuando el usuario ejecuta una aplicación, Android crea un hilo de ejecución que se
denomina principal (main). Este hilo es muy importante porque es el encargado de gestionar
los eventos que dispara el usuario a los componentes adecuados e incluye también los
eventos que dibujan la pantalla. Por esta razón, a este hilo principal también se le llama hilo de
la interfaz de usuario (UI thread).

Este modelo de ejecución de las aplicaciones se denomina Modelo de ejecución


de un único hilo (en inglés, Single Thread Model).

Android no crea un subproceso independiente para cada componente de la aplicación.


Todos sus componentes se ejecutan dentro del mismo proceso en el hilo principal. Por lo
tanto, los métodos que responden a eventos del sistema, como onKeyDown(), siempre se
ejecutan en este hilo principal o de la interfaz de usuario. Por ejemplo, cuando el usuario toca
con el dedo un botón de la pantalla del teléfono, el hilo principal invoca el evento
correspondiente en el widget apropiado y lo redibuja.

Si un usuario interacciona mucho con una aplicación, este modelo de ejecución con un
único hilo puede dar lugar a poca fluidez en ella a menos que implementemos adecuadamente
la aplicación. Es decir, como todo lo que ejecuta la aplicación se hace en un único hilo
principal, llevar a cabo operaciones que van a tardar cierto tiempo, como acceder a Internet o
consultar una base de datos, bloquearán la interfaz de usuario. Cuando el hilo principal está
bloqueado la pantalla de aplicación se bloquea (ni siquiera se dibuja) y, desde el punto de vista
del usuario, la aplicación se bloquea. Además, si el hilo principal está bloqueado durante 5
segundos, Android muestra al usuario una ventana con el mensaje "La aplicación no
responde" (en inglés, ANR: Application Not Responding):

Además, la interfaz de usuario de Android no es thread-safe. Una función o método


thread-safe puede ser invocado por múltiples hilos de ejecución sin preocuparnos de que los

157
datos a los que accede dicha función (o método) sean corrompidos por alguno de los hilos ya
que se asegura la atomicidad de la operación, es decir, la función se ejecuta de forma
serializada sin interrupciones

Es decir, en Android se puede manipular la interfaz de usuario desde otro subproceso


Por lo tanto, hay tener en cuenta dos reglas a la hora de diseñar aplicaciones en Android:

1. No bloquear nunca el hilo principal o de la interfaz de usuario.

2. No acceder a la interfaz de usuario de Android desde un hilo exterior.

3.4 HILOS EN SEGUNDO PLANO

Debido al Modelo de hilo único explicado anteriormente, es vital que no se bloquee el


hilo principal (o de interfaz de usuario) para que la interfaz de usuario de la aplicación responda
a la interacción del usuario. Si la aplicación tiene que realizar operaciones que no son
instantáneas, hay que ejecutarlas en hilos separados en segundo plano.

Por ejemplo, a continuación se muestra el código para que se descargue de Internet


una imagen cuando el usuario hace clic en un botón usando un hilo separado en segundo
plano:

public void onClick(View v) {


new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("http://pag.es/imagen.png");
miImageView.setImageBitmap(b);
}
}).start();
}

En este ejemplo hemos usado el estándar de hilos de Java, que se define en el


paquete "java.util.concurrent", para iniciar operaciones en segundo plano.

A primera vista, este código parece funcionar bien ya que crea un nuevo hilo en
segundo plano para descargar la imagen. Sin embargo, no cumple la segunda regla del
modelo de un hilo único: no acceder a la interfaz de usuario de Android desde un hilo
exterior a esta interfaz de usuario. Esto puede producir un comportamiento inesperado en la
aplicación y muy difícil de localizar.

Para solucionar este inconveniente, Android ofrece varias formas de acceder al hilo de
la interfaz de usuario desde otros hilos en segundo plano. A continuación se muestra la lista de
estos métodos:

 Activity.runOnUiThread(Runnable)
 View.post(Runnable)
 View.postDelayed(Runnable, long)

158
Más información sobre Android

Por ejemplo, para arreglar el ejemplo anterior podemos usar el método


View.post(Runnable):

public void onClick(View v) {


new Thread(new Runnable() {
public void run() {
final Bitmap b=loadImageFromNetwork("http://pag.es/imagen.png");
miImageView.post(new Runnable() {
public void run() {
miImageView.setImageBitmap(b);
}
});
}
}).start();
}

Ahora el código de la aplicación es correcto y la operación se descarga de la imagen


se realiza en un hilo en segundo plano, mientras que el componente de tipo ImageView se
manipula desde el hilo de la interfaz de usuario.

Sin embargo, si aumenta la complejidad de la operación que la aplicación debe


realizar, este tipo de codificación puede ser complicada y difícil de mantener por el
programador. Para gestionar interacciones más complejas con hilos en segundo plano, es
mejor usa un controlador (Handler) en este hilo de sendo plano para procesar los mensajes
que se envía al hilo principal de la interfaz de usuario.

La mejor solución es extender la clase AsyncTask que simplifica la ejecución de las


operaciones en segundo plano.

3.4.1 Utilización de tareas asíncronas con la clase AsyncTask

La clase AsyncTask permite realizar operaciones asíncronas en la interfaz de usuario.


Lleva a cabo las operaciones que bloquean la pantalla al usuario en un proceso de segundo
plano y devuelve su resultado al hilo principal de la interfaz de usuario de manera sencilla.

Para usar esta funcionalidad de Android hay que extender la clase AsyncTask e
implementar los siguientes métodos callback:

 doInBackground(): inicia los procesos en segundo plano.

 onPostExecute(): actualiza la interfaz de usuario desde el hilo principal de la


interfaz de usuario.

Así, para implementar el ejemplo anterior usando la clase AsyncTask escribimos:

159
// Método asociado al botón "Descargar" 

public void descargarImagen(View view) {

imagen.setVisibility(View.INVISIBLE);

cargando.setVisibility(View.VISIBLE);

  // Iniciamos la tarea de descarga 

  TareaDescargaImagen tarea = new TareaDescargaImagen();

tarea.execute(imagenURL);

// Clase que descarga una imagen de Internet como una tarea asíncrona.

// Es decir, podemos seguir usando la interfaz de usuario.

private class TareaDescargaImagen extends AsyncTask<String, Void, Bitmap> {

  // Método que se ejecuta en segundo plano 

protected Bitmap doInBackground(String... urls) {

return loadImageFromNetwork(urls[0]);

  // Cuando la tarea ha acabado se invoca automáticamente este método

protected void onPostExecute(Bitmap resultado) {

cargando.setVisibility(View.INVISIBLE);

  // Cargamos la imagen su se ha podido descargar la imagen de Internet

if (resultado!=null) imagen.setImageBitmap(resultado);

else imagen.setImageResource(R.drawable.error);

imagen.setVisibility(View.VISIBLE);

  } // end onPostExecute


}

Fíjate que para iniciar la tarea hemos usado el método execute().

Para definir la tarea asíncrona hemos extendido la clase así:

AsyncTask<String, Void, Bitmap>

El primer parámetro (String) define el tipo de variable que usamos como parámetro al
invocar doInBackground(). El segundo parámetro (Void) define el tipo de variable que
pasamos como parámetro al invocar onProgressUpdate(). Finalmente, el último parámetro
(Bitmap) define el tipo de variable que pasamos como parámetro al invocar onPostExecute()

160
Más información sobre Android

Aunque más adelante en el curso veremos cómo se indican los permisos que necesita
una aplicación para ejecutarse, para que este ejemplo funcione y se descargue una imagen de
Internet es necesario indicar el permiso en el fichero AndroidManifest.xml de la aplicación así:

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

Es recomendable que el alumno o alumna lea el manual de la clase AsyncTask para conocer
más funcionalidad, entre ella se encuentra:

 Es posible llamar al método publishProgress() dentro de doInBackground() para


invocar onProgressUpdate() y actualizar la pantalla del usuario para que vea cómo
progresa la operación.

 Es posible cancelar la operación en cualquier momento.

Desde Eclipse puedes abrirse el proyecto Ejemplo 2 (Hilos) de la Unidad 3. Estudia el


código fuente y ejecútalo para mostrar en el emulador una aplicación en la que usamos un
proceso en segundo plano

Para entender mejor esta aplicación que usa un proceso en segundo plano, inicia este
ejemplo en el emulador. Verás la pantalla siguiente:

161
Si pulsas en el botón "Descargar" comprobarás que puedes seguir usando el resto de
Vistas de la pantalla mientras se descarga la imagen.

Atención: Puede ocurrir un problema al utilizar un proceso en segundo plano cuando se


produce un reinicio inesperado de la Actividad debido a un cambio de configuración en
tiempo de ejecución, por ejemplo, cuando el usuario cambia la orientación de la pantalla. Esto
puede destruir el proceso en segundo plano y que la operación no finalice correctamente.

Además, es posible acceder a un hilo en segundo plano desde el proceso de otra aplicación,
para ello hay que desarrollar este hilo de manera thread-safe.

Estas funcionalidades forman parte de la programación avanzada en Android y no se describe


su uso en este curso.

3.5 MENÚS DE ANDROID

En informática un Menú contiene una serie de opciones que el usuario puede


elegir para realizar una determinada tarea.

En las aplicaciones de Android podemos utilizar tres tipos de menús diferentes:

 Menús Principales: son los usados con más frecuencia. Aparecen en la zona inferior

de la pantalla al pulsar el botón Menú del teléfono.

 Submenús: son los menús secundarios que aparecen al elegir una opción de un menú
principal.

 Menús Contextuales: son muy útiles y se muestran al realizar una pulsación larga
sobre algún elemento de la pantalla. Es el equivalente al botón derecho del ratón en un
PC.

Como es habitual en Android, existen dos formas de crear un menú en una aplicación
Android: definiendo el menú en un fichero XML e "inflándolo" después o creando el menú
directamente mediante código Java. En este apartado veremos ambas formas.

3.5.1 Ejemplo de Menú Principal y Submenú

Veamos en primer lugar cómo crear un menú principal con un submenú a partir de su
diseño en XML. Estos ficheros XML con el diseño del menú se deben guardar en la carpeta
res\menu del proyecto y tienen una estructura de este tipo (archivo menu_principal.xml):

162
Más información sobre Android

<?xml version="1.0" encoding="utf-8"?>

<menu

xmlns:android="http://schemas.android.com/apk/res/android">

<item android:id="@+id/MenuOp1" android:title="Opción 1"

android:icon="@drawable/menu_estrella"></item>

<item android:id="@+id/MenuOp2" android:title="Opción 2"

android:icon="@drawable/menu_brujula"></item>

<item android:id="@+id/MenuOp3" android:title="Opción 3"

android:icon="@drawable/menu_direccion">

<menu>

<item android:id="@+id/SubMenuOp1"

android:title="Opción 3.1" />

<item android:id="@+id/SubMenuOp2"

android:title="Opción 3.2" />

</menu>

</item>

</menu>

Podemos ver en el código anterior que la estructura básica del diseño del menú es
muy sencilla. Aparece un elemento principal <menu> que contiene los elementos <item> que
corresponden con las diferentes opciones del menú.

Estos elementos <item> tienen a su vez varias propiedades básicas, como su ID


(atributo android:id), su texto (atributo android:title) y su icono (atributo android:icon). Los
iconos utilizados deben guardarse en las carpetas res\drawable-... del proyecto.

Además, hemos definido un submenú (menú secundario) que se muestra al pulsar la


opción 3 del un menú principal. Los submenús en Android se muestran en una lista emergente
cuyo título contiene el texto de la opción elegida del menú principal. Este submenú tiene dos
nuevas opciones secundarias. Para ello, hemos añadido un nuevo elemento <menu> dentro
del <item> correspondiente a la opción 3.

De igual forma que otros archivos XML de un proyecto Android, podemos editarlo
visualmente haciendo clic en la pestaña “Layout” del archivo que define el menú:

163
Una vez definido el menú en el fichero XML, hay que implementar el método
onCreateOptionsMenu() de la Actividad para que se cree en la pantalla. En este método
debemos “inflar” el menú de forma parecida a como ya hemos hecho con otro tipo de
componentes layouts. Primero obtenemos una referencia al objeto "inflador" mediante el
método getMenuInflater() y, después, generamos la estructura del menú usando el método
inflate() y pasándole como parámetro el ID del archivo XML de diseño del menú. Finalmente,
el método debe devolver el valor true para indicar a la Actividad que debe mostrar el menú.

@Override

public boolean onCreateOptionsMenu(Menu menu) {

    //Forma 1: definimos el menú inflando el fichero XML con su diseño        


  MenuInflater inflater = getMenuInflater();

inflater.inflate(R.menu.menu_principal, menu);

return true;

A continuación, vamos a ver cómo implementar el diseño del menú programándolo


con sentencias Java.

Para ello, redefinimos el método onCreateOptionsMenu() añadiendo las opciones del


menú mediante el método add() del objeto Menu, que es un parámetro del primer método.
Este método add() se invoca con cuatro parámetros:

164
Más información sobre Android

 ID del grupo asociado a la opción: veremos qué es esto en el siguiente ejemplo con un
menú contextual, por lo que establecemos el valor Menu.NONE.

 ID único para la opción: declaramos unas constantes de la clase.

 Orden de la opción: como no queremos indicar ninguno, utilizamos Menu.NONE.

 Texto de la opción: texto que aparece en el menú.

El icono de cada opción lo establecemos mediante el método setIcon() pasándole el ID


del recurso.

Veamos cómo queda el código utilizando esta otra forma de implementarlo que genera
un menú exactamente igual al del ejemplo anterior:

private static final int MENU_OP1 = 1;

private static final int MENU_OP2 = 2;

private static final int MENU_OP3 = 3;

private static final int SMENU_OP1 = 31;

private static final int SMENU_OP2 = 32;

...

@Override

public boolean onCreateOptionsMenu(Menu menu) { 


// Forma 2: definimos el menú con sentencias Java 

menu.add(Menu.NONE, MENU_OP1, Menu.NONE, "Opción 1")


.setIcon(R.drawable.menu_estrella);

menu.add(Menu.NONE, MENU_OP1, Menu.NONE, "Opción 2")


.setIcon(R.drawable.menu_brujula);

SubMenu submenu = menu.addSubMenu(Menu.NONE, MENU_OP1, Menu.NONE,


"Opción 3").setIcon(R.drawable.menu_direccion);

submenu.add(Menu.NONE, SMENU_OP1, Menu.NONE, "Opción 3.1");

submenu.add(Menu.NONE, SMENU_OP2, Menu.NONE, "Opción 3.2");

return true;
}

Para añadir el submenú a la opción 3 utilizamos el método addSubMenu() en lugar de


add() y guardamos una referencia al submenú donde insertamos las dos nuevas opciones
utilizando una vez más el método add().

Una vez construido el menú, es necesario implementar las sentencias que se ejecutan
cuando el usuario selecciona una de las opciones, Para ello, usamos el evento

165
onOptionsItemSelected() de la Actividad. Este evento recibe como parámetro el elemento de
menú (MenuItem) que ha sido elegido por el usuario y cuyo ID podemos obtener con el
método getItemId(). En función de este ID podemos saber qué opción ha sido pulsada y
ejecutar unas sentencias u otras. En nuestro ejemplo, lo único que hacemos es modificar el
texto de la etiqueta labelResultado que hemos colocado en la pantalla principal de la
aplicación:

// Si el usuario selecciona una opción del menú mostramos la opción


// seleccionada en la etiqueta

public boolean onOptionsItemSelected(MenuItem item) {

switch (item.getItemId()) {

case R.id.MenuOp1:

labelResultado.setText("Has pulsado la opción 1");

return true;

case R.id.MenuOp2:

labelResultado.setText("Has pulsado la opción 2");

return true;

case R.id.MenuOp3:

labelResultado.setText("Has pulsado la opción 3");

return true;

case R.id.SubMenuOp1:

labelResultado.setText("Has pulsado la opción 3.1");

return true;

case R.id.SubMenuOp2:

labelResultado.setText("Has pulsado la opción 3.2");

return true;

default:

return super.onOptionsItemSelected(item);

} // end onOptionsItemSelected 

Desde Eclipse puedes abrir el proyecto Ejemplo 4 (Menús) de la Unidad 3. Estudia el código
fuente y ejecútalo para mostrar en el emulador una aplicación en la que usamos un menú
principal y un submenú,

166
Más información sobre Android

Para ver cómo funciona esta aplicación que usa un menú y un submenú inicia este ejemplo en
el emulador. Verás la pantalla siguiente:

3.5.2 Ejemplo de Menú Contextual

Los menús contextuales siempre están asociados a un componente en concreto de la


pantalla y se muestra cuando el usuario lo pulsa un rato. Normalmente, se suele mostrar
opciones específicas para el elemento pulsado. Por ejemplo, en un componente de tipo lista
podríamos tener un menú contextual que aparezca al pulsar sobre un elemento en concreto de
la lista y que permita editar su texto o eliminarlo de la lista.

La creación y utilización de este tipo de menús contextuales son muy parecidas a las
de los menús y submenús básicos que hemos visto anteriormente, aunque presentan algunas
particularidades que vamos a tratar a continuación.

Vamos a partir del ejemplo 4 de esta Unidad, al que vamos a añadir un menú
contextual que aparece al pulsar sobre la etiqueta de texto donde mostramos la opción
seleccionada y un ListView con elementos sobre los que pulsar seguidamente y mostrar
opciones de edición.

Lo primero que debemos hacer es indicar en el método onCreate() de la Actividad que


la etiqueta y el listado tienen asociado un menú contextual. Esto se hace usando la función
registerForContextMenu():

167
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

  //Obtenemos las referencias a los componentes 

  labelResultado = (TextView)findViewById(R.id.labelResultado);

listadoPrincipal = (ListView)findViewById(R.id.ListadoPrincipal);

  //Rellenamos la lista con datos de ejemplo 

  adaptador = new ArrayAdapter<String>(this,


android.R.layout.simple_list_item_1, datos);

listadoPrincipal.setAdapter(adaptador);

  //Asociamos los menús contextuales a los componentes 

  registerForContextMenu(labelResultado);

registerForContextMenu(listadoPrincipal); 
} // end onCreate 

A continuación, de igual forma que hicimos con los menús básicos para crear las
opciones disponibles con el método onCreateOptionsMenu(), vamos a construir los menús
contextuales asociados a los diferentes componentes de la aplicación con el método
onCreateContextMenu(). A diferencia del método onCreateOptionsMenu() Android invoca
este método cada vez que es necesario mostrar un menú contextual. Este método lo
implementaremos de misma forma que los menús básicos, inflándolo con un archivo de
diseño XML o creándolo con sentencias Java. En este ejemplo hemos decidido diseñar los
menús en XML. El menú contextual que aparece en la etiqueta se define en el fichero
menu_context_etiqueta.xml:

<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:android="http://schemas.android.com/apk/res/android">

<item android:id="@+id/ContextLabelOp1"
android:title="Opción 1 de etiqueta"></item>

<item android:id="@+id/ContextLabelOp2"
android:title="Opción 2 de etiqueta"></item>
</menu> 

168
Más información sobre Android

El menú contextual que aparece en el ListView se define en el fichero


menu_context_lista.xml:

<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:android="http://schemas.android.com/apk/res/android">

<item android:id="@+id/EditTextOp"
android:title="Editar texto opción"></item>

<item android:id="@+id/ReiniciaTextOp"

android:title="Reiniciar texto opción"></item>


</menu>

Para implementar el método onCreateContextMenu() hay que tener en cuenta que


definimos varios menús contextuales en la misma Actividad, por lo que hay que construir un
menú distinto dependiendo del componente asociado. Para hacerlo, obtenemos el ID del
componente al que se va a ir asociado el menú contextual, que se recibe en el parámetro
(View v) del método onCreateContextMenu() utilizando el método getId() de dicho
parámetro:

// Método donde definimos el menú contextual cuando se despliega

public void onCreateContextMenu(ContextMenu menu, View v,

ContextMenuInfo menuInfo)

super.onCreateContextMenu(menu, v, menuInfo);

//Inflador del menú contextual

MenuInflater inflater = getMenuInflater();

// Si el componente que vamos a dibujar es la etiqueta usamos

// el fichero XML correspondiente

if(v.getId() == R.id.labelResultado)

inflater.inflate(R.menu.menu_context_etiqueta, menu);

// Si el componente que vamos a dibujar es el ListView usamos

// el fichero XML correspondiente

else if(v.getId() == R.id.ListadoPrincipal)

AdapterView.AdapterContextMenuInfo info =

(AdapterView.AdapterContextMenuInfo)menuInfo;

169
// Definimos la cabecera del menú contextual

menu.setHeaderTitle(

listadoPrincipal.getAdapter().getItem(info.position).toString());

// Inflamos el menú contextual

inflater.inflate(R.menu.menu_context_lista, menu);

En el caso del menú contextual para el listado hemos personalizado el título del menú
contextual mediante el método setHeaderTitle(), para que muestre el texto del elemento
seleccionado en el listado.

Para hacer esto es necesario conocer la posición del elemento seleccionado en el


listado mediante el último parámetro menuInfo. Este parámetro contiene información adicional
del componente sobre el que el usuario ha pulsado para mostrar el menú contextual. En este
caso en particular del componente ListView contiene la posición del elemento pulsado. Para
obtenerlo, hacemos un cambio de formato (typecasting) del parámetro menuInfo a un objeto
del tipo AdapterContextMenuInfo y accedemos a su propiedad position.

Por último, para implementar las acciones que hay que ejecutar cuando el usuario
selecciona una opción determinada del menú contextual vamos a implementar el método
onContextItemSelected() de manera similar a cómo hacíamos con onOptionsItemSelected()
para los menús básicos:

// Si el usuario selecciona una opción del menú contextual mostramos

// la opción seleccionada en la etiqueta

public boolean onContextItemSelected(MenuItem item) {

AdapterContextMenuInfo info =

(AdapterContextMenuInfo) item.getMenuInfo();

switch (item.getItemId()) {

// Se selecciona la opción 1 de menú contextual de la etiqueta

case R.id.ContextLabelOp1:

labelResultado.setText("Etiqueta: Opción 1");

return true;

170
Más información sobre Android

// Se selecciona la opción 2 de menú contextual de la etiqueta

case R.id.ContextLabelOp2:

labelResultado.setText("Etiqueta: Opción 2");

return true;

// Se selecciona la opción "Editar texto opción" de menú contextual


// de la etiqueta

case R.id.EditTextOp:

labelResultado.setText("Opción " + info.position + " listado:


Cambio de texto");

// Cambiamos el contenido de la matriz de datos

datos[info.position]="Opción "+info.position+" listado


modificado";

// Reiniciamos el adaptador para que recargue los datos y


// actualice el ListBox

adaptador.notifyDataSetChanged();

return true;

// Se selecciona la opción "Reiniciar texto opción" de menú


// contextual de la etiqueta

case R.id.ReiniciaTextOp:

labelResultado.setText("Opción " + info.position + " listado:


Reinicio texto");

datos[info.position]="Opción "+info.position+" listado";

adaptador.notifyDataSetChanged();

return true;

default:

return super.onContextItemSelected(item);

Fíjate en el código anterior que se puede mantener pulsado el dedo sobre la etiqueta
azul o sobre una opción del listado y seleccionar una de las opciones del menú contextual.

En el código anterior también hemos utilizado la información del objeto


AdapterContextMenuInfo para saber qué elemento de la lista se ha pulsado aunque, en esta

171
ocasión, lo obtenemos llamando al método getMenuInfo() de la opción de menú MenuItem
recibida como parámetro.

Además, para modificar la opción del listado hemos usado el método


notifyDataSetChanged() del adaptador para que se recarguen los elementos del listado una
vez hemos modificado el texto de uno de ellos.

Desde Eclipse puedes abrir el proyecto Ejemplo 4 (Menú contextual) de la Unidad 3.


Estudia el código fuente y ejecútalo para mostrar en el emulador una aplicación en la que
usamos un menú contextua.

Para ver cómo funciona esta aplicación que usa un menú contextual inicia este
ejemplo en el emulador. Verás la pantalla siguiente:

3.5.3 Ventanas de diálogo en Android

El programador utiliza a menudo pequeñas ventanas de diálogo que para que el


usuario tome una decisión o reciba un mensaje informativo.

A diferencia de una ventana completa, estas pequeñas ventanas de diálogo tienen la


particularidad de que pueden aparecer flotando sobre la pantalla de fondo, que queda
inactiva.

Android define cuatro tipos diferentes de ventanas de diálogo:

172
Más información sobre Android

1. AlertDialog: puede contener hasta tres botones (incluso ninguno) o mostrar una lista
de elementos seleccionables como CheckBox o RadioButton. Es recomendable
utilizar este tipo de ventana de diálogo en la mayoría de las aplicaciones. Se hereda de
la clase Dialog de Android.

2. ProgressDialog: muestra una barra de progreso o la típica rueda de progreso. Se


hereda de la clase AlertDialog y, por lo tanto, también se pueden incluir botones.

3. DatePickerDialog: este diálogo permite seleccionar una fecha.

4. TimePickerDialog: este diálogo permite seleccionar una hora.

Una ventana de diálogo siempre se crea y se muestra como parte de una Actividad.

Normalmente debemos crear la ventana de diálogo en el método onCreateDialog() de


la Actividad para asociarla a ésta.

En el caso de que creemos una ventana de diálogo fuera del método anterior,
debemos indicar qué Actividad es la que alberga la ventana de diálogo mediante
setOwnerActivity(Activity).

Para mostrar un diálogo desde cualquier parte del código, hay que usar el método
showDialog() indicando como parámetro un entero único en la actividad que identifica el
diálogo que queremos mostrar. En el ejemplo 5 de esta unidad definimos estos identificadores
así:

private static final int DIALOGO_MENSAJE = 1;

private static final int DIALOGO_DOS_BOTONES = 2;

private static final int DIALOGO_TRES_BOTONES = 3;

...

Desde Eclipse puedes abrir el proyecto Ejemplo 5 (Ventanas de diálogo) de la Unidad 3.


Estudia el código fuente y ejecútalo para mostrar en el emulador una aplicación en la que
usamos varios tipos de ventanas de diálogo

A continuación, explicamos algunos tipos de ventanas de diálogo que aparecen en el


ejemplo 5:

173
3.5.3.1 Ventanas de diálogo con mensaje

Es una ventana de diálogo que obliga a que el usuario vea un mensaje bloqueando la

pantalla hasta que pulse la tecla volver . Sus métodos más importantes son éstos:

 setTitle(): establece el título de la ventana de diálogo.

 setMessage(): indica el mensaje que contiene la ventana de diálogo.

 setIcon():establece la propiedad Icon con una de las imágenes predefinidas del


sistema.

 show(): muestra la ventana de diálogo.

Si hacemos clic sobre el primer botón del ejemplo del curso, veremos que aparece el
siguiente mensaje:

El código fuente es el siguiente:

ventana = new AlertDialog.Builder(this);

ventana.setTitle("Atención");

ventana.setMessage("Tienes un mensaje nuevo. Pulsa el botón Atrás para volver


a la pantalla principal.");

ventana.setIcon(android.R.drawable.ic_dialog_email);

ventana.show();

3.5.3.2 Ventanas de diálogo con botones

En esta ventana, a diferencia de la anterior, se obliga al usuario a pulsar uno de los


botones creados mediante los métodos setPositiveButton(), setNegativeButton() o
setNeutralButton().

Además, con el método setCancelable() podemos inhabilitar la tecla de escape, para


evitar que el usuario cierre la ventana sin tomar una decisión.

Dispone además, de los mismos métodos de la ventana anterior.

174
Más información sobre Android

Si hacemos clic sobre el segundo o el tercer botón del ejemplo del curso, veremos que
aparecen los siguientes mensajes:

El código fuente es el siguiente:

ventana = new AlertDialog.Builder(this);

ventana.setIcon(android.R.drawable.ic_dialog_info);

ventana.setTitle("Encuesta");

ventana.setMessage("¿Te gusta la música clásica?");

ventana.setCancelable(false);

ventana.setPositiveButton("Sí", new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int boton) {

/* Sentencias si el usuario pulsa Sí */

Toast.makeText(getApplicationContext(), "Has pulsado el botón 'Sí'",


1).show();

});

ventana.setNegativeButton("No", new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int boton) {

/* Sentencias si el usuario pulsa No */

Toast.makeText(getApplicationContext(), "Has pulsado el botón


'No'",1).show();

});

ventana.setNeutralButton("A veces", new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int boton) {

/* Sentencias si el usuario pulsa A veces */

Toast.makeText(getApplicationContext(), "Has pulsado el botón 'A


veces'", 1).show();

175
});
ventana.show();

3.5.3.3 Ventanas de diálogo con selección

Esta ventana permite al usuario seleccionar una única opción de un listado de


RadioButton. Dispone de los métodos vistos anteriormente, salvo setMessage(), ya que
vamos a usar el método setSingleChoiceItems() para definir las posibles opciones del listado.

Si hacemos clic sobre el sexto botón del ejemplo del curso, veremos que aparece el
siguiente mensaje:

El código fuente es el siguiente:

ventana = new AlertDialog.Builder(this);

ventana.setIcon(android.R.drawable.ic_dialog_info);

ventana.setTitle("Selecciona un modelo de teléfono");

// ¡¡ No se puede incluir un mensaje dentro de este tipo de diálogo!!!

final CharSequence[] telefonos = {"iPhone", "Nokia", "Android"};

ventana.setSingleChoiceItems(telefonos, 0, new
DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int opcion) {

// Evento que ocurre cuando el usuario selecciona una opción

Toast.makeText(getApplicationContext(), "Has seleccionado "


+telefonos[opcion], Toast.LENGTH_SHORT).show();

});

176
Más información sobre Android

ventana.setPositiveButton("Aceptar", new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int boton) {

Toast.makeText(getApplicationContext(), "Has pulsado el botón


'Aceptar'", 1).show();

});

ventana.setNegativeButton("Cancelar", new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int boton) {

Toast.makeText(getApplicationContext(), "Has pulsado el botón


'Cancelar'", 1).show();

});
ventana.show(); 

3.5.3.4 Ventanas de diálogo personalizada

Esta ventana permite al programador diseñar mediante archivos XML la ventana de


diálogo que queremos mostrar al usuario. Dispone de los métodos vistos anteriormente, salvo
setMessage(), ya que vamos a usar el método setView() para definir el contenido de la
ventana.

Si hacemos clic sobre el octavo botón del ejemplo del curso, veremos que aparece el
siguiente mensaje:

El código fuente es el siguiente:

//  Primero preparamos el interior de la ventana de diálogo inflando su fichero


XML

String infService = Context.LAYOUT_INFLATER_SERVICE;

177
LayoutInflater li
=(LayoutInflater)getApplicationContext().getSystemService(infService); 

// Inflamos el componente compuesto definido en el XML

View inflador = li.inflate(R.layout.dialogo_entrada_texto, null);

// Buscamos los componentes dentro del Diálogo

final TextView nombreEdit = (TextView)


inflador.findViewById(R.id.nombre_edit);

final TextView passwordEdit = (TextView)


inflador.findViewById(R.id.password_edit);

ventana = new AlertDialog.Builder(this);

ventana.setTitle("Indica usuario y contraseña");

// Asignamos el contenido dentro del diálogo (el que hemos inflado


previamente)

ventana.setView(inflador);

// ¡¡ No se puede incluir un mensaje dentro de este tipo de diálogo!!!

ventana.setPositiveButton("Aceptar", new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int boton) {

Toast.makeText(getApplicationContext(), "Has escrito el nombre '"+

nombreEdit.getText().toString() +"', la contraseña '"+


passwordEdit.getText().toString() +"' y has pulsado el
botón 'Aceptar'", 1).show();

});

ventana.setNegativeButton("Cancelar", new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int boton) {

Toast.makeText(getApplicationContext(), "Has pulsado el botón


'Cancelar'", 1).show();

});
ventana.show();

Nota: es muy importante no usar el método setMessage() cuando usemos ventanas de


diálogo cuyo interior se define mediante listas de selección o un archivo de diseño XML, ya
que no se mostrará bien la ventana si lo hacemos.

En el Ejemplo 5 de esta Unidad puedes ver otros tipos de ventanas de diálogo muy

178
Más información sobre Android

similares a las estudiadas en este apartado. Recomendamos al alumno o alumna que estudie a
fondo el código fuente de este ejemplo.

179
 Una aplicación puede estar compuesta de una o varias Actividades.

 Una Tarea es un conjunto de actividades con las que un usuario interactúa.

 Android organiza las actividades en una pila de ejecución (en inglés stack)
donde se van apilando las actividades que el usuario invoca.

 Es posible mantener muchas tareas en segundo plano a la vez; sin embargo, si


el sistema operativo necesita memoria, puede destruirlas perdiendo sus estados de
ejecución.

 La configuración de un dispositivo Android puede cambiar mientras se ejecuta


una aplicación; por ejemplo, pueden cambiar la orientación de la pantalla
(vertical/horizontal), la disponibilidad de teclado, el idioma, etcétera. Cuando esto
ocurre la Actividad se destruye.

 Para almacenar la información importante sobre el estado de ejecución de una


Actividad podemos usar el método de llamada onSaveInstanceState() y
después restaurar esta información cuando el sistema vuelva a crear la Actividad.

 No hay garantías de que Android invoque siempre el método


onSaveInstanceState() justo antes de que la Actividad se destruya, ya que hay
casos en los que no es necesario guardar el estado de ejecución; por ejemplo,
cuando el usuario sale de la aplicación usando la tecla “Volver atrás.

 Debido a que no se garantiza que Android invoque siempre el método


onSaveInstanceState(), debe usarse únicamente para almacenar el estado
transitorio de la Actividad, es decir, la interfaz de usuario. Nunca debe emplearse
para almacenar datos persistentes con preferencias del usuario o datos de la
aplicación.

 Un hilo es una tarea que se ejecuta en paralelo con otra tarea.

 Todos los componentes de una misma aplicación Android se ejecutan en el mismo


hilo principal (en inglés se denomina "main thread"). A este hilo también se lo
denomina hilo de la interfaz de usuario (en inglés "UI thread").

180
 El sistema operativo Android sigue el modelo de ejecución de aplicaciones
denominado Modelo de ejecución de un único hilo (en inglés Single Thread Model).

 Hay tener en cuenta dos reglas cuando se diseñan aplicaciones en Android:

1. No bloquear nunca el hilo principal ni el de la interfaz de usuario.

2. No acceder a la interfaz de usuario de Android desde un hilo exterior.

 Si una aplicación Android tiene que realizar operaciones que no son instantáneas y
llevan cierto tiempo, hay que ejecutarlas en hilos separados en segundo plano.

 Un Menú es una serie de opciones que el usuario puede elegir para realizar una
determinada tarea.

 En las aplicaciones Android podemos utilizar tres tipos de menús diferentes:

1. Menús Principales: aparecen en la zona inferior de la pantalla al pulsar el


botón Menú del teléfono.
2. Submenús: menús secundarios que aparecen al elegir una opción de un
menú principal.
3. Menús Contextuales: se muestran al realizar una pulsación larga sobre
algún elemento de la pantalla. Es el equivalente al botón derecho del ratón
en un PC.

 Como es habitual en Android, existen dos formas de crear un menú en una


aplicación Android: mediante un fichero XML e "inflándolo" después o creando el
menú directamente mediante código Java.

 Las ventanas de diálogo son pequeñas ventanas que el programador utiliza a


menudo para que el usuario tome una decisión o reciba un mensaje informativo.

181
Unidad de Aprendizaje 9

EFECTOS
TRABAJANDO
DE TRANSICIÓN
CON Y
FICHEROS
ANIMACIÓN

ÍNDICE
4.1 FICHEROS EN ANDROID .......................................................... 185
4.1.1 Introducción ..................................................................... 185
4.1.2 Gestión de información en Android ................................. 185
4.1.3 Gestión del sistema de archivos en Android ................... 185
4.1.4 Clase Fichero File ............................................................. 186
4.1.4.1 Constructores más importantes .............................. 186
4.1.4.2 Métodos más importantes....................................... 187
4.1.5 Ficheros en la memoria interna del diapositivo ............... 188
4.1.6 Fichero de recurso de la aplicación ................................. 190
4.1.7 Fichero en almacenamiento externo................................ 191
4.1.8 Añadir datos a un fichero ................................................. 196
4.1.9 Gestionando las excepciones en la gestión de ficheros . 196 

4.2 PREFERENCIAS DE UNA APLICACIÓN ANDROID ................. 197


4.2.1 Preferencia de las aplicaciones Android.......................... 197
4.2.2 Pantallas de opciones ...................................................... 200

4.3 RECURSOS DE LAS APLICACIONES ANDROID ..................... 207


4.3.1 Tipos de recursos............................................................. 208
4.3.2 Crear literales en ficheros de recursos con Eclipse......... 208
4.3.3 Recursos de tipo Cadena (String Resources) .................. 209
4.3.4.1 Cadena (String) ....................................................... 210
4.3.4.2 Matriz de cadenas (String Array)............................. 211
4.3.4.3 Cadenas de cantidad (Quantity Strings) ................. 212
4.3.4 Formatear y cambiar el estilo de las cadenas de recursos213
4.3.4.1 Escapando comillas simples y dobles.................... 213
4.3.4.2 Formatear cadenas de texto................................... 214
4.3.4.3 Cambio de estilo ..................................................... 214

4.4 ACCESO A INTERNET CON ANDROID ..................................... 215


4.4.1 Ejemplo de conexión a Internet........................................ 216
4.4.2 Conexión a través de proxy ............................................. 220

4.5 QUÉ SON JSON (JavaScript Object Notation? ........................ 221


4.5.1 Cómo usar JSON en nuestras aplicaciones a Android.... 222
4.5.2 Cómo escribir ficheros en formato JSON ........................ 225
4.1 FICHEROS EN ANDROID

4.1.1 Introducción

En esta Unidad vamos a explicar cómo gestionar ficheros en Android, tanto en la


memoria interna del teléfono como en su tarjeta de memoria externa SD.

Además, veremos cómo almacenar las preferencias de una Aplicación de Android.

También detallaremos el uso de ficheros JSON en Android.

Finalmente, accederemos a Internet desde aplicaciones Android y veremos lo que


son los Recursos (Resources).

4.1.2 Gestión de información en Android

En Android existen tres formas de almacenar información, para poder usarla en las
aplicaciones:

 Preferencias de la aplicación
 Ficheros locales en el sistema de archivos del sistema operativo
 Base de datos SQLite

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

4.1.3 Gestión del sistema de archivos en Android

Como en cualquier sistema operativo, en Android también podemos manipular


ficheros de forma muy parecida a como se hacen en Java.

185
En Android, por defecto, los ficheros son privados y únicamente puede acceder a ellos
la aplicación que los crea. Para compartir información de estos ficheros se utilizan los Content
Providers que veremos en la Unidad 7.

Lo primero que hay debemos tener en cuenta es dónde queremos almacenar estos
ficheros y la manera en que vamos a acceder a ellos: lectura o escritura.

Podemos leer y escribir ficheros localizados en:

1. La memoria interna del dispositivo: como fichero o como recurso de la


aplicación.
2. La tarjeta SD externa, si existe, también denominada almacenamiento
externo.

4.1.4 Clase Fichero File

La clase File de Android se usa para identificar y gestionar archivos y directorios del
sistema operativo. Un archivo en Android puede estar identificado por su ruta absoluta,
relativa al directorio raíz del sistema de archivos, o por su ruta relativa, que es directorio actual
en el que se ejecuta la aplicación. Esta clase File está basada en la clase de Java.

El acceso a los ficheros es similar al Java estándar: se deben crear inputs y outpus
streams.

La clase File puede hacer referencia a un archivo que ya exista o a uno que vayamos a
crear. Aunque el nombre de esta clase sea File, también puede, referirse a un directorio o un
enlace (link) de Linux.

Esta clase proporciona una funcionalidad limitada para obtener y establecer permisos
del archivo, cambiar el tipo de archivo o establecer su fecha de última modificación.

4.1.4.1 Constructores más importantes

 File(File dir, String nombre): crea un fichero usando el nombre y el


directorio especificados como parámetros.

 File(String dir): crea un fichero en el directorio especificado.

 File(URI uri): crea un fichero usando el camino especificado en el parámetro


URI (del inglés Uniform Resource Identifier), que es un identificador uniforme
de recurso. Se trata de una cadena de caracteres corta que identifica
inequívocamente un recurso (servicio, página, documento, dirección de correo
electrónico, enciclopedia, etcétera.).

186
4.1.4.2 Métodos más importantes

 boolean canExecute(): comprueba si la aplicación puede ejecutar el fichero.

 boolean canRead(): indica si el fichero se puede leer.

 boolean canWrite(): comprueba si la aplicación puede escribir datos en el


fichero.

 boolean createNewFile(): crea un archivo nuevo y vacío en el sistema de


archivos de acuerdo con la ruta almacenada.

 static File createTempFile(String prefijo, String sufijo, File directorio): crea


un archivo temporal vacío en el directorio establecido usando un prefijo y un
sufijo para formar el nombre del mismo.

 static File createTempFile(String prefix, String suffix): crea un archivo


temporal vacío usando un prefijo y un sufijo para formar el nombre del mismo.

 boolean delete(): borra el fichero.

 boolean exists(): indica si existe un fichero.

 String getAbsolutePath(): obtiene el camino absoluto del fichero.

 long getFreeSpace(): indica el número de bytes libres en la partición actual


(donde se encuentra el fichero).

 String getName(): devuelve el nombre del archivo o del directorio que


representa al archivo.

 String getPath(): devuelve el camino del fichero.

 long getTotalSpace(): indica el tamaño total en bytes de la partición actual


(donde se encuentra el fichero).

 boolean isAbsolute(): indica si el camino del fichero es absoluto.

 boolean isDirectory(): indica si la clase File contiene un directorio.

 boolean isFile(): indica si la clase File contiene un archivo de datos.

 boolean isHidden(): indica si el fichero es de tipo oculto en el sistema


operativo.

 long lastModified(): devuelve la fecha de la última modificación del archivo


medido en número de milisegundos desde el 1 de enero 1970.

 long length(): indica el tamaño del fichero en bytes.

 String[] list(): devuelve un listado con los nombres de todos los ficheros y
subdirectorios contenidos en un directorio.

 File[] listFiles(): devuelve un listado de tipo File con todos los ficheros y
subdirectorios contenidos en un directorio.

 boolean mkdir(): crea un subdirectorio en el directorio actual.

187
 boolean mkdirs(): crea recursivamente un subdirectorio incluyendo todos los
subdirectorios superiores que completan el camino.

 boolean renameTo(File nuevoNombre): renombra un fichero.

 boolean setExecutable(boolean executable, boolean ownerOnly): Cambia


los permisos del fichero indicando si es un ejecutable y si únicamente lo puede
ejecutar el usuario que lo ha creado.

 boolean setLastModified(long time): establece la última fecha de


modificación del fichero.

 boolean setReadable(boolean readable, boolean ownerOnly): cambia los


permisos del fichero indicando si se puede leer y si únicamente lo puede leer el
usuario que lo ha creado.

 boolean setWritable(boolean writable, boolean ownerOnly): cambia los


permisos del fichero indicando si se puede escribir en él y si únicamente puede
escribir en el mismo el usuario que lo ha creado.

 URI toURI(): devuelve el identificador URI del fichero.

4.1.5 Ficheros en la memoria interna del diapositivo

Al almacenar archivos en la memoria interna debemos tener en cuenta las limitaciones


de espacio que tienen muchos dispositivos, por lo que no deberíamos abusar de este espacio
guardando ficheros de gran tamaño.

Crear ficheros en la memoria interna es muy sencillo. Android dispone del método
openFileOutput(), que recibe como parámetros el nombre del fichero y el modo de acceso al
mismo. Este modo de acceso puede ser:

o MODE_PRIVATE: es el valor por defecto, que únicamente permite el acceso privado


desde nuestra aplicación.
o MODE_APPEND: permite añadir datos a un fichero ya existente.
o MODE_WORLD_READABLE: permite a otras aplicaciones leer el fichero.
o MODE_WORLD_WRITABLE: permite a otras aplicaciones escribir en el fichero.

El método openFileOutput() devuelve una referencia a un objeto de tipo stream de


Java asociado al fichero (más en concreto, se trata de un objeto FileOutputStream), a partir
del cual podemos utilizar los métodos de manipulación de ficheros tradicionales del lenguaje
Java.

En el siguiente ejemplo convertimos este stream al tipo OutputStreamWriter para


escribir una cadena de texto en el fichero:

try

188
OutputStreamWriter fileout=
new OutputStreamWriter(openFileOutput("fichero_interno.txt",
Context.MODE_PRIVATE));

fileout.write("En un lugar de la Mancha...");

fileout.close();

catch (Exception excepcion)

Log.e("Fichero", "Error al escribir fichero en memoria interna");

Android almacena este archivo de texto en la memoria interna del dispositivo, en un


directorio determinado y siempre el mismo, sin que el programador pueda cambiarlo. Sigue
este patrón:

/data/data/paquete_java/files/nombre_del_fichero

En el ejemplo anterior se almacena en:

/data/data/es.mentor.unidad4.eje1.ficheros/files/fichero_interno.txt

Si ejecutamos el Ejemplo 1 de esta Unidad podemos comprobar en el DDMS cómo se


crea el fichero correctamente en el directorio indicado. Para acceder a esta herramienta dentro
de Eclipse, hay que hacer Clic en la opción del menú principal: Window -> Open Perspective
-> DDMS:

189
Leer ficheros desde la memoria interna es igual de sencillo. Procedemos de forma
análoga utilizando esta vez el método openFileInput(), para abrir el fichero y usamos los
métodos de lectura mostrados anteriormente para leer el contenido.

try

BufferedReader filein = new BufferedReader(

new InputStreamReader(openFileInput("fichero_interno.txt")));

String texto = filein.readLine();

filein.close();

catch (Exception ex)

Log.e("Ficheros", "Error al leer fichero de memoria interna");

4.1.6 Fichero de recurso de la aplicación

Otra forma de almacenar ficheros en la memoria interna del dispositivo es incluirlos


como un recurso de la propia aplicación. Aunque este método es muy útil, únicamente
debemos utilizarlo cuando no necesitemos realizar modificaciones sobre el fichero, ya que el
acceso al mismo es de sólo lectura.

Para incluir un fichero como recurso de la aplicación debemos colocarlo en la carpeta


/res/raw del proyecto Android. Esta carpeta no suele estar creada por defecto, por lo que
debemos crearla manualmente en Eclipse.

Una vez creada la carpeta raw, podemos añadir en ella cualquier fichero que
necesitemos incluir con la aplicación en tiempo de compilación en forma de recurso. En el
Ejemplo 1 de esta Unidad hemos incluido el fichero de texto “prueba_raw.txt“.
Posteriormente, en tiempo de ejecución, podemos acceder a este fichero, sólo en modo de
lectura, de una forma similar a la que ya hemos visto anteriormente para el resto de ficheros en
la memoria interna del dispositivo.

Estos ficheros de tipo recurso también pueden ser binarios: por ejemplo, imágenes,
vídeos, etcétera.

En primer lugar, para acceder a este fichero, obtenemos los recursos de la aplicación
con el método getResources() y sobre éste utilizamos el método

190
openRawResource(id_del_recurso) para abrir el fichero en modo lectura. Este método
devuelve un objeto de tipo InputStream que podemos manipular.

En el ejemplo del curso, convertimos el InputStream en un objeto BufferedReader


para leer el texto contenido en el fichero, tal y como haríamos en Java. A continuación,
mostramos el código fuente:

try

InputStream ficheroraw = getResources().openRawResource(R.raw.prueba_raw);

BufferedReader brin = new BufferedReader(new InputStreamReader(ficheroraw));

while (true) {

texto = brin.readLine();

// Si ya no hay más líneas que leer hemos acabado de leer el fichero

if (texto==null) break;
resultado.append("\n"+Html.fromHtml(texto));

} // end while

ficheroraw.close();

catch (Exception ex)

 Log.e("Ficheros", "Error al leer fichero de recurso de aplicación");

4.1.7 Fichero en almacenamiento externo

La mayoría de los dispositivos Android disponen de una tarjeta SD externa para que el
sistema tenga un espacio extra de almacenamiento de información. Normalmente en esta
tarjeta se guardan las fotos, los vídeos y, en las versiones últimas de Android, incluso se
instalan aplicaciones.

A diferencia de la memoria interna, la memoria externa no tiene por qué estar presente
en el dispositivo o puede que el sistema no reconozca su formato. Por lo tanto, antes de usar
ficheros en la memoria externa, hay que comprobar que dicha memoria está presente y
disponible para leer y/o escribir en ella.

Para esto, Android proporciona en la clase Environment el método estático


getExternalStorageStatus(), que indica si la memoria externa está disponible y si se puede
leer y escribir en ella. Este método devuelve una serie de valores que señalan el estado de la
memoria externa. Entre ellos, los más importantes son los siguientes:

191
 MEDIA_MOUNTED: indica si la memoria externa está disponible y es posible leer y
escribir en ella.

 MEDIA_MOUNTED_READ_ONLY: indica que la memoria externa está disponible, pero


únicamente podemos leer información.

 Otros valores que indicarán que existe algún tipo de problema y que, por lo tanto, no
podemos usar la memoria externa (MEDIA_UNMOUNTED, MEDIA_REMOVED, etcétera).
Se puede consultar todos estos valores en la documentación oficial de la clase
Environment.

Teniendo en cuenta esto, en el Ejemplo 1 realizamos comprobaciones previas del


estado de la memoria externa del dispositivo con el siguiente método:

// Método que comprueba si el almacenamiento externo está activo y si se puede


escribir en la tarjeta.

private void compruebaAlmacenamientoExt(){

  // Obtenemos el estado del almacenamiento externo del teléfono

  String estado = Environment.getExternalStorageState(); 

  // La tarjeta está activa y se puede escribir en ella 

  if (Environment.MEDIA_MOUNTED.equals(estado)) { 

hayAlmacenamientoExt = almacenamientoExtEscritura = true;

resultado.append("\n\nEl teléfono dispone de almacenamiento externo


conectado y se puede escribir en él.");

} else

  // Sólo se puede leer el almacenamiento externo 

if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(estado)) {

hayAlmacenamientoExt = true;

almacenamientoExtEscritura = false;

resultado.append("\n\nEl teléfono dispone de almacenamiento externo


conectado pero no se puede escribir en él.");

} else {

  // No se puede leer el almacenamiento externo 

hayAlmacenamientoExt = almacenamientoExtEscritura = false;

resultado.append("\n\nEl teléfono no tiene ningún almacenamiento


externo conectado.");

if (hayAlmacenamientoExt) {
192
  // Mostramos el directorio donde está el almacenamiento externo

  File dir = android.os.Environment.getExternalStorageDirectory();

dirAlmacExt=dir.getAbsolutePath();

resultado.append("\n\nDirectorio almacenamiento externo: "+dir);

}
} // end compruebaTarjetaSD

Una vez comprobado el estado de la memoria externa y dependiendo del resultado


obtenido, podemos leer o escribir en ella cualquier tipo de fichero.

Hemos aprovechado el método anterior para obtener la ruta al directorio raíz de esta
memoria. Para ello utilizamos el método getExternalStorageDirectory() de la clase
Environment, que devuelve un objeto File con la ruta de dicho directorio.

Empecemos por la funcionalidad de escritura en la memoria externa. Lo primero que


hemos hecho ha sido crear el directorio ejemplo_curso_Mentor dentro de la tarjeta SD
usando el método mkdirs de la clase File.

Después, para escribir un fichero en ese directorio de la tarjeta SD creamos un nuevo


objeto File que combina ambos elementos. Para acabar, sólo hay que encapsularlo en algún
objeto de escritura de ficheros de la API de Java y escribir algún dato. En este caso lo
convertimos de nuevo a un objeto OutputStreamWriter para escribir un archivo de texto. El
código fuente es el siguiente:

//Si la memoria externa está disponible y se puede escribir

if (hayAlmacenamientoExt && almacenamientoExtEscritura)

try

{
// Creamos un directorio de prueba

File directorio = new File (dirAlmacExt + "/ejemplo_curso_Mentor");

directorio.mkdirs();

resultado.append("- Creamos el directorio " + dirAlmacExt +


"/ejemplo_curso_Mentor");

// Abrimos in fichero en el raíz de la tarjeta SD

File fichero = new File(directorio, "prueba_sd.txt");

OutputStreamWriter fout = new OutputStreamWriter(new


FileOutputStream(fichero));

resultado.append("\n\n- Abrimos fichero '" + dirAlmacExt +


"/ejemplo_curso_Mentor/fichero_externo.txt' para escritura en
memoria externa");

193
fout.write("Caminante no hay camino se hace camino al andar...");

resultado.append("\n\n- Escribimos los datos");

fout.close();

resultado.append("\n\n- Cerramos fichero");

catch (Exception ex)

Log.e("Ficheros", "Error al escribir fichero en memoria externa");

resultado.append("Error al escribir fichero en memoria externa");

}
} else resultado.append("No hay almacenamiento externo disponible o no
se puede escribir en él.");

Hay que tener en cuenta que es preciso que especificar en el fichero


AndroidManifest.xml que la aplicación necesita el permiso de escritura en la memoria
externa. Para "manifestar" que la aplicación necesita este permiso usamos la cláusula <uses-
permission> utilizando el valor android.permission.WRITE_EXTERNAL_STORAGE. El
fichero queda así:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="es.mentor.unidad4.eje1.ficheros"

android:versionCode="1"

android:versionName="1.0">

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

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

<activity android:name=".ficherosActivity"
android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

194
</application>

 <!-- Damos permisos a la aplicación para que pueda escribir en la tarjeta SD


-->

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

</manifest>

Si ejecutamos ahora el Ejemplo 1 y abrimos al explorador de archivos del DDMS


podemos comprobar que se ha creado correctamente el fichero en el directorio especificado
de la tarjeta SD:

La forma de leer un fichero de la tarjeta SD es muy parecida a si estuviera almacenado


en la memoria interna del teléfono; únicamente cambia el directorio del que leemos el fichero.
El código fuente tiene este aspecto:

try

File fichero = new File(dirAlmacExt + "/ejemplo_curso_Mentor",


"prueba_sd.txt");

BufferedReader fin = new BufferedReader(new InputStreamReader(


new FileInputStream(fichero)));

resultado.append("- Abrimos fichero '"+ dirAlmacExt +


"/ejemplo_curso_Mentor/fichero_externo.txt' para lectura de memoria
externa");

String texto = fin.readLine();

resultado.append("\n\n- Leemos el contenido del fichero:\n");

resultado.append(texto);

fin.close();

resultado.append("\n\n- Cerramos fichero");

195
}

catch (Exception ex)

Log.e("Ficheros", "Error al leer fichero de memoria externa");


}

Desde Eclipse puedes abrir el proyecto Ejemplo 1 (Ficheros) de la Unidad 4. Estudia el


código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior, en el
que hemos utilizado los métodos de manipulación de archivos

4.1.8 Añadir datos a un fichero

Por defecto, Android sobrescribe siempre un fichero si éste ya existe perdiendo los
datos almacenados anteriormente. Esto ocurre en los ejemplos anteriores.

Para poder añadir información a un fichero existente podemos cambiar el constructor


FileOutputStream(File fichero) por el constructor FileOutputStream (File fichero, Boolean
append), que permite indicar si queremos agregar la información al final del fichero con el
parámetro append =true.

4.1.9 Gestionando las excepciones en la gestión de ficheros

Fíjate en que todo el código fuente que gestiona el fichero está contenido dentro de un
bloque try-catch. De esta manera se controlan los errores de ejecución de las sentencias
mediante el manejo de excepciones estándar de Java.

Por ejemplo, Android puede lanzar las siguientes excepciones cuando ocurre un error
al manipular un fichero:

1. Si el fichero indicado en el parámetro del constructor FileOutputStream(File)


no existe, se lanza la excepción FileNotFoundException.

2. Si ocurre algún error al cerrar un fichero con el método close(), entonces


Android lanza la excepción IOException.

El programador debe gestionar esas Excepciones para poder mostrar al usuario un


mensaje del sistema operativo que entienda, en lugar de en inglés. La forma estándar de
hacerlo tiene el siguiente aspecto:

196
try {

// Sentencias que queremos ejecutar y que pueden lanzar


excepciones

} catch (ExceptionType excepcion1) {

// Sentencias que gestiona la excepción 1

} catch (ExceptionType excepcion2) {

// Sentencias que gestiona la excepción 2

En el caso del Ejemplo 1 del curso hemos capturamos todas las excepciones en un
único bloque para mostrar al usuario y en la consola de Eclipse un mensaje sencillo de error:

catch (Exception ex)

Log.e("Ficheros", "Error al escribir fichero en memoria externa");

resultado.append("Error al escribir fichero en memoria externa");

4.2 PREFERENCIAS DE UNA APLICACIÓN ANDROID

4.2.1 Preferencia de las aplicaciones Android

Las preferencias de una aplicación son datos que una aplicación guarda y
recupera para personalizar la experiencia del usuario. Por ejemplo, se debe almacenar
información personal, configuración de la aplicación, opciones de presentación, etcétera.

En el apartado anterior hemos visto cómo almacenar información en ficheros. Las


preferencias de una aplicación se podrían almacenar utilizando este método, aunque Android
proporciona otro método alternativo diseñado específicamente para administrar este tipo de
datos: las Preferencias compartidas (shared preferences en inglés).

197
Cada preferencia se almacena siguiendo la estructura clave-valor. Es decir, cada una
de ellas está compuesta por un identificador único (por ejemplo, “email”) y un valor asociado a
dicho identificador (por ejemplo, “correo@email.com”). Estos datos se guardan en un fichero
XML.

Para gestionar las preferencias de una aplicación hay que usar la clase
SharedPrefences. Una misma aplicación Android puede gestionar varias colecciones de
preferencias que se diferencian por un identificador único. Para obtener la referencia a una
colección determinada utilizamos el método getSharedPrefences() indicando el identificador
de la colección y el modo de acceso a la misma. El modo de acceso indica qué aplicaciones
tienen acceso a esta colección de preferencias y qué operaciones podrán realizar sobre ellas.

Existen tres tipos de permisos principales:

o MODE_PRIVATE: únicamente la aplicación tiene acceso a las preferencias.


o MODE_WORLD_READABLE: todas las aplicaciones pueden leer las preferencias, aunque
únicamente la matriz puede modificarlas. 

o MODE_WORLD_WRITABLE:  todas las aplicaciones pueden leer y modificar las


preferencias.

Por ejemplo, para obtener la referencia a la colección de preferencias


“MisPreferencias”, con modo de acceso exclusivo,para la aplicación que las gestiona,
escribimos la siguiente sentencia:

SharedPreferences preferencias =

getSharedPreferences("MisPreferencias",Context.MODE_PRIVATE);

Una vez hemos creado el objeto que nos permite acceder a la colección de
preferencias, podemos leer, insertar o modificar claves de preferencias utilizando los métodos
get() o put() correspondientes al tipo de dato de cada preferencia. Por ejemplo, para obtener
el valor de la preferencia “email” de tipo String escribimos la siguiente setencia:

String correo = preferencias.getString("email", "correo@email.com");

Al método getString() le pasamos como parámetros el nombre de la preferencia que


queremos leer y el valor por defecto (por si no contiene nada o no existe). A parte del método
getString(), existen métodos análogos para el resto de tipos de datos básicos de Java. Por
ejemplo, getInt(), getLong(), getFloat(), getBoolean(), etcétera.

198
Actualizar o añadir nuevas claves de preferencias es muy parecido. En lugar de usar el
objeto SharedPreferences, utilizaremos SharedPreferences.Editor para editar preferencias.
Accedemos a este objeto mediante el método edit() de la clase SharedPreferences.

Una vez obtenida la referencia al editor, utilizamos los métodos put() oportunos en
función del tipo de dato de cada clave de preferencia para actualizar o insertar su valor.

Por ejemplo, putString(clave, valor) actualiza una preferencia de tipo String. De igual
forma, existen métodos get() para todos los tipos de datos básicos de Java: putInt(),
putFloat(), putBoolean(), etcétera.

Para acabar, una vez actualizados/insertados todos las claves de preferencias


necesarias invocamos el método commit() para confirmar los cambios. Fíjate en el siguiente
ejemplo sencillo:

SharedPreferences preferencias =

getSharedPreferences("MisPreferencias", Context.MODE_PRIVATE);

SharedPreferences.Editor editor = preferencias.edit();

editor.putString("email", "correo@email.com");

editor.putString("nombre", "Pedro del Cielo Lindo");

editor.commit();

Las preferencias se almacenan en un fichero XML dentro de un directorio con el


siguiente patrón:

/data/data/paquetejava/shared_prefs/nombre_coleccion.xml

En este caso encontraremos el fichero de preferencias en el directorio:

/data/data/es.mentor.unidad4.eje2.preferencias/shared_prefs/MisPreferencias.xml

Si descargamos este fichero desde el DDMS y lo abrimos con cualquier editor de


texto, veremos el contenido siguiente:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>

<map>

<string name="nombre">Pedro del Cielo Lindo</string>

<string name="email">correo@email.com</string>

</map>

199
En este fichero XML observamos cómo se almacenan las dos preferencias del ejemplo
anterior con sus claves y valores correspondientes.

4.2.2 Pantallas de opciones

Si abrimos cualquier pantalla de preferencias estándar de Android, veremos que todas


comparten una interfaz común, similar a la siguiente imagen (Preferencias de "Ajustes de
sonido" de Android):

Si nos fijamos en la imagen anterior, vemos que las distintas opciones se organizan
dentro de una pantalla de opciones que incluye varias categorías (“General”, “Llamadas
entrantes“, etcétera).

Dentro de cada categoría, aparecen varios tipos de opciones. Por ejemplo, de tipo
CheckBox (“Modo Silencio“) o de tipo lista de selección (“Vibrar“).

Android permite mejorar y simplificar la gestión de las preferencias de una aplicación


mediante el uso de una pantalla de opciones.

Para definir la pantalla de opciones vamos a usar un fichero de diseño (layout) XML,
que guardamos en la carpeta /res/xml/pantallapreferencias.xml del proyecto.

El contenedor principal de nuestra pantalla de preferencias será el elemento


<PreferenceScreen> que aparece en el fichero opciones.xml. Este elemento representa la
pantalla de opciones en sí, dentro de la cual incluiremos el resto de componentes de la interfaz
de usuario.

Dentro de esta pantalla podemos incluir una lista de opciones organizadas por
categorías, que se representan mediante el elemento <PreferenceCategory>, al que

200
añadimos un texto descriptivo utilizando el atributo android:title. Dentro de cada categoría
podemos incluir varias opciones. Éstas pueden ser de distintos tipos, por ejemplo:

 CheckBoxPreference: caja seleccionable.


 EditTextPreference: caja de texto.
 ListPreference: lista con valores seleccionables (sólo uno).
 MultiSelectListPreference: lista con valores varios seleccionables.

A continuación, vamos a describir los diferentes atributos de estos tipos de opciones:

 CheckBoxPreference

Se usa para introducir una opción de preferencia que sólo puede tener dos valores:
activada (marcada) o desactivada (desmarcada). Es el equivalente al componente de tipo
CheckBox. En este caso, hay que especificar los atributos: nombre interno de la opción
(android:key), texto que muestra (android:title) y descripción de la opción
(android:summary). Veamos un ejemplo:

<CheckBoxPreference android:key="opcion1"

android:title="Búsqueda automática"
android:summary="Iniciar búsqueda automática en Internet" />

 EditTextPreference

Se utiliza para introducir una opción de preferencia que contiene una cadena de texto.
Al pulsar sobre una opción de este tipo, se muestra un cuadro de diálogo sencillo que solicita
al usuario un texto. Para este tipo de opción, además de los tres atributos comunes a la
opción anterior, también hay que indicar el texto que aparece en el cuadro de diálogo
mediante el atributo android:dialogTitle. Por ejemplo:

<EditTextPreference android:key="opcion2"

android:title="Texto de la búsqueda"

android:summary="Indica el texto por defecto de la búsqueda"

android:dialogTitle="Introduce un texto" />

 ListPreference

Se emplea para que el usuario seleccione una única opción de preferencia de una lista
de valores predefinida. Además de los cuatro atributos anteriormente comentados, hay que

201
añadir dos más: uno para indicar la lista de valores que se visualizan en la lista, y otro para
señalar los valores internos que guardaremos para cada uno de los valores de la lista anterior.
Por ejemplo, al usuario podemos mostrarle una lista de buscadores con el texto “Google” y
“Bing”, pero internamente almacenarlos como “www.google.es” y “www.bing.com”.

Esta lista de valores la definimos en el fichero XML /res/xml/opciones.xml con los


tipos de recursos <string-array> necesarios. En este caso son dos: uno para la lista de
valores visibles y otro para la lista de valores internos que se guardan en el fichero de
preferencias. Este fichero tiene este aspecto:

<?xml version="1.0" encoding="utf-8"?>

<resources>

<string-array name="nombre">

<item>Google</item>

<item>Bing</item>

<item>Yahoo</item>

</string-array>

<string-array name="url">

<item>www.google.es</item>

<item>www.bing.com</item>

<item>www.yahoo.es</item>

</string-array>
</resources>

En la opción de preferencia utilizamos los atributos android:entries y


android:entryValues para hacer referencia a estas listas, como vemos a continuación:

<ListPreference

android:key="opcion3"

android:title="Buscadores"

android:summary="Indica el buscador por defecto"

android:dialogTitle="Selecciona buscador"

android:entries="@array/nombre"

android:entryValues="@array/url" />

202
 MultiSelectListPreference

Se emplea para que el usuario seleccione una o varias opciones de preferencia de una
lista de valores predefinida. Los atributos que debemos establecer son, por lo tanto, los
mismos que para el tipo ListPreference. Vemos un ejemplo a continuación:

<MultiSelectListPreference

android:key="opcion4"

android:title="Varios buscadores"

android:summary="Indica varios buscadores"

android:dialogTitle="Selecciona buscadores"

android:entries="@array/nombre"

android:entryValues="@array/url" />

A continuación, mostramos el fichero completo pantallapreferencias.xml que usamos


en el Ejemplo 2 de esta Unidad:

<?xml version="1.0" encoding="utf-8"?>

<PreferenceScreen

xmlns:android="http://schemas.android.com/apk/res/android">

<PreferenceCategory android:title="Categoría 1">

<CheckBoxPreference

android:key="opcion1"

android:title="Búsqueda automática"

android:summary="Iniciar búsqueda automática en Internet" />

<EditTextPreference

android:key="opcion2"

android:title="Texto de la búsqueda"

android:summary="Indica el texto por defecto de la búsqueda"

android:dialogTitle="Introduce un texto" />

</PreferenceCategory>

<PreferenceCategory android:title="Categoría 2">

<ListPreference

android:key="opcion3"

android:title="Buscadores"

203
android:summary="Indica el buscador por defecto"

android:dialogTitle="Selecciona buscador"

android:entries="@array/nombre"

android:entryValues="@array/url" />

</PreferenceCategory>
</PreferenceScreen>

Una vez está definida la estructura de la pantalla de opciones, hay que implementar
una nueva Actividad que la llamaremos cuando queramos mostrar la pantalla de preferencias,
y que se encargará internamente de gestionar todas las opciones, guardarlas, modificarlas,
etcétera, a partir de la definición XML.

Android facilita el trabajo al programador ofreciendo la clase PreferenceActivity, que


se encarga de gestionar todas las operaciones internamente. Únicamente hay que crear la
nueva Actividad PantallaPreferencias que se extiende de esta clase e implementar su
método onCreate() para invocar el método addPreferencesFromResource() indicando el
fichero XML en el que hemos definido la pantalla de opciones. El código fuente se encuentra
en el fichero PantallaPreferencias.java:

public class PantallaPreferencias extends PreferenceActivity {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

addPreferencesFromResource(R.xml.pantallapreferencias);
Toast.makeText(getBaseContext(), "Pulsa la tecla 'Volver atrás' para
guardar las preferencias y volver a la aplicación.", 1).show();

La nueva actividad, al descender de la clase PreferenceActivity, se encarga


automáticamente de crear la interfaz de usuario con la lista de opciones siguiendo el diseño
del fichero XML y se ocupa de mostrar, modificar y guardar estas opciones cuando sea
necesario.

Para que la aplicación pueda llamar a esta nueva Actividad, hay que incluirla en el
fichero AndroidManifest.xml como otra actividad más de la aplicación:

204
<activity android:name=".PantallaPreferencias"

android:label="@string/app_name">
</activity>

Para acabar la aplicación, hay que añadir algún mecanismo que nos permita mostrar la
pantalla de preferencias. Esta opción suele estar en un menú, pero para simplificar, en el
Ejemplo 2 vamos a incluir el botón preferenciasBtn en la interfaz del usuario, para mostrar la
ventana de preferencias.

Al pulsar este botón se muestra la ventana de preferencias mediante el método


startActivity() al que pasamos como parámetros el contexto de la aplicación y la clase de la
ventana de preferencias (PantallaPreferencias.class).

preferenciasBtn.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

startActivity(new Intent(PreferenciasActivity.this,
PantallaPreferencias.class));

}
});

En la Unidad 5 trataremos en detalle cómo usar Intents para iniciar Actividades de


nuestra aplicación o de otra externa.

Desde Eclipse puedes abrir el proyecto Ejemplo 2 (Preferencias) de la Unidad 4. Estudia el


código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior.

Si ejecutamos la aplicación en el emulador y pulsamos el botón de preferencias,


aparece la siguiente pantalla de opciones:

205
Podemos marcar o desmarcar directamente la primera opción pulsando sobre el
CheckBox. Si pulsamos sobre la segunda opción de tipo texto, se muestra una pequeña
ventana que permite introducir el valor de la opción:

Por último, la tercera opción de tipo lista muestra una ventana emergente con la lista
de valores posibles, donde únicamente podemos seleccionar uno:

Una vez hayamos establecidos los valores de las preferencias, podemos salir de esta
ventana de opciones pulsando el botón Atrás del dispositivo o del emulador . La Actividad
PantallaPreferencias se habrá ocupado por nosotros de guardar correctamente los valores
de las opciones.

Para comprobarlo vamos a añadir a la interfaz de usuario el botón


obtenerOpcionesBtn, que recupera el valor actual de las tres preferencias y las muestra en la
pantalla.

Para acceder a las preferencias compartidas de la aplicación usaremos el método


getDefaultSharedPreferences(). Como hemos hecho anteriormente, usamos los distintos
métodos get() para recuperar el valor de cada opción dependiendo de su tipo:

206
obtenerPreferenciasBtn.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

SharedPreferences prefe =
PreferenceManager.getDefaultSharedPreferences(PreferenciasActivity.this);

lblResultado.setText("");

Log.i("", "Opción 1: " + prefe.getBoolean("opcion1", false));

lblResultado.append("Búsqueda automática: " +


prefe.getBoolean("opcion1", false));

Log.i("", "Opción 2: " + prefe.getString("opcion2", ""));

lblResultado.append("\n\nTexto por defecto: " +


prefe.getString("opcion2", ""));

Log.i("", "Opción 3: " + pref.getString("opcion3", ""));

lblResultado.append("\n\nBuscador: " +
prefe.getString("opcion3", ""));

}
});

Si ejecutamos ahora la aplicación, establecemos las preferencias y pulsamos el botón


de consulta de las preferencias, veremos la siguiente pantalla:

4.3 RECURSOS DE LAS APLICACIONES ANDROID

Los Recursos de Android son archivos almacenados en el directorio /res del proyecto.
Estos ficheros de recursos pueden ser de tipo audio, vídeo, imágenes, texto, XML,
etcétera, y se pueden integrar en la aplicación.

¿Por qué usar ficheros con recursos?

207
1. Los recursos se integran en la aplicación de manera transparente y se pueden
cambiar sin necesidad de modificar el código fuente en Java.

2. Android genera un identificador único (ID) para cada archivo de recurso para
acceder a ellos directamente desde el código fuente en Java. Todos estos
identificadores de recursos se incluyen automáticamente en el archivo
/gen/nombre_paquete/R.Java.

3. Android trata automáticamente los archivos de recursos de tipo XML de forma


que se puede acceder a los valores definidos en ellos directamente.

4. El uso de Recursos es muy útil en la Localización e Internacionalización de la


aplicación para desarrollar aplicaciones en varios idiomas. Así, se modifican
los textos de las etiquetas, su alineación, imágenes, direcciones de Internet o
cualquier tipo de archivo en función del idioma del dispositivo Android.

4.3.1 Tipos de recursos

 Cadenas (String), colores, matrices (arrays) y dimensiones. Se definen en el


directorio res/values/ del proyecto. Son muy útiles para internacionalizar la
aplicación.

 Las imágenes e iconos se almacenan en el directorio res/drawable.

 Las animaciones se guardan en el directorio res/anime/.

 Los ficheros XML se encuentran en el directorio res/xml/.

 Diseños (Layout) de las pantallas o de elementos visibles (como una opción de


un ListView) de la aplicación. Se definen en el directorio res/layout/ del
proyecto.

 Definición de menús principales y contextuales en el directorio res/menu/.

En el curso ya hemos usado recursos en las aplicaciones anteriores. En este apartado


vamos a explicar en detalle cómo se incorporan éstos dentro de las mismas. Sobre todo,
centraremos la explicación en la Cadenas (String), ya que es la funcionalidad más compleja y
es muy útil para el programador.

4.3.2 Crear literales en ficheros de recursos con Eclipse

Usando Eclipse se pueden definir fácilmente atributos en los ficheros de Recursos de


los proyectos Android, como cadenas, colores, etcétera.

Para ello, hay que hacer doble clic en el fichero de recursos que queramos editar; por
ejemplo, en el fichero res/values/string.xml y hacer clic en el botón "Add":

208
Después, basta con seleccionar el tipo de recursos que necesitamos dar de alta y
rellenar el nombre y el valor que debe contener:

4.3.3 Recursos de tipo Cadena (String Resources)

Un recurso de tipo Cadena (String) permite definir cadenas de texto para usarlas en la
aplicación Android: Incluso podemos cambiar su estilo y formato. Hay tres recursos de tipo
Cadena:

 String: recurso que incluye una única cadena de texto.

 String Array: recurso que incluye una matriz de cadenas de texto.

 Quantity Strings (Plurals): recurso que incluye dos cadenas según una
cantidad sea singular o plural.

209
Más adelante veremos cómo se puede modificar el estilo y formatear el contenido de
todos estos recursos de tipo cadena.

4.3.4.1 Cadena (String)

Se trata de una Cadena de texto simple que se puede utilizar dentro del código fuente
Java o en otro fichero (layout) de diseño XML, como los que se usan para definir las pantallas.

Para definir recursos de cadenas de texto debemos usar la siguiente sintaxis:

<string name="nombre_cadena">texto de la cadena </string>

Si abrimos el fichero res/values/strings.xml del Ejemplo 3, veremos que en él


aparece la cadena:

<string name="etiqueta1">Este texto se carga de res/layout/main.xml</string>

El atributo name de <string> se usa para identificar la cadena unívocamente. Por lo


tanto, es el identificador (ID) de esta cadena.

Dentro de la etiqueta <string> incluimos el literal al que hace referencia.

Para hacer referencia a este recurso de cadena debemos escribir lo siguiente:

 En el código fuente Java: R.string.etiqueta1


 En un fichero XML (de diseño o de recursos): @string/etiqueta1

En el Ejemplo 3 de esta Unidad puedes ver cómo se hace referencia a esta etiqueta
desde el fichero XML que define el diseño de la pantalla principal main.xml:

<TextView android:layout_width="fill_parent"

android:layout_height="wrap_content"
android:id="@+id/etiqueta1"

android:text="@string/etiqueta1"
android:layout_margin="1dip"/>

También podemos usar el recurso de tipo cadena en el código fuente Java de la


aplicación:

etiqueta2.setText(getString(R.string.etiqueta2));

210
4.3.4.2 Matriz de cadenas (String Array)

Es un recurso similar al de tipo Cadena, pero en este caso almacenamos varias


cadenas en una matriz. Se puede utilizar, igualmente, dentro del código fuente Java o en otro
fichero (layout) de diseño XML, como los que usan para definir las pantallas.

Para definir recursos de tipo matriz debemos usar la siguiente sintaxis:

 <string-array name="nombre_matriz">
<item>texto elemento 1</item>
<item>texto elemento 2</item>
...
</string-array>

Si abrimos el fichero res/values/matrices.xml del Ejemplo 3, veremos que aparece la


matriz:

<string-array name="horoscopo">

<item>Cancer</item>

<item>Capricornio</item>

<item>Aries</item>

<item>Leo</item>

<item>Libra</item>

</string-array>

El atributo name de <string-array> se usa para identificar la matriz unívocamente. Por


lo tanto, es el identificador (ID) de esta matriz.

Dentro de la etiqueta <string-array> incluimos varias etiquetas <item> con los


literales que forman los elementos de la matriz.

Para hacer referencia a este recurso de cadena en el código fuente Java debemos
escribir R.array.horoscopo.

En el Ejemplo 3 de esta Unidad puedes ver cómo se usa este recurso de tipo matriz
para cargar las opciones de una caja de selección:

Spinner s = (Spinner) findViewById(R.id.spinner);

s.setPrompt("Elige el horóscopo");

ArrayAdapter<CharSequence> adapter =
ArrayAdapter.createFromResource(this, R.array.horoscopo,

android.R.layout.simple_spinner_item);

211
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
;
s.setAdapter(adapter);

Para obtener los elementos de la matriz desde el código fuente en Java podemos usar
la función getStringArray:

 Resources res = getResources();


String[] horoscopo = res.getStringArray(R.array.horoscopo);

4.3.4.3 Cadenas de cantidad (Quantity Strings)

Cada idioma tiene diferentes reglas de concordancia gramatical con las cantidades. En
español por ejemplo, escribimos "un libro" y, para cualquier otra cantidad, escribimos "3
libros". Esta distinción entre el singular y el plural es muy común en los idiomas. Incluso hay
idiomas que hacen distinciones más sutiles. Android incluye un conjunto completo y distingue
entre cero (zero), uno (one), dos (two), pocos (few), muchos (many) y otros (other).

Las reglas de concordancia pueden ser muy complejas dependiendo del idioma, por lo
que Android dispone del método getQuantityString(), que selecciona el recurso apropiado
según la cantidad numérica que estemos tratando.

Para definir recursos de tipo cantidad debemos usar la siguiente sintaxis:

 <plurals name="nombre_plural">
<item quantity=["zero" | "one" | "two" | "few" | "many" | "other"]>
texto del literal</item>
</plurals>

Si abrimos el fichero res/values/strings.xml del Ejemplo 3, veremos que aparece el


elemento cantidad:

<plurals name="numeroDeContactos">

<item quantity="one">Se ha encontrado un contacto.</item>

<item quantity="other">Se han encontrado %d contactos.</item>


</plurals>

El atributo name de <plurals> se usa para identificar la cadena de cantidad


unívocamente; por lo tanto, es su identificador (ID).

Dentro de la etiqueta <plurals> incluimos varias etiquetas <item quantity> con los
literales que forman las distintas opciones de plurales. Podemos escribir los siguientes tipos
de plurales en el atributo quantity:

212
Valor Descripción

Cuando el idioma requiere un tratamiento especial del número 0 (como el


zero
árabe).

Cuando el idioma requiere un tratamiento especial del número 1 (como el


one
español, el inglés, etcétera).

two Cuando el idioma requiere un tratamiento especial del número 2 (como el galés).

Cuando el idioma requiere un tratamiento especial de pequeñas cantidades


few
(como los números 2, 3 y 4 en checo).

Cuando el idioma requiere un tratamiento especial de los números grandes


many
(como los números que terminan en 11-99 del maltés).

other Valor por defecto del resto de cantidades.

Para hacer referencia a este recurso de cadena en el código fuente Java debemos
escribir R.plurals.numeroDeContactos.

En el Ejemplo 3 de esta Unidad puedes ver cómo se usa este recurso de tipo matriz
para cambiar el literal en función del número que escribe el usuario en una caja de texto:

//Obtenemos los recursos de la aplicación

Resources res = getResources();

int total = Integer.parseInt(s.toString());

// Cambiamos el texto de la etiqueta plurales en función del contador

String contactosStr = res.getQuantityString(R.plurals.numeroDeContactos,


total, total);
txtPlurales.setText(contactosStr);

Hemos usado la función getQuantityString() para obtener el literal que corresponde a


la cantidad total y, además, volvemos a pasar como tercer parámetro total, para que lo use
para formatear el literal en caso necesario. A continuación, veremos cómo formatear las
cadenas de recursos,

4.3.4 Formatear y cambiar el estilo de las cadenas de recursos

4.3.4.1 Escapando comillas simples y dobles

A la hora de escribir comillas simples o dobles dentro del literal de una cadena de
texto debemos "escaparlas", para que Android no las interprete como parte del fichero XML y
muestre errores de sintaxis.

213
Es necesario incluir siempre las comillas simples dentro de las dobles o usar el
carácter "\" para escaparlas. Además, no se permite incluir entidades de HTML para los
caracteres singulares como "&aacute;".

A continuación, mostramos algunos ejemplos que funcionan y otros que no:

<string name="buen_ejemplo_1">"Esto 'está' bien"</string>


<string name="buen_ejemplo_2"> Esto \'está\' bien </string>
<string name="ejemplo_incorrecto_1">Esto 'no' funciona</string>
<string name="ejemplo_incorrecto_2">Esto no funcionar&aacute;</string>

4.3.4.2 Formatear cadenas de texto

Podemos también formatear cadenas usando la función String.format(String,


Object...) incluyendo en ésta los argumentos que sean necesarios para formar el literal. Por
ejemplo, la siguiente cadena de recurso con formato

<string name="FormatoCadena">¡Hola %1$s! Tienes %2$d mensajes nuevos</string>

tiene dos argumentos: %1$s, que es una cadena y %2$d, que es un número decimal.
En el Ejemplo 3 de esta Unidad hemos usado así esta cadena con formato para formatear un
literal:

String strFormat=getString(R.string.FormatoCadena);

String texto=String.format(strFormat, "Usuario", 4);


etiqueta3.setText(texto);

 
4.3.4.3 Cambio de estilo

Es posible definir una cadena de recurso que contenga cambios de estilo HTML en el
texto. Así, en el Ejemplo 3 de esta Unidad definimos:

<string name="RecursoEstilo">Esto es un <u>ejemplo</u> de <i>Recurso</i> con


<b>Diseño</b> </string>

Las etiquetas HTML que se pueden usar son:

 <b> para texto en negrita.

 <i> para texto en cursiva.

 <u> para subrayar un texto.

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Recursos) de la Unidad 4. Estudia


el código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior.

214
Si ejecutamos la aplicación en el emulador, veremos que aparece la siguiente pantalla:

Para completar este ejemplo hemos incluido también un recurso de tipo imagen en el
directorio drawable y hemos definido un color para cambiar el aspecto de una de las
etiquetas.

Importante: Al definir recursos de tipo cadena hay que tener cuidado en no escribir caracteres
que Android no sepa interpretar. Cuando esto ocurra Eclipse mostrará el siguiente mensaje de
error y no permitirá compilar el proyecto:

4.4 ACCESO A INTERNET CON ANDROID

 
Android incluye la biblioteca Apache de cliente HTTP (Apache HttpClient library) que
permite a las aplicaciones conectar con servidores Web de Internet.

215
Android también permite usar la librería estándar Red de Java Java Networking API
(paquete java.net), aunque, si usamos este paquete java.net, Android utiliza internamente la
librería de Apache.

A partir de Android 2.2 también se puede utilizar la clase AndroidHttpClient. Para


construir un objeto a partir de esta clase hay que usar el constructor newInstance(), que
permite especificar el nombre de navegador (agente) que usa para conectar a una página de
Internet. La clase AndroidHttpClient incluye el protocolo seguro SSL y métodos GZIP para
comprimir y descomprimir los datos recibidos.

Para que una aplicación Android acceda a Internet, es necesario declararlo en el fichero
AndroidManifest.xml, que requiere el permiso "android.permission.INTERNET".

4.4.1 Ejemplo de conexión a Internet

A continuación, vamos a mostrar mediante un ejemplo cómo conectar una aplicación


Android a Internet para descargar la página de un servidor Web.

Desde Eclipse puedes abrir el proyecto Ejemplo 4 (Acceso Internet) de la Unidad 4. Estudia el
código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior.

Si ejecutamos la aplicación en el emulador y pulsamos el botón "Descargar página


web", aparece la siguiente pantalla:

En este ejemplo hemos usado también preferencias en la aplicación para guardar la


dirección de la página Web que el usuario escribe en esta pantalla. Hemos implementado esta
funcionalidad con las siguientes sentencias:

216
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

// Cargamos las preferencias y mostramos la última dirección en el EditText

cargaPreferencias();

pagWeb.setText(ultimaUrl);

...
}

// Carga las preferencias de la aplicación

private void cargaPreferencias() {

SharedPreferences preferences = getSharedPreferences(PREFERENCIAS,


Activity.MODE_PRIVATE);

// Obtenemos la última dirección sobre la que se ha descargado

ultimaUrl = preferences.getString(URL, "http://www.google.es");

@Override

protected void onPause() {

super.onPause();

// En el caso de que la aplicación se pause, guardamos las preferencias

SharedPreferences preferences = getSharedPreferences(PREFERENCIAS,


Activity.MODE_PRIVATE);

Editor preferenceEditor = preferences.edit();

preferenceEditor.putString(URL, pagWeb.getText().toString());

// No hay que olvidar nunca hacer el commit

preferenceEditor.commit();

Fíjate que en el código anterior definimos el evento onPause() de la Actividad, para


que se guarden las preferencias en el caso de que el ciclo de vida pase por este estado. Así,
nos aseguramos de que se guardará siempre la dirección de la página Web.

Para descargar la página de Internet hemos usado una tarea asíncrona que hemos
estudiado en la Unidad 3. En este caso hemos definido el método onProgressUpdate() para
ir actualizando la pantalla según se va descargando la página de Internet:

217
// Método onClick del botón Descargar

public void onClickHandler(View view) {

switch (view.getId()) {

case R.id.descargPagWeb:

ResultadoLabel.setText("");

if (! hayConexionInternet()) ResultadoLabel.setText("ERROR: no hay


conexión a Internet");

else {

cargando.setVisibility(View.VISIBLE);

// Iniciamos la tarea de descarga

TareaDescargaPaginaWeb tarea = new TareaDescargaPaginaWeb();

tarea.execute(pagWeb.getText().toString());

break;

} // end onClick

// Clase que descarga una página de Internet como una tarea asíncrona.

// Es decir, podemos seguir usando la interfaz de usuario.

private class TareaDescargaPaginaWeb extends


AsyncTask<String, String, String> {

// Método que se ejecuta en segundo plano

protected String doInBackground(String... urls) {

try {

HttpClient client = new DefaultHttpClient();

HttpGet request = new HttpGet(urls[0]);

HttpResponse respuesta = client.execute(request);

// Obtenemos la respuesta

BufferedReader rd = new BufferedReader(new InputStreamReader(

respuesta.getEntity().getContent()));

String linea = "";

String resultado = "";

// Mientras podamos leer una línea de la página Web

while ((linea = rd.readLine()) != null) {


218
resultado+=linea;

if (resultado.length()>1024) {

publishProgress(resultado);

resultado="";

} // end while

catch (Exception e) {

System.out.println("Error al descargar la página.");

return "ERROR al descargar la página: "+e.getMessage();

return null;

/** Actualiza la etiqueta al ir descargando la página */

protected void onProgressUpdate(String... values) {

ResultadoLabel.append(values[0]);

/** Cuando la tarea ha acabado, se invoca automáticamente este método */

protected void onPostExecute(String resultado) {

if (resultado !=null) ResultadoLabel.append(resultado);

cargando.setVisibility(View.INVISIBLE);

} // end onPostExecute

Para descargar la página de Internet hemos usado la clase DefaultHttpClient y


llamado al método HttpGet(). Después, usamos la clase InputStreamReader para leer los
datos como si se tratara de un fichero más.

Es evidente que un dispositivo Android no tiene siempre conexión a Internet. Por esto,
es bueno comprobar que tiene acceso a Internet a través del siguiente código:

public boolean hayConexionInternet() {

// Comprobamos si hay conexión a Internet

ConnectivityManager cm = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);

219
NetworkInfo networkInfo = cm.getActiveNetworkInfo();

// Si la variable networkInfo<> null, entonces tenemos conexión a Internet

if (networkInfo != null && networkInfo.isConnected()) {

return true;

return false;

En el código anterior usamos la clase ConnectivityManager para conocer el estado


de la conexión a Internet. Además, para poder hacerlo, hay que declarar en el fichero Manifest
de la aplicación el permiso correspondiente:

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

 
4.4.2 Conexión a través de proxy

Si estás programando aplicaciones Android y te conectas a Internet a través de un


Proxy, es interesante configurar el emulador de Android para que acceda a Internet a través de
ese Proxy.

Para configurarlo, haz clic en el icono "Ajustes" y establece las siguientes opciones:

Conexiones inalámbricas->Redes móviles ->APN->Telkila-> Proxy/Puerto

220
4.4 QUÉ SON JSON (JavaScript Object Notation?

JSON es el acrónimo en inglés de JavaScript Object Notation; es un formato ligero


para el intercambio de datos en aplicaciones Web. JSON tiene la ventaja de que no requiere el
uso de XML.

La simplicidad de JSON ha provocado la generalización de su uso; es una buena


alternativa, especialmente, al formato XML.

JSON es más fácil de utilizar como formato de intercambio de datos que XML, porque
es mucho más sencillo escribir un analizador semántico de JSON.

El formato JSON se basa en los tipos de datos y sintaxis del lenguaje JavaScript. Es
compatible con cadenas, números, boolean y valores nulos. También se pueden combinar
valores en matrices y objetos.

Los objetos en JSON son simplemente conjuntos desordenados de parejas


nombre/valor, donde el nombre es siempre una cadena y el valor es cualquier tipo de datos
válido para JSON, incluso otro objeto. A continuación, se muestra un ejemplo simple de
definición de los datos de un producto usando JSON:

{
"producto": {
"nombre": "Widget",
"compania": "ACME, Inc",
"numero": "7402-129",
"precios": [
{ "cantMin": 1, "precio": 12.49 },
{ "cantMin": 10, "precio": 9.99 },
{ "cantMin": 50, "precio": 7.99 }
]
}
}

221
Puedes ver más ejemplos en el siguiente enlace: json.org/example.html.

Twitter es una fuente muy grande que usa el formato JSON. En el Ejemplo 5 de esta
Unida vamos a cargar el Twitter de Mentor en una aplicación Android. La dirección es la
siguiente:

http://twitter.com/statuses/user_timeline/MinisterioEduc.json

Fíjate en que la cuenta de la que obtenemos los datos se llama #MinisterioEduc.

4.5.1 Cómo usar JSON en nuestras aplicaciones a Android

Android incluye la biblioteca JSON, que permite tratar este formato de dato. Las
clases más importantes de este paquete son:

 JSONArray: permite cargar y tratar una matriz de elementos en formato JSON.

 JSONObject: permite tratar un único elemento en formato JSON.

Desde Eclipse puedes abrir el proyecto Ejemplo 5 (JSON) de la Unidad 4. Estudia el código
fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior.

Si ejecutamos la aplicación en el emulador, veremos la siguiente pantalla:

222
Vamos a mostrar con un ejemplo práctico cómo usar una fuente de datos en formato
JSON en una aplicación Android. Si abrimos el fichero de código fuente del Ejemplo 5,
veremos las siguientes sentencias:

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

// Definimos la matriz que vamos a usar de adaptador en la ListActivity

ArrayList<String> datosAdaptador = new ArrayList<String>();

// Leemos los datos de la cuenta de Twitter

String datosCuentaTwitter = leeCuentaTwitter();

// Leemos el contenido interno del fichero obtenido

if (datosCuentaTwitter!= null)

try {

// Usamos una matriz de JSON

JSONArray matrizJSON = new JSONArray(datosCuentaTwitter);

TextView nNoticias = (TextView) findViewById(R.id.nNoticias);

nNoticias.setText("Número de noticias: " + matrizJSON.length());

// Recorremos ahora todos los elementos de la matriz

for (int i = 0; i < matrizJSON.length(); i++) {

// Leemos cada objeto y lo añadimos a la matriz de datos

JSONObject jsonObjeto = matrizJSON.getJSONObject(i);

datosAdaptador.add(jsonObjeto.getString("text"));

} // end for

} catch (Exception e) {

e.printStackTrace();

// Indicamos el adaptador de la ListActivity

setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, datosAdaptador));
}

// Método que lee las noticias de una cuenta de Twitter

public String leeCuentaTwitter() {

223
// String que permite ir añadiendo líneas

StringBuilder builder = new StringBuilder();

// Usamos un cliente HTTP para obtener el fichero JSON

HttpClient cliente = new DefaultHttpClient();

// Definimos un objeto para hacer una petición HTTP GET

HttpGet httpGet = new HttpGet(


"http://twitter.com/statuses/user_timeline/MinisterioEduc.json");

try {

// Ejecutamos la petición GET

HttpResponse respuesta = cliente.execute(httpGet);

// Obtenemos la respuesta del servidor

StatusLine statusLine = respuesta.getStatusLine();

// Y su código de estado asociado

int statusCode = statusLine.getStatusCode();

// 200 el servidor responde con datos

if (statusCode == 200) {

// Obtenemos un puntero a la respuesta

HttpEntity entity = respuesta.getEntity();

// Obtenemos el contenido de la respuesta

InputStream contenido = entity.getContent();

// Leemos el contenido como si fuera un fichero

BufferedReader reader = new BufferedReader(new


InputStreamReader(contenido));

String line;

while ((line = reader.readLine()) != null) {

builder.append(line);

} else {

Log.e(JSONActivity.class.toString(), "No se puede descargar el


fichero");

} catch (ClientProtocolException e) {

e.printStackTrace();

} catch (IOException e) {

224
e.printStackTrace();

return builder.toString();

} // end leeCuentaTwitter

En el código anterior hemos usado la clase DefaultHttpClient para descargar los


datos en formato JSON. Esta clase ya la hemos visto con anterioridad en otros ejemplos.

Una vez hemos obtenido los datos JSON del servidor, únicamente hay que crear un
objeto de la clase JSONArray cuyo parámetro sean los datos anteriores. Después, sólo hay
que recorrer esta matriz de elementos de tipo JSONObject y usar el método getString() para
obtener el contenido del mismo.

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

4.5.2 Cómo escribir ficheros en formato JSON

Escribir datos en formato JSON es muy sencillo. Basta con crear un objeto del tipo
JSONObject o del tipo JSONArray y utilizar el método toString() para transformar este objeto
en datos JSON.

Veamos un ejemplo práctico:

public void escribirJSON() {

JSONObject objeto = new JSONObject();

try {

objeto.put("nombre", "Pedro del Cielo Lindo");

objeto.put("edad", new Integer(33));

objeto.put("altura", new Double(1,77));

objeto.put("ciudad", "Avila");

} catch (JSONException e) {

e.printStackTrace();

System.out.println(objeto);

225
 En Android existen tres formas de almacenar información para usarla en las
aplicaciones:

o Preferencias de la aplicación (sólo lectura).

o Ficheros locales en el sistema de archivos del sistema operativo.

o Base de datos SQLite.

 Android también permite manipular ficheros de forma similar a como se hacen


en Java.

 En Android, por defecto, los ficheros son privados y únicamente puede acceder
a ellos la aplicación que los crea.

 Podemos leer y escribir ficheros localizados en:

o La memoria interna del dispositivo: como fichero (lectura y escritura) o


como recurso de la aplicación (sólo lectura).

o La tarjeta SD externa, si existe, también denominada almacenamiento


externo.

 La clase File de Android se usa para identificar y gestionar archivos y directorios


del sistema operativo.

 Es necesario “manifestar” que una aplicación necesita el permiso de escritura


en la memoria externa en su arhivo Manifest.xml.

 Las preferencias de una aplicación son datos que una aplicación guarda y
recupera para personalizar la experiencia del usuario.

 Para gestionar las preferencias de una aplicación hay que usar la clase
SharedPrefences, donde cada preferencia se almacena siguiendo la estructura
clave-valor.

226
 Las preferencias se almacenan en formato XML en un fichero en la memoria del
dispositivo.

 Android permite simplificar la gestión de las preferencias de una aplicación


mediante el uso de una pantalla de opciones dividida en categorías que tienen
opciones de diversos tipos.

 Los Recursos de Android son archivos contenidos en el proyecto de tipo audio,


vídeo, imágenes, texto, XML, etcétera, que se pueden usar en la aplicación. Estos
recursos son de sólo lectura..

 Un recurso de tipo Cadena (String) permite definir cadenas de texto para usarlas
en la aplicación Android; incluso podemos cambiar su estilo y formato. Son muy
útiles para el programador, pues facilita la Internacionalizar las aplicaciones.

 Android incluye las bibliotecas necesarias que permiten a las aplicaciones


conectar con servidores Web de Internet.

 Es necesario “manifestar” que una aplicación necesita el permiso de acceso a


Internet en su arhivo Manifest.xml.

 JSON es el acrónimo en inglés de JavaScript Object Notation; es un formato ligero


para el intercambio de datos en aplicaciones Web. JSON tiene la ventaja de que
no requiere el uso de XML.

 Android incluye la biblioteca JSON que permite leer y escribir este formato de
dato.

227
INTENTS EN ANDROID

ÍNDICE
5.1 INTENTS EN ANDROID.......................................................... 231
5.1.1  Introducción ...........................................................................231 
5.1.2  Intenciones (Intents) ..............................................................231 
5.1.3  Ficheros Manifest ...................................................................232 
5.1.4  Declarar capacidades de los componentes de las aplicaciones233 
5.1.5  Uso de intenciones .................................................................234 
5.1.6  Arranque explícito de una actividad.......................................234 
5.1.7  Arranque implícito de una actividad ......................................235 
5.1.7.1 Ejecutar subactividades .....................................235
5.1.8  Filtros de intenciones .............................................................239 
5.1.9  Resolución de intenciones implícitas......................................240 
5.1.10   Uso de intenciones para extender aplicaciones ....................241 

5.2 USO DE INTENTS ................................................................... 243


5.2.1  Uso de Intents ........................................................................243 
5.2.2  Invocación Explícita ................................................................243 
5.2.3  Invocación Implícita................................................................249 
5.2.4  Registro Acción para Invocación Implícita..............................252 
5.2.5  Detectar Acciones de Intents..................................................255 

5.3 PERMISOS Y SEGURIDAD EN ANDROID ........................... 256


5.3.1  Arquitectura de seguridad de Android ...................................256 
5.3.2  Firma de aplicación.................................................................256 
5.3.3  ID de usuario y Acceso a ficheros ...........................................257 
5.3.4  Permisos de aplicaciones........................................................257 
5.3.5  Autoprotección de aplicaciones Android ...............................261 
5.3.6  Asignar permisos de componentes internos de la aplicación 262 
5.3.7  Cómo obtienes permisos estas aplicaciones ..........................263 
5.3.8  Notas sobre seguridad en Android .........................................264 

5.4 Tab.Layout .............................................................................. 264


5.4.1  Pantallas con pestañas con Tab Layout ..................................264 
2
Intents

5.1 INTENTS EN ANDROID

5.1.1 Introducción

En esta Unidad vamos a explicar cómo usar Intenciones (Intents) en Android para
arrancar Actividades o servicios.

Además, veremos cómo definir los permisos de una Aplicación de Android.

Finalmente, diseñaremos pantallas de aplicaciones con pestañas con el


componente Tab Layout.

5.1.2 Intenciones (Intents)

Las Intenciones (Intents) permiten a las aplicaciones de Android expresar la intención


de que se desea ejecutar una acción sobre unos datos usando algún componente de ésta o
de otra aplicación. Las intenciones permiten interconectar componentes de la misma o de
distintas aplicaciones mediante mensajes.

De los cuatro componentes de Android, las Actividades, los Servicios y los Receptores
de mensajes de difusión se activan con un mensaje asíncrono que se denomina Intención. Los
Proveedores de contenidos quedan excluidos.

Para crear una Intención hay que usar el objeto Intent de Android.

Las intenciones se utilizan para arrancar componentes de dos formas:

 Explícita: invocando la clase Java del componente que queremos ejecutar.


Normalmente, esto se usa para invocar componentes de una misma
aplicación.

 Implícita: invocando la acción y los datos sobre los que aplicar dicha acción.
Android selecciona, en tiempo de ejecución, la actividad receptora que cumple
mejor con la acción y los datos solicitados.

Para las Actividades y Servicios, una intención define la acción que queremos realizar
(por ejemplo, "ver" o "enviar" algo) y puede especificar el identificador URI de los datos que
va a utilizar esa acción. Por ejemplo, una intención podría hacer una petición para arrancar una
actividad que muestre una imagen o abra una página Web.

En algunos casos, se puede iniciar una subactividad para recibir un resultado, en


cuyo caso esta subactividad devuelve el resultado en otra nueva intención. Por ejemplo, se
puede arrancar un Intent para que el usuario elija un contacto del teléfono y lo devuelta a la
Actividad principal (esta intención de respuesta se devuelve también como un identificador
URI que apunta al contacto seleccionado).

231
Las Intenciones son mensajes asíncronos entre componentes de aplicaciones que se usan
para realizar acciones e intercambiar datos, tanto en la petición como en la respuesta,.

De esta manera el usuario tiene la sensación de estar usando una única aplicación cuando, en
realidad, son componentes de varias.

A continuación, vemos un esquema que muestra cómo funciona un Intent:

Las Intenciones de difusión (en inglés Broadcast Intents) se envían a múltiples


destinatarios del sistema y pueden ser procesadas por cualquier receptor de mensajes de
difusión (Broadcast Receiver). Por ejemplo, el sistema genera este tipo de intenciones de
difusión para anunciar situaciones diversas, como que la ’batería del teléfono se agota’, que
llega una llamada de teléfono o un mensaje SMS, etcétera. Cualquier componente puede
registrar un receptor de mensajes de difusión para que esté informado de estos eventos.

El otro tipo de componente de Android, el Proveedor de contenido (Content Provider),


no se activa mediante intenciones, sino mediante una solicitud de un ContentResolver. En la
Unidad 7 de este curso veremos cómo funciona este procedimiento.

Uno de los usos principales de las intenciones es arrancar, parar y cambiar entre las
actividades y los servicios de una aplicación.

5.1.3 Ficheros Manifest

Para que Android pueda iniciar un componente de una aplicación, el sistema debe
conocer que existe este componente. Ya hemos visto que, para ello, se declaran los
componentes de una aplicación en el fichero AndroidManifest.xml. Este fichero se encuentra
en el directorio raíz del proyecto Android.
232
Intents

Por ejemplo, para declarar una Actividad debemos escribir:

<?xml version="1.0" encoding="utf-8"?>


<manifest ... >
<application android:icon="@drawable/app_icon.png" ... >
<activity android:name="com.ejemplo.projecto.EjemploActividad"
android:label="@string/ejemplo_label" ... >
</activity>
...
</application>
</manifest>

En el elemento <activity> usamos el atributo android:name para especificar el


nombre completo de clase de la actividad y el atributo android:label especifica la cadena de
texto visible que se muestra al usuario cuando la utiliza.

Hay que declarar todos los componentes de la aplicación de esta forma usando las
siguientes etiquetas:

 <activity>: Actividades

 <service>: Servicios

 <receiver>: Receptores de mensajes de difusión (Broadcast receivers)

 <provider>: Proveedores de contenido (Content providers)

Es obligatorio incluir en este fichero "AndroidManifest" todas las actividades, los


servicios y los proveedores de contenido, ya que, si no lo hacemos, no son visibles para el
sistema y, en consecuencia, no se pueden ejecutar.

Los receptores de mensajes de difusión pueden declarase en este fichero


"AndroidManifest" o bien podemos crearlos de forma dinámica en el código fuente Java.

5.1.4 Declarar capacidades de los componentes de las aplicaciones

Tal y como se ha mencionado anteriormente, si queremos iniciar los componentes de


una aplicación hay que utilizar una intención para ejecutar actividades, servicios y receptores
de mensajes de difusión.

Se puede hacer de forma explícita indicando el nombre de la clase del componente


destino.

Sin embargo, el potencial de uso de las intenciones radica en el concepto de Acción


mediante las invocaciones implícitas. En una Acción el programador sólo tiene que describir
el tipo de acción que desea realizar y, opcionalmente, los datos sobre los que desea realizar
esa acción. Si hay varios componentes que pueden llevar a cabo esta acción de la intención,
entonces el usuario del dispositivo puede seleccionar cuál aplicar.

233
Android identifica qué componentes pueden responder a una Acción determinada
buscándola en los filtros de intención (intent filters) que se declaran en el archivo
"AndroidManifest" de todas las aplicaciones del dispositivo.

Cuando se incluye un componente en el fichero "AndroidManifiest" de una aplicación,


se pueden especificar filtros de intención que declaren la capacidad de este componente para
que pueda responder a las Acciones de otras aplicaciones.

Para incluir un filtro de intención de un componente, hay que añadir el elemento


<intent-filter> dentro de la declaración del componente.

Veamos un ejemplo: una aplicación de correo electrónico con una Actividad que
componga un nuevo correo electrónico puede declarar el filtro de intención ACTION_SEND
que responda a la intención "send" (enviar) que, lógicamente, envía un mensaje. Una actividad
de otra aplicación puede entonces simplemente iniciar una intención con la acción
(ACTION_SEND) que provocará que Android busque la actividad o actividades que
concuerdan con esta acción en iniciar la intención "send". Es decir, el programador no tiene
que conocer el nombre de la intención, únicamente, debe invocar su acción. Más adelante
veremos otro ejemplo.

5.1.5 Uso de intenciones

Para arrancar una Actividad sin esperar una respuesta de la subactividad iniciada,
debemos usar la siguiente función:

startActivity(anIntent);

Para arrancar una Actividad y esperar una respuesta de la subactividad iniciada,


debemos usar la siguiente función:

startActivityForResult(anIntent, INTENT_COD);

Ambos métodos se pueden usar tanto en las invocaciones explícitas como implícitas.
La diferencia radica en que el primero inicia la subactividad y no espera respuesta de ésta; el
segundo método espera recibir una respuesta de la ejecución de la subactividad.

5.1.6 Arranque explícito de una actividad

Para iniciar explícitamente una Actividad hay que especificar en la intención el


contexto de la aplicación que la invoca y la clase de la actividad que se quiere arrancar:

Intent intent = new Intent(MiActividad.this, MiOtraActividad.class);

startActivity(intent);
234
Intents

En la Unidad 4, en el apartado de pantallas de preferencias, ya hemos usado esta


manera de invocar una Intención explícitamente.

5.1.7 Arranque implícito de una actividad

Una intención implícita especifica la acción requerida y los datos sobre los que actúa.
Es importante insistir en que las aplicaciones de Android deben publicar las acciones que
ofrecen.

Por ejemplo, la aplicación dialer de marcado de llamadas de teléfono de Android


ofrece la acción Intent.ACTION_DIAL que se invoca así:

if (...) {

Intent intent = new Intent(Intent.ACTION DIAL,


Uri.parse("tel:91-6666"));

startActivity(intent);

El código anterior inicia una llamada de teléfono al número indicado como parámetro
en la intención. Como hemos usado el método startActivity(), no trataremos la respuesta de
su ejecución.

5.1.7.1 Ejecutar subactividades

Se puede arrancar una actividad como subactividad de otra actividad principal.


Cuando termina esta subactividad, se invoca al método onActivityResult de la actividad
principal desde la que se inició.

Cualquier actividad registrada en el fichero "AndroidManifiest" puede ser invocada


como subactividad. Para arrancar una subactividad hay que usar el método
startActivityForResult(), pasando como parámetro la intención y un código de petición, que
se utiliza para identificar a qué subactividad corresponde la respuesta.

Veamos un ejemplo sencillo que inicia una subactividad desde una actividad principal:

private static final int SUBACTIVIDAD = 1;

Intent intent = new Intent(this, MiOtraActividad.class);

// Añadimos un dato extra en la intención que pasa a la subactividad


intent.putExtra("nombre", "Nadie");

startActivityForResult(intent, SUBACTIVIDAD);
235
En la Intención hemos incluido una colección de datos Extras con información
adicional. Para ello, usamos el método putExtra() de la clase Intent para incorporar la
información adicional.

Veamos ahora un ejemplo del código de una subactividad:

public void onCreate(Bundle bundle) {

super.onCreate(bundle);

setContentView(R.layout.subactividad);

// Obtenemos el intent que invoca esta actividad

Intent intent = getIntent();

// Obtenemos el contenido de los datos del intent

Uri datos = intent.getData();

// Obtenemos el contenidos de la variable extras

Bundle extra = intent.getExtras();

// Usamos los datos que hemos obtenido

if (extra == null) return;

// Leemos los contenidos de los datos de invocación y variables extra

String valor1 = extra.getString("nombre");

// Formamos la URL que debemos cargar

URL url = new URL(datos.getScheme(), datos.getHost(),


datos.getPath());

Button okButton = (Button) findViewById(R.id.ok_button);

okButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View view) {

Uri dato = Uri.parse("content://dato/" + id_dato_seleccionado);

Intent resultado = new Intent(null, dato);

resultado.putExtra(TODO_CORRECTO, correcto);

resultado.putExtra(DATO_SELECCIONADO, datoSeleccionado);

setResult(RESULT_OK, resultado);

finish();

});

236
Intents

Button cancelarButton = (Button) findViewById(R.id.cancel_button);

cancelarButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View view) {

setResult(RESULT_CANCELED, null);

finish();

});

A continuación, vamos a explicar los métodos más importantes:

 getIntent(): obtiene la intención que ha invocado la actividad. Normalmente este


método se usa en el constructor de la Actividad con el método onCreate(). Una
vez obtenida la intención, podemos conseguir más información contenida en ella
mediante los métodos:

o getData(): obtiene el dato contenido en la intención en formato Uri. Para


leer los contenidos internos de este dato podemos usar los métodos
getScheme() (esquema del dato), getUserInfo() (información de usuario),
getHost()(dirección del servidor), etcétera.

o getExtras(): obtiene los datos extras contenidos en la intención en


formato Bundle. Para leer los campos de este tipo de dato podemos usar
los métodos getString("nombre"), getInteger("nombre"), etcétera,

 setResult(): acepta dos parámetros: un código de respuesta y una intención con


los datos devueltos.

o Código de resultado: Activity.RESULT_OK,


Activity.RESULT_CANCELED o cualquier número entero.

o Intención con datos devueltos: identificador URI a los contenidos


devueltos (un contacto, un número de teléfono, una imagen, etcétera) y
una colección de datos Extras con información adicional. Para ello
creamos una clase Intent y usamos el método putExtra() para incorporar
la información adicional.

 finish(): devuelve a la actividad principal el resultado de la ejecución de la


subactividad. En el código Java de la subactividad se debe invocar siempre el
237
método setResult() antes que el método finish(), para devolver los resultados de la
ejecución de ésta.

Para recuperar los resultados devueltos a la actividad principal debemos definir el


método onActivityResult(). Veamos un ejemplo del código de una actividad principal que
trata la respuesta de una subactividad:

private static final int SUB_ACTIVIDAD_UNA = 1;

private static final int SUB_ACTIVIDAD_DOS = 2;

@Override

public void onActivityResult(int requestCode, int resultCode, Intent data) {

super.onActivityResult(requestCode, resultCode, data);

switch(requestCode) {

case (SUB_ACTIVIDAD_UNA) : {

if (resultCode == Activity.RESULT_OK) {

Uri dato = data.getData();

// Obtenemos el campo TODO_CORRECTO con el valor por defecto false

boolean correcto = dato.getBooleanExtra(TODO_CORRECTO,false);

String datoSeleccionado = dato.getStringExtra(DATO_SELECCIONADO);

break;

case (SUB_ACTIVIDAD_DOS) : {

if (resultCode == Activity.RESULT_OK) {

// Tratar el resultado de la Subactividad 2

break;

El método onActivityResult() recibe los siguientes parámetros:

238
Intents

 Código de petición: código que se utiliza para iniciar la subactividad.

 Código de resultado: código que devuelve la subactividad,


Activity.RESULT_OK, Activity.RESULT_CANCELED o cualquier número
entero.

 Intención con datos devueltos: identificador URI con la intención con los
resultados devueltos por la subactividad. Esta segunda actividad reciben
datos de la primera a través de la clase Bundle que pueden ser recuperados a
través de dos formas:

o método data.getExtras() en combinación con cualquiera de sus


métodos getString("nombre_dato"), getBoolean("nombre_dato"),
getInteger("nombre_dato"), etcétera.

o métodos getData() en combinación con cualquiera de los métodos


getStringExtra("nombre_dato"), getBooleanExtra("nombre_dato"),
getIntegerExtra("nombre_dato"), etcétera. En estos métodos se
puede definir un valor por defecto en el caso de que no esté definido
el dato.

Podemos comprobar con el método data.hasExtra("nombre_dato") si la intención


devuelta contiene el dato extra nombre_dato antes de tratar esta información.

5.1.8 <b Filtros de intenciones


La acción de una intención implícita puede estar en la misma aplicación, provenir de
un servicio nativo o de otra aplicación.

Los filtros de intenciones registran Actividades, Servicios y Receptores de mensajes


de difusión (broadcast receivers) como potenciales receptores de acciones sobre cierto tipo
de datos.

En el fichero "Manifest" se utiliza la etiqueta <intent-filter> en la sección del


componente de la aplicación que recibe la acción, especificando la acción, la categoría y los
datos para invocarla. Estos parámetros se usan para especificar lo siguiente:

 Acción: atributo android:name, que especifica la acción que se sirve a la misma


aplicación o a otras aplicaciones . Debemos definir una cadena única, por lo que
se debe utilizar la notación de paquetes (es.mentor.)

 Categoría: atributo android:category, que especifica bajo qué circunstancias se


debe servir la acción. Es posible especificar varias categorías. Este atributo añade
información adicional sobre la acción que se debe ejecutar. Por ejemplo,
CATEGORY_LAUNCHER indica que la acción debe aparecer en la pantalla de
Inicio (Launcher) como una aplicación, mientras que CATEGORY_ALTERNATIVE
239
indica que debe incluirse en una lista de acciones alternativas que el usuario
puede aplicar a un conjunto de datos.

 Datos: permite especificar mediante atributos los tipos de datos sobre los que
puede actuar el componente. Por ejemplo, android:host, android:mimetype,
android:path, etcétera.

Veamos un ejemplo del código de la actividad EjemploActividad que declara la


acción VER_TEXTO al sistema operativo para que otra Actividad la invoque:

<activity android:name=".EjemploActividad"
android:label="Ver Texto">

<intent-filter>

<action android:name="es.mentor.intent.action.VER_TEXTO">

</action>

<category android:name="android.intent.category.DEFAULT"/>

<category
android:name="android.intent.category.CATEGORY_ALTERNATIVE"

/>

<data android:mimeType="vnd.visualizador.cursor.item/*"/>

</intent-filter>
</activity>

<b
5.1.9 Resolución de intenciones implícitas

Puede ocurrir que exista más de una actividad que haya registrado un filtro de
intención (Acción) con el mismo nombre. Por ejemplo, el dispositivo Android puede disponer
de varios programas para navegar por Internet; si el usuario solicita abrir una página HTML,
entonces se le muestra un listado con las opciones posibles que completan las acciones.

Android elige la intención implícita que se resuelve con varias actividades posibles así:

1. Android genera una lista interna en el sistema operativo con todos los filtros de
intenciones posibles incluyendo los de las aplicaciones nativas preinstaladas.

2. Elimina los filtros que no coinciden con la acción solicitada.

3. Eliminan las categorías que no coinciden con la categoría invocada.

4. Cada parte de la URI de los datos de la intención que se invoca se compara


con la etiqueta <data> del filtro. Los parámetros especificados en el filtro han
de coincidir con los de la intención. Es decir, no podemos hacer una llamada
240
Intents

de teléfono con un fichero de música, sino que debemos usar un número de


teléfono.

5. Si después de los pasos anteriores, existe más de un filtro candidato, se


escoge el de mayor prioridad o se permite al usuario que seleccione la
Intención.

Con los métodos getIntent(), getAction() y getData() el componente elegido por el


proceso de resolución puede saber qué acción tiene que ejecutar y el dato sobre el que
ejecutarla. Fíjate en este ejemplo:

@Override

public void onCreate(Bundle bundle) {

super.onCreate(bundle);

setContentView(R.layout.main);

Intent intent = getIntent();

String action = intent.getAction();

Uri data = intent.getData();

Con el método startNextMatchingActivity() un componente puede derivar el


procesamiento de una intención implícita a otro componente cuando no quiere atenderla; por
ejemplo, cuando no es la adecuada para realizar esa acción. Fíjate en el siguiente ejemplo:

Intent intent = getIntent();

if (!noHayConexionInternet) startNextMatchingActivity(intent);

De esta manera, un componente de una aplicación Android puede decidir si responde


a una acción o cede el trabajo a otro componente más apropiado.

5.1.10 <b Uso de intenciones para extender aplicaciones


Es posible servirse de intenciones implícitas para proporcionar funcionalidad
(acciones) de nuevos componentes de aplicaciones desde menús de opciones de aplicaciones
compiladas, antes de que exista la acción en el sistema.

241
El método addIntentOptions() de la clase Menu permite especificar los datos sobre
los que puede operar una acción futura. Se especifican únicamente los datos, no la acción en
sí. Cuando Android resuelve la intención y devuelve una lista de acciones apropiadas para el
dato, se crea una nueva opción en el menú de la aplicación.

Muchas aplicaciones del sistema operativo emplean este mecanismo para extender su
funcionalidad a medida que nuevas actividades van implementando las acciones previstas.

Para declarar nuevas acciones susceptibles de ser invocadas desde menús


preexistentes en otras actividades, hay que exportarlas en los filtros de intenciones. La
etiqueta <category> del filtro debe ser ALTERNATIVE y/o SELECTED ALTERNATIVE. La
etiqueta <android:label> aparecerá en la opción del menú correspondiente.

Por ejemplo:

<activity android:name=".ActividadExtra">

<intent-filter android:label="Lanzar nueva actividad">

<action android:name="es.mentor.LANZAR"/>

<data android:mimeType="es.mentor.cursor.item/*"/>

<category android:name="android.intent.category.ALTERNATIVE"/>

<category

android:name="android.intent.category.SELECTED_ALTERNATIVE"

/>

</intent-filter>

</activity>

El método addIntentOptions() del objeto menú recibe como parámetro una intención
que especifica los datos para los que se quiere proporcionar una acción. Se invoca este
método desde los métodos onCreateOptionsMenu() o onCreateContextMenu(), que ya
hemos estudiado. La intención sólo especifica los datos y la categoría
CATEGORY_ALTERNATIVE o CATEGORY_SELECTED_ALTERNATIVE. No se debe
especificar ninguna acción ya que es lo que buscamos.

@Override

public boolean onCreateOptionsMenu(Menu menu) {

super.onCreateOptionsMenu(menu);

// Creamos un intent que se usa para resolver que acciones aplican a


// ese tipo de datos.

Intent intent = new Intent();

242
Intents

intent.setData(Dato.CONTENT_URI);

intent.addCategory(Intent.CATEGORY_SELECTED_ALTERNATIVE);

// Buscamos el menú que coincide con esos datos.


menu.addIntentOptions(
R.id.intent_group, // Menu group donde añadimos las nuevas opciones
0, // ID único de la opción (ninguno)
0, // Orden de la opción (ninguno)
this.getComponentName(), // Nombre Actividad actual
null, // Opciones que se colocan al principio (ninguna)
intent, // Intent creado con los datos y la categoría
0, // Parámetro adicional (ninguno)
null); // Matriz de MenuItems que correla opciones (ninguna)

return true;

5.2 USO DE INTENTS

5.2.1 <b Uso de Intents


Debido a la complejidad de uso de las Intenciones (Intents), vamos a describir cómo
se usan, tanto implícita como explícitamente.

5.2.2 <b Invocación Explícita


El Ejemplo 1 de este Unidad muestra cómo se pueden transferir datos entre dos
actividades. Para ello, vamos a invocar intenciones explícitamente entre dos actividades. La
primera actividad llama a la segunda, también llamada subactividad, a través de una intención
explícita. Esta segunda actividad recibe datos de la primera a través de la clase Bundle, que
pueden ser recuperados a través de intent.getExtras().

La segunda actividad (o subactividad) puede finalizar mediante el botón de retroceso


del teléfono o por una acción del usuario en ésta, por ejemplo, un clic en un botón o la
selección de una opción en un listado. En este caso se lanza el método finish(), en el que se
pueden transferir algunos datos como respuesta de la ejecución a la actividad principal inicial.
En esta actividad principal se utiliza el método startActivityForResult() para recibir estos
datos de la subactividad.

A continuación, vamos a ver cómo funciona la invocación explícita de Intenciones en


Android mediante un ejemplo sencillo que simula una aplicación que gestiona un único
contacto. En este ejemplo debemos prestar atención al código Java que interrelaciona ambas
Actividades.

Lo primero que vamos a hacer es definir el fichero XML que incluye los diseños (layout)
de ambas actividades. Puedes encontrar este diseño en los ficheros del proyecto Android:
243
 res/layout/main.xml: actividad principal.
 res/layout/segundaactividad.xml: actividad secundaria o subactividad.

Después, creamos el código fuente Java para las dos actividades. La segunda
actividad, que se invoca desde la primera, muestra los datos recibidos e indica si devuelve
datos a la actividad principal. Veamos el código fuente de la actividad principal:

public class IntencionexplicitaActivity extends Activity {

private static final int COD_PETICION = 10;

private String nombre, apellidos;

private TextView resultadoLbl;

private Button ModContactoBtn, AltaContactoBtn;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

// Subrayamos la etiqueta que hace de título

TextView tituloLbl = (TextView) findViewById(R.id.tituloLbl);

SpannableString contenido = new SpannableString(tituloLbl.getText());

contenido.setSpan(new UnderlineSpan(), 0, contenido.length(), 0);

tituloLbl.setText(contenido);

resultadoLbl = (TextView) findViewById(R.id.resultadoLbl);

// Al arrancar la aplicación como no hay un contacto no se puede


modificar

ModContactoBtn = (Button) findViewById(R.id.ModContactoBtn);

AltaContactoBtn = (Button) findViewById(R.id.AltaContactoBtn);

ModContactoBtn.setEnabled(false);

resultadoLbl.setText("No has dado de alta ningún contacto");

// Cargamos las variables con el dato

nombre="";

apellidos="";

public void onClick(View view) {

Intent i = new Intent(this, ActividadDos.class);

244
Intents

switch (view.getId()) {

case R.id.AltaContactoBtn:

i.putExtra("operacion", "alta");

i.putExtra("nombre", "");

i.putExtra("apellidos", "");

break;

case R.id.ModContactoBtn:

i.putExtra("operacion", "modifica");

i.putExtra("nombre", nombre);

i.putExtra("apellidos", apellidos);

break;

// Invocamos explícitamente la actividad con los datos "i"

startActivityForResult(i, COD_PETICION);

@Override

// Este método se invoca cuando la subactividad finaliza

protected void onActivityResult(int requestCode, int resultCode, Intent


data) {

// Si el Cód. petición no coincide (otra aplicación está usando esta


// actividad también) no tratamos la información

if (requestCode != COD_PETICION) return;

// Si la subactividad responde OK

if (resultCode == RESULT_OK) {

resultadoLbl.setText("");

if (data.hasExtra("nombre")) {

resultadoLbl.setText("Nombre: " +
data.getExtras().getString("nombre")+"\n");

nombre= data.getExtras().getString("nombre");

if (data.hasExtra("apellidos")) {

resultadoLbl.append("Apellidos: " +
data.getExtras().getString("apellidos")+"\n");

apellidos= data.getExtras().getString("apellidos");

245
}

if (!nombre.isEmpty() || !apellidos.isEmpty()) {

AltaContactoBtn.setEnabled(false);

ModContactoBtn.setEnabled(true);

String auxStr = "modificado";

String operacion = data.getExtras().getString("operacion");

if (operacion.equals("alta")) auxStr = "dado de alta";

Toast.makeText(this, "Has " + auxStr + " el contacto


correctamente", Toast.LENGTH_SHORT).show();

} else {

AltaContactoBtn.setEnabled(true);

ModContactoBtn.setEnabled(false);

} else

Toast.makeText(this, "Has salido de la subactividad sin pulsar


el botón 'Aceptar'", Toast.LENGTH_SHORT).show();

En el código anterior es importante fijarse en cómo se invoca la subactividad mediante


el método startActivityForResult() y cómo se trata la respuesta de ésta en el método
onActivityResult() de la actividad principal.

Veamos ahora el código fuente de la actividad secundaria o subactividad:

public class ActividadDos extends Activity {

private EditText nombre, apellidos;

private TextView tituloLbl;

private String operacion;

@Override

public void onCreate(Bundle bundle) {

super.onCreate(bundle);

setContentView(R.layout.segundaactividad);

// Buscamos los componentes de la UI

246
Intents

nombre = (EditText) findViewById(R.id.nombre);

apellidos = (EditText) findViewById(R.id.apellidos);

tituloLbl = (TextView) findViewById(R.id.tituloLbl);

// Obtenemos el contenidos de la variable extras

Bundle extra = getIntent().getExtras();

// Si no hay variable extra no ejecutamos las siguientes sentencias

if (extra == null) return;

// Leemos los contenidos de las variables extra

String valor1 = extra.getString("nombre");

String valor2 = extra.getString("apellidos");

operacion = extra.getString("operacion");

if (valor1 != null && valor2 != null && operacion != null) {

nombre.setText(valor1);

apellidos.setText(valor2);

// Cambiamos el título de la pantalla en función de la operación

if (operacion.equals("alta")) tituloLbl.setText("Alta de
contacto. Indica el nombre y los apellidos.");

else tituloLbl.setText("Modificar contacto. Cambia el nombre y


los apellidos.");
}

// Cuando el usuario pulsa aceptar devolvemos la información de nuevo

public void onClick(View view) {

Intent datos = new Intent();

datos.putExtra("nombre", nombre.getText().toString());

datos.putExtra("apellidos", apellidos.getText().toString());

datos.putExtra("operacion", operacion);

// Indicamos OK en el resultado

setResult(RESULT_OK, datos);

finish();

@Override

// Método de la actividad que se invoca cuando ésta finaliza

public void finish() {

247
super.finish();

En el código anterior es importante fijarse en cómo se usa la clase Bundle en el


método onCreate() para leer los datos extra que incorpora la actividad principal al invocar la
actividad secundaria y cómo se devuelve la respuesta de ésta con los métodos setResult() y
finish() a la actividad principal.

Finalmente, declaramos la actividad secundaria en el fichero "AndroidManifest.xml":

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="es.mentor.unidad5.eje1.intencionexplicita"

android:versionCode="1"

android:versionName="1.0">

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

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

<activity android:name=".IntencionexplicitaActivity"

android:label="@string/app_name">

<intent-filter>

                <action android:name="android.intent.action.MAIN" /> 

                <category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<!-- Declaramos la actividad secundaria -->

<activity android:label=" @string/actividadSecundaria"

android:name=".ActividadDos">

</activity>

</application>

</manifest>

248
Intents

Desde Eclipse puedes abrir el proyecto Ejemplo 1 (Intención explícita) de la Unidad 5.


Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior,
en el que hemos utilizado Intenciones invocadas de manera explícita.

Si ejecutamos la aplicación, vemos que al hacer clic en cualquiera de los botones de la


pantalla siguiente:

se inicia la actividad secundaria para hacer una tarea en concreto:

5.2.3 <b Invocación Implícita


El Ejemplo 2 de este Unidad muestra cómo invocar intenciones implícitamente entre
dos actividades mediante Acciones. Para ello, vamos usar actividades de aplicaciones que el
sistema operativo instala por defecto.

Lo primero que vamos a hacer es definir el fichero XML que incluye el diseño (layout)
de la actividad principal. Puedes encontrar este diseño en el fichero res/layout/main.xml del
proyecto Android.

Para poder usar Intents con componentes de Android hay que añadir los siguientes
permisos a la aplicación en el fichero "AndroidManifest.xml":

<!-- Permisos que necesita la aplicación -->

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

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

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

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

Después, creamos el código fuente Java para la actividad principal:

// Método que usan los botones de la pantalla principal

public void invocaIntent(View view) {

Intent intent = null;

// Invocamos un Intent con una Acción y con un dato de información para la


// acción: un teléfono, una dirección Internet, etcétera.

switch (view.getId()) {

case R.id.navegadorBtn:

intent = new Intent(Intent.ACTION_VIEW,


Uri.parse("http://www.google.es/"));

startActivity(intent);

break;

case R.id.llamarTfnoBtn:

intent = new Intent(Intent.ACTION_CALL,


Uri.parse("tel:(+34)12345789"));

startActivity(intent);

break;

case R.id.marcarTfnoBtn:

intent = new Intent(Intent.ACTION_DIAL,


Uri.parse("tel:(+34)12345789"));

startActivity(intent);

break;

case R.id.contactosBtn:

intent = new Intent(Intent.ACTION_VIEW,


Uri.parse("content://contacts/people/"));

startActivity(intent);

break;

case R.id.selContactoBtn:

250
Intents

intent = new Intent(Intent.ACTION_PICK,


Uri.parse("content://contacts/people/"));

// Ejecutamos este Intent indicando que esperamos la respuesta

startActivityForResult(intent, SELEC_CONTACTO);

break;

} // end switch

@Override

// Método que se lanza cuando un Intent acaba su tarea. En este caso sólo la
// acción que selecciona un contacto devuelve información

public void onActivityResult(int requestCode, int resultCode, Intent data) {

if (resultCode == Activity.RESULT_OK && requestCode == SELEC_CONTACTO) {

// En la Unidad 7 veremos los Content Providers. En este caso usamos


los contactos del teléfono

Uri contactoData = data.getData();

Cursor c = managedQuery(contactoData, null, null, null, null);

if (c.moveToFirst()) {

String nombre= c.getString(c.getColumnIndexOrThrow(


ContactsContract.Contacts.DISPLAY_NAME));

Toast.makeText(this, "Nombre seleccionado: "+ nombre,


Toast.LENGTH_LONG).show();

} // end RESULT_OK

En el código fuente anterior podemos ver que algunas actividades se inician con el
método startActivity() porque no queremos saber el resultado de la ejecución de la
subactividad. Sin embargo, el segundo botón "Seleccionar contacto" se inicia con el método
startActivityForResult() para obtener el resultado de la ejecución de la subactividad en el
método onActivityResult(). En la práctica lo que hacemos es obtener el contacto
seleccionado mediante un identificador URI de la base de datos de contactos del teléfono.

Desde Eclipse puedes abrir el proyecto Ejemplo 2 (Intención implícita) de la Unidad 5. Estudia
el código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior, en el
que hemos utilizado Intenciones invocadas de manera implícita.

251
Si ejecutamos la aplicación, veremos que al hacer clic en cualquiera de los botones de
la pantalla se inicia una nueva Actividad:

Fíjate que en el código fuente no especificamos la aplicación que tiene que lanzarse
para esa Acción, simplemente dejamos que Android decida qué aplicación es más apropiada
para la tarea solicitada.

5.2.4 <b Registro Acción para Invocación Implícita


En nuestras aplicaciones Android podemos informar al sistema que la aplicación
dispone de componentes que pueden ser utilizados por otras aplicaciones mediante Acciones.

El Ejemplo 2 de este Unidad muestra cómo definir filtros de intenciones (Filter Intens)
de una Actividad interna de la aplicación para que pueda ser invocada implícitamente desde
otra aplicación mediante su Acción correspondiente.

En este caso, hemos definido en el sistema operativo un nuevo navegador de Internet.


Por simplificación, esta nueva actividad del navegador de Internet lo vamos a iniciar desde la
misma aplicación, si bien esta opción aparecerá también en otras aplicaciones del sistema
operativo que deseen navegar por Internet.

Para que Android sepa que hay una nueva Acción disponible en el sistema operativo,
tenemos que añadir la Actividad con la Acción correspondiente en el fichero
"AndroidManifest.xml":

252
Intents

<activity android:name=".NavegadorActivity"

android:label="@string/navegadorAct">

<!-- Filtro que declara el nuevo navegador en el sistema operativo -->

<intent-filter>

<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />

<data android:scheme="http"/>

</intent-filter>
</activity>

En este caso, la acción es visualizar (VIEW) el protocolo HTTP (esquema del dato).
Hemos usado la etiqueta <intent-filter> para definir la nueva acción. Por la nomenclatura de
Android, es muy importante definir el punto "." en el nombre de la actividad:
android:name=".NavegadorActivity".

Lo primero que vamos a hacer es definir el fichero XML que incluye el diseño (layout)
de la actividad secundaria (Navegador de Internet). Puedes encontrar este diseño en el fichero
res/layout/navegador.xml del proyecto Android.

Después, creamos el código fuente Java para la nueva actividad:

// Clase sencilla que simula un navegador cargando una dirección de Internet

public class NavegadorActivity extends Activity {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.navegador);

// Usamos una etiqueta para mostrar la página descargada

TextView textoResultado = (TextView) findViewById(R.id.textView);

// Obtenemos el intent que invoca esta actividad

Intent intent = getIntent();

// Obtenemos el contenido de los datos del intent

Uri datos = intent.getData();

try {

// Formamos la URL que debemos cargar

253
URL url = new URL(datos.getScheme(), datos.getHost(),
datos.getPath());

// Leemos los datos de la página web y los vamos cargando en la


etiqueta

BufferedReader rd = new BufferedReader(new InputStreamReader(

url.openStream()));

String line = "";

while ((line = rd.readLine()) != null) {

textoResultado.append(line);

} catch (Exception e) {

e.printStackTrace();

Desde Eclipse puedes abrir el proyecto Ejemplo 2 (Intención implícita) de la Unidad 5. Estudia
el código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior, en el
que hemos definido una nueva Acción mediante Filtros de Intenciones.

Si ejecutamos la aplicación, veremos que al hacer clic en el botón "Iniciar el


navegador" de la pantalla podemos seleccionar el navegador con el que queremos abrir la
página Web. Si hacemos clic en "Navegador curso Mentor", se inicia la nueva Actividad que
hemos definido anteriormente:

254
Intents

5.2.5 <b Detectar Acciones de Intents


A veces, es interesante detectar en nuestras aplicaciones de Android si podemos usar
una determinada Acción en un Intent. Por ejemplo, puede ocurrir que ampliemos la
funcionalidad de una aplicación usando una actividad de otra aplicación opcional que el
usuario puede haber instalado.

Esto se puede hacer usando la clase PackageManager de Android y el método


queryIntentActivities(), para consultar si algún componente instalado en el teléfono responde
a esa acción.

El siguiente código comprueba si una Acción existe. Así, es muy fácil cambiar el
comportamiento de una aplicación, como mostrar u ocultar opciones de la misma.

public boolean existeAccion(Context context, String accion) {

final PackageManager packageManager = context.getPackageManager();


final Intent intent = new Intent(accion);
List<ResolveInfo> resolveInfo =
packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);

if (resolveInfo.size() > 0) {
return true;
}
return false;
}

255
5.3 PERMISOS Y SEGURIDAD EN ANDROID

Android es un sistema operativo que separa privilegios entre aplicaciones que se


ejecutan simultáneamente usando identidades diferentes del sistema, es decir, con un ID de
usuario de grupo de Linux diferente (cada aplicación se ejecuta con un usuario distinto).
Determinadas partes del sistema operativo también se separan usando diferentes identidades.
Linux aísla así las aplicaciones entre sí y del sistema operativo.

Además, se proporcionan características adicionales de seguridad más específicas a


través del mecanismo de "permiso" que impone restricciones a las operaciones concretas que
un proceso en particular puede realizar.

Nota: por coherencia, en este apartado tratamos también la seguridad de Content Providers,
Servicios y Receptores de mensajes de difusión (Broadcast Receivers), que estudiaremos en la
Unidad 7. Puedes buscar información en esta Unidad para conocer los conceptos básicos.

5.3.1 Arquitectura de seguridad de Android

La filosofía del diseño de la arquitectura de seguridad de Android consiste en


que, por defecto, una aplicación no tiene permisos para realizar cualquier operación que
pueda afectar negativamente a otras aplicaciones, al sistema operativo o al usuario. Esto
incluye la lectura o escritura de datos privados del usuario (como los contactos o correos
electrónicos), leer o escribir archivos de otra aplicación, acceder a Internet, etcétera.

Debido a que Android separa la ejecución de las aplicaciones en cajas de arena (del
inglés sandbox), las aplicaciones deben compartir recursos y datos de manera explícita.

Un sandbox es un sistema de aislamiento de procesos, mediante el cual se pueden


ejecutar distintos programas con seguridad y de manera separada.

Para compartir recursos entre aplicaciones, éstas deben declarar al sistema Android
los permisos adicionales que necesitan para funcionar correctamente y ampliar su
funcionalidad básica. Las aplicaciones declaran los permisos que necesitan y el sistema
Android solicita al usuario su consentimiento cuando instala la aplicación. Android no tiene
ningún mecanismo para dar permisos de forma dinámica (en tiempo de ejecución).

5.3.2 Firma de aplicación

Todas las aplicaciones Android (archivos .apk) deben estar firmadas con un certificado
que identifica al autor de la aplicación. El propósito de los certificados de Android es distinguir
a los autores de las aplicaciones. Esto permite al sistema conceder o denegar solicitudes de
acceso para compartir la identidad de Linux que tenga otra aplicación. Es decir, si el
desarrollador es el mismo, es posible ejecutar ambas aplicaciones con el mismo usuario de
Linux y que compartan el mismo sandbox.
256
Intents

5.3.3 ID de usuario y Acceso a ficheros

Cuando se instala una aplicación, Android asigna a cada paquete un identificador


único de usuario de Linux. Esta identidad no cambia mientras el paquete está instalado en el
dispositivo. En un dispositivo diferente el mismo paquete puede tener un ID de usuario
distinto; así pues, lo importante es que cada paquete siempre tiene otro ID de usuario en un
dispositivo cualquiera.

Debido a que la seguridad en Android se hace a nivel de proceso, el código de dos


paquetes cualquiera no se puede ejecutar en el mismo proceso por defecto, ya que funcionan
con diferentes usuarios de Linux.

Sin embargo, se puede utilizar el atributo sharedUserId del fichero


AndroidManifest.xml para asignar a cada paquete el mismo ID de usuario. De esta manera,
los dos paquetes se tratan como si fueran la misma aplicación con el mismo ID de usuario y
los permisos de archivo. Es importante tener en cuenta que, para poder hacer esto, las dos
aplicaciones deben estar firmadas con el mismo certificado de autor.

A los datos almacenados por una aplicación también se les asigna el ID de usuario y
normalmente otras aplicaciones no pueden acceder a ellos. Cuando se crea un nuevo archivo
con funciones del estilo getSharedPreferences(), openFileOutput(), etcétera, se puede usar
los parámetros MODE_WORLD_READABLE y MODE_WORLD_WRITEABLE para permitir
que cualquier otra aplicación lea o escriba en el archivo. Aunque se establezcan estos
indicadores, el archivo sigue siendo propiedad de la aplicación original, si bien se permite su
lectura y escritura globalmente, de manera que cualquier otra aplicación puede acceder a esta
información almacenada.

Por seguridad, no es recomendable usar la compartición de información de esta


manera; es mejor usar Proveedores de contenidos (Content Provider) para ello.

5.3.4 Permisos de aplicaciones

Una aplicación básica de Android no tiene permisos asociados, por lo que no puede
hacer nada que afectara al usuario o a cualquier aplicación instalada en el dispositivo. Para
hacer uso de las características protegidas del dispositivo, se debe incluir en el fichero
AndroidManifest.xml del proyecto una o más etiquetas <uses-permission> que declaren los
permisos que necesita la aplicación.

En alguno de los ejemplos anteriores del curso ya hemos usado esta etiqueta para
poder ampliar la funcionalidad de la aplicación y acceder a determinados recursos del sistema
operativo.

257
Podemos escribir una o varias etiquetas <uses-permission> en el archivo
AndroidManifest.xml. El elemento <uses-permission> requiere que definamos el atributo
android:name, dentro del cual indicamos el nombre del permiso que requiere la aplicación.

Por ejemplo:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="es.mentor.miapp" >
<uses-permission android:name="android.permission.RECEIVE_SMS" />
...
</manifest>

En este caso, estamos manifestando que esta aplicación necesita poder recibir
mensajes cortos SMS para funcionar.

Como hemos comentado, los permisos se asignan en el momento en que se instala la


aplicación en el dispositivo. Para ello, se pide al usuario su consentimiento para que la
aplicación pueda acceder a los recursos solicitados.

Por lo tanto, a la hora de programar una aplicación Android, es importante seleccionar


sólo los permisos que realmente necesita esa aplicación y justificar la petición al usuario. Ten
en cuenta que cuando programes y pruebes aplicaciones en el emulador de tu ordenador, este
mensaje no aparece; solamente aparece al instalar las aplicaciones en un dispositivo real.

La pantalla que informa de los permisos tiene este aspecto:

El programador puede definir sus propios permisos internos de aplicación para que
otra aplicación los utilice. Más adelante veremos cómo se hace.

Todos los permisos del sistema comienzan con el texto android.permission y se


pueden ver en la documentación oficial de Android para la clase Manifest.permission. Como

258
Intents

hemos dicho, las aplicaciones de terceros pueden tener sus propios permisos. De forma
general, resumimos algunos de los permisos más utilizados:

 ACCESS_WIFI_STATE: permite a la aplicación acceder a la información de las


conexiones WI-FI.

 INTERNET: permite a la aplicación acceder a Internet.

 READ_CALENDAR, READ_CONTACTS: todos los permisos con el prefijo


READ_ permiten a la aplicación leer información de un Content provider de
Android. En este caso, estos permisos otorgan acceso de lectura al calendario
y a los contactos del teléfono.

 WRITE_CALENDAR, WRITE_CONTACTS: todos los permisos con el prefijo


WRITE_ permiten a la aplicación modificar información con un Content
provider de Android. En este caso, estos permisos otorgan acceso de
escritura al calendario y a los contactos del teléfono.

Es posible que una aplicación no reciba la autorización para hacer algo porque nos
hayamos olvidado de declarar el permiso necesario en el fichero AndroidManifest. En este
caso podemos usar la excepción de tipo SecurityException, que indica el permiso que falta.

Existe otra forma de comprobar los permisos de nuestra aplicación en tiempo de


ejecución: podemos utilizar el método Context.checkPermission(permiso, paquete) de la
clase PackageManager para comprobar si una aplicación tiene concedido un permiso en
concreto. Este método devuelve el valor PERMISSION_GRANTED o PERMISSION_DENIED
para indicar si la aplicación tiene el permiso concedido o denegado respectivamente. En el
Ejemplo 3 de esta Unidad hemos usado este método.

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Permisos de aplicaciones) de la Unidad 5.


Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior,
en el que hemos comprobado si la aplicación tiene los permisos adecuados.

Si nos olvidáramos de declarar los permisos necesarios de una aplicación en el fichero


AndroidManifest y no controlamos en tiempo de ejecución los permisos que tiene asignados,
al ejecutar esta aplicación pulsando en su botón "Vibración simple", por ejemplo, veríamos el
siguiente mensaje y la aplicación finalizaría:

259
Para que funcione bien esta aplicación, debemos añadir la siguiente etiqueta en el
fichero AndroidManifest:

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

Este permiso posibilita a la aplicación que el teléfono vibre.

Para comprobar que la aplicación tiene asignado este permiso, hemos escrito las
siguientes sentencias en la Actividad:

// Cargamos el objeto de gestor de paquetes

PackageManager p = this.getPackageManager();

// Buscamos si el paquete de este ejemplo tiene el permiso para vibrar el


teléfono

if (p.checkPermission("android.permission.VIBRATE",
"es.mentor.unidad5.eje3.permisos")==PackageManager.PERMISSION_DENIED) {
...

Así, si borramos la etiqueta que solicita el permiso de vibración en el fichero


AndroidManifest.xml, veremos que la aplicación muestra un error y no permite al usuario
utilizarla:

260
Intents

En el caso de que la aplicación tenga los permisos adecuados, el usuario puede


utilizarla:

5.3.5 <Autoprotección de aplicaciones Android


Otro aspecto que hay que conocer de los permisos de Android tiene que ver con la
forma en que podemos proteger el acceso de otras aplicaciones a nuestras aplicaciones.

Existen dos formas complementarias de enfocar la seguridad en aplicaciones Android:

1. Seguridad externa: la aplicación está formada con Actividades e indicamos


los permisos necesarios para utilizar recursos de otras aplicaciones.

2. Seguridad interna (autoprotección): la aplicación utiliza Content Providers,


Servicios o Receptores de mensajes de difusión (Broadcast Receivers). En
este caso hay que controlar qué aplicaciones pueden acceder a la información
interna y cómo acceden a ella.

El primer procedimiento ya lo hemos visto anteriormente.

En el segundo procedimiento de autoprotección de nuestras aplicaciones hay que


definir, igualmente, los permisos dentro del archivo AndroidManifest.xml.

261
En este caso, en lugar de escribir la etiqueta <uses-permission>, utilizamos la
etiqueta <permission> para declarar un permiso al sistema. De igual forma, podemos definir
uno o varios permisos.

La declaración de un permiso es un poco más compleja que usar un permiso. Para


ello, debemos proporcionar tres datos:

 Nombre simbólico del permiso: el nombre del permiso no puede coincidir


con el de otra aplicación. Es recomendable utilizar nombres al estilo de las
clases Java de la aplicación. Por ejemplo: es.mentor.ejemplo.VER_LISTADO

 Etiqueta del permiso: nombre corto que ve el usuario.

 Descripción del permiso: texto más extenso con la descripción del permiso.

Ejemplo de permiso:

<permission

android:name="es.mentor.ejemplo.VER_LISTADO"
android:description="Permite ver el listado de ..."
android:label="Ver listado contactos" />

Esta definición de permiso sólo informa al sistema operativo de un posible permiso


que pueden usar otras aplicaciones; posteriormente, la aplicación debe decidir cómo gestionar
estos permisos.

5.3.6 <Asignar permisos de componentes internos de la aplicación


Dentro de las actividades y los servicios podemos definir el atributo
android:permission, que indica el nombre del permiso necesario para acceder a ese elemento
de la aplicación. Fíjate en este ejemplo:

<activity android:name=".nombreActividad"

android:label="@string/app_name"
android:permission="es.mentor.ejemplo.VER_LISTADO">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>
</activity>

De esta forma, únicamente las aplicaciones que hayan solicitado el permiso indicado
podrán acceder al elemento de forma segura. En este contexto, el “acceso” significa lo
siguiente:

262
Intents

 Las actividades no pueden ejecutarse sin el permiso.

 Los servicios no pueden arrancarse, detenerse o vincularse a una actividad sin


el permiso.

 Los Receptores de mensajes de difusión ignorarán los mensajes enviados o


recibidos a menos que el remitente tenga el permiso.

Los Proveedores de contenidos (Content providers) ofrecen dos atributos


diferentes: readPermission y writePermission. Fíjate en este ejemplo:

<provider android:name=".nombreProveedor"

android:authorities="es.mentor.ejemplo.proveedor"
android:readPermission="es.mentor.ejemplo.VER_LISTADO"
android:writePermission="es.mentor.ejemplo.ESCRIBIR_LISTADO" />
</provider>

En el ejemplo anterior, el atributo android:readPermission controla el acceso para


hacer consultas al Content provider y android:writePermission controla el acceso para
actualizar o borrar su información.

5.3.7 <Cómo obtienes permisos estas aplicaciones


Existen dos formas, en el código fuente Java, de comprobar que las aplicaciones
externas están solicitando los permisos necesarios para iniciar un componente de nuestra
aplicación:

 Los servicios pueden verificar los permisos usando el método


checkCallingPermission(). Este método devuelve también
PERMISSION_GRANTED o PERMISSION_DENIED dependiendo de si la
aplicación tiene el permiso o no. Como parámetro se indica el nombre del
permiso que se quiere comprobar.

 También cuando invocamos el método sendBroadcast() para enviar un


mensaje de difusión (Broadcast) podemos incluir un permiso. Así, sólo los
receptores de mensajes de difusión que tengan este permiso podrán recibir el
mensaje enviado. Por ejemplo, Android incluye el permiso RECEIVE_SMS
para saber cuándo se ha recibido en el teléfono un mensaje SMS nuevo.

263
5.3.8 <Notas sobre seguridad en Android
Al compilar una aplicación no existe un mecanismo que detecte automáticamente los
permisos que necesita ésta. Es decir, si no asignamos bien los permisos en el fichero
AndroidManifest, veremos los errores en tiempo de ejecución. Por esto, debemos prestar
especial cuidado a la hora de declarar los permisos correctos y probar la aplicación en el
emulador de Android.

Además, si definimos en la aplicación permisos internos a determinados


componentes, es importante documentar bien estos permisos para que otros desarrolladores
puedan aprovechar las capacidades de nuestra aplicación.

5.4 Tab.Layout

5.4.1 <Pantallas con pestañas con Tab Layout


En la Unidad 1 hemos visto cómo diseñar la interfaz de usuario de una aplicación
Android mediante el uso de diversos componentes de tipo Layout, como los lineales, los
absolutos, los relativos, etcétera.

Los Layouts son elementos organizativos básicos de la interfaz, pero, teniendo en


cuenta el poco espacio disponible en las pantallas de los teléfono o, simplemente, por
cuestiones de organización, a veces es interesante dividir la distribución de los componentes
en varias pantallas.

Una de las formas clásicas de hacerlo en programación es mediante la distribución de


los componentes en pestañas (en inglés tabs).

Android también permite diseñar este tipo de interfaces de usuario, si bien lo hace de
una forma característica, ya que la implementación depende de varios elementos que deben
estar distribuidos y estructurados de una forma determinada.

Adicionalmente, no es suficiente con definir la interfaz en el fichero XML de diseño que


hemos utilizado en otros ejemplos, sino que también es necesario completarlo con algunas
sentencias de código.

A continuación, en el Ejemplo 4 de esta Unidad, vamos a ver paso a paso cómo


implementar este tipo de interfaces.

El elemento principal de Android de un conjunto de pestañas es el componente


TabHost. Éste es el contenedor principal del conjunto de pestañas y debe tener
obligatoriamente como id el valor @android:id/tabhost.

264
Intents

Dentro de éste vamos a incluir un LinearLayout, que se usa para distribuir


verticalmente las secciones principales del TabHost, con las pestañas en la parte superior y la
sección con el contenido de cada pestaña en la parte inferior.

La parte de pestañas se representa mediante un elemento TabWidget, que debe tener


el valor id @android:id/tabs.

La parte inferior que alberga el contenido de las pestañas se define con el elemento
FrameLayout y con el id obligatorio @android:id/tabcontent.

Por último, dentro del FrameLayout incluimos el contenido de cada pestaña.


Normalmente, cada contenido se define dentro de su propio layout principal (en este ejemplo
hemos utilizado un LinearLayout), con un id único que permita, posteriormente, hacer
referencia a él fácilmente. En el ejemplo hemos utilizado los identificadores pest1 y pest2.

A continuación, se puede ver gráficamente la estructura descrita anteriormente:

Si abrimos el fichero XML del layout, veremos el siguiente código que corresponde a
esta estructura:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_height="match_parent" android:layout_width="match_parent">

<TabHost android:id="@android:id/tabhost"

android:layout_width="match_parent"

android:layout_height="match_parent">

265
<LinearLayout

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent" >

<TabWidget android:layout_width="match_parent"

android:layout_height="wrap_content"

android:id="@android:id/tabs" />

<FrameLayout android:layout_width="match_parent"

android:layout_height="match_parent"

android:id="@android:id/tabcontent" >

<LinearLayout android:id="@+id/pest1"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent" >

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

android:text="Contenido de la Pestaña 1"

android:layout_width="wrap_content"

android:layout_height="wrap_content" />

</LinearLayout>

<LinearLayout android:id="@+id/pest2"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent" >

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

android:text="Contenido de la Pestaña 2"

android:layout_width="wrap_content"

android:layout_height="wrap_content" />

</LinearLayout>

</FrameLayout>

</LinearLayout>

266
Intents

</TabHost>

</LinearLayout>

Como puedes observar en el código anterior, por simplificación, en el contenido de las


pestañas únicamente hemos añadido la etiqueta de texto “Contenido de la Pestaña Nº
Pestaña“.

Con el diseño anterior únicamente está montada toda la estructura de componentes


necesarios para la nueva pantalla con pestañas. Sin embargo, como ya indicamos al principio
de este apartado, esto no completa la funcionalidad.

Es necesario asociar cada pestaña con su contenido de forma que el componente de


pestañas funcione correctamente al cambiar de pestaña. Esto hay que hacerlo con sentencias
Java en la actividad principal.

Como viene siendo habitual, lo primero que hacemos es obtener una referencia al
componente principal TabHost y lo inicializamos preparándolo para su configuración
invocando su método setup().

Después, creamos un objeto de tipo TabSpec para cada una de las pestañas que
queramos añadir mediante el método newTabSpec(), al que pasamos como parámetro una
etiqueta identificativa de la pestaña; en el ejemplo usamos mipestania1 y mipestania2.

Además, también asignamos el Layout del contenido correspondiente a la pestaña en


particular con el método setContent().

Finalmente, indicamos el texto y el icono que se muestra en la pestaña mediante el


método setIndicator(texto, icono).

Para acabar, añadimos la nueva pestaña al componente TabHost mediante el método


addTab().

Veamos el código completo:

// Obtenemos la referencia al componente TabHost

TabHost tabs=(TabHost)findViewById(android.R.id.tabhost);

// Preparamos su configuración

tabs.setup();

// Preparamos un objeto con referencia a la pestaña 1

TabHost.TabSpec pestania=tabs.newTabSpec("mipestania1");

//Establecemos el contenido de la pestaña 1

pestania.setContent(R.id.pest1);

// Definimos la pestaña 1 en el TabHost

267
pestania.setIndicator("Pestaña 1",
res.getDrawable(android.R.drawable.ic_menu_agenda));

//Añadimos la pestaña 1 al TabHost

tabs.addTab(pestania);

// Preparamos un objeto con referencia a la pestaña 2

pestania=tabs.newTabSpec("mipestania2");

//Establecemos el contenido de la pestaña 2

pestania.setContent(R.id.pest2);

// Definimos la pestaña 2 en el TabHost

pestania.setIndicator("Pestaña 2",
res.getDrawable(android.R.drawable.ic_menu_directions));

//Añadimos la pestaña 2 al TabHost

tabs.addTab(pestania);

// Indicamos la pestaña activa por defecto


tabs.setCurrentTab(0);

Desde Eclipse puedes abrir el proyecto Ejemplo 4 (Pestañas) de la Unidad 5. Estudia el


código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior, en el que
hemos utilizado el componente TabHost.

Si ejecutamos la aplicación en el emulador de Eclipse, veremos la siguiente pantalla:

268
Intents

Normalmente no se suelen usar los eventos disponibles del componente TabHost,


aunque, a modo de ejemplo, vamos a ver el más interesante de ellos, que ocurre cuando el
usuario cambia de pestaña. El evento se denomina OnTabChanged e informa de la nueva
pestaña que ha seleccionado el usuario.

Este evento los podemos implementar mediante el método


setOnTabChangedListener() de la siguiente manera:

// Definimos el evento OnTabChanged (cuando el usuario ha cambiado de pestaña)

tabs.setOnTabChangedListener(new OnTabChangeListener() {

@Override

public void onTabChanged(String tabId) {

Toast.makeText(getBaseContext(), "Pestaña seleccionada: " +

tabId, 1).show();

}
});

El método onTabChanged() recibe como parámetro el identificador de la pestaña, no


su ID, identificador que asignamos al crear el objeto TabSpec con la pestaña correspondiente.

En el ejemplo del curso hemos detectado el cambio de pestaña y mostrado un


mensaje informativo con el identificador de la nueva pestaña seleccionada.

Al cambiar a la segunda pestaña vemos el mensaje “Pestaña seleccionada:


mipestania2“:

269
 Las Intenciones (Intents) permiten a las aplicaciones de Android expresar la
intención que desea ejecutar una acción sobre unos datos usando algún
componente de ésta o de otra aplicación.

 Para crear una Intención hay que usar el objeto Intent de Android.

 Las Intenciones son mensajes asíncronos que se usan para realizar acciones e
intercambiar datos, tanto en la petición como en la respuesta, entre componentes
de las aplicaciones.

 Las intenciones se invocan de dos formas:

o Explícita: invocando la clase Java del componente que queremos


ejecutar. Normalmente, se hace para invocar componentes de una misma
aplicación.

o Implícita: invocando la acción y los datos sobre los que aplicar dicha
acción. Android selecciona, en tiempo de ejecución, la actividad receptora
que cumple mejor con la acción y los datos solicitados.

 Para que Android pueda iniciar un componente de una aplicación, debe conocer
que existe; para ello, se declaran los componentes de una aplicación en el fichero
AndroidManifest.xml.

 La forma en que Android identifica qué componentes pueden responder a una


Acción es buscándola en los filtros de intención (intent filters) que se declaran en
el archivo "AndroidManifest" de todas las aplicaciones.

 Es posible emplear intenciones implícitas para proporcionar funcionalidad


(acciones) de nuevos componentes de aplicaciones desde los menús de
opciones de aplicaciones compiladas y antes de que existiera la acción en el
sistema.

 Android es un sistema operativo que separa privilegios entre aplicaciones que


se ejecutan simultáneamente.

 Una caja de arena, en inglés sandbox, es un sistema de aislamiento de


procesos, mediante el cual se pueden ejecutar distintos programas con seguridad
y de manera separada.

270
Intents

 Para compartir recursos entre aplicaciones, éstas deben declarar al sistema


Android los permisos adicionales que necesitan para funcionar correctamente y
ampliar su funcionalidad básica.

 Es posible compartir el mismo sandbox entre dos aplicaciones siempre y cuando


estén firmadas con el mismo certificado del autor.

 Hay dos formas complementarias de enfocar la seguridad en aplicaciones


Android:

1. Seguridad externa: la aplicación está formada con Actividades e


indicamos los permisos necesarios para utilizar recursos de otras
aplicaciones.

2. Seguridad interna (autoprotección): la aplicación utiliza Content


Providers, Servicios o Receptores de mensajes de difusión (Broadcast
Receivers). En este caso hay que controlar qué aplicaciones pueden acceder
a la información interna y cómo acceden a ella.

 No existe un mecanismo que detecte automáticamente al compilar los


permisos que necesita una aplicación. Por esto, debemos prestar especial
cuidado a la hora de declarar los permisos correctos y probar la aplicación en el
emulador de Android.

 Android también permite diseñar interfaces de usuario con pestañas mediante la


clase TabHost.

271
BASES DE DATOS Y XML

ÍNDICE
6.1 BASES DE DATOS .................................................................. 275
6.1.1 Introducción ...........................................................................275
6.1.2 Teoría sobre Bases de Datos ..............................................275
6.1.3 Ventajas de las bases de datos ..........................................279
6.1.4 Bases de datos relacionales................................................281
6.1.5 Diseño de bases de datos ...................................................283

6.2 SQLite en Android .................................................................. 284


6.2.1 Gestión de la información en Android ........................... 284
6.2.2 Gestión de la Base de Datos SQLite en Android........... 285
6.2.3 Creación de Bases de datos SQLite.............................. 286
6.2.4 Modificación de la información de BD SQLite............... 292
6.2.4.1 Método insert() .....................................................293
6.2.4.2 Método update() y método delete() ..................294
6.2.5 Uso de parámetros en los métodos SQLite................... 294

6.3 Consultas SQLite en Android ............................................... 295


6.3.1 Selección y recuperación de Consultas de BD SQLite ..295
6.3.2 Ejemplo práctico de BD SQLite con Android ................ 298
6.3.3 Acceso y creación de la Base de datos......................... 299
6.3.4 Recursos de diseño XML ............................................... 303
6.3.5 Actividades..................................................................... 304
6.3.6 Fichero Androidmanifest.xml ......................................... 310

6.4 GESTIÓN DE FICHEROS XML .............................................. 311


6.4.1 SAX es el modelo clásico en Android ............................ 316
6.4.2 SAX simplificado en Android.......................................... 323
6.4.3 DOM en Android ............................................................ 327
6.4.4 StAX en Android............................................................. 331
2
Introducción al entorno Android

6.1 BASES DE DATOS

6.1.1 Introducción

En esta Unidad vamos a repasar, como preámbulo , la teoría general sobre bases de
datos.

Después, explicaremos cómo gestionar bases de datos SQLite en Android dentro de


las aplicaciones.

Finalmente, detallaremos el uso de ficheros XML en Android.

6.1.2 Teoría sobre Bases de Datos

En este apartado vamos a explicar brevemente algunos conceptos


fundamentales sobre las bases de datos. Suponemos que el alumno o alumna ya tiene
conocimientos suficientes sobre las mismas y, por tanto, abordaremos sólo algunos
conocimientos más relacionados con la forma en que Android accede a las bases de datos y
trata su información.

El término base de datos es informático, pero puede aplicarse también a la forma


como se almacena, ordena y utiliza manualmente la información. Por ejemplo, ya hemos visto
en la Unidad 4 cómo leer y almacenar información en ficheros de preferencias o archivos en la
memoria del dispositivo Android. La información de estos ficheros puede ser considerada una
base de datos en un sentido amplio. Es decir, se almacena y se consulta la información
cuando es necesario.

Sin embargo, en el sentido informático, la palabra base de datos se refiere a una


colección, conjunto o depósito de datos, almacenados en un soporte magnético o de otro
tipo, accesibles por múltiples usuarios de forma rápida y eficaz mediante el ordenador a través
de una o de varias aplicaciones informáticas independientes de los datos. Éstos se relacionan
entre sí y están organizados de tal manera que es fácil introducirlos, actualizarlos, recuperarlos
o llevar a cabo con ellos otras operaciones de gestión.

En el caso de Android, las bases de datos son privadas y únicamente una


aplicación puede acceder a ellas para leer y escribir datos. Cuando una aplicación desea
consultar o modificar la información de la base de datos de otra aplicación, Android dispone
de los Content Providers que permiten a otras aplicaciones hacer las peticiones necesarias a
la aplicación que alberga la base de datos. Ésta devuelve a la aplicación la información
solicitada con los resultados de esas operaciones.

Android usa SQLite como motor de base de datos relacional. En el siguiente apartado
veremos sus características. La información que mostramos a continuación está basada en la
versión 3 de SQLite.

275
Generalmente, en las bases de datos relacionales, de las que hablaremos después, la
información está almacenada y organizada en ficheros formados por filas y columnas,
como puede verse en el ejemplo siguiente, en el que se presentan algunos datos de cinco
libros de una biblioteca:

Columnas

Título Autor Editorial


Antonio
El invierno en Lisboa Muñoz Seix Barral
Molina

Fondo de Cultura
¿Tener o ser? Erich Fromm
Económica
Filas
Crónica de una Gabriel García
Bruguera
muerte anunciada Márquez

Hermann
El lobo estepario Anaya Editores
Hesse

La vida está en otra


Milan Kundera Seix Barral
parte

Cada fila contiene el título, el autor y la editorial de un libro y se relaciona con las
demás filas gracias a que incluye el mismo tipo de información (datos de los libros) y en todas
ellas la información está organizada de la misma forma: la primera columna contiene el título
del libro, la segunda, el autor y la tercera, la editorial.

Así pues, una base de datos contiene un conjunto de ficheros cuya información está
organizada de tal forma que puede ser tratada informáticamente con rapidez y eficacia. La
información de una base de datos puede almacenarse en un solo fichero o en varios.

Los ficheros de una base de datos están grabados en el servidor. Tienen un nombre
(por ejemplo, flores, ríos, libros, coches, amigos, artículos, clientes, ventas, facturas, etcétera).
Su denominación debe seguir las normas establecidas para que el nombre de un fichero sea
correcto. Como puede verse, hemos prescindido de las tildes en los identificadores que las
llevan ortográficamente.

El tipo o extensión de estos ficheros de base de datos puede ser muy variado, según
el tipo de base de datos utilizado: en dBase es dbf (Data Base File, Fichero de Base de Datos),
en Access mdb, en Interbase db o dbf, en MySQL myd, etcétera.

En el caso de SQLite en Android, el archivo de la base de datos suele tener la


extensión .db, si bien, puede almacenarse en el directorio específico de la aplicación con

276
Introducción al entorno Android

cualquier extensión e, incluso, sin ella. En otros sistemas operativos, los archivos de una base
de datos de tipo SQLite suelen tener la extensión .sqlite.

Las filas de un archivo de base de datos se denominan registros y las columnas,


campos (fields, en inglés).

Así pues, un fichero de base de datos está integrado por registros, que son cada
uno de sus elementos o componentes (flor, río, libro, coche, amigo, artículo, cliente, venta o
factura). Todos los registros contienen un conjunto de campos en los que se almacena su
información; este conjunto define la estructura del fichero que integra una base de datos.

En la representación gráfica siguiente puede observarse, en forma de tabla, la


estructura de un fichero que contiene una base de datos con información sobre personas:

Campos

Nombre Sueldo Fecha_nac Observacion Foto

Registros 5

En las filas aparecen hasta once registros, cada uno de los cuales, en este caso,
contiene los cinco campos siguientes: Nombre, Sueldo, Fecha_nac, Observacion y Foto.

En el ejemplo anterior sólo se han incluido once registros y cinco campos, pero de
hecho en las bases de datos que vamos a usar el número de registros es ilimitado (depende
de la capacidad del soporte) y el de campos es muy amplio, según el tipo de base de datos
usada. Todos los registros tienen los mismos campos.

Si comparamos un fichero de base de datos con los archivadores de una biblioteca,


podemos decir que éstos integran la base de datos. Cada archivador es como un fichero de la
base de datos, las fichas que hay en su interior son los registros y los apartados de cada
ficha (título, autor, editorial, etcétera) son los campos.

277
Cada campo de un registro tiene un nombre, un tipo, una longitud o ancho, un
número de decimales si es de tipo numérico o de coma flotante y un índice opcional.
Según el tipo de base de datos que se esté utilizando, el identificador del campo, la clase de
tipos y la longitud de los mismos pueden ser diferentes.

Vamos a centrarnos en los tipos de campos que define SQLite. Este tipo de base de
datos no define todos los tipos de campos típicos en bases de datos relacionales. Únicamente
define unos tipos de campos básicos y luego los reutiliza para especificar otros tipos de
campos.

El nombre de cada campo puede ser muy largo, si bien recomendamos que en el
orden práctico sea lo más breve posible y tenga algún significado. Debe atenerse a las reglas
de todos los identificadores ya comentadas anteriormente.

Hay estos tipos de campos básicos en SQLite:

1. Campo de tipo Carácter. Es el más común (letras, dígitos, signos, etcétera), y


contiene información que es tratada como una cadena de caracteres. Se asigna este tipo a un
campo cuando no se realizan operaciones aritméticas con sus datos, ni contiene una fecha, ni
es un texto mayor de 255 caracteres. Por ejemplo, se asigna este tipo al campo cuyo
contenido va a ser el nombre de una persona, sus apellidos, domicilio, localidad, provincia,
etcétera. Admite índice. En SQLite hay un tipo único de campo para almacenar datos de esta
clase: TEXT que tiene una longitud máxima de 255 caracteres.

También podemos usar los siguientes tipos de campos de texto en SQLite:

CHARACTER(20): campo de texto con 20 caracteres de longitud.


VARCHAR(255): campo de texto de longitud variable hasta 255 caracteres.
VARYING CHARACTER(255): similar al anterior.
NCHAR(x): campo de texto con x caracteres de longitud.
NATIVE CHARACTER(70): campo de texto con 70 caracteres de longitud.
NVARCHAR(100): campo de texto de longitud variable hasta 100 caracteres.
CLOB: campo similar a TEXT.

Todos estos tipos de campo de texto se pueden definir al crear una tabla, si bien,
internamente, SQLite los traduce por afinidad al tipo TEXT inicial.

2. Campo de tipo Numérico. Se utiliza para escribir números, incluidos los signos
positivo y negativo. Se asigna este tipo a un campo cuando se realizan operaciones
aritméticas con números enteros o reales, como sumar, restar, multiplicar, dividir, etcétera.
Admite índice. SQLite admite estos valores para determinar los campos de este tipo:
INTEGER y REAL. Como puede verse, en realidad los valores posibles se refieren a si es un
campo de número entero o decimal.

Podemos usar los siguientes tipos de campos de tipo entero en SQLite:

278
Introducción al entorno Android

INT
TINYINT
SMALLINT
MEDIUMINT
BIGINT
UNSIGNED BIG INT
INT2
INT8

Todos estos tipos de campo de número entero se pueden definir al crear una tabla, si
bien, internamente, SQLite los traduce por afinidad al tipo INTEGER anterior.

En el caso del tipo de campo numérico con decimales, podemos usar los siguientes
tipos de campos:

DOUBLE
DOUBLE PRECISION
FLOAT

Todos estos tipos de campo de número con decimales se pueden definir al crear una
tabla, si bien, internamente, SQLite los traduce por afinidad al tipo REAL anterior.

3. Campo de tipo Fecha y Lógico. Puede contener fechas y tiempos (horas, minutos,
segundos) o almacenar valores lógicos (true / false). Admite índice. SQLite define el tipo de
campo interno NUMERIC para almacenar otros tipos de campos necesarios en las
aplicaciones en una tabla, tales como los campos lógicos o de fecha, así como los que
establecen los decimales exactos en un campo numérico. Podemos usar los siguientes tipos
de campos en SQLite:

DECIMAL(10,5)
BOOLEAN
DATE
DATETIME

Todos estos tipos de campo se pueden definir al crear una tabla, si bien, internamente,
SQLite los traduce por afinidad al tipo NUMERIC anterior.

4. Campo de tipo Memo. Es un campo de longitud variable que admite gran cantidad
de texto o datos binarios según nuestras necesidades. Para cada registro tendrá una longitud
distinta, según la cantidad de datos que se introduzcan en este campo. No admite índice.
SQLite admite únicamente BLOB.

6.1.3 Ventajas de las bases de datos

Hemos dicho que los archivadores de una biblioteca o de una agenda pueden
considerarse, en cierta forma, bases de datos, pues en ellos se almacena información en un
279
determinado orden y es posible buscar esta información, consultarla, modificarla o eliminarla
con facilidad.

Sin embargo, todas estas operaciones suelen llevar mucho tiempo y, en ocasiones, no
se efectúan tan fácilmente como desearíamos. Además, ocupan bastante espacio si la
información es abundante. Incluso, en ocasiones, algunas operaciones fundamentales son
imposibles de realizar manualmente.

Por ejemplo, si tenemos 1.000 fichas bibliográficas ordenadas por autor y necesitamos
ordenarlas por título, la operación ha de realizarse manualmente, mirando una a una cada
ficha, lo cual puede hacerse muy largo y pesado. Podíamos haber escrito dos ejemplares de
cada ficha, uno para el archivo por autores y otro para el de títulos, pero esto hubiera llevado
el doble de tiempo, de trabajo y éstas ocuparían el doble de espacio.

Supongamos ahora que necesitamos seleccionar todas las fichas en las que aparece
la misma editorial. De nuevo la tarea puede parecernos pesada y larga, y lo es. No digamos si
se cambia la situación de los libros en los armarios de la biblioteca. También será necesario
modificar la signatura en las fichas.

Hemos puesto este ejemplo para explicar los graves problemas que se derivan
de la gestión manual de la información. Las dificultades aumentan a medida que crece el
volumen de información que debe manejarse y según sean los criterios de ordenación y
selección.

En una base de datos informática, en cambio, al gestionarse la información


automáticamente, muchos de los problemas anteriormente mencionados desaparecen.

En primer lugar, la rapidez de las operaciones fundamentales (introducción de


datos, ordenación por diferentes campos, consultas, búsquedas, elaboración de informes,
actualización y modificación de los datos, etcétera) aumenta de una forma muy destacada.

En segundo lugar, el espacio que ocupa una base de datos es mucho menor que el
de cualquier otra forma de archivo manual. En un disco flexible de 3,5 pulgadas puede
almacenarse casi un millón y medio de caracteres. En los discos duros de los actuales
servidores el volumen de información puede ser prácticamente ilimitado.

En tercer lugar, las operaciones fundamentales de gestión de la información son


automáticas, lo cual hace que sean menos pesadas y tediosas si son llevadas a cabo por el
ordenador. Así pues, el trabajo se humaniza y el tiempo libre de las personas que manejan la
información es mayor.

Finalmente, la seguridad de los datos informatizados también es mayor que la


contenida en archivos de tipo manual, pues el ordenador nos permite hacer rápidamente
cuantas copias queramos de esa información en diferentes soportes.

Desde la aparición de los ordenadores, éstos se han dedicado al almacenamiento y


organización de grandes volúmenes de datos. Igualmente, se han aplicado a la evaluación de

280
Introducción al entorno Android

las diversas soluciones propuestas para resolver los problemas de estructuración y acceso a
dicha información.

6.1.4 Bases de datos relacionales

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


información de forma relacional. De aquí ha surgido el concepto de bases de datos
relacionales (RDBMS, Relation DataBase Management System).

El fundamento teórico de las bases de datos relacionales es complejo, ya que se


basa en el concepto matemático de relación entre los elementos de un conjunto. Sus
características y propiedades formales requieren ciertos conocimientos de la teoría de
conjuntos. Sin embargo, en la práctica, el concepto de relación es muy sencillo de utilizar
porque en ésta la organización de los datos es muy clara e intuitiva.

En otros tipos de organización de la información, como las bases de datos


jerárquicas o las bases de datos en red, anteriores a las relacionales, aparecían distintas
categorías de datos y estructuras muy complejas y poco flexibles que dificultaban la
posibilidad de relacionar éstos con eficacia y rapidez. En cambio, en las bases de datos
relacionales la información se organiza en ficheros que tienen estructura tabular o en
forma de tabla, en la que todos los datos tienen la misma categoría. Cada tabla también
recibe el nombre de relación.

Por ejemplo, en el gráfico siguiente puede observarse una tabla que contiene
diversos datos de personas:

Cabecera Nombre Dirección Edad Sexo Profesión

1 León García C/ Zurita, 25 25 V Admtvo.

2 María Pérez C/ Flores, 15 30 M Abogada

Filas
José
(Registros) C/ Río Sil, 11 50 V Dependiente
Rodríguez
3

4 Juana de Dios Avda. Canarias, 50 70 M Jubilada

5 Begoña López Pza. Segovia, s/n 15 M Estudiante

Columnas (Campos)

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

281
registro especial, denominado cabecera, que contiene los nombres de los campos y sus
atributos (tipo y longitud).

Generalmente, una base de datos no consta de una sola tabla, sino de varias.

Gráficamente puede representarse así:

Estas tablas no son independientes unas de otras, sino que tienen al menos un
campo común con las otras a través del cual se puede acceder a la información que
contienen todas en conjunto.

Por ejemplo, la base de datos de una biblioteca puede estar integrada por una tabla de
libros, otra de lectores, otra de préstamos y otra de editoriales. El fichero de libros puede
contener la información completa de cada volumen: título, autor, editorial, año de edición,
precio, número de páginas, código de materia, número de registro, etcétera.

El fichero de editoriales contendrá los datos de cada entidad editora: nombre,


dirección, teléfono, plazo de entrega, descuentos, etcétera.

El fichero de lectores estará integrado por los datos personales y profesionales de


éstos: nombre, DNI, dirección, teléfono, profesión, centro de trabajo, número de carné,
etcétera.

El fichero de préstamos contendrá datos de este tipo: número de registro del libro
prestado, número de carné del lector, fecha del préstamo, plazo, etcétera.

Como puede verse, la información no debe repetirse en todos los ficheros, pero sí
debe poder relacionarse. Por ejemplo, los ficheros de libros y editoriales, tienen en común el
campo EDITORIAL. Los ficheros de libros y préstamos tienen en común, al menos, el
NÚMERO DE REGISTRO del libro prestado, gracias a lo cual desde uno se puede acceder a
los datos del otro. Los ficheros de lectores y préstamos tienen en común el campo CARNÉ,
etcétera.
282
Introducción al entorno Android

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

6.1.5 Diseño de bases de datos

El diseño de bases de datos puede presentar distinto tipo de dificultad dependiendo


de la complejidad e interrelación de los datos que se quiera gestionar.

Imaginemos que una compañía aérea quiere gestionar toda la información contenida
en una base de datos relativa a los aviones y su mantenimiento, a los vuelos, viajes, destinos,
clientes, personal de la empresa, agencias de viajes, billetes, asistencia, etcétera. Es evidente
que, en este caso, la complejidad es enorme y que para realizar el diseño de esta base se
requiere la colaboración de técnicos especialistas que faciliten la tarea.

Sin embargo, en la mayoría de las ocasiones el diseño de una base de datos se


resuelve con uno, dos o tres ficheros como máximo. En este caso no es necesario profundizar
en aspectos complejos de técnicas de diseño, sino que basta aplicar el sentido común para
organizar los ficheros de la base de datos de forma coherente.

Deben crearse tantos ficheros como categorías o grupos de elementos distintos


haya que organizar. Por ejemplo, en una tienda que vende al por menor bastaría con crear un
fichero de artículos y otro de proveedores, y a lo sumo otros tres: de pedidos, de ventas y de
clientes.

Antes de ponerse a crear una base de datos con el ordenador, es preciso


diseñarla previamente sobre el papel. La planificación es fundamental en este caso para
evitar errores graves: falta de datos necesarios, repetición innecesaria de algunos,
equivocación del tipo de campo o falta de precisión en su longitud. Aunque es posible
modificar la estructura de una base de datos, una vez creada, se puede perder mucho tiempo
e incluso datos en esta operación.

Diseñar una base de datos consiste en determinar los datos que van a introducirse en
ella, la forma como se van a organizar y el tipo de esos datos. Además, se debe precisar la
forma como se van a solicitar y las clases de operaciones que hay que realizar con los
mismos: aritméticas, lógicas, de fechas, de carácter, etcétera. También conviene conocer los
resultados concretos que se espera obtener: consultas, informes, actualizaciones,
documentos, etcétera.

A continuación, se resumen las operaciones que deben llevarse a cabo al diseñar una
base de datos:

1. Atendiendo a la información que contiene es preciso:

• Identificar los diferentes elementos informativos (artículos, clientes, ventas,


facturas, etcétera) que forman parte de la base de datos.

283
• Determinar los datos que debe contener cada uno de esos elementos.

• Precisar el grado de necesidad y de utilización de cada dato.

• Concretar las operaciones que se van a realizar con los datos: aritméticas, lógicas,
de salida sólo por la pantalla, de salida también por la impresora, etcétera.

• Seleccionar el dato o datos esenciales que deben ser el campo clave por el que se
ordenarán las unidades o elementos mencionados.

• Fijar los datos comunes a los diferentes ficheros de la base de datos que van a
permitir relacionar la información distribuida entre ellos.

2. Atendiendo a la estructura de la base de datos

• Distribuir la información en ficheros según los diferentes grupos que se hayan


hecho (artículos, clientes, etcétera) y dar un nombre a cada fichero.

• Determinar el nombre de cada campo de los registros de cada fichero. Este


nombre ha de ser claro y debe significar algo para que pueda recordarse fácilmente.

• Decidir qué tipo conviene asignar a cada campo según la clase de operaciones
que vayamos a realizar con sus datos.

• Asignar a cada campo una longitud apropiada para tener los datos fundamentales
sin despilfarro de memoria interna ni de espacio en el disco duro o soporte
empleado.

• Establecer un orden lógico y práctico agrupando los campos según un criterio


concreto: clase e importancia de los datos, frecuencia de utilización, proximidad,
parecido, etcétera.

• Decidir cuál o cuáles van a ser los campos clave permanentes y situarlos al
principio de la estructura.

• No incluir campos que puedan ser el resultado de diversas operaciones de


tratamiento posterior.

• Fijar los campos comunes a todos los ficheros para poder relacionarlos con otros
de la misma aplicación.

6.2 SQLite en Android

6.2.1 Gestión de la información en Android

Como ya hemos estudiado, en Android existen tres formas de almacenar información


para usarla en las aplicaciones:

 Preferencias de la aplicación

284
Introducción al entorno Android

 Ficheros locales en el sistema de archivos del sistema operativo

 Base de datos SQLite

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

6.2.2 Gestión de la Base de Datos SQLite en Android

SQLite es un motor de bases de datos relacional muy popular por sus características,
que son muy especiales, como las siguientes:

 No necesita un servidor, ya que la librería se enlaza directamente en la


aplicación al compilarla.

 Ocupa muy poco tamaño: sólo unos 275 KB.

 Precisa de poca o nula configuración.

 Es posible hacer transacciones.

 Es de código libre.

Android incorpora todas las herramientas necesarias para la creación y gestión de


bases de datos SQLite mediante una API completa. Después iremos viendo otros comandos
para realizar consultas más complejas.

Usar bases de datos Android puede hacer más lentas las aplicaciones debido a que es
necesario escribir y leer información de la memoria física del dispositivo.

Por lo tanto, es recomendable realizar esta operaciones de forma Asíncrona, tal como hemos
estudiado en la Unidad 3 (Hilos). En los ejemplos de esta Unidad no vamos a incluir hilos, para
mostrar únicamente las sentencias de SQLite.

285
6.2.3 Creación de Bases de datos SQLite

La forma usual en Android de crear, modificar y conectar con una base de datos
SQLite consiste en usar la clase Java SQLiteOpenHelper. En realidad, debemos definir una
clase propia que derive de ella y personalizarla para adaptarnos a las necesidades concretas
de la aplicación.

La clase SQLiteOpenHelper define un único constructor que, normalmente, no es


necesario reescribir y los dos métodos abstractos onCreate() y onUpgrade() que tendremos
que implementar con el código Java necesario para crear la base de datos acorde con las
necesidades de la aplicación. También debemos hacerlo para modificar su estructura si hace
falta cambiar los campos que definen alguna tabla al actualizar la versión de la aplicación.

En el Ejemplo 1 de esta Unidad, vamos a crear una base de datos muy sencilla
llamada BDBiblioteca.db, con una única tabla interna llamada Ejemplares que albergará
únicamente cinco campos:

 _id: id registro de tipo INTEGER, índice PRIMARIO y autoincremental

 título: de tipo TEXT

 autor: de tipo TEXT

 año: de tipo INTEGER

 prestado: BOOLEAN

En el caso de Android es obligatorio definir un índice primario en la tabla con el identificador


“_id” para poder extraer, de manera sencilla, la información de los registros de una consulta
SQL mediante cursores usando la clase Cursor de Android.

Para esto, vamos a crear la clase BibliotecaSQLiteHelper derivada de


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

// La clase se debe heredar de SQLiteOpenHelper

public class BibliotecaSQLiteHelper extends SQLiteOpenHelper {

//Sentencia SQL para crear la tabla Ejemplares

static String createBDSQL = "CREATE TABLE Ejemplares (_id integer primary


key autoincrement, titulo TEXT, autor TEXT, anio TEXT,
prestado BOOLEAN)";

// Definimos el constructor indicando el contexto de la aplicación,

// el nombre de la base de datos y la versión de la BD

286
Introducción al entorno Android

public BibliotecaSQLiteHelper(Context contexto, String nombre,

CursorFactory factory, int version) {

super(contexto, nombre, factory, version);

@Override

// Si la BD no existe, Android llama a este método

public void onCreate(SQLiteDatabase db) {

//Se ejecuta la sentencia SQL de creación de la tabla

db.execSQL(createBDSQL);

@Override

public void onUpgrade(SQLiteDatabase db, int versionAnterior, int


versionNueva) {

/* NOTA: para simplificar este ejemplo eliminamos directamente la tabla

* anterior y la creamos de nuevo.

* Sin embargo, lo normal sería migrar los datos de la tabla antigua

* a la nueva estructura de campos, por lo que las sentencias podrían


* ser del estilo ALTER TABLE.

*/

//Se elimina la versión anterior de la tabla

db.execSQL("DROP TABLE IF EXISTS Ejemplares");

//Se crea la nueva versión de la tabla

db.execSQL(createBDSQL);

}
}

En el código fuente anterior se define la variable estática (en Java se definen así las
constantes) createBDSQL, donde se establece la orden SQL para crear la tabla llamada
Ejemplares con los campos alfanuméricos descritos anteriormente.

ATENCIÓN: en este curso no se describe la sintaxis del lenguaje SQL, pues se considera que
el alumno o alumna conoce cómo usar una base de datos relacional.

287
El método onCreate() se ejecuta automáticamente cuando es necesario crear la base
de datos, es decir, cuando aún no existe y se instala la aplicación por primera vez. Por lo
tanto, en este método debemos crear todas las tablas necesarias y añadir, si fuera necesario,
los registros iniciales.

Para la creación de la tabla hemos aplicado la sentencia SQL ya definida en la


constante y la ejecutamos en la base de datos utilizando el método más sencillo disponible en
la API SQLite de Android execSQL(SQL). Este método ejecuta directamente la orden SQL
incluida como parámetro.

Por otra parte, el método onUpgrade() se ejecuta automáticamente cuando sea


necesario actualizar la estructura de la base de datos al cambiar la versión de la aplicación que
la alberga.

Por ejemplo, desarrollamos la versión 1 de la aplicación que utiliza una tabla con los
campos descritos en el ejemplo anterior. Más adelante, ampliamos la funcionalidad de la
aplicación desarrollando la versión 2, que incluye en la tabla el campo "Editorial". Si un usuario
tiene instalada la versión 1 de la aplicación en su dispositivo Android, la primera vez que
ejecute la versión 2 de la aplicación hay que modificar la estructura de la tabla, para añadir el
nuevo campo; en este caso, Android ejecutará automáticamente el método onUpgrade().

Este método recibe como parámetros la versión actual de la base de datos en el


sistema y la nueva versión a la que se quiere convertir. En función de esta información
debemos realizar unas acciones u otras. Por ejemplo, modificar la tabla con la orden "ALTER
TABLE" para añadir el nuevo campo.

Una vez que hemos implementado la clase SQLiteOpenHelper, podemos abrir


fácilmente la base de datos desde la aplicación Android.

Lo primero que hacemos es crear un objeto de la clase BibliotecaSQLiteHelper al


que pasamos el contexto de la aplicación (en el ejemplo pasamos la referencia a la actividad
principal), el nombre de la base de datos, un objeto CursorFactory (más adelante veremos
cómo funcionan los cursores, en ese caso pasamos el valor null) y, por último, la versión de la
base de datos de la aplicación. Al crear este objeto pueden ocurrir varias cosas:

 Si ya existe la base de datos y su versión actual coincide con la solicitada, se


conecta con ella.

 Si la base de datos existe, pero su versión actual es anterior a la solicitada, se


invocará automáticamente al método onUpgrade(), para convertir la base de
datos a la nueva versión y conectarla con la base de datos convertida.

 Si la base de datos no existe, se llamará automáticamente al método


onCreate() para crearla y se conectará con la base de datos creada.

288
Introducción al entorno Android

Una vez obtenida una referencia al objeto BibliotecaSQLiteHelper, podemos invocar


el método getReadableDatabase() para realizar consultas a la base de datos o el método
getWritableDatabase() para llevar a cabo modificaciones, .

Después, utilizando el método execSQL(), podemos ejecutar órdenes SQL para


consultar o modificar registros. En el Ejemplo 1 se insertan cinco registros de prueba.

Por último, cerramos la conexión con la base de datos llamando al método close().

A continuación, vemos el aspecto que tiene el código fuente de la actividad principal:

//Abrimos la base de datos 'DBBiblioteca.db'

BibliotecaSQLiteHelper bibliodbh =

new BibliotecaSQLiteHelper(this, "DBBiblioteca.db", null, 1);

// Modo escritura

SQLiteDatabase db = bibliodbh.getWritableDatabase();

resultado.append("- La base de datos DBBiblioteca se ha abierto


correctamente.");

//Si se ha abierto correctamente la base de datos, entonces cargamos algunos


registros...

if(db != null)

// Hemos definido los datos en un fichero de recursos de la aplicación

Resources res = getResources();

String titulos[] = res.getStringArray(R.array.titulos);

String autores[] = res.getStringArray(R.array.autores);

String anios[] = res.getStringArray(R.array.anios);

String prestados[] = res.getStringArray(R.array.prestados);

//Insertamos 5 libros de ejemplo

for(int i=0; i<5; i++)

String SQLStr="INSERT INTO Ejemplares (titulo, autor, anio,


prestado) " + "VALUES ('" + titulos[i] +"', '" +
autores[i] +"', " + anios[i] +", '" + prestados[i] +"')";

resultado.append("\n- SQL ejecutada: "+SQLStr);

//Insertamos los datos en la tabla Ejemplares

db.execSQL(SQLStr);

289
//Cerramos la base de datos

db.close();

resultado.append("\n- La base de datos DBBiblioteca se ha cerrado.");


}

Android almacena los archivos de la base de datos en la memoria interna del


dispositivo, en un directorio determinado, siempre el mismo, sin que el programador pueda
cambiarlo. Es el siguiente:

/data/data/paquete_java/databases/nombre_del_fichero

En el ejemplo anterior se almacena en:

/data/data/es.mentor.unidad6.eje1.crearbd/databases/DBBiblioteca.db

Si ejecutas el Ejemplo 1 de esta Unidad, puedes comprobar en el DDMS cómo se


crea el fichero correctamente en el directorio indicado. Para acceder a esta herramienta en
Eclipse hay que hacer Clic en la opción del menú principal: Window -> Open Perspective ->
DDMS:

Desde Eclipse puedes abrir el proyecto Ejemplo 1 (Crear base de datos SQLite) de la
Unidad 6. Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del
programa anterior, en el que hemos utilizado los métodos de la base de datos SQLite.

Hemos visto que el fichero de la base de datos del Ejemplo 1 se ha creado en la ruta
correcta. Para comprobar que la tabla se ha creada correctamente y hemos insertado los
registros en la misma, podemos usar dos métodos:
290
Introducción al entorno Android

1. Transferir la base de datos a nuestro PC y consultarla con cualquier


administrador de bases de datos SQLite. Esto resulta un poco incómodo si
sólo necesitamos hacer una pequeña consulta, pero, a veces, es
imprescindible hacerlo para depurar errores en consultas SQL complejas.

2. Usar la consola de comandos del emulador de Android y recurrir a comandos


SQL para acceder y consultar la base de datos SQLite.

El primer método es simple. Podemos transferir el fichero de la base de datos a


nuestro PC utilizando el botón de descarga situado en la esquina superior derecha del
explorador de archivos (enmarcado en rojo en la imagen anterior). Al lado de este botón hay
otro botón para hacer la operación contraria, es decir, copiar un fichero local al sistema de
archivos del emulador. Además, hay otro botón para eliminar ficheros del emulador.

NOTA: a veces, al desarrollar una aplicación Android con bases de datos, el programador
debe eliminar a mano un fichero porque la estructura creada no es correcta y Android no
elimina el fichero automáticamente cada vez que cargamos la aplicación en el emulador de
Eclipse.

Una vez hemos descargado el fichero a nuestro PC, podemos utilizar cualquier
administrador de SQLite para abrir y consultar la base de datos.

En el segundo método accedemos de forma remota al emulador a través de su


consola de comandos (shell). Vamos a ver cómo hacerlo.

Ten en cuenta que para que este método funcione debemos haber incluido bien el
PATH del SDK de Android en el sistema operativo del PC donde trabajemos. En caso de duda,
conviene repasar el documento de Instalación de Android de este curso.

Con el emulador de Android ejecutándose, abrimos una consola de Windows (o del


sistema operativo correspondiente) y utilizamos la utilidad adb.exe (Android Debug Bridge)
situada en la carpeta platform-tools del SDK de Android.

En primer lugar, consultamos todos los identificadores de los emuladores en ejecución


mediante el comando "adb devices". Este comando debe devolver una única instancia con el
emulador abierto, que en el ejemplo se denomina “emulator-5554“.

Tras obtener este identificador del emulador activo vamos a acceder a su shell
mediante el comando “adb -s <identificador-del-emulador> shell“.

Una vez conectados a la consola del emulador, podemos acceder a la base de datos
utilizando el comando sqlite3 e indicando la ruta del fichero de la base de datos; en el caso
del ejemplo debemos escribir

“sqlite3 /data/data/es.mentor.unidad6.eje1.crearbd/databases/DBBiblioteca.db“.

A continuación, debe aparecer el prompt de SQLite “sqlite>“, que nos indica que ya
podemos escribir consultas SQL sobre la base de datos.

291
Vamos a comprobar que se han insertado bien los cinco registros del ejemplo en la
tabla Ejemplares. Para ello, escribimos la siguiente orden: “SELECT * FROM Ejemplares;“.

Si la orden está escrita correctamente, veremos el resultado en la pantalla; si no, se


mostrará el error correspondiente. En la imagen siguiente se muestra el resultado de todos los
comandos:

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

6.2.4 Modificación de la información de BD SQLite

La librería de SQLite incluida en Android proporciona dos formas para llevar a cabo
operaciones sobre una base de datos que no devuelven resultados. Por ejemplo, añadir,
actualizar y eliminar registros de una tabla; también se puede crear tablas, índices de
búsqueda, etcétera.

La primera forma. que ya la hemos visto anteriormente, consiste en usar el método


execSQL() de la clase SQLiteDatabase. Este método permite ejecutar cualquier orden SQL
sobre la base de datos siempre que no sea necesario obtener los resultados de la orden. Ya
hemos utilizado este método indicando como parámetro la cadena de texto de la orden SQL.

Aunque ya hemos visto en el Ejemplo 1 cómo se usa este método, a continuación,


mostramos algunos ejemplos más:

292
Introducción al entorno Android

//Insertar un registro
db.execSQL("INSERT INTO Ejemplares (titulo, autor, anio, prestado)
VALUES ('Título', 'Autor', 2001, 'false')”);

//Eliminar un registro

db.execSQL("DELETE FROM Ejemplares WHERE _id=1");

//Actualizar un registro

db.execSQL("UPDATE Ejemplares SET autor='Nombre' WHERE _id=1");

La segunda forma disponible en Android consiste en utilizar los métodos insert(),


update() y delete() proporcionados también por la clase SQLiteDatabase. Estos métodos
permiten añadir, actualizar y eliminar registros de la tabla mediante parámetros, el valor del
campo y las condiciones en que debe aplicarse la operación. Veamos un ejemplo de cada uno
de ellos:

6.2.4.1 Método insert()

Este método se usa para añadir nuevos registros en una tabla de la base de datos. Al
invocar insert (String table, String nullColumnHack, ContentValues values), es necesario
definir tres parámetros:

 table: nombre de la tabla en la que insertamos un registro.

 nullColumnHack: sólo es necesario en casos muy puntuales, por ejemplo, al


insertar registros completamente vacíos. Normalmente debemos indicar el
valor null en este segundo parámetro.

 values: valores del registro que se inserta.

Los valores que queremos insertar los pasamos como elementos de una colección de
tipo ContentValues. Esta colección es del tipo duplos de clave-valor, donde la clave es el
nombre del campo de la tabla y el valor es el dato que debemos insertar en dicho campo.
Veamos un ejemplo sencillo:

//Creamos el registro a partir del objeto ContentValues

ContentValues nuevoRegistro = new ContentValues();

nuevoRegistro.put("titulo", "Título de la obra");

nuevoRegistro.put("autor","Nombre del autor");


...

//Insertamos el registro en la tabla de la base de datos

db.insert("Ejemplares", null, nuevoRegistro);

293
Este método devuelve el campo ID del nuevo registro insertado o el valor -1 si ocurre
algún error durante la operación.

6.2.4.2 Método update() y método delete()

Estos métodos se usan para actualizar o borrar registros de una tabla. Los métodos
update (String table, ContentValues values, String whereClause, String[] whereArgs) y
delete(String table, String whereClause, String[] whereArgs) se invocan de manera
parecida a insert(). En estos métodos hay que usar el parámetro adicional whereArgs para
indicar la condición WHERE de la orden SQL.

Por ejemplo, para actualizar el autor del usuario de id 1 escribimos lo siguiente:

//Establecemos los campos-valores que actualizamos

ContentValues valores = new ContentValues();

valores.put("autor","Otro autor");

//Actualizamos el registro de la tabla

db.update("Ejemplares", valores, "_id=1");

En el tercer parámetro del método update() indicamos la condición tal como haríamos
en la cláusula WHERE en una orden UPDATE de SQL.

El método delete() se aplica de igual forma. Por ejemplo, para eliminar el registro 2
escribimos lo siguiente:

//Eliminamos el registro del _id 2

db.delete("Ejemplares", "_id=2");

De nuevo, indicamos como primer parámetro el nombre de la tabla y como segundo la


condición WHERE. Si queremos vaciar toda la tabla, podemos indicar null en este segundo
parámetro.

6.2.5 Uso de parámetros en los métodos SQLite

En el caso de los métodos execSQL(), update() y delete() de SQLiteDatabase


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

294
Introducción al entorno Android

Estos argumentos son piezas variables de la sentencia SQL, en forma de matriz, que
evitan tener que construir una sentencia SQL concatenando cadenas de texto y variables para
formar la orden final SQL.

Estos argumentos SQL se indican con el símbolo ‘?’ y los valores de dichos
argumentos deben pasarse en la matriz en el mismo orden que aparecen en la sentencia SQL.
Fíjate en el siguiente ejemplo:

//Elimina un registro con execSQL() utilizando argumentos

String[] args = new String[]{"Nombre de autor"};

db.execSQL("DELETE FROM Ejemplares WHERE autor=?", args);

//Actualiza dos registros con update() utilizando argumentos

ContentValues valores = new ContentValues();

valores.put("Título 1","Título 2");

String[] args = new String[]{“1”, "2"};

db.update("Ejemplares", valores, "_id=? OR _id=?", args);

6.3 Consultas SQLite en Android

6.3.1 Selección y recuperación de Consultas de BD SQLite

A continuación, vamos a describir la manera de hacer consultas a una base de datos


SQLite desde Android y de extraer la información de datos del resultado.

Existen dos formas de buscar y recuperar registros de una base de datos SQLite. La
primera de ellas consiste en utilizar directamente un comando de consulta SQL; la segunda
forma consiste en utilizar un método específico con parámetros de consulta a la base de
datos.

La primera forma se basa en la utilización del método rawQuery() de la clase


SQLiteDatabase, que ya hemos estudiado en el apartado anterior. En este indicamos
directamente como parámetro el comando SQL que queremos usar en la consulta señalando
los campos seleccionados y los criterios de selección.

El resultado de la consulta lo obtenemos en forma de Cursor. La clase Cursor


permite acceder en modo lectura/escritura a los resultados devueltos por una consulta a la
base de datos. Esta clase Cursor se puede usar con varios hilos (subprocesos) para obtener
asíncronamente información de una BD.

Fíjate en el siguiente ejemplo:

Cursor c = db.rawQuery(" SELECT autor,titulo FROM Ejemplares WHERE _id=1");

295
Tal y como hemos visto anteriormente en algunos métodos de modificación de datos,
también es posible incluir en este método una lista de argumentos variables que indicamos en
la orden SQL con el símbolo ‘?‘; por ejemplo, así:

String[] args = new String[] {"1"};

Cursor c =
db.rawQuery(" SELECT autor,titulo FROM Ejemplares WHERE _id=? ", args);

La segunda forma de obtención de datos se basa en utilizar el método query() de la


clase SQLiteDatabase. Este método query (String table, String[] columns, String selection,
String[] selectionArgs, String groupBy, String having, String orderBy, String limit) se
invoca con varios parámetros:

 table: nombre de la tabla consultada

 columns: matriz con los nombres de los campos seleccionados

 selection: cláusula WHERE del lenguaje SQL

 selectionArgs: matriz con los argumentos variables incluidos en el WHERE o


null si no se indican.

 groupBy: cláusula GROUP BY si existe; si no, escribimos null.

 having: cláusula HAVING si existe; si no, escribimos null.

 orderBy: cláusula ORDER BY si existe; si no, escribimos null.

 limit: número máximo de registros devueltos por la consulta.

Veamos el mismo ejemplo anterior utilizando el método query():

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

String[] args = new String[] {"1"};

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

Tanto en la primera forma como en la segundo, ambos métodos devuelven como


resultado de su ejecución un objeto de tipo Cursor, que debemos recorrer para procesar los
registros obtenidos.

La clase Cursor dispone de varios métodos para recorrer y manipular los registros
devueltos por la consulta. Entre ellos podemos destacar dos de los dedicados a recorrer el
cursor de forma secuencial y en orden natural:

 moveToFirst(): mueve el puntero del cursor al primer registro devuelto. Si no


hay ningún primer registro este método devuelve false.

296
Introducción al entorno Android

 moveToNext(): mueve el puntero del cursor al siguiente registro devuelto. Si


no hay ningún registro después, este método devuelve false.

 moveToPrevious(): mueve el cursor al registro anterior. Si no hay ningún


registro anterior, este método devuelve false.

 getCount(): devuelve el número de registros devueltos por la consulta.

 getColumnIndexOrThrow(String columna): devuelve el índice de la columna


dada o lanza la excepión IllegalArgumentException si no existe la columna.

 getColumnName(int indice): devuelve el nombre de la columna indicada en el


índice dado.

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


seleccionadas.

 moveToPosition(int posicion): mueve el cursor al registro que hay en esa


posición. Si no hay ningún registro en esa posición, este método devuelve
false.

 getPosition(): devuelve la posición actual del cursor.

Una vez colocado el cursor en el registro que queremos leer, podemos utilizar
cualquiera de los métodos getXXX(índice_columna) existentes para cada tipo de dato y así
recuperar el dato de cada campo de ese registro.

Por ejemplo, si queremos recuperar la segunda columna del registro actual y ésta
contiene un campo alfanumérico, usamos la sentencia getString(1).

La primera columna de la consulta tiene el índice 0, la segunda columna tiene índice 1 y así
sucesivamente.

En el caso de que la columna contenga un dato de tipo real, ejecutaríamos la


sentencia getDouble(1).

Teniendo todo esto en cuenta, veamos, a continuación, cómo recorrer todos los
registros devueltos por la consulta del ejemplo anterior usando un cursor:

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

String[] args = new String[] {"1"};

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

//Comprobamos que existe, al menos, un registro

if (c.moveToFirst()) {

297
//Recorremos el cursor mientras haya registros sin leer

do {

String autor = c.getString(0);

String titulo = c.getString(1);

} while(c.moveToNext());

Además de los métodos comentados de la clase Cursor, existen muchos más


métodos que pueden ser muy útiles. El alumno o la alumna puede consultar la lista completa
en la documentación oficial de la clase Cursor.

Como hemos comentado ya en esta Unidad, las bases de datos SQLite de una
aplicación son siempre privadas e internas a esta aplicación. Para que el resto de aplicaciones
pueda acceder a la información de la BD, Android define los Content Provider. En la Unidad
7 tratamos este tema en profundidad.

Además, se pueden tratar datos dinámicos de una base de datos usando la clase de
Android SQLiteQueryBuilder. Esta clase es similar a la interfaz de un proveedor de
contenidos, por lo que suele utilizarse conjuntamente con los Content Providers.

 
NOTA: Existen funcionalidades más avanzadas de gestión de BD con Android, como la
utilización de transacciones, pero no vamos a tratarlas en este curso por considerarse
programación avanzada.

 
6.3.2 Ejemplo práctico de BD SQLite con Android

A continuación, vamos a mostrar mediante un ejemplo completo cómo se usan todos


los métodos de acceso a base de datos que hemos estudiado hasta ahora.

Desde Eclipse puedes abrir el proyecto Ejemplo 2 (Notas) de la Unidad 6. Estudia el código
fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior, en el que hemos
utilizado métodos de la base de datos SQLite.

Si ejecutas la aplicación, verás que tiene el siguiente aspecto:

298
Introducción al entorno Android

Se trata de una aplicación donde un usuario puede gestiona notas sencillas por
categorías. Estas notas se almacenan en la base de datos "bdnotas.db" en la tabla "notas"
que tiene la siguiente estructura:

 _id: índice de registro de tipo entero con índice primario autoincremental

 categoria: texto no nulo

 titulo: texto no nulo

 descripcion: texto no nulo

La aplicación está formada por dos actividades: la primera muestra todas las notas en
un listado y la segunda permite editarlas o dar de alta una nueva. Ambas actividades se
interconectan con Intents invocados de manera explícita.

Para mostrar el listado con las notas en la actividad principal hemos heredado la clase
ListActivity. Como ya hemos visto anteriormente en el curso, esta clase define un ListView
interno. Podemos conectarlo con la clase Cursor, que devuelve los resultados de las consultas
a la BD, usando la clase SimpleCursorAdapter de Android.

Veamos cómo hacerlo en la práctica:

6.3.3 Acceso y creación de la Base de datos

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


aplicación es necesario crear una clase heredada de SQLiteOpenHelper. En este ejemplo
hemos definido la clase NotasBDHelper:

299
public class NotasBDHelper extends SQLiteOpenHelper {

// Definimos el nombre y la versión de la BD

private static final String BD_NOMBRE = "bdnotas.db";

private static final int BD_VERSION = 1;

// SQL que crea la base de datos

// Es muy importante usar el campo _id

private static final String BD_CREAR = "create table notas (_id integer
primary key autoincrement, " + "categoria text not null, titulo
text not null, descripcion text not null);";

// Contructor de la clase

public NotasBDHelper(Context context) {

super(context, BD_NOMBRE, null, BD_VERSION);

// Método invocado por Android si no existe la BD

@Override

public void onCreate(SQLiteDatabase database) {

// Creamos la estructura de la BD

database.execSQL(BD_CREAR);

// Método invocado por Android si hay un cambio de versión de la BD

@Override

public void onUpgrade(SQLiteDatabase database, int oldVersion,

int newVersion) {

// Eliminamos la BD y la volvemos a crear otra vez

database.execSQL("DROP TABLE IF EXISTS notas");

onCreate(database);

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

300
Introducción al entorno Android

Dentro de esta clase hemos definido el método abrir(), que se conecta a la base de
datos utilizando la clase NotasBDHelper.

Para actualizar y dar de alta registros hemos usado un argumento del tipo
ContentValues, que hemos estudiado en el apartado anterior.

class NotasBDAdapter {

// Campos de la BD

public static final String CAMPO_ID = "_id";

public static final String CAMPO_CATEGORIA = "categoria";

public static final String CAMPO_TITULO = "titulo";

public static final String CAMPO_DESCRIPCION = "descripcion";

private static final String TABLA_BD = "notas";

private Context contexto;

private SQLiteDatabase basedatos;

private NotasBDHelper bdHelper;

public NotasBDAdapter(Context context) {

this.contexto = context;

// Método que abre la BD

public NotasBDAdapter abrir() throws SQLException {

// Abrimos la base de datos en modo escritura

bdHelper = new NotasBDHelper(contexto);

basedatos = bdHelper.getWritableDatabase();

return this;

// Método que cierra la BD

public void cerrar() {

bdHelper.close();

// Método que crear una nota. Devuelve el id del registro nuevo si se ha


// dado de alta correctamente o -1 si no.

public long crearNota(String categoria, String titulo, String descripcion)


{

// Usamos un argumento variable para añadir el registro


301
ContentValues initialValues = crearContentValues(categoria, titulo,

descripcion);

// Usamos la función insert del SQLiteDatabase

return basedatos.insert(TABLA_BD, null, initialValues);

// Método que actualiza una nota

public boolean actualizarNota(long id, String categoria, String titulo,

String descripcion) {

// Usamos un argumento variable para modificar el registro

ContentValues updateValues = crearContentValues(categoria, titulo,

descripcion);

// Usamos la función update del SQLiteDatabase

return basedatos.update(TABLA_BD, updateValues, CAMPO_ID + "=" + id,


null) > 0;

// Método que borra una nota

public boolean borraNota(long id) {

// Usamos la función delete del SQLiteDatabase

return basedatos.delete(TABLA_BD, CAMPO_ID + "=" + id, null) > 0;

// Devuelve un Cursor con la consulta a todos los registros de la BD

public Cursor obtenerNotas() {

return basedatos.query(TABLA_BD, new String[] { CAMPO_ID,


CAMPO_CATEGORIA, CAMPO_TITULO, CAMPO_DESCRIPCION },

null, null, null, null, null);

// Devuelve la Nota del id

public Cursor getNota(long id) throws SQLException {

Cursor mCursor = basedatos.query(true, TABLA_BD, new String[] {

CAMPO_ID, CAMPO_CATEGORIA, CAMPO_TITULO, CAMPO_DESCRIPCION },

CAMPO_ID + "=" + id, null, null, null, null, null);

// Nos movemos al primer registro de la consulta

if (mCursor != null) {

mCursor.moveToFirst();

302
Introducción al entorno Android

return mCursor;

// Método que crea un objeto ContentValues con los parámetros indicados

private ContentValues crearContentValues(String categoria, String titulo,

String descripcion) {

ContentValues values = new ContentValues();

values.put(CAMPO_CATEGORIA, categoria);

values.put(CAMPO_TITULO, titulo);

values.put(CAMPO_DESCRIPCION, descripcion);

return values;

6.3.4 Recursos de diseño XML

A continuación, indicamos los ficheros XML de Layout que componen el diseño de la


interfaz del usuario:

 res/menu/menu_listado.xml: define el diseño del menú principal de la


aplicación.

 res/layout/main.xml: define el diseño de la pantalla de la actividad principal


NotasActivity.

 res/layout/editar_nota.xml: define el diseño de la actividad secundaria


GestionarNota, que sirve para editar y dar de alta notas.

 res/layout/fila_notas.xml: define el diseño de los elementos del ListView de


la actividad principal, es decir, el estilo de cada nota en el listado.

El alumno o alumna puede abrir estos ficheros en su ordenador y ver cómo están
implementados los distintos diseños.

Además, se definen los dos ficheros strings.xml y categorias.xml en la carpeta


res/values con los literales que usa la aplicación.

303
6.3.5 Actividades

Como hemos comentado, la aplicación está formada por dos actividades: la actividad
principal (NotasActivity) muestra un listado con todas las notas y la segunda (GestionarNota)
sirve para editarlas o dar de alta una nueva.

Veamos el contenido de la actividad principal:

public class NotasActivity extends ListActivity {

private NotasBDAdapter bdHelper;

private static final int ACTIVIDAD_NUEVA = 0;

private static final int ACTIVIDAD_EDITAR = 1;

private static final int MENU_ID = Menu.FIRST + 1;

private Cursor cursor;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

// Hacemos más ancha la línea de división entre elementos en el


listado

this.getListView().setDividerHeight(3);

// Creamos el adaptador que conecta con la BD

bdHelper = new NotasBDAdapter(this);

// Cargamos todos los datos

bdHelper.abrir();

cargaDatos();

// Indicamos el menú contextual asociado al listado

registerForContextMenu(getListView());

// Creamos el menú principal

@Override

public boolean onCreateOptionsMenu(Menu menu) {

MenuInflater inflater = getMenuInflater();

inflater.inflate(R.menu.menulistado, menu);

304
Introducción al entorno Android

return true;

// El usuario hace clic en una opción del menú principal

@Override

public boolean onMenuItemSelected(int id, MenuItem item) {

// Buscamos la opción del menú principal seleccionada

switch (item.getItemId()) {

case R.id.insertar:

// Creamos una actividad indicando el tipo de petición


// "ACTIVIDAD_NUEVA" y esperamos el resultado de la misma

Intent i = new Intent(this, DetallesNota.class);

startActivityForResult(i, ACTIVIDAD_NUEVA);

// Indicamos que hemos manejado la opción del menú

return true;

return super.onMenuItemSelected(id, item);

// El usuario hace clic en una opción del menú contextual del listado

@Override

public boolean onContextItemSelected(MenuItem item) {

// Buscamos la opción del menú contextual seleccionada

switch (item.getItemId()) {

case MENU_ID:

// Obtenemos el id del elemento seleccionado

AdapterContextMenuInfo info = (AdapterContextMenuInfo)


item.getMenuInfo();

// Borramos ese registro

bdHelper.borraNota(info.id);

// Recargamos los datos

cargaDatos();

// Indicamos que hemos manejado la opción del menú

return true;

return super.onContextItemSelected(item);

305
}

// Cuando hacemos clic en un elemento del listado, se edita la Nota

@Override

protected void onListItemClick(ListView l, View v, int position, long id) {

super.onListItemClick(l, v, position, id);

// Creamos una actividad indicando el tipo de petición


// "ACTIVIDAD_EDITAR" y esperamos el resultado de la misma

Intent i = new Intent(this, DetallesNota.class);

// Pasamos el campo _id como un dato extra

i.putExtra(NotasBDAdapter.CAMPO_ID, id);

startActivityForResult(i, ACTIVIDAD_EDITAR);

// Método que se llama cuando una subactividad devuelve el resultado

@Override

protected void onActivityResult(int requestCode, int resultCode,

Intent intent) {

super.onActivityResult(requestCode, resultCode, intent);

// Recargamos los datos si se ha modificado algo.

// Es decir, el usuario ha hecho clic en OK

if (resultCode == Activity.RESULT_OK) cargaDatos();

private void cargaDatos() {

cursor = bdHelper.obtenerNotas();

// Se indica que a la Actividad principal que controle los recursos

// cursor. Es decir, si se termina la Actividad, se elimina esta


// Cursor de la memoria

startManagingCursor(cursor);

// Indicamos cómo debe pasarse el campo título de (from) a (to)

// la Vista de la opción (fila_notas.xml)

String[] from = new String[] { NotasBDAdapter.CAMPO_CATEGORIA,


NotasBDAdapter.CAMPO_TITULO };

int[] to = new int[] { R.id.fila_categoria, R.id.fila_titulo };

306
Introducción al entorno Android

// Creamos un sencillo adaptador de tipo Matriz

// asociado al cursor

SimpleCursorAdapter notas = new SimpleCursorAdapter(this,

R.layout.fila_notas, cursor, from, to);

// Indicamos al listado el adaptador que le corresponde

setListAdapter(notas);

// Creamos el menú contextual

@Override

public void onCreateContextMenu(ContextMenu menu, View v,

ContextMenuInfo menuInfo) {

super.onCreateContextMenu(menu, v, menuInfo);

menu.add(0, MENU_ID, 0, R.string.menu_borrar);

// Cuando se acaba la Actividad cerramos la BD

// Es muy importante hacer esto para que se escriba toda la información

@Override

protected void onDestroy() {

super.onDestroy();

if (bdHelper != null) {

bdHelper.cerrar();

A continuación, vamos a ver el código de la Actividad secundaria o subactividad:

public class GestionarNota extends Activity {

private EditText tituloText;

private EditText descripcionText;


307
private Spinner categoriaSpinner;

// Usamos esta variable para saber si estamos editando (filaId=id) o

// se trata de un registro nuevo (filaId=null)

private Long filaId;

private NotasBDAdapter bdHelper;

@Override

protected void onCreate(Bundle bundle) {

super.onCreate(bundle);

// Creamos un adaptador u abrimos la BD

bdHelper = new NotasBDAdapter(this);

bdHelper.abrir();

// Dibujamos el UI y buscamos sus Vistas

setContentView(R.layout.editar_nota);

categoriaSpinner = (Spinner) findViewById(R.id.category);

tituloText = (EditText) findViewById(R.id.nota_editar_titulo);

descripcionText = (EditText)
findViewById(R.id.nota_editar_descripcion);

Button aceptaBoton = (Button) findViewById(R.id.nota_editar_boton);

// Variable con el ID del registro actual

filaId = null;

// Obtenemos el campo ID que se debe haber pasado en la invocación

// de la actividad si estamos editando el registro

Bundle extras = getIntent().getExtras();

// Si extras contiene algo cargamos ese ID

if (extras != null) {

filaId = extras.getLong(NotasBDAdapter.CAMPO_ID);

// Cargamos el registro en los componentes de la pantalla

cargarRegistro();

// Método del botón OK

aceptaBoton.setOnClickListener(new View.OnClickListener() {

308
Introducción al entorno Android

public void onClick(View view) {

// Si pulsa este botón guardamos los datos y devolvemos OK a la


Actividad

String categoria = (String) categoriaSpinner.getSelectedItem();

String titulo = tituloText.getText().toString();

String descripcion = descripcionText.getText().toString();

// Alta de registro

if (filaId == null) {

bdHelper.crearNota(categoria, titulo, descripcion);

} else { // Modificación de registro

bdHelper.actualizarNota(filaId, categoria, titulo,


descripcion);

setResult(RESULT_OK);

// Acabamos la actividad

finish();

});

} // end onCreate

private void cargarRegistro() {

if (filaId != null) {

Cursor nota = bdHelper.getNota(filaId);

// Volvemos a dejar que la actividad actual controle el Cursos

startManagingCursor(nota);

// Obtenemos el campo categoria

String categoria = nota.getString(


nota.getColumnIndexOrThrow(NotasBDAdapter.CAMPO_CATEGORIA));

for (int i=0; i<categoriaSpinner.getCount();i++){

// Cargamos una de la opciones del listado desplegable

309
String s = (String) categoriaSpinner.getItemAtPosition(i);

// Si coindice con la que está en la BD la seleccionamos


en el listado desplegable

if (s.equalsIgnoreCase(categoria)){

categoriaSpinner.setSelection(i);

break;

// Rellenamos las Vistas de Título y Descripción

tituloText.setText(nota.getString(
nota.getColumnIndexOrThrow(NotasBDAdapter.CAMPO_TITULO)));

descripcionText.setText(nota.getString(
nota.getColumnIndexOrThrow(NotasBDAdapter.CAMPO_DESCRIPCION)));

} // end cargarRegistro

6.3.6 Fichero Androidmanifest.xml

Para que la subactividad GestionarNota esté disponible en el sistema operativo,


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

<activity android:name=".GestionarNota"

android:windowSoftInputMode="stateVisible|adjustResize">

</activity>

El atributo android:windowSoftInputMode indica cómo interacciona esta


subactividad con el teclado flotante de Android:

310
Introducción al entorno Android

El establecimiento de este atributo afecta a dos aspectos del teclado:

 Al estado del teclado de la pantalla, es decir, si está oculto o visible cuando la


actividad está en primer plano y el usuario interacciona con ella.

 Al ajuste que sufren los componentes de la ventana principal de la actividad


para que el teclado quepa en la pantalla, es decir, si se ajusta el contenido
para dejar espacio al teclado o el contenido se mantiene intacto y el tecla
"flota" sobre éste.

En este ejemplo hemos usado las opciones stateVisible y adjustResize para que el
teclado se muestre cuando el usuario acceda a un componente de introducción de texto y
cambie las proporciones de la pantalla para hacer un "hueco" al teclado.

En la ayuda oficial de Android puedes encontrar todos los posibles valores con su
descripción.

6.4 GESTIÓN DE FICHEROS XML

EXtensible Markup Language (XML) es un formato de datos que se usa comúnmente


en las aplicaciones web modernas. XML utiliza etiquetas personalizadas para describir los
tipos de datos y se codifica como texto sin formato, por lo que es muy flexible y sencillo de
utilizar. Android incluye bibliotecas de clases diseñadas para el procesamiento de datos en
formato XML.

Los tres modelos más extendidos para leer y escribir ficheros de tipo XML son DOM
(Document Object Model), SAX (Simple API for XML) y StAX (Streaming API for XML):

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

 SAX: en este modelo, basado en eventos, la aplicación recorre todos los


elementos del archivo XML de una sola vez. La ventaja respecto al modelo
anterior consiste en que es más rápido y requiere menos memoria, si bien no
permite el acceso aleatorio a una de sus ramas.

 StAX: es una mezcla de las dos modelos anteriores. En este caso, también se
lee el fichero XML de forma secuencial, pero podemos controlar la forma en
que se leen sus elementos. En el caso de SAX es obligatorio leer todos los
elementos a la vez. Este modelo es también mucho más rápido que DOM,
pero algo más lento de SAX.

Un analizador sintáctico (en inglés parser) convierte el texto de entrada en otras


estructuras (comúnmente árboles), que son más útiles para el posterior análisis y capturan la
jerarquía implícita de la entrada.

Android dispone de analizadores XML para estos tres modelos. Con cualquiera de
ellos podemos hacer las mismas tareas. Ya veremos más adelante que, dependiendo de la
naturaleza de la aplicación, es más eficiente utilizar un modelo u otro.

Estas técnicas se pueden utilizar para leer cualquier documento XML, tanto de Internet
como del sistema de archivos. En el Ejemplo 3 de esta Unidad vamos a leer datos XML de un
documento RSS de un periódico; concretamente, del canal RSS de noticias de
20minutos.es.

Puedes modificar esta dirección cambiado la variable de la Actividad principal


XMLActivity:

static String feedUrl =

"http://20minutos.feedsportal.com/c/32489/f/478284/index.rss";

Si abrimos el documento RSS de esta fuente de noticias (en inglés feed), vemos la
estructura siguiente:

<rss version="2.0">

<channel>

<title>20minutos.es</title>

<link> http://www.20minutos.es/</link>

<description> Diario de información general y ...</description>

<language>es-ES</language>

<pubDate> Fri, 28 Oct 2011 18:54:41 GMT</pubDate>

312
Introducción al entorno Android

<lastBuildDate> Fri, 28 Oct 2011 18:54:41 GMT</lastBuildDate>

<item>

<title>Título de la noticia 1</title>

<link>http://link_de_la_noticia_2.es</link>

<description>Descripción de la noticia 2</description>

<pubDate>Fecha de publicación 2</pubDate>

</item>

<item>

<title>Título de la noticia 2</title>

<link>http://link_de_la_noticia_2.es</link>

<description>Descripción de la noticia 2</description>

<pubDate>Fecha de publicación 2</pubDate>

</item>

...

</channel>

</rss>

Como puedes observar, se compone de un elemento principal <channel>, seguido de


varios datos relativos al canal y, posteriormente, de una lista de elementos <item> para cada
noticia.

En este apartado vamos a describir cómo leer este archivo XML sirviéndonos de cada
una de las tres alternativas citadas anteriormente.

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (XML) de la Unidad 6. Estudia el código
fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior, en el que hemos
utilizado métodos de lectura del formato XML.

Si ejecutas la aplicación en el emulador de Android, verás que tiene el siguiente


aspecto:

313
Para implementar la Actividad principal, hemos usado la clase ListActivity, donde
mostraremos un listado con las noticias.

Para empezar, en primer lugar debemos definir una clase Java para almacenar los
datos leídos de una noticia. Para cargar el listado de la clase ListActivity con los titulares de
las noticias usamos una lista de objetos de este tipo. Veamos el código fuente de esta clase
que hemos denominado Noticia:

// Clase que sirve para cargar en un objeto cada noticia que leamos del
fichero XML

public class Noticia {

// Dispone de las variables y métodos típicos de una clase sencilla

private String titulo;

private URL enlace;

private String descripcion;

private String fecha;

public URL getEnlace() {

return enlace;

public void setEnlace(String enlace) {

// Intentamos cargar el enlace en forma de URL.

// Si tenemos un error lanzamos una excepción

314
Introducción al entorno Android

try {

this.enlace = new URL(enlace);

} catch (MalformedURLException e) {

throw new RuntimeException(e);

public void setFecha(String fecha) {

while (!fecha.endsWith("00")){

fecha += "0";

this.fecha = fecha.trim();

public String getFecha() {

return this.fecha;

public String getTitulo() {

return titulo;

public void setTitulo(String titulo) {

this.titulo = titulo;

public String getDescripcion() {

return descripcion;

public void setDescripcion(String descripcion) {

this.descripcion = descripcion;

Por simplificación, hemos tratado todos los datos como cadenas de texto.

315
6.4.1 SAX es el modelo clásico en Android

En el modelo clásico de SAX el tratamiento de un archivo XML se basa en un


analizador (parser) que lee secuencialmente el documento XML y va generando diferentes
eventos con la información de cada elemento leído.

Por ejemplo, a medida que lee el documentos XML, si el analizador encuentra una
etiqueta <title> lanzará el método startElement() del parser de inicio de etiqueta con la
información asociada. Si después de esa etiqueta encuentra una cadena de texto, invocará el
método characters() del parser con toda la información necesaria.

Por lo tanto, debemos implementar las sentencias necesarias para tratar cada uno de
los métodos posibles que el analizador puede lanzar durante la lectura del documento XML.

Los principales métodos que se pueden producir son los siguientes:

 startDocument(): comienza el documento XML.

 endDocument(): termina el documento XML.

 startElement(): comienza una etiqueta XML.

 endElement(): termina una etiqueta XML.

 characters(): se ha encontrado una cadena de texto.

Puedes encontrar la lista completa de los métodos en este enlace.

Estos métodos se definen en la clase org.xml.sax.helpers.DefaultHandler. Por esto


hay que heredar esta clase y sobrescribir los métodos necesarios. En este ejemplo la clase se
llama ParserSaxClasicoHandler:

public class ParserSaxClasicoHandler extends DefaultHandler{

// Variables temporales que usamos a lo largo del Handler

// Listado completo de las noticias

private List<Noticia> noticias;

// Noticia que estamos leyendo en ese momento

private Noticia noticiaActual;

// Variable temporal para almacenar el texto contenido en una etiqueta

private StringBuilder sbTexto;

// Método que devuelve todo el listado de noticias

public List<Noticia> getNoticias(){

316
Introducción al entorno Android

return noticias;

// Método que se lanza al iniciar la lectura de un XML

@Override

public void startDocument() throws SAXException {

// Lanzamos el método de la clase madre

super.startDocument();

// Iniciamos los variables temporales

noticias = new ArrayList<Noticia>();

sbTexto = new StringBuilder();

// Comienza una etiqueta XML

@Override

public void startElement(String uri, String localName,

String name, Attributes attributes) throws SAXException {

// Lanzamos el método de la clase madre

super.startElement(uri, localName, name, attributes);

// Si leemos una nueva etiqueta item es que empieza una noticias

if (localName.equals(EtiquetasRSS.ITEM)) {

noticiaActual = new Noticia();

// Finaliza una etiqueta XML

@Override

public void endElement(String uri, String localName, String name)

throws SAXException {

// Lanzamos el método de la clase madre

super.endElement(uri, localName, name);

// Si estamos leyendo una noticia

if (this.noticiaActual != null) {

317
// Cargamos el campo correspondiente de la etiqueta que acabamos
de leer

if (localName.equals(EtiquetasRSS.TITLE)) {

noticiaActual.setTitulo(sbTexto.toString());

} else if (localName.equals(EtiquetasRSS.LINK)) {

noticiaActual.setEnlace(sbTexto.toString());

} else if (localName.equals(EtiquetasRSS.DESCRIPTION)) {

noticiaActual.setDescripcion(sbTexto.toString());

} else if (localName.equals(EtiquetasRSS.PUB_DATE)) {

noticiaActual.setFecha(sbTexto.toString());

} else if (localName.equals(EtiquetasRSS.ITEM)) {

// Si leemos el final de la etiqueta "item" añadimos la


noticia al listado

noticias.add(noticiaActual);

// Reiniciamos la variable temporal de texto

sbTexto.setLength(0);

// Se ha encontrado una cadena de texto

@Override

public void characters(char[] ch, int start, int length)

throws SAXException {

// Lanzamos el método de la clase madre

super.characters(ch, start, length);

// Si estamos leyendo una noticia

if (this.noticiaActual != null)

// Asignamos el texto a la variable temporal

sbTexto.append(ch, start, length);

318
Introducción al entorno Android

En el código fuente anterior usamos la lista de noticias List<Noticia> para almacenar


todas la noticias leídas y el método getNoticias() las devuelve al finalizar la lectura del
documento.

Después, hay que implementar los métodos SAX necesarios.

Una vez hemos implementado nuestro handler, vamos a crear la nueva clase
ParserSaxClasico que hace uso de este handler para analizar un documento XML en
concreto usando el modelo SAX.

Esta clase únicamente define un constructor que recibe como parámetro la dirección
de Internet del documento XML que hay que analizar. El método público analizar() analiza el
documento XML y devuelve como resultado una lista de noticias. Veamos cómo queda esta
clase:

public class ParserSaxClasico implements RSSParser {

// URL del archivo XML

private URL feedUrl;

// Constructor de la clase, se asigna la URL a la variable local

protected ParserSaxClasico(String feedUrl){

try

this.feedUrl= new URL(feedUrl);

catch (MalformedURLException e)

throw new RuntimeException(e);

// Método que lee el documento XML

public List<Noticia> analizar() {

// Creamos acceso a la API Sax de Android

SAXParserFactory factory = SAXParserFactory.newInstance();

try {

// Creamos un analizador (parser) de SAX

319
SAXParser parser = factory.newSAXParser();

// Creamos el handle de SAX que implementamos en otra clase

ParserSaxClasicoHandler handler = new ParserSaxClasicoHandler();

// Analizamos el archivo con el handler anterior

parser.parse(getInputStream(), handler);

// Devolvemos las noticias encontradas

return handler.getNoticias();

} catch (Exception e) {

throw new RuntimeException(e);

// Método que abre una conexión a la URL y devuelve el

// puntero de tipo fichero al analizador correspondiente

private InputStream getInputStream()

try

return feedUrl.openConnection().getInputStream();

catch (IOException e)

throw new RuntimeException(e);

El constructor de la clase anterior acepta como parámetro una dirección URL del
documento XML que analiza y controla la validez de dicha dirección generando una
excepción si no puede crear la clase URL correspondiente.

Por otra parte, el método analizar() es el encargado de crear un nuevo parser SAX y
de iniciar el proceso de análisis pasando al parser una instancia del handler que hemos
creado anteriormente con una referencia al documento XML en forma de stream.

320
Introducción al entorno Android

Para pasar una referencia en forma de stream, implementamos el método auxiliar


privado getInputStream(), que abre la conexión con la dirección URL especificada mediante
el método openConnection() y obtiene el stream de entrada mediante el método
getInputStream().

En el apartado de Tratamiento de ficheros de la Unidad 4 hemos visto cómo usar la


clase stream en Android.

Finalmente, sólo queda aplicar la clase ParserSaxClasico para cargar un documento


XML con el modelo SAX. Para ello, en la Actividad principal XMLActivity de la aplicación
escribimos las siguientes sentencias:

// Creamos un objeto del parser (analizador XML) en función del tipo (opción
// menú principal). La dirección (URL) de la fuente de noticias es una
//constante en este ejemplo

RSSParser analizador = XMLParser.getParser(tipo, feedUrl);

// Guardamos el momento actual de inicio de

long inicio = System.currentTimeMillis();

// Descargamos y analizamos el fichero XML

noticias = analizador.analizar();

// Calculamos el tiempo que ha tardado en leer el XML

long duracion = System.currentTimeMillis() - inicio;

// Mostramos el tiempo de lectura del XML

Toast.makeText(this, "Se han cargado los datos en "+duracion+" milisegundos",


1).show();

// Creamos un listado con todos los títulos de las noticias

List<String> titulos = new ArrayList<String>(noticias.size());

for (Noticia msg : noticias){

titulos.add(msg.getTitulo());

// Definimos Adaptador sencillo con un diseño sencillo y el listado títulos

ArrayAdapter<String> adaptador =

new ArrayAdapter<String>(this, R.layout.fila, titulos);

this.setListAdapter(adaptador);

321
Primero creamos el parser correspondiente usando la dirección URL del documento
XML y, después, ejecutamos el método analizar() para obtener una lista de objetos de tipo
Noticia que, posteriormente, asignamos al adaptador del listado de la Actividad principal.

Si te fijas en el código anterior estamos creando el objeto analizador a partir de la


clase XMLParser en lugar de ParserSaxClasico. Si abrimos el fichero que define la clase
XMLParser veremos el código siguiente:

// Clase que crea un analizador XML del tipo necesario

// Se crea una interface RSSParser para poder renombrar el método analizar()

public abstract class XMLParser {

public static RSSParser getParser(TiposParser tipo, String feedURL){

switch (tipo){

case SAX_CLASICO:

return new ParserSaxClasico(feedURL);

case DOM:

return new ParserDom(feedURL);

case SAX_SIMPLIFICADO:

return new ParserSaxSimplificado(feedURL);

case XML_PULL:

return new ParserXmlPull(feedURL);

default: return null;

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

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

322
Introducción al entorno Android

6.4.2 SAX simplificado en Android

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


y eficientemente, tiene claras desventajas, ya que es obligatorio definir una clase
independiente para el handler. Adicionalmente, el modelo SAX implica poner bastante
atención al definir dicho handler, ya que los métodos SAX definidos no están asignados a
etiquetas concretas del documento XML, sino que se lanzan para todas ellas. Esto obliga a
realizar distinciones entre etiquetas dentro de cada método.

Esto se observa perfectamente en el método endElement() que definimos en el


ejemplo anterior. En primer lugar, hay que comprobar con la sentencia condicional si el
atributo noticiaActual no está vacío (null), para no confundir el elemento <title> descendiente
de <channel> con el elemento <title> descendiente de <item>, que es el que queremos leer.

Posteriormente, hay que distinguir con unas sentencias IF entre todas las etiquetas
posibles la acción que debemos realizar.

Tengamos en cuenta que hemos usado un documento XML muy sencillo, pero si
tratamos un documento XML más enrevesado, la complejidad de este handler aumenta
mucho y pueda dar lugar a errores de programación.

Para evitar estos problemas, Android propone una variante del modelo SAX que evita
definir una clase separada para el handler y que permite asociar directamente las acciones a
etiquetas concretas dentro de la estructura del documento XML.

Veamos cómo queda el analizador XML utilizando SAX simplificado para Android:

public class ParserSaxSimplificado implements RSSParser {

// Variables temporales que usamos a los largo del Handler

// Noticia que estamos leyendo en ese momento

private Noticia noticiaActual;

// Variable que define la etiqueta raíz del XML que es <rss >

static final String RSS = "rss";

// URL del archivo XML

private URL feedUrl;

// Constructor de la clase, se asigna la URL a la variable local

protected ParserSaxSimplificado(String feedUrl){

try

this.feedUrl= new URL(feedUrl);


323
}

catch (MalformedURLException e)

throw new RuntimeException(e);

// Método que lee el documento XML

public List<Noticia> analizar() {

// Variable que almacena las noticias encontradas

final List<Noticia> noticias = new ArrayList<Noticia>();

// Buscamos el elemento raíz <rss>

RootElement root = new RootElement(RSS);

// Buscamos el elemento channel dentro de la etiqueta raíz (root)

Element channel = root.getChild(EtiquetasRSS.CHANNEL);

// Buscamos el elemento item dentro de la etiqueta channel

Element item = channel.getChild(EtiquetasRSS.ITEM);

/*

* Definimos los listerners de estos elementos anteriores

*/

// Método de inicio de una nueva etiqueta item

item.setStartElementListener(new StartElementListener(){

public void start(Attributes attrs) {

noticiaActual = new Noticia();

});

// Método de finalización de una nueva etiqueta item

item.setEndElementListener(new EndElementListener(){

public void end() {

noticias.add(noticiaActual);

});

324
Introducción al entorno Android

// Método de obtención etiqueta title dentro de la etiqueta item

item.getChild(EtiquetasRSS.TITLE).setEndTextElementListener(new
EndTextElementListener(){

public void end(String body) {

noticiaActual.setTitulo(body);

});

// Método de obtención etiqueta link dentro de la etiqueta item

item.getChild(EtiquetasRSS.LINK).setEndTextElementListener(new
EndTextElementListener(){

public void end(String body) {

noticiaActual.setEnlace(body);

});

// Método de obtención etiqueta description dentro de la etiqueta item

item.getChild(EtiquetasRSS.DESCRIPTION).setEndTextElementListener(new
EndTextElementListener(){

public void end(String body) {

noticiaActual.setDescripcion(body);

});

// Método de obtención etiqueta pub_date dentro de la etiqueta item

item.getChild(EtiquetasRSS.PUB_DATE).setEndTextElementListener(new
EndTextElementListener(){

public void end(String body) {

noticiaActual.setFecha(body);

});

//Usamos el objeto Xml de Android para leer el archivo XML

try {

Xml.parse(this.getInputStream(), Xml.Encoding.UTF_8,
root.getContentHandler());

} catch (Exception e) {

throw new RuntimeException(e);

325
// Devolvemos las noticias leídas

return noticias;

// Método que abre una conexión a la URL y devuelve el

// puntero de tipo fichero al analizador correspondiente

private InputStream getInputStream()

try

return feedUrl.openConnection().getInputStream();

catch (IOException e)

throw new RuntimeException(e);

}
}

En este nuevo modelo SAX simplificado de Android las acciones que debemos realizar
dentro de cada método se definen dentro de la misma clase asociadas a etiquetas concretas
del XML.

Para esto, lo primero que hacemos es navegar por la estructura del archivo XML hasta
encontrar las etiquetas que tenemos que tratar y asignarlaa a algunos métodos de tipo
listeners ("escuchadores") disponibles como StartElementListener() de inicio de etiqueta o
EndElementListener() de finalización de etiqueta, incluyendo las sentencias oportunas dentro
de estos métodos.

Por ejemplo, para obtener el elemento <item>, en primer lugar buscamos el elemento
raíz del XML (<rss>) declarando un objeto RootElement. Después, accedemos a su elemento
hijo <channel> y, finalmente, obtenemos de éste último el elemento hijo <item>. En cada
"salto" hemos utilizado el método getChild().

Una vez hemos llegado a la etiqueta buscada, asignamos los listeners necesarios. En
este caso, uno de apertura y otro de cierre de etiqueta item, donde inicializamos la noticia
actual y la añadimos a la lista final, respectivamente, de forma similar a como lo hemos hecho
para el modelo SAX clásico.

326
Introducción al entorno Android

Para el resto de etiquetas internas de item, procedemos de la misma manera,


accediendo a ellas con getChild() y asignando los listeners necesarios.

Para acabar, arrancamos todo el proceso de análisis del XML llamando al método
parse(), definido en la clase android.Util.Xml, al que pasamos como parámetros el stream del
archivo XML, la codificación del documento XML y un handler SAX obtenido directamente del
objeto RootElement definido anteriormente.

Este modelo SAX simplificado evita implementar el handler necesario en el modelo


SAX clásico. Además, ayuda a evitar posibles errores de programación disminuyendo la
complejidad del mismo

Hay que tener en cuenta que el modelo clásico es tan válido y eficiente como éste, que
únicamente simplifica el trabajo al programador.

6.4.3 DOM en Android

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


completamente antes de poder realizar ninguna acción con su contenido. Es decir, cambia
radicalmente la manera de leer los archivos XML.

Al acabar la lectura del documento XML este modelo devuelve todo su contenido en
una estructura de tipo árbol, donde los distintos elementos del fichero XML se representan en
forma de nodos y su jerarquía padre-hijo se establece mediante relaciones entre dichos nodos.

Por ejemplo, si tenemos el siguiente documento XML:

<noticias>

<noticia>

<titulo>Título 1</titulo>

<enlace>Enlace 1</link>

</noticia>

<noticia>

<titulo>Título 2</titulo>

<enlace>Enlace 2</link>

</noticia>

<noticias>

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

327
Como vemos, este árbol conserva la misma información del fichero XML, pero en
forma de nodos y relaciones entre nodos. Por esta razón es sencillo buscar fácilmente dentro
de la estructura un elemento en concreto.

Este árbol se conserva en memoria una vez leído el documento completo, lo que
permite procesarlo en cualquier orden y tantas veces como sea necesario, a diferencia del
modelo SAX, donde el tratamiento es secuencial y siempre desde el principio hasta el final del
documento. Es decir, no se puede volver atrás una vez finalizada la lectura del documento
XML.

El modelo DOM de Android ofrece una serie de clases y métodos que permiten cargar
la información de la forma descrita y facilitan la búsqueda de elementos dentro de la estructura
creada.

Veamos cómo queda el analizador XML usando el modelo DOM de Android:

public class ParserDom implements RSSParser {

// URL del archivo XML

private URL feedUrl;

// Constructor de la clase, se asigna la URL a la variable local

protected ParserDom(String feedUrl){

try

this.feedUrl= new URL(feedUrl);

328
Introducción al entorno Android

catch (MalformedURLException e)

throw new RuntimeException(e);

// Método que lee el documento XML

public List<Noticia> analizar() {

// Creamos acceso a la API DOM de Android

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

List<Noticia> noticias = new ArrayList<Noticia>();

try {

// Creamos un analizador (parser) de DOM

DocumentBuilder parser = factory.newDocumentBuilder();

// Analizamos el archivo XML

Document dom = parser.parse(this.getInputStream());

// Obtenemos el elemento raíz del parser

Element root = dom.getDocumentElement();

// Buscamos las etiquetas ITEM dentro del elemento raíz

NodeList items = root.getElementsByTagName(EtiquetasRSS.ITEM);

// Recorremos todos los items y vamos cargando la lista de noticias

for (int i=0;i<items.getLength();i++){

// Creamos un nuevo objeto de Noticia

Noticia noticia = new Noticia();

// Leemos el item i

Node item = items.item(i);

// Obtenemos todas las etiquetas internas de item

// y buscamos los campos que nos interesan

NodeList etiquetas = item.getChildNodes();

for (int j=0;j<etiquetas.getLength();j++){

Node contenido = etiquetas.item(j);

String nombre = contenido.getNodeName();

329
if (nombre.equalsIgnoreCase(EtiquetasRSS.TITLE)){
noticia.setTitulo(contenido.getFirstChild().getNodeValue());

} else if (nombre.equalsIgnoreCase(EtiquetasRSS.LINK)){
noticia.setEnlace(contenido.getFirstChild().getNodeValue());

} else if (nombre.equalsIgnoreCase(EtiquetasRSS.DESCRIPTION)){

// Puede ocurrir que el texto esté distribuido en varias


// líneas, por lo que hay que leer todos los nodos internos

StringBuilder text = new StringBuilder();

NodeList chars = contenido.getChildNodes();

for (int k=0;k<chars.getLength();k++){


text.append(chars.item(k).getNodeValue());
}
noticia.setDescripcion(text.toString());

} else if (nombre.equalsIgnoreCase(EtiquetasRSS.PUB_DATE)){
noticia.setFecha(contenido.getFirstChild().getNodeValue());

} // end for j

// Añadimos la noticia al listado

noticias.add(noticia);

} // end for i

} catch (Exception e) {

throw new RuntimeException(e);

return noticias;

// Método que abre una conexión a la URL y devuelve el

// puntero de tipo fichero al analizador correspondiente

private InputStream getInputStream()

try

return feedUrl.openConnection().getInputStream();

catch (IOException e)

330
Introducción al entorno Android

throw new RuntimeException(e);

El método más importante es analizar(). De igual forma que se hace en el modelo


SAX, el primer paso es instanciar la API de DOM a partir de la clase
DocumentBuilderFactory. Posteriormente, creamos un nuevo parser a partir del método
newDocumentBuilder().

Después, únicamente hay que leer el documento XML invocando el método parse()
del parser DOM, pasándole como parámetro el stream de entrada del fichero.

Al hacer esto, el documento XML se lee completamente y se crea la estructura de


árbol equivalente, que se devuelve como un objeto de tipo Document por el que podemos
movernos para buscar los elementos <item> que necesita la aplicación.

Para esto, lo primero que hacemos es acceder al nodo raíz (root) del árbol utilizando el
método getDocumentElement(); en este ejemplo es la etiqueta <rss>,.

Una vez que sabemos dónde está el nodo raíz, vamos a buscar todos los nodos con la
etiqueta <item>. Para esto, usamos el método de búsqueda por el nombre de etiqueta
getElementsByTagName(“nombre_de_etiqueta“), que devuelve una lista de tipo NodeList
con todos los nodos hijos del nodo actual cuya etiqueta coincida con el nombre indicado.

Una vez hemos obtenido todos los elementos <item> que contienen cada noticia,
vamos a recorrerlos de uno en uno para crear todos los objetos de tipo Noticia necesarios.

Para cada uno de estos elementos obtenemos sus nodos hijos mediante el método
getChildNodes(). Después, recorremos estos nodos hijos obteniendo su texto y
almacenándolo en el campo correspondiente del objeto Noticia. Para saber qué etiqueta
representa cada nodo hijo utilizamos el método getNodeName().

6.4.4 StAX en Android

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


diferencia principal está en que, mientras que en el modelo SAX no hay control de ejecución
una vez iniciada la lectura del XML (el parser lee automáticamente todo el XML desde el inicio
hasta el final invocando los métodos necesarios), en el modelo StAX podemos guiar la lectura
del documento o intervenir en ella, solicitando de forma explícita la lectura del siguiente
elemento del documento XML y respondiendo al resultado con las acciones oportunas.

En este ejemplo hemos usado la implementación de StAX de Android que se lama


XmlPull. Fíjate en el código fuente de este analizador:

331
public class ParserXmlPull implements RSSParser {

// URL del archivo XML

private URL feedUrl;

// Constructor de la clase, se asigna la URL a la variable local

protected ParserXmlPull(String feedUrl){

try

this.feedUrl= new URL(feedUrl);

catch (MalformedURLException e)

throw new RuntimeException(e);

// Método que lee el documento XML

public List<Noticia> analizar() {

List<Noticia> noticias = null;

// Creamos un analizador (parser) de StAX que en Android se llama


XmlPullParser

XmlPullParser parser = Xml.newPullParser();

try {

// Asignamos el stream del XML al parsr

parser.setInput(this.getInputStream(), null);

// Guardamos el tipo de evento actual = START_DOCUMENT

int eventType = parser.getEventType();

Noticia noticiaActual = null;

// Variable que controla si se ha acabado el documento XML

boolean docAcabado = false;

// Mientras no acabe el documento

332
Introducción al entorno Android

while (eventType != XmlPullParser.END_DOCUMENT && !docAcabado){

// Variable temporal que guarda el nombre de la etiqueta

String nombre = null;

switch (eventType){

case XmlPullParser.START_DOCUMENT:

// Creamos el listado con las noticias

noticias = new ArrayList<Noticia>();

break;

// Etiqueta de incicio

case XmlPullParser.START_TAG:

// Creamos el objeto noticia o guardamos el campo


correspondiente

nombre = parser.getName();

if (nombre.equalsIgnoreCase(EtiquetasRSS.ITEM)){

noticiaActual = new Noticia();

} else if (noticiaActual != null){

if (nombre.equalsIgnoreCase(EtiquetasRSS.LINK)){
noticiaActual.setEnlace(parser.nextText());

} else if
(nombre.equalsIgnoreCase(EtiquetasRSS.DESCRIPTION)){
noticiaActual.setDescripcion(parser.nextText());

} else if
(nombre.equalsIgnoreCase(EtiquetasRSS.PUB_DATE)){

noticiaActual.setFecha(parser.nextText());

} else if (nombre.equalsIgnoreCase(EtiquetasRSS.TITLE)){

noticiaActual.setTitulo(parser.nextText());

break;

// Etiqueta de cierre

case XmlPullParser.END_TAG:

nombre = parser.getName();

if (nombre.equalsIgnoreCase(EtiquetasRSS.ITEM) &&
noticiaActual != null){

noticias.add(noticiaActual);

} else if (nombre.equalsIgnoreCase(EtiquetasRSS.CHANNEL)){
333
docAcabado = true;

break;

eventType = parser.next();

} // end while

} catch (Exception e) {

throw new RuntimeException(e);

// Devolvemos las noticias

return noticias;

// Método que abre una conexión a la URL y devuelve el

// puntero de tipo fichero al analizador correspondiente

private InputStream getInputStream()

try

return feedUrl.openConnection().getInputStream();

catch (IOException e)

throw new RuntimeException(e);

}
}

Una vez más nos centramos en el método analizar() de la clase.

Primero, creamos el nuevo analizador XmlPull y asignamos el fichero de entrada en


forma de stream. Después, definimos un bucle en el que solicitamos en cada iteración al
parser el siguiente evento encontrado en la lectura del archivo XML mediante el método
parser.next(). Para cada evento devuelto obtenemos su tipo mediante el método
parser.getEventType() y ejecutamos las sentencias oportunas.

334
Introducción al entorno Android

Una vez identificado el tipo de evento, podemos consultar el nombre de la etiqueta del
elemento XML mediante parser.getName() y el texto contenido mediante parser.nextText().

Si ejecutas la aplicación en el emulador de Android, verás que puedes acceder a los

distintos modelos de tratamiento de ficheros XML pulsando la tecla menú del emulador:

Si seleccionamos una de las opciones, podemos ver que se recarga el listado de


noticias y el tiempo que tarda en cargar el documento XML:

Se muestra un mensaje con el tiempo que ha tardado en cargar el documento XML.


Así podemos valorar la eficacia de cada modelo a la hora de leer un XML completo.
Observarás que los modelos SAX son los más rápidos.

335
Si haces clic sobre una noticia verás que Android te permite seleccionar el navegador
que quieres usar para iniciar la acción Intent.ACTION_VIEW que permite abrir una página
Web:

Esto ocurre porque en el Ejemplo 2 de la Unidad 5 hemos definido un navegador


sencillo que carga la página en formato HTML para invocar implícitamente una Intención
propia.

336
Introducción al entorno Android

 El término base de datos se refiere a una colección, conjunto o depósito de datos,


almacenados en un soporte magnético o de otro tipo, accesibles por múltiples
usuarios de forma rápida y eficaz mediante el ordenador a través de una o de varias
aplicaciones informáticas independientes de los datos.Para crear una Intención hay
que usar el objeto Intent de Android.

 En Android las bases de datos son privadas y únicamente una aplicación puede
acceder a ellas para leer y escribir datos.

 Para compartir información de base de datos entre aplicaciones Android hay que
usar los Content Providers.Explícita: invocando la clase Java del componente que
queremos ejecutar. Normalmente, se hace para invocar componentes de una
misma aplicación.

 Android usa SQLite como motor de base de datos relacional.

 Antes de crear una base de datos con el ordenador, es preciso diseñarla


previamente sobre el papel.

 Usar bases de datos Android hace más lentas las aplicaciones debido a que es
necesario escribir y leer información de la memoria física del dispositivo. Por esto,
es recomendable usar hilos de ejecución.

 La forma usual en Android de crear, modificar y conectar con una base de datos
SQLite consiste en usar la clase Java SQLiteOpenHelper.

 Existen dos formas de buscar y recuperar registros de una base de datos


SQLite. La primera de ellas consiste en utilizar directamente el comando de
consulta SQL rawQuery(). La segunda forma consiste en utilizar el método
específico query() con parámetros de consulta a la base de datos.

 EXtensible Markup Language (XML) es un formato de datos que se usa


comúnmente en las aplicaciones web modernas.

 Los tres modelos más extendidos para leer y escribir ficheros de tipo XML son
DOM (Document Object Model), SAX (Simple API for XML) y StAX (Streaming API
for XML).

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

 El modelo SAX se basa en eventos. La aplicación recorre todos los elementos del
archivo XML de una sola vez. La ventaja respecto a la anterior es que es más rápido
y requiere menos memoria, si bien no permite el acceso aleatorio a una de sus
ramas.

 El modelo StAX es una mezcla de las dos modelos anteriores. En este caso,
también se lee el fichero XML de forma secuencial, pero podemos controlar la
forma en que se leen los elementos. Este modelo es también mucho más rápido
que DOM, pero algo más lento que SAX.

 Un analizador sintáctico (en inglés parser) convierte el texto de entrada en otras


estructuras (comúnmente árboles), que son más útiles para el posterior análisis;
también captura la jerarquía implícita de la entrada.

338
CONTENT PROVIDERS,
SERVICIOS Y
NOTIFICACIONES

ÍNDICE
7.1 CONTENT PROVIDERS ............................................................. 341
7.1.1 Introducción ................................................................... 341
7.1.2 Proveedores de contenido (Content Providers) ....... 341
7.1.3 Construcción de un Content Provider ........................ 342

7.2 Uso de un Content Provider nuevo ........................................ 352

7.3 Uso de un Content Provider ya existente en Android ......... 355

7.4 SERVICIOS DE ANDROID Y RECEPTORES DE MENSAJES DE


DIFUSIÓN .............................................................................................. 359
7.4.1 Servicios (Services) ...................................................... 359
7.4.2 Servicios propios ........................................................... 360
7.4.3 Receptor de mensajes de difución (Broadcast Receiver)
361
7.4.4 Intención pendiente (Pending Intent) ......................... 361
7.4.5 Ejemplo de Receptor de mensajes (Broadcast
Receiver) ................................................................................ 362
7.4.6 Ejemplo de envío y recepción de mensajes internos en
una aplicación y uso de servicios por defecto de Android364
7.4.7 Crear un servicio propio ............................................... 367

7.5 NOTIFICACIONES AL USUARIO EN ANDROID ..................... 373


7.5.1 Mensajes emergentes (Toast) .................................... 373
7.5.2 Notificaciones en la barra de estado.......................... 378

7.6 USO DE VIEWPAGER EN APLICACIONES ANDROID ......... 383


7.6.1 Cómo se usa el componente ViewPager .................. 385
2
Content Providers, servicios y notificaciones

7.1 CONTENT PROVIDERS

7.1.1 Introducción

En esta Unidad vamos a estudiar los proveedores de contenidos (Content


Providers) para compartir información entre aplicaciones y el Resolvedor de contenidos
(Content Resolver) para consultar y actualizar la información de los Content Providers.

Después, explicaremos cómo funcionan los servicios en Android.

A continuación, detallaremos el uso de notificaciones en las aplicaciones Android.

Finalmente, veremos cómo utilizar el componente ViewPager que permite cambiar de


pantalla deslizando el dedo horizontalmente en el dispositivo.

7.1.2 Proveedores de contenido (Content Providers)

Un Proveedor de contenido (en inglés Content Provider) es el mecanismo


proporcionado por Android para compartir información entre aplicaciones.

Una aplicación que desee compartir de manera controlada parte de la información que
almacena con resto de aplicaciones debe declarar un Content Provider al sistema operativo a
través del cuál se realiza el acceso a dicha información.

Este mecanismo lo utilizan muchas aplicaciones estándar de un dispositivo Android,


como la lista de contactos, la aplicación de SMS para mensajes cortos, el calendario, etcétera.
Es decir, podemos acceder desde una aplicación cualquiera a los datos gestionados por otras
aplicaciones Android haciendo uso de los Content Providers correspondientes. Para ello, es
preciso que la aplicación tenga asignados los permisos adecuados para acceder a estos
contenidos.

Android, de serie, incluye varios proveedores de contenido para los tipos de datos más
comunes, como audio, vídeo, imágenes, agenda de contactos personal, etcétera. Puedes ver el
listado completo en el paquete android.provider.

En este apartado vamos a tratar dos funcionalidades diferenciadas, que son las
siguientes:

• Construcción de nuevos Content Providers personalizados, para que otras


aplicaciones puedan acceder a la información contenida en la nuestra.

• Utilización de un Content Provider ya existente, para que la nuestra pueda


acceder a los datos publicados por otras aplicaciones.

En la Unidad 5 ya hemos visto un ejemplo muy sencillo sobre del acceso a un Content
Provider ya existente, concretamente en la lista de contactos de Android.

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

7.1.3 Construcción de un Content Provider

Hay dos formas de compartir información de una aplicación: implementando el


proveedor de contenidos propio mediante la clase ContentProvider de Android o agregando
datos a un proveedor ya existente en el dispositivo, siempre y cuando los tipos de datos sean
parecidos y la aplicación tenga permiso para escribir en el Content Provider.

En el Ejemplo 1 de esta Unidad vamos a mostrar cómo crear un Content Provider


nuevo y cómo usarlo.

Por simplificación, será la misma aplicación la que acceda al Content Provider interno,
si bien el código necesario desde otra aplicación es exactamente el mismo.

Desde Eclipse puedes abrir el proyecto Ejemplo 1 (Content Provider) de la Unidad 7.


Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del programa, en el
que hemos utilizado un Content Provider.

Si ejecutas la aplicación, verás que tiene el siguiente aspecto:

Fíjate que en este ejemplo los botones "Insertar" y "Eliminar" son excluyentes. Sólo se
puede borrar un alumno si previamente ha sido dado de alta y viceversa.

Se trata de un Content Provider que comparte información de los alumnos de un


colegio.

La aplicación del colegio almacena la información que queremos compartir en una base
de datos SQLite.

342
Content Providers, servicios y notificaciones

Si bien internamente podemos tener la información almacenada de cualquier otra


forma, por ejemplo, en un ficheros de tipo texto, en XML, etcétera, en este ejemplo vamos a
usar una base de datos porque es más fácil gestionar información estructurada.

El Content Provider es el mecanismo que permite compartir estos datos con otras
aplicaciones de una forma homogénea usando una interfaz estandarizada.

Las tablas de la base de datos SQLite usadas por un Content Provider deben incluir siempre el
campo _ID que identifica sus registros de forma unívoca.

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

Lo primero que hemos hecho en este Ejemplo es crear una aplicación muy simple que
almacena y consulta los datos de los alumnos con la estructura similar a la tabla anterior.

Para esto, aplicamos los mismos conceptos que ya hemos estudiado en la Unidad 6
para el tratamiento de bases de datos.

Creamos una clase heredada de SQLiteOpenHelper donde definimos las sentencias


SQL que crean la tabla de alumnos implementando los métodos onCreate() y onUpgrade(). El
código de esta nueva clase tiene este aspecto:

public class ColegioSqliteHelper extends SQLiteOpenHelper {

//Sentencia SQL para crear la tabla de Alumnos en la BD BDColegio

String sqlCreate = "CREATE TABLE Alumnos " +

"(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +

" nombre TEXT, " +

" apellidos TEXT, " +

" curso TEXT )";

343
public ColegioSqliteHelper(Context contexto, String nombre,

CursorFactory factory, int version) {

super(contexto, nombre, factory, version);

@Override

public void onCreate(SQLiteDatabase db) {

//Se ejecuta la sentencia SQL de creación de la tabla

db.execSQL(sqlCreate);

String[] nombres={"Juan", "José", "Miguel", "Antonio", "Alicia",


"Luis", "Fernanda", "Lucía", "Mercedes", "Elisa"};

String[] apellidos={"Valle", "Fernández", "Martín", "Navas", "Conde",


"Díaz", "Verdú", "Cuenca", "Pérez", "Sevilla"};

String[] cursos={"1º ESO", "1º ESO", "2º ESO", "3º ESO", "1º ESO",
"4º ESO", "2º ESO", "2º ESO", "1º ESO", "4º ESO"};

//Insertamos 10 alumnos de ejemplo

for(int i=0; i<10; i++)

//Insertamos los datos en la tabla Alumnos

db.execSQL("INSERT INTO Alumnos (nombre, apellidos, curso) " +

"VALUES ('" + nombres[i] + "', '" + apellidos[i] +"', '"


+ cursos[i] + "')");

@Override

public void onUpgrade(SQLiteDatabase db, int versionAnterior, int


versionNueva) {

// NOTA: Por simplicidad, se elimina la tabla anterior y se crea de nuevo.

// Sin embargo, lo normal sería migrar datos de la tabla antigua

// a la nueva, por lo que este método debería ser más complejo.

//Se elimina la versión anterior de la tabla

db.execSQL("DROP TABLE IF EXISTS Alumnos");

344
Content Providers, servicios y notificaciones

//Se crea la nueva versión de la tabla

db.execSQL(sqlCreate);

}
}

Fíjate que hemos incluido el campo _id en la tabla de la base de datos de alumnos.
Este campo lo declaramos como INTEGER PRIMARY KEY AUTOINCREMENT para que se
incremente automáticamente cada vez que insertamos un nuevo registro en la tabla.

Además, esta clase añade algunos registros de ejemplo para poder hacer pruebas.

Una vez que ya contamos con una aplicación que ha definido su base de datos, vamos
a construir el nuevo Content Provider que permite compartir sus datos con otras aplicaciones.

El acceso a un Content Provider se realiza siempre mediante un identificador URI. Un


identificador URI es una cadena de texto parecida a una dirección Web de Internet. Es decir, si
para acceder a Google con el navegador escribimos “http://www.google.es“, para acceder a un
Content Provider utilizamos una dirección similar a
“content://es.mentor.unidad7.ejemplo/alumnos“.

Los identificadores URI de los Content Providers se pueden dividir en tres partes:

• Prefijo content://: indica que dicho recurso debe ser tratado por un Content
Provider.

• Identificador del Content Provider (también llamado authority): este campo


debe ser único en cada dispositivo Android; por esto, es una buena práctica
definir un authority con el nombre de clase java invertido, por ejemplo, en este
ejemplo es “es.mentor.ejemplo7.ejemplo“.

• Esquema o Entidad concreta de datos que queremos que comparta el


Content Provider. En este caso indicamos simplemente la tabla de “alumnos“.
Un Content Provider puede contener datos de varias entidades distintas en
esta última parte del URI. Todo esto es importante, ya que será nuestro
Content Provider el encargado de interpretar (parsear) el URI completo para
determinar los datos que se le están solicitando. Esto lo veremos un poco más
adelante en detalle.

Por último, apuntamos que, en el URI se puede hacer referencia directamente a un


registro concreto de la entidad seleccionada. Esto se hace indicando al final del URI de dicho
registro. Por ejemplo, el URI “content://es.mentor.unidad7.ejemplo/alumnos/17” hace referencia
directa al alumno con _ID = 17.

A continuación, vamos a crear el Content Provider de la aplicación. Para esto, hay que
extender la clase ContentProvider. Esta clase dispone de los métodos abstractos siguientes ,
que podemos implementar:

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

• query(): permite consultar datos que haya en el Content Provider.

• insert(): permite insertar datos en el Content Provider.

• update(): permite actualizar datos del Content Provider.

• delete(): permite borrar datos del Content Provider.

• getType(): permite conocer el tipo de dato devuelto por el Content Provider.

Además de implementar estos métodos, también definimos una serie de cadenas


constantes en la clase del Content Provider.

A continuación, estudiamos por partes la nueva clase ColegioContentProvider que


extienda de ContentProvider.

En primer lugar definimos el URI con el que se accede al Content Provider de la


aplicación: Vamos a usar “content://es.mentor.unidad7.ejemplo/alumnos”:

//Definición del CONTENT_URI

private static final String uri =

"content://es.mentor.unidad7.ejemplo/alumnos";

public static final Uri CONTENT_URI = Uri.parse(uri);

En todos los Content Providers de Android es necesario encapsular este identificador


URI en un objeto estático del tipo Uri que hemos llamado CONTENT_URI.

A continuación, definimos varias constantes con los nombres de los campos


proporcionados por el Content Provider. Como ya hemos comentado anteriormente, existen
columnas predefinidas que deben tener todos los Content Providers, como la columna _ID.
Esta columna estándar está definida internamente en la clase BaseColumns, por lo que al
añadir los campos (columnas) del Content Provider sólo hay que indicar las nuevas columnas.

//Clase interna para declarar las constantes de las columnas = campos

public static final class Alumnos implements BaseColumns

private Alumnos() {}

//Nombres de las columnas

public static final String COL_NOMBRE = "nombre";

public static final String COL_APELLIDOS = "apellidos";

public static final String COL_CURSO = "curso";


346
Content Providers, servicios y notificaciones

Por último, vamos a definir varias cadenas constantes privadas que almacenen
información auxiliar con el nombre de la base de datos, su versión y la tabla a la que accede el
Content Provider.

private ColegioSqliteHelper colegioBDhelper;

private static final String BD_NOMBRE = "BDColegio";

private static final int BD_VERSION = 1;


private static final String TABLA_ALUMNOS = "Alumnos";

Lo primero que debe hacer un Content Provider cuando otra aplicación le solicita una
operación es interpretar el URI utilizado. Para facilitar esta tarea al programador, Android
proporciona la clase llamada UriMatcher que interpreta los patrones en un URI.

Esto es muy útil para determinar, por ejemplo, si un URI hace referencia a una tabla
genérica o a un registro concreto a través de su ID:

• content://es.mentor.unidad7.ejemplo/alumnos: acceso genérico a la tabla


de alumnos.

• content://es.mentor.unidad7.ejemplo/alumnos/17: acceso directo al alumno


con el ID = 17.

Para ello definimos también en esta clase un objeto UriMatcher y dos nuevas
constantes que representan los dos tipos de URI que hemos indicado: acceso genérico a la
tabla (ALUMNOS) o acceso a un alumno por ID (ALUMNOS_ID).

Después, creamos el objeto UriMatcher indicando el formato de ambos tipos de URI


de forma que pueda diferenciarlos y devolvernos su tipo (una de las dos constantes definidas,
ALUMNOS o ALUMNOS_ID):

//Necesario para UriMatcher

private static final int ALUMNOS = 1;

private static final int ALUMNOS_ID = 2;

private static final UriMatcher uriMatcher;

static {

uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

uriMatcher.addURI("es.mentor.unidad7.ejemplo", "alumnos", ALUMNOS);

uriMatcher.addURI("es.mentor.unidad7.ejemplo", "alumnos/#", ALUMNOS_ID);


}

347
En el código anterior vemos que mediante el método addUri() indicamos el campo
authority del URI, el formato de la entidad que estamos solicitando y el tipo que identifica el
formato del dato. Más adelante veremos cómo utilizar esto de forma práctica.

Posteriormente, vamos a implementar los métodos internos del Content Provider.

El primero de ellos es onCreate(). En este método inicializamos la base de datos


mediante la clase ColegioSqliteHelper que creamos anteriormente:

public boolean onCreate() {

// Inicializamos el conector con la BD

colegioBDhelper = new ColegioSqliteHelper(

getContext(), BD_NOMBRE, null, BD_VERSION);

return true;
}

El método más importante del Content Provider es query(). Este método recibe como
parámetros un URI, una lista de nombres de columna, un criterio de selección, una lista de
valores para las variables utilizadas en el criterio anterior y un criterio de ordenación.

Todos estos parámetros son similares a los que estudiamos cuando tratamos sobre las
bases de datos SQLite para Android.

El método query() devuelve los datos solicitados según el URI, los criterios de
selección y ordenación indicados como parámetros. Así, si el URI hace referencia a un alumno
en concreto por su ID, ése debe ser el único registro devuelto. Si se solicita el contenido de la
tabla de alumnos, hay que realizar la consulta SQL correspondiente a la base de datos
respetando los criterios pasados como parámetros.

Para distinguir entre los dos tipos posibles de URI utilizamos el método match() del
objeto uriMatcher. Si el tipo devuelto es ALUMNOS_ID, es decir, se ha solicitado información
de un alumno en concreto, sustituimos el criterio de selección por uno que busca en la tabla de
alumnos sólo el registro con el ID indicado en la URI. Para obtener este ID utilizamos el método
getLastPathSegment() del objeto uri, que extrae el último elemento de la URI, en este caso el
ID del alumno.

Después, hay que realizar la consulta a la base de datos mediante el método query()
de SQLiteDatabase. Esto es muy fácil, ya que los parámetros son similares a los empleados
en el método query() del Content Provider:

public Cursor query(Uri uri, String[] projection,

String selection, String[] selectionArgs, String sortOrder) {

// Accedemos a la base de datos en modo lectura

SQLiteDatabase db = colegioBDhelper.getReadableDatabase();

348
Content Providers, servicios y notificaciones

//Si es una consulta a un ID concreto construimos el WHERE

String where = selection;

if(uriMatcher.match(uri) == ALUMNOS_ID){

// Obtenemos el último segmento del URI

where = "_id=" + uri.getLastPathSegment();

// Hacemos la consulta a la BD

Cursor c = db.query(TABLA_ALUMNOS, projection, where,

selectionArgs, null, null, sortOrder);

return c;
}

Podemos observar que los resultados se devuelven en forma de Cursor, tal y como lo
hace el método query() de SQLiteDatabase.

Por otra parte, los métodos update() y delete() son completamente similares al método
anterior. Únicamente se diferencian en que éstos devuelven como resultado el número de
registros afectados en lugar de un cursor. Veamos su código:

@Override

public int update(Uri uri, ContentValues values,

String selection, String[] selectionArgs) {

// Variable temporal

int cont;

// Accedemos a la base de datos en modo escritura

SQLiteDatabase db = colegioBDhelper.getWritableDatabase();

//Si es una actualización a un ID concreto construimos el WHERE

String where = selection;

if(uriMatcher.match(uri) == ALUMNOS_ID){

where = "_id=" + uri.getLastPathSegment();

// Actualizamos la tabla

cont = db.update(TABLA_ALUMNOS, values, where, selectionArgs);

// Devolvemos el nº de registros afectados por la consulta

return cont;

349
@Override

public int delete(Uri uri, String selection, String[] selectionArgs) {

// Variable temporal

int cont;

// Accedemos a la base de datos en modo escritura

SQLiteDatabase db = colegioBDhelper.getWritableDatabase();

//Si borramos un ID concreto construimos el WHERE

String where = selection;

if(uriMatcher.match(uri) == ALUMNOS_ID){

where = "_id=" + uri.getLastPathSegment();

// Borramos los registros

cont = db.delete(TABLA_ALUMNOS, where, selectionArgs);

// Devolvemos el nº de registros afectados por la consulta

return cont;

El método insert() se implementa de forma ligeramente distinta. La diferencia en este


caso está en que hay que devolver el URI que hace referencia al nuevo registro insertado. Para
ello, obtenemos el nuevo ID del elemento insertado y construimos el nuevo URI de respuesta
mediante el método auxiliar ContentUris.withAppendedId(), que recibe como parámetro el
URI del Content Provider y el ID del nuevo elemento:

public Uri insert(Uri uri, ContentValues values) {

// Variable temporal que guarda el ID dado de alta

long regId = -1;

// Accedemos a la base de datos en modo escritura

SQLiteDatabase db = colegioBDhelper.getWritableDatabase();

// Insertamos el registro en la tabla

regId = db.insert(TABLA_ALUMNOS, null, values);

// Uri con el resultado de la operación

Uri newUri = ContentUris.withAppendedId(CONTENT_URI, regId);

return newUri;
}

350
Content Providers, servicios y notificaciones

Por último, sólo queda implementar el método getType(). Este método se utiliza para
identificar el tipo de datos que devuelve el Content Provider. Este tipo de datos se expresa
como un MIME Type, tal y como hacen los navegadores Web para determinar qué tipo de
datos se está recibiendo al hacer una petición a un servidor. Identificar el tipo de datos que
devuelve un Content Provider ayuda a Android a determinar qué aplicaciones son capaces de
procesar dichos datos.

En este ejemplo, existen dos tipos MIME distintos para cada entidad del Content
Provider: el primero se usa cuando se devuelve un registro único concreto y el segundo cuando
se devuelven varios registros simultáneamente. Así, podemos utilizar los siguientes patrones
para definir uno u otro tipo de datos:

• vnd.android.cursor.item/vnd.xxxxxx: Registro único

• vnd.android.cursor.dir/vnd.xxxxxx: Listado de registros

En este ejemplo, hemos definido los siguientes tipos:

• vnd.android.cursor.dir/vnd.mentor.alumno

• vnd.android.cursor.item/vnd.mentor.alumno

Teniendo esto en cuenta, la implementación del método getType() tiene estas


sentencias:

@Override

public String getType(Uri uri) {

// Devolvemos un tipo de dato en función del URI

int match = uriMatcher.match(uri);

switch (match)

case ALUMNOS:

return "vnd.android.cursor.dir/vnd.mentor.alumno";

case ALUMNOS_ID:

return "vnd.android.cursor.item/vnd.mentor.alumno";

default:

return null;

Se puede observar que utilizamos de nuevo el objeto UriMatcher para determinar el


tipo de URI que se está solicitando y en función de éste devolvemos un tipo MIME u otro.

Para finalizar con el Content Provider, debemos declararlo en el fichero


AndroidManifest.xml, para que, al instalar la aplicación en el dispositivo Android, éste
conozca la existencia de dicho recurso. Para ello, basta con añadir un nuevo elemento
351
<provider> dentro de <application> indicando el nombre del Content Provider y su
authority:

<application android:icon="@drawable/icon"

android:label="@string/app_name">

...

<provider android:name=".ColegioContentProvider"

android:authorities="es.mentor.unidad7.ejemplo"/>

</application>

7.2 Uso de un Content Provider nuevo


Una vez completado el Content Provider, vamos a usarlo desde la propia aplicación
del ejemplo que hemos creado. Lo hacemos así para simplificar el ejemplo; de cualquier forma,
el código necesario es exactamente el mismo si lo usamos desde otra aplicación distinta.

Utilizar un Content Provider ya existente es muy sencillo, sobre todo si lo comparamos


con todo el proceso anterior de construcción de uno nuevo.

Para ello, vamos a usar la clase ContentResolver de Android que permite realizar
acciones (consultas de datos, actualizaciones de información, etcétera) con cualquier Content
Provider que esté disponible en el sistema operativo Android.

Desde la actividad principal hay que utilizar el método getContentResolver() para


obtener la referencia de la aplicación al objeto ContentResolver.

Una vez obtenida esta referencia, podemos utilizar sus métodos query(), update(),
insert() y delete() para realizar las acciones equivalentes sobre el Content Provider.

En la aplicación del ejemplo anterior hay tres botones en la pantalla principal: uno para
hacer una consulta de todos los alumnos, otro para insertar registros nuevos y el último para
eliminar todos los registros nuevos insertados con el segundo botón.

Empecemos por la consulta de alumnos. El procedimiento es prácticamente igual al


que hemos estudiado para acceder a bases de datos SQLite.

Primero definimos una matriz con los nombres de las columnas de la tabla que
queremos recuperar en el resultado de la consulta: ID, nombre, apellidos y curso.

352
Content Providers, servicios y notificaciones

Tras esto, obtenemos una referencia al Content Resolver y utilizamos su método


query() para obtener los resultados en forma de Cursor. El método query() se invoca con los
parámetros siguientes: el Uri del Content Provider al que queremos acceder, la matriz de
columnas que queremos recuperar, el criterio de selección, los argumentos variables y el
criterio de ordenación de los resultados.

En este caso, para no complicar el ejemplo tan sólo indicamos los dos primeros:
CONTENT_URI del Content Provider y la matriz de columnas que acabamos de definir:

//Columnas de la tabla

String[] columnas = new String[] {

Alumnos._ID,

Alumnos.COL_NOMBRE,

Alumnos.COL_APELLIDOS,

Alumnos.COL_CURSO };

// Definimos la Uri que queremos usar

Uri alumnosUri = ColegioContentProvider.CONTENT_URI;

// Acceso al contentresolver de la aplicación

ContentResolver cr = getContentResolver();

//Hacemos la consulta

Cursor cur = cr.query(alumnosUri,

columnas, //Columnas solicitadas

null, //Condición de la query

null, //Argumentos variables de la query


null); //Orden de los resultados

Una vez solicitada la consulta, hay que recorrer el cursor para procesar los registros.
Veamos cómo queda el código fuente:

// Si obtenemos resultados

if (cur.moveToFirst())

String nombre;

String apellidos;

String curso;

353
int colNombre = cur.getColumnIndex(Alumnos.COL_NOMBRE);

int colApellidos = cur.getColumnIndex(Alumnos.COL_APELLIDOS);

int colCurso = cur.getColumnIndex(Alumnos.COL_CURSO);

txtResultados.setText("Resultado consulta:\n\n");

// Recorremos todos los registros y los mostramos en pantalla

do {

nombre = cur.getString(colNombre);

apellidos = cur.getString(colApellidos);

curso = cur.getString(colCurso);

txtResultados.append(nombre + " " + apellidos + ". Curso: " +


curso + "\n");

} while (cur.moveToNext()); // end while


}

Insertar nuevos registros se implementa exactamente igual que si tratáramos


directamente con bases de datos SQLite. Rellenamos primero un objeto ContentValues con
los datos del nuevo alumno y utilizamos el método insert() pasándole como parámetros la
URI del Content Provider y los datos del nuevo registro:

ContentValues values = new ContentValues();

values.put(Alumnos.COL_NOMBRE, "Jesús");

values.put(Alumnos.COL_APELLIDOS, "Sanz");

values.put(Alumnos.COL_CURSO, "BACHIDERATO");

ContentResolver cr = getContentResolver();

cr.insert(ColegioContentProvider.CONTENT_URI, values);

txtResultados.setText("Se ha insertado el alumno. Pulsa el botón 'Consultar'


para ver todos los alumnos.");

Por último, la eliminación de registros la hacemos directamente utilizando el método


delete() del Content Resolver, indicando como segundo parámetro el criterio de identificación
de los registros que queremos eliminar:

ContentResolver cr = getContentResolver();

cr.delete(ColegioContentProvider.CONTENT_URI, Alumnos.COL_NOMBRE + " =


'Jesús'", null);

txtResultados.setText("Se ha borrado el alumno. Pulsa el botón 'Consultar'


para ver todos los alumnos.");

354
Content Providers, servicios y notificaciones

7.3 Uso de un Content Provider ya existente en Android

Hemos visto lo sencillo que resulta acceder a los datos proporcionados por un Content
Provider.

Mediante este mecanismo podemos utilizar en nuestras aplicaciones muchos datos de la


propia plataforma Android. En la documentación oficial del paquete android.provider podemos
consultar los datos que están disponibles a través de este mecanismo. Entre ellos encontramos
el historial de llamadas, la agenda de contactos, etcétera.

Para ver cómo se usan los Content Providers con un tipo de datos definido por Android, en
el Ejemplo 2 de esta Unidad vamos a consultar el historial de llamadas del dispositivo, usando
el Content Provider android.provider.CallLog.

Desde Eclipse puedes abrir el proyecto Ejemplo 2 (Historial de llamadas) de la Unidad 7.


Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del programa, en el
que hemos utilizado un Content Provider definido por Android.

Para poder ver algún dato en este ejemplo, en primer lugar, vamos a registrar varias
llamadas en el emulador de Android. Así, los resultados de la consulta al historial de llamadas
devolverán algunos registros.

A continuación, vamos a simular varias llamadas salientes desde el emulador y varias


llamadas entrantes desde el DDMS.

Las llamadas salientes son sencillas de realizar usando el emulador como si se tratara de
un teléfono normal y corriente. Accedemos al icono teléfono, marcamos un número y
descolgamos como si se tratara de un dispositivo físico:

355
Para simular llamadas entrantes debemos acceder desde Eclipse a la vista del DDMS. En
esta vista, en la pestaña “Emulator Control” aparece el apartado “Telephony Actions“, donde
podemos introducir un número cualquiera de teléfono origen “Incoming number” y pulsar el
botón “Call” para que el dispositivo del emulador reciba una llamada entrante.

Sin aceptar la llamada en el emulador, pulsaremos “Hang Up” para terminar la llamada
simulando así una llamada perdida.

Una vez hemos simulado tanto llamadas entrantes como llamadas salientes, vamos a
desarrollar una aplicación que consulte el historial de llamadas.

Si consultamos la documentación del Content Provider android.provider.CallLog, veremos


que podemos extraer diferentes datos relacionados con la lista de llamadas. En este ejemplo
vamos a usar únicamente el número origen o destino de la llamada y el tipo de llamada
(entrante, saliente y perdida). Los nombres de estas columnas se almacenan en las
constantes Calls.NUMBER y Calls.TYPE respectivamente.

A continuación, definimos una matriz con las columnas que vamos a recuperar,
obtenemos la referencia al Content Resolver de la aplicación y ejecutamos la consulta
llamando al método query(). Por último, recorremos el cursor obtenido y procesamos los
resultados. Veamos el código fuente:

356
Content Providers, servicios y notificaciones

// Constantes que definen los campos que consultamos

String[] columnas = new String[] {Calls.TYPE, Calls.NUMBER };

// La Uri está predefinida en una constante del sistema

Uri llamadasUri = Calls.CONTENT_URI;

// Cargamos el Content Resolver de la aplicación

ContentResolver cr = getContentResolver();

// Hacemos una consulta de las llamadas

Cursor cur = cr.query(llamadasUri,

columnas, //Columnas a devolver

null, //Condición de la query

null, //Argumentos variables de la query

null); //Orden de los resultados

// Si hay llamadas mostramos la información

if (cur.moveToFirst())

int tipo;

String tipoLlamada = "";

String telefono;

// Obtenemos el índice de las columnas

int colTipo = cur.getColumnIndex(Calls.TYPE);

int colTelefono = cur.getColumnIndex(Calls.NUMBER);

// Limpiamos la etiqueta de resultados

txtResultados.setText("");

// Mientras haya datos mostramos la información al usuario

do

// Obtenemos la información de las columnas

tipo = cur.getInt(colTipo);

telefono = cur.getString(colTelefono);

// Según el tipo de llamada usamos un texto distinto

if(tipo == Calls.INCOMING_TYPE)

tipoLlamada = "ENTRADA";

else if(tipo == Calls.OUTGOING_TYPE)

357
tipoLlamada = "SALIDA";

else if(tipo == Calls.MISSED_TYPE)

tipoLlamada = "PERDIDA";

// Mostramos la información

txtResultados.append(tipoLlamada + " : " + telefono + "\n");

} while (cur.moveToNext()); // end while

} else txtResultados.setText("No hay ninguna llamada en el histórico del


teléfono. Para que funcione bien esta aplicación debes simular alguna
llamada entrante o saliente. En la teoría del curso de esta Unidad se
muestra cómo hacerlo.");

Además, en el código fuente anterior decodificamos el valor del tipo de llamada


comparando el resultado con las constantes Calls.INCOMING_TYPE (llamada entrante),
Calls.OUTGOING_TYPE (llamada saliente) y Calls.MISSED_TYPE (llamada perdida).

Para que la aplicación pueda acceder al historial de llamadas del dispositivo hay que
incluir en el fichero AndroidManifest.xml el permiso READ_CONTACTS:

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

Si ejecutas el ejemplo 2, verás que tiene el siguiente aspecto:

358
Content Providers, servicios y notificaciones

7.4 SERVICIOS DE ANDROID Y RECEPTORES DE MENSAJES DE


DIFUSIÓN

7.4.1 Servicios (Services)

Un Servicio (en inglés service) es un componente de una aplicación Android que se


ejecuta en segundo plano, sin interactuar con el usuario (no tiene interfaz de usuario) y realiza
operaciones de larga duración.

La plataforma Android ofrece una gran cantidad de servicios predefinidos en el sistema


a los que podemos acceder a través de las clases de tipo Manager. En una Actividad
podemos acceder a estos servicios a través del método getSystemService().

Cuando una aplicación Android define sus propios Servicios, deben ser declarados en
el fichero AndroidManifest.xml del proyecto.

Un componente de una aplicación Android puede iniciar un servicio que seguirá


funcionando en segundo plano, incluso si el usuario cambiara a otra aplicación.

Además, un componente de la aplicación puede unirse (en inglés bind) al servicio para
interactuar con él e incluso realizar comunicaciones entre procesos. Por ejemplo, un servicio
podría conectarse a Internet en un segundo plano para descargar noticias, reproducir música,
etcétera,.

Un servicio puede funcionar de dos modos:

• Autónomo: cuando un componente de la aplicación, por ejemplo, una


actividad, inicia el servicio mediante el método StartService(). Una vez
arrancado, el servicio puede ejecutarse en segundo plano de forma indefinida,
incluso si el componente que lo inició se destruye. Normalmente, un servicio
iniciado de esta forma realiza una única operación y no devuelve el resultado al
componente que lo inicia. Por ejemplo, puede descargar de Internet un archivo
o cargarlo. Cuando la operación finaliza, el servicio debe detenerse.

• Dependiente o Ligado (en inglés a este modo se le denomina "bind"): cuando


un componente de la aplicación se une al servicio mediante el método
bindService(). Un servicio ligado ofrece una interfaz de tipo cliente-servidor
que permite a los componentes de una aplicación interactuar con él enviando
peticiones y recibiendo su resultado. Un servicio ligado sólo se ejecuta
mientras otro componente de la aplicación está unido a él. Es posible unir un
mismo servicio a varios componentes de una o de varias aplicaciones al mismo
tiempo; sin embargo, cuando todos ellos se “desligan”, el servicio se destruye.

Un servicio puede funcionar de las dos formas anteriores simultáneamente, es decir, se


puede arrancar en modo Autónomo (de manera indefinida) y también en modo Ligado.
359
Simplemente hay que implementar los métodos onStartCommand() para el modo Autónomo y
onBind() para el modo Ligado.

Cualquier componente de una aplicación puede iniciar un servicio. Incluso un


componente de otra aplicación distinta a la que define el servicio también puede iniciarlo de la
misma forma que iniciaríamos una Actividad de otra aplicación mediante Intenciones.

También se puede declarar un servicio como privado en la aplicación, en el archivo


AndroidManifest.xml, y bloquear el acceso desde otras aplicaciones.

Los servicios tienen que ser declarados en el archivo AndroidManifest.xml con la


etiqueta <service android:name="nombreClase"> </service> y la implementación de la
clase debe heredarse de la clase Service.

IMPORTANTE: los servicios propios de una aplicación se ejecutan en el hilo principal de su


proceso; por lo tanto, para no bloquear el hilo principal o de la interfaz debemos, ejecutar estos
servicios con hilos de ejecución, tal y como hemos visto en la Unidad 3.

7.4.2 Servicios propios

Una aplicación puede declarar su propio servicio para llevar a cabo operaciones que
tarden en ejecutarse y no necesiten interactuar con el usuario o para suministrar una nueva
funcionalidad a otras aplicaciones.

A continuación, se muestra un esquema con los métodos que invoca Android cuando
lanzamos un servicio según su modo de funcionamiento:

360
Content Providers, servicios y notificaciones

Una Actividad puede iniciar un servicio en modo Autónomo a través del método
StartService() y detenerlo mediante el método StopService(). Cuando lo hacemos, Android
invoca su método onCreate(); después, se invoca el método onStartCommand() con los datos
proporcionados por la Intención de la actividad.

En el método startService() también podemos indicar como parámetro el


comportamiento del ciclo de vida de los servicios:

• START_STICKY: se utiliza para indicar que el servicio debe ser explícitamente


iniciado o parado.

• START_NOT_STICKY: el servicio termina automáticamente cuando el método


onStartCommand() finaliza su ejecución.

Si la actividad quiere interactuar con un servicio (modo Dependiente o Ligado) para, por
ejemplo, mostrar el progreso de una operación, puede utilizar el método bindService(). Para
esto, hay que usar el objeto ServiceConnection, que permite conectarse al servicio y devuelve
un objeto de tipo IBinder, que la actividad puede utilizar para comunicar con el servicio. Más
adelante veremos en detalle cómo definir servicios en modo Ligado dentro de las aplicaciones
Android.

7.4.3 Receptor de mensajes de difución (Broadcast Receiver)

Hay casos en los que se usan mensajes de difusión (Broadcast) para comunicar
eventos entre servicios. Estos mensajes son, en realidad, Intents.

En este caso usamos la clase Receptor de mensajes de difusión (BroadcastReceiver),


que debemos declarar en el archivo AndroidManifest.xml. Esta clase puede recibir
Intenciones (Intents), es decir, mensajes enviados por otro componente de Android mediante
el método sendBroadcast() de la clase Context (contexto de la aplicación).

La clase BroadCastReceiver define el único método OnReceive() donde se recibe el


mensaje de difusión; por lo tanto, fuera de este método no se puede realizar ninguna operación
asíncrona porque el mensaje de difusión ya no está activo.

7.4.4 Intención pendiente (Pending Intent)

En este apartado también hacemos uso de las Intenciones pendientes (Pending


Intents). Una Intención pendiente es un tipo de Intent (mensaje entre componentes de
Android) que permite que otra aplicación ejecute un bloque de código predefinido con los
permisos de ejecución de la aplicación que inicia esta Intención pendiente.

Este tipo de Intenciones se usa mucho para iniciar aplicaciones como el Administrador
de notificaciones (Notification Manager) y Administrador de alarmas (Alarm Manager).

361
Para enviar un mensaje de difusión mediante una Intención pendiente hay que usar su
método getBroadcast(). Para iniciar una subactividad mediante una Intención pendiente hay
que usar su método getActivity().

7.4.5 Ejemplo de Receptor de mensajes (Broadcast Receiver)

A continuación, vamos a definir un receptor de mensajes de difusión (Broadcast


Receiver) que escucha los mensajes que lanza Android al resto de componentes del sistema
operativo cuando ocurre un cambio en el estado del teléfono. Si el dispositivo recibe una
llamada de teléfono, entonces nuestro receptor de mensajes recibirá una notificación y
registrará la llamada.

Para que la aplicación funcione bien, debemos incluir las siguientes sentencias en el
archivo AndroidManifest.xml del proyecto:

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

<receiver android:name="ReceptorLlamadas">

<intent-filter>

<action android:name="android.intent.action.PHONE_STATE">

</action>

</intent-filter>

</receiver>

</application>

...

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

</uses-permission>

En las sentencias anteriores hemos declarado al sistema usando la etiqueta


<receiver> que esta aplicación desea recibir los mensajes de difusión del tipo (etiqueta
<intent-filter>) estado del teléfono (PHONE_STATE) usando la clase ReceptorLlamadas para
gestionarlas.

La clase ReceptorLlamadas que implementa el receptor de mensajes de difusión


contiene las siguientes sentencias:

public class ReceptorLlamadas extends BroadcastReceiver {

362
Content Providers, servicios y notificaciones

@Override

public void onReceive(Context context, Intent intent) {

Bundle extras = intent.getExtras();

if (extras != null) {

String estado = extras.getString(TelephonyManager.EXTRA_STATE);

Log.w("ESTADO TELEFONO", estado);

if (estado.equals(TelephonyManager.EXTRA_STATE_RINGING)) {

String numeroTelefono= extras


.getString(TelephonyManager.EXTRA_INCOMING_NUMBER);

Log.w("NUMERO TELEFONO", numeroTelefono);

Como hemos comentado anteriormente, el mensaje de difusión se recibe en el método


onReceive() de la clase BroadcastReceiver. En este método hemos obtenido la información
extra de la intención y la hemos mostrado en el Log de mensajes de Eclipse.

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Receptor de mensajes de difusión) de la


Unidad 7. Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del
programa anterior, en el que hemos definido un Receptor de mensajes de difusión.

Si ejecutamos la aplicación, usando el DDMS para simular una llamada de teléfono


entrante, veremos la siguiente pantalla:

363
7.4.6 Ejemplo de envío y recepción de mensajes internos en una aplicación y uso de
servicios por defecto de Android

En este Ejemplo 3 vamos a usar el Gestor de alarmas (AlarmManager) y el de


vibraciones del teléfono (VibratorManager) para iniciar los servicios por defecto "Alarma" y
"Vibración" de Android. Vamos a configurar una alarma en el gestor de alarmas de Android y,
cuando termine la cuenta atrás del tiempo que establezca el usuario, el gestor de alertas
mandará un mensaje de difusión al receptor de mensajes que hemos definido previamente en
la misma aplicación.

Para recibir el mensaje de difusión hemos creado el receptor MiBroadcastReceiver a


partir de la clase BroadcastReceiver:

public class MiBroadcastReceiver extends BroadcastReceiver {

@Override

// Definimos el método onReceive para recibir mensajes de difusión

public void onReceive(Context context, Intent intent) {

Toast.makeText(context, "¡Se ha acabado la cuenta atrás! \nEl


teléfono está vibrando", Toast.LENGTH_LONG).show();

// Vibramos el teléfono durante 2 segundos obteniendo el servicio


Vibrator de Android

Vibrator vibrator = (Vibrator)


context.getSystemService(Context.VIBRATOR_SERVICE);

vibrator.vibrate(2000);
364
Content Providers, servicios y notificaciones

Este receptor de mensajes busca el servicio de Vibración (Vibrator), se conecta a él y


le indica que vibre el teléfono durante dos segundos.

Para cargar el servicio "Vibración" por defecto de Android hemos usado el método
getSystemService(), al que indicamos como parámetro el nombre del servicio al que
queremos acceder.

Para que Android conozca que tiene disponible un receptor de mensajes de difusión y
permita a la aplicación el acceso al servicio de vibración, debemos añadir al fichero
AndroidManifest.xml las siguientes líneas:

<receiver android:name="MiBroadcastReceiver"></receiver>

...

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

A continuación, solo queda indicar en la actividad principal que se inicie una la cuenta
atrás:

public class AlarmaActivity extends Activity {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

public void iniciarCuenta(View view) {

// Obtenemos el tiempo de la cuenta atrás

EditText texto = (EditText) findViewById(R.id.tiempo);

if (texto.getText().equals("")){

Toast.makeText(this, "Al menos debes indicar 1 segundo",

Toast.LENGTH_LONG).show();

return;

365
int i = Integer.parseInt(texto.getText().toString());

// Cargamos el BroadcastReceiver

Intent intent = new Intent(this, MiBroadcastReceiver.class);

// Lo iniciamos como una Intención pendiente

PendingIntent pendingIntent = PendingIntent.getBroadcast(

this.getApplicationContext(), 1, intent, 0);

// Creamos una alarma

AlarmManager alarmManager = (AlarmManager)


getSystemService(ALARM_SERVICE);

// Establecemos el tiempo de la alarma e indicamos el pendingIntent


que se debe ejecutar cuando acabe la cuenta

alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()

+ (i * 1000), pendingIntent);

// Mostramos un mensaje indicando que comienza la cuenta atrás

Toast.makeText(this, "Inicio de Cuenta atrás de " + i + " segundos",

Toast.LENGTH_LONG).show();

Para cargar el servicio "Alarma" por defecto de Android, hemos usado el método
getSystemService(), al que indicamos como parámetro el nombre del servicio al que
queremos acceder.

En el código anterior podemos observar que hemos usado la clase AlarmManager


para acceder al servicio de gestión de alarmas. Con su método set() se crea una nueva alarma
que salta pasados n segundos y que lanza, a continuación, la intención pendiente (es realidad
es una intención que hereda los permisos de la actividad principal).

Esta intención pendiente se forma a partir de una intención normal que invoca
explícitamente la clase que recibe el mensaje y que transformamos en un mensaje de difusión
con el método getBroadcast() de PendingIntent.

Si ejecutas el Ejemplo 3 de esta Unidad verás las siguientes pantallas:

366
Content Providers, servicios y notificaciones

7.4.7 Crear un servicio propio

En el Ejemplo 4 de esta Unidad vamos a ver cómo definir un servicio privado en modo
Ligado dentro de una aplicación Android.

Los servicios deben utilizarse para mantener en segundo plano tareas en ejecución de
la aplicación, como descargar mensajes de correo de un servidor.

Cuando el usuario solicita que se actualice su buzón de correo, la aplicación que ya


está ligada (en inglés bind) al servicio, invoca uno de sus métodos para obtener los nuevos
mensajes recibidos.

Como ya hemos comentado, para crear un servicio debemos definir una clase que se
extienda de la clase Service de Android:

public class Servicio extends Service {

// Variable donde guardamos los datos que devuelve el servicio

private ArrayList<String> listado = new ArrayList<String>();

// Constante donde tenemos los datos que vamos a ir cargando cada 5


segundos en la variable anterior

private static String[] listadoDatos = {

"El comercio internacional ha aumentado un 7%",

"Hoy se publica un nuevo libro de Pérez Jiménez",

"Benetton retira la foto que irritó al Vaticano",

"Diego Rivera vuelve al Nueva York de la crisis",


367
"Facebook reconoce un ataque coordinado",

"Bradley Cooper, el hombre más sexy del mundo",

"Dimite el responsable en Europa del FMI por 'motivos personales'",

"El invierno ya está aquí" };

// Usamos el temporizador para ir añadiendo datos al listado

private Timer temporizador = new Timer();

// Cada 5 segundos actualizamos los datos del listado

private static final long INTERVALO = 5000;

// IBinder que usa la actividad principal para unirse al servicio y obtener


información

private final IBinder miBinder = new MiBinder();

// Variable que usamos para controlar el último elemento añadido al listado

private int indice = 0;

// Debemos definir redefinir el método onCreate

public void onCreate() {

super.onCreate();

// Iniciamos el temporizado que va cargando datos poco a poco en el


listado

temporizador.scheduleAtFixedRate(new TimerTask() {

@Override

public void run() {

// Si el listado ya contiene los 7 elementos, quitamos el primer


elemento

if (listado.size() >= 8) {

listado.remove(0);

// Añadimos el listado el elemento siguiente de la matriz


constante

listado.add(listadoDatos[indice++]);

// Si ya hemos llegado al último elemento, volvemos a empezar

if (indice >= listadoDatos.length) {

indice = 0;

368
Content Providers, servicios y notificaciones

}, 0, INTERVALO);

// Debemos redefinir el método onDestroy

@Override

public void onDestroy() {

super.onDestroy();

// Si el temporizador sigue funcionando, lo liberamos de la memoria

if (temporizador != null) {

temporizador.cancel();

// Es obligatorio redefinir este método.

// Devuelve el canal de comunicación con el servicio.

@Override

public IBinder onBind(Intent arg0) {

return miBinder;

// Clase que devuelve el contexto del servicio

public class MiBinder extends Binder {

Servicio getService() {

return Servicio.this;

// Método del servicio que invoca la actividad principal

public List<String> getDatos() {

return listado;

369
Como vamos a usar el servicio en modo Ligado, hemos definido el método onBind() en
el código Java anterior.

En el archivo AndroidManifest.xml debemos declarar el nuevo servicio:

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

En la actividad principal del Ejemplo 4 implementamos cómo usar el servicio en modo


Ligado:

public class ServicioActivity extends Activity {

// Variable donde almacenamos el servicio

private Servicio s;

// Matriz que se usa para cargar el adaptador del ListView de la actividad


principal

private ArrayList<String> matrizAdaptador;

// Adaptador del ListView de la actividad principal

private ArrayAdapter<String> adaptador;

// Variable que recibe la Conexión al servicio de la aplicación

private ServiceConnection miConexion = new ServiceConnection() {

// Al conectar al servicio, obtenemos una referencia del mismo y

// mostramos un mensaje al usuario

public void onServiceConnected(ComponentName className, IBinder


binder) {

s = ((Servicio.MiBinder) binder).getService();

Toast.makeText(ServicioActivity.this, "Conectado al servicio",

Toast.LENGTH_SHORT).show();

// Desconexión del servicio, liberamos variables

public void onServiceDisconnected(ComponentName className) {

s = null;

};

370
Content Providers, servicios y notificaciones

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

// Unimos esta actividad al servicio indicando mediante una Intención


explícita el nombre del servicio, la variable de conexión que
recibe el puntero del servicio y el modo de operación

bindService(new Intent(this, Servicio.class), miConexion,


Context.BIND_AUTO_CREATE);

// Cargamos referencias ListView de la pantalla principal

matrizAdaptador = new ArrayList<String>();

adaptador = new ArrayAdapter<String>(this,


android.R.layout.simple_list_item_1, matrizAdaptador);

ListView list = (ListView) findViewById(R.id.list);

list.setAdapter(adaptador);

// Método que se invoca cuando el usuario hace clic sobre el botón de la


pantalla principal

public void buscarDatosServicio(View view) {

// Si el servicio está activo

if (s != null) {

// Obtenemos los nuevos datos del servicio

List<String> datos = s.getDatos();

// Limpiamos el adaptador con los nuevos datos

matrizAdaptador.clear();

matrizAdaptador.addAll(datos);

// Indicamos que los datos del adaptador han cambiado

adaptador.notifyDataSetChanged();

Para conectar con el servicio definido en la clase Servicio, hemos escrito la sentencia:
371
bindService(new Intent(this, Servicio.class), miConexion,
Context.BIND_AUTO_CREATE);

El método bindService (Intent service, ServiceConnection conn, int flags) se


invoca con los siguientes tres parámetros:

• service: Intent que identifica el servicio al que queremos conectar. Este Intent
puede ser explícito (como en el ejemplo) indicando el nombre de la clase que
implementa el servicio o implícito señalando la acción que se define mediante
un IntentFilter de un servicio publicado en el sistema.

• conn: recibe la información del resultado de la clase de conexión


ServiceConnection.

• flags: opciones que podemos indicar al unirnos al servicio. Puede contener 0,


BIND_AUTO_CREATE (crea el servicio mientras haya componentes ligados a
él), BIND_DEBUG_UNBIND (incluye información de depuración cuando se
produce un desligue de los componentes), BIND_NOT_FOREGROUND (no
permite que el servicio cambie de hilo de ejecución), BIND_ABOVE_CLIENT
(el servicio tiene más prioridad de ejecución que la aplicación que lo inicia),
BIND_ALLOW_OOM_MANAGEMENT (servicio normal que puede ser
eliminado de memoria si el sistema la necesita) o BIND_WAIVE_PRIORITY (el
servicio se trata en segundo plano sin cambio de prioridad), etcétera.

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

Si ejecutas la aplicación y pulsas el botón “Cargar información del servicio”, verás la


siguiente pantalla:

372
Content Providers, servicios y notificaciones

Si pulsas el botón cada 5 segundos, verás que la aplicación recarga datos en la


pantalla principal.

7.5 NOTIFICACIONES AL USUARIO EN ANDROID

En Android existen varias formas de notificar mensajes o información al usuario.

En la Unidad 3 de este curso ya hemos visto el uso de Diálogos para mostrar al


usuario información e, incluso, solicitar que introduzca algún texto.

En este apartado vamos a estudiar dos tipos de notificaciones más:

• Mensajes emergentes: en inglés Toast. Aunque ya hemos usado este tipo de


mensajes previamente en el curso, vamos a describir con más detalle toda su
funcionalidad, ya que son muy útiles en las aplicaciones Android.

• Mensajes en la barra de estado. Son mensajes que aparecen en forma de


icono en la barra de estado en la parte superior del dispositivo:

7.5.1 Mensajes emergentes (Toast)

Un mensaje emergente (en inglés Toast) es un mensaje que se muestra en la pantalla


del dispositivo Android durante unos segundos y desaparece automáticamente sin requerir
ningún tipo de actuación por parte del usuario.

373
Este mensaje no recibe el foco de la aplicación en ningún momento, es decir, no
interfiere con las acciones que esté realizando el usuario en ese momento.

Por defecto, aparecen en la parte inferior de la pantalla, dentro de un rectángulo gris


ligeramente translúcido. Este tipo de notificaciones son perfectas para mostrar mensajes
rápidos y sencillos al usuario, puesl no requiere confirmación.

Ya hemos visto durante el curso que su utilización es muy sencilla. La clase Toast
dispone del método estático makeText(Context context, CharSequence text, int duration) al
que debemos pasar como parámetros el contexto de la actividad, el texto del mensaje y el
tiempo que de permanecer en la pantalla en milisegundos.

En el parámetro duration podemos usar las siguientes constantes definidas por


Android:

• Toast.LENGTH_LONG: mensaje de duración larga. Se usa para textos muy


largos.

• Toast.LENGTH_SHORT: mensaje de duración corta. Se usa para mensajes


más cortos.

Tras obtener una referencia al objeto Toast a través de este método, usamos el
método show() para mostrar el mensaje en la pantalla.

En el Ejemplo 5 de esta Unidad vamos a definir distintos tipos de Toast.

Desde Eclipse puedes abrir el proyecto Ejemplo 5 (Notificaciones) de la Unidad 7. Estudia el


código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior, en el que
hemos usado distintos tipos de Toast.

Para comenzar, vamos a incluir un botón que muestre un Toast básico cuando
hagamos clic sobre él:

// Toast por defecto

xDefectoBtn.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View arg0) {

// Creamos el mensaje

Toast toast1 =

Toast.makeText(getApplicationContext(), "Toast por defecto",


Toast.LENGTH_SHORT);

// Mostramos el mensaje

toast1.show();

374
Content Providers, servicios y notificaciones

}
});

Si ejecutas la aplicación y pulsas el botón “Toast – Por defecto” verás la siguiente


pantalla:

También podemos personalizar este Toast cambiando su posición relativa en la


pantalla. Para esto utilizamos su método setGravity(), al que indicamos en qué zona
deseamos que aparezca la notificación. Esta zona se marca usando alguna de las constantes
definidas en la clase Gravity: CENTER, LEFT, BOTTOM, etcétera, o utilizando una
combinación de éstas.

En el Ejemplo 5 vamos a colocar el mensaje en la zona central derecha de la pantalla.


Para esto, hay un segundo botón en la aplicación que muestra un -Toast con estas
características:

// Toast con posicionamiento en pantalla

gravityBtn.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View arg0) {

Toast toast2 = Toast.makeText(getApplicationContext(), "Toast con


gravity", Toast.LENGTH_SHORT);

// Indicamos el posicionamiento
toast2.setGravity(Gravity.CENTER|Gravity.RIGHT,0,0);

toast2.show();

375
}
});

Si volvemos a ejecutar la aplicación y pulsamos el nuevo botón, veremos que el Toast


aparece en la zona indicada de la pantalla:

Es posible personalizar por completo el aspecto del mensaje. Android ofrece la


posibilidad de definir un fichero de diseño (layout) XML propio para Toast, donde podemos
incluir todos los elementos necesarios para adaptar la notificación a las necesidades de la
aplicación. Para este Ejemplo 5 hemos definido un layout sencillo con una imagen y una
etiqueta de texto sobre un rectángulo gris. Si abres el fichero res/layout/layout_toast.xml
podrás ver su diseño:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/layoutToast"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:orientation="horizontal"

android:background="#555555"

376
Content Providers, servicios y notificaciones

android:padding="5dip" >

<ImageView android:id="@+id/imagen"

android:layout_height="wrap_content"

android:layout_width="wrap_content"

android:src="@drawable/info" />

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

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center_vertical"

android:textColor="#FFFFFF"

android:paddingLeft="10dip" />

</LinearLayout>

Para asignar este fichero de diseño (layout) a un Toast, hay que proceder de una
forma algo distinta a como lo hemos hecho en las anteriores notificaciones.

En primer lugar, hay que inflar el layout mediante un objeto LayoutInflater, como ya
hemos usado en varias ocasiones a lo largo del curso, para diseñar la interfaz de usuario. Una
vez construido el layout, modificamos los valores de los distintos componentes internos de éste
para mostrar la información.

En este ejemplo, modificamos el mensaje de la etiqueta de texto y asignamos


estáticamente una imagen en el layout XML mediante el atributo android:src. Después,
establecemos la duración de la notificación con el método setDuration() y asignamos el layout
personalizado al Toast mediante el método setView(). El código fuente incluido en el tercer
botón del ejemplo tiene este aspecto:

// Toast con diseño

layoutBtn.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View arg0) {

// Creamos el Toast

Toast toast3 = new Toast(getApplicationContext());

// Inflamos el diseño de layout_toast.xml

LayoutInflater inflater = getLayoutInflater();

377
View layout = inflater.inflate(R.layout.layout_toast,

(ViewGroup) findViewById(R.id.layoutToast));

// Asignamos los componentes del diseño

TextView txtMsg = (TextView)layout.findViewById(R.id.mensajeLbl);

txtMsg.setText("Toast con diseño personalizado");

// Indicamos la duración corta para el mensaje

toast3.setDuration(Toast.LENGTH_SHORT);

// Asignamos el diseño al Toast

toast3.setView(layout);

// Mostramos el Toast

toast3.show();

}
});

Si ejecutamos ahora la aplicación del ejemplo y pulsamos el botón “Toast –


Personalizado”, aparece el Toast con la estructura definida en el archivo de diseño layout
personalizado:

7.5.2 Notificaciones en la barra de estado

En este apartado vamos a tratar otro tipo de notificaciones más persistentes y


complejas de implementar, que son las notificaciones de la barra de estado de Android.

Estas notificaciones son las que muestran los dispositivos Android cuando recibimos un
mensaje SMS, hay actualizaciones disponibles, está el reproductor de música funcionando en
segundo plano, etcétera.

Estas notificaciones consisten en un icono y un texto que aparece en la barra de estado


superior. Adicionalmente, podemos indicar un mensaje más largo y descriptivo y una marca de
fecha/hora que aparece al desplegar la bandeja del sistema.
378
Content Providers, servicios y notificaciones

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

Arrastrar

Si arrastramos la barra de estado del dispositivo, se despliega la bandeja del sistema


con más información. En este ejemplo en concreto se informa del evento producido (“Missed
calls“), los números de teléfonos asociados y la fecha/hora del evento. Además, al pulsar sobre
la notificación se abre automáticamente el historial de llamadas.

En el Ejemplo 5 de esta Unidad vamos a utilizar este tipo de notificaciones.

En este ejemplo hemos añadido un nuevo botón que genera una notificación en la
barra de estado con los elementos comentados y con la posibilidad de dirigirnos a la propia
aplicación del ejemplo cuando se pulsa sobre la notificación.

Para generar notificaciones en la barra de estado del sistema, lo primero que hay que
hacer es obtener una referencia al servicio de notificaciones de Android usando la clase
NotificationManager.

Utilizamos el método getSystemService() indicando como parámetro el identificador


del servicio al que queremos conectar, en este caso a Context.NOTIFICATION_SERVICE.

//Obtenemos una referencia al servicio de notificaciones

379
String ns = Context.NOTIFICATION_SERVICE;

NotificationManager notManager = (NotificationManager) getSystemService(ns);

Después, configuramos las características de la notificación. En primer lugar,


establecemos el icono y el texto que aparece en la barra de estado. También registramos la
fecha y hora asociadas a la notificación. Con estos datos construimos un objeto Notification.
En este ejemplo, utilizamos un icono predefinido de Android, el mensaje “¡Atención!” y
registramos la fecha/hora actual indicada por el método System.currentTimeMillis():

//Configuramos la notificación que va a aparecer en la barra

int icono = android.R.drawable.stat_sys_warning;

CharSequence textoEstado = "¡Atención!";

long hora = System.currentTimeMillis();

// Creamos la notificación

Notification notificacion = new Notification(icono, textoEstado, hora);

A continuación, utilizamos el método setLatestEventInfo() para asociar a la


notificación la información que aparece al desplegar la bandeja del sistema (título y
descripción) e indicar la actividad que debe iniciarse si el usuario pulsa sobre la notificación.
Los dos primeros datos son simples cadenas de texto.

Para indicar la actividad que se debe ejecutar si el usuario pulsa sobre la notificación,
debemos construir una Intención pendiente PendingIntent, que ya hemos usado en el
apartado anterior de esta Unidad.

Esta Intención pendiente contiene la información de la actividad asociada a la


notificación que será lanzada al pulsar sobre ella. Para esto, definimos un objeto Intent
indicando la clase de la actividad concreta que se debe ejecutar. En este ejemplo el objeto es
la propia actividad principal (NotificacionesActivity.class). Este Intent lo utilizamos para
construir el PendingIntent final mediante el método PendingIntent.getActivity().

Veamos cómo queda esta última parte del código, comentado:

Intent notIntent = new Intent(contexto, NotificacionesActivity.class);

// Usamos una PendingIntent para crear la notificación

PendingIntent contIntent = PendingIntent.getActivity(

contexto, 0, notIntent, 0);

// Incluimos la información de la notificación

notificacion.setLatestEventInfo(contexto, titulo, descripcion, contIntent);

380
Content Providers, servicios y notificaciones

Es posible indicar opciones adicionales, como, por ejemplo, que la notificación


desaparezca automáticamente de la bandeja del sistema cuando se pulsa sobre ella. Esto lo
conseguimos usando al atributo flags de la notificación con el valor
Notification.FLAG_AUTO_CANCEL.

También podríamos indicar que, al crearse la notificación, el dispositivo suene, vibre o


se encienda el LED de estado presente en muchos dispositivos. Para ello, basta con añadir al
atributo defaults de la notificación los valores DEFAULT_SOUND, DEFAULT_VIBRATE o
DEFAULT_LIGHTS.

//AutoCancel: cuando se pulsa la notificación desaparece

notificacion.flags |= Notification.FLAG_AUTO_CANCEL;

//Para añadir sonido, vibración y luces hay que descomentar estas sentencias

//notif.defaults |= Notification.DEFAULT_SOUND;

//notif.defaults |= Notification.DEFAULT_VIBRATE;

//notif.defaults |= Notification.DEFAULT_LIGHTS;

Existen otras muchas opciones y personalizaciones de estos atributos flags y defaults


que se pueden consultar en la documentación oficial de la clase Notification de Android.

Para acabar, una vez tenemos definidas las opciones de la notificación, podemos
generarla invocando el método notify() y pasando como parámetro un identificador único
definido por la aplicación, así como el objeto Notification construido anteriormente.

//Enviamos la notificación

notManager.notify(ID_MEN_BARRA_NOTIF, notificacion);

Si volvemos a ejecutar la aplicación y pulsamos de nuevo el botón “Notificación en la


barra de estado”, veremos que aparece un icono en la barra de estado del dispositivo virtual:

381
Si desplegamos la bandeja del sistema, podemos verificar el resto de información de la
notificación:

Por último, si pulsamos sobre la notificación, se abre automáticamente de nuevo la


aplicación de este ejemplo. Además, la notificación desaparece de la bandeja del sistema, ya
que lo habíamos configurado en el código Java con la opción FLAG_AUTO_CANCEL:

382
Content Providers, servicios y notificaciones

Desde Eclipse puedes abrir el proyecto Ejemplo 5 (Notificaciones) de la Unidad 7. Estudia el


código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior, en el que
hemos lanzado una notificación a la barra de estado del dispositivo.

7.6 USO DE VIEWPAGER EN APLICACIONES ANDROID

Si has utilizado alguna vez un dispositivo Android, te habrás dado cuenta de que
algunas aplicaciones permiten desplazar páginas deslizando el dedo horizontalmente sobre la
pantalla. Por ejemplo, en la aplicación del Android Market y en el visor de imágenes podemos
cambiar de página dentro de la misma aplicación:

383
Al desplazar el dedo se
cambia de pantalla.

Para desarrollar esta funcionalidad hay que emplear el componente ViewPager de


Android que está heredado de la clase ViewGroup.

Este componente no forma parte de las clases por defecto del SDK de Android. Está
incluido en el paquete externo de Compatibilidad de Android que deberías haber añadido al
instalar el SDK de Android en Eclipse. Para comprobar que está bien añadido, haz clic en el
botón "Opens the Android SDK Manager" de Eclipse:

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

Nota: el número de revisión puede ser mayor que 4.

Puedes encontrar estas librerías en el directorio


384
Content Providers, servicios y notificaciones

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

7.6.1 Cómo se usa el componente ViewPager

A continuación, vamos a mostrar en el Ejemplo 6 de esta Unidad cómo utilizar el


componente ViewPager en una aplicación Android.

Una vez que hemos comprobado que tenemos las librerías extra de compatibilidad de
Android, procedemos a incluirlas en el proyecto.

En este proyecto hemos creado la carpeta "libs" y copiado dentro el archivo android-
support-v4.jar del directorio donde se encuentre la librería:

A continuación, añadimos la librería al Build Path haciendo clic con el botón derecho
del ratón sobre el archivo de la librería y eligiendo la opción "Build Path->Add to Build Path"
del menú desplegable:

385
Para comprobar que hemos incluido la librería correctamente en Eclipse, debe
aparecer como Librería referenciada ("Referenced Libraries"):

La aplicación que vamos a desarrollar consta de una Actividad que muestra un visor
sencillo de imágenes dentro del ViewPager. Para generar las páginas contenidas en este
ViewPager es necesario usar un objeto PagerAdapter, que se encarga de alimentar de
páginas al componente ViewPager.

Veamos las sentencias comentadas para crear la Actividad principal de la aplicación:

public class ViewPagerActivity extends Activity {

// Define el nº de páginas en el ViewPager

private static int NUMERO_VIEWS = 10;

// Variable de ViewPager

private ViewPager vPager;

// Adaptador del ViewPager

private CustomPagerAdapter vPagerAdapter;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

// Buscamos el ViewPager en el diseño main.xml

vPager = (ViewPager) findViewById(R.id.vPager);

// Creamos el adaptador de N Páginas y pasamos el contexto de la


aplicación

vPagerAdapter = new CustomPagerAdapter(NUMERO_VIEWS, this);

// Asignamos el adaptador al ViewPager

vPager.setAdapter(vPagerAdapter);

386
Content Providers, servicios y notificaciones

// Definimos el evento de cambio de página en el ViewPager

vPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

@Override

public void onPageSelected(int position) {

Toast.makeText(getBaseContext(), "Has cambiado a la pantalla " +


(position+1), 1).show();

@Override

public void onPageScrollStateChanged(int arg0) {

// No definimos nada en el evento al hacer scroll en la página

@Override

public void onPageScrolled(int arg0, float arg1, int arg2) {

// No definimos nada en el evento al hacer scroll en la página

}); // end setOnPageChangeListener

}
}

En el código anterior no hay nada especial que resaltar. Buscamos en el archivo de


diseño el ViewPager y le asignamos su adaptador con el método setAdapter(). Además,
usamos el método setOnPageChangeListener() para mostrar un mensaje Toast cada vez que
el usuario cambie de página.

El archivo de diseño Layout de la actividad principal se implementa así:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:background="#a4c639">

<android.support.v4.view.ViewPager

387
android:layout_width="match_parent"

android:layout_height="match_parent"

android:id="@+id/vPager"/>

</LinearLayout>

Como se trata de una Vista que se define en un paquete extra de Android, es necesario
incluir el nombre completo del mismo android.support.v4.view.ViewPager.

Luego, creamos el adaptador personalizado a partir de la clase PagerAdapter, para


que cree las páginas internas del ViewPager, devolviendo vistas según vamos desplazando el
dedo horizontalmente por la pantalla:

public class CustomPagerAdapter extends PagerAdapter{

// Variables donde guardamos el contexto y el número de páginas

private Context contexto;

private int nViews;

// Constructor de la clase

public CustomPagerAdapter(int nViews, Context contexto) {

this.contexto=contexto;

this.nViews=nViews;

@Override

// Devuelve el nº de página del Adaptador del ViewPager

public int getCount() {

return nViews;

/**

* Crea la página de la position indicada. El adaptador

* es el responsable de añadir componentes a cada página.

* @param collection La Vista (View) donde se almacena la página.

* @param position Número de página que debemos crear.

* @return Devuelve el objeto que representa la página. No tiene por qué

388
Content Providers, servicios y notificaciones

* ser una Vista, puede contener a su vez otras páginas.

*/

@Override

public Object instantiateItem(View collection, int position) {

/* Creamos mediante sentencias Java el diseño de la página.

* También podríamos haber guardado el diseño en un archivo

* xml y haberlo inflado aquí.

*/

// Creamos el Layout donde añadimos el resto de Vistas

LinearLayout linearLayout = new LinearLayout(contexto);

//Orientacion vertical = 1

linearLayout.setOrientation(1);

// Definimos una etiqueta de texto

TextView tv = new TextView(contexto);

tv.setText("Imagen número " + (position+1));

tv.setTextColor(Color.WHITE);

tv.setTextSize(30);

// Definimos una imagen

ImageView imagen = new ImageView(contexto);

// Buscamos la imagen en el directorio /res/drawable en función del nº


de página

int resID = contexto.getResources().getIdentifier("imagen"+


(position+1), "drawable", "es.mentor.unidad7.eje6.viewpager");

// Asignamos la imagen cargada del recurso

imagen.setImageResource(resID);

// Definimos unos parámetros para alinear la etiwueta superior

LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,


LayoutParams.WRAP_CONTENT);

params.setMargins(0, 0, 0, 20);

params.gravity=Gravity.CENTER;

// Añadimos la etiqueta superior al Layout con los parámetros


anteriores

linearLayout.addView(tv, params);

// Añadimos la imagen al Layout

linearLayout.addView(imagen);

389
// Añadimos la página a la colección de páginas

((ViewPager) collection).addView(linearLayout,0);

// Devolvemos el diseño de la página

return linearLayout;

} // end instantiateItem

/**

* Destruye el contenido de la página indicada en position. El adaptador

* es el responsable de borrar los componentes de cada página.

* @param collection La Vista (View) donde se elimina la página.

* @param position Número de página que debemos eliminar.

* @return object El mismo objeto creado en {@link #instantiateItem(View,


int)}.

*/

@Override

public void destroyItem(View collection, int position, Object view) {

((ViewPager) collection).removeView((LinearLayout) view);

/**

* Compara si la Vista view está instanciada en el Objeto object. Método

necesario para la clase ViewPager

*/

@Override

public boolean isViewFromObject(View view, Object object) {

return view==((LinearLayout)object);

/**

* Android invoca este método cuando el cambio de una de las páginas se ha


completado.

*/

@Override

390
Content Providers, servicios y notificaciones

public void finishUpdate(View arg0) {}

/**

* Método que se invoca cuando Android indica que hay que recuperar el
estado de ejecución

*/

@Override

public void restoreState(Parcelable arg0, ClassLoader arg1) {}

/**

* Método que se invoca cuando Android indica que hay que guardar el estado
de ejecución

*/

@Override

public Parcelable saveState() {

return null;

/**

* Android invoca este método cuando se inicia el cambio de una de las


páginas.

*/

@Override

public void startUpdate(View arg0) {}

Los métodos más importantes del código anterior son:

• instantiateItem: crea la página para la posición indicada como parámetro del


método. Este adaptador es el responsable de añadir las Vistas a cada página.
Creamos el diseño de la Vistas contenidas en la página mediante sentencias
Java. También podríamos haber guardado el diseño en un archivo xml y
haberlo inflado.

• destroyItem: destruye la página indicada en el parámetro posición.

Las imágenes que se cargan en el visor de imágenes están almacenadas en el


directorio /res/drawable del proyecto. Para cargarlas dinámicamente en función del número de
página que el adaptador CustomPagerAdapter debe crear hemos obtenido los recursos del

391
contexto con la orden contexto.getResources(); después, hemos buscado el ID del recurso de
la imagen usando el método getIdentifier(nombre_recurso, tipo_recurso,
paquete_recurso).

Desde Eclipse puedes abrir el proyecto Ejemplo 6 (ViewPager) de la Unidad 7. Estudia el


código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior, en el que
hemos usado un ViewPager.

Si ejecutamos la aplicación y arrastramos el ratón horizontalmente sobre la pantalla


del emulador simulando el efecto de un dedo (puede costar un poco hacerlo con el ratón),
veremos las siguientes ventanas:

Arrastrar Arrastrar

Cambio de página Cambio de página

392
Content Providers, servicios y notificaciones

 Un Proveedor de contenido (en inglés Content Provider) es el mecanismo


proporcionado por Android para compartir información entre aplicaciones.

 Los Proveedores de contenidos los usan muchas aplicaciones estándar de un


dispositivo Android, como, por ejemplo, la lista de contactos, la aplicación de SMS
mensajes cortos, el calendario, etcétera.

 Para implementar un Proveedor de contenidos propio, hay que usar la clase


ContentProvider de Android.

 Las tablas de la base de datos SQLite usadas por un Content Provider deben
incluir siempre el campo _ID que identifica sus registros de forma unívoca.

 El acceso a un Content Provider se realiza siempre mediante un identificador URI,


que es una cadena de texto parecida a una dirección Web de Internet.

 La clase ContentResolver de Android permite realizar acciones con cualquier


Content Provider que esté disponible en el sistema operativo Android.

 Un Servicio (en inglés Service) es un componente de una aplicación Android que se


ejecuta en segundo plano, sin interactuar con el usuario (no tiene interfaz de
usuario), para realizar operaciones de larga duración.

 La plataforma Android ofrece una gran cantidad de servicios predefinidos en el


sistema, a los que podemos acceder a través de la clase de tipo Manager.

 Un servicio puede funcionar de dos modos:

• Autónomo: el servicio se puede ejecutar en segundo plano de forma


indefinida, incluso si el componente que lo inició se destruye.

• Dependiente o Ligado (bind): ofrece una interfaz de tipo cliente-


servidor que permite a los componentes de una aplicación interactuar
con él enviando peticiones y recibiendo su resultado. Un servicio ligado
sólo se ejecuta mientras otro componente de la aplicación está unido a
él.

393
 Los servicios propios de una aplicación se ejecutan en el hilo principal de su
proceso; por lo tanto, para no bloquear el hilo principal o de interfaz, debemos
ejecutar estos servicios con hilos de ejecución.

 Para implementar un servicio propio en una aplicación tenemos que extender la


clase Service de Android.

 Se pueden usar mensajes de difusión (Broadcast) para comunicar eventos entre


servicios. Estos mensajes son, en realidad, Intents.

 La clase Receptor de mensajes de difusión (BroadcastReceiver) se usa para


recibir Intenciones (Intents), es decir, mensajes enviados por otro componente de
Android.

 En Android existen varias formas de notificar mensajes o información al


usuario.

• Diálogos: muestran o solicitan información al usuario.


• Mensajes emergentes (en inglés Toast).
• Mensajes de notificación en la barra de estado del dispositivo.

 Un mensaje emergente (en inglés Toast) es un mensaje que se muestra en la


pantalla del dispositivo Android durante unos segundos y desaparece
automáticamente sin requerir ningún tipo de actuación por parte del usuario.

 Los mensajes de notificación de la barra de estado de Android se muestran en


la barra de estado de los dispositivos Android cuando recibimos un mensaje
SMS, hay actualizaciones disponibles, está el reproductor de música funcionando,
etcétera.

 Para generar notificaciones en la barra de estado del sistema, hay que obtener una
referencia al servicio de notificaciones de Android usando la clase
NotificationManager.

 El componente ViewPager permite diseñar aplicaciones que incluyen páginas que


se pueden desplazar deslizando el dedo horizontalmente sobre la pantalla.

 Este componente ViewPager no forma parte de las clases por defecto del SDK de
Android. Está incluido en el paquete externo de Compatibilidad de Android.

394
ANDROID AVANZADO

ÍNDICE
8.1 INTRODUCCIÓN ................................................................................... 397

8.2 CÓMO DEPURAR APLICACIONES ANDROID CON ECLIPSE ..... 397


8.2.1 Estableciendo Puntos de interrupción (Breakpoints) ............... 399
8.2.2 Iniciar la depuración (Debug) del código .................................... 400
8.2.3 Datos de depuración (Debug) del código................................... 401
8.2.4 Desactivar la depuración de código ............................................ 403
8.2.5 Propiedades de los puntos de interrupción ............................... 404
8.2.6 Puntos de interrupción de excepciones ..................................... 405
8.2.7 Puntos de interrupción de método .............................................. 405
8.2.8 Puntos de interrupción de clase (class) ..................................... 405
8.2.9 Finalizar la Depuración del código .............................................. 406

8.3 USO DE MAPAS EN APLICACIONES ANDROID ............................ 406


8.3.1 Preparación del Entorno de programación ................................ 407
8.3.2 Cómo incluir mapas en las aplicaciones Android ..................... 410

8.4 DESARROLLO DE APLICACIONES SENSIBLES A LA


ORIENTACIÓN DEL DISPOSITIVO .................................................... 418
8.4.1 Cambio de orientación automática.............................................. 420
8.4.2 Mantener la información del estado durante el cambio de
orientación ...................................................................................... 424
8.4.3 Cambio de orientación Manual ...................................................... 427

8.5 DESPLEGAR APLICACIONES ANDROID EN DISPOSITIVOS


VIRTUALES (AVD) O REALES ........................................................... 431

8.6 CÓMO PUBLICAR APLICACIONES EN EL ANDROID MARKET .. 435


8.6.1 Alta de cuenta de desarrollador en el Android Market ............. 435
8.6.2 Recomendaciones sobre aplicaciones para Android Market .. 439
8.6.2.1 Recomendaciones sobre aplicaciones para Android
Market .................................................................................... 439
8.6.2.2 Buenas prácticas para el desarrollo de aplicaciones
Android .................................................................................. 440
8.6.3 Generar fichero APK con certificado para Android Market ..... 441
8.6.4 Publicar una aplicación Android en el Android Market............. 445

2
Android Avanzado

8.1 INTRODUCCIÓN

En esta Unidad vamos a explicar cómo depurar (debug en inglés) aplicaciones


Android con Eclipse.

Después, veremos cómo utilizar Mapas en aplicaciones Android mediante la API de


Google.

Asimismo, veremos cómo cambiar el aspecto de las aplicaciones Android cuando


cambia la orientación del dispositivo.

Finalmente, conoceremos cómo desplegar aplicaciones en un dispositivo real


Android y publicar una aplicación en el Android Market.

8.2 CÓMO DEPURAR APLICACIONES ANDROID CON ECLIPSE

La Depuración de programas es el proceso de identificar y corregir errores de


programación en tiempo de ejecución. En inglés se denomina debugging, ya que se asemeja
a la eliminación de bichos (bugs), que es como se denominan informalmente los errores de
programación

Para depurar (en inglés Debug) una aplicación Andriod, vamos a emplear las
capacidades disponibles en el entorno de desarrollo Eclipse. Para ello, nos serviremos de la
última versión disponible, la 3.7, a fecha de edición de este documento.

Para que el alumno o alumna pueda practicar la Depuración de código Android con
Eclipse, hemos creado un proyecto Android con las siguientes clases:

public class DepuracionActivity extends Activity {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

// Usamos la clase que ya existe en el otro fichero

Contador contador = new Contador();

contador.count();

System.out.println("Hemos contado " + contador.getResultado() +


" veces.");

397
Object o = null;

o.toString();

}
}

// Clase sencilla que implementa un contador

public class Contador {

// Variable para guardar la cuenta actual

private int resultado=0;

public int getResultado() {

return resultado;

// Método que cuenta de 2 en 2

public void count() {

for (int i = 0; i < 100; i++) {

resultado += i++;

}
}

Es recomendable abrir en Eclipse el Ejemplo 1 (Depuración) de la Unidad 8 y practicar los


comandos que se muestran a continuación.

Si ejecutas la aplicación tal y como está, verás que aparece el siguiente mensaje de error:

398
Android Avanzado

Si haces clic en el botón "Force close", la aplicación termina. Veamos cómo depurar
este programa que provoca un error.

8.2.1 Estableciendo Puntos de interrupción (Breakpoints)

En el desarrollo de software, un punto de interrupción (Breakpoint en inglés) es una


marca en el código fuente que indica al depurador del lenguaje en que estemos programando
que debe detener o pausar la ejecución del programa para poder evaluar los valores asignados
a las variables y permitir al programador detectar errores en tiempo de ejecución.

Para establecer puntos de interrupción con Eclipse, hay que hacer clic en la opción
"Toggle Breakpoint" del menú desplegable que aparece si pulsamos el botón derecho del
ratón sobre el número de línea del código fuente correspondiente. También podemos hacer
doble clic en este número de línea para activar o desactivar esta opción:

399
8.2.2 Iniciar la depuración (Debug) del código

Para iniciar la depuración del código hay que hacer clic en la opción "Run->Debug" del

menú principal de Eclipse. También podemos usar la tecla rápida [F11] o usar el icono del
menú principal.

Si lo hacemos, a continuación se instalará y se ejecutará la aplicación en el dispositivo


virtual. Después, Eclipse muestra el siguiente mensaje:

Contestaremos que sí para cambiar el tipo de Perspectiva a "Debug", muy útil para
depurar programas. A continuación, cambiará la perspectiva de Eclipse así:

Y la ejecución del programa se parará en la primera línea del código que tenga un
punto de interrupción.

Podemos usar los siguientes atajos de teclado para depurar el programa:

400
Android Avanzado

Comando Descripción

La ejecución pasa a la siguiente sentencia del programa. Si la sentencia siguiente


F5 es la llamada a un método o función, se continuará con la ejecución de las
sentencias de este método o función.

La ejecución pasa a la siguiente sentencia del programa. Si la sentencia siguiente


F6 es la llamada a un método o función, se continuará con la ejecución de la sentencia
siguiente sin entrar en el código de este método o función.

La ejecución sigue todas las sentencias de todos los métodos o funciones que
F7 formen nuestro programa. Es decir, ejecuta en secuencia todas las órdenes que
conforman el programa.

El programa se ejecuta hasta que se encuentre otro punto de interrupción o hasta


F8
que el usuario lo cierre.

Nota: también existen unos botones de acceso rápido que permiten ejecutar estas
órdenes. Observa la imagen siguiente:

8.2.3 Datos de depuración (Debug) del código

La vista "Debug" permite ver el contenido de la Pila "Stack" de la aplicación:

En la parte superior derecha de Eclipse podemos ver el contenido de las variables.


También podemos usar el menú para cambiar el tipo de variables que han de visualizarse,
opción muy útil cuando hemos definido muchas variables:

401
Es posible también usar este menú para cambiar las columnas que han de aparecer en
esta vista:

Además, es posible utilizar la opción "New Detail Formater" (menú desplegable con el
botón derecho del ratón) para modificar la información mostrada sobre la variable. Por ejemplo,
como el texto (posición de memoria de una variable)
es.mentor.unidad8.eje1.depuracion.Contador@4051b760 no dice nada, podemos usar la
opción "New Detail Formater"

402
Android Avanzado

para invocar un método de una clase y mostrar su resultado:

Ahora ya podemos ver el resultado:

8.2.4 Desactivar la depuración de código

Si deseas desactivar temporalmente todos los puntos de interrupción, puedes pulsar el


botón "Skip All Breakpoints":

Si pulsas este botón otra vez, los puntos de interrupción se activarán de nuevo.

403
8.2.5 Propiedades de los puntos de interrupción

Después de establecer un punto de interrupción, puedes seleccionar las propiedades


de este punto para, por ejemplo, establecer una condición lógica de parada. En las
propiedades se puede, por ejemplo, activar el punto de interrupción y parar la ejecución del
programa sólo cuando una variable tenga cierto valor o se cumpla cierta condición.

Para acceder a las propiedades del punto de interrupción, hay que hacer clic en la
opción "Breakpoint Properties..." del menú desplegable con el botón derecho del ratón sobre
el punto de interrupción:

En la ventana emergente podemos establecer la condición de parada del punto de


interrupción:

404
Android Avanzado

8.2.6 Puntos de interrupción de excepciones

Los puntos de interrupción de excepciones detienen la ejecución de la aplicación si se


inicia una excepción específica. Para definir este tipo de punto de interrupción, hay que hacer
clic en el icono de excepción siguiente:

8.2.7 Puntos de interrupción de método

Un punto de interrupción de tipo método se define haciendo doble clic en el borde


izquierdo del editor del método correspondiente. Detiene el programa durante al ejecutar el
método o, después, al finalizar la ejecución del mismo.

8.2.8 Puntos de interrupción de clase (class)

Un punto de interrupción de tipo clase se define haciendo doble clic en el borde


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

405
8.2.9 Finalizar la Depuración del código

Para finalizar la depuración del código basta con cambiar la Perspectiva a "Java" de
nuevo. Cuando hagamos alguna modificación del código fuente, aparecerá el siguiente
mensaje para indicar que no se puede sustituir el código de una aplicación ya instalada en el
emulador de Android y se pregunta si deseamos desconectar ("Disconnect") el modo Debug:

Nota: en esta Unidad 8 puedes encontrar el vídeo “Cómo depurar aplicaciones Android en
Eclipse”, que muestra visualmente cómo llevar a cabo la depuración del Ejemplo 1 de esta
Unidad.

8.3 USO DE MAPAS EN APLICACIONES ANDROID

En este apartado vamos utilizar mapas en aplicaciones de Android haciendo uso de la


API Android de Google Maps.

La mayoría de los dispositivos Android permiten determinar su ubicación geográfica


actual a través de un módulo GPS (del inglés Global Positioning System, que se traduce como
Sistema de Posicionamiento Global). Android dispone del paquete android.location, que
proporciona la API para determinar la posición actual geográfica.

406
Android Avanzado

8.3.1 Preparación del Entorno de programación

Antes de empezar a utilizar el servicio de mapas de Google es necesario comprobar


que tenemos instalado el paquete correspondiente a las APIs de Google. Este paquete se
llama normalmente “Google APIs by Google, Android API x, revisión y“.

Al instalar el SDK de Android en Eclipse deberías haber añadido ya este paquete. Para
comprobar que está correctamente instalado, haz clic en el botón "Opens the Android SDK
Manager" de Eclipse:

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

Nota: el número de revisión puede ser mayor que 2.

Para poder probar las aplicaciones en el emulador, también es necesario crear un


nuevo dispositivo virtual AVD que utilice este paquete como "target". Para ello, pulsamos el
botón "Opens the Android Virtual Device Manager":

Y se presenta una ventana, donde pulsamos sobre el botón "New". A continuación,


aparece otra nueva ventana, donde rellenamos los campos tal y como aparecen en esta
captura:

407
Para acabar de crear el dispositivo virtual, hacemos clic en el botón "Create AVD".

Para poder utilizar la API de Google Maps es necesario obtener previamente una
clave de uso (API Key) que estará asociada al certificado con el que firmamos digitalmente las
aplicaciones. En el apartado "Permisos y Seguridad" de la Unidad 5 ya hemos hablado de
estos certificados, necesarios para firmar aplicaciones.

Si cambiamos el certificado con el que firmamos nuestra aplicación, algo que


normalmente se hace como paso previo a la publicación de la aplicación en el Android Market,
tendremos que modificar también la clave de uso de la API.

Cuando compilamos una aplicación en Eclipse y la probamos en el emulador de


Android, se aplica automáticamente un certificado de depuración creado por defecto. Por lo
tanto, para poder depurar en el emulador aplicaciones que hagan uso de Google Maps, hay
que solicitar una clave asociada a este certificado de depuración.

En primer lugar, hay que localizar el fichero donde se almacenan los datos del
certificado de depuración "debug.keystore". Podemos conocer la ruta de este fichero
accediendo a las preferencias de Eclipse, sección "Android", apartado "Build":

408
Android Avanzado

En esta ventana copiamos en el portapapeles la ruta que aparece en el campo


“Default Debug Keystore“. Observa que hemos borrado intencionalmente la parte de la ruta
que será distinta en tu ordenador.

Una vez conocemos la ruta del fichero debug.keystore, vamos a acceder a él con la
herramienta keytool.exe de Java para obtener el hash MD5 del certificado. Esto lo hacemos
desde una ventana de línea de comandos en el directorio C:\Program Files
(x86)\Java\jre6\bin (o donde esté instalado Java) mediante la orden:

C:\Program Files (x86)\Java\jre6\bin>keytool -list -alias androiddebugkey -keystore


"ruta_del_certificado\debug.keystore" -storepass android -keypass android

Nota: es necesario usar la versión 6 de Java, pues en la 7 no funciona.

Si lo hacemos, veremos la siguiente ventana:

409
A continuación, copiamos en el portapapeles el dato que aparece identificado como
“Huella digital de certificado (MD5)”.

Después, accedemos a la Web de Google para solicitar una clave de utilización de la


API de Google Maps para depurar aplicaciones. En esta Web tendremos que escribir la Huella
digital MD5 de nuestro certificado para obtener la clave de uso de la API. En la siguiente
imagen se muestra el resultado:

Nota: Observa que hemos borrado intencionalmente parte de la clave, pues, cuando
solicites ésta, te darán otra diferente.

Ya hemos terminado la preparación del entorno de programación para poder utilizar los
servicios de Google Maps dentro de nuestras aplicaciones Android.

8.3.2 Cómo incluir mapas en las aplicaciones Android

En el Ejemplo 2 de esta Unidad vamos a desarrollar una aplicación que incluye un


mapa sobre el que podemos hacer unas operaciones sencillas, como cambiar a vista satélite o
desplazar el mapa.

Para poder ver este proyecto en tu emulador Android es necesario que obtengas la clave de
uso de la API de Mapas de Google y la cambies en el fichero de diseño main.xml de la interfaz
de usuario. Si no lo haces, arrancará la aplicación del ejemplo pero no se mostrará el mapa,
como en la imagen siguiente:

410
Android Avanzado

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

Para incluir un mapa de Google Maps en una aplicación Android, utilizamos el


componente MapView. Este componente se puede añadir al diseño de la pantalla como otro
componente normal. Sin embargo, para poder usarlo, hay que indicar la clave de uso de
Google Maps en el atributo android:apiKey tal y como se muestra a continuación:

411
<!-- Aquí se escribe la clave de uso de Google Maps -->

<com.google.android.maps.MapView

android:id="@+id/mapa"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:apiKey="xxxxxxxxxxxxxLIdwwbCEmC3DeN1omnaSkig"

android:clickable="true" />

Además, también hemos establecido el atributo clickable a true, para que el usuario
pueda interactuar con el componente si quiere, por ejemplo, desplazar el mapa con el dedo.

Los componentes MapView sólo se pueden utilizar desde una actividad de tipo
MapActivity. La clase MapActivity se extiende de la clase Activity y permite la gestión del
ciclo de vida de la Actividad y de los servicios de visualización de un mapas. De igual forma
que ListActivity se usa para mostrar listas, MapActivity se usa para mostrar mapas.

En el Ejemplo 2 la Actividad principal hereda la clase MapActivity, tal y como vemos


en el siguiente código:

public class MapasActivity extends MapActivity {

// Variables donde se definen los controles de la Actividad

private MapView mapa = null;

private Button sateliteBtn = null;

private Button irBtn = null;

private Button animarBtn = null;

private Button moverBtn = null;

private MapController controlMapa = null;

// Constantes que llevan a un punto en el mapa

private static Double latitud = 40.6550*1E6;

private static Double longitud = -4.7000*1E6;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

//Obtenemos una referencia a las Vistas de la Actividad

mapa = (MapView)findViewById(R.id.mapa);
412
Android Avanzado

sateliteBtn = (Button)findViewById(R.id.SateliteBtn);

irBtn = (Button)findViewById(R.id.IrBtn);

animarBtn = (Button)findViewById(R.id.AnimarBtn);

moverBtn = (Button)findViewById(R.id.MoverBtn);

//Definimos el Controlador del mapa

controlMapa = mapa.getController();

// Definimos un nuevo punto de localización

GeoPoint loc = new GeoPoint(latitud.intValue(), longitud.intValue());

// Centramos el mapa en este punto

controlMapa.setCenter(loc);

// Hacemos zoon a 6 (puede tomar el valor de 1 a 21)

controlMapa.setZoom(6);

//Mostramos los controles de zoom sobre el mapa

mapa.setBuiltInZoomControls(true);

// Definimos el evento onClick del botón Satélite

sateliteBtn.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View arg0) {

// Intercambiamos la capa de tipo satélite en el mapa

if(mapa.isSatellite())

mapa.setSatellite(false);

else

mapa.setSatellite(true);

});

// Definimos el evento onClick del botón Ir a...

irBtn.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View arg0) {

// Definimos un nuevo punto de localización

413
GeoPoint loc = new GeoPoint(latitud.intValue(),
longitud.intValue());

// Centramos el mapa en este punto

controlMapa.setCenter(loc);

// Hacemos zoon a 10 (puede tomar el valor de 1 a 21)

controlMapa.setZoom(10);

});

// Definimos el evento onClick del botón Animar

animarBtn.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View arg0) {

// Definimos un nuevo punto de localización

GeoPoint loc = new GeoPoint(latitud.intValue(),


longitud.intValue());

// Movemos con animación el mapa en este punto

controlMapa.animateTo(loc);

// Hacemos zoom sobre esa posición del mapa

int zoomActual = mapa.getZoomLevel();

for(int i=zoomActual; i<12; i++)

controlMapa.zoomIn();

});

// Definimos el evento onClick del botón Mover

moverBtn.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View arg0) {

// Movemos el mapa 1000 píxeles en horizontal y 50 en


vertical

controlMapa.scrollBy(1000, 50);

});

414
Android Avanzado

// Método obligado de la clase que indica si estamos mostrando una ruta

@Override

protected boolean isRouteDisplayed() {

return false;

}
}

A continuación, vamos a explicar las partes pertinentes del código anterior.

Como la Actividad principal se hereda de la clase MapActivity, es obligatorio


implementar el método isRouteDisplayed(), que debe devolver el valor true si vamos a mostrar
algún tipo de información de ruta sobre el mapa. Según los términos de licencia de uso de la
API de Google Maps, debe indicarse cuándo se usan sus mapas para este propósito. En este
ejemplo del curso nos limitamos a mostrar un mapa en la pantalla principal de la aplicación, por
lo que devolvemos el valor false.

Además, en el método onCreate() de la Actividad se invoca el método


setBuiltInZoomControls() de la referencia de componente MapView para mostrar los
controles de zoom estándar sobre el mapa, de modo que podamos acercar y alejar la vista del
mapa.

Por defecto, cuando usamos un MapView en una aplicación, se muestra en el modo de


mapa tradicional. Sin embargo, este componente también permite cambiar las capas a la vista
satélite, ver fotos de la calle con StreetView o mostrar la información del tráfico. Para ello,
podemos usar los siguientes métodos de la clase MapView:

• setSatellite(true)

• setStreetView(true)

• setTraffic(true)

También existen otros tres métodos para consultar el estado de cada uno de estos
modos: isSatellite(), isStreetView() y isTraffic().

En el evento onClick del botón sateliteBtn hemos usado el método setSatellite() para
intercambiar el modo satélite y el estándar.

Además de los métodos para personalizar el aspecto gráfico del mapa, también
disponemos de varios métodos para consultar la información geográfica visualizada en el
mismo. Por ejemplo, podemos saber las coordenadas geográficas en las que el mapa está
centrado actualmente mediante el método getMapCenter() y el nivel de zoom que está
aplicando a través del método getZoomLevel().

415
Como podemos observar en el código anterior, las coordenadas del centro del mapa se
obtienen mediante el método getMapCenter() en forma de objeto GeoPoint que encapsula los
valores de latitud y longitud expresados en microgrados (grados * 1E6). Los valores en la
magnitud grados se pueden obtener mediante los métodos getLatitudeE6() y
getLongitudeE6() respectivamente.

El nivel de zoom del mapa contiene un valor entero entre 1 y 21, siendo 21 el que
ofrece mayor nivel de detalle en el mapa.

Para modificar el centro del mapa, en primer lugar, debemos acceder al controlador del
mapa (MapController) mediante el método getController(). Este método devuelve un objeto
MapController con el que podemos modificar la posición central del mapa. Para ello, podemos
usar los métodos setCenter() y setZoom() a los que podemos indicar las coordenadas
centrales del mapa y el nivel de zoom deseado, respectivamente.

En este ejemplo hemos incluido un botón irBtn que centra el mapa sobre un punto
determinado y hemos aplicado un nivel de zoom (10), que permite distinguir en el mapa
algunos detalle. Si pruebas el ejemplo del curso, verás que el desplazamiento a la posición y el
zoom al nivel indicados se hacen de forma instantánea sin ningún tipo de animación.

Para mejorar la sensación de movimiento en el mapa, la API de Google nos ofrece otra
serie de métodos que permiten desplazar el mapa a una posición específica de forma
progresiva y aumentar o disminuir el nivel de zoom de forma “animada”. El método
animateTo(GeoPoint) desplaza el mapa hasta un punto determinado y los métodos zoomIn()
y zoomOut() aumentan o disminuyen de forma progresiva, respectivamente, en 1 el nivel de
zoom. En el botón animarBtn hemos usado este método para desplazar de forma animada el
mapa.

Para acabar, disponemos de otro método que permite desplazar el mapa un


determinado número de pixeles en cierta dirección, tal y como puede hacer un usuario con el
dedo sobre el mapa. Este método se llama scrollBy() y recibe como parámetros el número de
pixeles que queremos desplazarnos en horizontal y en vertical. En el botón moverBtn hemos
usado este método para desplazar el mapa automáticamente.

Finalmente, ten en cuenta que, para ejecutar la aplicación del ejemplo sobre el
emulador de Android, hay que modificar el fichero AndroidManifest.xml. Es necesario
especificar que hacemos uso de la API de Google Maps mediante la cláusula <uses-library> y,
en segundo lugar, hay que solicitar los permisos de acceso a Internet mediante la cláusula
<uses-permission>.

Veamos el aspecto que tiene este fichero:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="es.mentor.unidad8.eje2.mapas"

android:versionCode="1"
416
Android Avanzado

android:versionName="1.0" >

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

<application

android:icon="@drawable/ic_launcher"

android:label="@string/app_name" >

<uses-library android:required="true"

android:name="com.google.android.maps">

</uses-library>

<activity

android:label="@string/app_name"

android:name=".MapasActivity" >

<intent-filter >

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

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


</manifest>

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

Si ejecutas la aplicación en el emulador de Android, verás que tiene el siguiente


aspecto:

417
8.4 DESARROLLO DE APLICACIONES SENSIBLES A LA ORIENTACIÓN
DEL DISPOSITIVO

Si has usado alguna vez un teléfono con Android, verás que, al cambiar la orientación
del mismo de vertical a horizontal y viceversa, normalmente se modifica el aspecto de la
aplicación que estás usando distribuyéndose las Vistas de la interfaz de usuario de forma
acorde.

Aunque a priori este cambio de orientación del dispositivo parece sencillo, a veces los
desarrolladores de aplicaciones Android deben desarrollar complejos códigos para controlarlo.
Este apartado describe cómo implementar esta funcionalidad.

Por ejemplo, si tenemos abierta la aplicación de Contactos de Android y cambiamos la


orientación del teléfono de vertical a horizontal, la aplicación modifica el aspecto de la interfaz
del usuario proporcionalmente:

418
Android Avanzado

Hay dos formas de controlar el cambio de orientación del dispositivo Android:

• Automática: dejamos a Android que haga todo la tarea y definimos el fichero


de diseño xml que debe aplicar para cada tipo de orientación vertical (portrait) u
horizontal (landscape).

• Manual: controlamos con sentencias Java qué diseño debe cargar en cada
momento.

ATENCIÓN

Para cambiar la orientación del emulador de Android podemos usar las teclas
[BLOQUE_NUM_7], [Ctrl+F11], [BLOQUE_NUM_9], [Ctrl+F12].

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

En el Ejemplo 3 de esta Unidad vamos a mostrar cómo funcionan las dos formas de
controlar el cambio de orientación del dispositivo Android.

Nota sobre Android 2.3.3

Hasta ahora en el curso hemos usado la versión 2.3.3 de Android en el emulador de


dispositivos. Cuando se ha escrito este texto, esta versión tiene un Bug al cambiar la
orientación del emulador de horizontal a vertical (no informa al emulador de la nueva
orientación y mantiene la horizontal).

419
Por lo tanto, hay que probar el Ejemplo 3 de esta Unidad en otra versión de Android.
Tal y como hemos hecho para la versión 2.3.3 en la Instalación del curso, hay que descargar
las librerías de Android 2.2 y crear el dispositivo virtual correspondiente:

También puedes usar la versión de Android del curso teniendo en cuenta que puedes
cambiar al modo horizontal, pero no volver de nuevo al vertical.

8.4.1 Cambio de orientación automática

Se trata de una forma muy fácil de personalizar la interfaz de usuario en función de la


orientación de la pantalla del dispositivo. Consiste en crear una carpeta de diseño separada
(/res/layout) que contenga los archivos XML que determinan la interfaz de usuario en cada tipo
de orientación.

Para definir el modo horizontal (landscape), hay que crear la carpeta res/layout-land.
Esta nueva carpeta contiene también el archivo main.xml:

También se puede aplicar el nombre de extensión -land a la carpeta drawable donde


están las imágenes de la aplicación. Por ejemplo, la carpeta res/drawable-land contiene
imágenes que se han diseñado teniendo en cuenta el modo horizontal, mientras que los
albergados en la carpeta res/drawable están diseñados para el modo vertical:

420
Android Avanzado

El archivo main.xml incluido en la carpeta /res/layout define la interfaz de usuario para


el modo vertical del dispositivo, mientras que el archivo main.xml de la carpeta /res/layout-
land define la interfaz de usuario en el modo horizontal.

A continuación, se muestra el contenido del archivo main.xml de la carpeta


/res/layout:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/relativeLayout1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_weight="0.85"

android:gravity="center_horizontal" >

<TextView

android:id="@+id/textView1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentLeft="true"

android:layout_alignParentTop="true"

android:layout_marginBottom="10dp"

android:layout_marginTop="32dp"

android:text="Esta aplicación muestra cómo controlar el cambio de


orientación del dispositivo Android.

\n\nPara cambiar la orientación del emulador de Android puede


usar las teclas [BLOQUE_NUM_7],

[Ctrl+F11], [BLOQUE_NUM_9], [Ctrl+F12] de tu ordenador"

android:textAppearance="?android:attr/textAppearanceMedium" />

421
<Button

android:id="@+id/boton1"

android:layout_width="150px"

android:layout_height="60px"

android:layout_alignParentLeft="true"

android:layout_below="@+id/textView1"

android:layout_marginLeft="100dp"

android:layout_marginTop="93dp"

android:text="Botón" />

<EditText

android:id="@+id/editText"

android:layout_width="197dp"

android:layout_height="wrap_content"

android:layout_alignParentLeft="true"

android:layout_below="@+id/boton1"

android:layout_marginLeft="50dp"

android:layout_marginTop="150dp" />

</RelativeLayout>

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

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/relativeLayout1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_weight="0.85"

android:gravity="center_horizontal" >

<TextView

android:id="@+id/textView1"

422
Android Avanzado

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentLeft="true"

android:layout_alignParentTop="true"

android:layout_marginBottom="10dp"

android:layout_marginTop="10dp"

android:text="Esta aplicación muestra cómo controlar el cambio de


orientación del dispositivo Android.

\n\nPara cambiar la orientación del emulador de Android puede


usar las teclas [BLOQUE_NUM_7],

[Ctrl+F11], [BLOQUE_NUM_9], [Ctrl+F12] de tu ordenador"

android:textAppearance="?android:attr/textAppearanceMedium" />

<Button

android:id="@+id/boton1"

android:layout_width="150px"

android:layout_height="60px"

android:layout_alignParentLeft="true"

android:layout_below="@+id/textView1"

android:layout_marginLeft="100dp"

android:layout_marginTop="30dp"

android:text="Botón" />

<EditText

android:id="@+id/editText"

android:layout_width="197dp"

android:layout_height="wrap_content"

android:layout_alignParentLeft="true"

android:layout_below="@+id/boton1"

android:layout_marginLeft="50dp"

android:layout_marginTop="70dp" />

</RelativeLayout>

423
Si no creas este archivo para el modo horizontal y ejecutas la aplicación, verás que al
cambiar al modo horizontal desaparece el componente TextView:

Sin embargo, si creamos el archivo de diseño horizontal, cuando cambiemos la


orientación del dispositivo, Android cambiará automáticamente el diseño de la pantalla:

8.4.2 Mantener la información del estado durante el cambio de orientación

Si en el ejemplo anterior escribes algo en el TextView y, a continuación, cambias la


orientación del dispositivo virtual, verás que el texto escrito en este componente se mantiene
sin añadir nuevo código Java.

En el apartado "Guardar y recuperar el estado de una Actividad" de la Unidad 3 hemos


estudiado que, cuando cambia la orientación de la pantalla (vertical/horizontal), Android reinicia
la Actividad usando el método OnDestroy() e inmediatamente llama de nuevo a onCreate().
Este comportamiento de reinicio está diseñado para que la aplicación se adapte a la nueva
configuración de forma automática, y así cambiar la posición de los componentes.

424
Android Avanzado

La mejor manera de manejar un cambio de configuración de este tipo para preservar el


estado de la aplicación es usar los métodos onSaveInstanceState() y onCreate().

Lo primero que hay que tener en cuenta es que es imprescindible establecer el atributo
android:id de todas las Vistas de la actividad. Este atributo es indispensable para que Android
guarde automáticamente el contenido de las Vistas cuando cambia la orientación de la pantalla
y se destruye la Actividad.

Por ejemplo, si un usuario ha introducido un texto en una Vista de tipo EditText y


cambia la orientación del dispositivo, si este EditText tiene asignado un valor al atributo
android:id, Android mantendrá el texto existente y lo restaurará de forma automática cuando la
actividad se vuelva a recrear. Si, por el contrario, la Vista de tipo EditText no tiene definido el
atributo android:id, el sistema no podrá conservar el texto y cuando se recree la actividad, el
texto se perderá.

Android invoca el método onSaveInstanceState() cuando una Actividad está a punto


de ser destruida o va a pasar a un segundo plano. Por ejemplo, cuando se cambia la
orientación de la pantalla, se invoca este método para que se pueda guardar el estado actual
de la actividad y poder restaurarlo más tarde.

Hay otro procedimiento que permite sustituir el evento onSaveInstanceState() para


guardar información extra necesaria en la Actividad y restaurarla cuando se recree. Por
ejemplo, el siguiente código muestra cómo guardar la orientación actual del dispositivo sin usar
el evento onSaveInstanceState():

* Se llama a este evento cuando Android inicia un cambio de orientación.

* ¡CUIDADO! Para que el cambio se haga de forma AUTOMÁTICA debemos delegarle

* esta funcionalidad. Esto se consigue quitando del archivo

* AndroidManifest.xml el atributo android:configChanges="orientation..."

* Si controlamos de forma MANUAL el cambio de orientación, debes comentar

* este método.

*/
@Override

public void onSaveInstanceState(Bundle outState)

// Obtenemos la orientación actual del dispositivo

String texto="";

// Conectamos con el servicio de ventanas de Android y obtenemos los datos


de la pantalla principal

Display display = ((WindowManager)


getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
425
int orientation = display.getRotation();

if ((orientation==Surface.ROTATION_90) ||
(orientation==Surface.ROTATION_270))

texto="vertical";

else texto="horizontal";

// Guardamos una información del estado

outState.putString("dato", texto);

super.onSaveInstanceState(outState);

Cuando la actividad se vuelve a recrear, Android invoca primero el método OnCreate(),


seguido por el método onRestoreInstanceState(). Este último método permite recuperar el
estado de ejecución guardado previamente:

* Se llama a este evento cuando Android inicia un cambio de orientación.

* ¡CUIDADO! Para que el cambio se haga de forma AUTOMÁTICA debemos delegarle

* esta funcionalidad. Esto se consigue quitando del archivo

* AndroidManifest.xml el atributo android:configChanges="orientation..."

* Si controlamos de forma MANUAL el cambio de orientación debes comentar

* este método.

*/
@Override

public void onRestoreInstanceState(Bundle savedInstanceState)

super.onRestoreInstanceState(savedInstanceState);

// Recuperamos la información del EditText

if (savedInstanceState.containsKey("dato"))

Toast.makeText(this, "Orientación anterior: " +


savedInstanceState.getString("dato"), Toast.LENGTH_SHORT).show();

Hemos visto que el método onSaveInstanceState() es útil para guardar la información


del estado de ejecución de una Actividad, aunque tiene la limitación de que sólo se puede

426
Android Avanzado

guardar información usando el objeto de tipo Bundle. No permite guardar estructuras de datos
más complejas, como objetos.

Para estos casos, podemos usar el método onRetainNonConfigurationInstance().


Este método se activa cuando una actividad está a punto de ser destruida debido a un cambio
de configuración, como un cambio de orientación de la pantalla. Este método permite guardar
una estructura de datos devolviendo un objeto como resultado de su ejecución. Fíjate en el
siguiente ejemplo:

@Override

public Object onRetainNonConfigurationInstance()

// Devolvemos un objeto donde hemos guardado un estado de ejecución

return(objeto);

Fíjate que el método anterior devuelve el tipo objeto (Object), lo que permite
prácticamente devolver cualquier tipo de dato.

Para extraer los datos guardados se puede usar dentro del método onCreate() el
método getLastNonConfigurationInstance(). Por ejemplo, así:

@Override

public void onCreate(Bundle savedInstanceState)

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

// Recuperamos el objeto original

Objeto objeto = (Objeto) getLastNonConfigurationInstance();

8.4.3 Cambio de orientación Manual

Hay casos en los que es necesario controlar el proceso de creación-destrucción de una


aplicación cuando se cambia la orientación del dispositivo y no queremos que Android lo haga
de manera automática.

En este caso, hay que especificar el atributo android:configChanges del elemento


<activity> en el archivo AndroidManifest.xml:

427
<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="es.mentor.unidad8.eje3.orientacion"

android:versionCode="1"

android:versionName="1.0" >

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

<application

android:icon="@drawable/ic_launcher"

android:label="@string/app_name" >

<!-- ¡CUIDADO! Para que el cambio lo haga Android debemos permitirle que
gestione esta funcionalidad. Esto se consigue
quitando el atributo android:configChanges="orientation..." -->

<activity

android:label="@string/app_name"

android:name=".OrientacionActivity"

android:configChanges="orientation|keyboardHidden" >

<intent-filter >

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

</manifest>

El atributo anterior indica que la Actividad gestiona los cambios de orientación


ocultando el teclado cuando este cambio ocurre. Además, cuando este giro del dispositivo
ocurre, Android invoca el método onConfigurationChanged(), en el que se puede volver a
dibujar la interfaz de usuario de la Actividad:

/* Se llama a este evento cuando Android cuando cambia la orientación

* del dispositivo. ¡CUIDADO! Para que este evento se invoque debemos

428
Android Avanzado

* gestionar de forma MANUAL la funcionalidad de cambio de orientación.

* Esto se consigue añadiendo en el archivo AndroidManifest.xml el atributo

* android:configChanges="orientation..."

* Si controlamos de forma MANUAL el cambio de orientación, ya no son

* necesarios los métodos onSaveInstanceState() y onRestoreInstanceState()

* y debemos comentarlos.

*/
@Override

public void onConfigurationChanged(Configuration newConfig) {

super.onConfigurationChanged(newConfig);

// Si controlamos el cambio, también hay que guardar los contenidos de


los componentes visuales

String texto = et.getText().toString();

if (newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE) {

Toast.makeText(this, "Cambio a horizontal",


Toast.LENGTH_SHORT).show();

setContentView(R.layout.main);

} else {

Toast.makeText(this, "Cambio a vertical",


Toast.LENGTH_SHORT).show();

setContentView(R.layout.main);

//Obtenemos una referencia a las Vistas de la Actividad

et = (EditText)findViewById(R.id.editText);

// Escribimos el texto que tenía el EditText antes del cambio de


orientación

et.setText(texto);

8.4.4 Cambiar la orientación de la pantalla con sentencias Java

429
En ocasiones, es necesario asegurarse de que una aplicación se muestra siempre en
una orientación concreta. Por ejemplo, muchos juegos sólo se visualizan bien en modo
horizontal. En este caso, mediante sentencias Java, se puede cambiar la orientación de la
pantalla con el método setRequestOrientation() de la clase de Activity:

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

//Obtenemos una referencia al EditText de la Actividad

et = (EditText)findViewById(R.id.editText);

Además de utilizar el método setRequestOrientation() para cambiar la orientación de


la pantalla, también se puede utilizar el atributo android:screenOrientation dentro del
elemento <activity> en el archivo AndroidManifest.xml. Fíjate en el siguiente ejemplo:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="es.mentor.unidad8.eje3.orientacion"

android:versionCode="1"

android:versionName="1.0" >

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

<application

android:icon="@drawable/ic_launcher"

android:label="@string/app_name" >

<activity

android:label="@string/app_name"

android:name=".OrientacionActivity"

android:screenOrientation="landscape" >

<intent-filter >

430
Android Avanzado

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

</manifest>

Desde Eclipse puedes abrir el proyecto Ejemplo 3 (Orientación) de la Unidad 8. Estudia el


código fuente y ejecútalo para mostrar en el emulador una aplicación en la que mostramos
cómo manejar la orientación de la pantalla de un dispositivo Android.

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

Atención: el emulador de Android no funciona muy bien a la hora de simular el cambio de


orientación del dispositivo. Dependiendo de la versión de Android, algunos cambios de
orientación no se pueden hacer o, a veces, un giro emulado del dispositivo destruye la
Actividad dos veces antes de cambiar la orientación del terminal.

Sin embargo, la teoría y funciones aquí expuestas sí son válidas para un dispositivo real que
funcionará correctamente según lo esperado.

8.5 DESPLEGAR APLICACIONES ANDROID EN DISPOSITIVOS


VIRTUALES (AVD) O REALES
Para poder desplegar aplicaciones compiladas (tienen la extensión .apk), primero
debemos conectar un dispositivo real por USB o arrancar un dispositivo virtual desde Eclipse.

Esto es muy útil si queremos ver en funcionamiento los modelos de las actividades
obligatorias de este curso, ya que únicamente se entregan compiladas.

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

Desde la ventana de dispositivos virtuales seleccionamos el dispositivo que


deseamos arrancar y pulsamos el botón "Start":

431
A continuación, arrancará el dispositivo virtual.

Si queremos instalar la aplicación en un dispositivo real de Android, no es necesario


iniciar ningún dispositivo virtual.

Nota: en el caso de algunos dispositivos reales, dependiendo de la marca de dispositivo


Android, puede ser necesario instalar los drivers para que el sistema operativo lo reconozca
correctamente.

Además, el dispositivo real debe estar configurado para admitir la instalación de aplicaciones
sin firmar por el Android Market. Si accedes en Ajustes->Aplicaciones debes marcar la
siguiente opción:

432
Android Avanzado

Una vez disponemos de un dispositivo (real o virtual) de Android ejecutándose o


conectado por USB al PC, abrimos una consola de Windows (o del sistema operativo
correspondiente) y utilizamos la utilidad adb.exe (Android Debug Bridge) situada en la carpeta
platform-tools del SDK de Android.

En primer lugar, consultamos todos los identificadores de los dispositivos en ejecución


mediante el comando "adb devices". Este comando debe devolver todas las instancias con los
dispositivos abiertos:

Los dispositivos que aparezcan con la etiqueta "emulator-xxx" son dispositivos


virtuales y los que muestren otra etiqueta son dispositivos reales (teléfonos, tablets, etcétera).

Además, los dispositivos que aparezcan con la etiqueta "offline" están conectados,
pero no están disponibles al ADB (Android Debug Bridge).

Para este ejemplo, hemos seleccionado el dispositivo “emulator-5556“ que corresponde


al dispositivo virtual con Android 2.3.3 para instalar una aplicación. Puedes ver el "id" del
dispositivo en la ventana del emulador:

433
Tras obtener este identificador del emulador, vamos a instalar la aplicación mediante el
comando “adb -s identificador-del-emulador install nombre-fichero-apk“. Fíjate en el
siguiente ejemplo:

Una vez instala la aplicación, tenemos que ejecutarla en el dispositivo buscando su


icono en la pantalla de aplicaciones:

434
Android Avanzado

Hacemos clic en el icono de la aplicación para ver su resultado:

8.6 CÓMO PUBLICAR APLICACIONES EN EL ANDROID MARKET

El Android Market (en español Mercado de Android) es una tienda de software en


línea desarrollada por Google para los dispositivos Android. Es una aplicación que está
preinstalada en la mayoría de los dispositivos Android y que permite a los usuarios buscar y
descargar aplicaciones publicadas por terceros desarrolladores. Los usuarios también pueden
buscar y obtener información sobre aplicaciones a través de una página Web.

Las aplicaciones en el Android Market pueden ser gratuitas o de pago. En este


apartado vamos a tratar cómo publicar una aplicación gratuita.

En este apartado vamos a explicar los pasos para publicar una aplicación en el Android
Market.

8.6.1 Alta de cuenta de desarrollador en el Android Market

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


Para ello, necesitamos disponer de una cuenta de Google (GMail).

Con el navegador de Internet accederemos a la dirección:


435
http://market.android.com/publish

En esta página introducimos el usuario y la contraseña de Google:

La primera vez que accedemos a la página se muestra un asistente para dar de alta
una nueva cuenta de desarrollador en el Android Market. Introducimos los datos que se
solicitan (nombre del desarrollador, correo electrónico, URL del sitio Web y número de
teléfono). Después, pulsamos el enlace "Seguir":

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

436
Android Avanzado

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

A continuación, aparece el detalle de la factura con el artículo "Android - Developer


Registration Free for xxx". En esta página introducimos los datos de nuestra tarjeta de crédito
para realizar el pago, así como la dirección postal de facturación donde llegará la
correspondiente factura por correo ordinario:

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

Después, leemos la licencia de desarrollador para el Android Market. Si estamos de


acuerdo, hacemos clic en el enlace "Acepto las condiciones y deseo asociar la tarjeta de
crédito y la cuenta que he registrado anteriormente al Acuerdo de distribución para
desarrolladores de Android Market". Pulsamos "Acepto. Continuar":

438
Android Avanzado

El asistente indicará que el registro ha concluido, con el mensaje "Se ha aprobado tu


registro en Android Market. Ahora puedes subir y publicar aplicaciones de software en Android
Market". A partir de este momento ya podremos usar nuestra cuenta para publicar aplicaciones:

8.6.2 Recomendaciones sobre aplicaciones para Android Market

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


debemos prestar especial atención a una serie de características.

8.6.2.1 Recomendaciones sobre aplicaciones para Android Market

Antes de empezar a desarrollar aplicaciones Android que vamos a publicar en el


Market, hay que saber que cuando un usuario realiza una búsqueda de una aplicación en el
Market usando su dispositivo Android, sólo le aparecerán las aplicaciones que cumplan los
filtros (de permisos y de características del dispositivo) y el nivel de API (API Level) indicados
en el archivo AndroidManifest.xml.

El "API Level" es la versión de Android compatible con la aplicación. Por ejemplo,


durante el curso hemos usado la versión 2.3.3 de Android que corresponde con el "API Level"
10. Si publicamos una aplicación desarrollada con esta versión de Android, únicamente será
visible y sólo podrá instalarse en dispositivos con una versión igual o superior a la 2.3.3 de
Android.

439
Los filtros de permisos permiten a una aplicación solicitar acceso a recursos de
Android. Ya hemos estudiado que si, por ejemplo, una aplicación requiere acceder a la cámara
de fotos, debemos indicarlo en el archivo AndroidManifest.xml:

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

Al Indicar estos permisos, esta aplicación no aparecerá en las búsquedas realizadas


desde dispositivos Android que no dispongan de cámara de fotos. Es decir, si solicitamos
acceder a un recurso (cámara, wifi, bluetooth, etcétera) que el dispositivo no tiene, la aplicación
no será visible en el Market.

Además, existen otros filtros con las características del dispositivo en el archivo
AndroidManifest.xml que hacen que la aplicación aparezca o no en el Market para un
dispositivo determinado:

• <supports-screens>: establece el tipo de pantalla (resolución mínima) que


necesita la aplicación para funcionar.

• <uses-feature>: especifica el uso de características del dispositivo, por


ejemplo:

o Para utilizar Bluetooth:


<uses-feature android:name="android.hardware.bluetooth" />

o Para usar la cámara:


<uses-feature android:name="android.hardware.camera" />

• <uses-library>: indica las librerías específicas que requiere la aplicación.

Es importante tener en cuenta que cuanto mayores sean los requisitos de hardware (cámara,
bluetooth, GPS, brújula, sensor de movimiento, etcétera), la aplicación será visible e instalable
en un menor número de dispositivos Android.

8.6.2.2 Buenas prácticas para el desarrollo de aplicaciones Android

A continuación, mostramos algunas recomendaciones a la hora de desarrollar


aplicaciones Android útiles, profesionales y fiables:

• Siempre hay que tener en cuenta que estamos desarrollando aplicaciones para
dispositivos con pantalla muy pequeña, si son teléfonos, lo que no ocurre en
los tablets, y teclado limitado, por lo que las aplicaciones deberían mostrar
pocos campos de texto y opciones reducidas.

• Antes de desarrollar una aplicación Android, es recomendable buscar en el


Market si ya existe una aplicación similar. Si queremos que nuestra aplicación
sea útil para los usuarios, debe ser interesante, original y sencilla incorporando
funciones que no tengan otras.

440
Android Avanzado

• Hay que procurar, en la medida de lo posible, desarrollar aplicaciones que se


puedan instalar en el mayor número posible de dispositivos para que tenga
más difusión. Por lo tanto, debemos realizar aplicaciones con la versión de
Android mínima y los requisitos de hardware básicos.

• Las aplicaciones deben ser rápidas. Si es necesario realizar algún proceso que
pueda tardar unos segundos, es recomendable avisar al usuario o, incluso,
usar hilos de ejecución, servicios, etcétera. El usuario de un dispositivo móvil
espera siempre rapidez de respuesta.

8.6.3 Generar fichero APK con certificado para Android Market

Cuando compilamos un proyecto Android al hacer "Run" en Eclipse, el fichero .apk


(paquete de instalación de la aplicación Android) generado dentro del directorio /bin no es
válido para subirlo directamente al Android Market. Si intentamos subir este fichero
directamente aparecerá este mensaje:

Market does not accept apks signed with the debug certificate. Create a new certificate
that is valid for at least 50 years. Market requires that the certificate used to sign the apk be
valid until at least October 22, 2033. Create a new certificate. Market requires the
minSdkVersion to be set to a positive 32-bit integer in AndroidManifest.xml.

Uno de los requisitos para poder publicar de aplicaciones en Android Market es


que el paquete de instalación APK debe estar firmado con un certificado válido de al
menos 25 años. A continuación, explicamos cómo hacerlo.

En primer lugar, una vez desarrollada y probada la aplicación Android con Eclipse,
hacemos clic con el botón derecho del ratón sobre la carpeta del proyecto y seleccionamos la
opción "Export" del menú emergente:

441
Abrimos la carpeta "Android" y seleccionamos "Export Android Application";
después, pulsamos el botón "Next":

En la ventana siguiente, en el campo "Project", podemos seleccionar otro proyecto si


nos hemos equivocado. Pulsamos de nuevo el botón "Next":

442
Android Avanzado

A continuación, si no disponemos de una clave, seleccionamos la opción "Create new


keystore". Introducimos un directorio y nombre para el almacén de claves, por ejemplo
C:\cursos_Mentor\Android\claves.android. Introducimos la contraseña para el almacén de
claves:

Si ya disponemos de un almacén de claves, seleccionamos "Use existing keystore" y


seleccionamos el certificado escribiendo la clave correspondiente.

Pulsamos el botón "Next" para seguir.

A continuación, escribimos los datos administrativos de la clave que vamos a crear


para certificar nuestras aplicaciones:

443
• Alias: identificador de la clave.
• Password: contraseña de la clave, debemos guardarla o recordarla pues la
necesitaremos cada vez que vayamos a publicar una nueva aplicación o
actualizar una ya existente en el Android Market.
• Confirm: reescribimos la contraseña anterior.
• Validity (years): validez del certificado, al menos 25 años.
• First and Last Name: nombre del desarrollador o de la empresa.
• Organization Unit: departamento.
• Organization: nombre de la empresa.
• City or Locality: ciudad.
• State or Province: provincia.
• Country Code: código postal de la ciudad.

Tras introducir los datos pulsamos el botón "Next":

A continuación, indicamos la carpeta y el nombre del paquete APK compilado que se


firma con el certificado anterior y que será el fichero que finalmente subiremos al Android
Market. En es caso hemos seleccionado la carpeta C:\cursos_Mentor\Android\android-
market del curso:

444
Android Avanzado

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

8.6.4 Publicar una aplicación Android en el Android Market

Vamos a explicar cómo publicar una aplicación firmada con el certificado para que
aparezca en Android Market y los usuarios puedan descargarla e instalarla.

Accedemos a la web de Android Market con la cuenta de desarrollador que hemos


dado de alta anteriormente escribiendo en la barra de direcciones del navegador:

https://market.android.com/publish/Home

Pulsamos en el enlace "Subir aplicación":

445
Aparecerá una pagina donde podemos seleccionar el fichero APK pulsando en
"Examinar" para elegir el fichero APK de nuestra aplicación Android firmada con el certificado:

Pulsamos en el botón "Publicar" para subirla al Android Market.

Si el paquete APK está correcto y cumple con todos los requisitos (versión de Android,
certificado, compilación, etcétera), el asistente muestra el botón "Guardar" y los datos del APK
(nombre de la aplicación, nombre de la versión, código de la versión, permisos que necesita,
funciones que necesita, tamaño, nombre de la clase Java). Pulsamos el botón "Guardar" para
almacenar la aplicación:

446
Android Avanzado

Tras subirlo, pulsamos en el enlace "Activar" para introducir los datos necesarios para
publicar la aplicación en el Android Market. Desde esta página podemos activar o desactivar la
publicación de las aplicaciones subidas. Por ejemplo, si hemos detectado algún error y no
queremos que los usuarios se descarguen una aplicación hasta solucionar el problema,
podremos desactivarla:

Si pulsamos el botón "Activar", a continuación, aparece una ventana donde debemos


añadir todos los datos requeridos en la pestaña "Información de producto" para acabar de dar
de alta la nueva aplicación:

• Capturas de pantalla de la aplicación: al menos debemos subir dos capturas;


es recomendable que tengan buena calidad, para que el usuario se haga una
idea del aspecto que tiene la aplicación.

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

• Imagen promocional, imagen de funciones y vídeo promocional de


Youtube: son datos opcionales que sirven para incluir más información de la
aplicación.

• Si no deseamos que la aplicación se anuncie fuera de Android Market,


marcamos la Casilla: "No promocionar mi aplicación salvo en Android Market” y
en los sitios web o para móviles propiedad de Google. Asimismo, soy
consciente de que cualquier cambio relacionado con esta preferencia puede
tardar sesenta días en aplicarse".

• Podemos elegir varios idiomas para escribir la descripción de las funciones y


uso de la aplicación. El inglés es obligatorio. En este punto se solicitan los
campos:

 Título de la aplicación: nombre que aparece en las búsquedas, no


debe ser muy largo (inferior a 30 caracteres).

 Descripción: descripción detallada (hasta 4000 caracteres) de la


funcionalidad de la aplicación.

 Cambios recientes: si se trata de una actualización, podemos indicar


aquí las últimas mejoras implementadas.

 Si hemos incluido un vídeo promocional, podemos añadir un texto


promocional.

 Tipo de aplicación: seleccionamos en el desplegable el tipo que más


se ajuste a la funcionalidad de la aplicación.

 Categoría: seleccionamos en el desplegable la categoría que más se


ajuste a la aplicación.

• Protección contra copias: lo usual es que no esté seleccionada esta opción,


ya que, como indica Android Market, esta función quedará obsoleta en breve,
siendo sustituida por el servicio de licencias.

• Clasificación del contenido: marcamos si nuestra aplicación es para todos


los públicos o contiene algún tipo de contenido para mayores.

• Precios: aquí indicamos si la aplicación es gratuita o de pago.

• Precio predeterminado: si hemos elegido de pago, en este campo


introducimos el precio de la aplicación. Pulsando el botón "Autocompletar" hará
los ajustes para los diferentes países en los que queramos publicarla.

448
Android Avanzado

• También se indica el número aproximado de modelos de dispositivos


Android sobre los que se podrá instalar la aplicación en función de los
filtros indicados en el archivo de manifiesto.

• Información de contacto:

o Sitio web.

o Correo electrónico.

o Teléfono.

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

Una vez introducidos los datos, pulsamos en el botón "Guardar" de la parte superior
derecha. A continuación, se comprueba si los datos son completos y correctos y, si no hay
errores, se guardarán los datos asociados al archivo APK.

449
Después, pulsamos en el botón "Publicar" (a la izquierda del botón "Guardar") para
publicar definitivamente la aplicación en Android Market:

Tras finalizar la publicación, se mostrará en "Todos los elementos de Android Market"


la nueva aplicación con el estado "Publicada". En esta página podemos llevar a cabo un
seguimiento del número de instalaciones, posibles errores, comentarios de los usuarios,
popularidad, etcétera.

450
Android Avanzado

 La Depuración de programas (en inglés Debug) es el proceso de identificar y


corregir errores de programación en tiempo de ejecución.

 Un Punto de interrupción (Breakpoint en inglés) es una marca en el código fuente


que pausa la ejecución de un programa, para que el programador pueda evaluar los
valores asignados a las variables y detectar errores en tiempo de ejecución.

 El entorno de desarrollo Eclipse permite llevar a cabo de manera sencilla la


Depuración de programas.

 Es posible incluir mapas en las aplicaciones de Android haciendo uso de la API


Android de Google Maps.

 Para poder utilizar la API de Google Maps, es necesario disponer de una clave de
uso (API Key) que estará asociada al certificado con el que firmamos digitalmente
las aplicaciones.

 El Android Market (en español Mercado de Android) es una tienda de software en


línea desarrollada por Google para los dispositivos Android.

 Para poder publicar aplicaciones en el Android Market, es necesario darse de alta


y pagar una cuota.

 El paquete de instalación APK de una aplicación del Android Market debe estar
firmado con un certificado válido de al menos 25 años.

451

También podría gustarte