You are on page 1of 158

Curso Programación de

dispositivos móviles
IFC03CM12

Cuadernillo de prácticas

Pedro Pablo Gómez Martín

Julio, 2012
Documento maquetado con TEXiS v.1.0.

Este documento está preparado para ser imprimido a doble cara.


Curso Programación de
dispositivos móviles
IFC03CM12

Cuadernillo de prácticas

Julio, 2012
Copyright
c Pedro Pablo Gómez Martín
Índice

1. Repaso de Java y Eclipse 1


Pr. 1.1. Hola mundo en Java . . . . . . . . . . . . . . . . . . . . . 1

Pr. 1.2. Uso de múltiples cheros . . . . . . . . . . . . . . . . . . 2

Pr. 1.3. Creación de un Applet básico . . . . . . . . . . . . . . . . 3

Pr. 1.4. Ciclo de vida de un Applet . . . . . . . . . . . . . . . . . 5

Pr. 1.5. Applet con un botón . . . . . . . . . . . . . . . . . . . . 7

Pr. 1.6. Reacción al evento: clase externa . . . . . . . . . . . . . . 8

Pr. 1.7. Reacción al evento: clase anidada . . . . . . . . . . . . . 9

Pr. 1.8. Reacción al evento: clase anidada no estática . . . . . . . 10

Pr. 1.9. Reacción al evento: clase local . . . . . . . . . . . . . . . 12

Pr. 1.10. Reacción al evento: clases anónimas . . . . . . . . . . . . 15

Pr. 1.11. Reacción al evento: esquema habitual . . . . . . . . . . . 16

Pr. 1.12. Instalación de Eclipse . . . . . . . . . . . . . . . . . . . . 17

Pr. 1.13. Hola mundo en Eclipse . . . . . . . . . . . . . . . . . . . 19

Pr. 1.14. Depuración en Eclipse . . . . . . . . . . . . . . . . . . . . 20

Notas bibliográcas . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

En el próximo capítulo . . . . . . . . . . . . . . . . . . . . . . . . . 24

2. Primeros pasos con Android 25


Pr. 2.1. Instalación de las SDK de Android . . . . . . . . . . . . 26

Pr. 2.2. Instalación de plataformas Android . . . . . . . . . . . 27

Pr. 2.3. Creación de Android Virtual Devices . . . . . . . . . . . 28

Pr. 2.4. Explorando el AVD . . . . . . . . . . . . . . . . . . . . . 32

Pr. 2.5. Interacción entre dos AVDs . . . . . . . . . . . . . . . . . 33

Pr. 2.6. La consola de los AVD . . . . . . . . . . . . . . . . . . . 34

Pr. 2.7. Preparación de Eclipse: ADT . . . . . . . . . . . . . . . . 35

Pr. 2.8. El plugin Android Development Tool . . . . . . . . . . . 37

Notas bibliográcas . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

En el próximo capítulo . . . . . . . . . . . . . . . . . . . . . . . . . 38

3. Actividades: las ventanas de Android 39


v
vi Índice

Pr. 3.1. Hola mundo . . . . . . . . . . . . . . . . . . . . . . . . . 39

Pr. 3.2. Nivel de API mínimo y de construcción . . . . . . . . . . 43

Pr. 3.3. Interactuando con el usuario: botón Púlsame . . . . . . 44

Pr. 3.4. Ejecución en un dispositivo físico . . . . . . . . . . . . . 46

Pr. 3.5. LogCat: el registro en Android . . . . . . . . . . . . . . . 48

Pr. 3.6. Android Debug Bridge . . . . . . . . . . . . . . . . . . . 49

Pr. 3.7. Depuración de Android . . . . . . . . . . . . . . . . . . . 51

Pr. 3.8. Ciclo de vida de las aplicaciones . . . . . . . . . . . . . . 52

Pr. 3.9. Primera aproximación a los recursos: internacionalización 56

Pr. 3.10. Interfaces de usuario como XML . . . . . . . . . . . . . . 59

Pr. 3.11. Propiedades básicas de los controles . . . . . . . . . . . . 65

Pr. 3.12. Etiquetas . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

Pr. 3.13. Cuadros de texto . . . . . . . . . . . . . . . . . . . . . . 68

Pr. 3.14. Linear Layout . . . . . . . . . . . . . . . . . . . . . . . . 68

Pr. 3.15. ½Adivina mi número! . . . . . . . . . . . . . . . . . . . . . 70

Notas bibliográcas . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

En el próximo capítulo . . . . . . . . . . . . . . . . . . . . . . . . . 74

4. Un poco más sobre UIs 75


Pr. 4.1. RelativeLayout . . . . . . . . . . . . . . . . . . . . . . . . 75

Pr. 4.2. Botones de radio . . . . . . . . . . . . . . . . . . . . . . . 78

Pr. 4.3. Casillas de vericacion . . . . . . . . . . . . . . . . . . . 80

Pr. 4.4. ScrollView . . . . . . . . . . . . . . . . . . . . . . . . . . 81

Pr. 4.5. Mensajes rápidos: toasts . . . . . . . . . . . . . . . . . . 81

Pr. 4.6. Mensajes de alerta con botones . . . . . . . . . . . . . . 83

En el próximo capítulo . . . . . . . . . . . . . . . . . . . . . . . . . 84

5. Intents, chero de maniesto y componentes 85


Pr. 5.1. Una aplicación con dos ventanas . . . . . . . . . . . . . . 85

Pr. 5.2. AndroidManifest.xml: Actividades . . . . . . . . . . . . . 86

Pr. 5.3. Enviando datos a la segunda ventana . . . . . . . . . . . 90

Pr. 5.4. Recogiendo valores de la segunda ventana . . . . . . . . . 91

Pr. 5.5. Llamando a una actividad de otra aplicación . . . . . . . 93

Pr. 5.6. Invocaciones implícitas con Intents . . . . . . . . . . . . . 96

Pr. 5.7. Permisos . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

Pr. 5.8. Exportación e instalación . . . . . . . . . . . . . . . . . . 103

Pr. 5.9. Receptores de mensajes del sistema . . . . . . . . . . . . 104

Notas bibliográcas . . . . . . . . . . . . . . . . . . . . . . . . . . . 108

En el próximo capítulo . . . . . . . . . . . . . . . . . . . . . . . . . 108

6. Acceso a datos 109


Índice vii

Pr. 6.1. Reconstrucción de una actividad . . . . . . . . . . . . . . 109

Pr. 6.2. Mejorando el juego Adivina mi número . . . . . . . . . . 112

Pr. 6.3. Almacenando preferencias . . . . . . . . . . . . . . . . . 114

Pr. 6.4. Leyendo un chero de los recursos . . . . . . . . . . . . . 119

Pr. 6.5. Leyendo y escribiendo en cheros externos . . . . . . . . 122

Notas bibliográcas . . . . . . . . . . . . . . . . . . . . . . . . . . . 128

En el próximo capítulo . . . . . . . . . . . . . . . . . . . . . . . . . 128

7. Sensores y otro hardware 129


Pr. 7.1. Vibración . . . . . . . . . . . . . . . . . . . . . . . . . . . 129

Pr. 7.2. Sensores . . . . . . . . . . . . . . . . . . . . . . . . . . . 135

Pr. 7.3. Acelerómetro . . . . . . . . . . . . . . . . . . . . . . . . . 137

Pr. 7.4. Grácos en Android . . . . . . . . . . . . . . . . . . . . . 142

Pr. 7.5. Multitouch . . . . . . . . . . . . . . . . . . . . . . . . . . 145

Notas bibliográcas . . . . . . . . . . . . . . . . . . . . . . . . . . . 147

El próximo capítulo . . . . . . . . . . . . . . . . . . . . . . . . . . 148


Capítulo 1

Repaso de Java y Eclipse


Lo último que uno sabe es por dónde
empezar.
Blas Pascal

Resumen: En este capítulo repasaremos algunas de las características


del lenguaje Java que son de importancia para el resto del curso.

Práctica 1.1: Hola mundo en Java

Vamos a crear nuestro primer programa en Java. Abre cualquier editor


de texto, crea un chero nuevo llamado HolaMundo.java y escribe:

public c l a s s HolaMundo {

public s t a t i c void main ( S t r i n g [ ] args ) {


System . o u t . p r i n t l n ( " ½ H o l a mundo ! " ) ;
}

};

Para compilarlo, necesitamos el JDK (Java Development Kit), en con-


creto el compilador de Java, javac:

$ javac HolaMundo.java

Observa la aparición de un nuevo chero, HolaMundo.class. Para ejecu-


tar el código:

$ java HolaMundo
½Hola mundo!

1
2 Capítulo 1. Repaso de Java y Eclipse

Fíjate que ahora hemos usado java (no javac) que es el programa que
contiene la implementación de la JVM. Además, no hemos especicado la
extensión.

Práctica 1.2: Uso de múltiples cheros

Crea un nuevo chero Fecha.java:


