Curso android aula mentor

453

description

Actividades y ejemplos https://onedrive.live.com/redir?resid=F77DE8627486245F!277&authkey=!ALpnEf5YBO1QRFs&ithint=folder%2curl http://1drv.ms/1wiHZwf Android es un sistema operativo multidispositivo, inicialmente diseñado para teléfonos móviles. En la actualidad se puede encontrar también en múltiples dispositivos, como ordenadores, tabletas, GPS, televisores, discos duros multimedia, mini ordenadores, cámaras de fotos, 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 multipla­taforma. El objetivo de este curso avanzado es que el alumno perfeccione la programación en este sistema operativo tratando materias no estudiadas en el curso de iniciación. Así, podrá desarrollar aplicaciones más complejas utilizando contenidos multimedia, 3D, sensores del dispositivo, servicios, etcétera. Las cinco unidades que lo componen son: Multimedia y gráficos en Android, interfaz de usuario avanzada, sensores y dispositivos de Android, bibliotecas, APIs y servicios de Android, y utilidades avanzadas. Este curso está dirigido a aquellas personas interesadas en aprender este lenguaje de programación en el ámbito de Android en lenguaje JAVA para implementar aplicaciones para dispositivos personales tales como teléfonos, tabletas, etcétera. 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. Está basado en Linux, que es un núcleo de sistema operativo libre, gratuito y multiplataforma. El curso contiene ocho unidades: introducción al entorno de Android; diseño de la Interfaz de usuario en Android; más información sobre Android; trabajando con ficheros; intenciones y seguridad; bases de datos y ficheros XML; proveedores de contenidos, servicios y notificaciones; Android práctico.

Transcript of Curso android aula mentor

Page 1: Curso android aula mentor
Page 2: Curso android aula mentor
Page 3: Curso android aula mentor

INTRODUCCIÓN

ÍNDICE

1.1 .............................................................. 3 INTRODUCCIÓN1.1.1 .................................................................3 Qué es Android1.1.2 ..........................................3 Proyecto libre (Open Source)1.1.3 ..........................................................................3 Su historia1.1.4 ..............................................4 Inconvenientes de Android

1.2 ............................................................. 5 QUÉ ES ECLIPSE1.2.1 ........................................................5 El Consorcio Eclipse1.2.2 ................6 Instalación de Java Developmente Kit (JDK)1.2.3 ......................................................7 Instalación de Eclipse1.2.4 ...........................9 Instalación de las librerías de Android1.2.5 ............19 Añadir versiones y componentes de Android1.2.6 ..............25 Definición del dispositivo virtual de Android

Page 4: Curso android aula mentor

2

Page 5: Curso android aula mentor

Introducción

3

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.

Page 6: Curso android aula mentor

4

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.

Page 7: Curso android aula mentor

Introducción

5

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.

Page 8: Curso android aula mentor

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

Page 9: Curso android aula mentor

Introducción

7

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".

Page 10: Curso android aula mentor

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

Page 11: Curso android aula mentor

Introducción

9

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.

Page 12: Curso android aula mentor

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

Page 13: Curso android aula mentor

Introducción

11

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".

Page 14: Curso android aula mentor

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

Page 15: Curso android aula mentor

Introducción

13

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.

Page 16: Curso android aula mentor

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

Page 17: Curso android aula mentor

Introducción

15

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":

Page 18: Curso android aula mentor

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

Page 19: Curso android aula mentor

Introducción

17

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:

Page 20: Curso android aula mentor

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

Page 21: Curso android aula mentor

Introducción

19

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:

Page 22: Curso android aula mentor

Se abrirá la ventana siguiente:

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

siguiente ventana:

20

Page 23: Curso android aula mentor

Introducción

21

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:

Page 24: Curso android aula mentor

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

Page 25: Curso android aula mentor

Introducción

23

NOMBRE CARPETA

DESCRIPCIÓN

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

docs/ Documentación completa en formato HTML, incluyendo la Guía del desarrollador y la guía de la API. Para leer la documentación, puedes abrir el fichero offline.html en un navegador Web.

platform-tools/

Contiene las herramientas de desarrollo comunes del SDK que se actualizancon cada nueva versión de Android, tales como el ADB (Android Debug Bridge), así como otras herramientas que no se suelen utilizar directamente.

platforms/ Contiene las versiones de Android con las que se puede desarrollar aplicaciones en Eclipse. Cada versión se encuentra en un directorio independiente.

<platform>/

Directorio de la plataforma de la versión correspondiente, por ejemplo, "Android-10". Todos los directorios de la versión de Android contienen un 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.

tools/ Contiene el conjunto de herramientas de desarrollo y creación de perfiles que 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 Readme.txt Archivo que explica cómo realizar la configuración inicial del SDK de Android.

SDK Manager.exe

Aplicación que inicia el SDK de Android y la herramienta AVD de gestión de paquetes. Sólo disponible en Windows.

Page 26: Curso android aula mentor

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

Page 27: Curso android aula mentor

Introducción

25

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:

Page 28: Curso android aula mentor

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

Page 29: Curso android aula mentor

Introducción

27

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”:

Page 30: Curso android aula mentor

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

Page 31: Curso android aula mentor

INTRODUCCIÓN AL ENTORNO

ANDROID

ÍNDICE

1.1 ....................  31 INTRODUCCIÓN AL ENTORNO DE ANDROID1.1.1 ...................................................................................31 Introducción1.1.2 ...........................................................31 Características de Android1.1.3 ...............................................................33 Arquitectura de Android1.1.4 ....................35 Creación de un proyecto por líneas de comando

1.2 ............... 37 CONCEPTOS DE LAS APLICACIONES ANDROID1.2.1 Características de las aplicaciones Android.......................... 37 1.2.2 Componentes de las aplicaciones......................................... 37

