Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Curso Android Aula Mentor
Curso Android Aula Mentor
ÍNDICE
1.1 INTRODUCCIÓN .............................................................. 3
1.1.1 Qué es Android .................................................................3
1.1.2 Proyecto libre (Open Source) ..........................................3
1.1.3 Su historia ..........................................................................3
1.1.4 Inconvenientes de Android..............................................4
1.1 INTRODUCCIÓN
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.
1.1.3 Su historia
Android era un sistema operativo para móviles prácticamente desconocido hasta que
en 2005 lo compró Google.
3
El despegue del sistema operativo fue lento porque se lanzó antes el sistema operativo
que el primer terminal móvil, aunque rápidamente se ha colocado como el sistema operativo
de móviles más vendido del mundo.
Versiones disponibles:
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.
Disponer del código fuente del sistema operativo no significa que se pueda tener
siempre la última versión de Android en un determinado móvil, ya que el código para soportar
el hardware de cada fabricante normalmente no es público; así que faltaría un trozo básico del
firmware (controladores) para poder hacerlo funcionar en dicho terminal.
Hay que tener en cuenta que las nuevas versiones de Android suelen requerir más
recursos, por lo que, en los modelos más antiguos, no se puede instalar la última versión por
insuficiente memoria RAM, velocidad de procesador, etcétera.
4
Introducción
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.
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.
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.
5
1.2.2 Instalación de Java Developmente Kit (JDK)
Es muy importante tener en cuenta que, para poder ejecutar el entorno de desarrollo
Eclipse y las librerías de Android, es necesario tener instaladas en el ordenador las librerías de
desarrollo de Java. Aunque ya está disponible la versión 1.7, para poder compilar
aplicaciones Android, es necesario instalar la versión 6 de Java (también conocida como
Java 1.6).
http://java.sun.com/javase/downloads
6
Introducción
Nota: en el caso de Linux o Mac, es posible también instalar Java usando los
programas habituales del sistema operativo que permiten la actualización de paquetes.
Nota: si vas a instalar Eclipse y las librerías de Android en Linux, lee las notas que se
encuentran en “Preguntas y Respuestas” de esta Introducción en la mesa del curso.
http://www.eclipse.org/downloads/
7
Hay que tener en cuenta que debemos descargar la versión 32 bits o 64 bits en función
del sistema operativo de que dispongamos.
En el caso de Windows podemos ver el tipo de sistema operativo haciendo clic con el
botón derecho del ratón en el icono "Equipo" o Mi PC del Escritorio y haciendo clic de nuevo
en "Propiedades":
$ 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.
8
Introducción
3. Si Extensiones y kernel de 64 bits está configurada como Sí, estás utilizando un kernel
de 64 bits.
Una vez descomprimido el fichero, Eclipse está listo para ser utilizado; no es necesario
hacer ninguna operación adicional.
9
Para descargar el fichero necesario, accedemos a la página de descarga del SDK de
Android y nos bajamos la versión que corresponda en función del sistema operativo donde
vayamos a instalarlo. Recomendamos que hay que descargar el archivo .zip, ya que con éste
la instalación es más sencilla y rápida:
Si usas otro directorio, toma nota del mismo, ya que, más adelante, tendrás que usar
el nombre de este directorio para acabar de configurar el plugin de Android en Eclipse.
Ahora vamos a instalar las librerías necesarias en Eclipse. Estas librerías se denominan
Android Development Tools (ADT). Para ello, arrancamos Eclipse haciendo doble clic sobre
el acceso directo que hemos creado anteriormente. A continuación, Eclipse pedirá que
seleccionemos el "workspace", es decir, el directorio donde queremos guardar los proyectos.
10
Introducción
Si no podemos seleccionar "jre6" debemos usar el botón "Add" para añadir las
librerías del JRE 6. Por ejemplo, en Windows, se pueden encontrar en el directorio:
"C:\Program Files\Java\jre6".
11
Para finalizar, en esta ventana hay que seleccionar la versión de Java utilizada para
compilar los proyectos de Android. Para ello hacemos clic en “Java->Compiler” y elegimos
“1.6” en el campo “Compiler compliance settings”:
12
Introducción
Importante: Es necesario disponer de conexión a Internet para poder continuar con los
siguientes pasos y poder descargar las librerías necesarias.
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:
13
Hacemos clic en el botón "Next".
14
Introducción
15
Al arrancar de nuevo Eclipse ya dispondremos de las librerías necesarias para
empezar a trabajar con Android:
16
Introducción
17
Pulsamos el botón “Finish” para finalizar la configuración. A continuación, aparece el
siguiente mensaje indicando que no hemos instalado ninguna versión del sistema operativo
Android:
18
Introducción
El SDK utiliza una estructura modular que separa las distintas versiones de Android,
complementos, herramientas, ejemplos y la documentación en un único paquete que se puede
instalar por separado. Para desarrollar una aplicación en Android, es necesario descargar, al
menos, una versión. En este curso vamos a usar la versión 2.3, por ser la más extendida en
el momento de redacción de la documentación. No obstante, vamos a emplear sentencias
compatibles y recompilables en otras versiones.
Para añadir esta versión hay que hacer clic en la opción “Android SDK Manager” del
menú principal “Window” de Eclipse:
19
Se abrirá la ventana siguiente:
20
Introducción
Nota: la revisión de las versiones de Android puede ser superior cuando al alumno o
alumna instale el SDK.
Una vez hemos pulsado el botón “Install 6 packages”, aparece esta ventana y
seleccionamos la opción “Accept All” y, después, hacemos clic en “Install”:
21
Para acabar, reiniciamos el ADB (Android Debug Bridge):
Ahora vamos a ver la estructura que tiene el SDK de Android. Para ello, abrimos el
explorador en el directorio “C:\cursos_Mentor\Android\android-sdk-windows” o en el
directorio donde lo hayamos descomprimido. La siguiente tabla describe los subdiretorios que
contiene esta carpeta:
22
Introducción
NOMBRE
DESCRIPCIÓN
CARPETA
Contiene los paquetes “add-on” del SDK de Android que permiten desarrollar
add-ons/ aplicaciones usando librerías externas disponibles para algunos dispositivos
o terminales.
samples/ Contiene los ejemplos de código para esa versión específica de Android.
SDK
Archivo que explica cómo realizar la configuración inicial del SDK de Android.
Readme.txt
23
Finalmente, vamos a incluir el directorio donde hemos instalado las librerías de
Android en el PATH del sistema operativo. En concreto, vamos a incluir los directorios tools y
platform-tools.
C:\cursos_Mentor\Android\android-sdk-windows\tools
y
C:\cursos_Mentor\Android\android-sdk-windows\platform-tools
24
Introducción
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.
Para poder hacer pruebas de las aplicaciones Android que desarrollemos sin
necesidad de disponer de un teléfono Android, el SDK incluye la posibilidad de definir un
Dispositivo Virtual de Android (en inglés, AVD, Android Virtual Device). Este dispositivo
emula un terminal con Android instalado.
Para definir el AVD, hacemos clic en la opción “Android AVD Manager” del menú principal
“Window” de Eclipse:
25
Aparecerá la siguiente ventana:
26
Introducción
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
27
En esta Introducción puedes encontrar el vídeo “Cómo instalar Eclipse y el plugin
Android”, que muestra de manera visual los pasos seguidos en las explicaciones anteriores
28
INTRODUCCIÓN AL ENTORNO
ANDROID
ÍNDICE
1.1 INTRODUCCIÓN AL ENTORNO DE ANDROID .................... 31
1.1.1 Introducción ...................................................................................31
1.1.2 Características de Android...........................................................31
1.1.3 Arquitectura de Android ...............................................................33
1.1.4 Creación de un proyecto por líneas de comando ....................35
1.1.1 Introducción
Diseñado para
El sistema operativo es compatible con pantallas VGA (y mayores),
dispositivo
gráficos 2D y gráficos 3D presentes en muchos teléfonos tradicionales.
pequeños
31
Android soporta los siguientes formatos multimedia: WebM, H.263,
H.264 (en 3GP o MP4), MPEG-4 SP, AMR, AMR-WB (en un contenedor
Soporte multimedia
3GP), AAC, HE-AAC (en contenedores MP4 o 3GP), MP3, MIDI, Ogg
Vorbis, WAV, JPEG, PNG, GIF y BMP.
32
Introducción al entorno Android
Los componentes principales de la arquitectura del sistema operativo Android son los
siguientes:
Núcleo Linux: Android está basado en Linux para los servicios base del sistema,
como seguridad, gestión de memoria, procesos y controladores.
33
DIAGRAMA DE LA ARQUITECTURA ANDROID
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.
o Reutilización de código
34
Introducción al entorno Android
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
Created directory
C:\cursos_Mentor\Android\proyectos\bienvenido\src\es\mentor\eje1\unidad1\bienvenido
Added file
C:\cursos_Mentor\Android\proyectos\bienvenido\src\es\mentor\eje1\unidad1\bienvenido\Bienv
enido.java
C:\cursos_Mentor\Android\proyectos>
35
Ahora no vamos a examinar en detalle los ficheros que hemos creado y que conjuntamente
forman el proyecto Android. En el siguiente apartado de esta Unidad los detallaremos con un
ejemplo real en Eclipse.
/src: en este directorio es donde se almacenan los archivos de código fuente Java
(con extensión .java).
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.
36
Introducción al entorno 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.
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.
Los Componentes de las aplicaciones son los elementos esenciales de una aplicación
Android. Cada componente es un punto diferente de entrada por el que el sistema operativo
puede interaccionar con la aplicación. No todos los componentes son verdaderos puntos de
entrada, sino que algunos dependen unos de otros, aunque cada uno exista como una entidad
separada en Android y desempeñe un papel específico que define el comportamiento general
de la aplicación.
37
Existen los siguientes tipos de componentes de la aplicación:
Actividades (Activities): una actividad representa una pantalla única con una interfaz
de usuario. Por ejemplo, una aplicación de correo electrónico puede tener una
actividad que muestra una lista de correo electrónico nuevo, otra actividad que
compone un correo y otra actividad que lee los mensajes. Aunque las actividades
trabajan conjuntamente para dar la sensación de una única aplicación, cada una de
ellas es independiente de las otras. Por lo tanto, otra aplicación externa diferente
podría iniciar cualquiera de estas actividades (si la aplicación de correo electrónico lo
permite). Por ejemplo, una aplicación que gestiona los contactos podría iniciar la
actividad que compone nuevos mensajes de correo indicando como destinatario del
mensaje al contacto seleccionado en la primera aplicación.
Una actividad se implementa a partir de la clase Java Activity. Más adelante veremos
cómo se usa.
Puedes pensar en una actividad de Android como si fuera una ventana en una
aplicación de escritorio o una página HTML en una aplicación Web. Android está
diseñado para cargar muchas actividades pequeñas, por lo que se permite al usuario
abrir nuevas actividades y pulsar el botón “Atrás” para ir a un estado anterior, al igual
que se hace en un navegador web.
38
Introducción al entorno Android
Los proveedores de contenidos se utilizan también para escribir y leer datos que son
privados de la aplicación y no se comparten. Por ejemplo, una aplicación de Notas
puede utilizar un proveedor de contenidos para guardar las notas.
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.
39
Por ejemplo, si una aplicación inicia la actividad de la aplicación de la cámara que hace fotos,
la actividad se ejecuta en el proceso que pertenece a la aplicación de la cámara, no en el
proceso de la aplicación original que ha hecho la llamada a la otra aplicación. Por lo tanto, a
diferencia de otros sistemas operativos, las aplicaciones de Android no tienen un punto de
entrada único (no hay función main()).
La primera vez que se ejecuta Eclipse se puede ver una pantalla muy similar a la que
se muestra a continuación.
40
Introducción al entorno Android
1.3.1.1 Editores
La ventana principal (la más grande) se llama “Editor”. El Editor es el espacio donde se
escribe el código fuente de los programas que estamos desarrollando.
Es posible tener varios ficheros de código fuente abiertos a la vez, apilados uno encima
de otro. En la parte superior de la ventana del Editor se muestran las pestañas que permiten
acceder a cada uno de los ficheros abiertos (o bien cerrarlos directamente).
Editor
1.3.1.2 Vistas
Además del Editor, existe un segundo tipo de ventanas “secundarias”, que se llaman
Vistas.
Las Vistas son ventanas auxiliares para mostrar información, introducir datos, etcétera.
Las Vistas se usan con múltiples propósitos, desde navegar por un árbol de directorios, hasta
mostrar el contenido de una consulta SQL.
41
Vistas
Si deseamos cambiar las Vistas, se puede usar la opción “Show View” en el menú de la
pestaña “Window”.
42
Introducción al entorno Android
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
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.
43
También existe un botón en la barra de herramientas principal para cambiar de
Perspectiva:
Si el alumno tiene dudas sobre el uso avanzado de Eclipse, en Internet existen muchos
tutoriales que indican cómo utilizarlo.
Además, es posible usar el menú "Help" o la tecla [F1] para solicitar ayuda.
Desgraciadamente, a día de hoy, esta ayuda sólo se encuentra en inglés.
Importante: para importar en Eclipse el código fuente de los ejemplos del curso hay que
usar la opción del menú principal: File -> Import.
44
Introducción al entorno Android
Después, hay que marcar Existing Proyects into Workspace en la ventana emergente y
pulsar en el botón “Next”:
45
Nota: en esta Unidad 1 puedes encontrar el vídeo “Cómo cargar los ejemplos en Eclipse”,
que muestra cómo se importan los ficheros con el código fuente de los proyectos que son los
ejemplos del curso.
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.
46
Introducción al entorno Android
47
Build Target: indica la versión del Android SDK que vamos a usar para compilar
la aplicación. Por ejemplo, si seleccionas Android 2.3, la aplicación se compilará
para funcionar en esta versión de Android y las siguientes. La versión
seleccionada aquí no tiene que coincidir con la versión del Emulador de Android
(AVD), ya que las aplicaciones de Android están diseñadas de manera que se
48
Introducción al entorno Android
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.
Importante: El nombre de la actividad no puede incluir tildes, la letra “ñ”, ni caracteres raros.
Para ver los ficheros del proyecto Android creados por Eclipse, en la barra lateral
Package Explorer, desplegamos las entradas haciendo clic en los ficheros marcados con
flechas rojas de los diferentes paquetes:
49
A continuación, vamos a explicar la estructura y contenido de los ficheros del proyecto.
Carpeta/src/
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:
Carpeta /gen/
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:
package es.mentor.unidad1.eje1.bienvenido;
Importante: Esta clase la crea automáticamente Android por lo que no debemos modificarla.
51
Carpeta /assets/
Alberga el resto de ficheros auxiliares necesarios para que aplicación funcione, como
los ficheros de configuración, de datos, etcétera.
Carpeta /bin/
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:
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.
52
Introducción al entorno 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).
o en la opción "Run" de menú “Run”. También disponemos del atajo del teclado
[Ctrl+F11]
53
Si no aparece ningún problema de compilación, entonces aparecerá la siguiente
ventana:
Eclipse inicia el emulador de Android AVD en el ordenador que estés utilizando, para
que puedas probar el proyecto que has desarrollado. Ten en cuenta que el dispositivo
virtual tarda un rato en cargar cada vez que lo inicias. Hay que tener un poco de paciencia
hasta que parezca la ventana de inicio.
54
Introducción al entorno Android
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.
55
Importante: En general, cada vez que modifiquemos el código fuente y deseemos probar de
nuevo nuestro proyecto no es necesario parar el emulador de aplicaciones y arrancarlo de
nuevo; simplemente hacemos clic de nuevo en el botón “Run” y Eclipse compilará, reinstalará
y ejecutará la aplicación modificada.
Una vez hayamos acabado de probar nuestro proyecto, es necesario parar el emulador de
Android.
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.
Si no conoces Android, lee las siguientes instrucciones para ver cómo se maneja.
Cambiaremos el idioma del sistema operativo.
56
Introducción al entorno Android
57
Si lo hacemos, veremos las aplicaciones instaladas. A esta pantalla se la denomina
Pantalla de lanzamiento (en inglés se denomina Launcher Screen):
58
Introducción al entorno Android
Para movernos en esta pantalla, podemos usar el ratón como si fuera el dedo de tu
mano. Es decir, para ver los iconos que están abajo hay que hacer clic en la pantalla y, sin
soltar el botón del ratón, arrastrar la ventana hacia arriba:
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:
59
Desplazando con el ratón hacia arriba esta ventana hacemos clic en “Language &
keyboard”:
60
Introducción al entorno Android
Para acabar, desplazamos de nuevo la pantalla hacia arriba hasta que veamos el idioma
en el que deseamos configurar Android:
61
Después, debemos desmarcar la opción “Japanese IME” de esta pantalla:
Si usamos el botón “Volver atrás”, veremos que el idioma del sistema operativo ha
cambiado en la Pantalla Inicial (Home Screen):
62
Introducción al entorno Android
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.
63
package es.mentor.unidad1.eje1.bienvenido;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
super.onCreate(savedInstanceState);
// de forma “programada”.
setContentView(tv);
// setContentView(R.layout.main);
65
Nota:
Al ejecutar varias veces una aplicación desde Eclipse puede ocurrir que aparezcan los
siguientes mensajes de error en la consola:
[2011‐11‐20 09:18:15 ‐ unidad1.eje1.bienvenido] Application already deployed.
No need to reinstall.
[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
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.
66
Introducción al entorno Android
<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>
Esta estructura XML hace que sea más fácil y rápido crear las interfaces de usuario.
Este modelo se basa en el modelo de desarrollo web, donde se separa la presentación
(interfaz de usuario) de la lógica de la aplicación (encargada de leer y escribir la información).
En el ejemplo de XML anterior sólo hay un elemento Vista: TextView, que tiene tres
atributos y un elemento de diseño Layout: LinearLayout, que tiene cuatro atributos. A
continuación, mostramos una descripción de los atributos:
67
Atributo Descripción
Define el largo que debe ocupar la Vista. En este caso, indicamos que
android:layout_width el TextView debe ocupar toda la pantalla con "fill_parent”.
El SDK de Android permite definir los ficheros de tipo XML de dos formas: a través de
un editor visual o directamente en el archivo XML. Se puede cambiar entre las dos formas
haciendo clic en la pestaña de la parte inferior de la ventana. Por ejemplo, en el Package
Explorer, seleccionamos res/layout/main.xml y hacemos clic en “Graphical Layout”:
68
Introducción al entorno Android
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.
En este fichero hay que declarar la Actividad para que Android tenga acceso a la misma.
Si abres el archive manifest, verás que existe el siguiente elemento <activity>:
69
<?xml version="1.0" encoding="utf‐8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<activity android:name=".BienvenidoActivity"
android:label="@string/app_name">
...
</manifest>
Además, si no has usado nunca el entorno de desarrollo Eclipse - Android, adquirirás soltura
utilizándolo.
Nota: Como en la literatura inglesa de Android se habla genéricamente de Vista (View) para
referirse a estos componentes visuales y en Internet siempre aparecen referencias a esta
palabra, vamos a usar esta nomenclatura a partir de ahora.
Es importante no confundir los widgets (componentes o Vistas) que usamos al desarrollar las
interfaces de usuario en Android con “Widget de la pantalla principal” (Screen Home), que son
pequeñas aplicaciones que el usuario del teléfono puede añadir a esta pantalla, tales como un
calendario dinámico, previsión meteorológica, etcétera.
70
Introducción al entorno Android
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.
71
Tipos de paneles (Layout)
É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.
fill_parent para que el componente hijo tenga la dimensión del layout que lo
contiene.
Ejemplo
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText android:id="@+id/TextoNombre"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</FrameLayout>
72
Introducción al entorno Android
Vertical y Horizontal
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>
73
Panel Tabla (TableLayout)
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:
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=”*”.
Ejemplo
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="1">
<TableRow>
<TextView android:text="Celda 1.1">
<TextView android:text="Celda 1.2">
</TableRow>
<TableRow>
74
Introducción al entorno Android
<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.
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.
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>
76
Introducción al entorno Android
Ejemplo
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="center"
android:src="@drawable/imagen" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dip"
android:layout_gravity="center_horizontal|bottom"
android:padding="12dip"
android:background="#AA000000"
android:textColor="#ffffffff"
android:text="Imagen" />
</FrameLayout>
En este ejemplo, mostramos una imagen que ocupa todo el área de la pantalla usando
el atributo android:scaleType="center" y una etiqueta superpuesta en la parte inferior de la
pantalla con el atributo android:layout_gravity="center_horizontal|bottom".
77
Componentes Básicos
Botones
Los botones se usan para que el usuario interactúe con la aplicación Web:
android:id="@+id/boton" android:layout_height="wrap_content"
android:layout_width="135dp">
</Button>
<ToggleButton android:id="@+id/toggleButton"
android:text="ToggleButton" android:layout_width="88dp"
android:textOn="Encendido"
android:textOff="Apagado"
android:layout_height="match_parent">
78
Introducción al entorno Android
</ToggleButton>
<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:
btnBoton1.setOnClickListener(new View.OnClickListener() {
@Override
});
En el caso del botón de tipo ToggleButton suele ser más útil conocer el estado en el
que está el botón tras ser pulsado. Para esto, se usa el método isChecked(). En el siguiente
ejemplo se comprueba el estado del botón después de ser pulsado y se realizan diferentes
acciones según su resultado:
79
final ToggleButton btnBoton2 =
(ToggleButton)findViewById(R.id.toggleButton);
btnBoton2.setOnClickListener(new View.OnClickListener() {
@Override
if(btnBoton2.isChecked())
lblEtiqueta.setText("Botón Encendido");
else
lblEtiqueta.setText("Botón Apagado");
});
Etiqueta (TextView)
android:textSize="30dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_width="wrap_content"
80
Introducción al entorno Android
android:layout_height="wrap_content"
android:textSize="25dp"
android:typeface="serif" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold" />
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í:
lblEtiqueta.setText(texto);
Imagen (ImageView)
81
otras, como las destinadas a establecer el tamaño máximo que puede ocupar la imagen:
android:maxWidth y android:maxHeight. Fíjate en el código del siguiente ejemplo:
<ImageView android:id="@+id/ImgFoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon" />
img.setImageResource(R.drawable.icon);
<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:
82
Introducción al entorno Android
texto = txtTexto.getText().toString();
txtTexto.setText("Esto es un texto");
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:
txtTexto.setText(
BufferType.SPANNABLE);
83
Cuadro de Selección (CheckBox)
<LinearLayout android:id="@+id/linearLayout4"
android:orientation="vertical" android:layout_width="154dp"
android:layout_height="wrap_content">
</LinearLayout>
android:layout_height="wrap_content"
android:text="Confirmar Selección"
android:layout_gravity="center_vertical" />
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() {
boolean isChecked)
84
Introducción al entorno Android
if (isChecked) {
" marcado!");
else {
" desmarcado!");
};
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<RadioButton android:id="@+id/radio1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
85
android:text="Opción 1" />
<RadioButton android:id="@+id/radio2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
</RadioGroup>
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:
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:
rg.setOnCheckedChangeListener(
new RadioGroup.OnCheckedChangeListener() {
// el ID marcado checkedId
86
Introducción al entorno Android
});
87
Android es un sistema operativo, inicialmente diseñado para teléfonos móviles
con sistemas operativos iOS (Apple), Symbian (Nokia) y Blackberry OS.
Android usa Java como lenguaje base para el desarrollo de las aplicaciones; por lo
tanto, emplea Paquetes Java (Package en inglés). Estos paquetes son
contenedores de clases que permiten agrupar las distintas partes de un programa
cuya funcionalidad tienen elementos comunes.
El nombre de los paquetes debe ser único en relación con los paquetes
instalados en Android. Por esta razón, es importante utilizar el estándar de dominio
“com.dominio…” para nombrarlos.
Los ficheros que contengan el código fuente de las actividades del alumno han de
guardarse en una carpeta personal. Recomendamos usar el directorio
C:\cursos_Mentor\Android\proyectos para este fin.
88
Introducción al entorno Android
Para comprobar que una aplicación funciona, hay que hacer clic en la opción "Run"
de menú "Run" de Eclipse (Atajo del teclado [Ctrl+F11]); después, arrancará el
Emulador (AVD) de Android para ver el resultado de su ejecución.
Un Dispositivo Virtual de Android (en inglés, AVD, Android Virtual Device) emula
un terminal instalado con Android en el que podemos probar las aplicaciones
desarrolladas.
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.
89
Las Vistas visibles están contenidos en los paneles y permiten interaccionar al
usuario con la aplicación.
90
DISEÑO DEL INTERFAZ DE
USUARIO
ÍNDICE
2.1 ACTIVIDADES - ANDROID .................................................. 93
2.1.1 Introducción..............................................................................93
2.1.2 Creación de una actividad......................................................93
2.1.3 Ciclo de vida de una actividad ..............................................94
2.1.4 Cómo se implementa el ciclo de vida de una actividad ....94
2.1.1 Introducción
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.
Para crear una Actividad debemos usar la clase Activity de Android. En la subclase
creada es necesario implementar los métodos callback que el sistema puede invocar cuando
hay un cambio en su ciclo de vida: la actividad se está creando, se detiene, se reanuda o se
destruye. Los dos métodos callback más importantes son:
93
onCreate(): es obligatorio implementar este método ya que el sistema lo invoca
cuando crea su actividad. Dentro de su implementación debemos iniciar los
componentes esenciales de la actividad, como dibujar la interfaz de usuario
empleado la función setContentView().
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.
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.
Cuando una actividad cambia entre los diferentes estados descritos anteriormente, el
sistema operativo le notifica el cambio mediante diferentes métodos callback. El programador
puede usar todos estos métodos callback para ejecutar las órdenes apropiadas. El ejemplo
siguiente incluye la estructura de cada uno de estos métodos fundamentales del ciclo de vida
de una Actividad:
94
Diseño del interfaz de usuario
@Override
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
@Override
super.onPause();
@Override
super.onRestart();
@Override
super.onResume();
@Override
super.onStart();
95
// Aquí deberíamos leer los datos de la última sesión para seguir la
@Override
super.onDestroy();
@Override
super.onStop();
A continuación, vamos a ver el ciclo de vida de una Actividad siguiendo sus estados y
los métodos callback que desencadena cada uno de ellos:
El ciclo de vida de una Actividad ocurre entre las llamadas a los métodos onCreate()
y OnDestroy(). En el primer método la Actividad debe realizar la reserva de memoria,
el diseño de la interfaz de usuario y recuperar el estado de la sesión anterior. En el
segundo método, hay que liberar todos los recursos usados con anterioridad.
El ciclo de vida "visible" de una Actividad ocurre entre las llamadas a los métodos
OnStart() y OnStop(). Durante este tiempo el usuario puede ver e interactuar con la
pantalla de la Actividad. El sistema invoca el método OnStop()cuando se inicia una
nueva actividad y la actual ya no está visible al usuario. Entre estos dos métodos, el
programador debe definir y destruir respectivamente los recursos necesarios para
mostrar la Actividad para el usuario.
96
Diseño del interfaz de usuario
El ciclo de vida "en ejecución" de una Actividad sucede entre las llamadas a los
métodos OnResume() y OnPause(). Durante este tiempo la Actividad se ejecuta en
primer plano y tiene el foco del usuario. A menudo, el sistema operativo invoca estos
métodos, por ejemplo, cuando el teléfono se queda en modo espera o cuando
aparece una ventana con un mensaje de la aplicación. Por esto, es conveniente incluir
en estos métodos pocas y sencillas sentencias para que la aplicación no se pare
cuando el usuario intenta hacer operaciones con ella.
97
Resumen de los métodos callback del ciclo de vida de una Actividad
Siguiente
Método Descripción Kill
método
98
Diseño del interfaz de usuario
Siguiente
Método Descripción Kill
método
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().
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.
99
código fuente y ejecútalo para mostrar en el emulador una aplicación en la que hemos
utilizado los métodos del ciclo de vida de una Actividad.
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.
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.
El siguiente ejemplo muestra cómo especificar los métodos de los eventos sobre un
EditText:
texto.addTextChangedListener(new TextWatcher() {
int after) {
int count) {
101
}
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.
diseño qué método debe invocar Android cuando el usuario haga clic sobre
él -->
android:text="@string/calcular" android:onClick="miClickHandler"
android:layout_gravity="center_horizontal"
android:layout_width="104dp">
</Button>
switch (view.getId()) {
if (texto.getText().length() == 0) {
return;
102
Diseño del interfaz de usuario
if (kilometrosButton.isChecked()) {
} else {
break;
onLongClick(): este método devuelve “true” para indicar que se han llevado a cabo
las operaciones necesarias para manejar el evento clic, por lo que ya no debe
lanzarse cualquier otro método de tipo “clic”. En caso contrario, si el método
devuelve el valor “false”, Android puede invocar a continuación otro método diferente
de tipo “clic”.
onKey(): este método devuelve “true” o "false" para avisar si se han llevado a cabo
las operaciones necesarias para manejar el evento de teclado, por lo que ya no debe
lanzarse cualquier otro método de tipo “teclado”.
onTouch(): en este método ocurre como en los dos casos anteriores, según devuelva
"true" o "false" para señalar si Android debe invocar los siguientes métodos.
Recuerda que los eventos de tipo teclado siempre afectan a la Vista que está activa en
ese momento.
103
Desde Eclipse puedes abrir el proyecto Ejemplo 2 (Eventos) de la Unidad 2. Estudia el
código fuente y ejecútalo para mostrar en el emulador una aplicación en la que hemos
utilizado varios métodos de eventos de Android.
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:
104
Diseño del interfaz de 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:
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".
Cada vez que un usuario pulsa una tecla o desplaza la bola de navegación (trackball),
el dispositivo sale del modo táctil y busca una Vista donde activar de nuevo el foco de la
aplicación. Así el usuario puede volver a interactuar con la interfaz de usuario sin tocar la
pantalla.
El estado de modo táctil se mantiene a lo largo de todo el sistema operativo (todas las
ventanas y actividades). Para consultar el estado actual, se puede usar el método
isInTouchMode(). De esta forma se puede saber si el dispositivo se encuentra en modo táctil.
105
2.2.5 Controlando la Vista con el foco activo
El sistema Android cambia la Vista activa (con foco) en respuesta a la interacción del
usuario. Las Vistas indican la posibilidad de recibir el foco a través del método isFocusable().
Para establecer si una Vista puede recibir el foco, hay que utilizar el método setFocusable().
Como hemos dicho, en el modo táctil, podemos usar el método isFocusableInTouchMode() y
establecer si una Vista puede recibir el foco con setFocusableInTouchMode().
El cambio de foco automático que hace Android se basa en un algoritmo que busca la
Vista vecina más cercana en una dirección. Normalmente, este algoritmo establecido por
defecto no es el esperado por el usuario. Cuando esto ocurre, es posible definir explícitamente
en el archivo de diseño res\layout\main.xml cómo se debe cambiar el foco con los siguientes
atributos: nextFocusDown, nextFocusLeft, nextFocusRight y nextFocusUp indicando el id
de la Vista al que se debe saltar. Por ejemplo:
<LinearLayout
android:orientation="vertical"
... >
<Button android:id="@+id/arriba"
android:nextFocusUp="@+id/abajo"
... />
<Button android:id="@+id/abajo"
android:nextFocusDown="@+id/arriba"
... />
</LinearLayout>
Por lo general, en el diseño vertical anterior, intentar ir hacia arriba desde el primer
botón no cambia el foco de la aplicación. Lo mismo ocurre con el segundo botón al intentar ir
hacia abajo. Sin embargo, al haber definido en el botón superior el nextFocusUp para pasar al
segundo botón, el foco cambia al botón de abajo y viceversa.
Si queremos indicar que una Vista que por defecto no recibe el foco de la aplicación
pueda recibirlo, podemos usar el atributo XML de la Vista android:focusable al diseño la
interfaz de usuario. También podemos usar el atributo android:focusableInTouchMode
cuando se trate del modo táctil.
También desde el código Java podemos usar el método requestFocus() para indicar
al sistema que una Vista debe tener el foco.
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.
En la Unidad 1 ya hemos visto cómo se inicia este emulador de Android; ahora vamos
a estudiar cómo se utiliza.
107
NOTA: es importante que el dispositivo virtual se encuentre configurado en el idioma Español
para que todos los ejemplos del curso funcionen correctamente. Se explica cómo hacerlo en
el apartado “Cómo crear un proyecto Android” de la Unidad 1.
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:
108
Diseño del interfaz de usuario
principal de Android.
Muestra un menú
desplegable con las
F2 o RePág
Menú (tecla izquierda) opciones de la pantalla
actual.
Buscar información.
F5
Buscar
F7
Botón encender/apagar
BLOQUE_NUM_MAS (+),
Subir volumen Ctrl+5
BLOQUE_NUM_MENOS (-)
Bajar volumen Ctrl+F6
Habilita y deshabilita la
conexión de datos del
F8 móvil.
Conexión datos
El emulador ocupa la
Pantalla completa Alt+Intro pantalla completa del
monitor del ordenador.
Inicia el modo de
F6 navegación con bola
Bola navegación
(trackball)
109
Arrastrar izq/arriba/dcha/abajo BLOQUE_NUM_4/8/6/2
Ten en cuenta que para usar las teclas del bloque numérico es necesario desactivar el
bloqueo numérico en tu ordenador.
NOTA: es recomendable familiarizarse con el emulador de Android usando las funciones del
dispositivo virtual e ir visitando las diferentes opciones del mismo así como utilizar los atajos
de teclado de la tabla anterior.
2.3.2 Cómo introducir tildes con el Teclado del Emulador
Para que se puedan introducir tildes en las cajas de texto de las aplicaciones es muy
importante que el dispositivo virtual se encuentre configurado en el idioma Español. En el
apartado “Cómo crear un proyecto Android” de la Unidad 1 se explica cómo hacerlo.
Para que podamos introducir tildes en las cajas de texto, hay que pulsar un rato con el
ratón en la vocal correspondiente:
Pulsar un rato
110
Diseño del interfaz de usuario
Pulsar un rato
Atención: también es posible pulsar un rato en la tecla de la vocal del teclado del ordenador
donde estés trabajando para poder escribir la tilde correspondiente.
2.3.3 Limitaciones del Emulador
Es posible cambiar el tamaño de la ventana de AVD del Emulador para que se vea
correctamente en la pantalla de tu ordenador. Para ellos hacemos clic en el menú de Eclipse
“Android SDK and AVD Manager”:
111
Después, hacemos clic en el botón “Start”:
En esta ventana podemos ver la escala (Scale) que se aplicará a la ventana del
emulador (un número entre 0,1 y 3). Para ello, podemos especificar la densidad del monitor del
ordenador en puntos por pulgada (Monitor DPI) y el tamaño en pulgadas de la pantalla del
dispositivo Android (Screen Size).
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:
112
Diseño del interfaz de usuario
Para acceder a esta herramienta en Eclipse hay que hacer clic en la opción del menú
principal: Window -> Open Perspective -> DDMS.
113
Nota: no todas las opciones posibles del Emulador están incluidas en la ventana anterior;
algunas deben configurarse mediante órdenes en la línea de comandos del sistema operativo
de tu ordenador.
En la Unidad 8 veremos cómo usar esta ventana para depurar aplicaciones Android.
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.
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.
114
Diseño del interfaz de usuario
// 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);
Ejemplo
<Spinner android:id="@+id/ListadoOpciones"
android:prompt="@string/selecciona"
android:drawSelectorOnTop="true"
115
android:layout_width="fill_parent"
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:
adaptador.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
listadoOpciones.setAdapter(adaptador);
listadoOpciones.setOnItemSelectedListener(
new AdapterView.OnItemSelectedListener() {
@Override
resultado.setText("");
});
116
Diseño del interfaz de usuario
Para cambiar el aspecto de las opciones de la lista emergente hay que usar el método
setDropDownViewResource(ID_layout). En este caso hemos utilizado el diseño predefinido
de Android para las listas desplegables android.R.layout.simple_spinner_dropdown_item.
Esto provoca que el diseño de la selección y el listado desplegable sean diferentes (fíjate en el
cambio de color):
117
Lista de selección (List View)
La Lista de selección (o ListView en inglés) permite al usuario hacer clic sobre una lista
de opciones seleccionables. Estas opciones se muestran directamente sobre el propio
componente; por lo tanto, no se trata de una lista emergente como el Spinner. Si no se
visualizan todas las opciones disponibles en la pantalla del dispositivo porque no caben, se
puede desplazar el listado con el dedo o los botones de navegación.
En el ejemplo siguiente vamos a usar un Adaptador particularizado para que dibuje las
opciones del menú personalizadas.
Fíjate en el código del siguiente ejemplo que define un ListView en el fichero main.xml
de layout de la aplicación:
Ejemplo
<ListView android:id="@+id/ListaOpciones"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
Para enlazar el adaptador que define las opciones de este tipo de listado y tratar el
evento que ocurre cuando el usuario selecciona una de las opciones disponibles, escribimos
las siguientes sentencias:
...
listaOpciones.setAdapter(adaptador);
118
Diseño del interfaz de usuario
listaOpciones.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
});
Activity contexto;
AdaptadorOpciones(Activity contexto) {
this.contexto = contexto;
119
VistaTag vistaTag;
If (item == null)
vistaTag.titulo = (TextView)item.findViewById(R.id.LblTitulo);
vistaTag.subtitulo =
(TextView)item.findViewById(R.id.LblSubTitulo);
item.setTag(vistaTag);
else
vistaTag = (VistaTag)item.getTag();
vistaTag.titulo.setText(datos[position].getTitulo());
vistaTag.subtitulo.setText(datos[position].getSubtitulo());
return(item);
120
Diseño del interfaz de usuario
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:
TextView titulo;
TextView subtitulo;
121
Desde Eclipse puedes abrir el proyecto Ejemplo 4 (Lista de selección) de la Unidad 2.
Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior,
en el que hemos utilizado el componente ListView.
Ejemplo
<GridView android:id="@+id/GridOpciones"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:numColumns="auto_fit"
android:columnWidth="80px"
android:horizontalSpacing="5px"
android:verticalSpacing="10px"
android:stretchMode="columnWidth" />
Como en los anteriores ejemplos, para enlazar el adaptador que define las opciones de
este tipo de listado escribimos las siguientes sentencias:
122
Diseño del interfaz de usuario
ArrayAdapter<String> adaptador =
gridOpciones.setAdapter(adaptador);
Ejemplo
<AutoCompleteTextView android:id="@+id/miautocomplete"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:completionThreshold="1"
/>
Como en los anteriores ejemplos, para enlazar el adaptador que define las opciones de
este tipo de listado escribimos las siguientes sentencias:
123
// Debemos implemetar los método de TextWatcher para poder detectar los
// eventos de abajo
@Override
String meses[]={
...
miAutoComplete =
(AutoCompleteTextView)findViewById(R.id.miautocomplete);
miAutoComplete.addTextChangedListener(this);
miAutoComplete.setAdapter(adaptador);
@Override
int after) {
// Aquí escribimos el código que se ejecuta antes de que el texto cambie
}
@Override
124
Diseño del interfaz de usuario
@Override
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.
Ejemplo
<ListView
android:id="@+id/android:list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
125
<TextView android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000"
android:text="No hay datos"/>
Para cambiar el diseño de las opciones del ListView interno de la actividad ListActivity
hay que definir un nuevo fichero XML de diseño dentro de la carpeta res\layout. Por ejemplo,
el fichero se puede llamar fila.xml y contener el siguiente código:
<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.
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
126
Diseño del interfaz de usuario
this.setListAdapter(new ArrayAdapter<String>(this, R.layout.fila,
R.id.texto, nombres));
}
}
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.
127
EditText (cuadro de texto) para que muestre el número de caracteres que contiene a medida
que se escribe en él.
Se trata de simular un editor de mensajes cortos SMS del propio sistema operativo que
nos avisa del número de caracteres que contiene el mensaje. En nuestro caso, como resultado
obtendremos un componente como el que se muestra en la siguiente imagen:
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:
pinceles();
super(context, attrs);
128
Diseño del interfaz de usuario
pinceles();
super(context);
pinceles();
// Función que inicia las pinceles que usamos para pintar el cuadradito negro
pNegro.setColor(Color.BLACK);
pNegro.setStyle(Style.FILL);
pBlanco.setColor(Color.WHITE);
// Para modificar el aspecto del EditText hay que reescribir este método
@Override
super.onDraw(canvas);
canvas.drawText("" + this.getText().toString().length(),
this.getWidth()-28, 17, pBlanco);
129
que es el “lienzo” sobre el que podemos dibujar todos los elementos extra necesarios en el
componente.
La clase Canvas proporciona varios métodos para dibujar líneas, rectángulos, elipses,
texto, imágenes, etcétera, sobre el espacio ocupado por el componente. En este caso
únicamente vamos a dibujar un rectángulo que sirve de fondo para el contador y el texto con
el número de caracteres actual que ha escrito el usuario.
Para dibujar el gráfico es necesario definir dos “pinceles” (clase Paint). El primero
permite pintar de color negro y con relleno sólido, y el segundo pinta de color blanco. Como
sólo es necesario crear estos pinceles una vez, los hemos definido como atributos de la clase
y los inicializamos en los tres constructores del componente.
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"/>
130
Diseño del interfaz de usuario
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">
<EditText android:id="@+id/TextoUsuario"
android:layout_height="wrap_content"
android:layout_width="fill_parent" />
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" />
android:layout_gravity="center_horizontal"
android:layout_height="wrap_content"
android:text="Entrar" android:paddingRight="20dip"
android:layout_width="104dp"></Button>
android:layout_height="wrap_content" android:paddingLeft="10dip"
android:textStyle="bold" />
</LinearLayout>
131
A continuación, creamos la clase Java asociada a este componente compuesto donde
se define toda su funcionalidad. Como el diseño está basado en la clase LinearLayout, el
nuevo componente debe heredar también esta clase Java de Android. Redefiniremos además
los dos constructores básicos:
super(context);
inicializar();
super(context, attrs);
inicializar();
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.ComponenteLogin);
botonLogin.setText(textoBoton);
a.recycle();
132
Diseño del interfaz de usuario
LayoutInflater li =
(LayoutInflater)getContext().getSystemService(infService);
textoUsuario = (EditText)findViewById(R.id.TextoUsuario);
textoPassword = (EditText)findViewById(R.id.TextoPassword);
botonLogin = (Button)findViewById(R.id.BotonAceptar);
labelMensaje = (TextView)findViewById(R.id.LabelMensaje);
asignarEventos();
listener = l;
botonLogin.setOnClickListener(new OnClickListener()
@Override
133
listener.onLogin(textoUsuario.getText().toString(),
textoPassword.getText().toString());
});
labelMensaje.setText(msg);
134
Diseño del interfaz de usuario
compLogin.setOnLoginListener(new OnLoginListener()
@Override
else
});
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sgo="http://schemas.android.com/apk/res/es.mentor.unidad2.eje6.logincomp
uesto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip" >
<es.mentor.unidad2.eje6.logincompuesto.ComponenteLogin
android:id="@+id/ComponenteLogin"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#0000AA"
135
sgo:texto_boton="Entrar" />
</LinearLayout>
<resources>
<declare-styleable name="ComponenteLogin">
</declare-styleable>
</resources>
136
Diseño del interfaz de usuario
137
Una Actividad (Activity) es un componente de Android que ofrece una pantalla
con la que los usuarios pueden interactuar en una aplicación.
Una 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.
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.
138
Diseño del interfaz de usuario
139
140
Unidad de Aprendizaje 9
EMÁS
FECTOS DE TRANSICIÓN
INFORMACIÓN SOBREY
ANANDROID
IMACIÓN
ÍNDICE
3.1 INTRODUCCIÓN .......................................................... 143
3.1.1 Introducción ...................................................................143
3.1.2 Gestión del botón “Hacia atrás” de Android ............143
3.1.3 Definición de una tarea en los proyectos Android...145
3.1 INTRODUCCIÓN
3.1.1 Introducción
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.
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.
143
Cuando el usuario toca un icono de esta pantalla, se inicia una tarea asociada a la
aplicación. Si es la primera vez que arrancamos la aplicación, el sistema operativo crea una
nueva tarea y su Actividad principal ("main") queda almacenada en primer lugar en la pila de
Android.
Cuando esta Actividad principal arranca otra, la nueva Actividad se añade a la parte
superior de la pila y pasa a primer plano. La Actividad anterior permanece en la pila y se
detiene manteniendo el estado actual de la interfaz de usuario. Si el usuario pulsa la tecla de
144
Más información sobre Android
Si el usuario continúa presionando "Volver atrás", se extraerá una a una cada actividad
de la pila mostrando la anterior, hasta que aparezca la pantalla de inicio u otra aplicación que
se haya iniciado antes. Cuando esto ocurre, la tarea se destruye.
Una tarea es una unidad compuesta de actividades que puede pasar a un segundo
plano cuando los usuarios inician una nueva tarea o van a la pantalla de inicio. Cuando una
tarea se encuentra en segundo plano, todas sus actividades se detienen, aunque su pila de
ejecución se mantiene intacta hasta que el usuario decida que vuelva al "primer plano".
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.
<activity android:name=".BienvenidoActivity"
android:label="@string/app_name">
<intent-filter>
</intent-filter>
</activity>
</application>
145
Nota: debido a la nomenclatura de Android, es obligatorio escribir un punto al principio del
nombre.
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.
146
Más información sobre Android
Por esta razón, es muy importante marcar la actividad principal con la categoría
"android.intent.category.LAUNCHER", para que el usuario pueda volver a una aplicación
que se encuentre en segundo plano y que ya no es visible.
En este apartado vamos a describir lo que hace Android cuando una Actividad se
destruye o se detiene, cómo se conserva su estado en la memoria del sistema operativo,
incluyendo todos los cambios que haya hecho el usuario en la interfaz introduciendo
información, seleccionando opciones, etcétera.
En el siguiente esquema se muestra los eventos y estados por los que pasa una
Actividad cuando se detiene o se destruye.
147
Como se puede ver en el esquema anterior, una Actividad recupera su estado de
ejecución si se detiene y vuelve a primer plano; o si se destruye y se vuelve a crear.
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.
148
Más información sobre Android
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.
<LinearLayout android:id="@+id/mainLayout"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
149
<TextView android:id="@+id/nombre" android:layout_width="wrap_content"
android:layout_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>
android:layout_height="wrap_content"
android:layout_marginLeft="3dip" android:layout_marginTop="10dip">
</TextView>
<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>
150
Más información sobre Android
@Override
super.onCreate(estadoAlmacenado);
setContentView(R.layout.main);
if (estadoAlmacenado != null) {
@Override
super.onSaveInstanceState(estado);
Toast.LENGTH_LONG).show();
@Override
super.onDestroy();
En el código anterior hemos usado los métodos putString() y getString() del objeto
Bundle para almacenar el contenido de una variable extra en el estado de la Actividad.
151
Desde Eclipse puedes abrir el proyecto Ejemplo 1 (Guardar estado de una actividad) de la
Unidad 3. Estudia el código fuente y ejecútalo para mostrar en el emulador una aplicación en
la que guardamos el estado de ejecución de la Actividad
A continuación, introduce algún texto en los campos que aparecen. Después, haz clic
Si volvemos a ejecutar la aplicación, veremos que podemos ver tanto los datos tanto
de la caja de texto usuario como de la contraseña. Esto ocurre porque Android no ha
destruido la Actividad.
152
Más información sobre Android
Para que Android pare (kill) la Actividad cuando la dejamos, hay que configurar las
herramientas de desarrollo del dispositivo conocidas como "Dev-Tools". Para acceder a
estas herramientas hay que hacer clic en el icono "Dev Tools" que aparece en el lanzador de
aplicaciones. Después, hacemos clic en la opción "Development Settings" y, finalmente,
marcamos la opción "Immediately destroy activities":
Si ahora ejecutas de nuevo la aplicación y pulsas otra vez el botón "INICIO", verás el
mensaje "El sistema ha terminado la Actividad", que indica que la aplicación se destruye:
153
3.3 PROCESOS EN HILOS EN ANDROID
Un hilo es una característica de la informática que permite a una aplicación realizar
varias tareas a la vez (concurrentemente). Esencialmente, un hilo es una tarea que se ejecuta
en paralelo con otra tarea. Todos los hilos que se ejecutan a la vez pueden compartir una
serie de recursos, tales como la memoria, los archivos abiertos, etcétera. Esta técnica permite
simplificar el diseño de una aplicación que puede así llevar a cabo distintas funciones
simultáneamente.
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.
154
Más información sobre Android
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.
155
3. Proceso de servicio: es un proceso que ejecuta un servicio que se ha iniciado con
el método startService() y no corresponde a ninguna de las dos categorías
anteriores. Aunque los procesos de servicio no están relacionados directamente
con lo que ve el usuario, por lo general, desempeñan tareas importantes para el
usuario, como reproducir música o descargar de datos de Internet.
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.
Hemos visto que un proceso que ejecuta un Servicio se clasifica con mayor prioridad
que un proceso con una Actividad en segundo plano. Si una actividad inicia una operación que
dura mucho, es recomendable crear un servicio para esa operación, en lugar de crear un
subproceso, especialmente si la operación va a durar más que la propia actividad. Por
ejemplo, si una actividad tiene que subir una foto a un sitio Web, es mejor iniciar un servicio
para llevar a cabo esta tarea, así la carga pueda continuar en segundo plano incluso si el
usuario sale de la actividad. El uso de servicios garantiza de que la operación tendrá, por lo
menos, la prioridad de un proceso de servicio, independientemente de lo que ocurra con la
actividad.
Por esta misma razón, los receptores de mensajes (broadcast receivers) deben
emplear servicios en lugar de tareas con un hilo de ejecución.
156
Más información sobre Android
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).
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):
157
datos a los que accede dicha función (o método) sean corrompidos por alguno de los hilos ya
que se asegura la atomicidad de la operación, es decir, la función se ejecuta de forma
serializada sin interrupciones
A primera vista, este código parece funcionar bien ya que crea un nuevo hilo en
segundo plano para descargar la imagen. Sin embargo, no cumple la segunda regla del
modelo de un hilo único: no acceder a la interfaz de usuario de Android desde un hilo
exterior a esta interfaz de usuario. Esto puede producir un comportamiento inesperado en la
aplicación y muy difícil de localizar.
Para solucionar este inconveniente, Android ofrece varias formas de acceder al hilo de
la interfaz de usuario desde otros hilos en segundo plano. A continuación se muestra la lista de
estos métodos:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
158
Más información sobre Android
Para usar esta funcionalidad de Android hay que extender la clase AsyncTask e
implementar los siguientes métodos callback:
159
// Método asociado al botón "Descargar"
imagen.setVisibility(View.INVISIBLE);
cargando.setVisibility(View.VISIBLE);
tarea.execute(imagenURL);
// Clase que descarga una imagen de Internet como una tarea asíncrona.
return loadImageFromNetwork(urls[0]);
cargando.setVisibility(View.INVISIBLE);
if (resultado!=null) imagen.setImageBitmap(resultado);
else imagen.setImageResource(R.drawable.error);
imagen.setVisibility(View.VISIBLE);
El primer parámetro (String) define el tipo de variable que usamos como parámetro al
invocar doInBackground(). El segundo parámetro (Void) define el tipo de variable que
pasamos como parámetro al invocar onProgressUpdate(). Finalmente, el último parámetro
(Bitmap) define el tipo de variable que pasamos como parámetro al invocar onPostExecute()
160
Más información sobre Android
Aunque más adelante en el curso veremos cómo se indican los permisos que necesita
una aplicación para ejecutarse, para que este ejemplo funcione y se descargue una imagen de
Internet es necesario indicar el permiso en el fichero AndroidManifest.xml de la aplicación así:
<uses-permission android:name="android.permission.INTERNET">
</uses-permission>
Es recomendable que el alumno o alumna lea el manual de la clase AsyncTask para conocer
más funcionalidad, entre ella se encuentra:
Para entender mejor esta aplicación que usa un proceso en segundo plano, inicia este
ejemplo en el emulador. Verás la pantalla siguiente:
161
Si pulsas en el botón "Descargar" comprobarás que puedes seguir usando el resto de
Vistas de la pantalla mientras se descarga la imagen.
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.
Menús Principales: son los usados con más frecuencia. Aparecen en la zona inferior
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.
Veamos en primer lugar cómo crear un menú principal con un submenú a partir de su
diseño en XML. Estos ficheros XML con el diseño del menú se deben guardar en la carpeta
res\menu del proyecto y tienen una estructura de este tipo (archivo menu_principal.xml):
162
Más información sobre Android
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
android:icon="@drawable/menu_estrella"></item>
android:icon="@drawable/menu_brujula"></item>
android:icon="@drawable/menu_direccion">
<menu>
<item android:id="@+id/SubMenuOp1"
<item android:id="@+id/SubMenuOp2"
</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ú.
De igual forma que otros archivos XML de un proyecto Android, podemos editarlo
visualmente haciendo clic en la pestaña “Layout” del archivo que define el menú:
163
Una vez definido el menú en el fichero XML, hay que implementar el método
onCreateOptionsMenu() de la Actividad para que se cree en la pantalla. En este método
debemos “inflar” el menú de forma parecida a como ya hemos hecho con otro tipo de
componentes layouts. Primero obtenemos una referencia al objeto "inflador" mediante el
método getMenuInflater() y, después, generamos la estructura del menú usando el método
inflate() y pasándole como parámetro el ID del archivo XML de diseño del menú. Finalmente,
el método debe devolver el valor true para indicar a la Actividad que debe mostrar el menú.
@Override
inflater.inflate(R.menu.menu_principal, menu);
return true;
164
Más información sobre Android
ID del grupo asociado a la opción: veremos qué es esto en el siguiente ejemplo con un
menú contextual, por lo que establecemos el valor Menu.NONE.
Veamos cómo queda el código utilizando esta otra forma de implementarlo que genera
un menú exactamente igual al del ejemplo anterior:
...
@Override
return true;
}
Una vez construido el menú, es necesario implementar las sentencias que se ejecutan
cuando el usuario selecciona una de las opciones, Para ello, usamos el evento
165
onOptionsItemSelected() de la Actividad. Este evento recibe como parámetro el elemento de
menú (MenuItem) que ha sido elegido por el usuario y cuyo ID podemos obtener con el
método getItemId(). En función de este ID podemos saber qué opción ha sido pulsada y
ejecutar unas sentencias u otras. En nuestro ejemplo, lo único que hacemos es modificar el
texto de la etiqueta labelResultado que hemos colocado en la pantalla principal de la
aplicación:
switch (item.getItemId()) {
case R.id.MenuOp1:
return true;
case R.id.MenuOp2:
return true;
case R.id.MenuOp3:
return true;
case R.id.SubMenuOp1:
return true;
case R.id.SubMenuOp2:
return true;
default:
return super.onOptionsItemSelected(item);
Desde Eclipse puedes abrir el proyecto Ejemplo 4 (Menús) de la Unidad 3. Estudia el código
fuente y ejecútalo para mostrar en el emulador una aplicación en la que usamos un menú
principal y un submenú,
166
Más información sobre Android
Para ver cómo funciona esta aplicación que usa un menú y un submenú inicia este ejemplo en
el emulador. Verás la pantalla siguiente:
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.
167
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
labelResultado = (TextView)findViewById(R.id.labelResultado);
listadoPrincipal = (ListView)findViewById(R.id.ListadoPrincipal);
listadoPrincipal.setAdapter(adaptador);
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:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/ContextLabelOp1"
android:title="Opción 1 de etiqueta"></item>
<item android:id="@+id/ContextLabelOp2"
android:title="Opción 2 de etiqueta"></item>
</menu>
168
Más información sobre Android
<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"
ContextMenuInfo menuInfo)
super.onCreateContextMenu(menu, v, menuInfo);
if(v.getId() == R.id.labelResultado)
inflater.inflate(R.menu.menu_context_etiqueta, menu);
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo)menuInfo;
169
// Definimos la cabecera del menú contextual
menu.setHeaderTitle(
listadoPrincipal.getAdapter().getItem(info.position).toString());
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.
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:
AdapterContextMenuInfo info =
(AdapterContextMenuInfo) item.getMenuInfo();
switch (item.getItemId()) {
case R.id.ContextLabelOp1:
return true;
170
Más información sobre Android
case R.id.ContextLabelOp2:
return true;
case R.id.EditTextOp:
adaptador.notifyDataSetChanged();
return true;
case R.id.ReiniciaTextOp:
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.
171
ocasión, lo obtenemos llamando al método getMenuInfo() de la opción de menú MenuItem
recibida como parámetro.
Para ver cómo funciona esta aplicación que usa un menú contextual inicia este
ejemplo en el emulador. Verás la pantalla siguiente:
172
Más información sobre Android
1. AlertDialog: puede contener hasta tres botones (incluso ninguno) o mostrar una lista
de elementos seleccionables como CheckBox o RadioButton. Es recomendable
utilizar este tipo de ventana de diálogo en la mayoría de las aplicaciones. Se hereda de
la clase Dialog de Android.
Una ventana de diálogo siempre se crea y se muestra como parte de una Actividad.
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í:
...
173
3.5.3.1 Ventanas de diálogo con mensaje
Es una ventana de diálogo que obliga a que el usuario vea un mensaje bloqueando la
pantalla hasta que pulse la tecla volver . Sus métodos más importantes son éstos:
Si hacemos clic sobre el primer botón del ejemplo del curso, veremos que aparece el
siguiente mensaje:
ventana.setTitle("Atención");
ventana.setIcon(android.R.drawable.ic_dialog_email);
ventana.show();
174
Más información sobre Android
Si hacemos clic sobre el segundo o el tercer botón del ejemplo del curso, veremos que
aparecen los siguientes mensajes:
ventana.setIcon(android.R.drawable.ic_dialog_info);
ventana.setTitle("Encuesta");
ventana.setCancelable(false);
});
});
175
});
ventana.show();
Si hacemos clic sobre el sexto botón del ejemplo del curso, veremos que aparece el
siguiente mensaje:
ventana.setIcon(android.R.drawable.ic_dialog_info);
ventana.setSingleChoiceItems(telefonos, 0, new
DialogInterface.OnClickListener() {
});
176
Más información sobre Android
});
});
ventana.show();
Si hacemos clic sobre el octavo botón del ejemplo del curso, veremos que aparece el
siguiente mensaje:
177
LayoutInflater li
=(LayoutInflater)getApplicationContext().getSystemService(infService);
ventana.setView(inflador);
});
});
ventana.show();
En el Ejemplo 5 de esta Unidad puedes ver otros tipos de ventanas de diálogo muy
178
Más información sobre Android
similares a las estudiadas en este apartado. Recomendamos al alumno o alumna que estudie a
fondo el código fuente de este ejemplo.
179
Una aplicación puede estar compuesta de una o varias Actividades.
Android organiza las actividades en una pila de ejecución (en inglés stack)
donde se van apilando las actividades que el usuario invoca.
180
El sistema operativo Android sigue el modelo de ejecución de aplicaciones
denominado Modelo de ejecución de un único hilo (en inglés Single Thread Model).
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.
181
Unidad de Aprendizaje 9
EFECTOS
TRABAJANDO
DE TRANSICIÓN
CON Y
FICHEROS
ANIMACIÓN
ÍNDICE
4.1 FICHEROS EN ANDROID .......................................................... 185
4.1.1 Introducción ..................................................................... 185
4.1.2 Gestión de información en Android ................................. 185
4.1.3 Gestión del sistema de archivos en Android ................... 185
4.1.4 Clase Fichero File ............................................................. 186
4.1.4.1 Constructores más importantes .............................. 186
4.1.4.2 Métodos más importantes....................................... 187
4.1.5 Ficheros en la memoria interna del diapositivo ............... 188
4.1.6 Fichero de recurso de la aplicación ................................. 190
4.1.7 Fichero en almacenamiento externo................................ 191
4.1.8 Añadir datos a un fichero ................................................. 196
4.1.9 Gestionando las excepciones en la gestión de ficheros . 196
4.1.1 Introducción
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.
185
En Android, por defecto, los ficheros son privados y únicamente puede acceder a ellos
la aplicación que los crea. Para compartir información de estos ficheros se utilizan los Content
Providers que veremos en la Unidad 7.
Lo primero que hay debemos tener en cuenta es dónde queremos almacenar estos
ficheros y la manera en que vamos a acceder a ellos: lectura o escritura.
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.
186
4.1.4.2 Métodos más importantes
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.
187
boolean mkdirs(): crea recursivamente un subdirectorio incluyendo todos los
subdirectorios superiores que completan el camino.
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:
try
188
OutputStreamWriter fileout=
new OutputStreamWriter(openFileOutput("fichero_interno.txt",
Context.MODE_PRIVATE));
fileout.close();
/data/data/paquete_java/files/nombre_del_fichero
/data/data/es.mentor.unidad4.eje1.ficheros/files/fichero_interno.txt
189
Leer ficheros desde la memoria interna es igual de sencillo. Procedemos de forma
análoga utilizando esta vez el método openFileInput(), para abrir el fichero y usamos los
métodos de lectura mostrados anteriormente para leer el contenido.
try
new InputStreamReader(openFileInput("fichero_interno.txt")));
filein.close();
Una vez creada la carpeta raw, podemos añadir en ella cualquier fichero que
necesitemos incluir con la aplicación en tiempo de compilación en forma de recurso. En el
Ejemplo 1 de esta Unidad hemos incluido el fichero de texto “prueba_raw.txt“.
Posteriormente, en tiempo de ejecución, podemos acceder a este fichero, sólo en modo de
lectura, de una forma similar a la que ya hemos visto anteriormente para el resto de ficheros en
la memoria interna del dispositivo.
Estos ficheros de tipo recurso también pueden ser binarios: por ejemplo, imágenes,
vídeos, etcétera.
En primer lugar, para acceder a este fichero, obtenemos los recursos de la aplicación
con el método getResources() y sobre éste utilizamos el método
190
openRawResource(id_del_recurso) para abrir el fichero en modo lectura. Este método
devuelve un objeto de tipo InputStream que podemos manipular.
try
while (true) {
texto = brin.readLine();
if (texto==null) break;
resultado.append("\n"+Html.fromHtml(texto));
} // end while
ficheroraw.close();
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.
191
MEDIA_MOUNTED: indica si la memoria externa está disponible y es posible leer y
escribir en ella.
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.
if (Environment.MEDIA_MOUNTED.equals(estado)) {
} else
if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(estado)) {
hayAlmacenamientoExt = true;
almacenamientoExtEscritura = false;
} else {
if (hayAlmacenamientoExt) {
192
// Mostramos el directorio donde está el almacenamiento externo
dirAlmacExt=dir.getAbsolutePath();
}
} // end compruebaTarjetaSD
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.
try
{
// Creamos un directorio de prueba
directorio.mkdirs();
193
fout.write("Caminante no hay camino se hace camino al andar...");
fout.close();
}
} else resultado.append("No hay almacenamiento externo disponible o no
se puede escribir en él.");
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="es.mentor.unidad4.eje1.ficheros"
android:versionCode="1"
android:versionName="1.0">
<activity android:name=".ficherosActivity"
android:label="@string/app_name">
<intent-filter>
</intent-filter>
</activity>
194
</application>
</manifest>
try
resultado.append(texto);
fin.close();
195
}
Por defecto, Android sobrescribe siempre un fichero si éste ya existe perdiendo los
datos almacenados anteriormente. Esto ocurre en los ejemplos anteriores.
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:
196
try {
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:
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.
197
Cada preferencia se almacena siguiendo la estructura clave-valor. Es decir, cada una
de ellas está compuesta por un identificador único (por ejemplo, “email”) y un valor asociado a
dicho identificador (por ejemplo, “correo@email.com”). Estos datos se guardan en un fichero
XML.
Para gestionar las preferencias de una aplicación hay que usar la clase
SharedPrefences. Una misma aplicación Android puede gestionar varias colecciones de
preferencias que se diferencian por un identificador único. Para obtener la referencia a una
colección determinada utilizamos el método getSharedPrefences() indicando el identificador
de la colección y el modo de acceso a la misma. El modo de acceso indica qué aplicaciones
tienen acceso a esta colección de preferencias y qué operaciones podrán realizar sobre ellas.
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:
198
Actualizar o añadir nuevas claves de preferencias es muy parecido. En lugar de usar el
objeto SharedPreferences, utilizaremos SharedPreferences.Editor para editar preferencias.
Accedemos a este objeto mediante el método edit() de la clase SharedPreferences.
Una vez obtenida la referencia al editor, utilizamos los métodos put() oportunos en
función del tipo de dato de cada clave de preferencia para actualizar o insertar su valor.
Por ejemplo, putString(clave, valor) actualiza una preferencia de tipo String. De igual
forma, existen métodos get() para todos los tipos de datos básicos de Java: putInt(),
putFloat(), putBoolean(), etcétera.
SharedPreferences preferencias =
getSharedPreferences("MisPreferencias", Context.MODE_PRIVATE);
editor.putString("email", "correo@email.com");
editor.commit();
/data/data/paquetejava/shared_prefs/nombre_coleccion.xml
/data/data/es.mentor.unidad4.eje2.preferencias/shared_prefs/MisPreferencias.xml
<map>
<string name="email">correo@email.com</string>
</map>
199
En este fichero XML observamos cómo se almacenan las dos preferencias del ejemplo
anterior con sus claves y valores correspondientes.
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“).
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.
Dentro de esta pantalla podemos incluir una lista de opciones organizadas por
categorías, que se representan mediante el elemento <PreferenceCategory>, al que
200
añadimos un texto descriptivo utilizando el atributo android:title. Dentro de cada categoría
podemos incluir varias opciones. Éstas pueden ser de distintos tipos, por ejemplo:
CheckBoxPreference
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"
ListPreference
Se emplea para que el usuario seleccione una única opción de preferencia de una lista
de valores predefinida. Además de los cuatro atributos anteriormente comentados, hay que
201
añadir dos más: uno para indicar la lista de valores que se visualizan en la lista, y otro para
señalar los valores internos que guardaremos para cada uno de los valores de la lista anterior.
Por ejemplo, al usuario podemos mostrarle una lista de buscadores con el texto “Google” y
“Bing”, pero internamente almacenarlos como “www.google.es” y “www.bing.com”.
<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>
<ListPreference
android:key="opcion3"
android:title="Buscadores"
android:dialogTitle="Selecciona buscador"
android:entries="@array/nombre"
android:entryValues="@array/url" />
202
MultiSelectListPreference
Se emplea para que el usuario seleccione una o varias opciones de preferencia de una
lista de valores predefinida. Los atributos que debemos establecer son, por lo tanto, los
mismos que para el tipo ListPreference. Vemos un ejemplo a continuación:
<MultiSelectListPreference
android:key="opcion4"
android:title="Varios buscadores"
android:dialogTitle="Selecciona buscadores"
android:entries="@array/nombre"
android:entryValues="@array/url" />
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="opcion1"
android:title="Búsqueda automática"
<EditTextPreference
android:key="opcion2"
android:title="Texto de la búsqueda"
</PreferenceCategory>
<ListPreference
android:key="opcion3"
android:title="Buscadores"
203
android:summary="Indica el buscador por defecto"
android:dialogTitle="Selecciona buscador"
android:entries="@array/nombre"
android:entryValues="@array/url" />
</PreferenceCategory>
</PreferenceScreen>
Una vez está definida la estructura de la pantalla de opciones, hay que implementar
una nueva Actividad que la llamaremos cuando queramos mostrar la pantalla de preferencias,
y que se encargará internamente de gestionar todas las opciones, guardarlas, modificarlas,
etcétera, a partir de la definición XML.
@Override
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();
Para que la aplicación pueda llamar a esta nueva Actividad, hay que incluirla en el
fichero AndroidManifest.xml como otra actividad más de la aplicación:
204
<activity android:name=".PantallaPreferencias"
android:label="@string/app_name">
</activity>
Para acabar la aplicación, hay que añadir algún mecanismo que nos permita mostrar la
pantalla de preferencias. Esta opción suele estar en un menú, pero para simplificar, en el
Ejemplo 2 vamos a incluir el botón preferenciasBtn en la interfaz del usuario, para mostrar la
ventana de preferencias.
preferenciasBtn.setOnClickListener(new OnClickListener() {
@Override
startActivity(new Intent(PreferenciasActivity.this,
PantallaPreferencias.class));
}
});
205
Podemos marcar o desmarcar directamente la primera opción pulsando sobre el
CheckBox. Si pulsamos sobre la segunda opción de tipo texto, se muestra una pequeña
ventana que permite introducir el valor de la opción:
Por último, la tercera opción de tipo lista muestra una ventana emergente con la lista
de valores posibles, donde únicamente podemos seleccionar uno:
Una vez hayamos establecidos los valores de las preferencias, podemos salir de esta
ventana de opciones pulsando el botón Atrás del dispositivo o del emulador . La Actividad
PantallaPreferencias se habrá ocupado por nosotros de guardar correctamente los valores
de las opciones.
206
obtenerPreferenciasBtn.setOnClickListener(new OnClickListener() {
@Override
SharedPreferences prefe =
PreferenceManager.getDefaultSharedPreferences(PreferenciasActivity.this);
lblResultado.setText("");
lblResultado.append("\n\nBuscador: " +
prefe.getString("opcion3", ""));
}
});
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.
207
1. Los recursos se integran en la aplicación de manera transparente y se pueden
cambiar sin necesidad de modificar el código fuente en Java.
2. Android genera un identificador único (ID) para cada archivo de recurso para
acceder a ellos directamente desde el código fuente en Java. Todos estos
identificadores de recursos se incluyen automáticamente en el archivo
/gen/nombre_paquete/R.Java.
Para ello, hay que hacer doble clic en el fichero de recursos que queramos editar; por
ejemplo, en el fichero res/values/string.xml y hacer clic en el botón "Add":
208
Después, basta con seleccionar el tipo de recursos que necesitamos dar de alta y
rellenar el nombre y el valor que debe contener:
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:
Quantity Strings (Plurals): recurso que incluye dos cadenas según una
cantidad sea singular o plural.
209
Más adelante veremos cómo se puede modificar el estilo y formatear el contenido de
todos estos recursos de tipo cadena.
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.
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"/>
etiqueta2.setText(getString(R.string.etiqueta2));
210
4.3.4.2 Matriz de cadenas (String Array)
<string-array name="nombre_matriz">
<item>texto elemento 1</item>
<item>texto elemento 2</item>
...
</string-array>
<string-array name="horoscopo">
<item>Cancer</item>
<item>Capricornio</item>
<item>Aries</item>
<item>Leo</item>
<item>Libra</item>
</string-array>
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:
s.setPrompt("Elige el horóscopo");
ArrayAdapter<CharSequence> adapter =
ArrayAdapter.createFromResource(this, R.array.horoscopo,
android.R.layout.simple_spinner_item);
211
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
;
s.setAdapter(adapter);
Para obtener los elementos de la matriz desde el código fuente en Java podemos usar
la función getStringArray:
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.
<plurals name="nombre_plural">
<item quantity=["zero" | "one" | "two" | "few" | "many" | "other"]>
texto del literal</item>
</plurals>
<plurals name="numeroDeContactos">
Dentro de la etiqueta <plurals> incluimos varias etiquetas <item quantity> con los
literales que forman las distintas opciones de plurales. Podemos escribir los siguientes tipos
de plurales en el atributo quantity:
212
Valor Descripción
two Cuando el idioma requiere un tratamiento especial del número 2 (como el galés).
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:
A la hora de escribir comillas simples o dobles dentro del literal de una cadena de
texto debemos "escaparlas", para que Android no las interprete como parte del fichero XML y
muestre errores de sintaxis.
213
Es necesario incluir siempre las comillas simples dentro de las dobles o usar el
carácter "\" para escaparlas. Además, no se permite incluir entidades de HTML para los
caracteres singulares como "á".
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);
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:
214
Si ejecutamos la aplicación en el emulador, veremos que aparece la siguiente pantalla:
Para completar este ejemplo hemos incluido también un recurso de tipo imagen en el
directorio drawable y hemos definido un color para cambiar el aspecto de una de las
etiquetas.
Importante: Al definir recursos de tipo cadena hay que tener cuidado en no escribir caracteres
que Android no sepa interpretar. Cuando esto ocurra Eclipse mostrará el siguiente mensaje de
error y no permitirá compilar el proyecto:
Android incluye la biblioteca Apache de cliente HTTP (Apache HttpClient library) que
permite a las aplicaciones conectar con servidores Web de Internet.
215
Android también permite usar la librería estándar Red de Java Java Networking API
(paquete java.net), aunque, si usamos este paquete java.net, Android utiliza internamente la
librería de Apache.
Para que una aplicación Android acceda a Internet, es necesario declararlo en el fichero
AndroidManifest.xml, que requiere el permiso "android.permission.INTERNET".
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.
216
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
cargaPreferencias();
pagWeb.setText(ultimaUrl);
...
}
@Override
super.onPause();
preferenceEditor.putString(URL, pagWeb.getText().toString());
preferenceEditor.commit();
Para descargar la página de Internet hemos usado una tarea asíncrona que hemos
estudiado en la Unidad 3. En este caso hemos definido el método onProgressUpdate() para
ir actualizando la pantalla según se va descargando la página de Internet:
217
// Método onClick del botón Descargar
switch (view.getId()) {
case R.id.descargPagWeb:
ResultadoLabel.setText("");
else {
cargando.setVisibility(View.VISIBLE);
tarea.execute(pagWeb.getText().toString());
break;
} // end onClick
// Clase que descarga una página de Internet como una tarea asíncrona.
try {
// Obtenemos la respuesta
respuesta.getEntity().getContent()));
if (resultado.length()>1024) {
publishProgress(resultado);
resultado="";
} // end while
catch (Exception e) {
return null;
ResultadoLabel.append(values[0]);
cargando.setVisibility(View.INVISIBLE);
} // end onPostExecute
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:
ConnectivityManager cm = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
219
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
return true;
return false;
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE">
</uses-permission>
4.4.2 Conexión a través de proxy
Para configurarlo, haz clic en el icono "Ajustes" y establece las siguientes opciones:
220
4.4 QUÉ SON JSON (JavaScript Object Notation?
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.
{
"producto": {
"nombre": "Widget",
"compania": "ACME, Inc",
"numero": "7402-129",
"precios": [
{ "cantMin": 1, "precio": 12.49 },
{ "cantMin": 10, "precio": 9.99 },
{ "cantMin": 50, "precio": 7.99 }
]
}
}
221
Puedes ver más ejemplos en el siguiente enlace: json.org/example.html.
Twitter es una fuente muy grande que usa el formato JSON. En el Ejemplo 5 de esta
Unida vamos a cargar el Twitter de Mentor en una aplicación Android. La dirección es la
siguiente:
http://twitter.com/statuses/user_timeline/MinisterioEduc.json
Android incluye la biblioteca JSON, que permite tratar este formato de dato. Las
clases más importantes de este paquete son:
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.
222
Vamos a mostrar con un ejemplo práctico cómo usar una fuente de datos en formato
JSON en una aplicación Android. Si abrimos el fichero de código fuente del Ejemplo 5,
veremos las siguientes sentencias:
@Override
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if (datosCuentaTwitter!= null)
try {
datosAdaptador.add(jsonObjeto.getString("text"));
} // end for
} catch (Exception e) {
e.printStackTrace();
setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, datosAdaptador));
}
223
// String que permite ir añadiendo líneas
try {
if (statusCode == 200) {
String line;
builder.append(line);
} else {
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
224
e.printStackTrace();
return builder.toString();
} // end leeCuentaTwitter
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".
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.
try {
objeto.put("ciudad", "Avila");
} catch (JSONException e) {
e.printStackTrace();
System.out.println(objeto);
225
En Android existen tres formas de almacenar información para usarla en las
aplicaciones:
En Android, por defecto, los ficheros son privados y únicamente puede acceder
a ellos la aplicación que los crea.
Las preferencias de una aplicación son datos que una aplicación guarda y
recupera para personalizar la experiencia del usuario.
Para gestionar las preferencias de una aplicación hay que usar la clase
SharedPrefences, donde cada preferencia se almacena siguiendo la estructura
clave-valor.
226
Las preferencias se almacenan en formato XML en un fichero en la memoria del
dispositivo.
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 la biblioteca JSON que permite leer y escribir este formato de
dato.
227
INTENTS EN ANDROID
ÍNDICE
5.1 INTENTS EN ANDROID.......................................................... 231
5.1.1 Introducción ...........................................................................231
5.1.2 Intenciones (Intents) ..............................................................231
5.1.3 Ficheros Manifest ...................................................................232
5.1.4 Declarar capacidades de los componentes de las aplicaciones233
5.1.5 Uso de intenciones .................................................................234
5.1.6 Arranque explícito de una actividad.......................................234
5.1.7 Arranque implícito de una actividad ......................................235
5.1.7.1 Ejecutar subactividades .....................................235
5.1.8 Filtros de intenciones .............................................................239
5.1.9 Resolución de intenciones implícitas......................................240
5.1.10 Uso de intenciones para extender aplicaciones ....................241
5.1.1 Introducción
En esta Unidad vamos a explicar cómo usar Intenciones (Intents) en Android para
arrancar Actividades o servicios.
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.
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.
231
Las Intenciones son mensajes asíncronos entre componentes de aplicaciones que se usan
para realizar acciones e intercambiar datos, tanto en la petición como en la respuesta,.
De esta manera el usuario tiene la sensación de estar usando una única aplicación cuando, en
realidad, son componentes de varias.
Uno de los usos principales de las intenciones es arrancar, parar y cambiar entre las
actividades y los servicios de una aplicación.
Para que Android pueda iniciar un componente de una aplicación, el sistema debe
conocer que existe este componente. Ya hemos visto que, para ello, se declaran los
componentes de una aplicación en el fichero AndroidManifest.xml. Este fichero se encuentra
en el directorio raíz del proyecto Android.
232
Intents
Hay que declarar todos los componentes de la aplicación de esta forma usando las
siguientes etiquetas:
<activity>: Actividades
<service>: Servicios
233
Android identifica qué componentes pueden responder a una Acción determinada
buscándola en los filtros de intención (intent filters) que se declaran en el archivo
"AndroidManifest" de todas las aplicaciones del dispositivo.
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.
Para arrancar una Actividad sin esperar una respuesta de la subactividad iniciada,
debemos usar la siguiente función:
startActivity(anIntent);
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.
startActivity(intent);
234
Intents
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.
if (...) {
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.
Veamos un ejemplo sencillo que inicia una subactividad desde una actividad principal:
startActivityForResult(intent, SUBACTIVIDAD);
235
En la Intención hemos incluido una colección de datos Extras con información
adicional. Para ello, usamos el método putExtra() de la clase Intent para incorporar la
información adicional.
super.onCreate(bundle);
setContentView(R.layout.subactividad);
okButton.setOnClickListener(new View.OnClickListener() {
resultado.putExtra(TODO_CORRECTO, correcto);
resultado.putExtra(DATO_SELECCIONADO, datoSeleccionado);
setResult(RESULT_OK, resultado);
finish();
});
236
Intents
cancelarButton.setOnClickListener(new View.OnClickListener() {
setResult(RESULT_CANCELED, null);
finish();
});
@Override
switch(requestCode) {
case (SUB_ACTIVIDAD_UNA) : {
if (resultCode == Activity.RESULT_OK) {
break;
case (SUB_ACTIVIDAD_DOS) : {
if (resultCode == Activity.RESULT_OK) {
break;
238
Intents
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:
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.
<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.
@Override
super.onCreate(bundle);
setContentView(R.layout.main);
if (!noHayConexionInternet) startNextMatchingActivity(intent);
241
El método addIntentOptions() de la clase Menu permite especificar los datos sobre
los que puede operar una acción futura. Se especifican únicamente los datos, no la acción en
sí. Cuando Android resuelve la intención y devuelve una lista de acciones apropiadas para el
dato, se crea una nueva opción en el menú de la aplicación.
Muchas aplicaciones del sistema operativo emplean este mecanismo para extender su
funcionalidad a medida que nuevas actividades van implementando las acciones previstas.
Por ejemplo:
<activity android:name=".ActividadExtra">
<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
super.onCreateOptionsMenu(menu);
242
Intents
intent.setData(Dato.CONTENT_URI);
intent.addCategory(Intent.CATEGORY_SELECTED_ALTERNATIVE);
return true;
Lo primero que vamos a hacer es definir el fichero XML que incluye los diseños (layout)
de ambas actividades. Puedes encontrar este diseño en los ficheros del proyecto Android:
243
res/layout/main.xml: actividad principal.
res/layout/segundaactividad.xml: actividad secundaria o subactividad.
Después, creamos el código fuente Java para las dos actividades. La segunda
actividad, que se invoca desde la primera, muestra los datos recibidos e indica si devuelve
datos a la actividad principal. Veamos el código fuente de la actividad principal:
@Override
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tituloLbl.setText(contenido);
ModContactoBtn.setEnabled(false);
nombre="";
apellidos="";
244
Intents
switch (view.getId()) {
case R.id.AltaContactoBtn:
i.putExtra("operacion", "alta");
i.putExtra("nombre", "");
i.putExtra("apellidos", "");
break;
case R.id.ModContactoBtn:
i.putExtra("operacion", "modifica");
i.putExtra("nombre", nombre);
i.putExtra("apellidos", apellidos);
break;
startActivityForResult(i, COD_PETICION);
@Override
// Si la subactividad responde OK
if (resultCode == RESULT_OK) {
resultadoLbl.setText("");
if (data.hasExtra("nombre")) {
resultadoLbl.setText("Nombre: " +
data.getExtras().getString("nombre")+"\n");
nombre= data.getExtras().getString("nombre");
if (data.hasExtra("apellidos")) {
resultadoLbl.append("Apellidos: " +
data.getExtras().getString("apellidos")+"\n");
apellidos= data.getExtras().getString("apellidos");
245
}
if (!nombre.isEmpty() || !apellidos.isEmpty()) {
AltaContactoBtn.setEnabled(false);
ModContactoBtn.setEnabled(true);
} else {
AltaContactoBtn.setEnabled(true);
ModContactoBtn.setEnabled(false);
} else
@Override
super.onCreate(bundle);
setContentView(R.layout.segundaactividad);
246
Intents
operacion = extra.getString("operacion");
nombre.setText(valor1);
apellidos.setText(valor2);
if (operacion.equals("alta")) tituloLbl.setText("Alta de
contacto. Indica el nombre y los apellidos.");
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
247
super.finish();
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="es.mentor.unidad5.eje1.intencionexplicita"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".IntencionexplicitaActivity"
android:label="@string/app_name">
<intent-filter>
</intent-filter>
</activity>
android:name=".ActividadDos">
</activity>
</application>
</manifest>
248
Intents
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":
249
<uses-permission android:name="android.permission.CALL_PRIVILEGED"></uses-
permission>
<uses-permission android:name="android.permission.CALL_PHONE"></uses-
permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-
permission>
switch (view.getId()) {
case R.id.navegadorBtn:
startActivity(intent);
break;
case R.id.llamarTfnoBtn:
startActivity(intent);
break;
case R.id.marcarTfnoBtn:
startActivity(intent);
break;
case R.id.contactosBtn:
startActivity(intent);
break;
case R.id.selContactoBtn:
250
Intents
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
if (c.moveToFirst()) {
} // end RESULT_OK
En el código fuente anterior podemos ver que algunas actividades se inician con el
método startActivity() porque no queremos saber el resultado de la ejecución de la
subactividad. Sin embargo, el segundo botón "Seleccionar contacto" se inicia con el método
startActivityForResult() para obtener el resultado de la ejecución de la subactividad en el
método onActivityResult(). En la práctica lo que hacemos es obtener el contacto
seleccionado mediante un identificador URI de la base de datos de contactos del teléfono.
Desde Eclipse puedes abrir el proyecto Ejemplo 2 (Intención implícita) de la Unidad 5. Estudia
el código fuente y ejecútalo para mostrar en el AVD el resultado del programa anterior, en el
que hemos utilizado Intenciones invocadas de manera implícita.
251
Si ejecutamos la aplicación, veremos que al hacer clic en cualquiera de los botones de
la pantalla se inicia una nueva Actividad:
Fíjate que en el código fuente no especificamos la aplicación que tiene que lanzarse
para esa Acción, simplemente dejamos que Android decida qué aplicación es más apropiada
para la tarea solicitada.
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.
Para que Android sepa que hay una nueva Acción disponible en el sistema operativo,
tenemos que añadir la Actividad con la Acción correspondiente en el fichero
"AndroidManifest.xml":
252
Intents
<activity android:name=".NavegadorActivity"
android:label="@string/navegadorAct">
<intent-filter>
<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.
@Override
super.onCreate(savedInstanceState);
setContentView(R.layout.navegador);
try {
253
URL url = new URL(datos.getScheme(), datos.getHost(),
datos.getPath());
url.openStream()));
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.
254
Intents
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.
if (resolveInfo.size() > 0) {
return true;
}
return false;
}
255
5.3 PERMISOS Y SEGURIDAD EN ANDROID
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.
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.
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).
Todas las aplicaciones Android (archivos .apk) deben estar firmadas con un certificado
que identifica al autor de la aplicación. El propósito de los certificados de Android es distinguir
a los autores de las aplicaciones. Esto permite al sistema conceder o denegar solicitudes de
acceso para compartir la identidad de Linux que tenga otra aplicación. Es decir, si el
desarrollador es el mismo, es posible ejecutar ambas aplicaciones con el mismo usuario de
Linux y que compartan el mismo sandbox.
256
Intents
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.
Una aplicación básica de Android no tiene permisos asociados, por lo que no puede
hacer nada que afectara al usuario o a cualquier aplicación instalada en el dispositivo. Para
hacer uso de las características protegidas del dispositivo, se debe incluir en el fichero
AndroidManifest.xml del proyecto una o más etiquetas <uses-permission> que declaren los
permisos que necesita la aplicación.
En alguno de los ejemplos anteriores del curso ya hemos usado esta etiqueta para
poder ampliar la funcionalidad de la aplicación y acceder a determinados recursos del sistema
operativo.
257
Podemos escribir una o varias etiquetas <uses-permission> en el archivo
AndroidManifest.xml. El elemento <uses-permission> requiere que definamos el atributo
android:name, dentro del cual indicamos el nombre del permiso que requiere la aplicación.
Por ejemplo:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="es.mentor.miapp" >
<uses-permission android:name="android.permission.RECEIVE_SMS" />
...
</manifest>
En este caso, estamos manifestando que esta aplicación necesita poder recibir
mensajes cortos SMS para funcionar.
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.
258
Intents
hemos dicho, las aplicaciones de terceros pueden tener sus propios permisos. De forma
general, resumimos algunos de los permisos más utilizados:
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.
259
Para que funcione bien esta aplicación, debemos añadir la siguiente etiqueta en el
fichero AndroidManifest:
<uses-permission android:name="android.permission.VIBRATE"></uses-permission>
Para comprobar que la aplicación tiene asignado este permiso, hemos escrito las
siguientes sentencias en la Actividad:
PackageManager p = this.getPackageManager();
if (p.checkPermission("android.permission.VIBRATE",
"es.mentor.unidad5.eje3.permisos")==PackageManager.PERMISSION_DENIED) {
...
260
Intents
261
En este caso, en lugar de escribir la etiqueta <uses-permission>, utilizamos la
etiqueta <permission> para declarar un permiso al sistema. De igual forma, podemos definir
uno o varios permisos.
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" />
<activity android:name=".nombreActividad"
android:label="@string/app_name"
android:permission="es.mentor.ejemplo.VER_LISTADO">
<intent-filter>
</intent-filter>
</activity>
De esta forma, únicamente las aplicaciones que hayan solicitado el permiso indicado
podrán acceder al elemento de forma segura. En este contexto, el “acceso” significa lo
siguiente:
262
Intents
<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>
263
5.3.8 <Notas sobre seguridad en Android
Al compilar una aplicación no existe un mecanismo que detecte automáticamente los
permisos que necesita ésta. Es decir, si no asignamos bien los permisos en el fichero
AndroidManifest, veremos los errores en tiempo de ejecución. Por esto, debemos prestar
especial cuidado a la hora de declarar los permisos correctos y probar la aplicación en el
emulador de Android.
5.4 Tab.Layout
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.
264
Intents
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.
Si abrimos el fichero XML del layout, veremos el siguiente código que corresponde a
esta estructura:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="match_parent" android:layout_width="match_parent">
<TabHost android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent">
265
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TabWidget android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@android:id/tabs" />
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@android:id/tabcontent" >
<LinearLayout android:id="@+id/pest1"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView android:id="@+id/textView1"
android: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:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
266
Intents
</TabHost>
</LinearLayout>
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.
TabHost tabs=(TabHost)findViewById(android.R.id.tabhost);
// Preparamos su configuración
tabs.setup();
TabHost.TabSpec pestania=tabs.newTabSpec("mipestania1");
pestania.setContent(R.id.pest1);
267
pestania.setIndicator("Pestaña 1",
res.getDrawable(android.R.drawable.ic_menu_agenda));
tabs.addTab(pestania);
pestania=tabs.newTabSpec("mipestania2");
pestania.setContent(R.id.pest2);
pestania.setIndicator("Pestaña 2",
res.getDrawable(android.R.drawable.ic_menu_directions));
tabs.addTab(pestania);
268
Intents
tabs.setOnTabChangedListener(new OnTabChangeListener() {
@Override
tabId, 1).show();
}
});
269
Las Intenciones (Intents) permiten a las aplicaciones de Android expresar la
intención que desea ejecutar una acción sobre unos datos usando algún
componente de ésta o de otra aplicación.
Para crear una Intención hay que usar el objeto Intent de Android.
Las Intenciones son mensajes asíncronos que se usan para realizar acciones e
intercambiar datos, tanto en la petición como en la respuesta, entre componentes
de las aplicaciones.
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.
270
Intents
271
BASES DE DATOS Y XML
ÍNDICE
6.1 BASES DE DATOS .................................................................. 275
6.1.1 Introducción ...........................................................................275
6.1.2 Teoría sobre Bases de Datos ..............................................275
6.1.3 Ventajas de las bases de datos ..........................................279
6.1.4 Bases de datos relacionales................................................281
6.1.5 Diseño de bases de datos ...................................................283
6.1.1 Introducción
En esta Unidad vamos a repasar, como preámbulo , la teoría general sobre bases de
datos.
Android usa SQLite como motor de base de datos relacional. En el siguiente apartado
veremos sus características. La información que mostramos a continuación está basada en la
versión 3 de SQLite.
275
Generalmente, en las bases de datos relacionales, de las que hablaremos después, la
información está almacenada y organizada en ficheros formados por filas y columnas,
como puede verse en el ejemplo siguiente, en el que se presentan algunos datos de cinco
libros de una biblioteca:
Columnas
Fondo de Cultura
¿Tener o ser? Erich Fromm
Económica
Filas
Crónica de una Gabriel García
Bruguera
muerte anunciada Márquez
Hermann
El lobo estepario Anaya Editores
Hesse
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.
276
Introducción al entorno Android
cualquier extensión e, incluso, sin ella. En otros sistemas operativos, los archivos de una base
de datos de tipo SQLite suelen tener la extensión .sqlite.
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.
Campos
Registros 5
En las filas aparecen hasta once registros, cada uno de los cuales, en este caso,
contiene los cinco campos siguientes: Nombre, Sueldo, Fecha_nac, Observacion y Foto.
En el ejemplo anterior sólo se han incluido once registros y cinco campos, pero de
hecho en las bases de datos que vamos a usar el número de registros es ilimitado (depende
de la capacidad del soporte) y el de campos es muy amplio, según el tipo de base de datos
usada. Todos los registros tienen los mismos campos.
277
Cada campo de un registro tiene un nombre, un tipo, una longitud o ancho, un
número de decimales si es de tipo numérico o de coma flotante y un índice opcional.
Según el tipo de base de datos que se esté utilizando, el identificador del campo, la clase de
tipos y la longitud de los mismos pueden ser diferentes.
Vamos a centrarnos en los tipos de campos que define SQLite. Este tipo de base de
datos no define todos los tipos de campos típicos en bases de datos relacionales. Únicamente
define unos tipos de campos básicos y luego los reutiliza para especificar otros tipos de
campos.
El nombre de cada campo puede ser muy largo, si bien recomendamos que en el
orden práctico sea lo más breve posible y tenga algún significado. Debe atenerse a las reglas
de todos los identificadores ya comentadas anteriormente.
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.
278
Introducción al entorno Android
INT
TINYINT
SMALLINT
MEDIUMINT
BIGINT
UNSIGNED BIG INT
INT2
INT8
Todos estos tipos de campo de número entero se pueden definir al crear una tabla, si
bien, internamente, SQLite los traduce por afinidad al tipo INTEGER anterior.
En el caso del tipo de campo numérico con decimales, podemos usar los siguientes
tipos de campos:
DOUBLE
DOUBLE PRECISION
FLOAT
Todos estos tipos de campo de número con decimales se pueden definir al crear una
tabla, si bien, internamente, SQLite los traduce por afinidad al tipo REAL anterior.
3. Campo de tipo Fecha y Lógico. Puede contener fechas y tiempos (horas, minutos,
segundos) o almacenar valores lógicos (true / false). Admite índice. SQLite define el tipo de
campo interno NUMERIC para almacenar otros tipos de campos necesarios en las
aplicaciones en una tabla, tales como los campos lógicos o de fecha, así como los que
establecen los decimales exactos en un campo numérico. Podemos usar los siguientes tipos
de campos en SQLite:
DECIMAL(10,5)
BOOLEAN
DATE
DATETIME
Todos estos tipos de campo se pueden definir al crear una tabla, si bien, internamente,
SQLite los traduce por afinidad al tipo NUMERIC anterior.
4. Campo de tipo Memo. Es un campo de longitud variable que admite gran cantidad
de texto o datos binarios según nuestras necesidades. Para cada registro tendrá una longitud
distinta, según la cantidad de datos que se introduzcan en este campo. No admite índice.
SQLite admite únicamente BLOB.
Hemos dicho que los archivadores de una biblioteca o de una agenda pueden
considerarse, en cierta forma, bases de datos, pues en ellos se almacena información en un
279
determinado orden y es posible buscar esta información, consultarla, modificarla o eliminarla
con facilidad.
Sin embargo, todas estas operaciones suelen llevar mucho tiempo y, en ocasiones, no
se efectúan tan fácilmente como desearíamos. Además, ocupan bastante espacio si la
información es abundante. Incluso, en ocasiones, algunas operaciones fundamentales son
imposibles de realizar manualmente.
Por ejemplo, si tenemos 1.000 fichas bibliográficas ordenadas por autor y necesitamos
ordenarlas por título, la operación ha de realizarse manualmente, mirando una a una cada
ficha, lo cual puede hacerse muy largo y pesado. Podíamos haber escrito dos ejemplares de
cada ficha, uno para el archivo por autores y otro para el de títulos, pero esto hubiera llevado
el doble de tiempo, de trabajo y éstas ocuparían el doble de espacio.
Supongamos ahora que necesitamos seleccionar todas las fichas en las que aparece
la misma editorial. De nuevo la tarea puede parecernos pesada y larga, y lo es. No digamos si
se cambia la situación de los libros en los armarios de la biblioteca. También será necesario
modificar la signatura en las fichas.
Hemos puesto este ejemplo para explicar los graves problemas que se derivan
de la gestión manual de la información. Las dificultades aumentan a medida que crece el
volumen de información que debe manejarse y según sean los criterios de ordenación y
selección.
En 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.
280
Introducción al entorno Android
las diversas soluciones propuestas para resolver los problemas de estructuración y acceso a
dicha información.
Por ejemplo, en el gráfico siguiente puede observarse una tabla que contiene
diversos datos de personas:
Filas
José
(Registros) C/ Río Sil, 11 50 V Dependiente
Rodríguez
3
Columnas (Campos)
Como se ve, una tabla consta de filas y de columnas; en cada columna, denominada
campo en la base de datos, hay un dato: Nombre, Dirección, Edad, etcétera; cada fila es un
registro que contiene todos los datos de los elementos de la base. Cada tabla tiene un
281
registro especial, denominado cabecera, que contiene los nombres de los campos y sus
atributos (tipo y longitud).
Generalmente, una base de datos no consta de una sola tabla, sino de varias.
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 préstamos contendrá datos de este tipo: número de registro del libro
prestado, número de carné del lector, fecha del préstamo, plazo, etcétera.
Como puede verse, la información no debe repetirse en todos los ficheros, pero sí
debe poder relacionarse. Por ejemplo, los ficheros de libros y editoriales, tienen en común el
campo EDITORIAL. Los ficheros de libros y préstamos tienen en común, al menos, el
NÚMERO DE REGISTRO del libro prestado, gracias a lo cual desde uno se puede acceder a
los datos del otro. Los ficheros de lectores y préstamos tienen en común el campo CARNÉ,
etcétera.
282
Introducción al entorno Android
Son bases de datos relacionales Microsoft Access, Oracle, SQL Server, MySQL,
SQLite y otras.
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.
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:
283
• Determinar los datos que debe contener cada uno de esos elementos.
• 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.
• 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.
• Decidir cuál o cuáles van a ser los campos clave permanentes y situarlos al
principio de la estructura.
• Fijar los campos comunes a todos los ficheros para poder relacionarlos con otros
de la misma aplicación.
Preferencias de la aplicación
284
Introducción al entorno Android
En la Unidad 4 hemos tratado las dos primeras formas y en esta Unidad 6 veremos las
bases de datos.
SQLite es un motor de bases de datos relacional muy popular por sus características,
que son muy especiales, como las siguientes:
Es de código libre.
Usar bases de datos Android puede hacer más lentas las aplicaciones debido a que es
necesario escribir y leer información de la memoria física del dispositivo.
Por lo tanto, es recomendable realizar esta operaciones de forma Asíncrona, tal como hemos
estudiado en la Unidad 3 (Hilos). En los ejemplos de esta Unidad no vamos a incluir hilos, para
mostrar únicamente las sentencias de SQLite.
285
6.2.3 Creación de Bases de datos SQLite
La forma usual en Android de crear, modificar y conectar con una base de datos
SQLite consiste en usar la clase Java SQLiteOpenHelper. En realidad, debemos definir una
clase propia que derive de ella y personalizarla para adaptarnos a las necesidades concretas
de la aplicación.
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:
prestado: BOOLEAN
286
Introducción al entorno Android
@Override
db.execSQL(createBDSQL);
@Override
*/
db.execSQL(createBDSQL);
}
}
En el código fuente anterior se define la variable estática (en Java se definen así las
constantes) createBDSQL, donde se establece la orden SQL para crear la tabla llamada
Ejemplares con los campos alfanuméricos descritos anteriormente.
ATENCIÓN: en este curso no se describe la sintaxis del lenguaje SQL, pues se considera que
el alumno o alumna conoce cómo usar una base de datos relacional.
287
El método onCreate() se ejecuta automáticamente cuando es necesario crear la base
de datos, es decir, cuando aún no existe y se instala la aplicación por primera vez. Por lo
tanto, en este método debemos crear todas las tablas necesarias y añadir, si fuera necesario,
los registros iniciales.
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().
288
Introducción al entorno Android
Por último, cerramos la conexión con la base de datos llamando al método close().
BibliotecaSQLiteHelper bibliodbh =
// Modo escritura
SQLiteDatabase db = bibliodbh.getWritableDatabase();
if(db != null)
db.execSQL(SQLStr);
289
//Cerramos la base de datos
db.close();
/data/data/paquete_java/databases/nombre_del_fichero
/data/data/es.mentor.unidad6.eje1.crearbd/databases/DBBiblioteca.db
Desde Eclipse puedes abrir el proyecto Ejemplo 1 (Crear base de datos SQLite) de la
Unidad 6. Estudia el código fuente y ejecútalo para mostrar en el AVD el resultado del
programa anterior, en el que hemos utilizado los métodos de la base de datos SQLite.
Hemos visto que el fichero de la base de datos del Ejemplo 1 se ha creado en la ruta
correcta. Para comprobar que la tabla se ha creada correctamente y hemos insertado los
registros en la misma, podemos usar dos métodos:
290
Introducción al entorno Android
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.
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.
Tras obtener este identificador del emulador activo vamos a acceder a su shell
mediante el comando “adb -s <identificador-del-emulador> shell“.
Una vez conectados a la consola del emulador, podemos acceder a la base de datos
utilizando el comando sqlite3 e indicando la ruta del fichero de la base de datos; en el caso
del ejemplo debemos escribir
“sqlite3 /data/data/es.mentor.unidad6.eje1.crearbd/databases/DBBiblioteca.db“.
A continuación, debe aparecer el prompt de SQLite “sqlite>“, que nos indica que ya
podemos escribir consultas SQL sobre la base de datos.
291
Vamos a comprobar que se han insertado bien los cinco registros del ejemplo en la
tabla Ejemplares. Para ello, escribimos la siguiente orden: “SELECT * FROM Ejemplares;“.
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".
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.
292
Introducción al entorno Android
//Insertar un registro
db.execSQL("INSERT INTO Ejemplares (titulo, autor, anio, prestado)
VALUES ('Título', 'Autor', 2001, 'false')”);
//Eliminar un registro
//Actualizar un registro
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:
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:
293
Este método devuelve el campo ID del nuevo registro insertado o el valor -1 si ocurre
algún error durante la operación.
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.
valores.put("autor","Otro autor");
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:
db.delete("Ejemplares", "_id=2");
294
Introducción al entorno Android
Estos argumentos son piezas variables de la sentencia SQL, en forma de matriz, que
evitan tener que construir una sentencia SQL concatenando cadenas de texto y variables para
formar la orden final SQL.
Estos argumentos SQL se indican con el símbolo ‘?’ y los valores de dichos
argumentos deben pasarse en la matriz en el mismo orden que aparecen en la sentencia SQL.
Fíjate en el siguiente ejemplo:
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.
295
Tal y como hemos visto anteriormente en algunos métodos de modificación de datos,
también es posible incluir en este método una lista de argumentos variables que indicamos en
la orden SQL con el símbolo ‘?‘; por ejemplo, así:
Cursor c =
db.rawQuery(" SELECT autor,titulo FROM Ejemplares WHERE _id=? ", args);
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:
296
Introducción al entorno Android
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.
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:
if (c.moveToFirst()) {
297
//Recorremos el cursor mientras haya registros sin leer
do {
} while(c.moveToNext());
Como hemos comentado ya en esta Unidad, las bases de datos SQLite de una
aplicación son siempre privadas e internas a esta aplicación. Para que el resto de aplicaciones
pueda acceder a la información de la BD, Android define los Content Provider. En la Unidad
7 tratamos este tema en profundidad.
Además, se pueden tratar datos dinámicos de una base de datos usando la clase de
Android SQLiteQueryBuilder. Esta clase es similar a la interfaz de un proveedor de
contenidos, por lo que suele utilizarse conjuntamente con los Content Providers.
NOTA: Existen funcionalidades más avanzadas de gestión de BD con Android, como la
utilización de transacciones, pero no vamos a tratarlas en este curso por considerarse
programación avanzada.
6.3.2 Ejemplo práctico de BD SQLite con Android
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.
298
Introducción al entorno Android
Se trata de una aplicación donde un usuario puede gestiona notas sencillas por
categorías. Estas notas se almacenan en la base de datos "bdnotas.db" en la tabla "notas"
que tiene la siguiente estructura:
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.
299
public class NotasBDHelper extends SQLiteOpenHelper {
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
@Override
// Creamos la estructura de la BD
database.execSQL(BD_CREAR);
@Override
int newVersion) {
onCreate(database);
Basada en esta clase anterior vamos a definir la nueva clase NotasBDAdapter, que es
la encargada de hacer las consultas a la base de datos, borrar y actualizar registros de ésta.
300
Introducción al entorno Android
Dentro de esta clase hemos definido el método abrir(), que se conecta a la base de
datos utilizando la clase NotasBDHelper.
Para actualizar y dar de alta registros hemos usado un argumento del tipo
ContentValues, que hemos estudiado en el apartado anterior.
class NotasBDAdapter {
// Campos de la BD
this.contexto = context;
basedatos = bdHelper.getWritableDatabase();
return this;
bdHelper.close();
descripcion);
String descripcion) {
descripcion);
if (mCursor != null) {
mCursor.moveToFirst();
302
Introducción al entorno Android
return mCursor;
String descripcion) {
values.put(CAMPO_CATEGORIA, categoria);
values.put(CAMPO_TITULO, titulo);
values.put(CAMPO_DESCRIPCION, descripcion);
return values;
El alumno o alumna puede abrir estos ficheros en su ordenador y ver cómo están
implementados los distintos diseños.
303
6.3.5 Actividades
Como hemos comentado, la aplicación está formada por dos actividades: la actividad
principal (NotasActivity) muestra un listado con todas las notas y la segunda (GestionarNota)
sirve para editarlas o dar de alta una nueva.
@Override
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.getListView().setDividerHeight(3);
bdHelper.abrir();
cargaDatos();
registerForContextMenu(getListView());
@Override
inflater.inflate(R.menu.menulistado, menu);
304
Introducción al entorno Android
return true;
@Override
switch (item.getItemId()) {
case R.id.insertar:
startActivityForResult(i, ACTIVIDAD_NUEVA);
return true;
// El usuario hace clic en una opción del menú contextual del listado
@Override
switch (item.getItemId()) {
case MENU_ID:
bdHelper.borraNota(info.id);
cargaDatos();
return true;
return super.onContextItemSelected(item);
305
}
@Override
i.putExtra(NotasBDAdapter.CAMPO_ID, id);
startActivityForResult(i, ACTIVIDAD_EDITAR);
@Override
Intent intent) {
cursor = bdHelper.obtenerNotas();
startManagingCursor(cursor);
306
Introducción al entorno Android
// asociado al cursor
setListAdapter(notas);
@Override
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
@Override
super.onDestroy();
if (bdHelper != null) {
bdHelper.cerrar();
@Override
super.onCreate(bundle);
bdHelper.abrir();
setContentView(R.layout.editar_nota);
descripcionText = (EditText)
findViewById(R.id.nota_editar_descripcion);
filaId = null;
if (extras != null) {
filaId = extras.getLong(NotasBDAdapter.CAMPO_ID);
cargarRegistro();
aceptaBoton.setOnClickListener(new View.OnClickListener() {
308
Introducción al entorno Android
// Alta de registro
if (filaId == null) {
setResult(RESULT_OK);
// Acabamos la actividad
finish();
});
} // end onCreate
if (filaId != null) {
startManagingCursor(nota);
309
String s = (String) categoriaSpinner.getItemAtPosition(i);
if (s.equalsIgnoreCase(categoria)){
categoriaSpinner.setSelection(i);
break;
tituloText.setText(nota.getString(
nota.getColumnIndexOrThrow(NotasBDAdapter.CAMPO_TITULO)));
descripcionText.setText(nota.getString(
nota.getColumnIndexOrThrow(NotasBDAdapter.CAMPO_DESCRIPCION)));
} // end cargarRegistro
<activity android:name=".GestionarNota"
android:windowSoftInputMode="stateVisible|adjustResize">
</activity>
310
Introducción al entorno Android
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.
Los tres modelos más extendidos para leer y escribir ficheros de tipo XML son DOM
(Document Object Model), SAX (Simple API for XML) y StAX (Streaming API for XML):
311
DOM: vuelca el documento XML en la memoria del dispositivo en forma de
estructura de árbol, de manera que se puede acceder aleatoriamente a los
elementos de las ramas.
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.
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.
"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>
<language>es-ES</language>
312
Introducción al entorno Android
<item>
<link>http://link_de_la_noticia_2.es</link>
</item>
<item>
<link>http://link_de_la_noticia_2.es</link>
</item>
...
</channel>
</rss>
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.
313
Para implementar la Actividad principal, hemos usado la clase ListActivity, donde
mostraremos un listado con las noticias.
Para empezar, en primer lugar debemos definir una clase Java para almacenar los
datos leídos de una noticia. Para cargar el listado de la clase ListActivity con los titulares de
las noticias usamos una lista de objetos de este tipo. Veamos el código fuente de esta clase
que hemos denominado Noticia:
// Clase que sirve para cargar en un objeto cada noticia que leamos del
fichero XML
return enlace;
314
Introducción al entorno Android
try {
} catch (MalformedURLException e) {
while (!fecha.endsWith("00")){
fecha += "0";
this.fecha = fecha.trim();
return this.fecha;
return titulo;
this.titulo = titulo;
return descripcion;
this.descripcion = descripcion;
Por simplificación, hemos tratado todos los datos como cadenas de texto.
315
6.4.1 SAX es el modelo clásico en Android
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.
316
Introducción al entorno Android
return noticias;
@Override
super.startDocument();
@Override
if (localName.equals(EtiquetasRSS.ITEM)) {
@Override
throws SAXException {
if (this.noticiaActual != null) {
317
// Cargamos el campo correspondiente de la etiqueta que acabamos
de leer
if (localName.equals(EtiquetasRSS.TITLE)) {
noticiaActual.setTitulo(sbTexto.toString());
} else if (localName.equals(EtiquetasRSS.LINK)) {
noticiaActual.setEnlace(sbTexto.toString());
} else if (localName.equals(EtiquetasRSS.DESCRIPTION)) {
noticiaActual.setDescripcion(sbTexto.toString());
} else if (localName.equals(EtiquetasRSS.PUB_DATE)) {
noticiaActual.setFecha(sbTexto.toString());
} else if (localName.equals(EtiquetasRSS.ITEM)) {
noticias.add(noticiaActual);
sbTexto.setLength(0);
@Override
throws SAXException {
if (this.noticiaActual != null)
318
Introducción al entorno Android
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:
try
catch (MalformedURLException e)
try {
319
SAXParser parser = factory.newSAXParser();
parser.parse(getInputStream(), handler);
return handler.getNoticias();
} catch (Exception e) {
try
return feedUrl.openConnection().getInputStream();
catch (IOException e)
El constructor de la clase anterior acepta como parámetro una dirección URL del
documento XML que analiza y controla la validez de dicha dirección generando una
excepción si no puede crear la clase URL correspondiente.
Por otra parte, el método analizar() es el encargado de crear un nuevo parser SAX y
de iniciar el proceso de análisis pasando al parser una instancia del handler que hemos
creado anteriormente con una referencia al documento XML en forma de stream.
320
Introducción al entorno Android
// 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
noticias = analizador.analizar();
titulos.add(msg.getTitulo());
ArrayAdapter<String> adaptador =
this.setListAdapter(adaptador);
321
Primero creamos el parser correspondiente usando la dirección URL del documento
XML y, después, ejecutamos el método analizar() para obtener una lista de objetos de tipo
Noticia que, posteriormente, asignamos al adaptador del listado de la Actividad principal.
switch (tipo){
case SAX_CLASICO:
case DOM:
case SAX_SIMPLIFICADO:
case XML_PULL:
Observa que, como estamos usando la misma aplicación para mostrar cómo
funcionan todos los modelos de carga de archivos XML en Android, hemos creado una clase
abstracta que devuelve un objeto en función del tipo de analizador que el usuario ha decido
usar en ese momento.
NOTA: Para que esta aplicación Android acceda a Internet, es necesario declararlo en el
fichero AndroidManifest.xml, que requiere el permiso "android.permission.INTERNET".
322
Introducción al entorno Android
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:
// Variable que define la etiqueta raíz del XML que es <rss >
try
catch (MalformedURLException e)
/*
*/
item.setStartElementListener(new StartElementListener(){
});
item.setEndElementListener(new EndElementListener(){
noticias.add(noticiaActual);
});
324
Introducción al entorno Android
item.getChild(EtiquetasRSS.TITLE).setEndTextElementListener(new
EndTextElementListener(){
noticiaActual.setTitulo(body);
});
item.getChild(EtiquetasRSS.LINK).setEndTextElementListener(new
EndTextElementListener(){
noticiaActual.setEnlace(body);
});
item.getChild(EtiquetasRSS.DESCRIPTION).setEndTextElementListener(new
EndTextElementListener(){
noticiaActual.setDescripcion(body);
});
item.getChild(EtiquetasRSS.PUB_DATE).setEndTextElementListener(new
EndTextElementListener(){
noticiaActual.setFecha(body);
});
try {
Xml.parse(this.getInputStream(), Xml.Encoding.UTF_8,
root.getContentHandler());
} catch (Exception e) {
325
// Devolvemos las noticias leídas
return noticias;
try
return feedUrl.openConnection().getInputStream();
catch (IOException e)
}
}
En este nuevo modelo SAX simplificado de Android las acciones que debemos realizar
dentro de cada método se definen dentro de la misma clase asociadas a etiquetas concretas
del XML.
Para esto, lo primero que hacemos es navegar por la estructura del archivo XML hasta
encontrar las etiquetas que tenemos que tratar y asignarlaa a algunos métodos de tipo
listeners ("escuchadores") disponibles como StartElementListener() de inicio de etiqueta o
EndElementListener() de finalización de etiqueta, incluyendo las sentencias oportunas dentro
de estos métodos.
Por ejemplo, para obtener el elemento <item>, en primer lugar buscamos el elemento
raíz del XML (<rss>) declarando un objeto RootElement. Después, accedemos a su elemento
hijo <channel> y, finalmente, obtenemos de éste último el elemento hijo <item>. En cada
"salto" hemos utilizado el método getChild().
Una vez hemos llegado a la etiqueta buscada, asignamos los listeners necesarios. En
este caso, uno de apertura y otro de cierre de etiqueta item, donde inicializamos la noticia
actual y la añadimos a la lista final, respectivamente, de forma similar a como lo hemos hecho
para el modelo SAX clásico.
326
Introducción al entorno Android
Para 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.
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.
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.
<noticias>
<noticia>
<titulo>Título 1</titulo>
<enlace>Enlace 1</link>
</noticia>
<noticia>
<titulo>Título 2</titulo>
<enlace>Enlace 2</link>
</noticia>
<noticias>
327
Como vemos, este árbol conserva la misma información del fichero XML, pero en
forma de nodos y relaciones entre nodos. Por esta razón es sencillo buscar fácilmente dentro
de la estructura un elemento en concreto.
Este árbol se conserva en memoria una vez leído el documento completo, lo que
permite procesarlo en cualquier orden y tantas veces como sea necesario, a diferencia del
modelo SAX, donde el tratamiento es secuencial y siempre desde el principio hasta el final del
documento. Es decir, no se puede volver atrás una vez finalizada la lectura del documento
XML.
El modelo DOM de Android ofrece una serie de clases y métodos que permiten cargar
la información de la forma descrita y facilitan la búsqueda de elementos dentro de la estructura
creada.
try
328
Introducción al entorno Android
catch (MalformedURLException e)
try {
// Leemos el item i
329
if (nombre.equalsIgnoreCase(EtiquetasRSS.TITLE)){
noticia.setTitulo(contenido.getFirstChild().getNodeValue());
} else if (nombre.equalsIgnoreCase(EtiquetasRSS.LINK)){
noticia.setEnlace(contenido.getFirstChild().getNodeValue());
} else if (nombre.equalsIgnoreCase(EtiquetasRSS.DESCRIPTION)){
} else if (nombre.equalsIgnoreCase(EtiquetasRSS.PUB_DATE)){
noticia.setFecha(contenido.getFirstChild().getNodeValue());
} // end for j
noticias.add(noticia);
} // end for i
} catch (Exception e) {
return noticias;
try
return feedUrl.openConnection().getInputStream();
catch (IOException e)
330
Introducción al entorno Android
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.
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().
331
public class ParserXmlPull implements RSSParser {
try
catch (MalformedURLException e)
try {
parser.setInput(this.getInputStream(), null);
332
Introducción al entorno Android
switch (eventType){
case XmlPullParser.START_DOCUMENT:
break;
// Etiqueta de incicio
case XmlPullParser.START_TAG:
nombre = parser.getName();
if (nombre.equalsIgnoreCase(EtiquetasRSS.ITEM)){
if (nombre.equalsIgnoreCase(EtiquetasRSS.LINK)){
noticiaActual.setEnlace(parser.nextText());
} else if
(nombre.equalsIgnoreCase(EtiquetasRSS.DESCRIPTION)){
noticiaActual.setDescripcion(parser.nextText());
} else if
(nombre.equalsIgnoreCase(EtiquetasRSS.PUB_DATE)){
noticiaActual.setFecha(parser.nextText());
} else if (nombre.equalsIgnoreCase(EtiquetasRSS.TITLE)){
noticiaActual.setTitulo(parser.nextText());
break;
// Etiqueta de cierre
case XmlPullParser.END_TAG:
nombre = parser.getName();
if (nombre.equalsIgnoreCase(EtiquetasRSS.ITEM) &&
noticiaActual != null){
noticias.add(noticiaActual);
} else if (nombre.equalsIgnoreCase(EtiquetasRSS.CHANNEL)){
333
docAcabado = true;
break;
eventType = parser.next();
} // end while
} catch (Exception e) {
return noticias;
try
return feedUrl.openConnection().getInputStream();
catch (IOException e)
}
}
334
Introducción al entorno Android
Una vez identificado el tipo de evento, podemos consultar el nombre de la etiqueta del
elemento XML mediante parser.getName() y el texto contenido mediante parser.nextText().
distintos modelos de tratamiento de ficheros XML pulsando la tecla menú del emulador:
335
Si haces clic sobre una noticia verás que Android te permite seleccionar el navegador
que quieres usar para iniciar la acción Intent.ACTION_VIEW que permite abrir una página
Web:
336
Introducción al entorno 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.
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.
Los tres modelos más extendidos para leer y escribir ficheros de tipo XML son
DOM (Document Object Model), SAX (Simple API for XML) y StAX (Streaming API
for XML).
337
El modelo DOM vuelca el documento XML en la memoria del dispositivo en
forma de estructura de árbol, de manera que se puede acceder aleatoriamente a
los elementos de las ramas.
El modelo SAX se basa en eventos. La aplicación recorre todos los elementos del
archivo XML de una sola vez. La ventaja respecto a la anterior es que es más rápido
y requiere menos memoria, si bien no permite el acceso aleatorio a una de sus
ramas.
El modelo StAX es una mezcla de las dos modelos anteriores. En este caso,
también se lee el fichero XML de forma secuencial, pero podemos controlar la
forma en que se leen los elementos. Este modelo es también mucho más rápido
que DOM, pero algo más lento que SAX.
338
CONTENT PROVIDERS,
SERVICIOS Y
NOTIFICACIONES
ÍNDICE
7.1 CONTENT PROVIDERS ............................................................. 341
7.1.1 Introducción ................................................................... 341
7.1.2 Proveedores de contenido (Content Providers) ....... 341
7.1.3 Construcción de un Content Provider ........................ 342
7.1.1 Introducción
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.
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:
En la Unidad 5 ya hemos visto un ejemplo muy sencillo sobre del acceso a un Content
Provider ya existente, concretamente en la lista de contactos de Android.
341
Dado que es importante conocer el funcionamiento interno de un Content Provider,
antes de pasar a utilizarlo en nuestras aplicaciones, vamos a estudiar cómo se construye.
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.
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.
La aplicación del colegio almacena la información que queremos compartir en una base
de datos SQLite.
342
Content Providers, servicios y notificaciones
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.
343
public ColegioSqliteHelper(Context contexto, String nombre,
@Override
db.execSQL(sqlCreate);
String[] cursos={"1º ESO", "1º ESO", "2º ESO", "3º ESO", "1º ESO",
"4º ESO", "2º ESO", "2º ESO", "1º ESO", "4º ESO"};
@Override
344
Content Providers, servicios y notificaciones
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.
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.
A continuación, vamos a crear el Content Provider de la aplicación. Para esto, hay que
extender la clase ContentProvider. Esta clase dispone de los métodos abstractos siguientes ,
que podemos implementar:
345
• onCreate(): se usa para inicializar todos los recursos necesarios para el
funcionamiento del nuevo Content Provider.
"content://es.mentor.unidad7.ejemplo/alumnos";
private Alumnos() {}
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.
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:
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).
static {
347
En el código anterior vemos que mediante el método addUri() indicamos el campo
authority del URI, el formato de la entidad que estamos solicitando y el tipo que identifica el
formato del dato. Más adelante veremos cómo utilizar esto de forma práctica.
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:
SQLiteDatabase db = colegioBDhelper.getReadableDatabase();
348
Content Providers, servicios y notificaciones
if(uriMatcher.match(uri) == ALUMNOS_ID){
// Hacemos la consulta a la BD
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
// Variable temporal
int cont;
SQLiteDatabase db = colegioBDhelper.getWritableDatabase();
if(uriMatcher.match(uri) == ALUMNOS_ID){
// Actualizamos la tabla
return cont;
349
@Override
// Variable temporal
int cont;
SQLiteDatabase db = colegioBDhelper.getWritableDatabase();
if(uriMatcher.match(uri) == ALUMNOS_ID){
return cont;
SQLiteDatabase db = colegioBDhelper.getWritableDatabase();
return newUri;
}
350
Content Providers, servicios y notificaciones
Por último, sólo queda implementar el método getType(). Este método se utiliza para
identificar el tipo de datos que devuelve el Content Provider. Este tipo de datos se expresa
como un MIME Type, tal y como hacen los navegadores Web para determinar qué tipo de
datos se está recibiendo al hacer una petición a un servidor. Identificar el tipo de datos que
devuelve un Content Provider ayuda a Android a determinar qué aplicaciones son capaces de
procesar dichos datos.
En este ejemplo, existen dos tipos MIME distintos para cada entidad del Content
Provider: el primero se usa cuando se devuelve un registro único concreto y el segundo cuando
se devuelven varios registros simultáneamente. Así, podemos utilizar los siguientes patrones
para definir uno u otro tipo de datos:
• vnd.android.cursor.dir/vnd.mentor.alumno
• vnd.android.cursor.item/vnd.mentor.alumno
@Override
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;
<application android:icon="@drawable/icon"
android:label="@string/app_name">
...
<provider android:name=".ColegioContentProvider"
android:authorities="es.mentor.unidad7.ejemplo"/>
</application>
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.
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.
Primero definimos una matriz con los nombres de las columnas de la tabla que
queremos recuperar en el resultado de la consulta: ID, nombre, apellidos y curso.
352
Content Providers, servicios y notificaciones
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
Alumnos._ID,
Alumnos.COL_NOMBRE,
Alumnos.COL_APELLIDOS,
Alumnos.COL_CURSO };
ContentResolver cr = getContentResolver();
//Hacemos la consulta
Una vez solicitada la consulta, hay que recorrer el cursor para procesar los registros.
Veamos cómo queda el código fuente:
// Si obtenemos resultados
if (cur.moveToFirst())
String nombre;
String apellidos;
String curso;
353
int colNombre = cur.getColumnIndex(Alumnos.COL_NOMBRE);
txtResultados.setText("Resultado consulta:\n\n");
do {
nombre = cur.getString(colNombre);
apellidos = cur.getString(colApellidos);
curso = cur.getString(colCurso);
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);
ContentResolver cr = getContentResolver();
354
Content Providers, servicios y notificaciones
Hemos visto lo sencillo que resulta acceder a los datos proporcionados por un Content
Provider.
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.
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.
Las llamadas salientes son sencillas de realizar usando el emulador como si se tratara de
un teléfono normal y corriente. Accedemos al icono teléfono, marcamos un número y
descolgamos como si se tratara de un dispositivo físico:
355
Para simular llamadas entrantes debemos acceder desde Eclipse a la vista del DDMS. En
esta vista, en la pestaña “Emulator Control” aparece el apartado “Telephony Actions“, donde
podemos introducir un número cualquiera de teléfono origen “Incoming number” y pulsar el
botón “Call” para que el dispositivo del emulador reciba una llamada entrante.
Sin aceptar la llamada en el emulador, pulsaremos “Hang Up” para terminar la llamada
simulando así una llamada perdida.
Una vez hemos simulado tanto llamadas entrantes como llamadas salientes, vamos a
desarrollar una aplicación que consulte el historial de llamadas.
A continuación, definimos una matriz con las columnas que vamos a recuperar,
obtenemos la referencia al Content Resolver de la aplicación y ejecutamos la consulta
llamando al método query(). Por último, recorremos el cursor obtenido y procesamos los
resultados. Veamos el código fuente:
356
Content Providers, servicios y notificaciones
ContentResolver cr = getContentResolver();
if (cur.moveToFirst())
int tipo;
String telefono;
txtResultados.setText("");
do
tipo = cur.getInt(colTipo);
telefono = cur.getString(colTelefono);
if(tipo == Calls.INCOMING_TYPE)
tipoLlamada = "ENTRADA";
357
tipoLlamada = "SALIDA";
tipoLlamada = "PERDIDA";
// Mostramos la información
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>
358
Content Providers, servicios y notificaciones
Cuando una aplicación Android define sus propios Servicios, deben ser declarados en
el fichero AndroidManifest.xml del proyecto.
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,.
Una aplicación puede declarar su propio servicio para llevar a cabo operaciones que
tarden en ejecutarse y no necesiten interactuar con el usuario o para suministrar una nueva
funcionalidad a otras aplicaciones.
A continuación, se muestra un esquema con los métodos que invoca Android cuando
lanzamos un servicio según su modo de funcionamiento:
360
Content Providers, servicios y notificaciones
Una Actividad puede iniciar un servicio en modo Autónomo a través del método
StartService() y detenerlo mediante el método StopService(). Cuando lo hacemos, Android
invoca su método onCreate(); después, se invoca el método onStartCommand() con los datos
proporcionados por la Intención de la actividad.
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.
Hay casos en los que se usan mensajes de difusión (Broadcast) para comunicar
eventos entre servicios. Estos mensajes son, en realidad, Intents.
Este tipo de Intenciones se usa mucho para iniciar aplicaciones como el Administrador
de notificaciones (Notification Manager) y Administrador de alarmas (Alarm Manager).
361
Para enviar un mensaje de difusión mediante una Intención pendiente hay que usar su
método getBroadcast(). Para iniciar una subactividad mediante una Intención pendiente hay
que usar su método getActivity().
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>
362
Content Providers, servicios y notificaciones
@Override
if (extras != null) {
if (estado.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
363
7.4.6 Ejemplo de envío y recepción de mensajes internos en una aplicación y uso de
servicios por defecto de Android
@Override
vibrator.vibrate(2000);
364
Content Providers, servicios y notificaciones
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:
@Override
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if (texto.getText().equals("")){
Toast.LENGTH_LONG).show();
return;
365
int i = Integer.parseInt(texto.getText().toString());
// Cargamos el BroadcastReceiver
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+ (i * 1000), pendingIntent);
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.
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.
366
Content Providers, servicios y notificaciones
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.
Como ya hemos comentado, para crear un servicio debemos definir una clase que se
extienda de la clase Service de Android:
super.onCreate();
temporizador.scheduleAtFixedRate(new TimerTask() {
@Override
if (listado.size() >= 8) {
listado.remove(0);
listado.add(listadoDatos[indice++]);
indice = 0;
368
Content Providers, servicios y notificaciones
}, 0, INTERVALO);
@Override
super.onDestroy();
if (temporizador != null) {
temporizador.cancel();
@Override
return miBinder;
Servicio getService() {
return Servicio.this;
return listado;
369
Como vamos a usar el servicio en modo Ligado, hemos definido el método onBind() en
el código Java anterior.
<service android:name=".Servicio"></service>
private Servicio s;
s = ((Servicio.MiBinder) binder).getService();
Toast.LENGTH_SHORT).show();
s = null;
};
370
Content Providers, servicios y notificaciones
@Override
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
list.setAdapter(adaptador);
if (s != null) {
matrizAdaptador.clear();
matrizAdaptador.addAll(datos);
adaptador.notifyDataSetChanged();
Para conectar con el servicio definido en la clase Servicio, hemos escrito la sentencia:
371
bindService(new Intent(this, Servicio.class), miConexion,
Context.BIND_AUTO_CREATE);
• 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.
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.
372
Content Providers, servicios y notificaciones
373
Este mensaje no recibe el foco de la aplicación en ningún momento, es decir, no
interfiere con las acciones que esté realizando el usuario en ese momento.
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.
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.
Para comenzar, vamos a incluir un botón que muestre un Toast básico cuando
hagamos clic sobre él:
xDefectoBtn.setOnClickListener(new OnClickListener() {
@Override
// Creamos el mensaje
Toast toast1 =
// Mostramos el mensaje
toast1.show();
374
Content Providers, servicios y notificaciones
}
});
gravityBtn.setOnClickListener(new OnClickListener() {
@Override
// Indicamos el posicionamiento
toast2.setGravity(Gravity.CENTER|Gravity.RIGHT,0,0);
toast2.show();
375
}
});
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layoutToast"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
android:background="#555555"
376
Content Providers, servicios y notificaciones
android:padding="5dip" >
<ImageView android:id="@+id/imagen"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:src="@drawable/info" />
<TextView android:id="@+id/mensajeLbl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="#FFFFFF"
android:paddingLeft="10dip" />
</LinearLayout>
Para asignar este fichero de diseño (layout) a un Toast, hay que proceder de una
forma algo distinta a como lo hemos hecho en las anteriores notificaciones.
En primer lugar, hay que inflar el layout mediante un objeto LayoutInflater, como ya
hemos usado en varias ocasiones a lo largo del curso, para diseñar la interfaz de usuario. Una
vez construido el layout, modificamos los valores de los distintos componentes internos de éste
para mostrar la información.
layoutBtn.setOnClickListener(new OnClickListener() {
@Override
// Creamos el Toast
377
View layout = inflater.inflate(R.layout.layout_toast,
(ViewGroup) findViewById(R.id.layoutToast));
toast3.setDuration(Toast.LENGTH_SHORT);
toast3.setView(layout);
// Mostramos el Toast
toast3.show();
}
});
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.
Por ejemplo, cuando hay una llamada perdida en nuestro teléfono, se muestra en un
lado el siguiente icono en la barra de estado:
Arrastrar
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.
379
String ns = Context.NOTIFICATION_SERVICE;
// Creamos la notificación
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.
380
Content Providers, servicios y notificaciones
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;
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);
381
Si desplegamos la bandeja del sistema, podemos verificar el resto de información de la
notificación:
382
Content Providers, servicios y notificaciones
Si has utilizado alguna vez un dispositivo Android, te habrás dado cuenta de que
algunas aplicaciones permiten desplazar páginas deslizando el dedo horizontalmente sobre la
pantalla. Por ejemplo, en la aplicación del Android Market y en el visor de imágenes podemos
cambiar de página dentro de la misma aplicación:
383
Al desplazar el dedo se
cambia de pantalla.
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:
C:\cursos_Mentor\Android\android-sdk-windows\extras\android\support\v4
Una vez que hemos comprobado que tenemos las librerías extra de compatibilidad de
Android, procedemos a incluirlas en el proyecto.
En este proyecto hemos creado la carpeta "libs" y copiado dentro el archivo android-
support-v4.jar del directorio donde se encuentre la librería:
A continuación, añadimos la librería al Build Path haciendo clic con el botón derecho
del ratón sobre el archivo de la librería y eligiendo la opción "Build Path->Add to Build Path"
del menú desplegable:
385
Para comprobar que hemos incluido la librería correctamente en Eclipse, debe
aparecer como Librería referenciada ("Referenced Libraries"):
La aplicación que vamos a desarrollar consta de una Actividad que muestra un visor
sencillo de imágenes dentro del ViewPager. Para generar las páginas contenidas en este
ViewPager es necesario usar un objeto PagerAdapter, que se encarga de alimentar de
páginas al componente ViewPager.
// Variable de ViewPager
@Override
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
vPager.setAdapter(vPagerAdapter);
386
Content Providers, servicios y notificaciones
vPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
@Override
@Override
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#a4c639">
<android.support.v4.view.ViewPager
387
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/vPager"/>
</LinearLayout>
Como se trata de una Vista que se define en un paquete extra de Android, es necesario
incluir el nombre completo del mismo android.support.v4.view.ViewPager.
// Constructor de la clase
this.contexto=contexto;
this.nViews=nViews;
@Override
return nViews;
/**
388
Content Providers, servicios y notificaciones
*/
@Override
*/
//Orientacion vertical = 1
linearLayout.setOrientation(1);
tv.setTextColor(Color.WHITE);
tv.setTextSize(30);
imagen.setImageResource(resID);
params.setMargins(0, 0, 0, 20);
params.gravity=Gravity.CENTER;
linearLayout.addView(tv, params);
linearLayout.addView(imagen);
389
// Añadimos la página a la colección de páginas
((ViewPager) collection).addView(linearLayout,0);
return linearLayout;
} // end instantiateItem
/**
*/
@Override
/**
*/
@Override
return view==((LinearLayout)object);
/**
*/
@Override
390
Content Providers, servicios y notificaciones
/**
* Método que se invoca cuando Android indica que hay que recuperar el
estado de ejecución
*/
@Override
/**
* Método que se invoca cuando Android indica que hay que guardar el estado
de ejecución
*/
@Override
return null;
/**
*/
@Override
391
contexto con la orden contexto.getResources(); después, hemos buscado el ID del recurso de
la imagen usando el método getIdentifier(nombre_recurso, tipo_recurso,
paquete_recurso).
Arrastrar Arrastrar
392
Content Providers, servicios y notificaciones
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.
393
Los servicios propios de una aplicación se ejecutan en el hilo principal de su
proceso; por lo tanto, para no bloquear el hilo principal o de interfaz, debemos
ejecutar estos servicios con hilos de ejecución.
Para generar notificaciones en la barra de estado del sistema, hay que obtener una
referencia al servicio de notificaciones de Android usando la clase
NotificationManager.
Este componente ViewPager no forma parte de las clases por defecto del SDK de
Android. Está incluido en el paquete externo de Compatibilidad de Android.
394
ANDROID AVANZADO
ÍNDICE
8.1 INTRODUCCIÓN ................................................................................... 397
2
Android Avanzado
8.1 INTRODUCCIÓ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:
@Override
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
contador.count();
397
Object o = null;
o.toString();
}
}
return resultado;
resultado += i++;
}
}
Si ejecutas la aplicación tal y como está, verás que aparece el siguiente mensaje de error:
398
Android Avanzado
Si haces clic en el botón "Force close", la aplicación termina. Veamos cómo depurar
este programa que provoca un error.
Para establecer puntos de interrupción con Eclipse, hay que hacer clic en la opción
"Toggle Breakpoint" del menú desplegable que aparece si pulsamos el botón derecho del
ratón sobre el número de línea del código fuente correspondiente. También podemos hacer
doble clic en este número de línea para activar o desactivar esta opción:
399
8.2.2 Iniciar la depuración (Debug) del código
Para iniciar la depuración del código hay que hacer clic en la opción "Run->Debug" del
menú principal de Eclipse. También podemos usar la tecla rápida [F11] o usar el icono del
menú principal.
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.
400
Android Avanzado
Comando Descripción
La ejecución sigue todas las sentencias de todos los métodos o funciones que
F7 formen nuestro programa. Es decir, ejecuta en secuencia todas las órdenes que
conforman el programa.
Nota: también existen unos botones de acceso rápido que permiten ejecutar estas
órdenes. Observa la imagen siguiente:
401
Es posible también usar este menú para cambiar las columnas que han de aparecer en
esta vista:
Además, es posible utilizar la opción "New Detail Formater" (menú desplegable con el
botón derecho del ratón) para modificar la información mostrada sobre la variable. Por ejemplo,
como el texto (posición de memoria de una variable)
es.mentor.unidad8.eje1.depuracion.Contador@4051b760 no dice nada, podemos usar la
opción "New Detail Formater"
402
Android Avanzado
Si pulsas este botón otra vez, los puntos de interrupción se activarán de nuevo.
403
8.2.5 Propiedades de los puntos de interrupción
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:
404
Android Avanzado
405
8.2.9 Finalizar la Depuración del código
Para finalizar la depuración del código basta con cambiar la Perspectiva a "Java" de
nuevo. Cuando hagamos alguna modificación del código fuente, aparecerá el siguiente
mensaje para indicar que no se puede sustituir el código de una aplicación ya instalada en el
emulador de Android y se pregunta si deseamos desconectar ("Disconnect") el modo Debug:
Nota: en esta Unidad 8 puedes encontrar el vídeo “Cómo depurar aplicaciones Android en
Eclipse”, que muestra visualmente cómo llevar a cabo la depuración del Ejemplo 1 de esta
Unidad.
406
Android Avanzado
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:
407
Para acabar de crear el dispositivo virtual, hacemos clic en el botón "Create AVD".
Para poder utilizar la API de Google Maps es necesario obtener previamente una
clave de uso (API Key) que estará asociada al certificado con el que firmamos digitalmente las
aplicaciones. En el apartado "Permisos y Seguridad" de la Unidad 5 ya hemos hablado de
estos certificados, necesarios para firmar aplicaciones.
En primer lugar, hay que localizar el fichero donde se almacenan los datos del
certificado de depuración "debug.keystore". Podemos conocer la ruta de este fichero
accediendo a las preferencias de Eclipse, sección "Android", apartado "Build":
408
Android Avanzado
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:
409
A continuación, copiamos en el portapapeles el dato que aparece identificado como
“Huella digital de certificado (MD5)”.
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.
Para poder ver este proyecto en tu emulador Android es necesario que obtengas la clave de
uso de la API de Mapas de Google y la cambies en el fichero de diseño main.xml de la interfaz
de usuario. Si no lo haces, arrancará la aplicación del ejemplo pero no se mostrará el mapa,
como en la imagen siguiente:
410
Android Avanzado
Hay que tener en cuenta que, a la hora de crear el proyecto Android en Eclipse,
tenemos que seleccionar "Google APIs" en el campo "Build Target" en las propiedades del
proyecto:
411
<!-- Aquí se escribe la clave de uso de Google Maps -->
<com.google.android.maps.MapView
android:id="@+id/mapa"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:apiKey="xxxxxxxxxxxxxLIdwwbCEmC3DeN1omnaSkig"
android:clickable="true" />
Además, también hemos establecido el atributo clickable a true, para que el usuario
pueda interactuar con el componente si quiere, por ejemplo, desplazar el mapa con el dedo.
Los componentes MapView sólo se pueden utilizar desde una actividad de tipo
MapActivity. La clase MapActivity se extiende de la clase Activity y permite la gestión del
ciclo de vida de la Actividad y de los servicios de visualización de un mapas. De igual forma
que ListActivity se usa para mostrar listas, MapActivity se usa para mostrar mapas.
@Override
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mapa = (MapView)findViewById(R.id.mapa);
412
Android Avanzado
sateliteBtn = (Button)findViewById(R.id.SateliteBtn);
irBtn = (Button)findViewById(R.id.IrBtn);
animarBtn = (Button)findViewById(R.id.AnimarBtn);
moverBtn = (Button)findViewById(R.id.MoverBtn);
controlMapa = mapa.getController();
controlMapa.setCenter(loc);
controlMapa.setZoom(6);
mapa.setBuiltInZoomControls(true);
sateliteBtn.setOnClickListener(new OnClickListener() {
@Override
if(mapa.isSatellite())
mapa.setSatellite(false);
else
mapa.setSatellite(true);
});
irBtn.setOnClickListener(new OnClickListener() {
@Override
413
GeoPoint loc = new GeoPoint(latitud.intValue(),
longitud.intValue());
controlMapa.setCenter(loc);
controlMapa.setZoom(10);
});
animarBtn.setOnClickListener(new OnClickListener() {
@Override
controlMapa.animateTo(loc);
controlMapa.zoomIn();
});
moverBtn.setOnClickListener(new OnClickListener() {
@Override
controlMapa.scrollBy(1000, 50);
});
414
Android Avanzado
@Override
return false;
}
}
• setSatellite(true)
• setStreetView(true)
• setTraffic(true)
También existen otros tres métodos para consultar el estado de cada uno de estos
modos: isSatellite(), isStreetView() y isTraffic().
En el evento onClick del botón sateliteBtn hemos usado el método setSatellite() para
intercambiar el modo satélite y el estándar.
Además de los métodos para personalizar el aspecto gráfico del mapa, también
disponemos de varios métodos para consultar la información geográfica visualizada en el
mismo. Por ejemplo, podemos saber las coordenadas geográficas en las que el mapa está
centrado actualmente mediante el método getMapCenter() y el nivel de zoom que está
aplicando a través del método getZoomLevel().
415
Como podemos observar en el código anterior, las coordenadas del centro del mapa se
obtienen mediante el método getMapCenter() en forma de objeto GeoPoint que encapsula los
valores de latitud y longitud expresados en microgrados (grados * 1E6). Los valores en la
magnitud grados se pueden obtener mediante los métodos getLatitudeE6() y
getLongitudeE6() respectivamente.
El nivel de zoom del mapa contiene un valor entero entre 1 y 21, siendo 21 el que
ofrece mayor nivel de detalle en el mapa.
Para modificar el centro del mapa, en primer lugar, debemos acceder al controlador del
mapa (MapController) mediante el método getController(). Este método devuelve un objeto
MapController con el que podemos modificar la posición central del mapa. Para ello, podemos
usar los métodos setCenter() y setZoom() a los que podemos indicar las coordenadas
centrales del mapa y el nivel de zoom deseado, respectivamente.
En este ejemplo hemos incluido un botón irBtn que centra el mapa sobre un punto
determinado y hemos aplicado un nivel de zoom (10), que permite distinguir en el mapa
algunos detalle. Si pruebas el ejemplo del curso, verás que el desplazamiento a la posición y el
zoom al nivel indicados se hacen de forma instantánea sin ningún tipo de animación.
Para mejorar la sensación de movimiento en el mapa, la API de Google nos ofrece otra
serie de métodos que permiten desplazar el mapa a una posición específica de forma
progresiva y aumentar o disminuir el nivel de zoom de forma “animada”. El método
animateTo(GeoPoint) desplaza el mapa hasta un punto determinado y los métodos zoomIn()
y zoomOut() aumentan o disminuyen de forma progresiva, respectivamente, en 1 el nivel de
zoom. En el botón animarBtn hemos usado este método para desplazar de forma animada el
mapa.
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>.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="es.mentor.unidad8.eje2.mapas"
android:versionCode="1"
416
Android Avanzado
android:versionName="1.0" >
<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 >
</intent-filter>
</activity>
</application>
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.
417
8.4 DESARROLLO DE APLICACIONES SENSIBLES A LA ORIENTACIÓN
DEL DISPOSITIVO
Si has usado alguna vez un teléfono con Android, verás que, al cambiar la orientación
del mismo de vertical a horizontal y viceversa, normalmente se modifica el aspecto de la
aplicación que estás usando distribuyéndose las Vistas de la interfaz de usuario de forma
acorde.
Aunque a priori este cambio de orientación del dispositivo parece sencillo, a veces los
desarrolladores de aplicaciones Android deben desarrollar complejos códigos para controlarlo.
Este apartado describe cómo implementar esta funcionalidad.
418
Android Avanzado
• 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.
419
Por lo tanto, hay que probar el Ejemplo 3 de esta Unidad en otra versión de Android.
Tal y como hemos hecho para la versión 2.3.3 en la Instalación del curso, hay que descargar
las librerías de Android 2.2 y crear el dispositivo virtual correspondiente:
También puedes usar la versión de Android del curso teniendo en cuenta que puedes
cambiar al modo horizontal, pero no volver de nuevo al vertical.
Para definir el modo horizontal (landscape), hay que crear la carpeta res/layout-land.
Esta nueva carpeta contiene también el archivo main.xml:
420
Android Avanzado
<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:textAppearance="?android:attr/textAppearanceMedium" />
421
<Button
android:id="@+id/boton1"
android:layout_width="150px"
android:layout_height="60px"
android:layout_alignParentLeft="true"
android:layout_below="@+id/textView1"
android:layout_marginLeft="100dp"
android:layout_marginTop="93dp"
android:text="Botón" />
<EditText
android:id="@+id/editText"
android:layout_width="197dp"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/boton1"
android:layout_marginLeft="50dp"
android:layout_marginTop="150dp" />
</RelativeLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/relativeLayout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.85"
android:gravity="center_horizontal" >
<TextView
android:id="@+id/textView1"
422
Android Avanzado
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Button
android:id="@+id/boton1"
android:layout_width="150px"
android:layout_height="60px"
android:layout_alignParentLeft="true"
android:layout_below="@+id/textView1"
android:layout_marginLeft="100dp"
android:layout_marginTop="30dp"
android:text="Botón" />
<EditText
android:id="@+id/editText"
android:layout_width="197dp"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/boton1"
android:layout_marginLeft="50dp"
android:layout_marginTop="70dp" />
</RelativeLayout>
423
Si no creas este archivo para el modo horizontal y ejecutas la aplicación, verás que al
cambiar al modo horizontal desaparece el componente TextView:
424
Android Avanzado
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.
* este método.
*/
@Override
String texto="";
if ((orientation==Surface.ROTATION_90) ||
(orientation==Surface.ROTATION_270))
texto="vertical";
else texto="horizontal";
outState.putString("dato", texto);
super.onSaveInstanceState(outState);
* este método.
*/
@Override
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState.containsKey("dato"))
426
Android Avanzado
guardar información usando el objeto de tipo Bundle. No permite guardar estructuras de datos
más complejas, como objetos.
@Override
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
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
427
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="es.mentor.unidad8.eje3.orientacion"
android:versionCode="1"
android:versionName="1.0" >
<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 >
</intent-filter>
</activity>
</application>
</manifest>
428
Android Avanzado
* android:configChanges="orientation..."
* y debemos comentarlos.
*/
@Override
super.onConfigurationChanged(newConfig);
if (newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE) {
setContentView(R.layout.main);
} else {
setContentView(R.layout.main);
et = (EditText)findViewById(R.id.editText);
et.setText(texto);
429
En ocasiones, es necesario asegurarse de que una aplicación se muestra siempre en
una orientación concreta. Por ejemplo, muchos juegos sólo se visualizan bien en modo
horizontal. En este caso, mediante sentencias Java, se puede cambiar la orientación de la
pantalla con el método setRequestOrientation() de la clase de Activity:
@Override
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
et = (EditText)findViewById(R.id.editText);
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="es.mentor.unidad8.eje3.orientacion"
android:versionCode="1"
android:versionName="1.0" >
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".OrientacionActivity"
android:screenOrientation="landscape" >
<intent-filter >
430
Android Avanzado
</intent-filter>
</activity>
</application>
</manifest>
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.
Sin embargo, la teoría y funciones aquí expuestas sí son válidas para un dispositivo real que
funcionará correctamente según lo esperado.
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:
431
A continuación, arrancará el dispositivo virtual.
Además, el dispositivo real debe estar configurado para admitir la instalación de aplicaciones
sin firmar por el Android Market. Si accedes en Ajustes->Aplicaciones debes marcar la
siguiente opción:
432
Android Avanzado
Además, los dispositivos que aparezcan con la etiqueta "offline" están conectados,
pero no están disponibles al ADB (Android Debug Bridge).
433
Tras obtener este identificador del emulador, vamos a instalar la aplicación mediante el
comando “adb -s identificador-del-emulador install nombre-fichero-apk“. Fíjate en el
siguiente ejemplo:
434
Android Avanzado
En este apartado vamos a explicar los pasos para publicar una aplicación en el Android
Market.
La primera vez que accedemos a la página se muestra un asistente para dar de alta
una nueva cuenta de desarrollador en el Android Market. Introducimos los datos que se
solicitan (nombre del desarrollador, correo electrónico, URL del sitio Web y número de
teléfono). Después, pulsamos el enlace "Seguir":
Para poder darnos de alta como desarrolladores del Android Market y publicar
aplicaciones, hay que abonar 25,00$. Se trata de una cuota única sin caducidad. Para pagar
436
Android Avanzado
esta cuota podemos usar el servicio Google Checkout o pulsar en "Continuar" para pagar con
tarjeta de crédito:
437
Si todo está correcto, el asistente mostrará la siguiente ventana, indicando que "Su
pedido se ha enviado al Android Market". Para continuar con el proceso, pulsamos en el enlace
"Vuelve al sitio de desarrolladores de Android Market para completar el registro":
438
Android Avanzado
439
Los filtros de permisos permiten a una aplicación solicitar acceso a recursos de
Android. Ya hemos estudiado que si, por ejemplo, una aplicación requiere acceder a la cámara
de fotos, debemos indicarlo en el archivo AndroidManifest.xml:
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:
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.
• 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.
440
Android Avanzado
• 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.
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.
En primer lugar, una vez desarrollada y probada la aplicación Android con Eclipse,
hacemos clic con el botón derecho del ratón sobre la carpeta del proyecto y seleccionamos la
opción "Export" del menú emergente:
441
Abrimos la carpeta "Android" y seleccionamos "Export Android Application";
después, pulsamos el botón "Next":
442
Android Avanzado
443
• Alias: identificador de la clave.
• Password: contraseña de la clave, debemos guardarla o recordarla pues la
necesitaremos cada vez que vayamos a publicar una nueva aplicación o
actualizar una ya existente en el Android Market.
• Confirm: reescribimos la contraseña anterior.
• Validity (years): validez del certificado, al menos 25 años.
• First and Last Name: nombre del desarrollador o de la empresa.
• Organization Unit: departamento.
• Organization: nombre de la empresa.
• City or Locality: ciudad.
• State or Province: provincia.
• Country Code: código postal de la ciudad.
444
Android Avanzado
Si hemos seguido bien los pasos anteriores, ya dispondremos del fichero APK firmado
con el certificado que podemos publicar en el Android Market:
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.
https://market.android.com/publish/Home
445
Aparecerá una pagina donde podemos seleccionar el fichero APK pulsando en
"Examinar" para elegir el fichero APK de nuestra aplicación Android firmada con el certificado:
Si el paquete APK está correcto y cumple con todos los requisitos (versión de Android,
certificado, compilación, etcétera), el asistente muestra el botón "Guardar" y los datos del APK
(nombre de la aplicación, nombre de la versión, código de la versión, permisos que necesita,
funciones que necesita, tamaño, nombre de la clase Java). Pulsamos el botón "Guardar" para
almacenar la aplicación:
446
Android Avanzado
Tras subirlo, pulsamos en el enlace "Activar" para introducir los datos necesarios para
publicar la aplicación en el Android Market. Desde esta página podemos activar o desactivar la
publicación de las aplicaciones subidas. Por ejemplo, si hemos detectado algún error y no
queremos que los usuarios se descarguen una aplicación hasta solucionar el problema,
podremos desactivarla:
447
• Icono de la aplicación: la aplicación se identifica con un icono que aparece en
la parte izquierda de la pantalla del Android Market cuando los usuarios buscan
aplicaciones.
448
Android Avanzado
• Información de contacto:
o Sitio web.
o Correo electrónico.
o Teléfono.
En la siguiente ventana se muestra parte de los datos que hay que incluir:
Una vez introducidos los datos, pulsamos en el botón "Guardar" de la parte superior
derecha. A continuación, se comprueba si los datos son completos y correctos y, si no hay
errores, se guardarán los datos asociados al archivo APK.
449
Después, pulsamos en el botón "Publicar" (a la izquierda del botón "Guardar") para
publicar definitivamente la aplicación en Android Market:
450
Android Avanzado
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 paquete de instalación APK de una aplicación del Android Market debe estar
firmado con un certificado válido de al menos 25 años.
451