public c l a s s Fecha {

public void construye ( int dia , int mes , int anyo ) {


_dia = d i a ;
_mes = mes ;
_anyo = anyo ;
}

public void escribe () {


System . o u t . p r i n t l n ( _dia + " / " + _mes + " / " + _anyo ) ;
}

private int _dia ;


private int _mes ;
private int _anyo ;

Crea un nuevo chero Main.java, que use el anterior:

public c l a s s Main {

public s t a t i c void main ( S t r i n g [] args ) {


F e c h a f = new F e c h a ( ) ;
f . construye (23 , 6, 1912);
System . o u t . p r i n t ( " La fecha es : ");
f . escribe ();
}

Compila éste último:

$ javac Main.java

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
3

Observa que se ha compilado automáticamente también la dependencia


y tendrás el chero Fecha.class.

Ejecuta el programa recién compilado:

$ java Main

Modica la clase Fecha.java, eliminando (o comentando) el método


escribe().

Compila esa clase Fecha (½no Main!):

$ javac Fecha.java

Se habrá regenerado el chero Fecha.class que ahora no tendrá el


método escribe(), que era usado por Main.java

Prueba otra vez a ejecutar el programa:

$ java Main

Práctica 1.3: Creación de un Applet básico

Crea un nuevo chero Java, AppletBasico.java, con el siguiente con-


tenido:

import java . a p p l e t . Applet ;

public c l a s s AppletBasico extends Applet {

public void init () {


add ( new j a v a . awt . L a b e l ( " H o l a mundo" ) ) ;
}

Compila el chero:

$ javac AppletBasico

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


4 Capítulo 1. Repaso de Java y Eclipse

Si lo intentas ejecutar, recibirás un error porque no tenemos método


main.

Un applet está pensado para ser ejecutado incrustado en una página


Web. Crea el chero AppletBasico.html con el contenido que aparece
a continuación:

<!DOCTYPE html>
<html>
<head>
< t i t l e >Mi a p p l e t</ t i t l e >
</ head>
<body>
<h1>Mi a p p l e t</ h1>
<applet code=" A p p l e t B a s i c o . c l a s s "
width=" 3 0 0 "
height=" 2 0 0 ">
</ applet>
</ body>
</ html>

Abre la página .html con un navegador. Si tiene habilitado Java, verás


el applet :

También puedes usar un visor de applets que proporciona el JDK. Le


tenemos que especicar el .html, aunque luego únicamente mostrará el
applet :

$ appletviewer AppletBasico.html

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
5

Práctica 1.4: Ciclo de vida de un Applet

Un applet no tiene método main. En su lugar se ejecuta en el contexto


del navegador, que crea un espacio para él, y llama a sus métodos cuando
se necesita. Se dice que los applets tienen un ciclo de vida controlado por
el contenedor. Los métodos con los que el applet recibe noticaciones de
cambios en su ciclo de vida son:

init(): llamado cuando el applet acaba de ser cargado por el contene-


dor. Es invocado una única vez, antes de la primera llamada a start().
Se utiliza para realizar inicializaciones básicas, como preparar el inter-
faz gráco, cargar recursos, crear hebras, etcétera.

start(): se llama para indicar que la ejecución comienza. Es invocado


tras init().

stop(): se llama para indicar que la ejecución se detiene (al menos


temporalmente). Ocurre cuando se minimiza la ventana.

destroy(): opuesto a init(), es invocado una única vez al nal de la


vida del applet, para pedirle que libere todos sus recursos antes de que
el contenedor lo elimine denitivamente. Normalmente se realizarán las
acciones inversas a las que se realizaron en init(), liberando todos los
recursos.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


6 Capítulo 1. Repaso de Java y Eclipse

Los métodos start() y stop() son simétricos, y se utilizan típicamente


para iniciar o detener efectos visuales o de sonido, de manera que el applet
no consuma recursos cuando esté minimizado.

La implementación de la clase Applet predenida tiene esos métodos


vacíos. Los applets concretos que se programan (que deben heredar de dicha
clase) sobreescribirán los métodos que les interesen, añadiendo el código que
necesiten.

Para probarlo, vamos a hacer un applet que escribirá por la salida están-
dar una cadena indicando que se ha invocado a cada uno de los métodos.
Ten en cuenta que esa salida estándar normalmente no se verá si el applet
se ejecuta directamente en el navegador.

Crea un chero nuevo, CicloVidaApplet.java con el contenido:

import java . a p p l e t . Applet ;

public c l a s s CicloVidaApplet extends Applet {

public void init () {


System . o u t . p r i n t l n ( " i n i t " ) ;
}

public void start () {


System . o u t . p r i n t l n ( " s t a r t " ) ;
}

public void stop () {


System . o u t . p r i n t l n ( " s t o p " ) ;
}

public void destroy () {


System . o u t . p r i n t l n ( " d e s t r o y " ) ;
}

Compila el código que acabas de crear.

javac CicloVidaApplet.java

Crea el .html contenedor del applet de una manera semejante al de la


práctica 1.3.

Lanza con appletviewer el applet y observa la consola. Verás aparecer


el texto init y start.
Minimiza y maximiza la ventana del applet varias veces. Comprueba
que aparecen stop y start adecuadamente.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
7

Cierra el visor, y comprueba que aparece destroy.

La aplicación appletviewer proporciona un menú con el que podemos


controlar el ciclo de vida para realizar pruebas durante el desarrollo. Expe-
rimenta con sus opciones.

Práctica 1.5: Applet con un botón

El JDK proporciona numerosos controles (o widgets ). Ya hemos visto


un tipo, las etiquetas, que se consiguen gracias a la clase java.awt.Label.
En esta práctica vamos a sustituir la etiqueta por un botón.

Crea un nuevo chero, AppletConBoton.java y escribe el siguiente


código:

import java . a p p l e t . Applet ;

public c l a s s Ap pl et Co nB ot on extends Applet {

public void init () {


j a v a . awt . B u t t o n boton ;
boton = new j a v a . awt . B u t t o n ( " Pulsame " ) ;
add ( b o t o n ) ;
} // i n i t

} // AppletConBoton

En este caso, la creación del botón e inclusión en el applet la hemos


separado en tres instrucciones. Podríamos habernos ahorrado código
resumiendo todo el código de init() con:

add(new java.awt.Button("Pulsame"));

Lo hemos separado en tres líneas porque más adelante haremos uso del
botón.

Compila la clase, crea el chero .html correspondiente, y lánzalo con


appletviewer.

Observa que la ventana muestra ahora un botón que reacciona al ratón,


aunque el programa no hace nada cuando se le pulsa.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


8 Capítulo 1. Repaso de Java y Eclipse

Práctica 1.6: Reacción al evento: clase externa

Las instancias de la clase java.awt.Button notican el evento de pulsa-


ción a los oyentes que se hayan registrado en ellas. En concreto, tendrán que
implementar el interfaz java.awt.event.ActionListener, y denir el mé-
todo public void actionPerformed(java.awt.event.ActionEvent e).
Para eso, podemos crear un chero nuevo MiActionListener.java que
implemente esa clase, y luego utilizarla desde la clase del applet. Para evitar
la proliferación de muchos cheros de código fuente, sin embargo, Java nos
permite denir en un mismo chero más de una clase, aunque sólo una podrá
ser pública.

Crea un nuevo chero llamado BotonClaseExterna.java y escribe el


siguiente código:

import java . a p p l e t . Applet ;

// C l a s e que manejará e l e v e n t o de p u l s a c i ó n d e l b o t ó n .
// Observa que e s t a c l a s e no e s p ú b l i c a ( no comienza con
// p u b l i c ) .
class MiActionListener
implements j a v a . awt . e v e n t . A c t i o n L i s t e n e r {

public void a c t i o n P e r f o r m e d ( j a v a . awt . e v e n t . A c t i o n E v e n t e) {


System . o u t . p r i n t l n ( " ½ P u l s a d o ! " ) ;
} // a c t i o n P e r f o r m e d

} // c l a s s M i A c t i o n L i s t e n e r

public c l a s s BotonClaseExterna extends Applet {

public void init () {


j a v a . awt . B u t t o n boton ;
boton = new j a v a . awt . B u t t o n ( " Pulsame " ) ;
// Línea nueva
boton . a d d A c t i o n L i s t e n e r ( new MiActionListener ( ) ) ;
add ( b o t o n ) ;
} // i n i t

} // c l a s s BotonClaseExterna

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
9

Compila la clase. Observa que aparecen dos cheros .class, uno para
cada una de las clases.

Crea el chero .html correspondiente, y lánzalo con appletviewer.


Pulsa el botón, y comprueba cómo aparece en la consola el texto
½Pulsado!.

Práctica 1.7: Reacción al evento: clase anidada

Con la solución anterior, la clase oyente está al mismo nivel que la del
applet, cosa que conceptualmente no es demasiado limpio. Además, podría-
mos querer acceder a atributos estáticos de la clase del applet desde el oyente,
y no lo podremos hacer a no ser que sean públicos.

Para solucionar las dos cosas, Java permite la denición anidada de clases.
En inglés se conoce como top-level nested classes and interfaces. A nosotros
sólo nos interesan aquí las clases.

Crea un nuevo chero llamado BotonClaseAnidada.java y escribe el


siguiente código:

import java . a p p l e t . Applet ;

public c l a s s BotonClaseAnidada extends Applet {

//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

// C l a s e anidada , d e n t r o de o t r a . Observa e l s t a t i c .
static class MiActionListener
implements j a v a . awt . e v e n t . A c t i o n L i s t e n e r {
public void a c t i o n P e r f o r m e d ( j a v a . awt . e v e n t . A c t i o n E v e n t e) {
// Accedemos a l a t r i b u t o p r o t e g i d o de l a c l a s e a l a
// que p er t en e ce m os .
System . o u t . p r i n t l n ( m e n s a j e ) ;
}
} // c l a s s M i A c t i o n L i s t e n e r

//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

public void init () {


j a v a . awt . B u t t o n miBoton = new j a v a . awt . B u t t o n ( " Pulsame " ) ;
miBoton . a d d A c t i o n L i s t e n e r ( new MiActionListener ( ) ) ;
add ( miBoton ) ;

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


10 Capítulo 1. Repaso de Java y Eclipse

} // i n i t

// Mensaje que queremos mostrar cuando s e p u l s e e l b o t ó n .


protected s t a t i c String m e n s a j e="½AY ! " ;

} // c l a s s BotonClaseAnidada

Las cosas a tener en cuenta son:

• La clase MiActionListener se encuentra ahora dentro de la clase


del applet.

• La clase del applet tiene un nuevo atributo estático protegido


mensaje con el texto que se quiere mostrar cuando se pulsa el
botón.

• La clase está denida como static.


• El método actionPerformed de la clase interna accede al atributo
estático, aunque sea protegido.

Compila la clase. Observa que aparecen dos cheros .class, pero ahora
el del listener tiene un nombre cualicado con el nombre de la clase a
la que pertenece (BotonClaseAnidada$MiActionListener.class).

Crea el chero .html correspondiente, y lánzalo con appletviewer.


Pulsa el botón, y comprueba cómo aparece en la consola el texto
½Pulsado!.

Práctica 1.8: Reacción al evento: clase anidada no estática

La siguiente evolución es que la clase anidada no sea estática (no tenga


el modicador static). En ese caso, la clase interna podrá acceder no sólo
a los atributos estáticos, sino también a los dinámicos. Aquí se dispara la
pregunta ¾de qué objeto?

La respuesta es sencilla. Las clases aninadas no estáticas (non-static inner


classes ) tienen una restricción que no tenían sus versiones estáticas: siempre
deben crearse en el contexto de un objeto de la clase externa. De hecho, no
se pueden crear instancias suyas fuera de la clase; sólo podrán crearse en
métodos de instancia de dicha clase externa. Con esta restricción, el objeto
al que pertenecen los atributos accedidos resulta natural: aquél que creó el
objeto.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
11

La posibilidad de acceder a atributos dinámicos no es especialmente útil


en el ejemplo que estamos utilizando. Sólo lo sería si quisiéramos tener varias
instancias de la clase externa, cada una con valores diferentes en el atributo
accedido desde la clase interna. Dado que la clase externa es el applet, y sólo
vamos a tener una instancia, resulta complicado poner un ejemplo sencillo
donde sea de utilidad. En cualquier caso, podemos verlo funcionando:

Crea un nuevo chero llamado BotonClaseAnidadaNoEstatica.java


y escribe el siguiente código:

import java . a p p l e t . Applet ;

public c l a s s BotonClaseAnidadaNoEstatica extends Applet {

//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

// C l a s e anidada , d e n t r o de o t r a . Observa que ahora no t i e n e


// e l s t a t i c .
class MiActionListener
implements j a v a . awt . e v e n t . A c t i o n L i s t e n e r {
public void a c t i o n P e r f o r m e d ( j a v a . awt . e v e n t . A c t i o n E v e n t e) {
// Accedemos a l a t r i b u t o dinámico ( no e s t á t i c o )
// p r o t e g i d o de l a c l a s e a l a que p e rt e ne c em o s .
System . o u t . p r i n t l n ( m e n s a j e ) ;
}
} // c l a s s M i A c t i o n L i s t e n e r

//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

public void init () {


j a v a . awt . B u t t o n miBoton = new j a v a . awt . B u t t o n ( " Pulsame " ) ;
miBoton . a d d A c t i o n L i s t e n e r ( new MiActionListener ( ) ) ;
add ( miBoton ) ;
}

// Mensaje que queremos mostrar cuando s e p u l s e e l b o t ó n .


protected String m e n s a j e="½AY ! " ;

} // c l a s s BotonClaseAnidadaNoEstatica

Las cosas a tener en cuenta son:

• Ahora la clase MiActionListener no está etiquetada como static.


• El atributo mensaje sigue siendo protegido, pero ahora es diná-
mico en lugar de estático.

• El método actionPerformed de la clase interna accede al atributo


no estático, incluso aunque sea protegido.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


12 Capítulo 1. Repaso de Java y Eclipse

Compila la clase. Observa que aparecen dos cheros .class, igual que
antes.

Crea el chero .html correspondiente, y lánzalo con appletviewer.


Pulsa el botón, y comprueba cómo aparece en la consola el texto
½Pulsado!.

Práctica 1.9: Reacción al evento: clase local

El paso siguiente es denir la clase dentro de un bloque de código. En


principio, es similar a las clases internas no estáticas anteriores, salvo que en
lugar de denirlo a nivel de la clase externa, se dene, por ejemplo, dentro de
un método. Ahora la clase no podrá ser visible fuera nunca (serán privadas al
bloque), por lo que no necesita especicar su visibilidad, no podrán crearse
instancias suyas fuera del bloque, y no tiene sentido que tengan miembros
estáticos.

Crea un nuevo chero llamado BotonClaseLocal1.java y escribe el


siguiente código:

import java . a p p l e t . Applet ;

public c l a s s BotonClaseLocal1 extends Applet {

public void init () {

// C l a s e l o c a l , p r i v a d a d e l método i n i t ( ) . No s e r á
// v i s i b l e f u e r a .
class MiActionListener
implements j a v a . awt . e v e n t . A c t i o n L i s t e n e r {
public void a c t i o n P e r f o r m e d (
j a v a . awt . e v e n t . A c t i o n E v e n t e) {
// Accedemos a l a t r i b u t o dinámico ( no e s t á t i c o )
// p r o t e g i d o de l a c l a s e a l a que p e r te n ec em o s .
System . o u t . p r i n t l n ( m e n s a j e ) ;
}
} // c l a s s M i A c t i o n L i s t e n e r

j a v a . awt . B u t t o n miBoton = new j a v a . awt . B u t t o n ( " Pulsame " ) ;


miBoton . a d d A c t i o n L i s t e n e r ( new MiActionListener ( ) ) ;
add ( miBoton ) ;
} // i n i t

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
13

// Mensaje que queremos mostrar cuando s e p u l s e e l b o t ó n .


protected String m e n s a j e="½AY ! " ;

} // B o t o n C l a s e L o c a l 1

La diferencia principal con la versión anterior es que ahora la clase


MiActionListener está dentro del método init() de la clase externa.
Compila la clase. Observa que aparecen dos cheros .class. Ahora
el nombre de la clase local es algo diferente, con un 1 de separación
(BotonClaseLocal1$1MiActionListener.class). El número se nece-
sita porque podríamos denir dentro de métodos diferentes clases lo-
cales con el mismo nombre, y es necesario un mecanismo que evite las
colisiones de nombres entre los .class. El número se iría incrementan-
do con la repetición del nombre de la clase.

Crea el chero .html correspondiente, y lánzalo con appletviewer.


Pulsa el botón, y comprueba cómo aparece en la consola el texto
½Pulsado!.

En principio, parece que las clases locales no aportan nada respecto a las
clases internas no estáticas anteriores. La diferencia que las hace especiales es
que pueden acceder a los atributos y variables locales del método donde se han
denido y creado, siempre que éstos sean final (constantes). Esto supone
un acercamiento a los cierres (closures ) de la programación funcional. Es
decir, si el método init() anterior tuviera una variable local (final), el
método actionPerformed() de la clase local podría acceder a él.
Esto es útil especialmente con los parámetros. Veamos un ejemplo que lo
demuestra. Para eso, el botón lo crearemos en un método nuevo que recibirá
parámetros, y lo invocaremos varias veces para poner varios botones en el
applet.

Crea un nuevo chero llamado BotonClaseLocal2.java y escribe el


siguiente código:

import java . a p p l e t . Applet ;

public c l a s s BotonClaseLocal2 extends Applet {

// Método que añade a l a p p l e t un b o t ó n con l a e t i q u e t a


// d e l primer parámetro , y que , a l s e r p u l s a d o ,
// e s c r i b i r á en l a s a l i d a e s t á n d a r e l segundo parámetro .
public void ponBoton ( S t r i n g texto , final String pulsado ) {
// C l a s e l o c a l a l método con e l a c t i o n l i s t e n e r .

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


14 Capítulo 1. Repaso de Java y Eclipse

class MiActionListener
implements j a v a . awt . e v e n t . A c t i o n L i s t e n e r {
public void a c t i o n P e r f o r m e d (
j a v a . awt . e v e n t . A c t i o n E v e n t e) {
// Accedemos t a n t o a l a t r i b u t o de l a c l a s e
// e x t e r n a , como a l parámetro ( g r a c i a s a que e s
// f i n a l ) .
System . o u t . p r i n t l n ( m e n s a j e + " : " + pulsado ) ;
}// a c t i o n P e r f o r m e d
} // c l a s s M i A c t i o n L i s t e n e r

j a v a . awt . B u t t o n miBoton = new j a v a . awt . B u t t o n ( t e x t o ) ;


miBoton . a d d A c t i o n L i s t e n e r ( new MiActionListener ( ) ) ;
add ( miBoton ) ;

} // ponBoton

public void init () {


// Ponemos t r e s b o t o n e s , cada uno con un t e x t o
// y un mensaje .
ponBoton ( "Uno" , "1" ) ;
ponBoton ( " Dos " , "2" ) ;
ponBoton ( " T r e s " , "3" ) ;
} // i n i t

protected String m e n s a j e="½AY ! " ;

} // c l a s s B o t o n C l a s e L o c a l 2

Las cosas a tener en cuenta son:

• Se ha añadido un método ponBoton() en el applet que añade un


botón. El método es llamado tres veces en init(), por lo que el
applet tendrá ahora tres botones.

• El método ponBoton() recibe dos parámetros. El primero indica el


texto de la etiqueta del botón. El segundo indica lo que queremos
que el botón muestre por la salida estándar al ser pulsado. Observa
que ese atributo es final (constante).

• El método contiene la clase local MiActionListener que en el


método actionPerformed accede al parámetro del método que
lo contiene. ½Esto es una cosa muy peculiar! Cuando el método
actionPerformed() sea llamado (el usuario haga click), en reali-
dad la ejecución del método ponBoton() ½habrá terminado hace
tiempo!

Compila la clase. Observa que aparecen dos cheros .class, y el nom-


bre de la clase local sigue el mismo esquema que antes.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
15

Crea el chero .html correspondiente, y lánzalo con appletviewer.


Pulsa los botones, y observa como el mensaje varía.

Práctica 1.10: Reacción al evento: clases anónimas

La última vuelta de tuerca es el uso de clases sin nombre. En el ejemplo


anterior estamos creando una clase llamada MiActionListener local al mé-
todo ponBoton() de la que luego únicamente creamos una instancia y no la
usamos más. Además, de esa clase, lo único que nos interesa es sobreescribir
un método.

Para abreviar la escritura de código, Java se amplió para soportar las


llamadas clases anónimas. Nos permite así hacer a la vez la creación de
la instancia (que pasamos en la invocación a addActionListener()) y la
denición de la clase (que no tendrá nombre). El código es un poco confuso en
principio, porque aparecen muchos signos de puntuación de cierre seguidos,
aunque termina resultando natural una vez que nos acostumbramos a esta
construcción.

Crea un nuevo chero llamado BotonClaseAnonima.java y escribe el


siguiente código:

import java . a p p l e t . Applet ;

public c l a s s BotonClaseAnonima extends Applet {

// Método que añade a l a p p l e t un b o t ó n con l a


// e t i q u e t a d e l primer parámetro , y que , a l s e r
// p u l s a d o , e s c r i b i r á en l a s a l i d a e s t á n d a r e l
// segundo parámetro .
public void ponBoton ( S t r i n g texto ,
final String pulsado ) {

j a v a . awt . B u t t o n miBoton = new j a v a . awt . B u t t o n ( t e x t o ) ;

// En l a l l a m a d a a a d d A c t i o n L i s t e n e r hacemos e l
// new y d e f i n i m o s l a c l a s e d i r e c t a m e n t e . Por
// nombre de l a c l a s e ponemos e l nombre de l a
// " s u p e r c l a s e " ( i n t e r f a z ) que vamos a
// implementar , l u e g o e l método , y cerramos
// todo .
miBoton . a d d A c t i o n L i s t e n e r (

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


16 Capítulo 1. Repaso de Java y Eclipse

new j a v a . awt . e v e n t . A c t i o n L i s t e n e r ( ) {
public void a c t i o n P e r f o r m e d (
j a v a . awt . e v e n t . A c t i o n E v e n t e) {
System . o u t . p r i n t l n ( m e n s a j e + " : " +
pulsado ) ;
} // a c t i o n P e r f o r m e d
}); // Cerramos l a d e f i n i c i ó n de l a c l a s e
// y d e l método
add ( miBoton ) ;
} // ponBoton

public void init () {


// Ponemos t r e s b o t o n e s , cada uno con un t e x t o
// y un mensaje .
ponBoton ( "Uno" , "1" ) ;
ponBoton ( " Dos " , "2" ) ;
ponBoton ( " T r e s " , "3" ) ;
} // i n i t

protected String m e n s a j e="½AY ! " ;

} // c l a s s BotonClaseAnonima

Las cosas a tener en cuenta son:

• Ahora la clase MiActionListener ya no existe. En su lugar, la


invocación a addActionListener() es mucho más complicada.
• En la invocación hacemos el new de una clase anónima. En lugar
del nombre de la clase, aparece su denición, indicando la super-
clase (o el interfaz) que implementa, y deniendo los métodos que
sobreescribe o proporciona.

• Desde la clase anónima podemos seguir accediendo a los atributos


del objeto que la ha creado, y también a los parámetros y variables
locales nales del método donde se dene.

Compila la clase. Observa que aparecen dos cheros .class. Como la


clase anónima no tiene nombre, se utiliza BotonClaseAnonima$1.class
como nombre del chero.

Crea el chero .html correspondiente, y lánzalo con appletviewer.

Pulsa los botones, y observa como el mensaje varía.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
17

Práctica 1.11: Reacción al evento: esquema habitual

En las prácticas anteriores hemos querido probar las características que


proporciona Java, y que fueron añadidas principalmente para la programa-
ción de interfaces grácas de usuario y la captura de eventos. No obstante,
los últimos usos que hemos hecho, con el uso del parámetro final, no es
demasiado habitual. En realidad, la construcción más normal es aquella en
la que la clase (en nuestro caso el applet ) tiene un método para procesar el
evento, que es invocado gracias a una clase anónima.

Puedes ver la idea en el código siguiente:

import java . a p p l e t . Applet ;

public c l a s s BotonFinal extends Applet {

public void init () {


j a v a . awt . B u t t o n miBoton = new j a v a . awt . B u t t o n ( " Pulsame " ) ;
// Creación de l a c l a s e anónima y e l p r o c e s a d o d e l
// e v e n t o . Llamamos a l método de l a c l a s e e x t e r n a que
// hará e l t r a b a j o .
miBoton . a d d A c t i o n L i s t e n e r (
new j a v a . awt . e v e n t . A c t i o n L i s t e n e r ( ) {
public void a c t i o n P e r f o r m e d (
j a v a . awt . e v e n t . A c t i o n E v e n t e) {
botonPulsado ( ) ;
}
}); // C i e r r e de l a c l a s e y l a l l a m a d a a l método
add ( miBoton ) ;
} // i n i t

// Método l l a m a d o cuando s e p u l s a e l b o t ó n d e l a p p l e t .
protected void botonPulsado ( ) {
System . o u t . p r i n t l n ( m e n s a j e ) ;
} // b o t o n P u l s a d o

// Mensaje que queremos mostrar cuando s e p u l s e e l b o t ó n .


protected String m e n s a j e="½AY ! " ;

} // BotonFinal

Práctica 1.12: Instalación de Eclipse

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


18 Capítulo 1. Repaso de Java y Eclipse

Eclipse es un entorno integrado de desarrollo muy popular en el mundo


de Java. Está disponible en http://www.eclipse.org. La instalación varía
de un sistema a otro. Nosotros haremos una instalación manual sobre nuestro
sistema GNU/Linux, aunque es probable que la propia distribución (Debian,
Ubuntu, Max) ya tenga Eclipse paquetizado.

En la página hay disponibles varias versiones; debemos obtener la mar-


cada como Eclipse IDE for Java Developers, que contiene todo lo nece-
sario para desarrollar en Java. Una vez descargado, se obtiene un chero
eclipse-java-<version>.tar.gz. Descomprímelo en un directorio conoci-
do (la descompresión creará automáticamente el directorio eclipse donde
se meterán los archivos):

$ tar xfz $rutaDescarga/eclipse-java-<version>.tar.gz .


$ ./eclipse &

Cuando lo lanzamos, aparece la splash window durante la carga:

Luego nos pregunta la ruta donde queremos tener nuestro espacio de


trabajo. En el directorio que elijamos se guardarán los proyectos.

Al lanzarse eclipse, nos muestra el workspace vacío. Podemos abrir un


resumen de las características de Eclipse, las características nuevas de la

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
19

versión, ejemplos de proyectos, un tutorial de uso... Lo que queremos es co-


menzar a trabajar, y para eso pulsamos sobre el icono de la derecha (Work-
bench).

Podremos volver a esta ventana en cualquier momento con Help - Wel-


come.

Práctica 1.13: Hola mundo en Eclipse

Vamos a crear un primer proyecto en Java con eclipse:

Ve a File - New - Java Project

Establece como nombre del proyecto HolaMundo y pulsa Next.

Recorre las opciones predenidas brevemente, y pulsa Finish.

Al volver, verás en el Package explorer el nuevo proyecto recién creado:

Tiene una carpeta src vacía.

Tiene una carpeta JRE System Library donde se enumeran las libre-
rías disponibles en el JRE que se usará para lanzar la aplicación.

Vamos a crear el chero de código fuente.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


20 Capítulo 1. Repaso de Java y Eclipse

Pulsa sobre la carpeta src con el botón derecho, y selecciona New -


Class. También puedes hacerlo con el menú de la aplicación.

Como nombre de la nueva clase pon HolaMundo. Observa el resto de las


opciones y sus valores predenidos. Verás aparecer un aviso de Eclipse
indicando que no es recomendable poner las clases en el paquete por
defecto (vacío). Como es una prueba, no nos preocuparemos.

Activa la casilla de vericación para que Eclipse nos cree el método


main, y pulsa Finish.

Verás aparecer el esqueleto del código de la nueva clase. Escribe en el


main:

System.out.println("½Hola mundo!");

Para lanzar la aplicación, pulsa sobre el botón Play de la barra de


herramientas, o en el menú Run - Run (Ctrl-F11). En la parte inferior
de Eclipse, donde se muestra la consola de ejecución, aparece el texto,
y el programa termina.

Mira en el directorio del proyecto y observa la aparición del che-


ro HolaMundo.class en el directorio bin. Eclipse se ha encargado de
compilar la clase por nosotros.

Práctica 1.14: Depuración en Eclipse

Aunque para esta práctica no resulte de interés, vamos a ver brevemente


las capacidades de depuración de Eclipse. Modica el código ligeramente
para conseguir:

public c l a s s HolaMundo {

public s t a t i c saluda ( String nombre ) {

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
21

System . o u t . p r i n t l n ( " ½ H o l a " + nombre + " ! " ) ;


}

public s t a t i c void main ( S t r i n g [ ] args ) {

saluda ( " Asterix " ) ;


saluda ( " Obelix " ) ;

Vamos a utilizarlo para probar el depurador de Eclipse:

Haz doble click sobre el lateral izquierdo del código, para añadir un
punto de ruptura (breakpoint ). También puedes colocar el cursor so-
bre la línea y pulsar <Ctrl>-<Mays>-B. Verás aparecer un circulito
indicando la existencia del punto de ruptura.

Lanza la ejecución en depuración. Para eso puedes pulsar F11, o utilizar


el icono en la barra de herramientas:

Verás aparecer un aviso indicando que para depurar es preferible usar

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


22 Capítulo 1. Repaso de Java y Eclipse

la perspectiva de depuración, y nos pregunta si queremos pasar a ella.


Acepta la propuesta.

Date cuenta de que la ejecución se ha detenido en la línea donde pusiste


el punto de ruptura.

Pulsa F5, el icono Step into de la barra de herramientas, o la opción


del menú Run - Step Into.

Observa que el punto de parada ahora está en la primera línea del


método saluda(String). Además, en la ventana Debug donde está la

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
Notas bibliográcas 23

pila de llamadas verás los dos métodos (main y saluda) que están a
mitad de su ejecución.

Coloca el ratón sobre el identicador nombre del código. Verás aparecer


una descripción de su contenido (Asterix).

Pulsa de nuevo F5 (Step into ). Eclipse comenzará a ejecutar la invo-


cación al System.out, entrando (y deteniéndose) en un código que no
es nuestro. Para ejecutar hasta terminar el método actual, pulsa F7
(Step Return ), o usa el botón de la barra de herramientas, cercano al
de Step Into.

Vuelve a pulsar F7 para terminar la ejecución de la primera invocación


a saluda. Comprueba en la ventana Console la aparición del texto
½Hola Asterix!.

El programa habrá vuelto al main. Date cuenta de que en la ventana


Debug ha desaparecido el método.

Pulsa F6 (Step Over ) o el correspondiente icono en la barra de he-


rramientas para ejecutar la línea actual completamente, sin detenerse
en la ejecución paso a paso de las invocaciones. No entraremos en el
método saluda, que se ejecutará completamente.

Habrá aparecido el segundo saludo. Pulsa F8 (Resume ) para ejecutar


1
sin volverse a detener .

Notas bibliográcas
La cantidad de documentación disponible sobre Java es inmensa. Para
comenzar, quizá un buen punto de partida sean los tutoriales proporcionados
por Oracle (http://docs.oracle.com/javase/tutorial/index.html) o, si
se preeren los libros tradicionales, la última edición del conocido Piensa en
Java de Bruce Eckel.
Los applets vivieron su explendor a nales de la década de 1990 y prin-
cipios de la del 2000. Hoy su uso es minoritario, habiendo sido desplazados
por los modos de interacción con el navegador que han proporcionado las

1
Este comando apenas es útil en este ejemplo porque estábamos terminando, y no
habría diferencia con usar cualquiera de los demás comandos. Sin embargo en situaciones
más complejas es muy práctico para relanzar la ejecución hasta el siguiente punto de
ruptura.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


24 Capítulo 1. Repaso de Java y Eclipse

págianas dinámicas y JavaScript. En este capítulo nos ha interesado princi-


palmente su ciclo de vida, y el hecho de que se ejecutan integrados en un
navegador. Se puede encontrar más información sobre ellos por ejemplo en
http://docs.oracle.com/javase/tutorial/deployment/applet/.
También en los tutoriales de Oracle hay una sección dedicada especial-
mente a las clases anidadas (http://docs.oracle.com/javase/tutorial/
java/javaOO/nested.html).
Eclipse es un IDE ampliamente utilizado por los desarrolladores de Java.
Sin embargo, en ocasiones puede ser bastante complejo, debido a su tama-
ño y versatilidad. Afortunadamente, incluye una ayuda relativamente rica,
que incluye incluso tutoriales para iniciarse en su uso. En el sitio web del
programa (http://www.eclipse.org) también se pueden encontrar algunos
tutoriales.

En el próximo capítulo. . .
En el próximo capítulo daremos nuestros primeros pasos con Android,
instalando el software necesario para desarrollar aplicaciones para él, y ana-
lizando sus herramientas principales.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
Capítulo 2

Primeros pasos con Android


Si no cuidas bien tu pasos nunca sabes
hacia dónde te pueden llevar.
El señor de los Anillos, J.R.R. Tolkien

Resumen: En este capítulo daremos nuestros primeros pasos con


Android, instalando el software necesario para desarrollar aplicaciones

para él.

Android es una pila software para dispositivos móviles. Incluye el sistema


operativo, un middleware, y una serie de aplicaciones básicas. Su origen se
remonta a Octubre de 2003, año de la creación de Android Inc., que nació
para desarrollar dispositivos móviles más inteligentes, que fueran conscientes
de la localización y preferencias de sus usuarios.

En Agosto de 2005 es comprada por Google, dejando ver su intención


de entrar en el mercado de la telefonía móvil. En noviembre de 2007 se
anuncia la creación de la Open Handset Alliance, un consorcio de empresas
(Google, HTC, Intel, Samsung, Motorola...) que persigue el desarrollo de
estándares abiertos para dispositivos móviles. El mismo día se anuncia el
primer producto: la plataforma Android para dispositivos móviles basada en
Linux. Aunque normalmente se dice que Android es de Google, formalmente
Android es desarrollado por el Android Open Source Project que, eso sí, es
liderado por Google.

Las características más destacadas son:

Código abierto

Gran cantidad de servicios: acceso al hardware (acelerómetro, GPS,


...), base de datos relacional, ...

Aplicaciones hechas de componentes

25
26 Capítulo 2. Primeros pasos con Android

Capacidades multimedia

Seguridad (entre aplicaciones y para el usuario)

Gestión del ciclo de vida automático

Ejecución en hardware variado

Programación en Java (o en C/C++, aunque menos recomendable)

Google mantiene el Android Market predenido

La arquitectura de Android se resume en la siguiente gura:

Práctica 2.1: Instalación de las SDK de Android

Para poder desarrollar aplicaciones para Android necesitamos herramien-


tas especícas que nos permitan compilar el código, empaquetar la aplica-
ción, y visualizarla en un teléfono software (un emulador), o enviarla a un
dispositivo físico.

Todas esas aplicaciones se proporcionan en las SDK (software develop-


ment kit ) de Android, disponibles en http://developer.android.com/sdk/.
La instalación es sencilla. En GNU/Linux basta con descomprimir el -
chero descargado (.tgz) en algún lugar donde podamos escribir con el usua-
rio actual:

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
27

$ tar xfz android-sdk_<version>-linux.tgz

Se creará el directorio android-sdk-linux en la ruta actual, y se des-


comprimirá ahí todo el contenido.

En realidad lo que conseguiremos con esta instalación son únicamente las


herramientas básicas de las SDK. Pero con ellas aún no podremos desarrollar
nada. Necesitamos descargarnos lo que se denominan  Android platforms ,
que contiene la información especíca de la versión concreta de Android
para la que queremos desarrollar. Por tanto, lo que acabamos de instalar es
prácticamente un gestor de descargas, y un emulador de terminales Android
vacío al que le falta el propio Android que debe ejecutar.

Si miras lo que acabas de instalar encontrarás:

add-ons: de momento es un directorio vacío. En él se guardarán las


ampliaciones a versiones concretas de Android (proporcionadas por
terceros) que hayamos instalado.

platforms: también vacío, contendrá el software de las diferentes ver-


siones de Android que instalemos. El emulador de teléfonos Android
ejecutará ese software.

tools: contiene las herramientas básicas para la gestión de las SDK.


El más importante es android.

Práctica 2.2: Instalación de plataformas Android

Para poder comenzar a hacer algo, necesitamos las platform SDK y la


información sobre alguna plataforma Android (alguna de las versiones).

Para eso:

Ejecuta el gestor de las SDK:

$ ./android

Espera mientras se actualiza la información. Necesitarás tener conexión


a Internet.

En la lista que aparece en la parte central de la ventana, se enumera


el software disponible para ser descargado. Hay tres grandes tipos:

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


28 Capítulo 2. Primeros pasos con Android

• Tools : incluye dos paquetes. El primero es Android SDK Tools,


que son las SDK propiamente dichas que acabas de instalar. Si
pasado el tiempo aparece una versión nueva de las SDK, podrás
actualizar tu versión directamente ahí. El segundo son las An-
droid SDK Platform-tools, con herramientas necesarias para el
desarrollo. Deberás instalarlo obligatoriamente para poder reali-
zar aplicaciones.

• Android x.x.x (API X) : por cada versión de Android aparecerá


una carpeta, para podertela descargar. Es lo que se denomina la
Android platform. Por defecto, se te habrá marcado para instala-
ción la más reciente.

• Extras : contiene paquetes adicionales. El más importante es el


driver USB (sólo para Windows) que añade compatibilidad con
muchos móviles Android (aunque no con todos).

Asegúrate de que marcas al menos Android 2.3.3 (nivel de API 10)


y Android 1.5 (nivel de API 3). Marca también las Android SDK
platform-tools. Para Android 2.3.3 puedes dejar únicamente los tres
primeros paquetes (SDK Platform, Samples for SDK y Google APIs)
y descartar los demás. Ten en cuenta que la instalación requiere la des-
carga previa, por lo que la instalación puede necesitar mucho tiempo
si se eligen muchos niveles de API.

Pulsa Install packages para comenzar la instalación. Te aparecerá una


ventana informando de la licencia de los paquetes que has seleccionado,
y las dependencias. Marca Accept All, y pulsa Install.

Verás el proceso de descarga e instalación.

Cuando termine, vuelve a recorrer el directorio donde instalaste las


SDK. Notarás que dentro de platforms hay un subdirectorio por ca-
da plataforma Android que hayas instalado. También habrán apareci-
do algunos directorios nuevos (platform-tools, samples, temp) y en
add-ons habrá también algunas APIs nuevas de Google.

Práctica 2.3: Creación de Android Virtual Devices

Antes de comenzar a desarrollar nada, crearemos emuladores de teléfo-


nos sobre los que ejecutar nuestros programas. También podremos utilizar
dispositivos físicos, pero es más cómodo utilizar emuladores software, al me-
nos en las primeras etapas del desarrollo.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
29

En las SDK de Android los teléfonos virtuales se conocen como AVD


(Android Virtual Device ). Cada AVD está compuesto de:

Perl hardware (capacidades): si tiene acelerómetro, cámara, teclado...

Imagen del sistema: qué software ejecuta (versión de Android).

Opciones adicionales: piel (aspecto para imitar un terminal físico), di-


mensiones, si se puede hacer snapshot, ...

Almacenamiento dedicado en el disco duro antrión para guardar las


aplicaciones instaladas en el terminal, la tarjeta de memoria (si tiene),
etcétera.

1
En esencia, es similar a una máquina virtual , pero ejecutándose sobre
un hardware emulado particular (el de los teléfonos), y sobre él el software
de Android en la versión que elijamos.

A nivel hardware, se emula:

CPU ARMv5 y su unidad de gestión de memoria (MMU)

Pantalla LCD de 16 bits.

Uno o más teclados.

Chip de sonido con entrada y salida.

Particiones de la memoria ash (emulado con cheros en el disco duro


antrión).

Módem GSM con su tarjeta SIM (falsa).

Aunque el emulador resulta increíblemente útil para el desarrollo (entre


otras cosas, para poder probar la aplicación sobre diferentes plataformas An-
droid sin necesidad de tener un dispositivo físico de cada una), tiene algunas
limitaciones. Entre otras cosas, no es posible:

Hacer o recibir llamadas reales, aunque se puede simular que se hacen


y se reciben.

Realizar conexiones USB.

Hacer uso de la cámara: aunque se pueda simular su existencia de una


cámara, no se mapea a una cámara real del antrión.

Detección de la conexión y desconexión de auriculares.

1
De hecho, el emulador de las SDK está basado en QEmu

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


30 Capítulo 2. Primeros pasos con Android

Determinar el nivel de carga de la batería o si se está cargando.

Detectar la inserción y retirada de tarjetas SD

Comunicación por Bluetooth

Vamos a crear nuestro primer teléfono. Para eso, lanza el gestor de AVDs
con:

$ android avd

Inicialmente no aparece ningún dispositivo. Pulsa el botón New y verás


un nuevo cuadro de diálogo con varias opciones:

Name : como nombre pon Android2.3

Target : indica la plataforma Android destino que llevará el AVD que


estamos creando. Observa que aparecen únicamente aquellas platafor-
mas que hayas instalado. Selecciona Android 2.3.3 (nivel de API 10).

CPU/ABI : indica el procesador y el ABI (application binary interface )


que se usará. No podemos cambiarlo; el único valor posible es ARM,
que es la familia de procesadores que llevan la inmensa mayoría de los
teléfonos móviles.

SD Card : si queremos dotar al AVD de tarjeta de memoria, y su tama-


ño. Por debajo, se creará un chero con ese tamaño, que se mapeará a
la unidad virtual del teléfono, de manera similar a los cheros de discos
del software de virtualización. Simularemos una tarjeta de 128 MB.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
31

Snapshot : si queremos o no soportar snapshots. Si lo activas, el AVD


podrá ser hibernado, ahorrándote el apagado y encendido cada vez.
Dado que iniciar un AVD lleva mucho tiempo, es una característica
muy útil, por lo que actívala.

Skin : es la piel (aspecto) del teléfono (y también su resolución de


pantalla). Las SDK traen unos predenidos con un móvil prototipo
de cada plataforma. Se pueden crear pieles propias, por lo que en In-
ternet circulan pieles de teléfonos reales. La piel nos marca también la
resolución de pantalla.

Hardware : qué dispositivos tiene. Podemos añadir hardware nuevo pul-


sando New. Se debe tener en cuenta que los dispositivos ya tienen un
hardware predenido (dependiendo de qué plataforma se haya escogi-
do). Por tanto, por ejemplo, no es necesario añadir el acelerómetro,
porque lo trae de serie. Si queremos quitarlo, entonces también hay
que pulsar a New (por muy poco intuitivo que resulte), escogerlo en la
lista, y modicar el campo  Value  por No.

Cuando termines la conguración, pulsa Create AVD, y lo verás aparecer


en la lista. Pulsa Start... y verás aparecer un nuevo cuadro de diálogo. El
signicado de las casillas de vericación es el siguiente:

Scale display to real size: a partir de los datos de tamaño del monitor,
intenta que la representación del móvil tenga un tamaño real similar
al dispositivo físico que se está emulando.

Wipe user data: elimina los datos de usuario que se hayan introducido
tras la creación del AVD. Es como reformatearlo.

Launch from snapshot: lanza el AVD partiendo de un snapshot previo,


es decir de la suspensión previa. Sólo estará habilitado si al crear el
AVD activamos la posibilidad de guardarlos. En la primera ejecución
no hay snapshot previo, por lo que no tiene efecto.

Save to snapshot: si queremos que cuando cerremos el AVD se guarde


su estado a disco para recuperarlo más rápidamente.

Lancemos nuestro AVD por primera vez:

Asegúrate de que marcas Save to snapshot.

Pulsa Launch ...

... y siéntate a esperar.

Aparecerá una ventana con la pantalla del teléfono, y una serie de


botones y un teclado que representa la entrada del teléfono.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


32 Capítulo 2. Primeros pasos con Android

Sin hacer nada, ciérralo.

La información de los AVD no se almacenan en la ruta de las SDK, para


permitir una instalación multiusuario. En su lugar, se guardan en la
carpeta personal del usuario, .android/avd en el caso de GNU/LInux.
Ve a ese directorio y observa los cheros que han aparecido.

Lanza de nuevo el AVD. En esta ocasión desmarca la casilla de veri-


cación de Save to snapshot, pero dejando marcada la de Launch from
snapshot. Con esto conseguimos varias cosas:

• Iniciaremos el AVD más deprisa, en el último estado guardado.

• El apagado del AVD es más rápido al no necesitar volcar el snaps-


hot.

• Como no guardamos el estado, la próxima vez que lancemos el


AVD estará limpio, sin las pruebas que hayamos podido hacer.

Antes de continuar, crea un segundo AVD, de nombre Android1.5, con esa


versión de la plataforma y la conguración predenida (recuerda marcar la
casilla de vericación Snapshot al crear el AVD, para que se puedan guardar).
Lánzalo, de una manera similar a la anterior. Observa que la piel ahora es
diferente.

Práctica 2.4: Explorando el AVD

Si no has tenido acceso previo a un dispositivo con Android, familiarizate


con su uso. Lanza algunas aplicaciones y pruébalas (por ejemplo, las de
API demo, proporcionadas con las SDK de Android). Fíjate que sólo puede
haber una aplicación en primer plano, y que no hay botones para maximizar,
minimizar o cerrar. A continuación se describen algunas de las teclas que
puedes utilizar en el emulador:

Escape: volver (atrás)

Inicio: home. Vuelve al escritorio

Ctrl-F11/Ctrl-F12 o teclas 7 y 9 del teclado numérico (con el num-lock


desactivado ) rotan el dispositivo

Ctrl-F5/Ctrl-F6 o teclas + y - para cambiar el volumen

F8: des/habilitar GSM (llamadas)

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
33

Alt-Enter: pantalla completa

F3: botón de llamar

Práctica 2.5: Interacción entre dos AVDs

Si no los tienes ya, lanza ambos AVDs (Android1.5 y Android2.3). Fíjate


que en cada uno de ellos aparece un número como título de la ventana, jun-
to al nombre. Por ejemplo 5554:android2.3 o 5556:android1.5. Podemos
considerar ese número como el identicador temporal (en realidad es un
número de puerto).

Fíjate en el número asociado al AVD de Android1.5 que tengas lanzado.

Ve al AVD de Android 2.3 y pulsa F3 para entrar en las llamadas.


También puedes utilizar el botón de llamada del lado derecho de la
ventana, o simular el uso del teléfono y pulsar sobre el icono.

Como número de teléfono, escribe el identicador del AVD de An-


droid 1.5 (por ejemplo, 5556).

Ve al AVD de Android 1.5 y observa la llamada entrante, junto con un


número de teléfono, que termina con el identicador del AVD llamante.
Descuelga (pero no esperes recibir sonido...).

Vuelve al Android 2.3 y observa que se ha detectado que se ha contes-


tado la llamada, y aparece el temporizador.

Cuelga en cualquiera de los dos teléfonos, y observa que se detecta el


n de la llamada en el otro.

También podemos enviar mensajes cortos (SMS) entre AVDs:

En Android 1.5, escoge la aplicación Messaging y pulsa New message.

En el campo To, especica el número asociado al AVD de Android


2.3.

En el asunto pon cualquier texto. Como es un dispositivo emulado,


puedes usar el teclado del ordenador, en lugar del del AVD.

Ve al AVD con Android 2.3 y observa la recepción del mensaje.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


34 Capítulo 2. Primeros pasos con Android

Por último, podemos hacer uso de la agenda. Añade en cualquiera de los


dos teléfonos el contacto del identicador del otro, y repite alguna de las dos
operaciones.

Ten en cuenta que el identicador es temporal, pudiendo cambiar en


ejecuciones posteriores, por lo que meterlo en la agenda no es especialmente
útil. En cualquier caso, estos cambios desaparecerán si desmarcaste Save to
snapshot al lanzar los AVD.


Práctica 2.6: La consola de los AVD

El identicador que veíamos antes es en realidad un puerto TCP (del


antrión) donde se queda escuchando el AVD para recibir comandos. El
primer AVD que se lanza se quedará escuchando en el puerto 5554, y se van
incrementando de dos en dos. Como hemos visto, se puede conocer el puerto
en el que está un AVD a la escucha mirando la barra de título de su ventana.
El puerto se habilita únicamente para el localhost, por lo que no es posible
la comunicación entre AVDs en diferentes máquinas. Puedes comprobarlo con
netstat -tlpn. Verás que también están ocupados los puertos impares (5555
y 5557); un AVD ocupa dos puertos consecutivos, aunque por el momento
sólo nos interesa el puerto par.

La comunicación con el AVD en su puerto es en texto, a través de un


interfaz en línea de comandos relativamente intuitivo. Naturalmente, esto no
podrá hacerse con los dispositivos físicos conectados por USB.

Conéctate por telnet a alguno de los dos AVDs:

$ telnet localhost 5554

Simula una llamada entrante de un número arbitrario:

gsm call 555666777

Ve al AVD y descuelga.

Vuelve al CLI y comprueba el estado de la llamada:

gsm list
inbound from 555666777 : active

Simula la recepción de una segunda llamada antes de haber terminado


la primera:

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
35

gsm call 444333222

Con gsm list observa que la segunda llamada está en estado incoming.

Vuelve al AVD, y descuelga. La otra queda en espera.

Vuelve a la consola y mira la lista de llamadas:

gsm list
inbound from 555666777 : held
inbound from 444333222 : active

En el AVD, intercambia las llamadas, dejando en espera la segunda, y


vuelve a listarlas.

Desde el propio CLI podemos hacer muchas tareas. Por ejemplo, po-
demos terminar una llamada:

gsm cancel 444333222

Con una llamada en marcha, simula la recepción de un mensaje:

sms send 999888777 "Comunicas? Cuelga ya!!!"

Vuelve al AVD y observa la llegada del mensaje en la barra de noti-


cación superior.

Práctica 2.7: Preparación de Eclipse: ADT

Aunque las SDK de Android proporcionan herramientas sucientes para


desarrollar aplicaciones para Android, su uso resulta muy laborioso al estar
principalmente basado en línea de comandos.

Afortunadamente, Google proporciona un plug-in para Eclipse denomi-


nado ADT (Android Development Tools ) que facilita enormemente la tarea.
Naturalmente, el ADT requiere las SDK de Android instaladas para invo-
carlas cuando se necesite.

El proceso de instalación de ADT es sencillo, y se puede hacer incluso


sin ser administrador de la máquina.

Lanza Eclipse

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


36 Capítulo 2. Primeros pasos con Android

Ve a Help - Install new software...

Pulsa el botón Add, a la derecha del cuadro de edición Work with.

En el cuadro de diálogo pon ADT Plugin como nombre, y https:


//dl-ssl.google.com/android/eclipse/ como ruta y pulsa OK.

Tras conectarse a Internet te muestra las opciones disponibles. Marca


la casilla de vericación Developer tools y pulsa Next.

Nos hace un resumen de todo lo que nos va a instalar, y aceptamos


pulsando Next.

Acepta la licencia, y pulsa Next.

Durante la instalación, aparece un aviso de que se instalará software


sin rmar. Acéptalo.

Reinicia Eclipse.

Al relanzarlo, nos pregunta si queremos instalar las SDK de Android,


o darle la ruta donde están. Hacemos esto último.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
Notas bibliográcas 37

Decide si quieres o no enviar estadísticas de uso a Google.

Práctica 2.8: El plugin Android Development Tool

Tras relanzar Eclipse, el ADT se reconoce por la nueva barra de herra-


mientas:

Con el primer botón se abre el gestor de las SDK que ya conocemos. Esto
demuestra que efectivamente el ADT es un envoltorio de las SDK, invocán-
dolas desde Eclipse. Lo que hagamos ahí afectará a nuestra instalación de
las SDK, igual que si lo hubiéramos hecho lanzando la aplicación a mano por
separado.

El segundo botón es el Android Virtual Device Manager que lanza


también una herramienta conocida, la de gestión de los AVD. Verás que
aparecen los dos AVDs que denimos en su momento.

El tercer botón, Android LINT, es una herramienta para detectar errores


habituales en proyectos de Android.

Por último, hay tres botones para asistentes que, ahora ya sí, forman
parte del ADT y facilitan la creación de proyectos de Android con Eclipse.

Notas bibliográcas
La documentación on-line de Android es muy rica y está bien escrita
(aunque en inglés):

Introducción a Android:

http://developer.android.com/guide/basics/what-is-android.html

Descripción básica de las aplicaciones disponibles en las SDK:

http://developer.android.com/tools/help/index.html

Plataformas Android y niveles de API:

http://developer.android.com/guide/appendix/api-levels.html

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


38 Capítulo 2. Primeros pasos con Android

Android Virtual Devices :

http://developer.android.com/guide/developing/devices/index.
html
Gestión de AVDs:

http://developer.android.com/guide/developing/devices/managing-avds.
html
Características del emulador que ejecuta los AVDs:

http://developer.android.com/guide/developing/devices/emulator.
html
Uso del emulador:

http://developer.android.com/guide/developing/tools/emulator.
html

En el próximo capítulo. . .
En el próximo capítulo haremos nuestras primeras aplicaciones para An-
droid. Para eso, analizaremos las actividades (Activity ) de Android, que
constituyen sus piezas fundamentales.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
Capítulo 3

Actividades: las ventanas de


Android
Todos los ordenadores se usarán de esta
forma algún día.
Steve Jobs

Resumen: En este capítulo crearemos nuestras primeras aplicaciones


para Android, explorando las llamadas actividades (activity ), su ciclo

de vida y sus interfaces grácas.

Práctica 3.1: Hola mundo

Vamos a hacer nuestro primer programa para Android, usando Eclipse.

Lanza, si no lo has hecho ya, Eclipse.

Para lanzar el asistente de creación de un nuevo proyecto, ve a File -


New - Project - Android project. También puedes utilizar el botón de
la barra de herramientas del ADT.

La primera ventana del asistente nos pregunta por la información bá-


sica del proyecto:

• Application name: nombre de la aplicación que verá el usuario en


el dispositivo. Especicaremos Hola mundo. Al escribirlo, Eclipse
proporciona valores predenidos para algunos de los demás cam-
pos.

39
40 Capítulo 3. Actividades: las ventanas de Android

• Project name: nombre del proyecto en el entorno de Eclipse. Usa-


remos HolaMundo1 .
• Package name: nombre del paquete Java donde meteremos las
clases de nuestra aplicación. Debe ser único entre todos los pa-
quetes instalados en el dispositivo nal, por lo que se debe ser
especialmente cuidadoso para no repetir nombres. Lo habitual es
utilizar el convenio de Java, poniendo el nombre de dominio de
la organización que ha desarrollado la aplicación como nombre
de paquete, seguido del nombre de la propia aplicación. Eclipse
nos propone com.example.<nombreProyecto>, pero es necesario
modicar la primera parte (no se pueden subir al  market  aplica-
ciones que estén dentro de ese paquete). Versiones sucesivas de la
misma aplicación deben pertenecer al mismo paquete. Nosotros
usaremos libro.azul.holamundo.
• Build SDK: la plataforma Android que usaremos como base para
compilar nuestra aplicación. Normalmente se pone la más recien-
2
te que hayamos instalado en las SDK . Selecciona Android 2.3.3
(API 10).

• Minimum Required SDK: versión de la plataforma mínima que


requiere la aplicación para poder ser ejecutada. Debe ser la menor
posible. Escoje API 3: Android 1.5.

• Deja el resto de casillas de vericación con sus valores por defecto.

• Pulsa Next.

Se abre una nueva ventana para la selección del icono de la aplicación.


Experimenta con las opciones y deja el que más te guste antes de pulsar
Next de nuevo.

El asistente nos pregunta si queremos crear una actividad (Activity ).


Una actividad representa una ventana en la aplicación. Deja la casilla
de vericación marcada para que nos cree una, de tipo BlankActivity.

En la siguiente ventana nos pregunta varios nombres relacionados con


la actividad que va a crear. Cambia el título por  ½Hola mundo! y
deja el resto igual.

Si no instalaste la Android Support Library durante la conguración,


Eclipse te avisa y te facilita un botón para realizar la instalación.

Aunque no vamos a entrar en mucho detalle, aún, con respecto a todo


lo que el asistente ha creado por nosotros, hay algunas cosas que podemos
apreciar en los cheros que ha añadido a nuestro recién creado proyecto:
1
Si hiciste las prácticas del capítulo 1, es posible que ya tengas un proyecto con ese
nombre. Puedes utilizar, por ejemplo, HolaMundoAndroid en ese caso.
2
Observa que la lista desplegable sólo muestra las plataformas que hayas instalado.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
41

src/: es el directorio con el código fuente de nuestras clases. Por el


momento únicamente hay una, MainActivity.java, que resulta ser la
actividad que le pedimos que creara por nosotros.

gen/: directorio con código Java generado automáticamente durante la


construcción del programa. El más importante es el de la llamada clase
R, con identicadores de recursos, de los que hablaremos más adelante.
Carpetas Android y Android Dependencies. La primera contiene todo
el API de la plataforma Android contra la que estamos compilando
nuestra aplicación. En este ejemplo es la versión 2.3.3 (API 10). En un
proyecto de Java normal, es el equivalente a la librería del JRE (Java
Runtime Environment ). La segunda indica dependencias adicionales
que la aplicación tendrá que proporcionar (en este caso, la librería
android support library ).

assets/ y res/ contienen recursos de la aplicación. Veremos más sobre


esto en prácticas posteriores.

bin/: contiene la versión compilada de nuestro proyecto.

libs/: contiene la librería de las android support library para se aña-


dida al paquete nal de la aplicación.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


42 Capítulo 3. Actividades: las ventanas de Android

AndroidManifest.xml: mantiene una descripción de las características


básicas de la aplicación. Diseccionaremos diferentes elementos de este
importante chero en prácticas posteriores.

En esta primera práctica, únicamente queremos probar que todo funcio-


na, antes de intentar entender cómo lo hace. Vamos a lanzar la ejecución.

Si no los tienes ya, lanza los AVD de Android 1.5 y Android 2.3.3.
Recuerda desmarcar Save to snapshot.

Cuando estén completamente arrancados, en Eclipse pulsa Ctrl-F11, la


opción del menú Run - Run, o el botón de la barra de herramientas .
3

Se nos pregunta cómo queremos realizar la ejecución, y le decimos que


se trata de una aplicación de android.

Eclipse te pregunta en qué dispositivo quieres ejecutarlo. Ha detectado


la disponibilidad de ambos AVDs, y te pide que escojas. Elige el AVD
con Android 1.5.

En la ventana Console aparece información sobre la compilación, y


también sobre la instalación del .apk en el dispositivo y el inicio de la
ejecución.

Ve al AVD y observa la aparición de la aplicación. Como título verás


½Hola mundo!, que es el que le dimos a la actividad. El asistente nos
ha creado la actividad con un texto en el centro que dice Hello world!.

Pulsa el botón de volver (o escape) para cerrar el programa. En la lista


de programas instalados en el dispositivo verás una nueva aplicación.

3
Esta ejecución no es de depuración, por lo que no se modicará la perspectiva de
Eclipse

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
43

Vuelve a ejecutarlo, ahora en la versión de Android 2.3.

Práctica 3.2: Nivel de API mínimo y de construcción

En la práctica anterior, el asistente nos preguntó dos veces por la versión


del API que queríamos usar, con los campos Build SDK y Minimum Required
SDK .
4

Cuando compilamos una aplicación necesitamos tener acceso a las clases


auxiliares que vamos a utilizar, como por ejemplo la clase padre de las acti-
vidades, android.app.Activity, de la que hereda la única clase de nuestro
programa. Si Eclipse no encuentra esa clase, no podrá compilar el programa.
Esas clases las proporciona la plataforma Android contra la que estamos com-
pilando nuestra aplicación (Build SDK ). A modo de ejemplo, si en el proyecto
anterior despliegas android.jar (en la carpeta Android <version>), verás
que, entre otras muchas, está la mencionada android.app.Activity. Las
versiones más recientes de la plataforma contienen las clases de las versiones
anteriores, junto con otras nuevas que proporcionan funcionalidad adicional.

Es importante ser consciente de que cuando enviamos una aplicación a


un dispositivo (el .apk que nos ha generado Eclipse), esas clases no se in-
cluyen. Dicho de otro modo, se espera que el dispositivo disponga del chero
android.jar. Pero, ¾qué versión? Esa pregunta la responde Minimum Re-
quired SDK. En el caso de nuestro ejemplo, esperamos que al menos el dispo-
sitivo disponga de Android 1.5 (API 3), aunque podría ser superior. Es muy
importante especicar el mínimo posible, dado que si publicáramos nuestra
aplicación en el market , los dispositivos con versiones de Android inferiores
a la mínima solicitada no verán nuestro programa (y no podrán instalarla).
Poner un valor caprichosamente alto, por tanto, nos restará visibilidad.

El punto negativo de tener los dos valores es que debemos ser cuidadosos
al desarrollar, para no utilizar características de la versión Build SDK que
no estén en Minimum Required SDK. Si lo hacemos, fallará en ejecución en
el dispositivo.

Afortunadamente, las SDK contienen una herramienta llamada Lint


5 que

analiza el proyecto y avisa de construcciones erróneas o potencialmente pro-


blemáticas. Eclipse lo ejecuta automáticamente, por lo que si hacemos uso
4
En realidad, el término SDK aquí es incorrecto. Debería decir API, dado que nos está
preguntando por la versión de la plataforma Android, no de las herramientas usadas para
el desarrollo, que llevan una numeración diferente.
5
En realidad, el nombre lint se usa para referirse a cualquier herramienta que analiza
el código fuente de cualquier lenguaje de programación buscando construcciones sospe-
chosas, normalmente haciendo análisis estático del código.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


44 Capítulo 3. Actividades: las ventanas de Android

de alguna funcionalidad que no se soporte en el Minimum Required SDK,


recibiremos un error de lint.

En cualquier caso, vamos a probarlo:

En el código del proyecto que hemos creado antes, en el código del


método onCreate(), añade el siguiente código:

new android.view.animation.AnticipateInterpolator();

Esto crea un objeto de una clase que se añadió a Android en su versión


1.6 (API 4). Por tanto, funcionará en Android 2.3, pero no en 1.5.

Intenta ejecutarlo. Recibirás un error de lint al hacer uso de una fun-


cionalidad que no está disponible en el mínimo nivel de API declarado
por la aplicación.

Queremos probar qué ocurre si ejecutamos este programa en un An-


droid 1.5, por tanto tenemos que pedirle a Eclipse que ignore el vere-
dicto de Lint. Tenemos dos opciones:

• Deshabilitar lint para que Eclipse no lo use. Para eso, nos va-
mos al menú Window - Preferences, y en Android - Lint Error
Checking deshabilitamos las dos casillas de vericación.

• Añadir una anotación al método onCreate() para indicarle a lint


que en ese método no haga comprobaciones sobre el API utiliza-
do. Para eso, añadimos encima de él @SupressLint("NewApi") o
@TargetApi(4).
Una vez que Eclipse no pone pegas a nuestro código, pruébalo tanto en
Android 1.5 como en Android 2.3. Verás que en uno de ellos funciona,
y en el otro no. Ten en cuenta que si como Minimum Required SDK
hubiéramos puesto un número mayor, entonces no habríamos podido
probarlo en Android 1.5 por no cumplir la restricción del API mínimo.

Práctica 3.3: Interactuando con el usuario: botón Púlsame

Vamos a crear nuestra segunda aplicación, que contendrá un botón que


podremos pulsar. El esquema de trabajo es similar al que vimos en el ca-
pítulo 1 con los applets. La diferencia principal (al menos por el momento)
es que los nombres de las clases y sus métodos son diferentes. Con los ap-
plets hacíamos uso de la librería estándar de Java (el JRE), y ahora haremos

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
45

uso de los controles proporcionados por Android. Sin embargo, respecto a la


recepción de los eventos de pulsación la idea es similar.

Crea un nuevo proyecto de Android:

• Name: Púlsame

• Project name: Pulsame

• Package name: libro.azul.Pulsame


• Build SDK: Android 2.3.3 (API 10)

• Minimum required SDK: API 3: Android 1.5 (Cupcake)

• Activity Name: Pulsame

• Activity Title: Púlsame

• Deja el resto de campos con los valores predenidos.

Si se te ha abierto activity_pulsame.xml, ciérralo.

Abre el chero Pulsame.java y sustitúyelo por el código que aparece


a continuación.

package l i b r o . a z u l . pulsame ;

import a n d r o i d . o s . Bundle ;
import a n d r o i d . app . A c t i v i t y ;

// Dos nuevos i m p o r t s para poder u s a r e l b o t ó n


// y h a c e r n o s o y e n t e s suyo .
import a n d r o i d . v i e w . View ;
import a n d r o i d . w i d g e t . Button ;

/∗ ∗
∗ A c t i v i d a d de Android . R e p r e s e n t a l a ventana
∗ p r i n c i p a l de l a a p l i c a c i ó n .
∗/
public c l a s s Pulsame extends Activity {

/∗ ∗
∗ Método l l a m a d o cuando s e c r e a l a a c t i v i d a d .
∗/
@Override
public void o n C r e a t e ( Bundle savedInstanceState ) {
// Llamamos a l método que estamos s o b r e e s c r i b i e n d o
// de l a c l a s e padre .
super . o n C r e a t e ( s a v e d I n s t a n c e S t a t e ) ;

// Configuramos l a ventana , añadiendo un b o t ó n


// que l l a m a r á a n u e s t r o método p r o t e g i d o .

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


46 Capítulo 3. Actividades: las ventanas de Android

_boton = new Button ( this ) ;


_boton . s e t T e x t ( " ½ P ú l s a m e ! " ) ;
_boton . s e t O n C l i c k L i s t e n e r (
new View . O n C l i c k L i s t e n e r ( ) {
public void o n C l i c k ( View v) {
botonPulsado ( ) ;
}
}
);
// Ponemos e l b o t ó n como componente de l a
// a c t i v i d a d .
s e t C o n t e n t V i e w ( _boton ) ;

} // onCreate

/∗ ∗
∗ Método l l a m a d o cuando s e p u l s a s o b r e e l b o t ó n
∗ de l a ventana . Es l l a m a d o a t r a v é s de l a c l a s e
∗ anónima d e l e v e n t o .
∗/
private void botonPulsado ( ) {
++_numVeces ;
_boton . s e t T e x t ( " P u l s a d o " + _numVeces + " veces " ) ;
} // b o t o n P u l s a d o

/∗ ∗
∗ Botón de l a ventana .
∗/
private Button _boton ;

/∗ ∗
∗ Número de v e c e s que s e ha p u l s a d o e l b o t ó n .
∗/
private int _numVeces ;

} // Pulsame

Lanza la aplicación tanto en Android 1.5 como en Android 2.3 y prué-


bala. Fíjate que también queda instalada.

Práctica 3.4: Ejecución en un dispositivo físico

Si queremos ejecutar nuestra aplicación en un dispositivo físico, tendre-


mos que conectarlo al ordenador por USB. En Windows necesitaremos ins-

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
47

talar los drivers que se proporcionan con las SDK, en uno de los paquetes
6
de la sección Extra . En GNU/Linux normalmente no es necesario instalar
nada.

Normalmente los móviles no permiten la ejecución de aplicaciones y el


control externo. La conexión por USB es utilizada por la mayoría de los
usuarios para sincronización de datos y acceso al almacenamiento, no para
ejecutar aplicaciones adicionales. Por tanto, antes de conectar el dispositi-
vo por USB es necesario congurarlo para que admita lo que se denomina
depuración por USB. Para eso, ve a los Ajustes, Aplicaciones, Desarrollo, y
marca Depuración de USB.

Cuando lo hagas, conecta el móvil por USB. Si todo va bien, las SDK
de Android deberían tener acceso a él. Para comprobarlo, puedes usar una
aplicación en consola:

$ cd $ANDROID_SDK_PATH
$ cd platform-tools
$ ./adb devices
List of devices attached
emulator-5554 device
emulator-5556 device
D32C76402AF837AE device
6
Dependiendo del móvil, podrían necesitarse controladores especícos del fabricante.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


48 Capítulo 3. Actividades: las ventanas de Android

En este caso, vemos los dos AVDs lanzados, y un dispositivo físico conec-
tado por USB. Si no ves tu móvil, comprueba que tienes los controladores
necesarios.

Para lanzar la aplicación sobre el móvil, basta con que en Eclipse inicies
una ejecución normal. Eclipse te mostrará la lista de los dispositivos dis-
ponibles que pueden ejecutar tu programa, y es suciente con que elijas el
dispositivo físico. Verás tu aplicación en el móvil un instante después.

Ten en cuenta que la aplicación quedará instalada, y podrás utilizarla pos-


teriormente incluso aunque desconectes el móvil y deshabilites la depuración
por USB.

Práctica 3.5: LogCat: el registro en Android

En las aplicaciones para móviles no funcionan los habituales System.out


o System.err, dado que no hay consola. Puedes probarlo si añades en el
programa de la práctica 3.3, al nal del método botonPulsado() el código:

System.out.println("Pulsado " + _numveces + " veces");

Si lo ejecutas, verás que en la consola mostrada por Eclipse (donde veía-


mos la salida en las aplicaciones de Java), no aparece nada al pulsar el botón
en el emulador.

Como ayuda a la depuración, en Android tenemos el sistema de log llama-


do LogCat. Las aplicaciones envían mensajes al sistema de log, y, durante la
ejecución, Eclipse obtiene todos los mensajes y nos los muestra en la ventana
LogCat.
Los mensajes de registro tienen la siguiente información:

Tag: es una cadena elegida por la aplicación que ha generado el mensa-


je, y que lo identica. Normalmente se utiliza el nombre del programa.
El sistema Android envía sus propios mensajes de registro, y utiliza
tags que identican los componentes internos que los han generado
(ActivityManager, dalvikvm, AndroidRuntime, etcétera).

Nivel: es el nivel de prioridad (gravedad) del mensaje:

• Verbose: mensajes de poca importancia, con detalles extremada-


mente nos de la ejecución. Nunca deberían generarse mensajes
con esta prioridad en una aplicación en producción.

• Debug: mensajes de depuración.

• Information: mensajes informativos sobre la ejecución.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
49

• Warning: avisos de sucesos importantes que podrían terminar


desembocando en problemas.

• Error: noticaciones de errores que afectan a la aplicación.

Para enviar mensajes al log, utilizamos la clase android.util.Log, y sus


métodos v(), d(), i(), w() y e(), para cada uno de las cinco prioridades
de errores. Todos son estáticos, y reciben dos parámetros. El primero es el
tag, que identica a la aplicación; el segundo es el mensaje que queremos
registrar.

Recupera la práctica 3.3, y añade un atributo estático:

/**
* Constante con el "tag" usado para registro de sucesos
* en LogCat relativos a esta actividad.
*/
private static final String TAG = "Pulsame";

Al nal del método botonPulsado() añade el código:

android.util.Log.d(TAG, "botonPulsado " + _numVeces);

Lanza la aplicación en alguno de los AVD.

Ve a la ventana LogCat de Eclipse. Si no está visible, ábrela con Win-


dow, Show View, LogCat. Observa la cantidad de mensajes que apare-
cen, relacionados con el propio sistema Android.

Pulsa el botón varias veces.

Comprueba la aparición del mensaje en el LogCat.

Ejecuta la aplicación en el dispositivo físico y comprueba la salida de


LogCat.

Práctica 3.6: Android Debug Bridge

En el capítulo 2 vimos que cada AVD tenía un identicador equivalente


a un número de puerto TCP en el que se quedaba escuchando para recibir

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


50 Capítulo 3. Actividades: las ventanas de Android

solicitudes a través de un protocolo de línea de comandos. Esos números de


puerto siempre son pares.

Además, cada AVD escucha en el puerto impar inmediatamente posterior


(empezando por 5555) y proporciona un servicio de depuración que sigue un
protocolo particular comprendido por el demonio adb.

Ese protocolo no se basa en texto, pero podemos aprovecharnos de él


usando la aplicación adb.
El esquema de uso es el siguiente:

adb devices: muestra los dispositivos detectados.

adb [-d | -e | -s <numeroSerie>] comando: ejecuta el comando so-


bre el dispositivo solicitado.

• -d: al único dispositivo conectado por USB. No podrá ser usado


si hay más de uno.

• -e: al único emulador detectado. No podrá ser usado si hay más


de uno.

• -s <numeroSerie> al dispositivo indicado.

Conéctate a cualquiera de los AVDs que tengas lanzados, y muestra el


logcat. Por ejemplo, asumiendo que sólo hay un AVD:

$ ./adb -e logcat

La salida que muestra tiene menos información que la vista en Eclipse.


Muestra únicamente la gravedad, el tag, y el PID. Para mostrar toda
la información, prueba:

$ ./adb -e logcat -v threadtime

Ahora verás toda la información que nos daba eclipse. Las alternativas
son brief, process, tag, raw, time, threadtime y long. Pruébalas e
identica sus diferencias.

Prueba a poner ltros:

$ ./adb -e logcat -v ActivityManager:* dalvikvm:* *:S

Abre un shell con algún AVD. Por ejemplo, si sólo tienes uno lanzado:

$ ./adb -e shell
# <<--- shell remoto en el móvil

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
51

Observa el #, que es el promt del root.


Utiliza los comandos del shell habituales en GNU/Linux para recorrer
la estructura de directorios. Usa ps para ver los procesos lanzados o
top para ver el consumo (aunque no es tan ágil como el tradicional).
Termina saliendo con exit.

Si tienes un dispositivo físico, podrás hacer lo mismo accediendo a él


(si habilitaste la depuración de programas). Haz las pruebas de logcat y
también las del shell. Observa que en ese caso no serás root (el prompt que
aparece es $).


Práctica 3.7: Depuración de Android

El demonio adb proporciona un puente para depurar aplicaciones y eje-


cutarlas paso a paso. Desde el punto de vista del programador, los detalles
no importan. El plug-in ADT se encarga de que todo el proceso sea transpa-
rente, consiguiendo que la depuración sea igual que si fuera una aplicación
de Java tradicional.

Recupera en Eclipse el proyecto de la práctica 3.3.

Pon un punto de ruptura en la primera línea del método onCreate()


y otra en la de botonPulsado().
Lanza la ejecución en modo depuración (pulsando F11 o con el icono de
la barra de herramientas) sobre un AVD. Observa que la ejecución se
detiene en el onCreate(). Haz la ejecución paso a paso como vimos en
la práctica 1.14. Si quieres continuar la ejecución sin tener que terminar
de recorrer paso a paso el método, pulsa el botón play (Resume) o F8
para lanzar la aplicación

Pulsa el botón de la aplicación en el AVD. Observa que no reacciona, y


que en Eclipse se ha detenido la ejecución. Vuelve a depurar, comprueba
que puedes ver el valor de la variable _numVeces, y lanza de nuevo la
ejecución con F8.

Cierra el programa en el AVD, y observa que la depuración termina en


Eclipse.

Si tienes un dispositivo físico, repite el proceso sobre él.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


52 Capítulo 3. Actividades: las ventanas de Android

Práctica 3.8: Ciclo de vida de las aplicaciones

Lanza la aplicación Pulsame en cualquiera de los AVDs.

Pulsa varias veces el botón, para que aumente el número.

Pulsa escape o Atrás.

Vuelve a lanzar el programa, buscándolo entre las aplicaciones instala-


das.

Observa que se reinicia la cuenta. El programa se cerró, y se ha lanzado


de nuevo desde el principio.

Pulsa varias veces sobre el botón otra vez.

Simula una llamada entrante en el dispositivo:

• Conéctate por telnet al puerto del AVD indicado en el título de


la ventana:

$ telnet localhost 55xx


• Simula una llamada entrante de un número arbitrario:

gsm call 555666777


Descuelga la llamada en el AVD, y luego nalízala.

Terminarás volviendo a nuestra aplicación que esta vez no se habrá


reiniciado.

Pulsa ahora la tecla Inicio, o el botón  Home , para volver al escritorio


de Android.

Lanza de nuevo el programa, buscándolo entre las aplicaciones.

Aunque lo hayamos lanzado otra vez, no se ha reiniciado, y la cuenta


se mantiene.

Fíjate que el programa que hemos hecho no tiene método main(), igual
que ocurre en los applets. De hecho, nuestra aplicación hereda de la clase
android.app.Activity. Una actividad es un componente de una aplicación,
que muestra una ventana con la que interactuar (buscar en la agenda, hacer
una foto, escribir un mensaje, mirar los mensajes entrantes). Cada actividad
tiene una ventana que, normalmente, ocupará toda la pantalla.

El ciclo de vida de las actividades lo controla el propio Android. Es el


sistema quién decide cuando eliminar una actividad. Hemos visto que:

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
53

Al pulsar Atrás, el sistema elimina la actividad, dado que al volverla


a ejecutar se reinició la cuenta.

Si se superpone una aplicación nueva sobre la nuestra (como ocurre


cuando recibimos una llamada), la actividad no es destruída, y volve-
remos a ella al terminar. Android mantiene una pila de actividades.

Si pulsamos el botón Home , la actividad se mantiene lanzada, pero


no es visible, ni podemos volver a ella a no ser que la lancemos de
nuevo. Android detecta que ya tiene una instancia abierta, y vuelve a
ella.

Igual que en los applets, podemos sobreescribir métodos de la superclase


android.app.Activity para ser noticados de cambios en el estado. El ciclo
de vida se resume en la siguiente gura:

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


54 Capítulo 3. Actividades: las ventanas de Android

Los métodos llamados por el sistema aparecen en rectángulos. Deberías


reconocer onCreate(), dado que es donde en la práctica 3.3 creábamos y
congurábamos el botón. Siempre que se sobreescribe un método es obli-
gatorio llamar al método de la clase padre; de otro modo se generará una
excepción en ejecución.

Una forma diferente de representar el ciclo de vida es como una máquina


de estados, donde los nodos son los estados y las transiciones representan los
eventos, es decir los métodos a los que invoca el sistema.

Ten en cuenta, que los estados Created y Started son transitorios; el


sistema pasará por ellos rápidamente invocando a los siguientes eventos.

Vamos a hacer una nueva aplicación que nos muestre un mensaje cuando
se invoque a los métodos que controlan el ciclo de vida.

Crea un nuevo proyecto Android:

• Application name: Ciclo de vida

• Project Name: CicloDeVida

• Package Name: libro.azul.ciclodevida


• Build SDK: Android 2.3.3

• Min SDK: Android 1.5

• Activity Name: CicloDeVida


• Title: Ciclo de vida

Escribe el código siguiente:

package l i b r o . azul . ciclodevida ;

import a n d r o i d . o s . Bundle ;
import a n d r o i d . app . A c t i v i t y ;

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
55

public c l a s s CicloDeVida extends Activity {

@Override
public void o n C r e a t e ( B u n d l e s a v e d I n s t a n c e S t a t e ) {
super . o n C r e a t e ( s a v e d I n s t a n c e S t a t e ) ;
s e t C o n t e n t V i e w (R . l a y o u t . a c t i v i t y _ c i c l o _ d e _ v i d a ) ;
a n d r o i d . u t i l . Log . i (TAG, " onCreate " ) ;
}

protected void o n S t a r t ( ) {
super . o n S t a r t ( ) ;
a n d r o i d . u t i l . Log . i (TAG, " onStart " ) ;
}

protected void o n R e s t a r t ( ) {
super . o n R e s t a r t ( ) ;
a n d r o i d . u t i l . Log . i (TAG, " onRestart " ) ;
}

protected void onResume ( ) {


super . onResume ( ) ;
a n d r o i d . u t i l . Log . i (TAG, " onResume " ) ;
}

protected void o n P a u s e ( ) {
super . o n P a u s e ( ) ;
a n d r o i d . u t i l . Log . i (TAG, " onPause " ) ;
}

protected void o n S t o p ( ) {
super . o n S t o p ( ) ;
a n d r o i d . u t i l . Log . i (TAG, " onStop " ) ;
}

protected void o n D e s t r o y ( ) {
super . o n D e s t r o y ( ) ;
a n d r o i d . u t i l . Log . i (TAG, " onDestroy " ) ;
}

private s t a t i c f i n a l S t r i n g TAG = " C i c l o D e V i d a " ;

} // c l a s s CicloDeVida

Ejecútalo en cualquier AVD, y repite las pruebas anteriores, mirando


la ventana LogCat en Eclipse.

Ejecútalo en un dispositivo físico y pruébalo también. Espera a que el


dispositivo se suspenda, con la aplicación en primer plano, y mira el

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


56 Capítulo 3. Actividades: las ventanas de Android

registro en Eclipse. Reactiva el dispositivo y comprueba el resultado


en el log.

Práctica 3.9: Primera aproximación a los recursos: interna-


cionalización

En la práctica 3.3 creamos una aplicación en la que establecíamos el texto


del botón directamente por código. Eso no es buena idea, porque impedimos
la internacionalización de nuestra aplicación.

Las cadenas de texto son un tipo especial de recursos, que el sistema


puede gestionar automáticamente por nosotros. En código le pedimos que
haga uso de un recurso a partir de su identicador, y Android se encargará
de buscar el valor más adecuado para la conguración actual del sistema.

Desde Eclipse, haz una copia del proyecto Pulsame y ponle el nombre
PulsameLocalizado. Vamos a cambiarlo para hacer uso de recursos
de tipo cadena, y haciendo una copia mantenemos la versión inicial. No
obstante, como el nombre del paquete no lo cambiaremos, no podremos
tener en el mismo dispositivo las dos aplicaciones a la vez.

Abre el chero de localización de cadenas en res/values/strings.xml.


Verás que hay varias cadenas denidas, que nos creó el asistente al crear
el proyecto. Podemos utilizar el interfaz gráco para modicarlas, o po-
demos abrir directamente el chero en su forma XML, en la pestaña
inferior.

Añade una nueva cadena. Para eso, pulsa Add, String.

Como identicador pon pulsameAdmiracion y como texto ½Púlsame!.


En la vista XML, observa que tu nueva cadena se ha incluído.

Abre el chero R.java en la carpeta gen/ del proyecto. Es un chero


generado automáticamente a partir de los recursos. Observa la apari-
ción del campo R.string.pulsameAdmiracion, al que se le ha asignado
un valor numérico arbitrario.

En el código fuente, en el método onCreate, sustituye la línea:

_boton.setText("½Púlsame!");
por:

_boton.setText(R.string.pulsameAdmiracion);

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
57

Ejecuta el programa. No deberías notar ninguna diferencia visible. Pero


hemos dado el primer paso hacia la localización.

Vamos a crear ahora un nuevo chero de cadenas para que se utilice


cuando el móvil esté congurado con idioma inglés. Selecciona la car-
peta res/, y en el menú contextual pulsa New, Other (o pulsa Ctrl-N).

Selecciona Android - Android XML Values File.

Especica strings.xml como nombre. Nos avisa de que el chero ya


existe en el directorio res/values donde debería ir para que Android
lo considere un chero de recurso de cadenas. Sin embargo es un mero
aviso, dado que todavía podemos añadirle cualicadores. Pulsa Next.

Añade como cualicador Language, y establece en (inglés). El mensaje


de aviso de chero ya existente desaparece. Nos indica que el nuevo
chero se guardará en res/values-en.

En el nuevo chero, que de momento no tiene cadenas, crea una nueva.

Como identicador escribe pulsameAdmiracion (el mismo que pusimos


antes), y como texto Push me!.

Relanza la aplicación. Verás que el texto del botón aparece ahora en


inglés. Sin embargo, el nombre de la aplicación sigue saliendo en espa-
ñol; no hemos localizado todas las cadenas, y se hace uso de los valores
por defecto (en el directorio sin cualicadores).

Añade al nuevo chero de cadenas (en inglés), las que teníamos en


español. Es más rápido utilizar la vista en XML, copiar y pegar el con-
tenido original y modicar lo que corresponda. Verás que hay algunas
cadenas que añadió el asistente que ni siquiera estamos usando (y que
de hecho aparecen en inglés). Además, algunos valores están repetidos
(el nombre de la aplicación y el título de nuestra actividad). Prueba a
poner valores diferentes para comprobar dónde se usa cada una.

Cambia la conguración del idioma del AVD a español. Para eso, ve a


Settings, Language & keyboard, Select language, y elige Español (Espa-
ña).

El interfaz del AVD te aparece ahora en español. Busca la aplicación


y ejecútala. Comprueba que aparece en español.

Renombra el directorio values a values-es, para indicar que debe uti-


lizarse cuando el idioma sea español. No deberías notar ningún cambio.

Modica la conguración del AVD y ponlo en francés. Relanza la apli-


cación desde Eclipse para que se envíe al móvil la nueva versión.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


58 Capítulo 3. Actividades: las ventanas de Android

La aplicación falla al no encontrar los recursos. Es muy importante


tener siempre valores predenidos de los recursos para que se usen en
las conguraciones de móviles que no tengan versiones especícas de
ellos.

Cambia de nuevo el nombre del directorio values-es a values. Vuelve


a poner el idioma en español.

Tradicionalmente, un problema de la localización/internacionalización ha


sido los plurales construídos en código, pues cada idioma los crea de una
forma. Afortunadamente, Android también nos da soporte para ellos aunque
el ADT no nos proporcione ninguna ayuda.

Entra en la vista XML de strings.xml en español y añade:

<plurals name="numPulsaciones">
<item quantity="one">Pulsado %d vez</item>
<item quantity="other">Pulsado %d veces</item>
</plurals>

Esto es un nuevo recurso de nombre numPulsaciones, que dice que


cuando la cantidad sea 1 debe usarse la primera cadena, y cuando sea
un número diferente, la segunda.

En el chero strings.xml para el inglés pon:

<plurals name="numPulsaciones">
<item quantity="one">Pushed %d time</item>
<item quantity="other">Pushed %d times</item>
</plurals>

Observa que el identicador es el mismo, pero han cambiado los textos.

Necesitamos internacionalizar nuestra aplicación, para hacer uso de


este nuevo recurso, que ahora es más complicado. Sustituye el código
del método botonPulsado() por:

private void botonPulsado() {


++_numVeces;
android.content.res.Resources res = getResources();
String numPulsados;
numPulsados = res.getQuantityString(
R.plurals.numPulsaciones,
_numVeces,
_numVeces);
_boton.setText(numPulsados);
} // botonPulsado

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
59

Lo que hacemos es conseguir el gestor de recursos, y pedirle la cadena


de plurales cuyo identicador es R.plurals.numPulsaciones especí-
ca para cuando el número es _numVeces (segundo parámetro), y que
nos sustituya el %d que incluye por el valor de _numVeces (tercer pará-
metro).

Ejecuta la aplicación tanto con la conguración en español como en


inglés, y observa que se utiliza la forma correcta.

Práctica 3.10: Interfaces de usuario como XML

En la práctica 3.9 hemos hecho uso de recursos para las cadenas, que
ya no están cableadas en el código. Sin embargo, estamos construyendo el
interfaz de la ventana manualmente. El método onCreate() es:

public void onCreate(Bundle savedInstanceState) {


// Llamamos al método que estamos sobreescribiendo
// de la clase padre.
super.onCreate(savedInstanceState);

// Configuramos la ventana, añadiendo un botón


// que llamará a nuestro método protegido.
_boton = new Button(this);
_boton.setText(R.string.pulsameAdmiracion);
_boton.setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
botonPulsado();
}
}
);
// Ponemos el botón como componente de la
// actividad.
setContentView(_boton);

} // onCreate

Llamamos al método de la superclase que estamos sobreescribiendo.

Creamos un nuevo objeto Button, que representa un control (widget )


de usuario.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


60 Capítulo 3. Actividades: las ventanas de Android

Establecemos su texto utilizando un recurso, que puede ser localizado.

Añadimos un listener al botón, para que sea invocado cuando el usua-


rio lo pulse. Usamos una función anónima que invocará al método
botonPulsado() de la clase.

Por último, le decimos a la actividad (this) que como contenido esta-


blezca al botón que hemos creado, que ocupará toda la ventana.

Dado que el botón lo estamos guardando en el campo _boton, en el


método botonPulsado() podemos acceder a él para modicar el texto que
muestra, tal y como ya hemos visto.

Congurar los controles de la ventana manualmente por código no es


buena idea. Salvo para interfaces simples, resulta bastante laborioso, y cada
prueba requiere recompilación y ejecución, siendo un proceso lento. Además
si quisiéramos personalizar el interfaz para diferentes entornos, igual que
hemos hecho con las cadenas, tendríamos que llenar nuestro programa de
código condicional. Por ejemplo, podríamos querer que el interfaz cambiara
si el dispositivo está en vertical (portrait ) o en horizontal (landscape ), o que
en una ventana pidiendo datos personales pidiera el segundo apellido en
España, pero no en Estados Unidos, donde debería pedir a cambio el middle
name.
Afortunadamente, Android dispone de un tipo de recursos llamados la-
yout que permiten especicar la disposición de los controles de un interfaz
de usuario (una actividad ) en un XML. Como con las cadenas, ese XML
tendrá un identicador, y podremos pedirle al sistema que nos construya el
interfaz a partir de él. Podremos tener diferentes versiones del mismo layout,
cualicadas con las opciones del sistema de una forma similar a la que ya
vimos para las cadenas.

Vamos a modicar poco a poco nuestra aplicación para hacer uso de un


layout como recurso.

Haz una copia del proyecto PulsameLocalizado y dale un nuevo nom-


bre, por ejemplo PulsameLayout.

Elimina (o comenta) todo el código del método onCreate().

Busca el chero res/layout/activity_pulsame.xml. Es un chero


que nos creó el asistente del proyecto, y que no estábamos usando, al
haber sustituído el onCreate(). Contiene el recurso predenido de la
actividad que nos escribió para que lo usáramos como punto de partida.
Ábrelo. Si se te habre el Graphical Layout, selecciona la vista en XML
en la pestaña inferior.

Elimina todo el texto que aparece, y copia el siguiente:

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
61

<Button
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/boton"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="@string/pulsameAdmiracion"/>

Estamos indicando que nuestro layout (disposición) contendrá única-


mente un objeto de la clase Button. El primer atributo es estándar
para especicar el espacio de nombres XML. El resto de campos son
más interesantes:

• android:id: indica el identicador del botón. Podremos utilizarlo


para acceder al botón desde código. El identicador de los con-
troles siempre deben tener la estructura @id/<identificador> o
@+id/<identificador> (la diferencia está en el +). Si no se po-
ne el +, tendremos que denir nosotros el identicador de alguna
forma en el chero R (lo normal será porque estemos reutilizando
un identicador). Si se indica el + (lo más habitual), le pedimos a
aapt (la herramienta que procesa los recursos) que nos añada el
identicador automáticamente a la clase R.

• android:layout_width y android:layout_height: indica cuán-


to espacio del contenedor en el que se muestra el control (en este
caso, el botón en la ventana) deberá ocupar a lo ancho y a lo alto.
Los posibles valores son:

◦ match_parent (o fill_parent): el control debe ocupar todo


el espacio disponible para él en el contenedor padre.

◦ wrap_content: el control ocupará el espacio mínimo necesario


para mostrarse.

En este caso, hemos elegido ocupar el máximo espacio, tanto a lo


ancho como a lo alto.

• android:text: texto del botón. Si lo comenzamos con @, Android


asumirá que estamos accediendo a un recurso, y buscará la ca-
dena asociada a él. Si no, pondrá el texto que escribamos. Lo
recomendable es usar siempre recursos.

Eclipse automáticamente nos regenera la clase R con los identicadores


de los recursos. Ve a ella, y comprueba que hay dos nuevos:

• R.layout.activity_pulsame: el nombre proporcionado para este


recurso es el nombre del chero (sin la extensión). En el caso de
las cadenas, el nombre se especicaba en el XML, y el nombre
del chero era indiferente. Dado que los layouts son recursos que

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


62 Capítulo 3. Actividades: las ventanas de Android

ocupan un chero completo, el identicador que se utiliza es el


del nombre del chero.

• R.layout.id.boton: es el identicador del botón del layout. Lo


ha creado automáticamente al especicar el + en él.

Ya tenemos denido el recurso. Ahora tenemos que usarlo. En el mé-


todo onCreate(), comenta (o elimina) todo el código salvo la llamada
al método de la superclase, y añade:

setContentView(R.layout.activity_pulsame);

Recuerda que originalmente teníamos:

setContentView(_boton);

donde _boton era el objeto que habíamos creado manualmente. Ahora


le estamos diciendo a Android como contenido, pon los controles de-
nidos en el recurso asociado a este identicador. Android buscará el
recurso, creará y congurará los objetos a partir de los datos del XML,
y lo asociará al contenido de la actividad.

Ejecuta la aplicación y comprueba que todo va bien. Deberías ver el


texto en el botón (con el idioma localizado), aunque no hará nada al
ser pulsado.

Modica el layout y prueba a establecer wrap_content en lugar de


fill_parent en layout_* para ver cómo funciona.

Para poder reaccionar al evento, necesitamos congurar el botón. Para


eso, necesitamos acceder a él, e invocar a su setOnClickListener(),
como hacíamos con el botón creado manualmente. Para buscar un con-
findViewById(id), que
trol por el identicador de su recurso, se utiliza
devuelve un objeto de la clase android.view.View, padre de todos los
controles. Añade el siguiente código al método onCreate():

// Obtenemos el objeto Button a partir de su id.


_boton = (Button) findViewById(R.id.boton);
// Nos hacemos observadores de sus pulsaciones.
_boton.setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
botonPulsado();
}
}
);

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
63

Ahora el campo _boton se está obteniendo del recurso (una vez que
ya se ha congurado la vista), en lugar de crearlo manualmente como
hacíamos antes con new. Además, ya no establecemos el contenido (lo
hace el propio layout ). Únicamente añadimos el listener.

Prueba la aplicación. Verás que vuelve a funcionar la pulsación del


botón.

Si te jas en el método onClick() que estamos escribiendo en la clase


anónima que captura el objeto, verás que tiene un parámetro View v.
El objeto que recibimos ahí es el widget que ha ocasionado la invocación
al método, es decir el propio botón que ha sido pulsado. Aunque el pro-
totipo del método dice que recibe un objeto de la clase View, en realidad
eso es el tipo estático, pero el tipo dinámico (gracias al polimorsmo)
es en realidad Button. Vamos a modicar el código para que desde la
clase anónima enviemos ese objeto al método botonPulsado(). Se lo
enviaremos aún con tipo (estático) View, y haremos la conversión de
tipos (hacia abajo) en el propio botonPulsado(). Es decir, el código
debería pasar ahora a ser:

// ...
public void onCreate(Bundle savedInstanceState) {
....
// Nos hacemos observadores de sus pulsaciones.
_boton.setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
botonPulsado(v);
}
}
);

} // onCreate