1.3 ........................... 40 CÓMO CREAR UN PROYECTO ANDROID1.3.1 Un vistazo general al IDE de Eclipse ..................................... 40

1.3.1.1 .................................................................................41 Editores1.3.1.2 ................................................................................…..41 Vistas1.3.1.3 ............43 Barras de Herramientas principal y secundarias1.3.1.4 .........................................................................43 Perspectivas

1.3.2 ...............................................46 Cómo crear un proyecto Android1.3.2.1 ........................................47 Creación de un nuevo proyecto1.3.2.2 .......................................................49 Nombre de la actividad1.3.2.3 49 Descripción de los ficheros por defecto del proyecto....1.3.2.4 ........................................53 Ejecución del proyecto Android1.3.2.5 .......................56 Cómo usar el emulador de Android (AVD)

1.4 63 CÓMO CREAR LA PRIMERA APLICACIÓN CON ANDROID1.4.1 Cambio en la Interfaz de usuario con Layout .........................66

1.5 .............................. 70 DISEÑO DE LA INTERFAZ DE USUARIO1.5.1 ..............70 Cómo diseñar la interfaz de usuario mediante Vistas1.5.2 .....................................................71 Vistas disponibles de Android

Page 32: Curso android aula mentor

2

Page 33: Curso android aula mentor

Introducción al entorno Android

31

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 dispositivo pequeños

El sistema operativo es compatible con pantallas VGA (y mayores), gráficos 2D y gráficos 3D presentes en muchos teléfonos tradicionales.

Almacenamiento Dispone de la base de datos ligera SQLite donde se almacenan los datos de las aplicaciones.

Conectividad

Android soporta las siguientes tecnologías de conectividad: GSM/EDGE, IDEN, CDMA, EV-DO, UMTS, Bluetooth, Wi-Fi, LTE y WiMAX. Algunas son muy populares en los teléfonos actuales y otras se están desarrollando.

Mensajería Se pueden usar tanto SMS como MMS.

Navegador web El navegador web incluido en Android está basado en el motor del navegador de código abierto WebKit. Este navegador es muy eficiente y permite cargar las páginas Web rápidamente.

Soporte de Java

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, 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.

Page 34: Curso android aula mentor

32

Soporte multimedia

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

Soporte para streaming

(distribución en Internet)

Android soporta los siguientes formatos de streaming: RTP/RTSP, descarga progresiva de HTML (tag <video> de HTML5). Adobe Flash Streaming (RTMP) es soportado mediante la instalación de Adobe Flash Player, pero sólo para algunos terminales.

Soporte para hardware adicional

Android puede manejar cámaras de fotos, de vídeo, pantallas táctiles, GPS, acelerómetros, giroscopios, magnetómetros, sensores de proximidad y de presión, termómetro, aceleración 2D y 3D.

Entorno de desarrollo

El entorno de desarrollo es Eclipse 3.7 y el plugin de Herramientas de Desarrollo de Android (ADT) que incluye un emulador de dispositivos, herramientas de depuración y análisis de rendimiento.

Market (Mercado de aplicaciones)

El Android Market es un catálogo de aplicaciones gratuitas y de pago que pueden ser descargadas e instaladas desde los propios dispositivos Android.

Multi-táctil Android tiene soporte nativo para pantallas multi-táctiles que permiten 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.

Multitarea Existe la multitarea real de aplicaciones, es decir, las aplicaciones que no se están ejecutando en primer plano reciben ciclos de reloj del procesador para actualizar su estado.

Características basadas en voz

Es posible dar órdenes de voz al terminal. Por ejemplo, la búsqueda en Google a través de la voz ya estaba disponible desde la primera versión.

Tethering (compartición de

conexión a Internet)

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

Page 35: Curso android aula mentor

Introducción al entorno Android

33

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.

Page 36: Curso android aula mentor

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

Page 37: Curso android aula mentor

Introducción al entorno Android

35

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\Bienvenido.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>

Page 38: Curso android aula mentor

36

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.

Page 39: Curso android aula mentor

Introducción al entorno Android

37

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.

Page 40: Curso android aula mentor

38

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.

Page 41: Curso android aula mentor

Introducción al entorno Android

39

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.

Page 42: Curso android aula mentor

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

Page 43: Curso android aula mentor

Introducción al entorno Android

41

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.

Page 44: Curso android aula mentor

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

Page 45: Curso android aula mentor

Introducción al entorno Android

43

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.

Page 46: Curso android aula mentor

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

Page 47: Curso android aula mentor

Introducción al entorno Android

45

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”:

Page 48: Curso android aula mentor

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

Page 49: Curso android aula mentor

Introducción al entorno Android

47

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:

Page 50: Curso android aula mentor

  

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

Page 51: Curso android aula mentor

Introducción al entorno Android

49

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:

Page 52: Curso android aula mentor

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

Page 53: Curso android aula mentor

Introducción al entorno Android

51

/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.

Page 54: Curso android aula mentor

52

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:

Page 55: Curso android aula mentor

Introducción al entorno Android

53

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]

Page 56: Curso android aula mentor

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

Page 57: Curso android aula mentor

Introducción al entorno Android

55

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:

Page 58: Curso android aula mentor

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

Page 59: Curso android aula mentor

Introducción al entorno Android

57

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:

Page 60: Curso android aula mentor

Si lo hacemos, veremos las aplicaciones instaladas. A esta pantalla se la denomina

Pantalla de lanzamiento (en inglés se denomina Launcher Screen):

58

Page 61: Curso android aula mentor

Introducción al entorno Android

59

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

Page 62: Curso android aula mentor

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

keyboard”:

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

60