...

private void botonPulsado(View v) {


++_numVeces;
android.content.res.Resources res = getResources();
String numPulsados;
numPulsados = res.getQuantityString(R.plurals.numPulsaciones,
_numVeces, _numVeces);
Button b = (Button) v;
b.setText(numPulsados);
} // botonPulsado

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


64 Capítulo 3. Actividades: las ventanas de Android

La diferencia principal es que ahora en botonPulsado() no necesita-


mos el campo _boton, dado que lo recibimos directamente como pará-
metro (aunque nos veamos forzados a realizar una conversión de tipos).

Prueba la aplicación y comprueba que sigue funcionando.

Dado que ya no necesitamos _boton en botonPulsado(), podemos aho-


rrárnoslo completamente. Elimina el atributo, y en onCreate() crea
una variable local que sea un Button para guardar el valor devuelto
por findViewById().

Necesitar acceder al widget para establecer el listener que nos llame al


método de procesamiento de la pulsación es bastante tedioso. Es posible
especicarlo directamente en el recurso, y que Android haga la asociación
automáticamente.

En la denición del layout, añade un nuevo atributo al botón:

android:onClick="botonPulsado"

En el código fuente del método onCreate(), elimina la parte de es-


tablecer el listener. El código completo del método deberá ser mucho
más corto:

public void onCreate(Bundle savedInstanceState) {


// Llamamos al método que estamos sobreescribiendo
// de la clase padre.
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pulsame);
}

Convierte en public el método botonPulsado().

Prueba la aplicación.

Para que esto funcione, el método de la actividad establecido en el atri-


buto android:onClick del XML debe cumplir:

Ser público

No devolver ningún valor (void)

Recibir un único parámetro de tipo View, que contendrá el elemento


pulsado.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
65

Por desgracia, no todos los eventos de los controles se pueden asignar


directamente a través del XML.

Práctica 3.11: Propiedades básicas de los controles

En la práctica 3.10 denimos nuestro primer interfaz de usuario con un


único botón, que, además, respondía a su pulsación.

Antes de adentrarnos en disposiciones más complicadas, con varios con-


troles, vamos a familiarizarnos con algunas de las propiedades visuales más
importantes de los controles. La propiedad android:id es importante pa-
ra la denición de un identicador, aunque no tiene repercusión visual. Por
su parte, la propiedad android:text de los botones especica el texto que
muestra, y tampoco nos detendremos más en él.

Para las pruebas que vamos a realizar, abre el chero del recurso de tipo
layout activity_pulsame.xml de dicha práctica usando, esta vez sí, la vista
Graphical Layout. Eclipse nos muestra un entorno gráco para diseñar la
ventana, en lugar de tener que utilizar directamente el XML. Nos permite así
hacer cambios y ver el resultado automáticamente, sin necesidad de compilar
la aplicación y probarla en un AVD o terminal físico.

Para la edición necesitamos espacio en pantalla. Oculta la vista Package


explorer si lo necesitas, y abre Outline (con el botón de la vista, o en el menú
Window, Show view, Outline ). El objetivo es que consigas una conguración
similar a la siguiente.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


66 Capítulo 3. Actividades: las ventanas de Android

Es interesante que según vayas realizando las pruebas, modiques la


orientación de la pantalla, para probar cómo quedaría en una posición apai-
sada del dispositivo:

Fíjate que los atributos que conocíamos con los nombres layout_width
y layout_height en el XML, están, en el editor de propiedades, dentro
de una sección Layout Parameters y se llaman, sencillamente, width y
height. Esto es debido a que son atributos heredados del contenedor
en el que está el botón, no del botón en sí mismo. En el XML tendremos
que especicar como nombre layout_*, pero en la vista gráca tenemos
que buscarlos dentro de esa sección de las propiedades.

Como hemos visto, layout_width sirve para indicar cuanto espacio


horizontal del contenedor donde está el control ocupa éste. Con el va-
lor match_parent (o fill_parent) se ocupa todo el espacio posible.
Cámbialo a wrap_content y observa el resultado.

Haz lo mismo con layout_height.

También en esa misma sección podemos ver la propiedad gravity (que


será layout_gravity en el XML). Experimenta con ella. ¾Averiguas
para qué sirve? Al nal, déjala vacía.

Vuelve a colocar en layout_* en fill_parent. Manipula la propiedad


margins del layout parameters (en el XML será layout_margin para
el global, y layout_margin<Lado>, con Lado uno de Bottom, Left,
Right o Top). ¾Para qué sirve? ¾Crees que funciona bien? Prueba a
cambiar a la vez Gravity.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
67

Manipula las propiedades Gravity y Padding (asegúrate de que tienes


layout_height y layout_width en fill_parent). Estas propiedades
son ya del control, no del layout. ¾Para qué sirven? ¾En qué se diferencia
layout_gravity de Margin?

Haz pruebas similares con gravity y Padding, pero dejando layout_*


en wrap_content. ¾Sirven para algo? ¾Qué hacen?

Práctica 3.12: Etiquetas

Hasta ahora hemos utilizado únicamente botones en nuestras actividades.


Vamos a experimentar con el funcionamiento de las etiquetas.

Son un tipo de control muy sencillo e incluso podemos considerar que ya


sabemos usarlas: la clase Button hereda de TextView (las etiquetas). Además,
el asistente de proyectos del ADT nos crea siempre una actividad con una
única etiqueta con el texto Hello World!

Crea un nuevo proyecto, de nombre Etiqueta. Ajusta el resto de valores


como de costumbre.

Elimina el texto asociado al XML del recurso por:

<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/textView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:text="@string/hello_world" />

El texto de la etiqueta se establece con el atributo android:text, que


puede acceder a un recurso de tipo cadena al igual que con los botones.

Haz las mismas pruebas sobre ella que las que hiciste con el botón.
Ten en cuenta que con las etiquetas es más complicado hacerse una
idea de qué está pasando, porque son transparentes. Cuando se dise-
ña un layout, a menudo es interesante poner fondos temporales a los
controles transparentes, para tener una visión más clara de lo que está
ocurriendo. Para eso, puedes poner en el atributo android:background
un color con el formato #RRGGBB (por ejemplo, #FF0000 para rojo).

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


68 Capítulo 3. Actividades: las ventanas de Android

Práctica 3.13: Cuadros de texto

Los cuadros de texto son controles donde el usuario puede introducir


texto. Son proporcionados por la clase EditText, que es similar en dicultad
de uso al resto (de nuevo, EditText hereda de TextView).

Haz un nuevo proyecto y en el layout establece:

<EditText
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/editText"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:inputType="text"
android:text="@string/escribeAlgo" />

Hay un atributo adicional que no teníamos antes llamado inputType.


Sirve para especicar el tipo de entrada. Especicamos text si quere-
mos un texto genérico. Podemos congurar el control para que reciba
un número, una contraseña, un nombre, una fecha, y un largo etcétera.

Crea un nuevo recurso de tipo cadena con el identicador escribeAlgo


y el texto Escribe algo.

Haz las mismas pruebas sobre ella que las que hiciste antes.

Práctica 3.14: Linear Layout

Hasta ahora sólo hemos puesto un control en nuestra ventana. Si inten-


táramos añadir más de uno, obtendríamos un error, porque una ventana sólo
puede tener un control raíz. Es necesario añadir contenedores, que hospeda-
rán a otros controles o a otros contenedores. Construiremos así una jerarquía
de controles que crean la composición nal. Los controles que hemos vis-
to (etiquetas, botones, y cuadros de edición) son hojas en ese árbol, y
son subclases de View. Los contenedores agrupan controles, son subclases de

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
69

ViewGroup, e implementan una política de organización de sus controles hijo


en el espacio del que disponen, a la que se le denomina layout.

En esta práctica vamos a ver el funcionamiento de la clase LinearLayout,


que organiza sus controles en línea, ya sea horizontal o verticalmente.

Crea un nuevo proyecto de prueba, llámalo LinearLayout y deja el resto


de valores con los predenidos en el asistente.

Abre el chero del layout de la actividad principal, y sustitúyelo por


el siguiente texto:

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

<!-- Controles que contiene. Dos etiquetas -->


<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#80FF80"
android:text="@string/etiqueta1" />
<TextView
android:id="@+id/text2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#FF8080"
android:text="@string/etiqueta2" />
</LinearLayout>

Se ha puesto intencionadamente un color de fondo a cada etiqueta y al


propio layout para que las pruebas que realicemos queden más claras.

Dene las cadenas etiqueta1 y etiqueta2 en el chero de recursos,


usando como texto Etiqueta 1 y Etiqueta 2.

Ve a la vista Graphical layout y comprueba el resultado. Lanza la


aplicación en un AVD para verlo también.

Observa la estructura en árbol tanto en el XML como en la relación


de los contenedores y controles nales que tienes disponible en la ven-
tana Outline. Dentro del LinearLayout podríamos haber incluído otro
LinearLayout.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


70 Capítulo 3. Actividades: las ventanas de Android

Selecciona en la ventana outline el LinearLayout.


• Modica las propiedades layout_width y layout_height estable-
ciendolas al valor wrap_content y comprueba el efecto. Vuelve a
colocarlo en fill_content.
• Modica la orientación a horizontal y comprueba el efecto. ¾Qué
ha ocurrido?

• Modica el atributo layout_width a wrap_content para la pri-


mera etiqueta. ¾Qué ocurre?

• Vuelve a poner el LinearLayout en vertical. ¾Qué sucede?

• Restaura a fill_parent el layout_width de la etiqueta 1.

• Cambia el padding del LinearLayout a 10dp. ¾Qué ocurre?

• Deshaz el último cambio. Modica el layout_margin de la eti-


queta 1 a 10dp. ¾En qué se diferencia del anterior?

• Modica la propiedad gravity del LinearLayout a bottom. ¾Qué


ocurre?

• Vuelve a poner el LinearLayout con orientación horizontal, a la


etiqueta 1 con layout_width en wrap_content y quítale el mar-
gen.

• Pon también wrap_content en layout_width en la etiqueta 2.


Ambas quedarán juntas, ocupando el menor espacio posible.