Page 63: Curso android aula mentor

Introducción al entorno Android

61

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:

Page 64: Curso android aula mentor

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

Page 65: Curso android aula mentor

Introducción al entorno Android

63

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:

Page 66: Curso android aula mentor

64

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.

Page 67: Curso android aula mentor

Introducción al entorno Android

65

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:

Page 68: Curso android aula mentor

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

Page 69: Curso android aula mentor

Introducción al entorno Android

67

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:

Page 70: Curso android aula mentor

Atributo Descripción

xmlns:android

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 a continuación.

android:id

Asigna un identificador único para el elemento correspondiente. Esteidentificador sirve para poder acceder al componente desde el código fuente o desde las declaraciones de otros elementos en este archivo XML.

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

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

android:text

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

ejemplo se utiliza una cadena que se establece en el archivores/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

Page 71: Curso android aula mentor

Introducción al entorno Android

69

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>:

Page 72: Curso android aula mentor

70

<?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.

Page 73: Curso android aula mentor

Introducción al entorno Android

71

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.

Page 74: Curso android aula mentor

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

Page 75: Curso android aula mentor

Introducción al entorno Android

73

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.

Page 76: Curso android aula mentor

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

Page 77: Curso android aula mentor

Introducción al entorno Android

75

<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

Page 78: Curso android aula mentor

76

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”).

Page 79: Curso android aula mentor

Introducción al entorno Android

77

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".

Page 80: Curso android aula mentor

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

Page 81: Curso android aula mentor

Introducción al entorno Android

79

</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:

Page 82: Curso android aula mentor

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

Page 83: Curso android aula mentor

Introducción al entorno Android

81

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

Page 84: Curso android aula mentor

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

Page 85: Curso android aula mentor

Introducción al entorno Android

83

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.

Page 86: Curso android aula mentor

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

Page 87: Curso android aula mentor

Introducción al entorno Android

85

{

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"

Page 88: Curso android aula mentor

86

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);

Page 89: Curso android aula mentor

Introducción al entorno Android

87

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:

Page 90: Curso android aula mentor

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.

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.

88

Page 91: Curso android aula mentor

Introducción al entorno Android

89

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].

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).

Page 92: Curso android aula mentor

90

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.

Page 93: Curso android aula mentor

DISEÑO DEL INTERFAZ DE

USUARIO

ÍNDICE

2.1 .................................................. 93 ACTIVIDADES - ANDROID2.1.1 ..............................................................................93 Introducción2.1.2 ......................................................93 Creación de una actividad2.1.3 ..............................................94 Ciclo de vida de una actividad2.1.4 ....94 Cómo se implementa el ciclo de vida de una actividad

2.2 ...................................................... 100 EVENTOS Y LISTENERS2.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 ................................. 106 USO DEL EMULADOR DE ANDROID2.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 ........................................... 114 COMPONENTES AVANZADOS2.4.1 Qué son los Adaptadores de Android (adapters) ........... 114

2.5 ................................ 127 COMPONENTES PERSONALIZADOS2.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

Page 94: Curso android aula mentor

2

Page 95: Curso android aula mentor

Diseño del interfaz de usuario

93

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:

Page 96: Curso android aula mentor

94

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:

Page 97: Curso android aula mentor

Diseño del interfaz de usuario

95

 

// 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();

Page 98: Curso android aula mentor

96

  // 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.

Page 99: Curso android aula mentor

Diseño del interfaz de usuario

97

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.

Page 100: Curso android aula mentor

98

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

Método Descripción Kill Siguiente método

onCreate()

Se invoca cuando la Actividad se crea por primera vez. Aquí es donde se reserva la memoria necesaria, se crea la interfaz de usuario, se recupera el estado de la sesión anterior, etcétera.

No onStart()

onRestart() Se invoca cuando la Actividad está parada, justo antes de iniciarse de nuevo.

No onStart()

onStart()

Se invoca justo antes de que el usuario pueda ver la Actividad.

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

No onResume()

o onStop()

onResume() Se invoca justo antes de que el usuario comience a interactuar con la Actividad.

No onPause()

onPause()

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. Estas sentencias deben ser muy rápidas porque la nueva Actividad no se inicia hasta que finaliza este método.

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

Sí onResume()

o onStop()

Page 101: Curso android aula mentor

Diseño del interfaz de usuario

99

Método Descripción Kill Siguiente método

onStop()

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 primer plano.

A continuación, se puede invocar el método onRestart() si la Actividad vuelve al primer plano o onDestroy() si se destruye.

Sí onRestart()

o onDestroy()

onDestroy()

Se invoca antes de que se destruya una Actividad; se trata, pues, del último método.

El sistema operativo puede invocar este método 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().

Sí ninguno

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

Page 102: Curso android aula mentor

100

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.

Page 103: Curso android aula mentor

Diseño del interfaz de usuario

101

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 

Page 104: Curso android aula mentor

102

  } 

}); // 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;

}

Page 105: Curso android aula mentor

Diseño del interfaz de usuario

103

  // 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.

Page 106: Curso android aula mentor

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

Page 107: Curso android aula mentor

Diseño del interfaz de usuario

105

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.

Page 108: Curso android aula mentor

106

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

Page 109: Curso android aula mentor

Diseño del interfaz de usuario

107

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.

Page 110: Curso android aula mentor

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 INICIO Se vuelve a la pantalla

108

Page 111: Curso android aula mentor

Diseño del interfaz de usuario

109

principal de Android.

Menú (tecla izquierda) F2 o RePág

Muestra un menú desplegable con las opciones de la pantalla actual.

Volver ESC Vuelve a la pantalla anterior.

Llamar F3 Llamar por teléfono.

Colgar F4 Acabar una llamada.

Buscar F5 Buscar información.

Botón encender/apagar F7

Subir volumen BLOQUE_NUM_MAS (+),

Ctrl+5

Bajar volumen BLOQUE_NUM_MENOS (-)

Ctrl+F6

Cámara Ctrl+BLOQUE_NUM_5, Ctrl+F3 Inicia la cámara de fotos.

Cambio orientación

BLOQUE_NUM_7, Ctrl+F11

BLOQUE_NUM_9, Ctrl+F12

Cambia la orientación del móvil de horizontal (landscape) a vertical (portrait).

Conexión datos F8

Habilita y deshabilita la conexión de datos del móvil.

Pantalla completa Alt+Intro El emulador ocupa la pantalla completa del monitor del ordenador.

Bola navegación F6

Inicia el modo de navegación con bola (trackball)

Entra momentáneamente en modo bola navegación.

Suprimir Mientras tengamos pulsada la tecla Suprimir

Page 112: Curso android aula mentor

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

Page 113: Curso android aula mentor

Diseño del interfaz de usuario

111

 

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”:

Page 114: Curso android aula mentor

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

Page 115: Curso android aula mentor

Diseño del interfaz de usuario

113

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:

Page 116: Curso android aula mentor

114

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.

Page 117: Curso android aula mentor

Diseño del interfaz de usuario

115

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" 

Page 118: Curso android aula mentor

116

         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("");

}

});

Page 119: Curso android aula mentor

Diseño del interfaz de usuario

117

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

Page 120: Curso android aula mentor

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

Page 121: Curso android aula mentor

Diseño del interfaz de usuario

119

 

// 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

Page 122: Curso android aula mentor

120

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

Page 123: Curso android aula mentor

Diseño del interfaz de usuario

121

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;

}

 

Page 124: Curso android aula mentor

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

Page 125: Curso android aula mentor

Diseño del interfaz de usuario

123

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:

Page 126: Curso android aula mentor

124

// 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 

}

Page 127: Curso android aula mentor

Diseño del interfaz de usuario

125

@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"/>

Page 128: Curso android aula mentor

126

<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); 

 

 

 

Page 129: Curso android aula mentor

Diseño del interfaz de usuario

127

   

   // 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

Page 130: Curso android aula mentor

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

Page 131: Curso android aula mentor

Diseño del interfaz de usuario

129

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

Page 132: Curso android aula mentor

130

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.

Page 133: Curso android aula mentor

Diseño del interfaz de usuario

131

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>

Page 134: Curso android aula mentor

132

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();

}

 

 

Page 135: Curso android aula mentor

Diseño del interfaz de usuario

133

  // 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) {

Page 136: Curso android aula mentor

134

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:

Page 137: Curso android aula mentor

Diseño del interfaz de usuario

135

// 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.logincompuesto"

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"

Page 138: Curso android aula mentor

136

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:

Page 139: Curso android aula mentor

Diseño del interfaz de usuario

137

Ten en cuenta que en el código de esta aplicación únicamente se da por bueno el

usuario y contraseña admin/admin.

Page 140: Curso android aula mentor

138

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.

Page 141: Curso android aula mentor

Diseño del interfaz de usuario

139

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

Page 142: Curso android aula mentor

140

Page 143: Curso android aula mentor

Unidad de Aprendizaje 9

EAN

FECTOS DE TRANSICIÓN Y

IMACIÓN MÁS INFORMACIÓN SOBRE

ANDROID

ÍNDICE

3.1 .......................................................... 143 INTRODUCCIÓN3.1.1 ...................................................................143 Introducción3.1.2 ............143 Gestión del botón “Hacia atrás” de Android3.1.3 ...145 Definición de una tarea en los proyectos Android

3.2 .................................................................... 147

GUARDAR Y RECUPERAR EL ESTADO DE UNA ACTIVIDAD

3.3 ........................ 154 PROCESOS EN HILOS EN ANDROID3.3.1 ........................................................................154 Procesos3.3.2 ....................................155 Ciclo de vida de los procesos3.3.3 ....................................157 Hilos de ejecución en Android

3.4 ..................................... 158 HILOS EN SEGUNDO PLANO3.4.1

..............................................................................159 Utilización de tareas asíncronas con la clase

AsyncTask

3.5 ................................................. 162 MENÚS DE ANDROID3.5.1 ....................162 Ejemplo de Menú Principal y Submenú3.5.2 ......................................167 Ejemplo de Menú Contextual3.5.3 ................................172 Ventanas de diálogo en Android

3.5.3.1 ....................174 Ventanas de diálogo con mensaje3.5.3.2 ....................174 Ventanas de diálogo con botones3.5.3.3 ..................176 Ventanas de diálogo con selección3.5.3.4 ..................177 Ventanas de diálogo personalizada

Page 144: Curso android aula mentor
Page 145: Curso android aula mentor

Más información sobre Android

143

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):

Page 146: Curso android aula mentor

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

Page 147: Curso android aula mentor

Más información sobre Android

145

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.

Page 148: Curso android aula mentor

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

Page 149: Curso android aula mentor

Más información sobre Android

147

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.

Page 150: Curso android aula mentor

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

Page 151: Curso android aula mentor

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

149

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">

Page 152: Curso android aula mentor

<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

Page 153: Curso android aula mentor

Más información sobre Android

151

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.

Page 154: Curso android aula mentor

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

Page 155: Curso android aula mentor

Más información sobre Android

153

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.

Page 156: Curso android aula mentor

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

Page 157: Curso android aula mentor

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.

155

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.

Page 158: Curso android aula mentor

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

Page 159: Curso android aula mentor

Más información sobre Android

3.3.3

157

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

Page 160: Curso android aula mentor

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