• El espacio sobrante podemos repartirlo entre los controles con-


tenidos por el layout usando la propiedad layout_weight. El
LinearLayout suma el valor de esa propiedad de todos los elemen-
tos que contiene, y reparte el espacio sobrante equitativamente
según esos pesos.

• Cambia el layout_width del LinearLayout a wrap_content y


comprueba el efecto.

Práctica 3.15: ½Adivina mi número!

Vamos a hacer un programa completo, en el que diseñaremos un interfaz


en el que tendremos que capturar eventos y manipular la salida. En concre-
to, se trata del conocido juego adivine mi número. El programa escoge un
número aleatorio entre 1 y 100, y el usuario debe adivinar cuál es con suce-
sivos intentos. Tras cada uno, el programa le indicará si el número pensado
es mayor o menor.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
71

El layout debería ser similar al de la gura.

Cuando el usuario introduce un número y pulsa el botón Probar, el


programa compara el número con el introducido y sustituye el texto supe-
rior por¾NN? ½Uy! El número que he pensado es mayor (o menor), don-
de NN es el número escrito por el usuario. Si acierta, deberá mostrar ½½Has
acertado!!.
Todo el texto debe aparecer en el chero strings.xml para poder ser
localizado.

Algunas pistas:

Para no construir manualmente el texto mostrado ante un error, pue-


des poner como recurso la cadena ¾ %1$d? ½Uy! El número que he
pensado es mayor. Luego, se construye el texto nal buscando la plan-
tilla en los recursos, y formateándola sustituyendo el %1$d por el nú-
mero introduciro por el usuario:

String format = getResources().getString(R.string.<id>);


String cadFinal = String.format(format, numUsuario);

Para generar un número aleatorio puedes utilizar la clase estándar


java.util.Random(). Crea una instancia de esa clase en el onCreate(),
guárdala en un atributo llamado, por ejemplo, dado, y utiliza:

numeroAAdivinar = dado.nextInt(100) + 1;

No obstante, durante las pruebas haz que el número a adivinar sea


siempre el mismo para conocerlo y estar seguro de que el programa
funciona.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


72 Capítulo 3. Actividades: las ventanas de Android

En el método llamado desde el evento de pulsación del botón, el objeto


View del parámetro será el botón no el cuadro de texto. Necesitarás
usar findViewById(...) para conseguirlo.

Para extraer el texto escrito en un EditText se usa:

String numUsuarioTxt = miEditText.getText().toString();

Para convertir una cadena a número puedes usar:

int numUsuario = Integer.parseInt(numUsuarioTxt);

Además, aquí van algunas ideas, algo más avanzadas, para mejorar el
programa:

A nivel de usabilidad, es molesto tener que introducir el número y


luego pulsar el botón. Puedes eliminar el botón, y capturar el evento
de pulsación de intro en el cuadro de texto, aunque no es muy sencillo:

et.setOnKeyListener(new android.view.View.OnKeyListener() {
public boolean onKey(View v,
int keyCode,
android.view.KeyEvent event) {
// Han pulsado (o soltado) una tecla...
if ((event.getAction() ==
android.view.KeyEvent.ACTION_DOWN) &&
(keyCode ==
android.view.KeyEvent.KEYCODE_ENTER)) {
// Ha sido una pulsación de "intro"
// PENDIENTE! HACER ALGO!!
return true;
}
else
return false;
} // onKey
});

Puedes mejorar el programa para que cuando acabe la partida, se des-


habilite (o incluso oculte) el cuadro de texto (y el botón, si sigues con
él), y aparezca un nuevo botón que pueda ser pulsado para volver a
jugar. Para eso, es interesante saber que los controles tienen un atribu-
to android:visibility que puede tomar (en el XML) los siguientes
valores:

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
Notas bibliográcas 73

• visible: el control es visible y aparece en su posición en función


de la organización del interfaz. Es el valor predenido.

• invisible: el control no es visible pero sigue ocupando el espacio


que le correspondería si lo estuviera.

• gone: el control no es visible y no ocupa espacio. Si se muestra,


desplazará otros controles para ajustar la disposición global.

Desde código podemos usar el método setVisibility(), y pasar por


parámetro una de las constantes VISIBLE, INVISIBLE o GONE, denidas
en la clase android.View.
Por último, ¾qué tal añadir una etiqueta que diga cuantos intentos
llevas?

Notas bibliográcas
A continuación se proporcionan algunas direcciones web con información
sobre cada uno de los temas tratados en este capítulo:

Primera aplicación para Android (con y sin Eclipse):

http://developer.android.com/training/basics/firstapp/index.
html
Introducción a los interfaces de usuario con Android:

http://developer.android.com/training/basics/firstapp/building-ui.
html
Herramienta lint de las SDK de Android:

http://tools.android.com/tips/lint/
Logcat, el sistema de log de Android:

http://developer.android.com/tools/debugging/debugging-log.
html
Clase para enviar mensajes de log:

http://developer.android.com/reference/android/util/Log.html
Ciclo de vida de las actividades:

http://developer.android.com/guide/components/activities.html
http://developer.android.com/training/basics/activity-lifecycle/

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


74 Capítulo 3. Actividades: las ventanas de Android

Documentación sobre la clase Activity:


http://developer.android.com/reference/android/app/Activity.
html
Descripción de los recursos en Android

http://developer.android.com/guide/topics/resources/overview.
html
Cualicadores de los recursos:

http://developer.android.com/guide/topics/resources/providing-resources.
html
Introducción a la creación de interfaces de usuario:

http://developer.android.com/guide/topics/ui/overview.html
Introducción a los layout :

http://developer.android.com/guide/topics/ui/declaring-layout.
html

En el próximo capítulo. . .
En el próximo capítulo veremos algunas posibilidades más avanzadas de
los interfaces grácos de usuario sobre Android.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
Capítulo 4

Un poco más sobre UIs


El usuario no es más que un periférico
que teclea cuando se le envía una
petición de lectura
P. Williams

Resumen: En este capítulo veremos brevemente algunas otras clases


que tenemos disponibles para crear interfaces de usuario en las activi-

dades de Android.

Práctica 4.1: RelativeLayout

En la práctica 3.14 analizamos el LinearLayout, un contenedor que or-


ganiza los controles que se le añaden en una columna o en una la. Es po-
sible crear interfaces grácos complejos anidando múltiples LinearLayout.
Sin embargo, en ocasiones fuerza a jerarquías complicadas que requieren la
creación de muchos objetos.

El RelativeLayout es un contenedor que añade a los controles que hos-


peda atributos adicionales para denir sus posiciones de una manera relativa
a otros controles hermanos, o al padre (el propio RelativeLayout). El nú-
mero de nuevos atributos es desbordante al principio, y no todos pueden ser
utilizados simultáneamente.

Debemos tener cuidado cuando se utiliza este layout para evitar crear por
error referencias circulares que impidan la colocación de los componentes.
Además, en este caso suele ser más sencillo crear el layout directamente en
XML, porque la herramienta gráca de diseño es bastante difícil de domar.

La mayoría de los atributos que añade el RelativeLayout a sus contro-


les hijo tienen como parámetro el identicador de otro control hermano. En

75
76 Capítulo 4. Un poco más sobre UIs

ocasiones, sin embargo, son atributos booleanos. Para hacernos una idea cla-
ra de los atributos de este layout, es interesante organizarlos en función de
qué lado del control sitúan. Es necesario tener en cuenta que esos atributos
sólo colocan un lado. Por ejemplo, el atributo below coloca el control que
estamos especicando debajo de otro. Eso signica que el lado superior de
dicho control estará en la misma horizontal que el lado inferior del control al
que referenciamos. Pero no signica que estén exactamente debajo (tocándo-
se). La posición en horizontal (izquierda-derecha) se establece con atributos
diferentes.

Colocación del lado superior:

• below: coloca el control debajo de otro.

• alignTop: coloca el lado superior en la misma horizontal que el


lado superior de otro control.

• alignParentTop: este atributo es booleano. Coloca el lado supe-


rior alineado con la parte superior del padre (el layout ).

Colocación del lado inferior:

• above: coloca el control encima de otro.

• alignBottom: coloca el lado inferior en la misma horizontal que


el lado inferior de otro control.

• alignParentBottom: atributo booleano para colocar el lado infe-


rior pegado a la parte inferior del padre (layout ).

Colocación del lado izquierdo:

• toRightOf: coloca el control a la derecha de otro.

• alignLeft: coloca el lado izquierdo en la misma vertical que el


lado izquierdo de otro control.

• alignParentLeft: atributo booleano para colocar el lado izquier-


do pegado a la parte izquierda del padre (layout ).

Colocación del lado derecho:

• toLeftOf: coloca el control a la izquierda de otro.

• alignRight: coloca el lado derecho en la misma vertical que el


lado derecho de otro control.

• alignParentRight: atributo booleano para colocar el lado derecho


pegado a la parte derecha del padre (layout ).

Hay otros atributos que no encajan directamente en ninguna de esas


categorías:

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
77

centerHorizontal: atributo booleano que, de ser cierto, hace que el


control quede centrado horizontalmente en el espacio del padre (la-
yout ).

centerVertical: atributo booleano que, de ser cierto, hace que el con-


trol quede centrado verticalmente en el espacio del padre (layout ).

centerInParent: atributo booleano que fusiona los dos anteriores.

alignBaseLine: alinea la líneas base del control con el de otro. Se


entiende por línea base al texto que contenga. Sirve por ejemplo para
poner seguidos una etiqueta y un botón que, en principio, tendrán altos
diferentes, pero que queremos que los textos estén alineados.

alignWithParentIfMissing: valor booleano para determinar que si el


control al que referenciamos en alguno de los atributos no está (su
visibilidad es gone), entonces debemos utilizar como referencia al padre
(el layout ).

Debe tenerse en cuenta que no hace falta jar todos los lados de
layout_width y
los controles, dado que aún se hará uso de los atributos
layout_height. Además, como se ha dicho, no se debe hacer un uso contra-
dictorio de los atributos (por ejemplo, no tiene sentido establecer al mismo
tiempo toLeftOf y centerHorizontal).
Para dar más posibilidades de colocación, recuerda que puedes también
modicar los atributos de margen de los controles, con lo que se consigue
primero alinear un control con respecto a otro, y luego desplazarlo gracias
al margen.

Por último, es importante evitar las referencias circulares, pero esto hace
referencia a referencias de lados iguales. Sí podremos, sin embargo, establecer
por ejemplo el lado izquierdo del control A en relación con el control B, y
a su vez el lado superior del B en relación con el del A. Cuando se hacen
construcciones de ese tipo, en el XML ¾qué control denimos antes? ¾El A o
el B? Dado que ambos referencian al otro, tendremos un potencial problema
por identicadores desconocidos. Si eso ocurre, se puede referenciar a un
control que aún no existe poniendo el + como en la creación de los controles.
A modo de ejemplo, para denir el layout de la gura:

usaríamos el código:

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


78 Capítulo 4. Un poco más sobre UIs

<TextView
android:id="@+id/tvNombre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/etNombre"
android:padding="@dimen/padding_medium"
android:text="@string/nombre"/>

<EditText
android:id="@+id/etNombre"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_toRightOf="@+id/tvNombre"
android:layout_alignParentTop="true"
android:ems="10"/>

¾Eres capaz de conseguir, usando un RelativeLayout, la disposición de


la práctica 3.15?

Práctica 4.2: Botones de radio

Los botones de radio (RadioButton) son controles en los que se da al


usuario la posibilidad de seleccionar una de varias opciones, y el hecho de

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
79

seleccionar una hace que las demás se desmarquen. Los botones de radio se
agrupan en RadioGroup, que se encarga de garantizar que sólo uno de los
botones de radio esté activo en un determinado momento.

La clase RadioGroup hereda de LinearLayout, por lo que podremos orien-


tarlo tanto horizontal como verticalmente. Se espera que en el interior de un
RadioGroup sólo haya objetos de la clase RadioButton, aunque no es obli-
gatorio.

Por su parte, el atributo más destacado de los RadioButton es checked,


que nos indica si está o no seleccionado.

Para saber en código qué opción está seleccionada, tenemos dos opcio-
nes. La primera es recorrer todos los RadioButton y mirar cuál tiene el
atributo checked a cierto. La segunda, mucho más práctica, es preguntarle
al RadioGroup el identicador del RadioButton seleccionado.

Crea un nuevo proyecto de nombre RadioGroup con los valores habi-


tuales.

Crea un interfaz como el de la gura.

Coloca una etiqueta oculta en la parte inferior de la ventana.

Añade código al evento del botón para que escriba en la etiqueta in-
ferior un comentario en relación con la selección (por ejemplo Vaya
RadioGroup
con la manzanita...). Para eso, puedes usar el método del
getCheckedRadioButtonId() (que tendrás que haber conseguido con
findViewById()), y luego hacer un switch para compararlo con cada
uno de los identicadores de los RadioButton.

También puedes ahorrarte el botón de aceptar, y mostar el mensaje

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


80 Capítulo 4. Un poco más sobre UIs

cada vez que se modica la selección. Para eso, puedes registrarte como
oyente del RadioGroup usando un código como:

RadioGroup rg = ...;
rg.setOnCheckedChangeListener(
new RadioGroup.OnCheckedChangeListener() {
public void onCheckedChanged(RadioGroup group,
int checkedId) {
// Hacer algo...
}
}
);

Práctica 4.3: Casillas de vericacion

Una casilla de vericación es un control tipo botón especial que tiene dos
posibles estados, marcado y desmarcado. Muestra un texto y una parte que
representa el estado. Para desarrollar su comportamiento, la clase CheckBox
hereda de Button.

Tiene los atributos android:text y android:checked que podríamos


esperar. Para saber desde código si está o no marcado, podemos usar boolean
isChecked(). Por último, para detectar los cambios de estado, tendremos
que registrarnos como observadores suyos:

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
81

CheckBox cb = ...
cb.setOnCheckedChangeListener(
new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
// Hacer algo! Sabemos quién ha sido pulsado
// y su estado
} // onCheckedChanged
} // new
); // setOnCheckedChangeListener

Crea un proyecto nuevo llamado CheckBox con las opciones habituales.


Dene la disposición de la ventana como la de la gura.

En la parte inferior, coloca una etiqueta invisible con el texto ½No! ½El
domingo no!, que se haga visible cuando se seleccione el domingo.

Práctica 4.4: ScrollView

En la práctica anterior, si se coloca el dispositivo en apaisado (landscape ),


las casillas de vericación no entran completamente, y no puede accederse a
ellas. Recuerda que para rotar el dispositivo en el AVD puedes usar Ctrl-F11
o Ctrl-F12 (o las teclas de los números 7 y 9 del teclado numérico sin el
bloqueo numérico activado).

Android nos proporciona un tipo especial de layout llamado ScrollView


que sirve para hospedar un único componente (que normalmente será otro
layout ) y que permite desplazarse verticalmente en él si ocupa más espacio
que el disponible en la ventana.

Sabiendo esto, modica la práctica para que se pueda utilizar también


con el móvil en posición apaisada.

Práctica 4.5: Mensajes rápidos: toasts

Para proporcionar al usuario información rápida, de poca importancia


y de manera poco intrusiva Android proporciona las que denomina toasts
(tostadas). El nombre hace referencia a las tostadas que saltan en un

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


82 Capítulo 4. Un poco más sobre UIs

tostador para avisar de que ya están listas, y luego vuelven hacia dentro y
desaparecen. En Android, son pequeños textos que se muestran brevemente
al usuario otando sobre la ventana principal, y desaparecen automática-
mente un instante después.

El esquema de uso es sencillo:

Context contexto = getApplicationContext();


int duracion = Toast.LENGTH_SHORT; // Toast.LENGTH_LONG

Toast toast = Toast.makeText(contexto,


R.string.<recurso>,
duracion);

toast.show();

En este código se ha asumido que la cadena la tenemos como recurso


(recomendado). También hay un método que recibe una cadena.

Crea un proyecto nuevo que muestre un botón que al ser pulsado muestre
un mensaje usando un Toast.

El método Toast.makeText() devuelve una instancia de la clase Toast


con una conguración predenida. Es posible crear instancias manualmente
y personalizar el layout que mostrarán, en cuyo caso aprovecharemos la clase
Toast para que se encargue de hacer visible nuestro aviso durante un breve
espacio de tiempo.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
83

Práctica 4.6: Mensajes de alerta con botones

Android permite la creación de cuadros de diálogo a través de su cla-


se Dialog. Además, para hacer cuadros de diálogo sencillos (por ejemplo,
para pedir conrmación de una acción), existe la clase AlertDialog que
nos simplica la tarea. Incluso podemos utilizar una clase auxiliar, llama-
da AlertDialog.Builder, que nos facilita la conguración del cuadro de
diálogo de alerta.

Por ejemplo, para mostrar un cuadro de diálogo con un único botón,


podemos usar el siguiente esquema:

AlertDialog.Builder alert = new AlertDialog.Builder(this);


alert.setMessage(R.string.<id>);
alert.setPositiveButton(android.R.string.ok, null);
alert.show();

Algunos comentarios:

Para establecer el texto a mostrar usamos setMessage(), pasando el


identicador del recurso o una cadena cableada.

Podemos establecer el título del cuadro de diálogo con setTitle(),


pasando también el identicador de una cadena o la cadena directa-
mente. Ten en cuenta que si el mensaje es corto, el resultado es poco
claro, porque el título y el mensaje podrían salir con el mismo tamaño
de letra.

El método setPositiveButton() tiene dos parámetros.

• El primero indica la cadena a mostrar. En el ejemplo, observa


que hemos utilizado un recurso predenido de Android, en con-
creto android.R.string.ok, que contiene el texto (localizado al
idioma del sistema) de Ok (aceptar). Esto nos ahorra trabajo de
localización a nosotros, aunque puede hacer que un usuario vea
toda la aplicación en español, salvo los botones de los diálogos
que le saldrán en su idioma nativo.

• El segundo recibe una instancia de una clase interna pública,


DialogInterface.OnClickListener a la que se llamará cuan-
do se pulse el botón. En este caso pasamos null porque no nos
interesa la recepción del mensaje.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


84 Capítulo 4. Un poco más sobre UIs

Podemos usar setNegativeButton(), que tiene el mismo prototipo


que setPositiveButton(), pero ahora está pensado para el botón de
no.

setNeutralButton() es similar al anterior, pero para el botón neutral


(por ejemplo para el tercer botón de Sí, No, Cancelar).

Podemos usar setIcon(<id>) para establecer un icono. Podemos apro-


vechar los iconos predenidos de Android.

Vamos a probarlo.

Haz un proyecto nuevo AlertDialog con un botón, que muestre un


cuadro de diálogo al ser pulsado que tenga únicamente un botón de
aceptar, y pruébalo.

Haz que se muestre un Toast justo después de lanzar el cuadro de


diálogo.

El resultado de la última ejecución sorprende. Lo más destacado de los


cuadros de diálogo en Android es que no son modales. Es decir, cuando se
muestra uno, no se congela la ejecución. Por eso habrás visto aparecer el
toast antes de cerrar el diálogo, puesto que la aplicación sigue ejecutándose
(aunque afortunadamente la entrada se bloquea para toda la actividad).

Esto signica que la construcción tradicional en Windows de:

DialogResult resultado = MessageBox(this, mensaje,


titulo, botones);

if (result == DialogResult.Yes) {
...
}

no puede hacerse en Android. Los cuadros de diálogo no devuelven un


valor. Para crear un funcionamiento de este tipo, tendrás que hacerlo de
manera asíncrona, metiendo en tu actividad un evento que se llame cuando
el usuario acepte en el cuadro de diálogo.

En el próximo capítulo. . .
En el próximo capítulo nos preocuparemos por invocar a una actividad
desde otra. Esto nos llevará a los intents y al chero AndroidManifest.xml.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
Capítulo 5

Intents, chero de maniesto y


componentes
Hazlo, o no lo hagas, pero no lo intentes
Maestro Yoda

Resumen: En este capítulo analizaremos los intents, la herramienta

que Android pone a disposición del programador para realizar invoca-

ciones a otros componentes del sistema. Esto nos llevará a analizar el

chero de maniesto de las aplicaciones y los permisos. Terminaremos

viendo un segundo tipo de componentes, los broadcast receivers.

Práctica 5.1: Una aplicación con dos ventanas

Crea un proyecto que se llame DosActivities usando el asistente. Pí-


dele que cree la actividad habitual. Puedes dejar el nombre predenido
MainActivity, pero cambia al menos en título a Dos activities.

Modica la actividad predenida quitándole la etiqueta  Hello world! 


habitual y poniendo un botón con el texto  Púlsame. Asocia el evento
de pulsación a un método onPulsame(View v). De momento, deja el
código vacío.

Crea una actividad nueva. Para eso utiliza, por ejemplo, la opción
del menú File - New - Other. En la ventana de diálogo que aparece,
despliega Android y selecciona Android Activity.

85
86 Capítulo 5. Intents, chero de maniesto y componentes

Verás aparecer el asistente de creación de actividad, que nos aparece


automáticamente en el asistente de creación de proyecto. Deja la nueva
actividad en el proyecto actual (DosActivities ), ponle como nombre
SecondaryActivity, y como título Segunda ventana.

Comprueba la aparición de algunas nuevas cadenas en el chero de


recursos strings.xml del proyecto. Modica lo que necesites para que
en esta segunda actividad se muestre el texto Esta es la segunda
ventana.

Vuelve al código de onPulsame() de la primera actividad. Para invo-


car desde ahí a la segunda, necesitamos enviar a Android un intent
(intento), en el que indiquemos qué queremos lanzar exactamente:

Intent i = new Intent(this, SecondaryActivity.class);


startActivity(i);

Ejecuta la aplicación y pulsa el botón. Verás aparecer la segunda ven-


tana, con el texto que hayas puesto.

Pulsa atrás. Verás que la ventana desaparece y se vuelve a la principal.

Un intent es una descripción abstracta de una operación que queremos


que se realice, por lo que puede verse como un mensaje asíncrono. Es recogido
por Android, que lo procesa y lanza el componente (activity ) que se haya
pedido.

En este ejemplo, estamos haciendo uso de lo que se conoce como modo


de invocación explícito : hemos congurado con total exactitud la actividad
que queremos que se lance, dando su clase Java.

Es importante ser consciente de que, al igual que ocurría al abrir un


AlertDialog, el envío de un intent no detiene la ejecución de la actividad.
Para comprobarlo, puedes añadir la generación de un toast tras el envío del
intent y lo verás aparecer sobre la segunda ventana.


Práctica 5.2: AndroidManifest.xml: Actividades

Tras realizar la práctica 5.1, si vas al lanzador de aplicaciones del dispo-


sitivo donde la hayas probado verás dos iconos referenciando a la aplicación,
uno llamado Dos activities, y otro Segunda ventana. Si pulsas sobre el se-
gundo icono, verás que se abre la segunda ventana.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
87

El culpable de que aparezcan ambas es el chero AndroidManifest.xml,


que podrás encontrar en la raíz del proyecto (o del .apk). Contiene informa-
ción esencial sobre la aplicación, que el sistema debe tener disponible antes
de plantearse hacer uso de ella.

Si abres el chero, verás que Eclipse organiza toda la información que


contiene en cuatro pestañas diferentes (Manifest, Application, Permission
y Instrumentation ). También muestra una quinta pestaña con la vista del
chero en su versión en XML. No siempre hay una relación directa entre los
elementos XML y las pestañas. Por ejemplo, aunque la pestaña Application
sirve para denir todo lo referente al elemento XML <application> y sus
subelementos, la pestaña Permissions sirve para denir elementos de tipo
<permissions> y <uses-permissions> entre otros.

El número de opciones que podemos congurar en el chero de mani-


esto es muy grande, por lo que no analizaremos todas. Sí analizaremos sin
embargo las que nos coloca el asistente en la versión predenida.

<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="libro.azul.dosactivities"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="3"
android:targetSdkVersion="15" />

<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >

....

</application>

</manifest>

<manifest>: es el raíz del XML. Tiene tres atributos importantes:

• package: contiene el nombre del paquete que sirve como identi-


cador de la aplicación. No puede repetirse entre aplicaciones, pues
se considerarían la misma.

• versionCode: número de versión. Debe ser siempre un número


entero, e incrementarse de modo que una versión posterior tenga

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


88 Capítulo 5. Intents, chero de maniesto y componentes

siempre un número mayor. Es un código interno no visible al


usuario.

• versionName: nombre de la versión. Es el que verá el usuario, y


puede contener cualquier texto.

<uses-sdk>: mantiene la información sobre los niveles de API ne-


cesarios por la aplicación. En el atributo minSdkVersion se indica
el mínimo nivel de API necesario para ejecutar la aplicación, y en
targetSdkVersion la versión más alta contra la que se ha probado la
aplicación.

<application>: contiene información sobre la propia aplicación. Es el


elemento que más ocupa. Tiene gran cantidad de atributos opcionales,
aunque Eclipse sólo nos congura tres:

• icon: enlace al recurso con el icono.

• label: nombre de la aplicación.

• theme: tema o estilo de los controles.

Observa que el valor de los tres atributos hace referencia a un recurso


utilizando el esquema habitual @<tipo>/<identificador>.

Dentro del elemento <application> aparecen subelementos deniendo


los componentes de la aplicación. De hecho, los atributos icon, label y
theme que hemos visto sirven para darles valores por defecto, pero pueden
ser sobreescritos en la denición de los componentes.

Por el momento, el único tipo de componente que conocemos son las


actividades. En el chero de maniesto creado en la práctica 5.1 puedes
comprobar que aparecen dos elementos de tipo <activity>, dado que hemos
creado dos actividades:

<activity
android:name=".MainActivity"
android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondaryActivity"
android:label="@string/title_activity_secondary" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
89

<category android:name="android.intent.category.LAUNCHER" />


</intent-filter>
</activity>
El atributo más importante de los elementos XML que denen compo-
nentes es android:name, que indica el nombre de la clase que lo implementa.
Cuando Android tiene que lanzar una actividad, busca el nombre de la clase
en ese atributo, crea una nueva instancia suya y la ejecuta. En concepto, es
similar a la referencia a la clase de un applet en una página HTML.

El nombre de la clase debe estar totalmente cualicado (debe incluir el


paquete). Si se quiere omitir, se puede comenzar el nombre con un punto, y se
package del elemento <manifest>.
asumirá el paquete denido en el atributo

Eclipse también nos ha establecido un nuevo atributo label para indicar


el nombre de la actividad. Gracias a ello hemos podido diferenciar las dos
ventanas en el lanzador de aplicaciones, al tener cada una su nombre.

Además, dentro de <activity> nos ha añadido un elemento adicional


<intent-filter> o ltro de intents. Este elemento sirve para indicar en qué
circunstancias de lanzará esa actividad.