Page 161: Curso android aula mentor

Más información sobre Android

159

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:

Page 162: Curso android aula mentor

// 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

Page 163: Curso android aula mentor

Más información sobre Android

161

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:

Page 164: Curso android aula mentor

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

Page 165: Curso android aula mentor

Más información sobre Android

163

<?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ú:

Page 166: Curso android aula mentor

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

Page 167: Curso android aula mentor

Más información sobre Android

165

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

Page 168: Curso android aula mentor

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

Page 169: Curso android aula mentor

Más información sobre Android

167

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():

Page 170: Curso android aula mentor

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

Page 171: Curso android aula mentor

Más información sobre Android

169

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;

Page 172: Curso android aula mentor

// 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

Page 173: Curso android aula mentor

Más información sobre Android

171

// 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

Page 174: Curso android aula mentor

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

Page 175: Curso android aula mentor

Más información sobre Android

1.

173

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:

Page 176: Curso android aula mentor

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

Page 177: Curso android aula mentor

Más información sobre Android

175

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();

}

Page 178: Curso android aula mentor

});

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

Page 179: Curso android aula mentor

Más información sobre Android

177

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;

Page 180: Curso android aula mentor

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

Page 181: Curso android aula mentor

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

Page 182: Curso android aula mentor

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

Page 183: Curso android aula mentor

181

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.

Page 184: Curso android aula mentor
Page 185: Curso android aula mentor

Unidad de Aprendizaje 9

EFECTOS DE TRANSICIÓN Y

ANIMACIÓN TRABAJANDO CON

FICHEROS

Í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 ................. 197 PREFERENCIAS DE UNA APLICACIÓN ANDROID4.2.1 Preferencia de las aplicaciones Android.......................... 197 4.2.2 Pantallas de opciones ...................................................... 200

4.3 ..................... 207 RECURSOS DE LAS APLICACIONES ANDROID4.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 ..................................... 215 ACCESO A INTERNET CON ANDROID4.4.1 Ejemplo de conexión a Internet........................................ 216 4.4.2 Conexión a través de proxy ............................................. 220

4.5 ........................ 221 QUÉ SON JSON (JavaScript Object Notation?4.5.1 Cómo usar JSON en nuestras aplicaciones a Android.... 222 4.5.2 Cómo escribir ficheros en formato JSON........................ 225

Page 186: Curso android aula mentor
Page 187: Curso android aula mentor

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

Page 188: Curso android aula mentor

186

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.).

Page 189: Curso android aula mentor

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

Page 190: Curso android aula mentor

188

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

{

Page 191: Curso android aula mentor

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

Page 192: Curso android aula mentor

190

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

Page 193: Curso android aula mentor

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

Page 194: Curso android aula mentor

192

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) {

Page 195: Curso android aula mentor

  // 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

Page 196: Curso android aula mentor

194

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>

Page 197: Curso android aula mentor

</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

Page 198: Curso android aula mentor

196

}

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:

Page 199: Curso android aula mentor

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

Page 200: Curso android aula mentor

198

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, “[email protected]”). 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", "[email protected]");

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.

Page 201: Curso android aula mentor

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", "[email protected]");

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">[email protected]</string>

</map>

199

Page 202: Curso android aula mentor

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

Page 203: Curso android aula mentor

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

Page 204: Curso android aula mentor

202

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" />

Page 205: Curso android aula mentor

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

Page 206: Curso android aula mentor

204

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:

Page 207: Curso android aula mentor

<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

Page 208: Curso android aula mentor

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

Page 209: Curso android aula mentor

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

Page 210: Curso android aula mentor

208

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":

Page 211: Curso android aula mentor

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

Page 212: Curso android aula mentor

210

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));

Page 213: Curso android aula mentor

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

Page 214: Curso android aula mentor

212

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:

Page 215: Curso android aula mentor

Valor Descripción

zero Cuando el idioma requiere un tratamiento especial del número 0 (como el árabe).

one Cuando el idioma requiere un tratamiento especial del número 1 (como el español, el inglés, etcétera).

two Cuando el idioma requiere un tratamiento especial del número 2 (como el galés).

few Cuando el idioma requiere un tratamiento especial de pequeñas cantidades (como los números 2, 3 y 4 en checo).

many Cuando el idioma requiere un tratamiento especial de los números grandes (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

Page 216: Curso android aula mentor

214

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.

Page 217: Curso android aula mentor

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

Page 218: Curso android aula mentor

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

Page 219: Curso android aula mentor

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

Page 220: Curso android aula mentor

218

// 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) {

Page 221: Curso android aula mentor

219

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);

Page 222: Curso android aula mentor

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

Internet. Además, para poder hacerlo, hay que declarar en el fichero Manifest

de la ap

de la conexión a

licació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

ador de Android para que acceda a Internet a través de

ese Pro

Conexiones inalámbricas->Redes móviles ->APN->Telkila-> Proxy/Puerto

Proxy, es interesante configurar el emul

xy.

Para configurarlo, haz clic en el icono "Ajustes" y establece las siguientes opciones:

220

Page 223: Curso android aula mentor

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

Page 224: Curso android aula mentor

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

Page 225: Curso android aula mentor

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

Page 226: Curso android aula mentor

224

// 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) {

Page 227: Curso android aula mentor

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

Page 228: Curso android aula mentor

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

Page 229: Curso android aula mentor

227

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.

Page 230: Curso android aula mentor
Page 231: Curso android aula mentor

INTENTS EN ANDROID

ÍNDICE

5.1 .......................................................... 231 INTENTS EN ANDROID5.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 .....................................235 Ejecutar subactividades5.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 ................................................................... 243 USO DE INTENTS5.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 ........................... 256 PERMISOS Y SEGURIDAD EN ANDROID5.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 .............................................................................. 264 Tab.Layout5.4.1  Pantallas con pestañas con Tab Layout ..................................264 

Page 232: Curso android aula mentor

2

Page 233: Curso android aula mentor

Intents

231

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).

Page 234: Curso android aula mentor

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

Page 235: Curso android aula mentor

Intents

233

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.

Page 236: Curso android aula mentor

234

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);

Page 237: Curso android aula mentor

Intents

235

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);

Page 238: Curso android aula mentor

236

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();

}

});

Page 239: Curso android aula mentor

Intents

237

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

Page 240: Curso android aula mentor

238

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:

Page 241: Curso android aula mentor

Intents

239

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

Page 242: Curso android aula mentor

240

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

Page 243: Curso android aula mentor

Intents

241

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.

Page 244: Curso android aula mentor

242

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();

Page 245: Curso android aula mentor

Intents

243

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:

Page 246: Curso android aula mentor

244

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);

Page 247: Curso android aula mentor

Intents

245

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");

Page 248: Curso android aula mentor

246

}

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

Page 249: Curso android aula mentor

Intents

247

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() {

Page 250: Curso android aula mentor

248

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>

Page 251: Curso android aula mentor

Intents

249

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 -->

Page 252: Curso android aula mentor

250

<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:

Page 253: Curso android aula mentor

Intents

251

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.

Page 254: Curso android aula mentor

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

Page 255: Curso android aula mentor

Intents

253

<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

Page 256: Curso android aula mentor

254

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:

Page 257: Curso android aula mentor

Intents

255

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; }

Page 258: Curso android aula mentor

256

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.

Page 259: Curso android aula mentor

Intents

257

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.

Page 260: Curso android aula mentor

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

Page 261: Curso android aula mentor

Intents

259

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:

Page 262: Curso android aula mentor

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

Page 263: Curso android aula mentor

Intents

261

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.

Page 264: Curso android aula mentor

262

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:

Page 265: Curso android aula mentor

Intents

263

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.

Page 266: Curso android aula mentor

264

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.

Page 267: Curso android aula mentor

Intents

265

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">

Page 268: Curso android aula mentor

266

<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>

Page 269: Curso android aula mentor

Intents

267

</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

Page 270: Curso android aula mentor

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

Page 271: Curso android aula mentor

Intents

269

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“:

Page 272: Curso android aula mentor

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

Page 273: Curso android aula mentor

Intents

271

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.

Page 274: Curso android aula mentor
Page 275: Curso android aula mentor

BASES DE DATOS Y XML

ÍNDICE

6.1 .................................................................. 275 BASES DE DATOS6.1.1 ...........................................................................275 Introducción6.1.2 ..............................................275 Teoría sobre Bases de Datos6.1.3 ..........................................279 Ventajas de las bases de datos6.1.4 ................................................281 Bases de datos relacionales6.1.5 ...................................................283 Diseño de bases de datos

6.2 .................................................................. 284 SQLite en Android6.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 .....................................................293 Método insert()6.2.4.2 ..................294 Método update() y método delete()

6.2.5 Uso de parámetros en los métodos SQLite................... 294

6.3 ............................................... 295 Consultas SQLite en Android6.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 .............................................. 311 GESTIÓN DE FICHEROS XML6.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

Page 276: Curso android aula mentor

2

Page 277: Curso android aula mentor

Introducción al entorno Android

275

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.

Page 278: Curso android aula mentor

276

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

Filas

Título Autor Editorial

El invierno en Lisboa Antonio Muñoz Molina

Seix Barral

¿Tener o ser? Erich Fromm Fondo de Cultura Económica

Crónica de una muerte anunciada

Gabriel GarcíaMárquez

Bruguera

El lobo estepario Hermann Hesse

Anaya Editores

La vida está en otra parte

Milan Kundera Seix Barral

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

Page 279: Curso android aula mentor

Introducción al entorno Android

277

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

No bmbre Sueldo Fecha_nac O servacion Foto

1

2

3

4

Re gistros 5

6

7

8

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.

Page 280: Curso android aula mentor

278

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:

Page 281: Curso android aula mentor

Introducción al entorno Android

279

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

Page 282: Curso android aula mentor

280

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

Page 283: Curso android aula mentor

Introducción al entorno Android

281

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 (Registros)

3

José Rodríguez

C/ Río Sil, 11 50 V Dependiente

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

Page 284: Curso android aula mentor

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

Page 285: Curso android aula mentor

Introducción al entorno Android

283

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.

Page 286: Curso android aula mentor

284

• 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

Page 287: Curso android aula mentor

Introducción al entorno Android

285

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.

Page 288: Curso android aula mentor

286

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

Page 289: Curso android aula mentor

Introducción al entorno Android

287

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.

Page 290: Curso android aula mentor

288

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.

Page 291: Curso android aula mentor

Introducción al entorno Android

289

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);

}

Page 292: Curso android aula mentor

//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

Page 293: Curso android aula mentor

Introducción al entorno Android

291

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.

Page 294: Curso android aula mentor

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

Page 295: Curso android aula mentor

Introducción al entorno Android

293

//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);

Page 296: Curso android aula mentor

294

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.

Page 297: Curso android aula mentor

Introducción al entorno Android

295

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");

Page 298: Curso android aula mentor

296

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.

Page 299: Curso android aula mentor

Introducción al entorno Android

297

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()) {

Page 300: Curso android aula mentor

298

//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

ontenidos, por lo que suele utilizarse conjuntamente con los Content Providers. c

 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:

Page 301: Curso android aula mentor

Introducción al entorno Android

299

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:

Page 302: Curso android aula mentor

300

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.

Page 303: Curso android aula mentor

Introducción al entorno Android

301

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

Page 304: Curso android aula mentor

302

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();

Page 305: Curso android aula mentor

Introducción al entorno Android

303

}

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