Modica el chero de maniesto y comenta (encerrando entre <!-- y


--> la declaración completa de la segunda actividad.

Ejecuta la aplicación de nuevo. Verás que falla al pulsar el botón para


abrir la segunda ventana.

Mira la salida del logcat. Verás que ha saltado una excepción del tipo
ActivityNotFoundException. Sólo las actividades registradas en el
chero de maniesto pueden ser ejecutadas, incluso aunque la clase
exista realmente. Por tanto deben declararse todas.

Observa que en el lanzador de aplicaciones, el icono a la segunda acti-


vidad ya no aparece.

Vuelve al chero de maniesto, descomenta la declaración de la acti-


vidad .SecondaryActivity y comenta (o elimina) únicamente el ele-
mento interno <intent-filter>.
Ejecuta de nuevo la aplicación. Verás que ahora sí funciona.

Ve al lanzador de aplicaciones del dispositivo, y comprueba que el icono


de la segunda actividad sigue sin aparecer.

El signicado de:

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

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


90 Capítulo 5. Intents, chero de maniesto y componentes

es esta actividad puede ser lanzada para realizar su tarea principal


(MAIN) desde el lanzador de aplicaciones (LAUNCHER).

Al eliminar esta sección, quitamos esa posibilidad de inicio, y Android


no incluye el icono en el lanzador.

Práctica 5.3: Enviando datos a la segunda ventana

Cuando conguramos un intent, podemos añadirle información adicional


que podrá ser leída, en última instancia, por la actividad que Android termine
lanzando. Esa información no es más que una lista de pares atributo-valor.

Intent tiene varios métodos so-


Desde el lado del invocante, la clase
brecargados con el nombre putExtra(atributo, valor). El atributo
siempre será una cadena, pero el valor puede tener una gran cantidad
de tipos.

Desde el punto de vista del invocado, la actividad tiene acceso al in-


tent que ocasionó su invocación (o al menos a una copia) a través
del método getIntent(). Una vez que lo tiene, puede usarse cual-
quiera de los métodosget*Extra() (por ejemplo getIntExtra() o
getStringExtra()) para conseguir los valores establecidos por el in-
vocante. Todos esos métodos tienen el atributo buscado (en la lista de
pares) como primer parámetro y, algunos, tienen un segundo paráme-
tro con el valor por defecto que deseamos obtener si no hay ningún
atributo con ese nombre en los datos enviados por el invocante.

Con esta información:

Haz una copia del proyecto de la práctica anterior y ponle el nombre


DosActivitiesPutExtra.
Modica la actividad principal para que pregunte al usuario su nombre.

En el evento del botón, obtén el nombre del cuadro de texto. Si está


vacío, muestra un toast indicando el problema. En otro caso, envía a
la segunda actividad el nombre a través del atributo extra de clave
nombre.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
91

Intent i = new Intent(this, SecondaryActivity.class);


i.putExtra("nombre", elNombre); // Sacado del EditText
startActivity(i);

En la segunda ventana, haz que se se muestre Hola, <nombre>, des-


de la segunda ventana. Para eso, añade en el método onCreate() el
acceso al atributo pasado como parámetro.

String nombre = getIntent().getStringExtra("nombre");


String saludo = getResources().getString(R.string.<idStr>);
saludo = String.format(saludo, nombre);
TextView etiqueta = (TextView) findViewById(R.id.<idLabel>);
etiqueta.setText(saludo);

Práctica 5.4: Recogiendo valores de la segunda ventana

Al igual que desde la actividad invocante podemos enviar datos a la


nueva, también podemos enviar datos en el sentido inverso. Para eso:

Al ejecutar el intent, usaremos el método startActivityForResult().


La segunda ventana creará un nuevo intent que enviará de vuelta a la
actividad invocante usando el método setResult().
La primera actividad recogerá de manera asíncrona el resultado en su
método onActivityResult().

La invocación a startActivityForResult() tampoco es bloqueante. La


ejecución continúa; el resultado lo recibiremos por una llamada asíncrona al
método onActivityResult(). Dado que una actividad podría querer abrir
varias actividades en diferentes momentos, y siempre se recibirá el resultado
a través del método startActivityForResult(), éste recibe un entero (que
normalmente se denomina requestCode) que será enviado de vuelta por
Android en la invocación a onActivityResult() correspondiente. De ese
modo, la actividad puede utilizar códigos numéricos diferentes en la apertura
de diferentes actividades y así conocer qué respuesta debe procesar en cada
momento.

Haz una copia del proyecto DosActivitiesPutExtra y dale el nombre


DosActivitiesForResult.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


92 Capítulo 5. Intents, chero de maniesto y componentes

Modica la segunda actividad para que incluya un nuevo cuadro de


texto y un botón, donde el usuario podrá introducir un saludo de vuel-
ta. Por ejemplo, si el usuario introdujo Android como nombre, deberá
ver algo similar a:

Modica MainActivity.java para que haga uso del mencionado méto-


do startActivityForResult(). Para eso, dene una constante nueva
para ser usada como marca para diferenciar la actividad que envía
un valor de vuelta:

private static final int SECONDARY_ACTIVITY_TAG = 1;

y sustituye la invocación original con:

startActivityForResult(i, SECONDARY_ACTIVITY_TAG);

Añade a SecondaryActivity.java el código para el evento de la pul-


sación del botón, donde se enviará de vuelta a la actividad invocante
el nombre recibido. Además, se cerrará la actividad con el método
finish().

String respuesta;
EditText et = (EditText) findViewById(R.id.etRespuesta);

respuesta = et.getText().toString();

Intent datos = new Intent();


datos.putExtra("respuesta", respuesta);
setResult(RESULT_OK, datos);
finish();

Fíjate que la respuesta está compuesta de dos elementos:

• Un código de respuesta (en este caso RESULT_OK). Podemos tam-


bién devolver RESULT_CANCELED o, en general, cualquier valor nu-
mérico mayor o igual que la constante RESULT_FIRST_USER.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
93

• Un intent nuevo con datos extra.

De nuevo en MainActivity.java, crea el nuevo método al que llamará


Android, onActivityResult(). En él, mostraremos en un toast el sa-
ludo que ha escrito el usuario en la segunda ventana, recogiéndolo del
intent que recibimos como parámetro. Ten en cuenta que si el usua-
rio cierra (con el botón volver) la actividad secundaria, Android nos
avisará de todos modos, indicando RESULT_CANCELED como resultado:

@Override
protected void onActivityResult(int requestCode,
int resultCode,
Intent data) {

String respuesta;
if ((resultCode == RESULT_CANCELED) ||
(data.getStringExtra("respuesta").isEmpty()))
respuesta = getResources().getString(
R.string.antipatico);
else
respuesta = data.getStringExtra("respuesta");

Context contexto = getApplicationContext();


Toast.makeText(contexto, respuesta,
Toast.LENGTH_LONG).show();

} // onActivityResult

Ejecuta el programa y pruébalo. Comprueba que al volver desde la


segunda ventana, en la primera aparece el toast con el nombre. Si en la
segunda ventana no escribes nada o pulsas Volver, la primera ventana
te llama antipático.

Práctica 5.5: Llamando a una actividad de otra aplicación

Ya se ha mencionado que cuando se envía un intent, es Android quién


lo recoge, lo analiza y se encarga de lanzar la actividad correspondiente. En
principio esto parece demasiado trabajo para lanzar una ventana de la misma
aplicación.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


94 Capítulo 5. Intents, chero de maniesto y componentes

El objetivo, sin embargo, es conseguir que sea igual lanzar una actividad
implementada en nuestra aplicación, que una ventana de otra.

En esta práctica vamos a hacer una nueva aplicación que será una copia
de la anterior, pero que tendrá únicamente la primera actividad, e invocará
a la segunda de la original.

Haz una copia del proyecto DosActivitiesForResult y dale como


nombre InvocacionRemota.
Elimina la segunda actividad. Para eso:

• Borra el chero SecondaryActivity.java


• Borra el chero activity_secondary.xml
• En el chero de maniesto, elimina el elemento <activity> que
declaraba la actividad SecondaryActivity.
Renombra el paquete de la aplicación, para que sea diferente al original
y podamos instalar ambas en el mismo dispositivo. Para eso:

• En el package explorer, selecciona el paquete de la práctica ante-


rior, libro.azul.dosactivities, pulsa con el botón derecho y
elige Refactor - Rename...

• Pon como nombre libro.azul.invocacionremota.


• En el chero de maniesto, modica el atributo package del ele-
mento <manifest> y pon el nuevo nombre.

Modica el nombre de la aplicación que verá el usuario. Para eso, en


el chero strings.xml
• Modica la cadena app_name y pon  InvocacionRemota.

• Modica la cadena title_activity_main y pon  Invocación


remota.
Dado que hemos copiado la actividad principal, para diferenciarla de la
original modica el layout de la nueva que hemos creado aquí y ponle
android:background="#80ff80".
Eclipse mostrará un error en MainActivity.java, en la línea de código
fuente donde se congura el objeto de la clase Intent. Originalmente
lo congurábamos pasándole directamente la clase de la actividad a
invocar. Ahora esa clase no la tenemos disponible, por lo que tenemos
que indicar la actividad a partir de su nombre. Sustituye el código
original:

Intent i = new Intent(this, SecondaryActivity.class);

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
95

por:

Intent i = new Intent();


i.setClassName("libro.azul.dosactivities",
"libro.azul.dosactivities.SecondaryActivity");

y deja el resto igual (establecimiento del nombre e invocación a la


actividad).

Ejecuta la práctica 5.4 para estar seguro de que el dispositivo donde lo


pruebes tiene la última versión. Cierra el programa.

Ejecuta ahora el proyecto nuevo de invocación remota, introduce un


nombre y pulsa el botón. Al hacerlo, obtendrás un error. Mira en el
logcat y verás que se trata de un problema de seguridad. La aplicación
libro.azul.dosactivities no permite la invocación externa a su ac-
tividad secundaria. Para permitirlo, debe decirlo explícitamente en su
chero de maniesto.

Modica el chero de maniesto de la práctica anterior (de nombre


libro.azul.dosactivities). En la declaración de la actividad secun-
daria, añade un intent-filter que dé permiso para la ejecución ex-
terna:

<activity
android:name=".SecondaryActivity"
android:label="@string/title_activity_secondary" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>

Fíjate que no hemos añadido la categoría LAUNCHER, por lo que la


segunda actividad seguirá sin aparecer en el lanzador de aplicaciones.

Vuelve a ejecutar la práctica original para que se envíe al dispositivo


el cambio en su maniesto.

Ejecuta ahora la nueva, y verás que la invocación remota sí funciona.

Ten en cuenta que la actividad principal está en una aplicación, y la


segunda está en otra. Android protege las aplicaciones ejecutándolas en pro-
cesos diferentes. Vamos a comprobarlo:

Detén las dos aplicaciones. Para eso, si estás ejecutándolas en un AVD,


puedes usar la consola:

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


96 Capítulo 5. Intents, chero de maniesto y componentes

$ ./adb shell
# ps
# kill <pid de dosactivities>
# kill <pid de invocacionremota>

Si estás usando un dispositivo físico, ve a la conguración de las aplica-


ciones, selecciona cada una de ellas y pulsa, si está habilitado, el botón
Forzar detención.

Usando el adb shell, asegúrate de que ninguna está lanzada (esto po-
drás hacerlo también sobre un dispositivo físico, aunque no seas root).
Deja abierta la conexión con el shell.

Lanza Invocación remota. Usa ps en el shell para ver la aparición del


proceso. Asegúrate de que el proceso de la práctica anterior Dos Acti-
vities no aparece.

Introduce un nombre y pulsa el botón.

Vuelve al shell, ejecuta ps y nota la aparición del nuevo proceso para


esa actividad.

Práctica 5.6: Invocaciones implícitas con Intents

En los dos ejemplos anteriores hemos invocado una segunda actividad


usando el llamado método explícito, en el que se proporciona la clase exacta
que deberá procesar nuestra solicitud.

Sin embargo, los intents se han diseñado para usarlos de una manera
mucho más potente llamada uso implícito, en el que no se proporciona qué
actividad debe procesar la solicitud. El objetivo es que los intents creen un
lenguaje en el sistema para que una aplicación pueda decir quiero llamar
a casa, y el sistema sepa qué actividad es capaz de ejecutar ese deseo. El
nombre intent (intención) encaja con esa idea, en el que las aplicaciones
notican su deseo de que algo ocurra.

Con esta idea en mente, los intents poseen, además del nombre de la
clase que hemos usado y los datos extra (setClassName() y putExtra())
varios campos más. Ten en cuenta que en las invocaciones implícitas nunca
se establece el nombre de la clase, y el sistema hará uso del resto de campos
para deducir qué actividad es capaz de atender la solicitud. En concreto:

Acción : es una cadena que indica la acción que se desea realizar. Se


establece con setAction(), o incluso pasándola en el constructor.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
97

Datos : una URI sobre la que ejecutar la acción. Se establece con el


método setData(Uri u). Si la URI se posee a través de una cadena,
será necesario convertirla, por lo que la construcción más habitual será
setData(Uri.parse("...")).

Tipo de los datos : si a partir de la Uri no es posible identicar el tipo


de datos, podemos indicárselo explícitamente al intent con setType().
El parámetro es una cadena, indicando el tipo MIME (por ejemplo
image/*). Podemos establecer la URI y el tipo simultáneamente con
setDataAndType(Uri, String). En ocasiones, querremos especicar
un tipo de datos pero no una URI.

Categorías : un intent puede especicar varias categorías para la soli-


citud. Cada categoría es una cadena.

De todos estos campos, el más importante es la acción. Android especica


una gran cantidad de acciones, declaradas como constantes dentro de la clase
Intent. Podemos considerar a las acciones como el nombre de un método.
Es la acción la que determina cómo se utilizará el resto de la información
contenida en el intent.

Como se ha dicho, dentro de la clase Intent se denen bastantes cons-


tantes con cadenas de acciones predenidas que comprende el sistema. En
esta práctica vamos a probar algunas de ellas, enviando diferentes solicitudes
al sistema Android utilizando intents para ver algunas de las posibilidades
que nos ofrece. Por comodidad, reutilizaremos el mismo proyecto, y nos li-
mitaremos a ir cambiando la conguración del intent que generaremos.

Crea un nuevo proyecto en Eclipse y llámalo IntentImplicito. Elige


las opciones habituales.

Modica la ventana para que tenga un único botón que ejecute el


método onBotonEjecutar(). Por ejemplo, suponiendo que mantienes
el RelativeLayout inicial:

<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="@dimen/padding_medium"
android:text="@string/ejecutarIntent"
android:onClick="onBotonEjecutar" />

donde la cadena @string/ejecutarIntent se habrá denido como


Ejecutar intent implícito.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


98 Capítulo 5. Intents, chero de maniesto y componentes

En el código Java, crea siguiente método:

public void onBotonEjecutar(View v) {


Intent i = new Intent();
i.setAction(Intent.ACTION_POWER_USAGE_SUMMARY);
startActivity(i);
}

También podríamos haber usado la inicialización directamente en el


constructor:

public void onBotonEjecutar(View v) {


Intent i = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
startActivity(i);
}

Ejecuta el programa. En este caso, es preferible que lo ejecutes en un


dispositivo físico.

Verás que la acción solicitada sirve para lanzar la actividad que muestra
el resumen de uso de la batería. Nos muestra una estimación del tiempo
que durará la batería, y los porcentajes de uso de batería de las diferentes
1
aplicaciones disponibles . Dado que los AVD no simulan la batería con tanta
exactitud, en este caso el resultado en el emulador será mucho más pobre.

La acción seleccionada no necesita información adicional, por lo que no


hemos necesitado establecer más campos.

Hagamos uso ahora de una acción que necesita una URI.

Comenta, si quieres conservarlo, el código anterior donde congurába-


mos el intent.

Congura el intent para que la acción sea ACTION_VIEW, y en la URI


pon una dirección Web:

public void onBotonEjecutar(View v) {


Intent i = new Intent();
/*
// Lanzamos la actividad que muestra el resumen
// de uso y estado de la batería.
i.setAction(Intent.ACTION_POWER_USAGE_SUMMARY);
/**/

1
El resultado podría variar dependiendo del teléfono concreto.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
99

// Lanzamos un navegador Web.


i.setAction(Intent.ACTION_VIEW);
i.setData(Uri.parse("http://www.programa-me.com"));

startActivity(i);
}

Ejecuta la aplicación. Si no tienes tarifa de datos en el móvil, utiliza


mejor un AVD.

Al hacer uso de la acción ACTION_VIEW, el sistema interpreta que quere-


mos ver la URI que le pasamos como datos, por lo que nos abre un navegador
Web y nos la muestra.

Es interesante observar que la acción ACTION_VIEW está sobrecargada,


y dependiendo de la URI que pongamos se abrirá una aplicación u otra.

Comenta la conguración del intent de la prueba anterior.

Establece la siguiente conguración:

// Abrir la edición de un SMS


i.setAction(Intent.ACTION_VIEW);
i.setData(Uri.parse("sms:5554433"));

Ejecuta el programa. Verás aparecer la aplicación para escribir mensa-


jes cortos, dado que en la URI hemos puesto sms: en lugar de http://.

Esto nos demuestra que en algunas ocasiones Android hace uso única-
mente de la acción para decidir qué aplicación lanzar (como ocurría en el
ejemplo de las estadísticas de uso de la batería), pero a veces necesita anali-
zar también la URI. Precisamente eso es lo que ocurre aquí; como la acción
ACTION_VIEW es muy genérica, Android necesita analizar la URI y al ver que
hace referencia a mensajes cortos lanza la aplicación adecuada.

En este caso, podríamos haber lanzado el editor de mensajes SMS de otro


modo. En lugar de establecer los datos, podríamos haber establecido el tipo
MIME a mensaje de texto, y haber proporcionado el número de teléfono a
través de los datos extra:

i.setAction(Intent.ACTION_VIEW);
i.setType("vnd.android-dir/mms-sms");
i.putExtra("address", "5554433");

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


100 Capítulo 5. Intents, chero de maniesto y componentes

Si ejecutas la aplicación, el resultado será exactamente el mismo. Las


aplicaciones receptoras de intents especican el protocolo que compren-
den, y que deberá ser utilizado por los usuarios que quieran invocarlas. No
obstante, esta prueba nos demuestra que Android puede utilizar también el
tipo (en lugar de la URI) para seleccionar la actividad que se hará cargo de
atender un intent.

Aunque en el ejemplo de los SMS tengamos dos alternativas, normalmen-


te habrá solo una. Además, los datos extra (que ponemos con putExtra())
no pueden ser utilizados por Android para seleccionar la actividad receptora
del intent. Esa es la razón de que se consideren datos extra: Android los
ignora durante la toma de la decisión.

En este sentido se debe tener en cuenta que las aplicaciones pueden crear
2
sus propias acciones para que otras las utilicen . Incluso, pueden reutilizar
las acciones existentes, y diferenciarse de las demás a través de la URI o del
tipo MIME. Por ejemplo, la aplicación Skype informa a Android de que es
capaz de procesar intents con la acción VIEW_ACTION, pero con URIs del tipo
skype:<usuario>.
La conclusión de esto es que la información más importante para una
acción irá siempre en la URI, y es el uso que se recomienda. Los datos extra
deben utilizarse para información adicional que no afecte a la elección de la
actividad destino.

Modica la aplicación para añadir al intent información extra:

i.putExtra("sms_body",
"Hola! Estoy aprendiendo Android :-)");

Lanza la aplicación, pulsa el botón, y verás que se abre la ventana de


edición de SMS, con un mensaje parcialmente escrito. La aplicación de
los SMS mira si se ha establecido en los datos extra algo asociado al
atributo sms_body y lo establece como texto inicial.

Si das hacia atrás, para cerrar la escritura del mensaje, la actividad


funciona como normalmente y dejará el mensaje en los borradores.

Esta opción funciona tanto en la conguración del intent a través de la


URI como a través del tipo MIME.

La invocación a otras actividades también podemos utilizarla para recibir


datos. Aunque no vamos a entrar en ello, sí podemos probar que, al menos,
la actividad se lanza. Además, en este ejemplo, lo importante no es la URI
(que no hay) sino únicamente el tipo de datos:

2
Para eso, necesitarán conocerla cadena de la acción. Fíjate que nosotros aqui hemos
estado usando las constantes denidas en la clase Intent, que serán cadenas.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
101

Comenta el código de conguración del intent que hiciste en la prueba


anterior.

Establece el siguiente:

i.setAction(Intent.ACTION_GET_CONTENT);
i.setType("image/*");

Lanza la aplicación y pulsa el boton. Verás aparecer la galería mul-


timedia, mostrando las imágenes y fotos que tengas en el dispositivo.
En este caso es preferible utilizar un móvil físico, porque los AVDs no
traen ninguna.

Selecciona una foto. Observa que no se muestra. En su lugar, se vuelve


a nuestra actividad. Si hubiéramos usado startActivityForResult(),
habríamos recibido una URI de vuelta con la localización de la imagen
seleccionada.

Modica el tipo MIME pedido, y pon audio/*. Relanza la aplicación


y nota que ahora se solicita un chero de audio.

Para llamar por teléfono no usamos ACTION_VIEW como en el caso de los


mensajes cortos:

Comenta la conguración del intent de la última prueba.

Congúralo para usar la acción ACTION_DIAL, y establece en la URI el


número de teléfono al que quieres llamar:

i.setAction(Intent.ACTION_DIAL);
i.setData(Uri.parse("tel:5554433"));

Ejecuta la aplicación y pulsa el botón.

Observa que se abre la ventana de marcación, con el teléfono indicado


en la URI ya escrito.

Si lo ejecutas sobre un teléfono físico y pones un número de teléfono


que esté en la agenda, la aplicación buscará el nombre y te lo mostrará.

La prueba anterior no realizaba la llamada. Tan sólo la conguraba


para que el usuario la aceptara. Podemos iniciar la llamada usando una
acción diferente.

Copia la conguración del intent de la última prueba, y comenta una


de las dos para que puedas conservarla.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


102 Capítulo 5. Intents, chero de maniesto y componentes

Modica la acción para usar ACTION_CALL en lugar de ACTION_DIAL.

Lanza la aplicación y pulsa el botón.

La aplicación falla.

Mira el contenido del logcat. Observa que se debe a un problema de


seguridad y permisos.

Práctica 5.7: Permisos

Como medida de protección, las aplicaciones que necesitan realizar tareas


que pueden perjudicar al sistema (por ejemplo, impedir que entre en modo de
suspensión) o al usuario (por ejemplo, realizar llamadas o detectar la posición
a través del GPS) deben declararlo en su chero de maniesto. Cuando se
instala una aplicación nueva, el usuario debe conceder explícitamente todos
esos permisos. De otro modo, la aplicación no se instalará.

Una vez que la aplicación se ha instalado, el usuario no vuelve a ser


preguntado por el uso de esos permisos, lo que evita la aparición de mensajes
continuos que el usuario dejaría pronto de leer y aceptaría por costumbre.

Si una aplicación intenta hacer uso de una característica de Android de-


licada para la que no ha pedido permiso explícito en su chero de maniesto,
Android la detendrá generando una excepción de seguridad, tal y como he-
mos visto.

En la práctica anterior, abre el chero de maniesto.

Ve a la pestaña Permissions, y pulsa Add...

Selecciona Uses permission, que se utiliza para solicitar permiso de uso


de una característica protegida.

Despliega los posibles permisos disponibles. Selecciona el que tiene por


nombre android.permission.CALL_PHONE.

En la vista XML, comprueba que ha aparecido un nuevo elemento:

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

Vuelve a ejecutar la aplicación. Verás que ahora sí se realiza la llamada.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
103

Modica el teléfono y pon un número que conozcas. Ejecútalo en tu


teléfono y verás que la llamada se realiza, y aparece el nombre corres-
pondiente de la agenda.

Práctica 5.8: Exportación e instalación

En la práctica 5.7 creamos una aplicación que requería un permiso deter-


minado, y sin embargo cuando la ejecutamos no nos preguntó si queríamos
darle ese permiso. Por tanto, ¾dónde está la protección de la que alardea
Android?

Si no nos preguntó fue porque lanzamos la aplicación con la depuración


por USB habilitada. Durante el desarrollo, el dispositivo no nos molesta con
las preguntas de los permisos, pues asume que sabe lo que estamos haciendo.

En esta práctica vamos a crear un .apk que podríamos distribuir (o


publicar en Google Play ) y lo instalaremos, para ver la solicitud de concesión
de permisos.

En Eclipse, selecciona la opción del menú File - Export.

En el cuadro de diálogo, escoge Android - Export Android Application.

Selecciona el proyecto que quieres exportar (el de la práctica 5.7).

Crea el certicado digital que usarás para rmar el proyecto. El certi-


cado te identica como desarrollador, y debe ser el mismo para todas
las versiones futuras de la aplicación. De otro modo se considerarían
aplicaciones diferentes, dicultando la actualización.

Elige la localización y nombre del chero .apk que vas a generar.

Copia el .apk al móvil. Para eso:

• Deshabilita la depuración por USB.

• Conecta el móvil al ordenador.

• Copia el .apk a algún lugar de la memoria interna.

Ya puedes desconectar el móvil del ordenador.

Ve a la conguración de las aplicaciones y marca Fuentes desconoci-


das. De otro modo, el teléfono se negará a instalar un .apk que no
venga de Google Play u otros markets reconocidos por él.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


104 Capítulo 5. Intents, chero de maniesto y componentes

Ve al gestor de aplicaciones y asegúrate de que la versión que se ins-


taló para depurar desde Eclipse no está ejecutándose. Puedes incluso
desinstalarla.

Abre el explorador de archivos del móvil, y selecciona el .apk que


copiaste antes, para iniciar la instalación.

El móvil te muestra los permisos que el .apk declara que necesita.


Verás que solicita un servicio con coste (llamar a números de teléfono
directamente). Acepta la instalación.

Ejecuta el programa y comprueba que funciona correctamente.

Para evitar problemas en el futuro:

• Desinstala la aplicación.

• Borra el .apk.
• Desactiva la opción Fuentes desconocidas.

Práctica 5.9: Receptores de mensajes del sistema

Hasta ahora hemos utilizado únicamente actividades. En Android, las


aplicaciones están creadas de componentes, y las actividades son únicamente
uno de los tipos de componentes posibles.

Es importante darse cuenta de que desde el punto de vista del programa-


dor, el sistema no ejecuta procesos, sino componentes. En nuestra aplicación
no hay programa principal (igual que no lo hay en los applets ). En su lugar,
en el chero de maniesto debemos declarar los componentes que proporcio-
namos, y bajo qué circunstancias deben ser lanzados.

Los componentes se ejecutan siempre a través de intents. Incluso el lan-


zador de aplicaciones del teléfono utiliza un intent para lanzar los programas
(¾recuerdas la acción MAIN y la categoría LAUNCHER?). El sistema controla el
ciclo de vida de los componentes y de los procesos, eliminando los menos
importantes cuando surgen necesidades adicionales de memoria.

En esta práctica vamos a ver un nuevo tipo de componente, llamado


broadcast receivers o receptores de mensajes del sistema. Estos componentes
se lanzan automáticamente cuando se produce algún evento del sistema en
el que están interesados. Para eso, en el chero de maniesto de la aplicación
deben indicar de qué sucesos quieren ser noticados, y sólo cuando ocurran
Android instanciará el componente y le dará la oportunidad de ejecutarse.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
105

Estos componentes no poseen interfaz de usuario. Algunas veces intro-


ducen avisos en la barra de noticaciones. También pueden, naturalmente,
ocasionar la apertura de una actividad enviando un intent, para que el usua-
rio pueda actuar en consecuencia.

Crea un proyecto nuevo en Eclipse llamado BroadcastReceiver. Como


nombre del paquete pon libro.azul.broadcastreceiver. En este caso no
crees una actividad.

El proyecto creado estará vacío. Crea una clase nueva con el menú File
- New - Class.

En el cuadro de diálogo que se muestra, escribe como nombre del


paquete libro.azul.broadcastreceiver (el mismo que pusiste en
el nombre del paquete de la aplicación). Como nombre de la cla-
DeteccionEnchufado, y como superclase escribe (o bus-
se escribe
ca)android.content.BroadcastReceiver, el equivalente a la clase
android.app.Activity de la que heredan siempre nuestras activida-
des. Asegúrate de que la casilla de vericación Inherited abstract met-
hods está marcada para que Android incluya sus prototipos automáti-
camente.

El código fuente que nos crea Eclipse tiene denido un método llamado
onReceive(), sin código, que es abstracto en la clase padre. Ese método
será llamado automáticamente por Android cuando ocurra un suceso de
nuestro interés. Observa que recibe como segundo parámetro un intent
con la información que ha ocasionado la invocación. Esto demuestra
que los mensajes del sistema también se envían a través de intents. En
este caso, la acción del intent llevará la información sobre el suceso
ocurrido.

Vamos a hacer un receptor de mensajes del sistema que escriba en el


registro (logcat ) un aviso cada vez que se conecta o desconecta el cable
de alimentación. Para eso, escribe el siguiente código en el método
onReceive():

if (i.getAction().equals(
Intent.ACTION_POWER_CONNECTED))
android.util.Log.i(TAG, "Cargador conectado");
else if (i.getAction().equals(
Intent.ACTION_POWER_DISCONNECTED))
android.util.Log.i(TAG, "Cargador desconectado");

Añade también el atributo estático TAG:

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


106 Capítulo 5. Intents, chero de maniesto y componentes

private static final String TAG = "BroadcastReceiver";

Antes de poder ejecutar la aplicación, necesitamos declarar el nuevo


componente en el chero de maniesto. Eclipse no lo habrá hecho por
nosotros como hizo con la actividad, dado que no hemos usado un
asistente del ADT, sino uno genérico de Eclipse. Abre el chero de
maniesto, y dentro del elemento <application> añade la denición
del nuevo componente:

<receiver android:name=".DeteccionEnchufado">
</receiver>

El nombre de la clase comienza con punto, por lo que se da por supuesto


que pertenece al mismo paquete que la aplicación.

Nos falta denir en qué sucesos estamos interesados. Para eso tenemos
que hacer uso de un elemento <intent-filter>, que establece ltros
a los intents que deberían llegarnos. Dentro del elemento <receiver>
que acabas de crear, añade:

<intent-filter>
<action android:name=
"android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name=
"android.intent.action.ACTION_POWER_DISCONNECTED"/>
</intent-filter>

Esto indica que nos interesan los mensajes relacionados con la conexión
y desconexión del cargador. Fíjate que en el código fuente utilizamos
la constante Intent.ACTION_POWER_DISCONNECTED. En el chero de
maniesto debemos usar su valor (que siempre es una cadena).

La aplicación la ejecutaremos sobre un AVD para no tener que conec-


tar y desconectar el móvil físico a una toma de corriente para probar
nuestro programa. Lanza, si no lo habías hecho ya, un AVD.

Para estar seguros del estado de partida del AVD, usa el interfaz en
consola y desconecta el cable de alimentación. Para eso, conéctate por
telnet con el puerto par en el que está escuchando el AVD (recuerda
que puedes saber cual es mirándolo en el título de la ventana del AVD):

$ telnet localhost 55xx


...
OK
power ac off
OK

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
107

El comando power ac off desconecta el cable de alimentación.

No cierres esta conexión; la vamos a utilizar más adelante.

Ejecuta la aplicación sobre el AVD. Verás que no ocurre nada. Al n


y al cabo, nuestra aplicación no declara ninguna actividad principal.

En una ventana nueva, abre una sesión con el shell y lista los procesos.

$ ./adb
# ps

Asegúrate de que no está lanzada nuestra aplicación.

En el mismo shell, muestra el registro:

# logcat

Verás aparecer mucha información de sucesos, y se quedará bloqueado


para mostrar a continuación cualquier otro mensaje que llegue.

Vuelve a la ventana donde tenías la conexión de telnet abierta. Simula


ahora la conexión del cable de alimentación:

power ac on
OK

Ve a la ventana del shell donde se está mostrando la salida de logcat.


Observa la aparición de nuestro mensaje:

I/BroadcastReceiver( PID): Cargador conectado

Repite el proceso, desconectando la alimentación (con power ac off).


Comprueba que aparece el mensaje de log correspondiente.

Detén la salida de logcat pulsando Ctrl-C. Ejecuta ps para comprobar


la aparición de nuestro proceso (cuyo PID coincide con el que aparecía
en el logcat ).

Puedes eliminarlo con kill, y volver a simular la conexión y desco-


nexión del cable de alimentación. Android volverá a ejecutarlo para
noticarle el cambio, y el proceso reaparecerá (con otro PID).

Esta práctica ejemplica que los receptores de mensajes son lanzados


directamente por el sistema y no tienen que mantener un demonio en eje-
cución. Al registrar en el chero de maniesto los mensajes que nos interesan,
Android lanzará el componente cuando ocurran.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


108 Capítulo 5. Intents, chero de maniesto y componentes

Notas bibliográcas
A continuación se proporcionan algunas direcciones web con información
sobre cada uno de los temas tratados en este capítulo:

Archivo de maniesto

http://developer.android.com/guide/topics/manifest/manifest-intro.
html

Descripción de los intent e intent lters :

http://developer.android.com/guide/components/intents-filters.html

Firma de aplicaciones

http://developer.android.com/tools/publishing/app-signing.html

Receptores de mensajes del sistema

http://developer.android.com/reference/android/content/BroadcastReceiver.
html

En el próximo capítulo. . .
En el próximo capítulo veremos algunas de las posibilidades para guardar
y recuperar datos en Android.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
Capítulo 6

Acceso a datos
640 KB deberían ser sucientes para
todo el mundo
Erróneamente atribuída a Bill Gates

Resumen: En este capítulo veremos algunas de las herramientas que


Android pone a disposición del programador para almacenar a datos

persistentes.

Práctica 6.1: Reconstrucción de una actividad

El primer modo de almacenar datos que vamos a ver puede que, incluso,
ni siquiera deba ser categorizado como tal, dado que es el propio sistema
quien hace la mayor parte del trabajo.

Cuando Android detecta una necesidad de memoria que debe atender


y no tiene suciente, podría eliminar actividades que estén ocultas. Si más
adelante el usuario vuelve a ellas, las volverá a crear, y éstas tendrán que ser
capaces de reconstruirse en el estado original, para que el usuario no note
que fueron eliminadas temporalmente.

La destrucción y reconstrucción de las actividades ocurre también en


situaciones menos límite. En concreto, cuando el usuario gira el teléfono,
Android también reinicia la actividad. El objetivo no es otro que darla la
oportunidad de reconstruirse utilizando un layout nuevo, especíco de la
nueva orientación.

En el guardado y recuperación del estado de la actividad entran en juego


dos métodos:

onSaveInstanceState(Bundle outInstance): es un método que de-


bemos sobreescribir en la actividad, y que será invocado automática-

109
110 Capítulo 6. Acceso a datos

mente por Android antes de destruirnos. El parámetro es un bundle,


donde podremos guardar las parejas de atributo-valor que considere-
mos oportuno con lo mínimo imprescindible para reconstruir nuestro
estado.

onCreate(Bundle savedInstanceState): es el método al que Android


invoca al iniciar una actividad. El parámetro será null cuando se haga
una creación fresca. Sin embargo, si se está reconstruyendo la activi-
dad a partir de un estado anterior, el parámetro contendrá una copia
del bundle que rellenamos en la llamada a onSaveInstanceState().

Para practicar, vamos a recuperar la aplicación Pulsame de la prácti-


ca 3.10 y a guardar su estado cuando sea necesario para que, al girar el
móvil, el usuario no note la destrucción y recreación de la actividad.

Haz una copia del proyecto PulsameLayout y llámalo PulsameEstable.

Ejecútala. Pulsa varias veces sobre el botón. Comprueba que, efectiva-


mente, el contador aumenta.

Rota el móvil. Verás que la cuenta se reinicia.

Vamos a meter la persistencia en la aplicación. Lo único que tene-


mos que mantener para reconstruir el estado es el valor de la variable
_numVeces.

En el código en Java, añade un nuevo atributo estático que guardará la


constante con el nombre que usaremos como atributo para el bundle :

private final static String STATE_NUM_VECES = "numVeces";

Ahora, sobreescribe el método onSaveInstanceState() e introduce en


_numVeces.
el bundle del parámetro el valor del atributo

@Override
public void onSaveInstanceState(Bundle outInstance) {

// Llamamos al método de la superclase.


super.onSaveInstanceState(outInstance);

// Guardamos el único valor que nos interesa.


outInstance.putInt(STATE_NUM_VECES, _numVeces);

} // onSaveInstanceState

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
111

En el método onCreate(), vamos a tener que recuperar ese mismo


valor, y actualizar la etiqueta del botón en función a él cuando estemos
reconstruyendo la actividad. En el método botonPulsado() del evento
de pulsación del botón ya tenemos código con el que establecemos la
etiqueta. Para no repetirlo, lo refactorizamos creando un nuevo método
que cambie la etiqueta en función del valor de _numVeces:

private String getEtiquetaBoton() {

android.content.res.Resources res = getResources();


String numPulsados;
numPulsados = res.getQuantityString(
R.plurals.numPulsaciones,
_numVeces, _numVeces);

return numPulsados;

} // getEtiquetaBoton

Modica el método del evento botonPulsado() para que utilice el nue-


vo método:

public void botonPulsado(View v) {

// Incrementamos el contador...
++_numVeces;

// ... y actualizamos la etiqueta del botón.


Button b = (Button) v;
b.setText(getEtiquetaBoton());

} // botonPulsado

Ahora, modicamos el método onCreate(). Diferenciaremos si esta-


mos realizando una inicialización nueva, o a partir de datos guardados
previamente dependiendo de si el parámetro Bundle que recibimos es
o no null:

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_pulsame);

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


112 Capítulo 6. Acceso a datos

if (savedInstanceState != null) {
_numVeces = savedInstanceState.getInt(
STATE_NUM_VECES);

Button b = (Button) findViewById(R.id.boton);


b.setText(getEtiquetaBoton());
} // if (savedInstanceState != null)

} // onCreate

Ejecuta de nuevo el programa. Pulsa varias veces el botón y rota el


móvil. Comprueba que, ahora sí, se mantiene el contador correctamen-
te.

Práctica 6.2: Mejorando el juego Adivina mi número

El juego Adivina mi número que hicimos en la práctica 3.15 tampoco


reaccionaba bien si Android se veía obligado a eliminar temporalmente la
actividad, por ejemplo ante la rotación del dispositivo.

En esta práctica el código que se muestra es únicamente informativo,


dado que cada uno puede haber implementado el juego de una manera dife-
rente.

Haz una copia del proyecto con el juego Adivina mi número. Llámalo
AdivinaMiNumeroEstable.

Lánzalo. Haz un par de intentos de adivinar el número.

Escribe un tercer número. En lugar de dar a aceptar, pulsa volver


para cerrar el teclado en pantalla, y rota el móvil.

La partida se ha reiniciado. Sin embargo, en el cuadro de texto se man-


tiene el último número que has escrito. Esto nos indica que la superclase
android.app.Activity guarda por sí misma el estado de los controles
en los que típicamente el usuario introduce datos (como ocurre con
los EditText) en su método onSaveInstanceState(), y establece esos
mismos valores en su método onCreate(). Ésa es una de las razones
por las que resulta importante llamar siempre a la superclase en los
métodos del ciclo de vida de las actividades.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
113

Para esta aplicación, necesitamos reconstruir el estado de nuestras va-


riables donde guardamos el número que hay que adivinar (asumiremos
que se llama _numElegido) y el número de intentos hechos (asumire-
mos _numIntentos). Reconstruir el atributo con el objeto generador
de números aleatorios no es importante. También necesitamos saber
el estado de la aplicación, para actualizar el interfaz. En concreto, si
la partida ha terminado tendremos que ocultar algunos controles y
mostrar el botón de jugar otra partida. Además, si la partida no ha
terminado, necesitamos establecer el texto de la etiqueta superior para
indicar si el último número era mayor o menor.

Para conseguir todo esto, vamos a seguir algunos atajos. Vamos a guar-
dar:

• Último número elegido (el que se está usando en la partida en


curso). Si la partida ha terminado, la variable _numElegido la
pondremos a -1, lo que nos servirá de marca en el caso de que
tengamos que reiniciar la actividad.

• Número de intentos que llevamos.

• Texto que estamos mostrando en la etiqueta superior. Podríamos


guardar el último número introducido y congurar la etiqueta
manualmente, pero guardando el texto nos ahorramos trabajo.

Modica la aplicación para que cuando se detecte el nal de la partida


se ponga _numElegido en -1.

Dene tres constantes estáticas de cadena con los nombres que utili-
zarás en el bundle de mantenimiento de estado para los tres datos que
vamos a guardar:

private static final String STATE_NUM_ELEGIDO = "numElegido";

private static final String STATE_NUM_INTENTOS="numIntentos";

private static final String STATE_MENSAJE = "mensajeActual";

Implementa el método onSaveInstanceState() en el que guardes esos


tres valores.

Modica el método onCreate(). Al principio, tendrás que mantener la


conguración del GUI y establecimiento de los listeners. La inicializa-
ción de la partida (para elegir un número aleatorio) tendrás que hacerla
únicamente si no hemos recibido bundle (estamos iniciando una acti-
vidad desde cero). En otro caso, tendrás que establecer las variables

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


114 Capítulo 6. Acceso a datos

_numElegido y _numIntentos. Además, si la partida está terminada,


tendrás que ocultar los controles de introducción del número y mostrar
el botón de jugar de nuevo, y si la partida está en marcha tendrás que
establecer el texto de la etiqueta del mensaje superior y del número de
intentos. El resultado debería ser un código similar al siguiente:

if (savedInstanceState == null) {
// Estamos iniciándonos desde cero.
// Comenzamos una partida.
reiniciaPartida();
}
else {
// Tenemos que reconstruirnos desde el bundle.
_numElegido = savedInstanceState.getInt(
STATE_NUM_ELEGIDO);
_numIntentos = savedInstanceState.getInt(
STATE_NUM_INTENTOS);
if (_numElegido == -1)
// La partida está terminada.
partidaAcabada();
else {
// La partida está a mitad. Ponemos el texto de la
// etiqueta superior.
TextView tv =
tv = (TextView)findViewById(R.id.etiquetaSuperior);
tv.setText(
savedInstanceState.getString(STATE_MENSAJE));
// Actualizamos la etiqueta del número de intentos
actualizarIntentos();
}
}

Práctica 6.3: Almacenando preferencias

El estado de la aplicación que se almacena gracias al bundle recibido


en el método onSaveInstanceState() lo gestiona el propio sistema y su
tiempo de vida es corto; únicamente sirve para reconstruir la actividad a
su estado anterior si fue Android quién la destruyó. Pero si la actividad se
eliminó porque, por ejemplo, el usuario pulsó volver, entonces la creación
de nuestra actividad partirá de cero.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
115

Si queremos que algo de nuestro estado perdure, necesitaremos preocu-


parnos nosotros mismos de guardarlo. Un ejemplo habitual es el almacena-
miento de preferencias de la aplicación, que son leídas durante la inicializa-
ción.

Android nos facilita la tarea a través de la clase SharedPreferences.


En esencia, es similar a un bundle, que se lee y escribe en un chero XML
cuya localización, lectura y escritura es gestionada automáticamente por el
sistema.

Cada aplicación tiene un directorio particular para guardar sus cheros


de preferencias. Además, es posible tener más de un chero, diferenciándolos
por nombre. Para conseguir uno de estos almacenes de preferencias basta
con:

SharedPreferences preferencias;

preferencias = getSharedPreferences(<nombre>, <tipoAcceso>);

El tipo de acceso será uno de los siguientes:

Context.MODE_PRIVATE: el chero será accesible únicamente para la


aplicación.

Context.MODE_WORLD_READABLE: el chero podrá ser leído por el resto


de aplicaciones.

Context.MODE_WORLD_WRITABLE: el chero podrá ser escrito por el res-


to de aplicaciones.

Puedes combinar los dos últimos concatenándolos con la OR a nivel de


bits (|).

Una vez conseguido el objeto, obtener los atributos que contiene es similar
a como lo haríamos con un bundle :

int v = preferencias.getInt(<atributo>, <valorPorDefecto>);

El valor por defecto será el devuelto en el caso de que no exista en las


preferencias el atributo solicitado.

La escritura de las preferencias requiere la ayuda de una clase adicional:

SharedPreferences.Editor editor;
editor = preferencias.edit();

editor.putInt(<atributo>, <valor>);
...

editor.commit();

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


116 Capítulo 6. Acceso a datos

Naturalmente, aunque en ambos ejemplos se ha utilizado un entero como


tipo de datos, tenemos disponibles muchos otros.

Por conveniencia, la clase Activity proporciona, además, un método


getPreferences(<modoApertura>) que devuelve un objeto de preferencias
asociado a su propio nombre. La intención es que se utilice ese método para
conseguir un objeto en el que guardar las preferencias particulares de la
actividad, y que se use el método con nombre cuando se estén guardando
datos que vayan a ser leídos desde cualquier parte de la aplicación.

Para esta práctica vamos a hacer una actividad nueva que simulará un
cuadro de diálogo de selección de opciones. Mostrará varios botones de radio,
y el usuario deberá elegir una de las opciones que ofrecen. Tendrá además
dos botones, uno de aceptar y otro de cancelar. Si se pulsa el de aceptar,
se guardará la selección, de modo que la próxima vez que se lance la activi-
dad aparecerá seleccionada. Si se pulsa cancelar, se cerrará la actividad sin
guardar nada:

Puedes partir de cero creando el proyecto o, si lo preeres, comenzar


basándote en la práctica 4.2. En ese caso:

• Haz una copia del proyecto original y renómbralo a Preferencias.


• Cambia el nombre del paquete de libro.azul.radiobutton a
libro.azul.preferencias. Puedes usar la refactorización pro-
porcionada por Eclipse; asegúrate de que también lo cambia en el
chero de maniesto.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
117

• Cambia el nombre de la aplicación y de la actividad en el chero


strings.xml.
• Modica el layout para quitar la etiqueta inferior con el mensaje
de respuesta original, añade un ScrollView externo, y modica
los textos de los controles. Cambia también el identicador de los
botones de radio.

• Añade el botón de Cancelar.

Ejecuta la aplicación. Comprueba que, gracias a la implementación


predenida del método onSaveInstanceState(), se mantiene auto-
máticamente la última selección cuando se rota el teléfono. Prueba a
cambiar la selección y rotar varias veces para estar seguro.

Sin embargo, si seleccionas cualquier opción (diferente a la de partida)


y cierras el programa con el botón Volver, la última selección se olvida
y al lanzar el programa de nuevo comenzará con el primer botón de
radio seleccionado.

En el evento del botón de cancelar, llama a finish() para cerrar la


actividad sin hacer nada más.

Dene un atributo estático constante con el nombre del atributo que


utilizaremos para guardar la última selección del usuario.

private final static String PREFERENCIA_MELODIA = "melodia";

En el evento del botón de aceptar mete el código necesario para guar-


dar en el chero de preferencias particular de la actividad el último
valor seleccionado. Por simplicidad, vamos a guardar directamente el
identicador del control que está marcado. Normalmente, dado que es-
te valor se utilizaría en otros lugares de la aplicación, declararíamos un
tipo enumerado para cada alternativa y guardaríamos su valor:

public void onAceptar(View v) {


SharedPreferences preferencias;

preferencias = getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor;
editor = preferencias.edit();

RadioGroup rg;
rg = (RadioGroup) findViewById(R.id.preferenciasMelodia);

editor.putInt(PREFERENCIA_MELODIA,

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


118 Capítulo 6. Acceso a datos

rg.getCheckedRadioButtonId());

editor.commit();

finish();

} // onAceptar

En el código del método onCreate() añade código para leer de las


preferencias el botón de radio seleccionado la última vez que se aceptó
el cuadro de diálogo. Si no se lee ninguno, elegiremos por defecto el
primer botón de radio.

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

SharedPreferences preferencias;
preferencias = getPreferences(Context.MODE_PRIVATE);

int id = preferencias.getInt(PREFERENCIA_MELODIA,
R.id.silencio);
RadioButton rb;
rb = (RadioButton) findViewById(id);

rb.setChecked(true);

} // onCreate

Ejecuta la aplicación en un AVD. Comprueba que tras pulsar aceptar,


se consigue que, efectivamente, en ejecuciones posteriores se recupere
la selección, y que, sin embargo, al pulsar cancelar no ocurre lo mismo.

Usando adb, abre un shell con el AVD en el que hayas ejecutado el


1
programa .

En el shell, muevete por la jerarquía de directorios hasta llegar a


/data/data/libro.azul.preferencias/shared_prefs.

Comprueba la existencia del chero MainActivity.xml (el nombre de


la actividad).

1
Esta prueba no podrás hacerla si has ejecutado la aplicación en un dispositivo físico,
dado que el shell no tiene priviliegios de administrador.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
119

Haz un cat del chero para ver la estructura del XML. Comproba-
rás que dentro está la entrada para nuestro atributo. El valor es el
identicador del botón de radio.

Si utilizas ls -l, podrás ver los permisos del chero. Comprueba que
el usuario y el grupo tienen acceso de lectura y de escritura. Ambos
son el usuario particular que Android ha asociado a nuestra aplicación
(app_xx).

MODE_PRIVATE
Vuelve al código java y sustituye las dos apariciones de
por MODE_WORLD_READABLE. Vuelve a ejecutar el programa, modica la
selección y vuelve a mirar los permisos del chero. Verás que el resto
del mundo tiene ahora acceso de lectura.

Por último, utiliza MODE_WORLD_WRITABLE. Repite el proceso y com-


prueba que ahora los permisos permiten al resto del mundo escribir.

Práctica 6.4: Leyendo un chero de los recursos

El directorio res/ de los .apk contiene cheros que son automáticamente


gestionados por Android. Durante la construcción de la aplicación, se genera
el conocido chero R.java, con identicadores a todos los recursos denidos,
y el programador puede utilizarlos a través de diferentes llamadas al API
de Android. Además de encargarse de su carga y procesamiento, también es
capaz de escoger la versión del recurso que más se adapte a la conguración
particular del dispositivo, como hemos visto con los recursos de tipo cadena.

Dentro de res/ podemos crear un directorio particular, res/raw/ (cru-


do ), donde meter cheros propios que no serán interpretados por Android
(no son cadenas, ni imágenes, ni layouts ) pero que podremos abrir muy fá-
cilmente utilizando un identicador de recurso como en el resto de los casos.
Además, si tenemos varios directorios raw con cualicadores (por ejemplo
raw-en/) el sistema se encargará de proporcionarnos el chero que está en
el directorio más apto para la conguración del sistema.

Para abrir uno de estos cheros se utiliza un código como el siguiente:

InputStream is = getResources().openRawResource(R.raw.<id>);

Fíjate que esto nos proporciona automáticamente un objeto de la clase


InputStream, que es estándar de Java y que representa un ujo de bytes
para ser consumidos. Ese stream podremos envolverlo en clases adicionales

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


120 Capítulo 6. Acceso a datos

(como BufferReader o Scanner) para que nos facilite la lectura, al igual que
haríamos con una aplicación en Java tradicional.

En esta práctica vamos a hacer una actividad que se parecerá a la de


la práctica 6.3, mostrando un conjunto de botones de radio con opciones de
melodías. En este caso, sin embargo, los títulos de las melodías las leeremos
de un chero situado en res/raw. Para eso, abriremos el chero y leeremos
línea por línea, y por cada una añadiremos un nuevo botón de radio a la
actividad a través de programación.

Ten en cuenta que no pretendemos imitar el funcionamiento de la prác-


tica 6.3. En concreto, no vamos a intentar guardar la selección a modo de
preferencias, dado que al generar dinámicamente los botones de radio no
tendremos sus identicadores y necesitaríamos algún otro mecanismo para
guardar la opción elegida.

Crea un nuevo proyecto y denomínalo CargaResRaw.

ScrollView como
Congura el layout de la actividad para que tenga un
raíz con un LinearLayout dentro, que, a su vez, posea un RadioGroup
vacío y el botón de aceptar.

<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RadioGroup
android:id="@+id/rgOpciones"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
</RadioGroup>
<Button
android:id="@+id/bAceptar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@android:string/ok"
android:onClick="onAceptar"/>
</LinearLayout>
</ScrollView>

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
121

En el Package Explorer de Eclipse, pulsa con el botón derecho sobre


el directorio res/ y crea un nuevo directorio (New - Folder ) y llámala
raw.

Pulsa con el botón derecho sobre el nuevo directorio, y crea un nuevo


chero (New - File ) y llámalo melodias.txt.

Abre el chero recién creado, y mete varias líneas de texto con lo que
serían las melodías disponibles.

Silencio
Andrómeda
Basic Bell
Cassiopeia
Chime
A cricket chirps
Crossing walk
Cuisine
Down hill
Emotive sensation
Eridani
Faint
Happy synth
Illuminator
Lira

Abre el chero de código fuente en Java de la actividad. En el evento


onCreate() vamos a añadir el código para abrir el chero, leer línea
por línea, y por cada una añadir un nuevo botón de radio con el nom-
bre de la melodía como texto. Para eso, crea un objeto nuevo de la
clase RadioButton, establece el texto, y añádelo al radio group de la
actividad que has metido en el layout. Cuando hayas metido todos los
botones, marca como seleccionado el primero de ellos.

public void onCreate(Bundle savedInstanceState) {


super.onCreate(savedInstanceState);
setContentView(R.layout.activity_carga_res_raw);

RadioGroup rg;
rg = (RadioGroup) findViewById(R.id.rgOpciones);

InputStream is;
is = getResources().openRawResource(R.raw.melodias);
Scanner sc = new Scanner(is);

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


122 Capítulo 6. Acceso a datos

while (sc.hasNextLine()) {
String melodia = sc.nextLine();
RadioButton rb;
rb = new RadioButton(this);
rb.setText(melodia);
rg.addView(rb);
}
((RadioButton)rg.getChildAt(0)).setChecked(true);
} // onCreate

Ejecuta el programa. Verás que se muestra un botón de radio por cada


línea que hayas metido en el chero.

Para comprobar que Android aplica también los cualicadores a la


elección del chero, crea un nuevo directorio res/raw-en, copia el -
chero original sobre él, y modifícalo. Para probar que, efectivamente,
Android leerá del nuevo chero es suciente con que cambies una de
las líneas (por ejemplo, sustituye Silencio por Silent).
Ejecuta de nuevo la aplicación, asegurándote de que tienes la con-
guración del AVD con el idioma en inglés. Comprueba que ahora el
chero que se abre es el nuevo.

Ten en cuenta que esta aplicación es sólo un ejemplo para probar la carga
desde cheros de recursos crudos. La utilidad que pretende tener (hacer las
veces de preferencias del teléfono para elegir una melodía) requeriría todavía
bastante más trabajo de programación.

Además, el uso de cheros en res/raw tiene la ventaja de ser gestionado


automáticamente por Android y sus identicadores, pero eso signica que no
resulta sencillo averiguar qué cheros tenemos, ni podemos tener subdirec-
torios. Si queremos hacer este tipo de tareas (y que nuestros cheros sigan
estando empaquetados en el .apk) deberíamos utilizar el directorio assets/,
aunque entonces quedamos fuera de los identicadores del chero R.java.


Práctica 6.5: Leyendo y escribiendo en cheros externos

El uso de los cheros en el directorio res/raw o assets/ del .apk tienen


el problema de que son de sólo lectura. Si queremos utilizar cheros para
guardar información de cualquier tipo y no nos sirven las preferencias que
vimos en la práctica 6.3, tendremos que utilizar otro mecanismo.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
123

En concreto, para escribir en cheros particulares de las actividades, éstas


nos proporcionan el método:

FileOutputStream openFileOutput(String nombre, int tipoAcceso);

que nos devuelve una instancia de una clase tradicional de Java que
representa un stream de un chero. Como antes, podremos utilizar clases
auxiliares para recubrirlo y que nos facilite la escritura.

El parámetro llamado tipoAcceso es equivalente al que tenía el método


getSharedPreferences() que vimos en la práctica 6.3. Además, se añade
la posibilidad de utilizar Context.MODE_APPEND, en cuyo caso todo lo que
escribamos se añadirá por el nal.

Para la lectura, podemos utilizar el método simétrico:

FileInputStream openFileInput(String nombre);

Para ponerlos en práctica, vamos a realizar una aplicación que muestre


un libro de visitas. En la parte superior mostrará un cuadro de texto en el
que el usuario podrá escribir su nombre, que se irá acumulando a los visi-
tantes. Cada nuevo nombre quedará registrado en el chero de visitantes, y
cuando se arranque el programa se mostrarán todos los que se han registrado
anteriormente.

Crea un proyecto nuevo y llámalo LibroDeVisitas.


Congura el layout de la actividad para que tenga el aspecto de la
gura. Tendrá tres etiquetas, y un cuadro de texto.

<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/padding_medium" >

<TextView
android:id="@+id/tvNombre"
android:layout_width="wrap_content"

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


124 Capítulo 6. Acceso a datos

android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignBaseline="@+id/etNombre"
android:padding="@dimen/padding_medium"
android:text="@string/introduceNombre"
tools:context=".LibroVisitas" />

<EditText
android:id="@+id/etNombre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tvNombre"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:inputType="text"/>

<TextView
android:id="@+id/tvVisitantes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/etNombre"
android:layout_margin="@dimen/padding_medium"
android:text="@string/visitantesAnteriores"/>

<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="@id/tvVisitantes">
<TextView
android:id="@+id/etVisitantes"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:inputType="textMultiLine"
android:gravity="top"/>
</ScrollView>
</RelativeLayout>

En el método onCreate() de la actividad, busca el cuadro de texto y


registra un listener onNuevoNombre() para que se llame cuando el usua-
rio pulse intro en él. Además, al terminar deberá llamar a un método
actualizarVisitantes() que lea del chero los nombres registrados
y los añada a la etiqueta de las visitas.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
125

public void onCreate(Bundle savedInstanceState) {


super.onCreate(savedInstanceState);
setContentView(R.layout.activity_libro_visitas);

EditText et;
et = (EditText) findViewById(R.id.etNombre);
et.setOnKeyListener(
new android.view.View.OnKeyListener() {
public boolean onKey(View v,
int keyCode,
android.view.KeyEvent event) {
// Han pulsado una tecla...
if ((event.getAction() ==
android.view.KeyEvent.ACTION_DOWN) &&
(keyCode ==
android.view.KeyEvent.KEYCODE_ENTER)) {
// Ha sido "intro"
onNuevoNombre();
return true;
}
return false;
}
});

// Actualizamos la lista de visitantes a partir


// de los datos del fichero.
actualizarVisitantes();
} // onCreate

Dene una constante nueva donde mantendremos el nombre del chero


con los visitantes.

private final static String NOMBRE_FICHERO = "visitantes.txt";

En el método onNuevoNombre, abre dicho chero para escritura por el


nal, y añade el nombre leído del cuadro de texto:

protected void onNuevoNombre() {

EditText et;
et = (EditText) findViewById(R.id.etNombre);

String nuevoNombre;

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


126 Capítulo 6. Acceso a datos

nuevoNombre = et.getText().toString();

if (nuevoNombre.trim().equals("")) {
muestraMensaje(R.string.errorNombre);
return;
}

try {
FileOutputStream fos;
fos = openFileOutput(NOMBRE_FICHERO,
Context.MODE_PRIVATE |
Context.MODE_APPEND);

java.io.OutputStreamWriter out;
out = new OutputStreamWriter(fos);
out.write(nuevoNombre + "\n");
out.close();
muestraMensaje(R.string.bienvenido);
actualizarVisitantes();
}
catch (Exception e) {
muestraMensaje(R.string.errorRegistro);
}

InputMethodManager imm;
imm = (InputMethodManager)getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(et.getWindowToken(), 0);

et.requestFocus();
et.setText("");

} // onNuevoNombre

El método actualizarVisitantes() tendrá que hacer la labor inversa.


Leerá el chero completamente, y añadirá cada nombre en la etiqueta
de los visitantes:

protected void actualizarVisitantes() {

StringBuffer strBuf = new StringBuffer();


TextView tv;
tv = (TextView) findViewById(R.id.etVisitantes);

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
127

try {
String visitante;
FileInputStream fis;
fis = openFileInput(NOMBRE_FICHERO);
Scanner scanner = new Scanner(fis);
if (scanner.hasNextLine()) {
visitante = scanner.nextLine();
strBuf.append(visitante);
}
while (scanner.hasNextLine()) {
visitante = scanner.nextLine();
strBuf.append("\n" + visitante);
} // while
scanner.close();
tv.setText(strBuf.toString());
} // try
catch (Exception e) {
// Algo fue mal :-(
muestraMensaje(R.string.errorVisitantes);
} // try-catch

} // actualizarVisitantes

Sólo nos falta el método auxiliar que muestra un mensaje en un toast :

protected void muestraMensaje(int id) {

Toast t;
String mensaje;
mensaje = getResources().getString(id);
t = Toast.makeText(this, mensaje, Toast.LENGTH_LONG);
t.show();

} // muestraMensaje

Añade la denición de todas las cadenas utilizadas.

Prueba la aplicación en un AVD e introduce varios nombres.

Abre y cierra la aplicación varias veces, y constata que los nombres


anteriores se mantienen.

Abre una sesión de shell en el AVD.

$ ./avd shell

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


128 Capítulo 6. Acceso a datos

Navega por los directorios hasta llegar al directorio de la aplicación,


/data/data/libro.azul.librovisitas/files
Mira el contenido y verás el chero visitantes.txt. Comprueba los
permisos de acceso, coherentes con el MODE_PRIVATE usado, y mira el
contenido para ver los visitantes.

Con este método de guardar cheros, la ruta también es predenida. En


concreto, todos los cheros creados así se almacenan en el directorio local de
la aplicación. En ninguno de los dos métodos (de apertura para leer y para
escribir) podemos utilizar símbolos de ruta en el nombre (/).

Notas bibliográcas
A continuación se proporcionan algunas direcciones web con información
sobre cada uno de los temas tratados en este capítulo:

Recreación de una actividad tras su destrucción automática

http://developer.android.com/training/basics/activity-lifecycle/
recreating.html
Entrada/salida en Java

http://docs.oracle.com/javase/tutorial/essential/io/
Descripción general sobre recursos (entre ellos los de raw/)
http://developer.android.com/guide/topics/resources/providing-resources.
html
Descripción general de las posibilidades de almacenamiento en An-
droid.

http://developer.android.com/guide/topics/data/data-storage.html
No hemos visto cómo acceder a cheros arbitrarios. Si tienes curiosidad,
puedes comenzar por la clase Environment.
http://developer.android.com/reference/android/os/Environment.html

En el próximo capítulo. . .
En el próximo capítulo veremos el acceso a algunos de los sensores y
hardware especíco habituales en los dispositivos móviles.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
Capítulo 7

Sensores y otro hardware


Si los sentidos no son veraces, toda
nuestra razón es falsa
Lucrecio

Resumen: En este capítulo veremos las posibilidades de vibración de


los móviles Android, el acceso a los datos de los sensores y un pequeño

ejemplo de grácos.

Práctica 7.1: Vibración

La posibilidad de vibrar ha formado parte del mundo de los dispositivos


móviles desde sus inicios. Android nos proporciona un API para contro-
lar la vibración desde su primera versión, a través de una clase de nombre
android.os.Vibrator.
El acceso a una instancia de esa clase se realiza a través del contexto
(entorno de la aplicación). En concreto, se considera un servicio del sistema
(igual que lo son, por ejemplo, el servicio de alarma o el de localización). Por
tanto, debemos pedirle al contexto actual dicha instancia usando el método
getSystemService().
Afortunadamente, las actividades son contextos, por lo que no tendremos
que preocuparnos en conseguirlo. En cualquier método de nuestra actividad
podremos hacer:

Vibrator vibrator;
vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);

Los métodos de la clase son:

129
130 Capítulo 7. Sensores y otro hardware

void vibrate(long miliseconds): hace que vibre durante los mili-


segundos indicados. No es bloqueante.

void cancel(): detiene la vibración en marcha.

void vibrate(long[] pattern, int repeat): vibra con un patrón.


El array del primer parámetro representa el patrón a reproducir, y
va alternando el número de milisegundos en reposo y en activo,
empezando en reposo.

Además, podemos pedir que se repita el efecto en ciclo. Para eso, en el


segundo parámetro se debe especicar la posición del patrón a la que
volver (siendo 0 la primera). Si no se quiere ejecución en un bucle, se
debe especicar -1.

Es importante tener en cuenta que la repetición del patrón siempre em-


pieza en pausa . Por tanto, para que el ciclo funcione como se espera,
el segundo parámetro debería ser siempre par (donde están los tiempos
de pausa en el array), pues de otro modo convertiremos lo que en la
primera reproducción fue tiempo de espera en tiempo de vibración y
viceversa.

boolean hasVibrator(): está disponible a partir del API 11 (An-


droid 3.0). Indica si el dispositivo puede o no vibrar.

Para probar la vibración, vamos a hacer una pequeña aplicación que


reproduce una canción y pregunta cual es.

Crea un nuevo proyecto y llámalo Vibracion.

En el método onCreate(), obtén el objeto Vibrator solicitándoselo a


la actividad, y guárdalo en un atributo privado del objeto, para evitar
tener que recuperarlo contínuamente. Activa la vibración durante 100
milisegundos a modo de bienvenida.

@Override
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vibracion);

_vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);


_vibrator.vibrate(100);

} // onCreate

private Vibrator _vibrator;

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
131

1
Prueba la aplicación . Fallará.

Activar la vibración se considera una actividad prilegiada, por lo que


debemos solicitar permiso de uso. Modica el chero de maniesto y
añade:

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

También lo puedes hacer desde la parte visual de Eclipse, abriendo


el chero, y en la pestaña  Permissions , añadiendo uno de tipo Uses
Permission, con la etiqueta anterior (android.permission.VIBRATE).

Vuelve a probar la aplicación. Ahora sí debería funcionar.

Manipula el layout para conseguir la ventana de la gura.

<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/padding_medium">

<TextView
1
Como podrás suponer, el emulador de AVD no emula la vibración, por lo que tendrás
que usar un dispositivo físico.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


132 Capítulo 7. Sensores y otro hardware

android:id="@+id/tvCancion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/padding_medium"
android:layout_alignBaseline="@+id/bReproducir"
android:layout_alignParentLeft="true"
android:text="@string/queCancionEs"/>

<Button
android:id="@+id/bReproducir"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tvCancion"
android:layout_alignParentRight="true"
android:padding="@dimen/padding_medium"
android:text="@string/reproducir"
android:onClick="onReproducir"
tools:context=".Vibracion" />

<ScrollView
android:id="@+id/scrollView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/bReproducir"
android:layout_above="@+id/bAceptar">
<RadioGroup
android:id="@+id/rgOpciones"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="@dimen/padding_medium">
<RadioButton
android:id="@+id/ParaElisa"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ParaElisa"
android:checked="true"/>
<RadioButton
android:id="@+id/champions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/champions"/>
<RadioButton
android:id="@+id/cumple"
android:layout_width="wrap_content"

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
133

android:layout_height="wrap_content"
android:text="@string/cumple"/>
<RadioButton
android:id="@+id/wakaWaka"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/wakaWaka"/>
<RadioButton
android:id="@+id/yellowSubmarine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/yellowSubmarine"/>
</RadioGroup>
</ScrollView>

<Button
android:id="@+id/bAceptar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:onClick="onAceptar"
android:text="@android:string/ok"/>
</RelativeLayout>

Añade las cadenas en el chero de recursos que necesites.

Dene el método onReproducir() llamado al pulsar el botón de repro-


ducción. Tendrás que denir el patrón de una canción utilizando la
duración de las notas de su melodía, e insertando pequeños instantes
de pausa entre nota y nota a modo de separadores. Por ejemplo:

public void onReproducir(View v) {

_vibrator.cancel();

final int corcheaBase = 200;


final int hueco = 10;
final int negra = corcheaBase * 2 - hueco;
final int blanca = corcheaBase * 4 - hueco;
final int corchea = corcheaBase - hueco;
long[] patron = {0, // Siempre empezamos con "pausa"
corchea, hueco, corchea, hueco,
blanca, hueco, negra, hueco,negra, hueco,
blanca, hueco + negra + hueco,

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


134 Capítulo 7. Sensores y otro hardware

corchea, hueco, corchea, hueco,


blanca, hueco, negra, hueco,
negra, hueco, blanca, hueco + negra + hueco,

corchea, hueco, corchea, hueco,


blanca, hueco, negra, hueco,
negra, hueco, negra, hueco, blanca, hueco,

corchea, hueco, corchea, hueco,


blanca, hueco, negra, hueco,
negra, hueco, blanca
};

_vibrator.vibrate(patron, -1);

} // onReproducir

Ejecuta la aplicación y comprueba que al pulsar el botón se reproduce


el patrón.

Implementa el método onPause() del ciclo de vida de la actividad. Si


dejamos de ser la actividad en primer plano deberíamos detener nuestra
ejecución, de modo que cancelaremos la vibración que pudiéramos tener
en marcha:

public void onPause() {

super.onPause();
_vibrator.cancel();

} // onPause

Implementa el método llamado cuando se pulsa el botón aceptar. Ex-


trae la opción seleccionada por el usuario, comprueba si es o no la
esperada, y muestra un mensaje (en un toast ) en función de ello. Ase-
gúrate de detener la vibración que pudiéramos tener en marcha.

public void onAceptar(View v) {

_vibrator.cancel();

RadioGroup rg;

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
135

rg = (RadioGroup) findViewById(R.id.rgOpciones);
int idRadioButton;
idRadioButton = rg.getCheckedRadioButtonId();

int idString;
if (idRadioButton == R.id.cumple)
idString = R.string.acertaste;
else
idString = R.string.fallaste;

Toast.makeText(this, idString, Toast.LENGTH_SHORT).show();

} // onAceptar

Ejecuta la aplicación y comprueba que funciona como esperas.

Práctica 7.2: Sensores

Los dispositivos móviles van equipados con una creciente cantidad de sen-
sores que les permiten captar información del entorno a través de diferentes
magnitudes físicas.

El acceso a los sensores se realiza a través del gestor de sensores, que


puede conseguirse a través del contexto, utilizando el mismo método que
usamos en la práctica 7.1 para conseguir el acceso a la capacidad de vibración:

SensorManager sm;
sm = (SensorManager) getSystemService(SENSOR_SERVICE);

Una vez que tenemos el gestor de sensores, podemos hacer un recorrido


sobre todos ellos para averiguar qué capacidades tiene nuestro dispositivo.

En esta práctica vamos a mostrar en una etiqueta los nombres de los


sensores disponibles.

Crea un proyecto nuevo y denomínalo Sensores.


La actividad creada por el asistente nos sirve en este caso para nues-
tros objetivos, pues nos vamos a limitar a mostrar información y para
eso podemos reutilizar la etiqueta. Modica el layout para asociarle
el identicador @+id/sensores de modo que podamos acceder a ella
posteriormente.

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


136 Capítulo 7. Sensores y otro hardware

En el método onCreate() consigue una referencia al gestor de sensores,


y utilizando su método getSensorList() obtén una lista con todos los
sensores disponibles. Luego recórrela, pregunta a cada uno su nombre
y acumúlalos en una cadena. Termina metiendo dicha cadena en la
etiqueta de la ventana:

@Override
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sensores);

SensorManager sm;
sm = (SensorManager) getSystemService(SENSOR_SERVICE);

java.util.List<Sensor> listaSensores;
listaSensores = sm.getSensorList(Sensor.TYPE_ALL);

StringBuffer sb = new StringBuffer();


sb.append("Tu móvil tiene ")
.append(listaSensores.size())
.append(" sensores\n\n");

for (Sensor s: listaSensores)


sb.append(s.getName()).append("\n");

TextView tv;
tv = (TextView) findViewById(R.id.sensores);
tv.setText(sb.toString());

} // onCreate

Ejecuta el programa en un AVD. El resultado es desolador: no simula


ningún sensor.

Ejecútalo en un dispositivo físico y comprueba si los valores devueltos


coinciden con lo esperado a partir de su especicación.

En Android se proporciona acceso a una gran cantidad de sensores:

m
Acelerómetro, gravedad y aceleración lineal, medidos en
s2

Temperatura ambiente en grados Celsius

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
137

Giróscopo: nos proporciona la velocidad de giro en cada eje, en radianes


por segundo

Nivel de luz medido en luxes

Campo magnético: nos lo proporciona en los tres ejes, y se mide en


microteslas

Presión atmosférica medida en milibares

Detección de proximidad

Humedad relativa en porcentaje

Vector de rotación: indica la orientación del dispositivo

En los sensores en los que es necesario un sistema de referencia, se utiliza


el del propio móvil.

Práctica 7.3: Acelerómetro

Seguramente el sensor más conocido sea el del acelerómetro. Detecta la


aceleración que sufre el dispositivo en cada momento. Este tipo de sensor
llegó al gran público con el mando de la Wii, la consola de Nintendo, aunque
llevaba tiempo siendo utilizado en otros lugares, como discos duros o airbags.

A nivel de programación, para todos los sensores tenemos que proce-


der de la misma manera. En lugar de leer los valores de los sensores, será

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


138 Capítulo 7. Sensores y otro hardware

Android quién nos informe sobre los cambios en sus valores. Por tanto, nece-
sitamos registrarnos como oyentes del sensor en el gestor de sensores. Para
eso, utilizaremos el método:

boolean SensorManager::registerListener(SensorListener listener,


Sensor sensor,
int rate);

donde:

listener: es el objeto en el que queremos recibir las noticaciones de


los cambios de los datos del sensor.

sensor: el sensor en el que estamos interesados.

rate: perioricidad con la que queremos ser informados de los cam-


bios. Podemos indicar un valor en microsegundos, aunque lo habi-
SensorManager
tual será especicar una de las constantes denidas en
(SENSOR_DELAY_FASTEST, SENSOR_DELAY_GAME, SENSOR_DELAY_NORMAL
o SENSOR_DELAY_UI).

Es importante también pedir que se nos desregistre como oyentes en


cuanto dejemos de ser la aplicación en primer plano, dado que los sensores
consumen mucha batería, y si hay una aplicación que los utiliza, Android no
deja de atenderlos ni siquiera cuando se suspende la pantalla. Por tanto, el
procedimiento recomendado es hacer uso del ciclo de vida de la actividad, y
realizar el registro en el sensor en el métodoonResume(), y el desregistro en
onPause() usando SensorManager:unregisterListener().
Los oyentes de los sensores deben implementar dos métodos, uno en el
que reciben las noticaciones de los cambios leídos, y otro en el que reciben
los cambios de precisión. Por comodidad, en este caso no usaremos una clase
anónima, sino que directamente la actividad implementará el interfaz del
oyente y se registrará a sí misma.

Vamos a terminar de ver los detalles directamente sobre la práctica. Ha-


remos una aplicación que muestre en cuatro etiquetas los valores leídos del
acelerómetro y su precisión.

Crea un proyecto nuevo y denomínalo Acelerómetro.

Congura el layout para que tenga un aspecto similar al de la gura.


En los valores numéricos puedes poner etiquetas sin texto.

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

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
139

android:layout_width="match_parent"
android:layout_height="match_parent" >

<TextView
android:id="@+id/tvX"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_above="@+id/tvY"
android:padding="@dimen/padding_medium"
android:text="@string/x" />

<TextView
android:id="@+id/tvValorX"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tvX"
android:layout_alignBaseline="@+id/tvX"
android:padding="@dimen/padding_medium" />

<TextView
android:id="@+id/tvY"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


140 Capítulo 7. Sensores y otro hardware

android:layout_centerVertical="true"
android:padding="@dimen/padding_medium"
android:text="@string/y" />

<TextView
android:id="@+id/tvValorY"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tvY"
android:layout_alignBaseline="@+id/tvY"
android:padding="@dimen/padding_medium" />

<TextView
android:id="@+id/tvZ"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/tvY"
android:padding="@dimen/padding_medium"
android:text="@string/z" />

<TextView
android:id="@+id/tvValorZ"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tvZ"
android:layout_alignBaseline="@+id/tvZ"
android:padding="@dimen/padding_medium"/>

<TextView
android:id="@+id/tvPrecision"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignRight="@id/tvZ"
android:layout_alignParentBottom="true"
android:padding="@dimen/padding_medium"
android:text="@string/precision" />

<TextView
android:id="@+id/tvValorPrecision"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/tvPrecision"
android:layout_alignBaseline="@+id/tvPrecision"

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
141

android:padding="@dimen/padding_medium"/>
</RelativeLayout>

En la clase de la actividad, dene dos nuevos atributos privados. Uno


almacenará el gestor de sensores, y el otro guardará el sensor del ace-
lerómetro. Guarda también las etiquetas para poder acceder a ellas
directamente sin tener que buscarlas, dado que vamos a tener que ac-
tualizarlas a menudo.

private SensorManager _sensorManager;


private Sensor _acelerometro;

TextView _etX;
TextView _etY;
TextView _etZ;
TextView _etPrecision;

En el método onCreate(), consigue una referencia al gestor de sensores


y al sensor del acelerómetro. Consigue también las referencias a las
etiquetas. No te registres como oyente del acelerómetro aún.

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

_sensorManager = (SensorManager) getSystemService(


SENSOR_SERVICE);
_acelerometro = _sensorManager.getDefaultSensor(
Sensor.TYPE_ACCELEROMETER);

_etX = (TextView) findViewById(R.id.tvValorX);


_etY = (TextView) findViewById(R.id.tvValorY);
_etZ = (TextView) findViewById(R.id.tvValorZ);
_etPrecision = (TextView)
findViewById(R.id.tvValorPrecision);
} // onCreate

Haz que la clase de la actividad implemente el interfaz necesario para


recibir los eventos de los sensores.

public class Acelerometro extends Activity


implements SensorEventListener {
....

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


142 Capítulo 7. Sensores y otro hardware

Implementa el método onResume() que forma parte del ciclo de vida de


la actividad, y en él regístrate como oyente del sensor del acelerómetro:

public void onResume() {


super.onResume();
_sensorManager.registerListener(this, _acelerometro,
SensorManager.SENSOR_DELAY_NORMAL);
}

En el método onPause() desregístrate:

public void onPause() {


super.onPause();
_sensorManager.unregisterListener(this);
}

Implementa los métodos del interfaz. Actualiza las etiquetas en el que


se nos informa de un cambio en el sensor. Del método de cambio de
precisión no nos preocuparemos para este sensor.

public void onAccuracyChanged(Sensor sensor, int accuracy) {


}

public void onSensorChanged(SensorEvent event) {


_etX.setText(""+event.values[0]);
_etY.setText(""+event.values[1]);
_etZ.setText(""+event.values[2]);
_etPrecision.setText(""+event.accuracy);
}

Prueba la práctica en un dispositivo físico. Verás que los valores cam-


bian de acuerdo a su orientación.

Práctica 7.4: Grácos en Android

En Android tenemos dos maneras de conseguir representar grácos en


nuestras actividades. La primera, y más simple, es hacer uso de los con-
troles (subclases de View) que permiten dibujar imágenes (ImageView e

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
143

ImageButton). En ese caso, podemos incluírlas directamente en nuestros


layouts, por lo que se integran perfectamente con el resto de elementos y
resultan cómodas de utilizar. En contra de lo que indica su nombre, pueden
encargarse de representar no sólo imágenes sino, de manera general, cualquier
subclase de Drawable. Los drawable son recursos, por lo que se integran per-
fectamente con el modelo de programación y generación de contenido en
Android, y no sólo incluyen imágenes, sino también formas geométricas, su-
perposición de guras, etcétera.

La segunda alternativa es implementar una subclase propia de la clase


View, sobreescribir el método onDraw(), que es invocado por Android cuando
la vista se tiene que pintar, y programar en ella lo que se considere opor-
tuno. El método recibe un objeto de la clase Canvas, que tiene métodos con
operaciones de dibujado.

Vamos a ver un ejemplo muy simple que demuestra esta última posibili-
dad, para crear una actividad en la que aparecen barras de colores alternos.

Crea un nuevo proyecto y denomínalo Dibujos.

Abre el chero .java. Dentro de la clase de la actividad, inserta una


nueva clase (interna) llamada DibujosView que herede de View. Añade
el constructor esperado, que deberá recibir un Context. Llama en él a
la superclase. En breve añadiremos código adicional.

class DibujosView extends View {


public DibujosView(Context context) {

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


144 Capítulo 7. Sensores y otro hardware

super(context);
}
} // class DibujosView

El método onDraw(Canvas) debe ejecutarse lo más rápido posible. El


dibujado sobre el canvas requiere el uso de brochas (objetos de la
clase Paint) que indican una conguración de la operación de dibujado
(estilo de relleno, color, estilo del trazo, etcétera). En lugar de crear
esos objetos en cada repintado, los vamos a crear en el constructor y
los conservaremos como atributos de la clase para reutilizarlos. Dene
dos nuevos atributos en la clase DibujosView:

Paint _brocha1;
Paint _brocha2;

Vuelve al constructor y añade el código para crear esas brochas. Una


pintará rellenando de amarillo, y la otra de azul:

_brocha1 = new Paint();


_brocha1.setColor(0xFFFFFF00);
_brocha1.setStyle(Paint.Style.FILL);

_brocha2 = new Paint();


_brocha2.setColor(0xFF0000FF);
_brocha2.setStyle(Paint.Style.FILL);

Implementa el método onDraw() de la clase DibujosView. Será llamado


por Android cuando necesite que la vista se repinte. Utilizaremos los
objetos Paint anteriores para dibujar las bandas de colores alternos:

public void onDraw(Canvas c) {


float ancho = c.getWidth();
float alto = c.getHeight();

float incX = ancho/10;

Paint brocha = _brocha1;


for (int i = 0; i < 10; ++i) {
c.drawRect(i*incX, 0, (i+1)*incX, alto, brocha);
brocha = (brocha==_brocha1)?_brocha2:_brocha1;
}
}

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
145

La clase DibujosView ya la tenemos preparada. Ahora la clase de la


actividad tiene que usar uno de estos objetos como su vista, ignorando
el layout en XML del asistente, tal y como ya hicimos en la práctica 3.3:

public void onCreate(Bundle savedInstanceState) {


super.onCreate(savedInstanceState);
setContentView(new DibujosView(this));
} // onCreate

Ejecuta el programa.

Práctica 7.5: Multitouch

El multitouch es la detección de varias pulsaciones simultáneas sobre la


pantalla. Android informa de los eventos de pulsación a las vistas a tra-
vés del método onTouchEvent(MotionEvent), y en el parámetro tenemos
información sobre el suceso. Esto incluye también el seguimiendo de todos
los dedos, por lo que resulta sencillo, aprovechando los conocimientos de
la práctica anterior, hacer una aplicación que nos pinte círculos debajo del
lugar donde pulsamos.

Crea un nuevo proyecto y llámalo Multitouch.


Igual que antes, tendremos una clase interna que heredará de View.
Como atributos guardará una lista estática (con un array y un con-
tador) que mantendrá las posiciones de los dedos (conseguidas en el
evento onTouchEvent()) y que serán usados en onDraw() para pintar
los círculos. Además, tendremos 10 brochas, una para cada potencial
dedo, cada una de un color. En el constructor lo inicializaremos todo:

class MultitouchView extends View {

public MultitouchView(Context context) {


super(context);

_brochas = new Paint[10];


_dedos = new PointF[10];
int r = 0;
int g = 0xFF;
int b = 0;

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


146 Capítulo 7. Sensores y otro hardware

for (int i = 0; i < 10; ++i) {


_dedos[i] = new PointF();
_brochas[i] = new Paint();

_brochas[i].setColor(0xFF000000 +
(r << 16) + (g << 8) + b);
_brochas[i].setStyle(Paint.Style.FILL);
r += 20;
g -= 20;
b += 20;
} // for
} // Constructor

private Paint[] _brochas;


private PointF[] _dedos;
private int _numDedos = 0;

} // class DibujosView

En el evento invocado por android ante cada pulsación, actualizaremos


la lista de dedos, e invocaremos a invalidate(), que anula la vista
y pide a Android que nos repinte.

public boolean onTouchEvent(MotionEvent event) {

if (event.getAction() == MotionEvent.ACTION_UP)
_numDedos = 0;
else {
_numDedos = event.getPointerCount();
if (_numDedos > 10)
_numDedos = 10;
for (int i = 0; i < _numDedos; ++i) {
_dedos[i].x = event.getX(i);
_dedos[i].y = event.getY(i);
}
}
invalidate();
return true;
}

En el método onDraw(), recorremos la lista de dedos, y pintamos


cada uno con el color de su brocha:

public void onDraw(Canvas c) {

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
Notas bibliográcas 147

// Ponemos un fondo (borramos lo anterior).


c.drawColor(0xFFFFFFFF);
for (int i = 0; i < _numDedos; ++i)
c.drawCircle(_dedos[i].x, _dedos[i].y, 70, brochas[i]);

} // onDraw

Ya sólo falta el onCreate() de la actividad, donde añadiremos nuestra


vista:

public void onCreate(Bundle savedInstanceState) {


super.onCreate(savedInstanceState);
setContentView(new MultitouchView(this));
} // onCreate

Prueba la aplicación en un dispositivo físico, y pulsa con varios dedos


en la pantalla. ½Cada uno saldrá de un color!

Notas bibliográcas
A continuación se proporcionan algunas direcciones web con información
sobre cada uno de los temas tratados en este capítulo:

Clase para controla la vibración del dispositivo

http://developer.android.com/reference/android/os/Vibrator.html

Información sobre sensores

http://developer.android.com/reference/android/hardware/SensorManager.
html
http://developer.android.com/reference/android/hardware/SensorEvent.
html

Introducción a creación de grácos en Android

http://developer.android.com/guide/topics/graphics/2d-graphics.html

IFC03CM12 - Programación de dispositivos móviles - Julio 2012

Pedro Pablo Gómez Martín


148 Capítulo 7. Sensores y otro hardware

El próximo capítulo. . .
... ½½te toca escribirlo a ti!! Ha llegado la hora de que experimentes con
todas las posibilidades que proporciona Android. Se quedan en el tintero
muchas características interesantes. Conamos que el curso te haya servido
como impulso para encontrarlas :-)

IFC03CM12 - Programación de dispositivos móviles - Julio 2012


Pedro Pablo Gómez Martín
¾Qué te parece desto, Sancho?  Dijo Don Quijote 
Bien podrán los encantadores quitarme la ventura,
pero el esfuerzo y el ánimo, será imposible.

Segunda parte del Ingenioso Caballero


Don Quijote de la Mancha
Miguel de Cervantes

Buena está  dijo Sancho ; fírmela vuestra merced.


No es menester rmarla  dijo Don Quijote,
sino solamente poner mi rúbrica.

Primera parte del Ingenioso Caballero


Don Quijote de la Mancha
Miguel de Cervantes

You might also like