os ficheros XML de Layout que componen el diseño de la

interfaz

enu/menu_listado.xml: define el diseño del menú principal de la

ain.xml: define el diseño de la pantalla de la actividad principal

ar_nota.xml: define el diseño de la actividad secundaria

ntos del ListView de

cómo están

ficheros strings.xml y categorias.xml en la carpeta

res/val

A continuación, indicamos l

del usuario:

res/m

aplicación.

res/layout/m

NotasActivity.

res/layout/edit

GestionarNota, que sirve para editar y dar de alta notas.

res/layout/fila_notas.xml: define el diseño de los eleme

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

implementados los distintos diseños.

Además, se definen los dos

ues con los literales que usa la aplicación.

Page 306: Curso android aula mentor

304

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);

Page 307: Curso android aula mentor

Introducción al entorno Android

305

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);

Page 308: Curso android aula mentor

306

}

// 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 };

Page 309: Curso android aula mentor

Introducción al entorno Android

307

// 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;

Page 310: Curso android aula mentor

308

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() {

Page 311: Curso android aula mentor

Introducción al entorno Android

309

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

Page 312: Curso android aula mentor

310

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:

Page 313: Curso android aula mentor

Introducción al entorno Android

311

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):

Page 314: Curso android aula mentor

312

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>

Page 315: Curso android aula mentor

Introducción al entorno Android

313

<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:

Page 316: Curso android aula mentor

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

Page 317: Curso android aula mentor

Introducción al entorno Android

315

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.

Page 318: Curso android aula mentor

316

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(){

Page 319: Curso android aula mentor

Introducción al entorno Android

317

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) {

Page 320: Curso android aula mentor

318

// 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);

}

}

Page 321: Curso android aula mentor

Introducción al entorno Android

319

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

Page 322: Curso android aula mentor

320

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.

Page 323: Curso android aula mentor

Introducción al entorno Android

321

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);

Page 324: Curso android aula mentor

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

Page 325: Curso android aula mentor

Introducción al entorno Android

323

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);

Page 326: Curso android aula mentor

324

}

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);

}

});

Page 327: Curso android aula mentor

Introducción al entorno Android

325

// 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);

}

Page 328: Curso android aula mentor

326

// 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.

Page 329: Curso android aula mentor

Introducción al entorno Android

327

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:

Page 330: Curso android aula mentor

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

Page 331: Curso android aula mentor

Introducción al entorno Android

329

}

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();

Page 332: Curso android aula mentor

330

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)

{

Page 333: Curso android aula mentor

Introducción al entorno Android

331

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:

Page 334: Curso android aula mentor

332

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

Page 335: Curso android aula mentor

Introducción al entorno Android

333

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)){

Page 336: Curso android aula mentor

334

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.

Page 337: Curso android aula mentor

Introducción al entorno Android

335

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.

Page 338: Curso android aula mentor

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

Page 339: Curso android aula mentor

Introducción al entorno Android

337

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).

Page 340: Curso android aula mentor

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

Page 341: Curso android aula mentor

Í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

CONTENT PROVIDERS, SERVICIOS Y

NOTIFICACIONES

Page 342: Curso android aula mentor

2

Page 343: Curso android aula mentor

Content Providers, servicios y notificaciones

341

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.

Page 344: Curso android aula mentor

342

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.

Page 345: Curso android aula mentor

Content Providers, servicios y notificaciones

343

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 )";

Page 346: Curso android aula mentor

344

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");

Page 347: Curso android aula mentor

Content Providers, servicios y notificaciones

345

//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:

Page 348: Curso android aula mentor

346

• 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";

Page 349: Curso android aula mentor

Content Providers, servicios y notificaciones

347

}

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); }

Page 350: Curso android aula mentor

348

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();

Page 351: Curso android aula mentor

Content Providers, servicios y notificaciones

349

//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;

}

Page 352: Curso android aula mentor

350

@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; }

Page 353: Curso android aula mentor

Content Providers, servicios y notificaciones

351

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

Page 354: Curso android aula mentor

352

<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.

Page 355: Curso android aula mentor

Content Providers, servicios y notificaciones

353

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;

Page 356: Curso android aula mentor

354

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.");

Page 357: Curso android aula mentor

Content Providers, servicios y notificaciones

355

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:

Page 358: Curso android aula mentor

356

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:

Page 359: Curso android aula mentor

Content Providers, servicios y notificaciones

357

// 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)

Page 360: Curso android aula mentor

358

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:

Page 361: Curso android aula mentor

Content Providers, servicios y notificaciones

359

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.

Page 362: Curso android aula mentor

360

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:

Page 363: Curso android aula mentor

Content Providers, servicios y notificaciones

361

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).

Page 364: Curso android aula mentor

362

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 {

Page 365: Curso android aula mentor

Content Providers, servicios y notificaciones

363

@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:

Page 366: Curso android aula mentor

364

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);

Page 367: Curso android aula mentor

Content Providers, servicios y notificaciones

365

}

}

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;

}

Page 368: Curso android aula mentor

366

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:

Page 369: Curso android aula mentor

Content Providers, servicios y notificaciones

367

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",

Page 370: Curso android aula mentor

368

"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;

}

}

Page 371: Curso android aula mentor

Content Providers, servicios y notificaciones

369

}, 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;

}

}

Page 372: Curso android aula mentor

370

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;

}

};

Page 373: Curso android aula mentor

Content Providers, servicios y notificaciones

371

@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:

Page 374: Curso android aula mentor

372

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:

Page 375: Curso android aula mentor

Content Providers, servicios y notificaciones

373

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.

Page 376: Curso android aula mentor

374

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();

Page 377: Curso android aula mentor

Content Providers, servicios y notificaciones

375

} });

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();

Page 378: Curso android aula mentor

376

} });

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"

Page 379: Curso android aula mentor

Content Providers, servicios y notificaciones

377

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();

Page 380: Curso android aula mentor

378

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.

Page 381: Curso android aula mentor

Content Providers, servicios y notificaciones

379

Por ejemplo, cuando hay una llamada perdida en nuestro teléfono, se muestra en un

lado el siguiente icono en la barra de estado:

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

Arrastrar

Page 382: Curso android aula mentor

380

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);

Page 383: Curso android aula mentor

Content Providers, servicios y notificaciones

381

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:

Page 384: Curso android aula mentor

382

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:

Page 385: Curso android aula mentor

Content Providers, servicios y notificaciones

383

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:

Page 386: Curso android aula mentor

384

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

Al desplazar el dedo se cambia de pantalla.

Page 387: Curso android aula mentor

Content Providers, servicios y notificaciones

385

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:

Page 388: Curso android aula mentor

386

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);

Page 389: Curso android aula mentor

Content Providers, servicios y notificaciones

387

// 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

Page 390: Curso android aula mentor

388

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é

Page 391: Curso android aula mentor

Content Providers, servicios y notificaciones

389

* 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);

Page 392: Curso android aula mentor

390

// 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

Page 393: Curso android aula mentor

Content Providers, servicios y notificaciones

391

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

Page 394: Curso android aula mentor

392

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

Page 395: Curso android aula mentor

Content Providers, servicios y notificaciones

393

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.

Page 396: Curso android aula mentor

394

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.

Page 397: Curso android aula mentor

Í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

ANDROID AVANZADO

Page 398: Curso android aula mentor

2

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

Page 399: Curso android aula mentor

Android Avanzado

397

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.");

Page 400: Curso android aula mentor

398

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:

Page 401: Curso android aula mentor

Android Avanzado

399

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:

Page 402: Curso android aula mentor

400

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:

Page 403: Curso android aula mentor

Android Avanzado

401

Comando Descripción

F5 La ejecución pasa a la siguiente sentencia del programa. Si la sentencia siguiente 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.

F6 La ejecución pasa a la siguiente sentencia del programa. Si la sentencia siguiente 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.

F7 La ejecución sigue todas las sentencias de todos los métodos o funciones que formen nuestro programa. Es decir, ejecuta en secuencia todas las órdenes que conforman el programa.

F8 El programa se ejecuta hasta que se encuentre otro punto de interrupción o hasta 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:

Page 404: Curso android aula mentor

402

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"

Page 405: Curso android aula mentor

Android Avanzado

403

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.

Page 406: Curso android aula mentor

404

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:

Page 407: Curso android aula mentor

Android Avanzado

405

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:

Page 408: Curso android aula mentor

406

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.

Page 409: Curso android aula mentor

Android Avanzado

407

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:

Page 410: Curso android aula mentor

408

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":

Page 411: Curso android aula mentor

Android Avanzado

409

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:

Page 412: Curso android aula mentor

410

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:

Page 413: Curso android aula mentor

Android Avanzado

411

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:

Page 414: Curso android aula mentor

412

<!-- 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);

Page 415: Curso android aula mentor

Android Avanzado

413

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

Page 416: Curso android aula mentor

414

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);

}

});

Page 417: Curso android aula mentor

Android Avanzado

415

}

// 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().

Page 418: Curso android aula mentor

416

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"

Page 419: Curso android aula mentor

Android Avanzado

417

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:

Page 420: Curso android aula mentor

418

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:

Page 421: Curso android aula mentor

Android Avanzado

419

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).

Page 422: Curso android aula mentor

420

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:

Page 423: Curso android aula mentor

Android Avanzado

421

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" />

Page 424: Curso android aula mentor

422

<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"

Page 425: Curso android aula mentor

Android Avanzado

423

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>

Page 426: Curso android aula mentor

424

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.

Page 427: Curso android aula mentor

Android Avanzado

425

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();

Page 428: Curso android aula mentor

426

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

Page 429: Curso android aula mentor

Android Avanzado

427

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:

Page 430: Curso android aula mentor

428

<?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

Page 431: Curso android aula mentor

Android Avanzado

429

* 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

Page 432: Curso android aula mentor

430

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 >

Page 433: Curso android aula mentor

Android Avanzado

431

<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":

Page 434: Curso android aula mentor

432

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:

Page 435: Curso android aula mentor

Android Avanzado

433

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:

Page 436: Curso android aula mentor

434

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:

Page 437: Curso android aula mentor

Android Avanzado

435

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:

Page 438: Curso android aula mentor

436

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

Page 439: Curso android aula mentor

Android Avanzado

437

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:

Page 440: Curso android aula mentor

438

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":

Page 441: Curso android aula mentor

Android Avanzado

439

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.

Page 442: Curso android aula mentor

440

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.

Page 443: Curso android aula mentor

Android Avanzado

441

• 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:

Page 444: Curso android aula mentor

442

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":

Page 445: Curso android aula mentor

Android Avanzado

443

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:

Page 446: Curso android aula mentor

444

• 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:

Page 447: Curso android aula mentor

Android Avanzado

445

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":

Page 448: Curso android aula mentor

446

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:

Page 449: Curso android aula mentor

Android Avanzado

447

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.

Page 450: Curso android aula mentor

448

• 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.

Page 451: Curso android aula mentor

Android Avanzado

449

• 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.

Page 452: Curso android aula mentor

450

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.

Page 453: Curso android aula mentor

Android Avanzado

451

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.