Académique Documents
Professionnel Documents
Culture Documents
ndroid
d
Por sggoliver on 04
4/08/2010 enn Android, Programacinn
Para empezar
e conn este Cursoo de Program
macin Anddroid, voy a describir los
l pasos bsicos para
disponner en nuesttro PC del entorno y las
l herramieentas necesaarias para comenzar
c a programar
aplicacciones Andrroid.
Paso 1.
1 Descarga e instalacin de Eclipsse.
Descaargar Eclipsee
Paso 2.
2 Descargar el SDK dee Android.
El SDDK de la plaataforma Anndroid se pueede descargar desde aquu (en el moomento de revisar
r este
artcullo la ltimaa versin ess la 20.0.3, que funcionna perfectam mente con Eclipse
E 4.2)). Una vez
descarrgado, bastarr con ejecuttar el instalaador estndarr de Window
ws.
Paso 3.
3 Descargar el plugin Android
A para Eclipse.
https://dl-ssl.
.google.com/android/
/eclipse/
Selecccionaremos los
l dos paquuetes disponiibles Develloper Tools y NDK Pluugins y pullsaremos el
botn Next> parra comenzarr con el asistente de instaalacin.
Instalaar Plugin AD
DT
Paso 4.
4 Configura
ar el plugin
n ADT.
Configgurar ADT
Paso 5.
5 Instalar las Platform Tools y los Platforms necesarios.
n
Ademms del SDK de Androidd comentadoo en el paso 2, que conttiene las herrramientas bsicas para
desarrrollar en Android, tambiin deberemmos descargaar las llamaddas Platflorm m Tools, quue contiene
herrammientas espeecficas de la ltima verrsin de la plataforma,
p y una o varrias plataforrmas (SDK
Platforms) de And droid, que no son ms que
q las librerras necesariias para desaarrollar sobrre cada una
de las versiones concretas
c de Android. As,
A si querem mos desarrollar por ejemmplo para Android
A 2.2
tendreemos que desscargar su plataforma
p coorrespondiennte. Mi conssejo personaal es siempree instalar al
menoss 2 plataforrmas: la coorrespondiennte a la lltima versin disponibble de Anddroid, y la
correspondiente a la mnima versin
v de Anndroid que queremos
q quue soporte nuuestra aplicaacin.
Para ello,
e desde Eclipse
E debemmos accederr al men W Window / Anndroid SDK K Manager. En la lista
de paaquetes disp ponibles seleccionaremoos las Anddroid SDK Platform-toools, las plataformas
p
Andrroid 4.1 (APII 16) y Android 2.2 (A API 8), y el paquete extrra Androidd Support Libbrary, que
es unna librera que q nos peermitir utillizar en versiones antiiguas de Android
A caraactersticas
introduucidas por versiones
v ms recientes. Pulsaremos el botn Innstall packagges y esperaremos a
que finnalice la desscarga.
Androoid SDK Man
nager
Paso 6.
6 Configura
ar un AVD..
A la hora
h de probaar y depurar aplicaciones Android no tendremoss que hacerloo necesariammente sobre
un disspositivo fssico, sino quue podremoss configurarr un emuladdor o dispossitivo virtuaal (Android
Virtuaal Device, o AVD) donnde poder reealizar fcilm mente estas tareas. Paraa ello, accedderemos al
AVD Manager
M (m
men Window w / AVD Manager),
M y en
e la seccinn Virtual Deevices podremmos aadir
tantos AVD como o se necesiteen (por ejem
mplo, configgurados paraa distintas veersiones de Android o
distinttos tipos de dispositivo)). Nuevamennte, mi conssejo ser coonfigurar al menos dos AVD, uno
para la mnima versin
v de Android
A que queramos soportar,
s y otro para laa versin ms reciente
disponnible.
AVD Manager
M
Para configurar
c el AVD tan slo tendrem mos que inddicar un nom mbre descripptivo, el verrsin de la
platafoorma Androiid que utilizzar, y las caaractersticass de hardwarre del dispossitivo virtual, como por
ejemplo su resoluccin de panttalla, el tamaao de la tarrjeta SD, o la disponibilidad de GPS S. Adems,
marcaaremos la opccin Snapshhot enabled, que nos peermitir arraancar el emuulador ms rpidamente
en futuuras ejecucio
ones.
Crear nuevo AVD
D
Y conn este paso ya tendram mos preparaadas todas lasl herramieentas necesaarias para comenzar a
desarrrollar aplicacciones Androoid. En prxximos artcullos veremos como crearr un nuevo proyecto,
p la
estructtura y comp ponentes de un
u proyectoo Android, y crearemos unau aplicaciin sencilla para poner
en prctica todos los
l conceptoos aprendidos.
Esttructu
ura de un prroyecto And
droid
Por sggoliver on 09
9/08/2010 enn Android, Programacinn
En la siguiente
s im
magen vemos los elementtos creados inicialmente
i para un nueevo proyectoo Android:
Contiene todo el cdigo fuente de la aplicacin, cdigo de la interfaz grfica, clases auxiliares, etc.
Inicialmente, Eclipse crear por nosotros el cdigo bsico de la pantalla (Activity) principal de la
aplicacin, siempre bajo la estructura del paquete java definido.
Carpeta /res/
Contiente todos los ficheros de recursos necesarios para el proyecto: imgenes, vdeos, cadenas de
texto, etc. Los diferentes tipos de recursos de debern distribuir entre las siguientes carpetas:
Como ejemplo, para un proyecto nuevo Android, se crean los siguientes recursos para la aplicacin:
Carpeta /gen/
Esta clase R contendr en todo momento una serie de constantes con los ID de todos los recursos de
la aplicacin incluidos en la carpeta /res/, de forma que podamos acceder facilmente a estos
recursos desde nuestro cdigo a traves de este dato. As, por ejemplo, la constante
R.drawable.icon contendr el ID de la imagen icon.png contenida en la carpeta
/res/drawable/. Veamos como ejemplo la clase R creada por defecto para un proyecto nuevo:
1
2 package net.sgoliver;
3
4 public final class R {
5 public static final class attr {
}
6
public static final class drawable {
7 public static final int icon=0x7f020000;
8 }
9 public static final class layout {
10 public static final int main=0x7f030000;
}
11 public static final class string {
12 public static final int app_name=0x7f040001;
13 public static final int hello=0x7f040000;
14 }
15}
16
Carpeta /assets/
Contiene todos los dems ficheros auxiliares necesarios para la aplicacin (y que se incluirn en su
propio paquete), como por ejemplo ficheros de configuracin, de datos, etc.
La diferencia entre los recursos incluidos en la carpeta /res/raw/ y los incluidos en la carpeta
/assets/ es que para los primeros se generar un ID en la clase R y se deber acceder a ellos con
los diferentes mtodos de acceso a recursos. Para los segundos sin embargo no se generarn ID y se
podr acceder a ellos por su ruta como a cualquier otro fichero del sistema. Usaremos uno u otro
segn las necesidades de nuestra aplicacin.
Fichero AndroidManifest.xml
Contiene la definicin en XML de los aspectos principales de la aplicacin, como por ejemplo su
identificacin (nombre, versin, icono, ), sus componentes (pantallas, mensajes, ), o los
permisos necesarios para su ejecucin. Veremos ms adelante ms detalles de este fichero.
En el siguiente post veremos los componentes software principales con los que podemos construir
una aplicacin Android.
En el post anterior vimos la estructura de un proyecto Android y aprendimos dnde colocar cada
uno de los elementos que componen una aplicacin, tanto elementos de software como recursos
grficos o de datos. En ste nuevo post vamos a centrarnos especficamente en los primeros, es
decir, veremos los distintos tipos de componentes de software con los que podremos construir una
aplicacin Android.
En Java o .NET estamos acostumbrados a manejar conceptos como ventana, control, eventos o
servicios como los elementos bsicos en la construccin de una aplicacin.
Pues bien, en Android vamos a disponer de esos mismos elementos bsicos aunque con un pequeo
cambio en la terminologa y el enfoque. Repasemos los componentes principales que pueden formar
parte de una aplicacin Android [Por claridad, y para evitar confusiones al consultar documentacin
en ingls, intentar traducir lo menos posible los nombres originales de los componentes].
Activity
View
Los objetos view son los componentes bsicos con los que se construye la interfaz grfica de la
aplicacin, anlogo por ejemplo a los controles de Java o .NET. De inicio, Android pone a nuestra
disposicin una gran cantidad de controles bsicos, como cuadros de texto, botones, listas
desplegables o imgenes, aunque tambin existe la posibilidad de extender la funcionalidad de estos
controles bsicos o crear nuestros propios controles personalizados.
Service
Los servicios son componentes sin interfaz grfica que se ejecutan en segundo plano. En concepto,
son exactamente iguales a los servicios presentes en cualquier otro sistema operativo. Los servicios
pueden realizar cualquier tipo de acciones, por ejemplo actualizar datos, lanzar notificaciones, o
incluso mostrar elementos visuales (activities) si se necesita en algn momento la interaccin con
del usuario.
Content Provider
Un content provider es el mecanismo que se ha definido en Android para compartir datos entre
aplicaciones. Mediante estos componentes es posible compartir determinados datos de nuestra
aplicacin sin mostrar detalles sobre su almacenamiento interno, su estructura, o su
implementacin. De la misma forma, nuestra aplicacin podr acceder a los datos de otra a travs
de los content provider que se hayan definido.
Broadcast Receiver
Widget
Los widgets son elementos visuales, normalmente interactivos, que pueden mostrarse en la pantalla
principal (home screen) del dispositivo Android y recibir actualizaciones peridicas. Permiten
mostrar informacin de la aplicacin al usuario directamente sobre la pantalla principal.
Intent
Un intent es el elemento bsico de comunicacin entre los distintos componentes Android que
hemos descrito anteriormente. Se pueden entender como los mensajes o peticiones que son enviados
entre los distintos componentes de una aplicacin o entre distintas aplicaciones. Mediante un intent
se puede mostrar una actividad desde cualquier otra, iniciar un servicio, enviar un mensaje
broadcast, iniciar otra aplicacin, etc.
En el siguiente post empezaremos ya a ver algo de cdigo, analizando al detalle una aplicacin
sencilla.
Lo priimero que vaamos a haceer es disear nuestra panntalla princippal modificanndo la que Eclipse
E nos
ha creado por defeecto. Pero dnde
d y cmmo se define cada pantallla de la apliccacin? En Android,
A el
diseoo y la lgicaa de una paantalla estann separados en dos fichheros distintoos. Por un lado,
l en el
ficheroo /res/la ayout/mai in.xml tenndremos el diseo
d puram
mente visual de la pantallla definido
como fichero XML y por otroo lado, en el fichero /sr rc/paquet tejava/Ho olaUsuari io.java,
enconttraremos el cdigo
c java que determina la lgica de la pantallla.
Vamos a modificaar en primer lugar el aspecto de la veentana principal de la applicacin aaadiendo los
controoles (views) que vemoss en la primmera capturaa de pantallaa. Para elloo, vamos a sustituir el
contennido del fich
hero main.x
xml por el siguiente:
<?xml version="
"1.0" encoding="utf-
-8"?>
<Line
earLayout
xmlns:a
android="ht
ttp://sche
emas.androi
id.com/apk
k/res/andro
oid"
a
android:ori
ientation="vertical"
"
a
android:lay
yout_width="fill_par
rent"
a
android:lay
yout_height="fill_pa
arent" >
<TextVi
iew android
d:text="@string/nomb
bre"
and
droid:layou
ut_width="fill_paren
nt"
and
droid:layou
ut_height=
="wrap_cont
tent" />
<EditTe
ext android
d:id="@+id
d/TxtNombre
e"
android:l
layout_heig
ght="wrap_
_content"
android:l
layout_widt
th="fill_p
parent" />
<Button
n android:i
id="@+id/B
BtnHola"
android:l
layout_widt
th="wrap_c
content"
android:l
layout_heig
ght="wrap_
_content"
android:t ing/hola" />
text="@stri
</LinearLayout>
En este XML se definen los elementos visuales que componen la interfaz de nuestra pantalla
principal y se especifican todas sus propiedades. No nos detendremos mucho en cada detalle porque
se ser tema de otro artculo, pero expliquemos un poco lo que vemos en el fichero.
Lo primero que nos encontramos es un elemento LinearLayout. Los layout son elementos no
visibles que determinan cmo se van a distribuir en el espacio los controles que incluyamos en su
interior. Los programadores java, y ms concretamente de Swing, conocern este concepto
perfectamente. En este caso, un LinearLayout distribuir los controles uno tras otro y en la
orientacin que indique su propiedad android:orientation.
Dentro del layout hemos incluido 3 controles: una etiqueta (TextView), un cuadro de texto
(EditText), y un botn (Button). En todos ellos hemos establecido las siguientes propiedades:
Con esto ya tenemos definida la presentacin visual de nuestra ventana principal de la aplicacin.
De igual forma definiremos la interfaz de la segunda pantalla, creando un nuevo fichero llamado
frmmensaje.xml, y aadiendo esta vez tan solo una etiqueta (TextView) para mostrar el mensaje
personalizado al usuario. Veamos cmo quedara nuestra segunda pantalla:
<TextView android:id="@+id/TxtMensaje"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="$mensaje"></TextView>
</LinearLayout>
Una vez definida la interfaz de las pantallas de la aplicacin deberemos implementar la lgica de la
misma. Como ya hemos comentado, la lgica de la aplicacin se definir en ficheros java
independientes. Para la pantalla principal ya tenemos creado un fichero por defecto llamado
HolaUsuario.java. Empecemos por comentar su cdigo por defecto:
Como ya vimos en un post anterior, las diferentes pantallas de una aplicacin Android se definen
mediante objetos de tipo Activity. Por tanto, lo primero que encontramos en nuestro fichero java
es la definicin de una nueva clase HolaUsuario que extiende a Activity. El nico mtodo
que sobreescribiremos de esta clase ser el mtodo OnCreate, llamado cuando se crea por primera
vez la actividad. En este mtodo lo nico que encontramos en principio, adems de la llamada a su
implementacin en la clase padre, es la llamada al mtodo
setContentView(R.layout.main). Con esta llamada estaremos indicando a Android que
debe establecer como interfaz grfica de esta actividad la definida en el recurso R.layout.main,
que no es ms que la que hemos especificado en el fichero /res/layout/main.xml. Una vez ms
vemos la utilidad de las diferentes constantes de recursos creadas automticamente en la clase R al
compilar el proyecto.
En principio vamos a crear una nueva actividad para la segunda pantalla de la aplicacin anloga a
sta primera, para lo que crearemos una nueva clase FrmMensaje que exienda de Activity y
que implemente el mtodo onCreate indicando que utilice la interfaz definida en
R.layout.frmmensaje.
Como vemos, el cdigo incluido por defecto en estas clases lo nico que hace es generar la interfaz
de la actividad. A partir de aqu nosotros tendremos que incluir el resto de la lgica de la aplicacin.
Y vamos a empezar con la actividad principal HolaUsuario, obteniendo una referencia a los
diferentes controles de la interfaz que necesitemos manipular, en nuestro caso slo el cuadro de
texto y el botn. Para ello utilizaremos el mtodo findViewById() indicando el ID de cada
control, definidos como siempre en la clase R:
Una vez tenemos acceso a los diferentes controles, ya slo nos queda implementar las acciones a
tomar cuando pulsemos el botn de la pantalla. Para ello implementaremos el evento onClick de
dicho botn, veamos cmo:
btnHola.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
Intent intent = new Intent(HolaUsuario.this, FrmMensaje.class);
startActivity(intent);
}
});
Si quisiramos tan slo mostrar una nueva actividad ya tan slo nos quedara llamar a
startActivity() pasndole como parmetro el intent creado. Pero en nuestro ejemplo
queremos tambin pasarle cierta informacin a la actividad, concretamente el nombre que
introduzca el usuario en el cuadro de texto. Para hacer esto vamos a crear un objeto Bundle, que
puede contener una lista de pares clave-valor con toda la informacin a pasar entre las actividades.
En nuestro caso slo aadiremos un dato de tipo String mediante el mtodo
putString(clave, valor). Tras esto aadiremos la informacin al intent mediante el mtodo
putExtras(bundle).
Hecho esto tan slo nos queda construir el texto de la etiqueta mediante su mtodo
setText(texto) y recuperando el valor de nuestra clave almacenada en el objeto Bundle
mediante getString(clave).
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.frmmensaje);
Con esto hemos concluido la lgica de las dos pantallas de nuestra aplicacin y tan slo nos queda
un paso importante para finalizar nuestro desarrollo. Como indicamos en uno de los artculos
anteriores, cualquier aplicacin Android utiliza un fichero especial en formato XML
(AndroidManifest.xml) para definir, entre otras cosas, los diferentes elementos que la componen.
Por tanto, todas las actividades de nuestra aplicacin deben quedar convenientemente recogidas en
este fichero. La actividad principal ya debe aparecer puesto que se cre de forma automtica al
crear el nuevo proyecto Android, por lo que debemos aadir tan slo la segunda. Para este ejemplo
nos limitaremos a incluir la actividad en el XML, ms adelante veremos que opciones adicionales
podemos especificar.
<activity android:name=".FrmMensaje"></activity>
</application>
</manifest>
Una vez llegado aqu, si todo ha ido bien, deberamos poder ejecutar el proyecto sin errores y
probar nuestra aplicacin en el emulador.
Espero que esta aplicacin de ejemplo os haya servido para aprender temas bsicos en el desarrollo
para Android, como por ejemplo la definicin de la interfaz grfica, el cdigo java necesario para
acceder y manipular los elementos de dicha interfaz, o la forma de comunicar diferentes actividades
de Android. En los artculos siguientes veremos algunos de estos temas de forma ms especfica y
ampliaremos con algunos temas ms avanzados.
Interfaz de usuario en Android: Layouts
Por sgoliver on 17/08/2010 en Android, Programacin
En el artculo anterior del curso, donde desarrollamos una sencilla aplicacin Android desde cero,
ya hicimos algunos comentarios sobre los layouts. Como ya indicamos, los layouts son elementos
no visuales destinados a controlar la distribucin, posicin y dimensiones de los controles que se
insertan en su interior. Estos componentes extienden a la clase base ViewGroup, como muchos
otros componentes contenedores, es decir, capaces de contener a otros controles. En el post anterior
conocimos la existencia de un tipo concreto de layout, LinearLayout, aunque Android nos
proporciona algunos otros. Vemos cuntos y cules.
FrameLayout
ste es el ms simple de todos los layouts de Android. Un FrameLayout coloca todos sus
controles hijos alineados con su esquina superior izquierda, de forma que cada control quedar
oculto por el control siguiente (a menos que ste ltimo tenga transparencia). Por ello, suele
utilizarse para mostrar un nico control en su interior, a modo de contenedor (placeholder) sencillo
para un slo elemento sustituible, por ejemplo una imagen.
Ejemplo:
1
<FrameLayout
2 xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="fill_parent"
4 android:layout_height="fill_parent">
5
6 <EditText android:id="@+id/TxtNombre"
7 android:layout_width="fill_parent"
android:layout_height="fill_parent" />
8
9 </FrameLayout>
10
LinearLayout
1
2 <LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
4 android:layout_height="fill_parent"
5 android:orientation="vertical">
6
7 <EditText android:id="@+id/TxtNombre"
8 android:layout_width="fill_parent"
android:layout_height="fill_parent" />
9
10
<Button android:id="@+id/BtnAceptar"
11 android:layout_width="wrap_content"
12 android:layout_height="fill_parent" />
13
14</LinearLayout>
15
Esta propiedad nos va a permitir dar a los elementos contenidos en el layout unas dimensiones
proporcionales entre ellas. Esto es ms dificil de explicar que de comprender con un ejemplo. Si
incluimos en un LinearLayout vertical dos cuadros de texto (EditText) y a uno de ellos le
establecemos un layout_weight=1 y al otro un layout_weight=2 conseguiremos
como efecto que toda la superficie del layout quede ocupada por los dos cuadros de texto y que
adems el segundo sea el doble (relacin entre sus propiedades weight) de alto que el primero.
1
2 <LinearLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 android:layout_width="fill_parent"
android:layout_height="fill_parent"
5 android:orientation="vertical">
6
7 <EditText android:id="@+id/TxtDato1"
8 android:layout_width="fill_parent"
9 android:layout_height="fill_parent"
10 android:layout_weight="1" />
11
<EditText android:id="@+id/TxtDato2"
12 android:layout_width="fill_parent"
13 android:layout_height="fill_parent"
14 android:layout_weight="2" />
15
16</LinearLayout>
17
As pues, a pesar de la simplicidad aparente de este layout resulta ser lo suficiente verstil como
para sernos de utilidad en muchas ocasiones.
TableLayout
Un TableLayout permite distribuir sus elementos hijos de forma tabular, definiendo las filas y
columnas necesarias, y la posicin de cada componente dentro de la tabla.
La estructura de la tabla se define de forma similar a como se hace en HTML, es decir, indicando
las filas que compondrn la tabla (objetos TableRow), y dentro de cada fila las columnas
necesarias, con la salvedad de que no existe ningn objeto especial para definir una columna (algo
as como un TableColumn) sino que directamente insertaremos los controles necesarios dentro del
TableRow y cada componente insertado (que puede ser un control sencillo o incluso otro
ViewGroup) corresponder a una columna de la tabla. De esta forma, el nmero final de filas de la
tabla se corresponder con el nmero de elementos TableRowinsertados, y el nmero total de
columnas quedar determinado por el nmero de componentes de la fila que ms componentes
contenga.
Por norma general, el ancho de cada columna se corresponder con el ancho del mayor componente
de dicha columna, pero existen una serie de propiedades que nos ayudarn a modificar este
comportamiento:
Todas estas propiedades del TableLayout pueden recibir una lista de ndices de columnas
separados por comas (ejemplo: android:stretchColumns=1,2,3) o un asterisco para
indicar que debe aplicar a todas las columnas (ejemplo: android:stretchColumns=*).
Otra caracterstica importante es la posibilidad de que una celda determinada pueda ocupar el
espacio de varias columnas de la tabla (anlogo al atributo colspan de HTML). Esto se indicar
mediante la propiedad android:layout_span del componente concreto que deber tomar
dicho espacio.
1 <TableLayout
2 xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="fill_parent"
android:layout_height="fill_parent"
4 android:stretchColumns="1" >
5
6 <TableRow>
7 <TextView android:text="Celda 1.1" />
8 <TextView android:text="Celda 1.2" />
9 </TableRow>
10
11 <TableRow>
12 <TextView android:text="Celda 2.1" />
<TextView android:text="Celda 2.2" />
13 </TableRow>
14
15 <TableRow>
16 <TextView android:text="Celda 3"
17 android:layout_span="2" />
</TableRow>
18
19</TableLayout>
20
21
22
RelativeLayout
El ltimo tipo de layout que vamos a ver es el RelativeLayout. Este layout permite especificar
la posicin de cada elemento de forma relativa a su elemento padre o a cualquier otro elemento
incluido en el propio layout. De esta forma, al incluir un nuevo elemento X podremos indicar por
ejemplo que debe colocarse debajo del elemento Y y alineado a la derecha del layout padre.
Veamos esto en el ejemplo siguiente:
1
2 <RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android
android:layout_width="fill_parent"
4
android:layout_height="fill_parent" >
5
6 <EditText android:id="@+id/TxtNombre"
7 android:layout_width="fill_parent"
8 android:layout_height="wrap_content" />
9
10 <Button android:id="@+id/BtnAceptar"
android:layout_width="wrap_content"
11 android:layout_height="wrap_content"
12 android:layout_below="@id/TxtNombre"
13 android:layout_alignParentRight="true" />
14
15</RelativeLayout>
16
Al igual que estas tres propiedades, en un RelativeLayout tendremos un sinfn de propiedades para
colocar cada control justo donde queramos. Veamos las principales [creo que sus propios nombres
explican perfectamente la funcin de cada una]:
Posicin relativa a otro control:
android:layout_above.
android:layout_below.
android:layout_toLeftOf.
android:layout_toRightOf.
android:layout_alignLeft.
android:layout_alignRight.
android:layout_alignTop.
android:layout_alignBottom.
android:layout_alignBaseline.
android:layout_alignParentLeft.
android:layout_alignParentRight.
android:layout_alignParentTop.
android:layout_alignParentBottom.
android:layout_centerHorizontal.
android:layout_centerVertical.
android:layout_centerInParent.
android:layout_margin.
android:layout_marginBottom.
android:layout_marginTop.
android:layout_marginLeft.
android:layout_marginRight.
android:padding.
android:paddingBottom.
android:paddingTop.
android:paddingLeft.
android:paddingRight.
En prximos artculos veremos otros elementos comunes que extienden a ViewGroup, como por
ejemplo las vistas de tipo lista (ListView), de tipo grid (GridView), y en pestaas
(TabHost/TabWidget).
Interfaz de usuario en Android: Controles
bsicos (I)
Por sgoliver on 19/08/2010 en Android, Programacin
En el artculo anterior del Curso de Programacin en Android ya vimos los distintos tipos de layouts
con los que contamos en Android para distribuir los controles de la interfaz por la pantalla del
dispositivo. En los prximos artculos dedicados a Android vamos a hacer un repaso de los
diferentes controles que pone a nuestra disposicin la plataforma de desarrollo de este sistema
operativo. Empezaremos con los controles ms bsicos y seguiremos posteriormente con algunos
algo ms elaborados.
En este primer post sobre el tema nos vamos a centrar en los diferentes tipos de botones y cmo
podemos personalizarlos. El SDK de Android nos proporciona tres tipos de botones: el clsico
(Button), el de tipo on/off (ToggleButton), y el que puede contener una imagen
(ImageButton). En la imagen siguiente vemos el aspecto por defecto de estos tres controles.
No vamos a comentar mucho sobre ellos dado que son controles de sobra conocidos por todos, ni
vamos a enumerar todas sus propiedades porque existen decenas. A modo de referencia, a medida
que los vayamos comentando ir poniendo enlaces a su pgina de la documentacin oficial de
Android para poder consultar todas sus propiedades en caso de necesidad.
Un control de tipo Button es el botn ms bsico que podemos utilizar. En el ejemplo siguiente
definimos un botn con el texto Plsame asignando su propiedad android:text. Adems de
esta propiedad podramos utilizar muchas otras como el color de fondo
(android:background), estilo de fuente (android:typeface), color de fuente
(android:textcolor), tamao de fuente (android:textSize), etc.
<Button android:id="@+id/BtnBoton1"
android:text="Plsame"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
Un control de tipo ToggleButton es un tipo de botn que puede permanecer en dos estados,
pulsado/no_pulsado. En este caso, en vez de definir un slo texto para el control definiremos dos,
dependiendo de su estado. As, podremos asignar las propiedades android:textOn y
android:textoOff para definir ambos textos. Veamos un ejemplo a continuacin.
<ToggleButton android:id="@+id/BtnBoton2"
android:textOn="ON"
android:textOff="OFF"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
En un control de tipo ImageButton podremos definir una imagen a mostrar en vez de un texto,
para lo que deberemos asignar la propiedad android:src. Normalmente asignaremos esta
propiedad con el descriptor de algn recurso que hayamos incluido en la carpeta /res/drawable. As,
por ejemplo, en nuestro caso hemos incluido una imagen llamada ok.png por lo que haremos
referencia al recurso @drawable/ok.
<ImageButton android:id="@+id/BtnBoton3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ok" />
Eventos de un botn
Como podis imaginar, aunque estos controles pueden lanzar muchos otros eventos, el ms comn
de todos ellos y el que querremos capturar en la mayora de las ocasiones es el evento onClick.
Para definir la lgica de este evento tendremos que implementarla definiendo un nuevo objeto
View.OnClickListener() y asocindolo al botn mediante el mtodo
setOnClickListener(). La forma ms habitual de hacer esto es la siguiente:
btnBoton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0)
{
lblMensaje.setText("Botn 1 pulsado!");
}
});
En el caso de un botn de tipo ToggleButton suele ser de utilidad conocer en qu estado ha quedado
el botn tras ser pulsado, para lo que podemos utilizar su mtodo isChecked(). En el siguiente
ejemplo se comprueba el estado del botn tras ser pulsado y se realizan acciones distintas segn el
resultado.
btnBoton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0)
{
if(btnBoton2.isChecked())
lblMensaje.setText("Botn 2: ON");
else
lblMensaje.setText("Botn 2: OFF");
}
});
En la imagen anterior vimos el aspecto que presentan por defecto los tres tipos de botones
disponibles. Pero, y si quisiramos personalizar su aspecto ms all de cambiar un poco el tipo o el
color de la letra o el fondo?
Para cambiar la forma de un botn podramos simplemente asignar una imagen a la propiedad
android:background, pero esta solucin no nos servira de mucho porque siempre se
mostrara la misma imagen incluso con el botn pulsado, dando poca sensacin de elemento
clickable.
La solucin perfecta pasara por tanto por definir diferentes imgenes de fondo dependiendo del
estado del botn. Pues bien, Android nos da total libertad para hacer esto mediante el uso de
selectores. Un selector se define mediante un fichero XML localizado en la carpeta
/res/drawable, y en l se pueden establecer los diferentes valores de una propiedad
determinada de un control dependiendo de su estado.
Por ejemplo, si quisiramos dar un aspecto plano a un botn ToggleButton, podramos disear
las imgenes necesarias para los estados pulsado (en el ejemplo toggle_on.png) y no pulsado
(en el ejemplo toggle_off.png) y crear un selector como el siguiente:
<item android:state_checked="false"
android:drawable="@drawable/toggle_off" />
<item android:state_checked="true"
android:drawable="@drawable/toggle_on" />
</selector>
<ToggleButton android:id="@+id/BtnBoton4"
android:textOn="ON"
android:textOff="OFF"
android:padding="10dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/toggle_style"/>
Despus de haber hablado en el artculo anterior de los controles de tipo botn, en esta nueva
entrega nos vamos a centrar en otros tres componentes bsicos imprescindibles en nuestras
aplicaciones: las imgenes (ImageView), las etiquetas (TextView) y por ltimo los cuadros de
texto (EditText).
<ImageView android:id="@+id/ImgFoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon" />
El control TextView es otro de los clsicos en la programacin de GUIs, las etiquetas de texto, y
se utiliza para mostrar un determinado texto al usuario. Al igual que en el caso de los botones, el
texto del control se establece mediante la propiedad android:text. A parte de esta propiedad, la
naturaleza del control hace que las ms interesantes sean las que establecen el formato del texto
mostrado, que al igual que en el caso de los botones son las siguientes: android:background
(color de fondo), android:textColor (color del texto), android:textSize (tamao de la
fuente) y android:typeface (estilo del texto: negrita, cursiva, ).
<TextView android:id="@+id/LblEtiqueta"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Escribe algo:"
android:background="#AA44FF"
android:typeface="monospace" />
De igual forma, tambin podemos manipular estas propiedades desde nuestro cdigo. Como
ejemplo, en el siguiente fragmento recuperamos el texto de una etiqueta con getText(), y
posteriormente le concatenamos unos nmeros, actualizamos su contenido mediante setText() y
le cambiamos su color de fondo con setBackgroundColor().
<EditText android:id="@+id/TxtTexto"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/LblEtiqueta" />
De igual forma, desde nuestro cdigo podremos recuperar y establecer este texto mediante los
mtodos getText() y setText(nuevoTexto) respectivamente:
Un detalle que puede haber pasado desapercibido. Os habis fijado en que hemos tenido que hacer
un toString() sobre el resultado de getText()? La explicacin para esto es que el mtodo
getText() no devuelve un String sino un objeto de tipo Editable, que a su vez implementa
la interfaz Spannable. Y esto nos lleva a la caracterstica ms interesante del control EditText,
y es que no slo nos permite editar texto plano sino tambin texto enriquecido o con formato.
Veamos cmo y qu opciones tenemos, y para empezar comentemos algunas cosas sobre los
objetos Spannable.
Interfaz Spanned
Un objeto de tipo Spanned es algo as como una cadena de caracteres (deriva de la interfaz
CharSequence) en la que podemos insertar otros objetos a modo de marcas o etiquetas (spans)
asociados a rangos de caracteres. De esta interfaz deriva la interfaz Spannable, que permite la
modificacin de estas marcas, y a su vez de sta ltima deriva la interfaz Editable, que permite
adems la modificacin del texto.
Aunque en el apartado en el que nos encontramos nos interesaremos principalmente por las marcas
de formato de texto, en principio podramos insertar cualquier tipo de objeto.
Existen muchos tipos de spans predefinidos en la plataforma que podemos utilizar para dar formato
al texto, entre ellos:
De esta forma, para crear un nuevo objeto Editable e insertar una marca de formato podramos
hacer lo siguiente:
En este ejemplo estamos insertando un span de tipo StyleSpan para marcar un fragmento de
texto con estilo negrita. Para insertarlo utilizamos el mtodo setSpan(), que recibe como
parmetro el objeto Span a insertar, la posicin inicial y final del texto a marcar, y un flag que
indica la forma en la que el span se podr extender al insertarse nuevo texto.
Hemos visto cmo crear un objeto Editable y aadir marcas de formato al texto que contiene, pero
todo esto no tendra ningn sentido si no pudiramos visualizarlo. Como ya podis imaginar, los
controles TextView y EditText nos van a permitir hacer esto. Vemos qu ocurre si asignamos al
nuestro control EditText el objeto Editable que hemos creado antes:
txtTexto.setText(str);
Tras ejecutar este cdigo veremos como efectivamente en el cuadro de texto aparece el mensaje con
el formato esperado:
Ya hemos visto c mo asignarr texto con y sin formatto a un cuaddro de texto,, pero qu ocurre a la
hora de
d recuperarr texto con formato desde el contrrol?. Ya vim mos que la funcin ge etText()
devuellve un objetto de tipo Ed ditable y que sobre ste podam mos hacer unn toStrin ng(). Pero
con essta solucin estamos
e perddiendo todo el formato del
d texto, por lo que no podramos
p p ejemplo
por
salvarllo a una basee de datos.
La sollucin a estto ltimo paasa obviameente por recuuperar direcctamente el objeto Edi itable y
serializarlo de alg
gn modo, mejor
m an sii es en un formato
f estaandar. Pues bien, en Anndroid este
trabajoo ya nos vien
ne hecho de fbrica a traavs de la clase Html [A API], que dispone de mtodos para
converrtir cualquieer objeto Sp
panned enn su representacin HT TML equivaalente. Veam mos cmo.
Recupperemos el
e texto de la ventana anterior y utiliccemos el mtodo
Html. .toHtml(S Spannable) para connvertirlo a foormato HTM ML:
//Obt
tiene el te
exto del control con
n etiquetas de forma
ato HTML
Strin
ng aux2 = Html.toHtm
H ml(txtTexto
o.getText());
Hacienndo esto, ob
btendramos una cadena de texto commo la siguiennte, que ya podramos
p p ejemplo
por
almaceenar en unaa base de datos
d o pubblicar en cuualquier webb sin perderr el formatoo de texto
estableecido:
<p>Esto es un <b>simulac
< ro</b>.</p
p>
La opperacin con
ntraria tambbin es possible, es decir, cargar un cuadro de texto de d Android
(EdittText) o una
u etiqueta (TextView w) a partir de
d un fragm
mento de texxto en formaato HTML.
Para ello
e podemoss utilizar el mtodo
m Html.fromHtml(Strin ng) de la sigguiente formaa:
Podis descargar parte del cdigo de este artculo desde este enlace.
Tras hablar de varios de los controles indispensables en cualquier aplicacin Android, como son los
botones y los cuadros de texto, en este artculo vamos a ver cmo utilizar otros dos tipos de
controles bsicos en muchas aplicaciones, los checkboxes y los radio buttons.
Un control checkbox se suele utilizar para marcar o desmarcar opciones en una aplicacin, y en
Android est representado por la clase del mismo nombre, CheckBox. La forma de definirlo en
nuestra interfaz y los mtodos disponibles para manipularlos desde nuestro cdigo son anlogos a
los ya comentados para el control ToggleButton.
De esta forma, para definir un control de este tipo en nuestro layout podemos utilizar el cdigo
siguiente, que define un checkbox con el texto Mrcame:
<CheckBox android:id="@+id/ChkMarcame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mrcame!" />
En cuanto a la personalizacin del control podemos decir que ste extiende [indirectamente] del
control TextView, por lo que todas las opciones de formato ya comentadas en artculos anteriores
son vlidas tambin para este control.
En el cdigo de la aplicacin podremos hacer uso de los mtodos isChecked() para conocer el
estado del control, y setChecked(estado) para establecer un estado concreto para el control.
if (checkBox.isChecked()) {
checkBox.setChecked(false);
}
En cuanto a los posibles eventos que puede lanzar este control, el ms interesante es sin duda el que
informa de que ha cambiado el estado del control, que recibe el nombre de onCheckedChanged.
Para implementar las acciones de este evento podramos utilizar por tanto la siguiente lgica:
cb.setOnCheckedChangeListener(
new CheckBox.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {
cb.setText("Checkbox marcado!");
}
else {
cb.setText("Checkbox desmarcado!");
}
}
});
Al igual que los controles checkbox, un radiobutton puede estar marcado o desmarcado, pero en
este caso suelen utilizarse dentro de un grupo de opciones donde una, y slo una, de ellas debe estar
marcada obligatoriamente, es decir, que si se marca una de ellas se desmarcar automticamente la
que estuviera activa anteriormente. En Android, un grupo de botones radiobutton se define
mediante un elemento RadioGroup, que a su vez contendr todos los elementos RadioButton
necesarios. Veamos un ejemplo de cmo definir un grupo de dos controles radiobutton en nuestra
interfaz:
<RadioGroup android:id="@+id/gruporb"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<RadioButton android:id="@+id/radio1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Opcin 1" />
<RadioButton android:id="@+id/radio2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Opcin 2" />
</RadioGroup>
En primer lugar vemos cmo podemos definir el grupo de controles indicando su orientacin
(vertical u horizontal) al igual que ocurra por ejemplo con un LinearLayout. Tras esto, se aaden
todos los objetos RadioButton necesarios indicando su ID mediante la propiedad android:id y
su texto mediante android:text.
Una vez definida la interfaz podremos manipular el control desde nuestro cdigo java haciendo uso
de los diferentes mtodos del control RadioGroup, los ms importantes: check(id) para
marcar una opcin determinada mediante su ID, clearCheck() para desmarcar todas las
opcionnes, y getC
CheckedRa adioButto onId() quee como su noombre indicaa devolver el ID de la
opcinn marcada (o
o el valor -1 si no hay ninnguna marcaada). Veamoos un ejempllo:
final RadioGrou
up rg = (R
RadioGroup)
)findViewById(R.id.g
gruporb);
rg.clearCheck()
);
rg.ch
heck(R.id.r
radio1);
int idSeleccion
nado = rg.getChecked
dRadioButtonId();
En cuaanto a los ev
ventos lanzados, al iguall que en el caso
c de los checkboxes, el ms impoortante ser
el quee informa ded los cambbios en el elemento seleccionado
s o, llamado tambin
t en este caso
onChe eckedChan nge. Vemos cmo trataar este eventoo del objeto RadioGro oup:
final RadioGrou
up rg = (R
RadioGroup)
)findViewById(R.id.g
gruporb);
rg.se
etOnChecked
dChangeListener(
n
new roup.OnCheckedChange
RadioGr eListener() {
public void onCheckedChang
ged(RadioGroup group
p, int chec
ckedId) {
lbl
lMensaje.setText("ID
D opcion se
eleccionad
da: " + che
eckedid);
}
});
Veamoos finalmen
nte una imaggen del aspeecto de estoos dos nuevoos tipos de controles bsicos
b que
hemoss comentado
o en este artcculo:
Podiss descargar el
e cdigo de este artculoo desde este enlace.
En este primer artculo dedicado a los controles de seleccin vamos a describir un elemento
importante y comn a todos ellos, los adaptadores, y lo vamos a aplicar al primer control de los
indicados, las listas desplegables.
Para los desarrolladores de java que hayan utilizado frameworks de interfaz grfica como Swing, el
concepto de adaptador les resultar familiar. Un adaptador representa algo as como una interfaz
comn al modelo de datos que existe por detrs de todos los controles de seleccin que hemos
comentado. Dicho de otra forma, todos los controles de seleccin accedern a los datos que
contienen a travs de un adaptador.
Adems de proveer de datos a los controles visuales, el adaptador tambin ser responsable de
generar a partir de estos datos las vistas especficas que se mostrarn dentro del control de
seleccin. Por ejemplo, si cada elemento de una lista estuviera formado a su vez por una imagen y
varias etiquetas, el responsable de generar y establecer el contenido de todos estos sub-elementos
a partir de los datos ser el propio adaptador.
Android proporciona de serie varios tipos de adaptadores sencillos, aunque podemos extender su
funcionalidad facilmente para adaptarlos a nuestras necesidades. Los ms comunes son los
siguientes:
Para no complicar excesivamente los tutoriales, por ahora nos vamos a conformar con describir la
forma de utilizar un ArrayAdapter con los diferentes controles de seleccin disponibles. Ms
adelante aprenderemos a utilizar el resto de adaptadores en contextos ms especficos.
Veamos cmo crear un adaptador de tipo ArrayAdapter para trabajar con un array genrico de java:
Comentemos un poco el cdigo. Sobre la primera lnea no hay nada que decir, es tan slo la
definicin del array java que contendr los datos a mostrar en el control, en este caso un array
sencillo con cinco cadenas de caracteres. En la segunda lnea creamos el adaptador en s, al que
pasamos 3 parmetros:
1. El contexto, que normalmente ser simplemente una referencia a la actividad donde se crea
el adaptador.
2. El ID del layout sobre el que se mostrarn los datos del control. En este caso le pasamos el
ID de un layout predefinido en Android
(android.R.layout.simple_spinner_item), formado nicamente por un control
TextView, pero podramos pasarle el ID de cualquier layout de nuestro proyecto con
cualquier estructura y conjunto de controles, ms adelante veremos cmo.
3. El array que contiene los datos a mostrar.
Con esto ya tendramos creado nuestro adaptador para los datos a mostrar y ya tan slo nos quedara
asignar este adaptador a nuestro control de seleccin para que ste mostrase los datos en la
aplicacin.
Las listas desplegables en Android se llaman Spinner. Funcionan de forma similar al de cualquier
control de este tipo, el usuario selecciona la lista, se muestra una especie de lista emergente al
usuario con todas las opciones disponibles y al seleccionarse una de ellas sta queda fijada en el
control. Para aadir una lista de este tipo a nuestra aplicacin podemos utilizar el cdigo siguiente:
1<Spinner android:id="@+id/CmbOpciones"
2 android:layout_width="fill_parent"
3 android:layout_height="wrap_content" />
Poco vamos a comentar de aqu ya que lo que nos interesan realmente son los datos a mostrar. En
cualquier caso, las opciones para personalizar el aspecto visual del control (fondo, color y tamao
de fuente, ) son las mismas ya comentadas para los controles bsicos.
Para enlazar nuestro adaptador (y por tanto nuestros datos) a este control utilizaremos el siguiente
cdigo java:
Con estas
e simpless lineas de cdigo
c conseguiremos mostrar
m un control
c comoo el que vem
mos en las
siguienntes imgenes:
1 cmb
bOpciones.ssetOnItemSelectedLisstener(
2 new AdapterView
A w.OnItemSelectedList
tener() {
3 public void onItemSelected(AdapterView<?> parent,
4 android.view.View v, int position, long id) {
lblMensaje.setText("Seleccionado: " + datos[position]);
5 }
6
7 public void onNothingSelected(AdapterView<?> parent) {
8 lblMensaje.setText("");
9 }
});
10
11
Para este evento definimos dos mtodos, el primero de ellos (onItemSelected) que ser
llamado cada vez que se selecciones una opcin en la lista desplegable, y el segundo
(onNothingSelected) que se llamar cuando no haya ninguna opcin seleccionada (esto puede
ocurrir por ejemplo si el adaptador no tiene datos).
Un control ListView muestra al usuario una lista de opciones seleccionables directamente sobre
el propio control, sin listas emergentes como en el caso del control Spinner. En caso de existir
ms opciones de las que se pueden mostrar sobre el control se podr por supuesto hacer scroll
sobre la lista para acceder al resto de elementos.
Para empezar, veamos como podemos aadir un control ListView a nuestra interfaz de usuario:
1<ListView android:id="@+id/LstOpciones"
2 android:layout_width="wrap_content"
3 android:layout_height="wrap_content" />
Una vez ms, podremos modificar el aspecto del control utilizando las propiedades de fuente y
color ya comentadas en artculos anteriores. Por su parte, para enlazar los datos con el control
podemos utlizar por ejemplo el mismo cdigo que ya vimos para el control Spinner. Definiremos
primero un array con nuestros datos de prueba, crearemos posteriormente el adaptador de tipo
ArrayAdapter y lo asignaremos finalmente al control mediante el mtodo setAdapter():
1
fin
nal String[] datos =
2 new String[]{"Elem11","Elem2","Elem3",""Elem4","Ellem5"};
3
4 ArrrayAdapter<<String> adaptador =
5 new Array
yAdapter<Sttring>(this,
6 androoid.R.layo ut.simple_
_list_item_1, datos);
7
ListView lstOOpciones = (ListVieww)findViewById(R.id..LstOpcione
es);
8
9 lsttOpciones.ssetAdapter(adaptadorr);
10
En estte caso, parra mostrar loos datos de cada elemeento hemos utilizado ottro layout genrico
g de
Androoid para
p los controles de tipo L
ListView
(andrroid.R.la ayout.sim mple_list t_item_1)), formado nicamentee por un TextView
T
con unas
u dimensiones determminadas. Laa lista creadda quedara como se muestra
m en la imagen
siguiennte:
1lstO Opciones.se
etOnItemClickListene er(new OnIt
temClickLis
stener() {
2 @Override
3 public void onItemClick(Adapte erView<?> a, View v,, int posit
tion, long id) {
4 //Acci
iones nece sarias al hacer cli ck
5 }
});
6
Hasta aqu todo sencillo. Pero, y si necesitamos mostrar datos ms complejos en la lista? qu
ocurre si necesitamos que cada elemento de la lista est formado a su vez por varios elementos?
Pues vamos a provechar este artculo dedicado a los ListView para ver cmo podramos
conseguirlo, aunque todo lo que comentar es extensible a otros controles de seleccin.
Para no complicar mucho el tema vamos a hacer que cada elemento de la lista muestre por ejemplo
dos lneas de texto a modo de ttulo y subttulo con formatos diferentes (por supuesto se podran
aadir muchos ms elementos, por ejemplo imgenes, checkboxes, etc).
En primer lugar vamos a crear una nueva clase java para contener nuestros datos de prueba. Vamos
a llamarla Titular y tan slo va a contener dos atributos, ttulo y subttulo.
1
2 package net.sgoliver;
3
4 public class Titular
5 {
6 private String titulo;
private String subtitulo;
7
8 public Titular(String tit, String sub){
9 titulo = tit;
10 subtitulo = sub;
11 }
12
13 public String getTitulo(){
return titulo;
14 }
15
16 public String getSubtitulo(){
17 return subtitulo;
18 }
}
19
20
En cada elemento de la lista queremos mostrar ambos datos, por lo que el siguiente paso ser crear
un layout XML con la estructura que deseemos. En mi caso voy a mostrarlos en dos etiquetas de
texto (TextView), la primera de ellas en negrita y con un tamao de letra un poco mayor.
Llamaremos a este layout listitem_titular.xml:
Ahora que ya tenemos creados tanto el soporte para nuestros datos como el layout que necesitamos
para visualizarlos, lo siguiente que debemos hacer ser indicarle al adaptador cmo debe utilizar
ambas cosas para generar nuestra interfaz de usuario final. Para ello vamos a crear nuestro propio
adaptador extendiendo de la clase ArrayAdapter.
1
2 class AdaptadorTitulares extends ArrayAdapter {
3
4 Activity context;
5
AdaptadorTitulares(Activity context) {
6 super(context, R.layout.listitem_titular, datos);
7 this.context = context;
8 }
9
10 public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
11 View item = inflater.inflate(R.layout.listitem_titular, null);
12
13 TextView lblTitulo = (TextView)item.findViewById(R.id.LblTitulo);
14 lblTitulo.setText(datos[position].getTitulo());
15
16 TextView lblSubtitulo =
17(TextView)item.findViewById(R.id.LblSubTitulo);
lblSubtitulo.setText(datos[position].getSubtitulo());
18
19 return(item);
20 }
21}
22
El mtodo getView() se llamar cada vez que haya que mostrar un elemento de la lista. Lo primero
que debe hacer es inflar el layout XML que hemos creado. Esto consiste en consultar el XML de
nuestro layout y crear e inicializar la estructura de objetos java equivalente. Para ello, crearemos un
nuevo objeto LayoutInflater y generaremos la estructura de objetos mediante su mtodo
inflate(id_layout).
Tras esto, tan slo tendremos que obtener la referencia a cada una de nuestras etiquetas como
siempre lo hemos hecho y asignar su texto correspondiente segn los datos de nuestro array y la
posicin del elemento actual (parmetro position del mtodo getView()).
1
2 private Titular[] datos =
3 new Titular[]{
new Titular("Ttulo 1", "Subttulo largo 1"),
4
new Titular("Ttulo 2", "Subttulo largo 2"),
5 new Titular("Ttulo 3", "Subttulo largo 3"),
6 new Titular("Ttulo 4", "Subttulo largo 4"),
7 new Titular("Ttulo 5", "Subttulo largo 5")};
8
9 //...
//...
10
11AdaptadorTitulares adaptador =
12 new AdaptadorTitulares(this);
13
14ListView lstOpciones = (ListView)findViewById(R.id.LstOpciones);
15
16lstOpciones.setAdapter(adaptador);
17
Hecho esto, y si todo ha ido bien, nuestra nueva lista debera quedar como vemos en la imagen
siguiente:
Podiss descargar el
e cdigo de este artculoo desde este enlace.
Aunquue ya sabem mos utilizar y personalizzar las listass en Androidd, en el prxximo artcullo daremos
algunaas indicacionnes para utiliizar de una forma
f muchho ms eficieente los conttroles de estee tipo, algo
que loos usuarios ded nuestra aplicacin
a a
agradecern enormementte al mejoraarse la respuuesta de la
aplicaccin y reduccirse el consuumo de baterra.
En el artculo anterior ya vimos cmo utilizar los controles de d tipo ListV View en Anndroid. Sin
embarrgo, acabamo os comentanndo que exista una form ma ms eficieente de haceer uso de dicho control,
de form
ma que la reespuesta de nuestra
n apliccacin fuera ms agil y se
s reduciese el consumo de batera,
algo que
q en platafoformas mvilles siempre es e importantte.
Como base para este artcullo vamos a utilizar com mo cdigo que ya escrribimos en el artculo
anterioor, por lo qu
ue si has lleggado hasta aqqu directam
mente te recom
miendo que leas primeroo el primer
post dedicado al control ListV View.
Cuanddo comentam mos cmo crrear nuestro propio adapptador, extenndiendo de AArrayAdap pter, para
personnalizar la forma en que nuestros daatos se ibann a mostrar ene la lista escribimos
e e siguiente
el
cdigoo:
1 cla
ass Adaptad
dorTitulare
Activity context;
es extends ArrayAdapt
ter {
2
3 AdaptadorrTitulares(Activity context) {
4 super(context, R.layout.listitem_titular, datos);
5 this.context = context;
}
6
7 public View getView(int position, View convertView, ViewGroup parent) {
8 LayoutInflater inflater = context.getLayoutInflater();
9 View item = inflater.inflate(R.layout.listitem_titular, null);
10
11 TextView lblTitulo = (TextView)item.findViewById(R.id.LblTitulo);
lblTitulo.setText(datos[position].getTitulo());
12
13 TextView lblSubtitulo =
14(TextView)item.findViewById(R.id.LblSubTitulo);
15 lblSubtitulo.setText(datos[position].getSubtitulo());
16
17 return(item);
18 }
}
19
20
21
Centrndonos en la definicin del mtodo getView() vimos que la forma normal de proceder
consista en primer lugar en inflar nuestro layout XML personalizado para crear todos los objetos
correspondientes (con la estructura descrita en el XML) y posteriormente acceder a dichos objetos
para modificar sus propiedades. Sin embargo, hay que tener en cuenta que esto se hace todas y cada
una de las veces que se necesita mostrar un elemento de la lista en pantalla, se haya mostrado ya o
no con anterioridad, ya que Android no guarda los elementos de la lista que desaparecen de
pantalla (por ejemplo al hacer scroll sobre la lista). El efecto de esto es obvio, dependiendo del
tamao de la lista y sobre todo de la complejidad del layout que hayamos definido esto puede
suponer la creacin y destruccin de cantidades ingentes de objetos (que puede que ni siquiera nos
sean necesarios), es decir, que la accin de inflar un layout XML puede ser bastante costosa, lo que
podra aumentar mucho, y sin necesidad, el uso de CPU, de memoria, y de batera.
Para aliviar este problema, Android nos propone un mtodo que permite reutilizar algn layout que
ya hayamos inflado con anterioridad y que ya no nos haga falta por algn motivo, por ejemplo
porque el elemento correspondiente de la lista ha desaparecido de la pantalla al hacer scroll. De esta
forma evitamos todo el trabajo de crear y estructurar todos los objetos asociados al layout, por lo
que tan slo nos quedara obtener la referencia a ellos mediante findViewById() y modificar
sus propiedades.
Pero cmo podemos reutilizar estos layouts obsoletos? Pues es bien sencillo, siempre que exista
algn layout que pueda ser reutilizado ste se va a recibir a travs del parmetro convertView
del mtodo getView(). De esta forma, en los casos en que ste no sea null podremos obviar el
trabajo de inflar el layout. Veamos cmo quedara el mtod getView() tras esta optimizacin:
Pero vamos
v n poco ms all. Con la optimizacin que acabaamos de impplementar coonseguimos
a ir un
ahorraarnos el trabajo de inflarr el layout definido
d cadaa vez que se muestra unn nuevo elem
mento. Pero
an haay otras dos llamadas reelativamentee costosas quue se siguenn ejecutando en todas lass llamadas.
Me reffiero a la ob
btencin de la referencia a cada uno de los objetoos a modificcar mediantee el mtodo
findV ViewById( (). La bsqqueda por ID D de un control determinnado dentro del rbol de objetos de
un layyout tambin puede ser una tareea costosa dependiendoo de la coomplejidad del d propio
layoutt.Por qu non aprovechhamos que estamos gguardando un layout anterior parra guardar
tambin la refereencia a los controles quue lo formaan de formaa que no teengamos quee volver a
buscarrlos? Pues eso
e es exacttamente lo que q vamos a hacer mediante lo quue en los ejjemplos de
Androoid llaman un n ViewHol lder. La claase ViewHo older tan slos va a conntener una referencia a
cada uno de los controles que tengamos que manipular de nuestro layout, en nuestro caso las dos
etiquetas de texto. Definamos por tanto esta clase de la siguiente forma:
La idea ser por tanto crear e inicializar el objeto ViewHolder la primera vez que inflemos un
elemento de la lista y asociarlo a dicho elemento de forma que posteriormente podamos recuperarlo
fcilmente. Pero dnde lo guardamos? Fcil, en Android todos los controles tienen una propiedad
llamada Tag (podemos asignarla y recuperarla mediante los mtodos setTag() y getTag()
respectivamente) que puede contener cualquier tipo de objeto, por lo que resulta ideal para guardar
nuestro objeto ViewHolder. De esta forma, cuando el parmetro convertView llegue
informado sabremos que tambin tendremos disponibles las referencias a sus controles hijos a
travs de la propiedad Tag. Veamos el cdigo modificado de getView() para aprovechar esta
nueva optimizacin:
1
2
3 public View getView(int position, View convertView, ViewGroup parent)
{
4 View item = convertView;
5 ViewHolder holder;
6
7 if(item == null)
8 {
LayoutInflater inflater = context.getLayoutInflater();
9
item = inflater.inflate(R.layout.listitem_titular, null);
10
11 holder = new ViewHolder();
12 holder.titulo = (TextView)item.findViewById(R.id.LblTitulo);
13 holder.subtitulo = (TextView)item.findViewById(R.id.LblSubTitulo);
14
15 item.setTag(holder);
}
16 else
17 {
18 holder = (ViewHolder)item.getTag();
19 }
20
21 holder.titulo.setText(datos[position].getTitulo());
holder.subtitulo.setText(datos[position].getSubtitulo());
22
23 return(item);
24}
25
26
Con estas dos optimizaciones hemos conseguido que la aplicacin sea mucho ms respetuosa con
los recursos del dispositivo de nuestros usuarios, algo que sin duda nos agradecern.
Podis descargar el cdigo fuente de este artculo desde este enlace.
Tras haber visto en artculos anteriores los dos controles de seleccin ms comunes en cualquier
interfaz grfica, como son las listas desplegables (Spinner) y las listas fijas (ListView), tanto
en su versin bsica como optimizada, en este nuevo artculo vamos a terminar de comentar los
controles de seleccin con otro menos comn pero no por ello menos til, el control GridView.
1
<GridView android:id="@+id/GridOpciones"
2 android:layout_width="fill_parent"
3 android:layout_height="fill_parent"
4 android:numColumns="auto_fit"
5 android:columnWidth="80px"
6 android:horizontalSpacing="5px"
android:verticalSpacing="10px"
7 android:stretchMode="columnWidth" />
8
Una vez definida la interfaz de usuario, la forma de asignar los datos desde el cdigo de la
aplicacin es completamente anloga a la ya comentada tanto para las listas desplegables como para
las listas estticas: creamos un array genrico que contenga nuestros datos de prueba, declaramos un
adaptador de tipo ArrayAdapter pasndole en este caso un layout genrico
(simple_list_item_1, compuesto por un simple TextView) y asociamos el adaptador al
control GridView mediante su mtodo setAdapter():
1 private String[] datos = new Striing[25];
2 //...
3 for
r(int i=1; i<=25; i+++)
datoss[i-1] = "Dato " + i;
4
5 Arr
rayAdapter<<String> adaptador =
6 new ArrayAdapte
A er<String>(this, and
droid.R.lay
yout.simpl
le_list_ite
em_1,
7 dat
tos);
8
9 fin
nal GridView grdOpcioones = (GridView)fin
ndViewById(
(R.id.Grid
dOpciones);
;
10
11grd
dOpciones.ssetAdapter(adaptadorr);
En cuaanto a los ev
ventos disponnibles, el ms interesante vuelve a seer el lanzadoo al seleccionnarse una
celda determinada
d a de la tabla: onItemSe elected. EsteE evento podemos
p cappturarlo de laa misma
forma que hacam mos con los coontroles Spi inner y ListView. Veamos V un ejemplo de cmo
c
hacerlo:
1 grd
dOpciones.s
setOnItemSelectedLis stener(
new AdapterView.OnIItemSelectedListenerr() {
2 public
p void
d onItemSel
lected(AdapterView<??> parent,
3 android.view.Vieew v, int position,
p l
long id) {
4 lblMensaje.ssetText("Seleccionaddo: " + dattos[positio
on]);
5 }
6
7 public
p void
d onNothing
gSelected(AdapterVieew<?> paren nt) {
lblMensaje.setTeext("");
8 }
9 });
10
11
Todo lo comentado hasta el momento se refiere al uso bsico del control GridView, pero por
supuesto podramos aplicar de forma practicamente directa todo lo comentado para las listas en los
dos artculos anteriores, es decir, la personalizacin de las celdas para presentar datos complejos
creando nuestro propio adaptador, y las distintas optimizaciones para mejorar el rendiemiento de la
aplicacin y el gasto de batera.
Y con esto finalizamos todo lo que tena previsto contar sobre los distintos controles disponibles
de serie en Android para construir nuestras interfaces de usuario. Existen muchos ms, y es
posible que los comentemos ms adelante en algn otro artculo, pero por el momento nos vamos a
conformar con los ya vistos. En el prximo artculo, y para terminar con la serie dedicada a los
controles Android, veremos las distintas formas de crear controles de usuario personalizados.
En artculos anteriores de la serie hemos conocido y aprendido a utilizar muchos de los controles
que proporciona Android en su SDK. Con la ayuda de estos controles podemos disear interfaces
grficas de lo ms variopinto pero en ocasiones, si queremos dar un toque especial y original a
nuestra aplicacin, o simplemente si necesitamos cierta funcionalidad no presente en los
componentes estandar de Android, nos vemos en la necesidad de crear nuestros propios controles
personalizados, diseados a medida de nuestros requisitos.
Android admite por supuesto crear controles personalizados, y permite hacerlo de diferentes
formas:
En este primer artculo sobre el tema vamos a hablar de la primera opcin, es decir, vamos a ver
cmo podemos crear un nuevo control partiendo de la base de un control ya existente. A modo de
ejemplo, vamos a extender el control EditText (cuadro de texto) para que muestre en todo
momento el nmero de caracteres que contiene a medida que se escribe en l. Intentaramos emular
algo as como el editor de mensajes SMS del propio sistema operativo, que nos avisa del nmero de
carateres que contiene el mensaje. En nuestro caso, como resultado obtendremos un control como el
que se muestra en la siguiente imagen:
Como vemos, en la l esquina suuperior dereccha del cuaddro de texto vamos
v a mosstrar el nmeero de
caracteeres del men
nsaje de texto introduciddo, que ira acctualizndose a medida que
q modificaamos el
texto.
Para empezar,
e vam
mos a crear una
u nueva cllase java quee extienda deel control quue queremos utilizar
como base, en estee caso Edit
tText.
1public class Ex
xtendedEditText exte
ends EditTe
ext
2{
3 //...
4 }
Tras esto,
e sobreesccribiremos siempre
s los tres
t construcctores heredaados, donde por
p el momeento nos
limitarremos a llam
mar al mismoo constructorr de la clase padre.
1
2 pubblic Extend
dedEditTextt(Context context, A
AttributeSe
et attrs, int defSty
yle){
super(conntext, attrs,defStylle);
3 }
4
5 pubblic Extend
dedEditTextt(Context context, AttributeSe
A et attrs) {
6 super(conntext, attrs);
7 }
8
pub
blic Extend
dedEditTextt(Context context) {
9 super(conntext);
10}
11
En primer lugar definiremos los pinceles (objetos Paint) que utilizaremos para dibujar, uno de
ellos (p1) de color negro y relleno slido para el fondo del contador, y otro (p2) de color blanco
para el texto.
Dado que estos elementos tan slo har falta crearlos la primera vez que se dibuje el control, para
evitar trabajo innecesario no incluiremos su definicin en el mtodo onDraw(), sino que los
definiremos como atributos de la clase y los inicializaremos en el constructor del control (en los
tres).
Una vez definidos los diferentes pinceles necesarios, dibujaremos el fondo y el texto del contador
mediante los mtodos drawRect() y drawText(), respectivamente, del objeto canvas recibido
en el evento.
1
2 @Override
3 public void onDraw(Canvas canvas)
{
4 //Llamamos al mtodo de la clase base (EditText)
5 super.onDraw(canvas);
6
7 //Dibujamos el fondo negro del contador
8 canvas.drawRect(this.getWidth()-30, 5,
9 this.getWidth()-5, 20, p1);
10
//Dibujamos el nmero de caracteres sobre el contador
11 canvas.drawText("" + this.getText().toString().length(),
12 this.getWidth()-28, 17, p2);
13}
14
Como puede comprobarse, a estos mtodos se les pasar como parmetro las coordenadas del
elemento a dibujar relativas al espacio ocupado por el control y el pincel a utilizar en cada caso.
Hechoo esto, ya tennemos finalizzado nuestroo cuadro de texto
t personalizado con contador de
caracteeres. Para a
adirlo a la interfaz de nuuestra aplicaacin lo incluuiremos en el
e layout XM
ML de la
ventanna tal como haramos
h o control,, teniendo enn cuenta que deberemos hacer
conn cualquier otro
referenncia a l con
n el nombre completo
c dee la nueva claase creada (iincluido el paquete
p java)), que en
mi casso particular sera net.s sgoliver.ExtendedEditTex xt.
1<net
t.sgoliver.
.ExtendedEditText
2 android:id
d="@+id/TxtPrueba"
3 android:la
ayout_width="fill_paarent"
4 android:la
ayout_heig ht="wrap_c
content" />
>
En el siguiente
s arttculo verem
mos cmo creear un controol personalizzado utilizanddo la segundda de las
opcionnes expuestaas, es decir, combinando
c varios contrroles ya exisstentes. Commentaremos adems
a
como aadir eventtos y propieddades personnalizadas a nuestro
n contrrol y cmo hacer
h referenncia a
dichass propiedadees desde su definicin
d XM
ML.
Podiss descargar el
e cdigo de este artculoo desde este enlace.
Intterfaz de usu
uario en An
ndroid
d: Con
ntroless
perrsonallizadoss (II)
Por sggoliver on 23
3/12/2010 enn Android, Programacinn
Ya vimos cmo Android offrece tres foormas difereentes de creear controlees personalizzados para
nuestrras aplicacio
ones y dedicamos el artculo anterioor a comentaar la primerra de las possibilidades,
que coonsista en ex
xtender la fuuncionalidadd de un contrrol ya existennte.
A este control aadiremos adems eventos personalizados, veremos como aadirlo a nuestras
aplicaciones, y haremos que se pueda personalizar su aspecto desde el layout XML de nuestra
interfaz utilizando tambin atributos XML personalizados.
Empecemos por el principio. Lo primero que vamos a hacer es construir la interfaz de nuestro
control a partir de controles sencillos: etiquetas, cuadros de texto y botones. Para ello vamos a crear
un nuevo layout XML en la carpeta \res\layout con el nombre control_login.xml. En este
fichero vamos a definir la estructura del control como ya hemos visto en muchos artculos
anteriores, sin ninguna particularidad destacable. Para este caso quedara como sigue:
1 <LinearLayout
2 xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="fill_parent"
4 android:layout_height="wrap_content"
android:orientation="vertical"
5 android:padding="10dip">
6
7 <TextView android:id="@+id/TextView01"
8 android:layout_width="wrap_content"
9 android:layout_height="wrap_content"
android:text="Usuario:"
10 android:textStyle="bold" />
11
12 <EditText android:id="@+id/TxtUsuario"
13 android:layout_height="wrap_content"
14 android:layout_width="fill_parent" />
15
16 <TextView android:id="@+id/TextView02"
android:layout_width="wrap_content"
17 android:layout_height="wrap_content"
18 android:text="Contrasea:"
19 android:textStyle="bold" />
20
21 <EditText android:id="@+id/TxtPassword"
android:layout_height="wrap_content"
22 android:layout_width="fill_parent" />
23
24 <LinearLayout android:orientation="horizontal"
25 android:layout_width="fill_parent"
26 android:layout_height="fill_parent" >
27
28 <Button
android:layout_width="wrap_content"
29 android:layout_height="wrap_content"
30 android:id="@+id/BtnAceptar"
31 android:text="Login"
32 android:paddingLeft="20dip"
android:paddingRight="20dip" />
33
34
35 <Text
tView andro
oid:id="@+id/LblMenssaje"
36 android:la
a yout_width
h="wrap_co ntent"
android:la
a yout_heigh
ht="wrap_coontent"
37 android:pa
a ddingLeft=
="10dip"
38 android:te
a xtStyle="b
bold" />
39
40 </LinearL
Layout>
41
42</L
LinearLayou
ut>
43
44
45
46
47
48
A conntinuacin crearemos
c suu clase javaa asociada donde definniremos todda la funcionnalidad de
nuestrro control. Dado
D que noss hemos basaado en un LinearLay yout para coonstruir el coontrol, esta
nueva clase deberr heredar taambin de laa clase java LinearLa ayout de Android.
A Reddefiniremos
adems los dos coonstructores bsicos:
b
1
2 pubblic class ControlLog
C in extends
s LinearLay
yout
{
3
public ControlLogiin(Context context) {
4 super(cont
s ext);
5 inicializa
i r();
6 }
7
8 public ControlLogiin(Context context, AttributeS
Set attrs) {
super(conntext, attrs);
9 inicializzar();
10 }
11}
12
1
2 private void inicializar()
3 {
4 //Utilizamos el layout 'control_login' como interfaz del control
String infService = Context.LAYOUT_INFLATER_SERVICE;
5
LayoutInflater li =
6 (LayoutInflater)getContext().getSystemService(infService);
7 li.inflate(R.layout.control_login, this, true);
8
9 //Obtenemoslas referencias a los distintos control
10 txtUsuario = (EditText)findViewById(R.id.TxtUsuario);
txtPassword = (EditText)findViewById(R.id.TxtPassword);
11 btnLogin = (Button)findViewById(R.id.BtnAceptar);
12 lblMensaje = (TextView)findViewById(R.id.LblMensaje);
13
14 //Asociamos los eventos necesarios
15 asignarEventos();
16 }
17
Con esto ya tenemos definida la interfaz y la funcionalidad bsica del nuevo control por lo que ya
podemos utilizarlo desde otra actividad como si se tratase de cualquier otro control predefinido.
Para ello haremos referencia a l utilizando la ruta completa del paquete java utilizado, en nuestro
caso quedara como net.sgoliver.ControlLogin. Vamos a insertar nuestro nuevo control
en la actividad principal de la aplicacin:
1
2 <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
3
android:orientation="vertical"
4 android:layout_width="fill_parent"
5 android:layout_height="fill_parent"
6 android:padding="10dip" >
7
8 <net.sgoliver.ControlLogin android:id="@+id/CtlLogin"
android:layout_width="fill_parent"
9 android:layout_height="wrap_content"
10 android:background="#0000AA" />
11</LinearLayout>
12
En seegundo lugaar, todo conntrol que see precie debbe tener alggunos eventtos que noss permitan
responnder a las acciones del usuario
u de la aplicacin. As por ejem
mplo, los bootones tienenn un evento
OnCli ick, las lisstas un evennto OnItem mSelected d, etc. Puess bien, nosootros vamos a dotar a
nuestrro control dee un eventoo personalizaado, llamadoo OnLogin n, que se lannce cuando el usuario
introduuce sus cred
denciales de identificaci
i n y pulsa el botn Loggin.
Para ello,
e lo prim
mero que vam mos a hacerr es concrettar los detallles de dichoo evento, crreando una
interfaaz java para definir su liistener. Estaa interfaz tann slo tendr un mtodo llamado on
nLogin()
que deevolver los dos datos inntroducidos por p el usuariio (usuario y contrasea)). Vemos cmmo queda:
1pack
kage net.sg
goliver;
2
3public interface OnLoginListener
4{
5 void onLog
gin(String usuario, String pas
ssword);
}
6
A conntinuacin, deberemos
d a
aadir un nuuevo miembbro de tipo OnLoginL Listener a la clase
Contr rolLogin, y un mtoddo pblico quue permita suscribirse
s all nuevo evennto.
1 pub
blic class ControlLog
C in extends
s LinearLay
yout
2 {
3 private OnLoginLis
stener listener;
4
//...
5
6 public void
v setOnLoginListen
ner(OnLoginListener l)
7 {
8 listener = l;
9 }
}
10
11
Con esto,
e la apliccacin princcipal ya pueede suscribirrse al eventoo OnLogin n y ejecutarr su propio
cdigoo cuando stte se generee. Pero cunndo se geneera exactameente? Dijimoos antes quee queremos
lanzarr el evento OnLogin
O cuuando el usuuario pulse ell botn Loggin de nuesstro control. Pues bien,
para hacerlo,
h volvvamos al mtodo
m asiggnarEvent tos() que antes dejam mos aparcaddo. En este
mtoddo vamos a implementar
i r el evento OnClick
O deel botn de Login
L para lanzar
l el nueevo evento
OnLog gin del co ontrol. Confundido?. Inntento explicarlo de otrra forma. Vamos
V a aprrovechar el
eventoo OnClick( () del botn Login (quue es un evennto interno a nuestro conntrol, no se ver desde
fuera) para lanzar hacia el extterior el evennto OnLogi in() (que ser el que debe
d capturarr y tratar la
aplicaccin que hagga uso del coontrol).
Para ello,
e implementaremos el e evento OnClick com mo ya hemoss hecho en otras
o ocasionnes y como
accionnes generareemos el evvento OnLog gin de nuestro
n listenner pasndoole los datoos que ha
introduucido el usu
uario en los cuadros
c de teexto Usuariio y Contraasea:
1
2 private void asignarEve
a ntos()
{
3
btnLogin..setOnClickListener((new OnClic
ckListener(()
4 {
5 @Overrride
6 publiic void onClick(View v) {
7 listener.o
l nLogin(txt
tUsuario.getText().ttoString(),
,
txtPass
sword.getTeext().toSt
tring());
8 }
9 });
10}
11
Con toodo esto, la aplicacin principal
p ya puede
p impleementar el evvento OnLogin de nuesttro control,
hacienndo por ejem mplo la validaacin de las credenciales del usuarioo y modificaando convenientemente
el textto de la etiqu
ueta de resulttado:
1
2
@Ov
verride
3 pubblic void on
nCreate(Bundle saveddInstanceState)
4 {
5 super.onCCreate(sav
vedInstanceeState);
6 setContenntView(R.layout.mainn);
7
ctlLogin = (ControlLogin)fin ndViewById((R.id.CtlL
Login);
8
9
ctlLogin..setOnLoginListener((new OnLogi
inListener(()
10 {
11 @Overridee
12 public void onLogin(String ussuario, String passwword)
13 {
//Vallidamos el usuario y la contrasea
14 if (usuario.equuals("demo") && passsword.equalls("demo"))
15 ctlLogin.s
c etMensaje(
("Login correcto!");;
16 else
17 ctlLogin.s
c etMensaje(
("Vuelva a intentarllo.");
18 }
});
19}
20
21
Cuando vimos cmo aadir el control de login al layout de la aplicacin principal dijimos que
podamos utilizar cualquier atributo xml permitido para el contenedor LinearLayout, ya que
nuestro control derivaba de ste. Pero vamos a ir ms all y vamos a definir tambin atributos xml
exclusivos para nuestro control. Como ejemplo, vamos a definir un atributo llamado login_text
que permita establecer el texto del botn de Login desde el propio layout xml, es decir, en tiempo
de diseo.
Primero vamos de declarar el nuevo atributo y lo vamos a asociar al control ControlLogin. Esto
debe hacerse en el fichero \res\values\attrs.xml. Para ello, aadiremos una nueva seccin
<declare-styleable> asociada a ControlLogin dentro del elemento <resources>,
donde indicaremos el nombre (name) y el tipo (format) del nuevo atributo.
1<resources>
2 <declare-styleable name="ControlLogin">
3 <attr name="login_text" format="string"/>
4 </declare-styleable>
</resources>
5
En nuestro caso, el tipo del atributo ser string, dado que contendr una cadena de texto con el
mensaje a mostrar en el botn.
Con esto ya tendremos permitido el uso del nuevo atributo dentro del layout de la aplicacin
principal. Ahora nos falta procesar el atributo desde nuestro control personalizado. Este tratamiento
lo podemos hacer en el construtor de la clase ControlLogin. Para ello, obtendremos la lista de
atributos asociados a ControlLogin mediante el mtodo obtainStyledAttributes()
del contexto de la aplicacin, obtendremos el valor del nuevo atributo definido (mediante su ID, que
estar formado por la concatenacin del nombre del control y el nombre del atributo, en nuestro
caso ControlLogin_login_text) y modificaremos el texto por defecto del control con el
nuevo texto.
1xmln
ns:sgo="htt
tp://schem
mas.android
d.com/apk/res/net.sg
goliver"
Tras esto,
e slo quueda asignaar el valor del
d nuevo atributo
a preccedido del nuevo
n nameespace, por
ejemplo con el tex
xto Entrar:
1
2 <LinearLayout
t
3 xmlns:and
droid="http://schemaas.android.com/apk/rres/android
d"
xmlns:sgo
o="http://schemas.anndroid.com/apk/res/nnet.sgolive
er"
4
android:o
orientation="verticaal"
5 android:l
layout_wid
dth="fill_pparent"
6 android:l
layout_height="fill__parent"
7 android:p
padding="10dip" >
8
9 <net.sgol
liver.ControlLogin android:id=="@+id/CtlLLogin"
andro
oid:layout_width="fiill_parent"
10 andro
oid:layout_height="wwrap_content"
11 andro
oid:background="#00000AA"
12 sgo:l
login_text="Entrar" />
13</L
LinearLayou
ut>
14
Podiss descargar el
e cdigo fueente de este artculo
a pulssando aqu.
Esperoo que os sea til y que siigis los artcculos que quuedan por veenir.
Intterfaz de usu
uario en An
ndroid
d: Con
ntroless
perrsonallizadoss (III))
Por sggoliver on 10
0/02/2011 enn Android, Programacinn
En arttculos anterriores del cuurso ya comentamos doss de las possibles vas queq tenemos para crear
controoles personallizados en Android:
A la primera de elllas extendieendo la funciionalidad de un control
ya existente, y com
mo segunda opcin creanndo un nuevvo control coompuesto porr otros ms sencillos.
s
Los coolores dispon nibles van a ser slo cuaatro, que se mostrarn enn la franja suuperior del control.
c En
la partte inferior see mostrar ell color seleccionado en cada
c momennto, o permaanecer negroo si an no
se ha seleccionado o ningn coolor. Valga la l siguiente imagen commo muestra del d aspecto que tendr
nuestrro control de seleccin de color:
Por supuesto este control no tiene mucha utilidad prctica dada su sencillez, pero creo que puede
servir como ejemplo para comentar los pasos necesarios para construir cualquier otro control ms
complejo. Empecemos.
En las anteriores ocasiones vimos cmo el nuevo control creado siempre heredaba de algn otro
control o contenedor ya existente. En este caso sin embargo, vamos a heredar nuestro contro
directamente de la clase View (clase padre de la gran mayora de elementos visuales de Android).
Esto implica, entre otras cosas, que por defecto nuestro control no va a tener ningn tipo de interfaz
grfica, por lo que todo el trabajo de dibujar la interfaz lo vamos a tener que hacer nosotros.
Adems, como paso previo a la representacin grfica de la interfaz, tambin vamos a tener que
determinar las dimensiones que nuestro control tendr dentro de su elemento contenedor. Como
veremos ahora, ambas cosas se llevarn a cabo redefiniendo dos eventos de la clase View,
onDraw() para el dibujo de la interfaz, y onMeasure() para el clculo de las dimensiones.
Por llevar un orden cronolgico, empecemos comentando el evento onMeasure(). Este evento se
ejecuta automticamente cada vez que se necesita recalcular el tamao de un control. Pero como ya
hemos visto en varias ocasiones, los elementos grficos incluidos en una aplicacin Android se
distribuyen por la pantalla de una forma u otra dependiendo del tipo de contenedor o layout
utilizado. Por tanto, el tamao de un control determinado en la pantalla no depender slo de l,
sino de ciertas restricciones impuestas por su elemento contenedor o elemento padre. Para resolver
esto, en el evento onMeasure() recibiremos como parmetros las restricciones del elemento
padre en cuanto a ancho y alto del control, con lo que podremos tenerlas en cuenta a la hora de
determinar el ancho y alto de nuestro control personalizado. Estas restricciones se reciben en forma
de objetos MeasureSpec, que contiene dos campos: modo y tamao. El significado del segundo
de ellos es obvio, el primero por su parte sirve para matizar el significado del segundo. Me explico.
Este campo modo puede contener tres valores posibles:
AT_MOST: indica que el control podr tener como mximo el tamao especificado.
EXACTLY: indica que al control se le dar exactamente el tamao especificado.
UNSPECIFIED: indica que el control padre no impone ninguna restriccin sobre el tamao.
Dependiendo de esta pareja de datos, podremos calcular el tamao deseado para nuestro control.
Para nuestro control de ejemplo, apuraremos siempre el tamao mximo disponible (o un tamao
por defecto de 200*100 en caso de no recibir ninguna restriccin), por lo que en todos los casos
elegiremos como tamao de nuestro control el tamao recibido como parmetro:
1 @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
2 {
3 int ancho = calcularAncho(widthMeasureSpec);
4 int alto = calcularAlto(heightMeasureSpec);
5
6 setMeasuredDimension(ancho, alto);
7 }
8
private int calcularAlto(int limitesSpec)
9 {
10 int res = 100; //Alto por defecto
11
12 int modo = MeasureSpec.getMode(limitesSpec);
13 int limite = MeasureSpec.getSize(limitesSpec);
14
if (modo == MeasureSpec.AT_MOST) {
15 res = limite;
16 }
17 else if (modo == MeasureSpec.EXACTLY) {
18 res = limite;
}
19
20 return res;
21}
22
23private int calcularAncho(int limitesSpec)
24{
25 int res = 200; //Ancho por defecto
26
int modo = MeasureSpec.getMode(limitesSpec);
27 int limite = MeasureSpec.getSize(limitesSpec);
28
29 if (modo == MeasureSpec.AT_MOST) {
30 res = limite;
31 }
else if (modo == MeasureSpec.EXACTLY) {
32 res = limite;
33 }
34
35 return res;
36}
37
38
39
40
41
42
Como nota importante, al final del evento onMeasure() siempre debemos llamar al mtodo
setMeasuredDimension() pasando como parmetros el ancho y alto calculados para nuestro
control.
Con esto ya hemos determinado las dimensiones del control, por lo que tan slo nos queda dibujar
su interfaz grfica. Como hemos indicado antes, esta tarea se realiza dentro del evento onDraw().
Este evento recibe como parmetro un objeto de tipo Canvas, sobre el que podremos ejecutar
todas las operaciones de dibujo de la interfaz. No voy a entrar en detalles de la clase Canvas,
porque se ser tema central de un prximo artculo. Por ahora nos vamos a conformar sabiendo
que es la clase que contiene la mayor parte de los mtodos de dibujo en interfaces Android, por
ejemplo drawRect() para dibujar rectngulos, drawCircle() para crculos,
drawBitmap() para imagenes, drawText() para texto, e infinidad de posibilidades ms. Para
consultar todos los mtodos disponibles puedes dirigirte a la documentacin oficial de la clase
Canvas de Android. Adems de la clase Canvas, tambin me gustara destacar la clase Paint,
que permite definir el estilo de dibujo a utilizar en los metodos de dibujo de Canvas, por ejemplo
el ancho de trazado de las lneas, los colores de relleno, etc.
Para nuestro ejemplo no necesitaramos conocer nada ms, ya que la interfaz del control es
relativamente sencilla. Vemos primero el cdigo y despus comentamos los pasos realizados:
1
2
3 @Override
4 protected void onDraw(Canvas canvas)
5 {
6 //Obtenemos las dimensiones del control
int alto = getMeasuredHeight();
7 int ancho = getMeasuredWidth();
8
9 //Colores Disponibles
10 Paint pRelleno = new Paint();
11 pRelleno.setStyle(Style.FILL);
12
pRelleno.setColor(Color.RED);
13
canvas.drawRect(0, 0, ancho/4, alto/2, pRelleno);
14
15 pRelleno.setColor(Color.GREEN);
16 canvas.drawRect(ancho/4, 0, 2*(ancho/4), alto/2, pRelleno);
17
18 pRelleno.setColor(Color.BLUE);
19 canvas.drawRect(2*(ancho/4), 0, 3*(ancho/4), alto/2, pRelleno);
20
pRelleno.setColor(Color.YELLOW);
21 canvas.drawRect(3*(ancho/4), 0, 4*(ancho/4), alto/2, pRelleno);
22
23 //Color Seleccionado
24 pRelleno.setColor(colorSeleccionado);
25 canvas.drawRect(0, alto/2, ancho, alto, pRelleno);
26
27 //Marco del control
Paint pBorde = new Paint();
28 pBorde.setStyle(Style.STROKE);
29 pBorde.setColor(Color.WHITE);
30 canvas.drawRect(0, 0, ancho-1, alto/2, pBorde);
31 canvas.drawRect(0, 0, ancho-1, alto-1, pBorde);
}
32
33
34
Con esto, ya deberamos tener un control con el aspecto exacto que definimos en un principio. El
siguiente paso ser definir su funcionalidad implementando los eventos a los que queramos que
responda nuestro control, tanto eventos internos como externos.
En nuestro caso slo vamos a tener un evento de cada tipo. En primer lugar definiremos un evento
interno (evento que slo queremos capturar de forma interna al control, sin exponerlo al usuario)
para responder a las pulsaciones del usuario sobre los colores de la zona superior, y que
utilizaremos para actualizar el color de la zona inferior con el color seleccionado. Para ello
implementaremos el evento onTouch(), lanzado cada vez que el usuario toca la pantalla sobre
nuestro control. La lgica ser sencilla, simplemente consultaremos las coordenadas donde ha
pulsado el usuario (mediante los mtodos getX() y getY()), y dependiendo del lugar pulsado
determinaremos el color sobre el que se ha seleccionado y actualizaremos el valor del atibuto
colorSeleccionado. Finalmente, llamamos al mtodo invalidate() para refrescar la
interfaz del control, reflejando as el cambio en el color seleccionado, si se ha producido.
1
2
3 @Override
4 public boolean onTouchEvent(MotionEvent event)
5 { //Si se ha pulsado en la zona superior
6 if (event.getY() > 0 && event.getY() < (getMeasuredHeight()/2))
7 {
8 //Si se ha pulsado dentro de los lmites del control
9 if (event.getX() > 0 && event.getX() < getMeasuredWidth())
{
10 //Determinamos el color seleccionado segn el punto pulsado
11 if(event.getX() > (getMeasuredWidth()/4)*3)
12 colorSeleccionado = Color.YELLOW;
13 else if(event.getX() > (getMeasuredWidth()/4)*2)
14 colorSeleccionado = Color.BLUE;
else if(event.getX() > (getMeasuredWidth()/4)*1)
15 colorSeleccionado = Color.GREEN;
16 else
17 colorSeleccionado = Color.RED;
18
19 //Refrescamos el control
this.invalidate();
20 }
21 }
22
23 return super.onTouchEvent(event);
24}
25
26
En segundo lugar crearemos un evento externo personalizado, que lanzaremos cuando el usuario
pulse sobre la zona inferior del control, como una forma de aceptar definitivamente el color
seleccionado. Llamaremos a este evento onSelectedColor(). Para crearlo actuaremos de la
misma forma que ya vimos en el artculo anterior. Primero definiremos una interfaz para el listener
de nuestro evento:
1package net.sgoliver.android;
2
3public interface OnColorSelectedListener
4{
5 void onColorSelected(int color);
}
6
Posteriormente, definiremos un objeto de este tipo como atributo de nuestro control y escribiremos
un nuevo mtodo que permita a las aplicaciones suscribirse al evento:
1
2 public class SelectorColor extends View
{
3 private OnColorSelectedListener listener;
4
5 //...
6
7 public void setOnColorSelectedListener(OnColorSelectedListener l)
8 {
listener = l;
9 }
10}
11
Y ya slo nos quedara lanzar el evento en el momento preciso. Esto tambin lo haremos dentro del
evento onTouch(), cuando detectemos que el usuario ha pulsado en la zona inferior de nuestro
control:
1 @Override
2 public boolean onTouchEvent(MotionEvent event)
{
3 //Si se ha pulsado en la zona superior
4 if (event.getY() > 0 && event.getY() < (getMeasuredHeight()/2))
5 {
6 //...
}
7 //Si se ha pulsado en la zona inferior
8 else if (event.getY() > (getMeasuredHeight()/2) &&
9 event.getY() < getMeasuredHeight())
10 {
11 //Lanzamos el evento externo de seleccin de color
listener.onColorSelected(colorSeleccionado);
12 }
13
14 return super.onTouchEvent(event);
15}
16
17
18
Con esto, nuestra aplicacin principal ya podra suscribirse a este nuevo evento para estar
informada cada vez que se seleccione un color. Sirva la siguiente plantilla a modo de ejemplo:
1
2
3 public
{
class ControlPersonalizado extends Activity
4 private SelectorColor ctlColor;
5
6 @Override
7 public void onCreate(Bundle savedInstanceState)
8 {
super.onCreate(savedInstanceState);
9 setContentView(R.layout.main);
10
11 ctlColor = (SelectorColor)findViewById(R.id.scColor);
12
13 ctlColor.setOnColorSelectedListener(new OnColorSelectedListener()
14 {
15 @Override
public void onColorSelected(int color)
16 {
17 //Aqu se tratara el color seleccionado (parmetro 'color'
18 //...
19 }
});
20 }
21}
22
23
Con esto, tendramos finalizado nuestro control completamente personalizado, que hemos
construido sin utilizar como base ningn otro control predefinido, definiendo desde cero tanto su
aspecto visual como su funcionalidad interna o sus eventos pblicos.
En artculos anteriores del Curso de Programacin Android hemos visto como dar forma a la
interfaz de usuario de nuestra aplicacin mediante el uso de diversos tipos de layouts, como por
ejemplo los lineales, absolutos, relativos, u otros ms elaborados como los de tipo lista o tabla.
stos van a ser siempre los elementos organizativos bsicos de nuestra interfaz, pero sin embargo,
dado el poco espacio con el que contamos en las pantallas de muchos dispositivos, o simplemente
por cuestiones de organizacin, a veces es necesario/interesante dividir nuestros controles en varias
pantallas. Y una de las formas clsicas de conseguir esto consiste en la distribucin de los
elementos por pestaas o tabs. Android por supuesto permite utilizar este tipo de interfaces, aunque
lo hace de una forma un tanto peculiar, ya que la implementacin no va a depender de un slo
elemento sino de varios, que adems deben estar distribuidos y estructurados de una forma
determinada nada arbitraria. Adicionalmente no nos bastar simplemente con definir la interfaz en
XML como hemos hecho en otras ocasiones, sino que tambin necesitaremos completar el conjunto
con algunas lneas de cdigo. Desarrollemos esto poco a poco.
1 <TabHost android:id="@android:id/tabhost"
android:layout_width="match_parent"
2 android:layout_height="match_parent">
3
4 <LinearLayout
5 android:orientation="vertical"
6 android:layout_width="fill_parent"
android:layout_height="fill_parent" >
7
8 <TabWidget android:layout_width="match_parent"
9 android:layout_height="wrap_content"
10 android:id="@android:id/tabs" />
11
12 <FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
13 android:id="@android:id/tabcontent" >
14
15 <LinearLayout android:id="@+id/tab1"
16 android:orientation="vertical"
17 android:layout_width="match_parent"
18 android:layout_height="match_parent" >
<TextView android:id="@+id/textView1"
19 android:text="Contenido Tab 1"
20 android:layout_width="wrap_content"
21 android:layout_height="wrap_content" />
22 </LinearLayout>
23
<LinearLayout android:id="@+id/tab2"
24 android:orientation="vertical"
25 android:layout_width="match_parent"
26 android:layout_height="match_parent" >
27 <TextView android:id="@+id/textView2"
28 android:text="Contenido Tab 2"
android:layout_width="wrap_content"
29 android:layout_height="wrap_content" />
30 </LinearLayout>
31
32 </FrameLayout>
33 </LinearLayout>
34
35</TabHost>
36
37
38
39
40
41
Como podis ver, como contenido de las pestaas tan slo he aadido por simplicidad una etiqueta
de texto con el texto Contenido Tab NTab. Esto nos permitir ver que el conjunto de pestaas
funciona correctamente cuando ejecutemos la aplicacin.
Con esto ya tendramos montada toda la estructura de controles necesaria para nuestra interfaz de
pestaas. Sin embargo, como ya dijimos al principio del artculo, con esto no es suficiente.
Necesitamos asociar de alguna forma cada pestaa con su contenido, de forma que el el control se
comporte correctamente cuando cambiamos de pestaa. Y esto tendremos que hacerlo mediante
cdigo en nuestra actividad principal.
Empezaremos obteniendo una referencia al control principal TabHost y preparndolo para su
configuracin llamando a su mtodo setup(). Tras esto, crearemos un objeto de tipo TabSpec
para cada una de las pestaas que queramos aadir mediante el mtodo newTabSpec(), al que
pasaremos como parmetro una etiqueta identificativa de la pestaa (en mi caso de ejemplo
mitab1, mitab2, ). Adems, tambin le asignaremos el layout de contenido correspondiente a
la pestaa llamando al mtodo setContent(), e indicaremos el texto y el icono que queremos
mostrar en la pestaa mediante el mtodo setIndicator(texto, icono). Veamos el
cdigo completo.
1
2 Resources res = getResources();
3
4 TabHost tabs=(TabHost)findViewById(android.R.id.tabhost);
5 tabs.setup();
6
TabHost.TabSpec spec=tabs.newTabSpec("mitab1");
7 spec.setContent(R.id.tab1);
8 spec.setIndicator("TAB1",
9 res.getDrawable(android.R.drawable.ic_btn_speak_now));
10tabs.addTab(spec);
11
12spec=tabs.newTabSpec("mitab2");
spec.setContent(R.id.tab2);
13spec.setIndicator("TAB2",
14 res.getDrawable(android.R.drawable.ic_dialog_map));
15tabs.addTab(spec);
16
17tabs.setCurrentTab(0);
18
Si vemos el cdigo, vemos por ejemplo como para la primera pestaa creamos un objeto TabSpec
con la etiqueta mitab1, le asignamos como contenido uno de los LinearLayout que incluimos
en la seccin de contenido (en este caso R.id.tab1) y finalmente le asignamos el texto TAB1 y
el icono android.R.drawable.ic_btn_speak_now (ste es un icono incluido con la
propia plataforma Android. Si no existiera en vuestra versin podis sustituirlo por cualquier otro
icono). Finalmente aadimos la nueva pestaa al control mediante el mtodo addTab().
Si ejecutamos ahora la aplicacin tendremos algo como lo que muestra la siguiente imagen, donde
podremos cambiar de pestaa y comprobar cmo se muestra correctamente el contenido de la
misma.
En cuuanto a los eventos diisponibles del d control TabHost, aunque noo suele serr necesario
capturrarlos, podem
mos ver a modo
m de ejemmplo el ms interesante de
d ellos, OnTabChang ged, que se
lanza cada vez qu ue se cambiaa de pestaaa y que nos informa
i de la nueva pesstaa visualiizada. Este
eventoo podem
mos im
mplementarloo y asignarlo mediantte el mtodo
setOn nTabChang gedListener() de la siguiente forma:
f
Puedes descargar el cdigo fuente compleeto del ejempplo utilizado en este artcculo a travss de este
enlacee.
Meens en And
droid (I):
( Cooncepttos bsicos
Por sggoliver on 21
1/03/2011 enn Android, Programacinn
En loss dos siguieentes artcullos del Cursso de Prograamacin Anndroid nos vamos
v a cenntrar en la
creacin de menss de opcionees en sus difeerentes variaantes.
En Android podemos encontrar 3 tipos diferentes de mens:
En este primer artculo sobre el tema veremos cmo trabajar con los dos primeros tipos de mens.
En el siguiente, comentaremos los mens contextuales y algunos caractersticas ms avanzadas.
Como casi siempre, vamos a tener dos alternativas a la hora de mostrar un men en nuestra
aplicacin Android. La primera de ellas mediante la definicin del men en un fichero XML, y la
segunda creando el men directamente mediante cdigo. En este artculo veremos ambas
alternativas.
Veamos en primer lugar cmo crear un men a partir de su definicin en XML. Los ficheros XML
de men se deben colocar en la carpeta res\menu de nuestro proyecto y tendrn una estructura
anloga a la del siguiente ejemplo:
1
2 <?xml version="1.0" encoding="utf-8"?>
<menu
3 xmlns:android="http://schemas.android.com/apk/res/android">
4
5 <item android:id="@+id/MnuOpc1" android:title="Opcion1"
6 android:icon="@drawable/tag"></item>
7 <item android:id="@+id/MnuOpc2" android:title="Opcion2"
8 android:icon="@drawable/filter"></item>
<item android:id="@+id/MnuOpc3" android:title="Opcion3"
9 android:icon="@drawable/chart"></item>
10
11</menu>
12
Como vemos, la estructura bsica de estos ficheros es muy sencilla. Tendremos un elemento
principal <menu> que contendr una serie de elementos <item> que se correspondern con las
distintas opciones a mostrar en el men. Estos elementos <item> tendrn a su vez varias
propiedades bsicas, como su ID (android:id), su texto (android:title) o su icono
(android:icon). Los iconos utilizados debern estar por supuesto en las carpetas
res\drawable- de nuestro proyecto (al final del artculo os paso unos enlaces donde podis
conseguir algunos iconos de men Android gratuitos).
Una vez definido el men en el fichero XML, tendremos que implementar el evento
onCreateOptionsMenu() de la actividad que queremos que lo muestre. En este evento
deberemos inflar el men de forma parecida a cmo ya hemos hecho otras veces con otro tipo de
layouts. Primero obtendremos una referencia al inflater mediante el mtodo
getMenuInflater() y posteriormente generaremos la estructura del men llamando a su
mtoddo infate( D del menu definido en XML, que en nuestro
() pasndolle como parmetro el ID
caso ser
s R.menu u.menu_pr rincipal. Por ltimoo devolveremmos el valorr true paraa confirmar
que deebe mostrarsse el men.
1
@Ove
erride
2public boolean onCreateOptionsMenuu(Menu menu
u) {
3 //Alternat
tiva 1
4 MenuInflat
ter inflater = getMeenuInflater();
5 inflater.i
inflate(R. menu.menu_
_principal, menu);
return true;
6}
7
Y ya hemos term minado, conn estos sencillos pasoss nuestra applicacin yaa debera mostrar
m sin
probleemas el men que hemoss construdo, aunque toddava nos falltara implem
mentar la funncionalidad
de cadda una de lass opciones mostradas.
m
1
2 @Override
3 public boolean onOptionsItemSelected(MenuItem item) {
4 switch (item.getItemId()) {
5 case R.id.MnuOpc1:
lblMensaje.setText("Opcion 1 pulsada!");
6 return true;
7 case R.id.MnuOpc2:
8 lblMensaje.setText("Opcion 2 pulsada!");;
9 return true;
10 case R.id.MnuOpc3:
lblMensaje.setText("Opcion 3 pulsada!");;
11 return true;
12 default:
13 return super.onOptionsItemSelected(item);
14 }
15}
16
Pasemos ahora a comentar los submens. Un submen no es ms que un men secundario que se
muestra al pulsar una opcin determinada de un men principal. Los submens en Android se
muestran en forma de lista emergente, cuyo ttulo contiene el texto de la opcin elegida en el men
principal. Como ejemplo, vamos a aadir un submen a la Opcin 3 del ejemplo anterior, al que
aadiremos dos nuevas opciones secundarias. Para ello, bastar con insertar en el XML de men un
nuevo elemento <menu>
< denntro del item
m corresponddiente a la opcin
o 3. De
D esta forma, el XML
quedarra ahora com
mo sigue:
1
2 <?xxml version="1.0" encoding="utf f-8"?>
3 <meenu
4 x
xmlns:androoid="http://schemas. .android.coom/apk/res
s/android">
>
5
6 <ittem android
d:id="@+id/ /MnuOpc1" android:tit
a tle="Opcio
on1"
androidd:icon="@ddrawable/taag"></item>
7
<it
tem android
d:id="@+id/ /MnuOpc2" android:tit
a tle="Opcio
on2"
8 androidd:icon="@ddrawable/fiilter"></item>
9 <ittem android
d:id="@+id/ /MnuOpc3" android:tit
a tle="Opcio
on3"
10 androidd:icon="@ddrawable/chhart">
11 <menu>
<itemm android:iid="@+id/SubMnuOpc1""
12 android:title="Opc cion 3.1" />
/
13 <itemm android:iid="@+id/SubMnuOpc2""
14 android:title="Opc cion 3.2" />
/
15 </menu>
16</item>
17
</m
menu>
18
19
1//Alternativa 2
2menu.add(Menu.NONE, MNU_OPC1, Menu.NONE, "Opcion1").setIcon(R.drawable.tag);
3menu.add(Menu.NONE, MNU_OPC1, Menu.NONE, "Opcion2").setIcon(R.drawable.filter);
4//menu.add(Menu.NONE, MNU_OPC1, Menu.NONE,
"Opcion3").setIcon(R.drawable.chart);
5
6SubMenu smnu = menu.addSubMenu(Menu.NONE, MNU_OPC1, Menu.NONE, "Opcion3")
7 .setIcon(R.drawable.chart);
8smnu.add(Menu.NONE, SMNU_OPC1, Menu.NONE, "Opcion 3.1");
9smnu.add(Menu.NONE, SMNU_OPC2, Menu.NONE, "Opcion 3.2");
Por tanto, con esto habramos terminado de comentar las opciones bsicas a la hora de crear mens
y submenus en nuestras aplicaciones Android. En el siguiente artculo veremos algunas opciones
algo ms avanzadas que, aunque menos frecuentes, puede que nos hagan falta para desarrollar
determinadas aplicaciones.
Si necesitis iconos para mostrar en los mens aqu tenis varios enlaces con algunos gratuitos que
podis utilizar en vuestras aplicaciones Android:
http://www.androidicons.com/freebies.php
http://www.glyfx.com/products/free_android2.html
En el artculo anterior del curso ya vimos cmo crear mens y submens bsicos para nuestras
aplicaciones Android. Sin embargo, existe otro tipo de mens que nos pueden ser muy tiles en
determinados contextos: los mens contextuales. Este tipo de men siempre va asociado a un
control concreto de la pantalla y se muestra al realizar una pulsacin larga sobre ste. Suele mostrar
opciones especficas disponibles nicamente para el elemento pulsado. Por ejemplo, en un control
de tipo lista podramos tener un men contextual que apareciera al pulsar sobre un elemento
concreto de la lista y que permitiera editar su texto o eliminarlo de la coleccin.
Pues bien, la creacin y utilizacin de este tipo de mens es muy parecida a lo que ya vimos para
los mens y submens bsicos, pero presentan algunas particularidades que hacen interesante
tratarlos al margen del resto en este nuevo artculo.
Empecemos por un caso sencillo. Vamos a partir del ejemplo del artculo anterior, al que vamos a
aadir en primer lugar un men contextual que aparezca al pulsar sobre la etiqueta de texto que
mostrbamos en la ventana principal de la aplicacin. Para ello, lo primero que vamos a hacer es
indicar en el mtodo onCreate() de nuestra actividad principal que la etiqueta tendr asociado
un men contextual. Esto lo conseguimos con una llamada a registerForContextMenu():
1
public void onCreate(Bundle savedInstanceState) {
2 super.onCreate(savedInstanceState);
3 setContentView(R.layout.main);
4
5 //Obtenemos las referencias a los controles
6 lblMensaje = (TextView)findViewById(R.id.LblMensaje);
7
8 //Asociamos los mens contextuales a los controles
registerForContextMenu(lblMensaje);
9 }
10
A continuacin, igual que hacamos con onCreateOptionsMenu() para los mens bsicos,
vamos a sobreescribir en nuestra actividad el evento encargado de construir los mens contextuales
asociados a los diferentes controles de la aplicacin. En este caso el evento se llama
onCreateContextMenu(), y a diferencia de onCreateOptionsMenu() ste se llama cada
vez que se necesita mostrar un men contextual, y no una sola vez al inicio de la aplicacin. En este
evento actuaremos igual que para los mnus bsicos, inflando el men XML que hayamos creado
con las distintas opciones, o creando a mano el men mediante el mtodo add() [para ms
informacin leer el artculo anterior]. En nuestro ejemplo hemos definido un men en XML
llamado menu_ctx_etiqueta.xml:
1
<?xml version="1.0" encoding="utf-8"?>
2 <menu
3 xmlns:android="http://schemas.android.com/apk/res/android">
4
5 <item android:id="@+id/CtxLblOpc1"
6 android:title="OpcEtiqueta1"></item>
<item android:id="@+id/CtxLblOpc2"
7
android:title="OpcEtiqueta2"></item>
8
9 </menu>
10
1@Override
public void onCreateContextMenu(ContextMenu menu, View v,
2 ContextMenuInfo menuInfo)
3{
4 super.onCreateContextMenu(menu, v, menuInfo);
5
6 MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_ctx_etiqueta, menu);
7}
8
9
Por ltimo, para implementar las acciones a realizar tras pulsar una opcin determinada del men
contextual vamos a implementar el evento onContextItemSelected() de forma anloga a
cmo hacamos con onOptionsItemSelected() para los mens bsicos:
1
2 @Override
3 public boolean onContextItemSelected(MenuItem item) {
4
switch (item.getItemId()) {
5
case R.id.CtxLblOpc1:
6 lblMensaje.setText("Etiqueta: Opcion 1 pulsada!");
7 return true;
8 case R.id.CtxLblOpc2:
9 lblMensaje.setText("Etiqueta: Opcion 2 pulsada!");
return true;
10 default:
11 return super.onContextItemSelected(item);
12 }
13}
14
Con esto, ya tendramos listo nuestro men contextual para la etiqueta de texto de la actividad
principal, y como veis todo es prcticamente anlogo a cmo construimos los mens y submens
bsicos en el artculo anterior. En este punto ya podramos ejecutar el proyecto en el emulador y
comprobar su funcionamiento.
Ahora vamos con algunas particularidades. Los mens contextuales se utilizan a menudo con
controles de tipo lista, lo que aade algunos detalles que conviene mencionar. Para ello vamos a
aadir a nuestro ejemplo una lista con varios datos de muestra y vamos a asociarle un nuevo men
contextual. Modificaremos el layout XML de la ventana principal para aadir el control ListView
y modificaremos el mtodo onCreate() para obtener la referencia al control, insertar vaios datos
de ejemplo, y asociarle un men contextual:
Como en el caso anterior, vamos a definir en XML otro men para asociarlo a los elementos de la
lista, lo llamaremos menu_ctx_lista:
1
<?xml version="1.0" encoding="utf-8"?>
2 <menu
3 xmlns:android="http://schemas.android.com/apk/res/android">
4
5 <item android:id="@+id/CtxLstOpc1"
6 android:title="OpcLista1"></item>
7 <item android:id="@+id/CtxLstOpc2"
android:title="OpcLista2"></item>
8
9 </menu>
10
Como siguiente paso, y dado que vamos a tener varios mens contextuales en la misma actividad,
necesitaremos modificar el evento onCreateContextMenu() para que se construya un men
distinto dependiendo del control asociado. Esto lo haremos obteniendo el ID del control al que se va
a asociar el men contextual, que se recibe en forma de parmetro (View v) en el evento
onCreateContextMenu(). Utilizaremos para ello una llamada al mtodo getId() de dicho
parmetro:
@Override
1 public void onCreateContextMenu(ContextMenu menu, View v,
2 ContextMenuInfo menuInfo)
3 {
4 super.onCreateContextMenu(menu, v, menuInfo);
5
MenuInflater inflater = getMenuInflater();
6
7 if(v.getId() == R.id.LblMensaje)
8 inflater.inflate(R.menu.menu_ctx_etiqueta, menu);
9 else if(v.getId() == R.id.LstLista)
10 {
AdapterView.AdapterContextMenuInfo info =
11 (AdapterView.AdapterContextMenuInfo)menuInfo;
12
13 menu.setHeaderTitle(
14 lstLista.getAdapter().getItem(info.position).toString());
15
inflater.inflate(R.menu.menu_ctx_lista, menu);
16 }
17}
18
19
20
21
Vemos cmo en el caso del men para el control lista hemos ido adems un poco ms all, y hemos
personalizado el ttulo del men contextual [mediante setHeaderTitle()] para que muestre el
texto del elemento seleccionado en la lista. Para hacer esto nos hace falta saber la posicin en la
lista del elemento seleccionado, algo que podemos conseguir haciendo uso del ltimo parmetro
recibido en el evento onCreateContextMenu(), llamado menuInfo. Este parmetro contiene
informacin adicional del control que se ha pulsado para mostrar el men contextual, y en el caso
particular del control ListView contiene la posicin del elemento concreto de la lista que se ha
pulsado. Para obtenerlo, convertimos el parmetro menuInfo a un objeto de tipo
AdapterContextMenuInfo y accedemos a su atributo position tal como vemos en el
cdigo anterior.
La respuesta a este nuevo men se realizar desde el mismo evento que el anterior, todo dentro de
onContextItemSelected(). Por tanto, incluyendo las opciones del nuevo men contextual
para la lista el cdigo nos quedara de la siguiente forma:
1 @Override
2 public boolean onContextItemSelected(MenuItem item) {
3
4 AdapterContextMenuInfo info =
5 (AdapterContextMenuInfo) item.getMenuInfo();
6
7 switch (item.getItemId()) {
case R.id.CtxLblOpc1:
8 lblMensaje.setText("Etiqueta: Opcion 1 pulsada!");
9 return true;
10 case R.id.CtxLblOpc2:
11 lblMensaje.setText("Etiqueta: Opcion 2 pulsada!");
return true;
12 case R.id.CtxLstOpc1:
13 lblMensaje.setText("Lista[" + info.position + "]: Opcion 1
14pulsada!");
15 return true;
16 case R.id.CtxLstOpc2:
lblMensaje.setText("Lista[" + info.position + "]: Opcion 2
17pulsada!");
18 return true;
19 default:
20 return super.onContextItemSelected(item);
}
21}
22
23
Si volvemos a ejeecutar el proyecto en estte punto poddremos compprobar el asppecto de nueestro men
contexxtual al pulsaar cualquier elemento dee la lista:
Como siempre, el cdigo fuennte de este arrtculo podiis descargarllo desde estee enlace.
Meens en And
droid (III):
( O
Opcio nes avvanzad
das
Por sggoliver on 06
6/10/2011 enn Android, Programacinn
Veamos primero cmo definir un grupo de opciones de men. Como ya comentamos, Android nos
permite definir un men de dos formas distintas: mediante un fichero XML, o directamente a travs
de cdigo. Si elegimos la primera opcin, para definir un grupo de opciones nos basta con colocar
dicho grupo dentro de un elemento <group>, al que asignaremos un ID. Veamos un ejemplo.
Vamos a definir un men con 3 opciones principales, donde la ltima opcin abre un submen con
2 opciones que formen parte de un grupo. A todas las opciones le asignaremos un ID y un texto, y a
las opciones principales asignaremos adems una imagen.
1
2 <menu
3 xmlns:android="http://schemas.android.com/apk/res/android">
4
5 <item android:id="@+id/MnuOpc1" android:title="Opcion1"
6 android:icon="@drawable/tag"></item>
7 <item android:id="@+id/MnuOpc2" android:title="Opcion2"
android:icon="@drawable/filter"></item>
8 <item android:id="@+id/MnuOpc3" android:title="Opcion3"
9 android:icon="@drawable/chart">
10 <menu>
11 <group android:id="@+id/grupo1">
12
13 <item android:id="@+id/SubMnuOpc1"
android:title="Opcion 3.1" />
14 <item android:id="@+id/SubMnuOpc2"
15 android:title="Opcion 3.2" />
16
17 </group>
18 </menu>
</item>
19
20</menu>
21
22
Como vemos, las dos opciones del submen se han incluido dentro de un elemento <group>. Esto
nos permitir ejecutar algunas acciones sobre todas las opciones del grupo de forma conjunta, por
ejemplo deshabilitarlas u ocultarlas:
Si optamos por construir el men directamente mediante cdigo debemos utilizar el mtodo
setGroupCheckable() al que pasaremos como parmetros el ID del grupo y el tipo de
seleccin que deseamos (exclusiva o no). As, veamos el mtodo de construccin del men anterior
mediante cdigo:
Como vemos, al final del mtodo nos ocupamos de marcar manualmente la opcin seleccionada
actualmente, que debemos conservar en algn atributo interno (en mi caso lo he llamado
opcionSeleccionada) de nuestra actividad. Esta marcacin manual la hacemos mediante el
mtodo getItem() para obtener una opcin determinada del submen y sobre sta el mtodo
setChecked() para establecer su estado. Por qu debemos hacer esto? No guarda Android el
estado de las opciones de menu seleccionables? La respuesta es s, s lo hace, pero siempre que no
reconstruyamos el men entre una visualizacin y otra. Pero no dijimos que la creacin del men
slo se realiza una vez en la primera llamada a onCreateOptionsMenu()? Tambin es cierto,
pero despus veremos cmo tambin es posible preparar nuestra aplicacin para poder modificar de
forma dinmica un men segn determinadas condiciones, lo que s podra implicar reconstruirlo
previamente a cada visualizacin. En definitiva, si guardamos y restauramos nosotros mismos el
estado de las opciones de men seleccionables estaremos seguros de no perder su estado bajo
ninguna circunstancia.
Por supuesto, para mantener el estado de las opciones har falta actualizar el atributo
opcionSeleccionada tras cada pulsacin a una de las opciones. Esto lo haremos como
siempre en el mtodo onOptionItemSelected().
1 @Override
2 public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
3
4 //...
5 //Omito el resto de opciones por simplicidad
6
7 case SMNU_OPC1:
8 opcionSeleccionada = 1;
9 item.setChecked(true);
return true;
10 case SMNU_OPC2:
11 opcionSeleccionada = 2;
12 item.setChecked(true);
13 return true;
14
15 //...
}
16}
17
18
19
Con esto ya podramos probar cmo nuestro men funciona de la forma esperada, permitiendo
marcar slo una de las opciones del submen. Si visualizamos y marcamos varias veces distintas
opciones veremos cmo se mantiene correctamente el estado de cada una de ellas entre diferentes
llamadas.
El segundo tema que quera desarrollar en este artculo trata sobre la modificacin dinmica de un
men durante la ejecucucin de la aplicacin de forma que ste sea distinto segun determinadas
condiciones. Supongamos por ejemplo que normalmente vamos a querer mostrar nuestro men con
3 opciones, pero si tenemos marcada en pantalla una determinada opcin queremos mostrar en el
men una opcin adicional. Cmo hacemos esto si dijimos que el evento
onCreateOptionsMenu() se ejecuta una sola vez? Pues esto es posible ya que adems del
evento indicado existe otro llamado onPrepareOptionsMenu() que se ejecuta cada vez que se
va a mostrar el men de la aplicacin, con lo que resulta el lugar ideal para adaptar nuestro men a
las condiciones actuales de la aplicacin.
Para mostrar el funcionamiento de esto vamos a colocar en nuestra aplicacin de ejemplo un nuevo
checkbox (lo llamar en mi caso chkMenuExtendido). Nuestra intencin es que si este
checkbox est marcado el men muestre una cuarta opcin adicional, y en caso contrario slo
muestre las tres opciones ya vistas en los ejemplos anteriores.
1
2 @Override
public boolean onPrepareOptionsMenu(Menu menu)
3 {
4 menu.clear();
5
6 if(chkMenuExtendido.isChecked())
7 construirMenu(menu, true);
8 else
construirMenu(menu, false);
9
10 return super.onPrepareOptionsMenu(menu);
11}
12
Como vemos, en primer lugar debemos resetear el men mediante el mtodo clear() y
posteriormente llamar de nuevo a nuestro mtodo de construccin del men indicando si queremos
un men extendido o no segn el valor de la check.
Si queris descargar el cdigo fuente completo del ejemplo utilizado en este artculo podis hacerlo
desde este enlace.
En los dos prximos artculos del Curso de Programacin Android vamos a describir cmo crear un
widget de escritorio (home screen widget).
En esta primera parte construiremos un widget esttico (no ser interactivo, ni contendr datos
actualizables, ni responder a eventos) muy bsico para entender claramente la estructura interna de
un componente de este tipo, y en el siguiente artculo completaremos el ejercicio aadiendo una
ventana de configuracin inicial para el widget, aadiremos algn dato que podamos actualizar
periodicamente, y haremos que responda a pulsaciones del usuario.
Como hemos dicho, en esta primera parte vamos a crear un widget muy bsico, consistente en un
simple marco rectangular negro con un mensaje de texto predeterminado (Mi Primer Widget). La
sencillez del ejemplo nos permitir centrarnos en los pasos principales de la construccin de un
widget Android y olvidarnos de otros detalles que nada tienen que ver con el tema que nos ocupa
(grficos, datos, ). Para que os hagis una idea, ste ser el aspecto final de nuestro widget de
ejemplo:
Los pasos principales para la creacin de un widget Android son los siguientes:
En el primer paso no nos vamos a detener mucho ya que es anlogo a cualquier definicin de layout
de las que hemos visto hasta ahora en el curso. En esta ocasin, la interfaz del widget estar
compuesta nicamente por un par de frames (FrameLayout), uno negro exterior y uno blanco
interior algo ms pequeo para simular el marco, y una etiqueta de texto (TextView) que
albergar el mensaje a mostrar. Veamos cmo queda el layout xml, que para este ejemplo
llamaremos miwidget.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dip">
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#000000"
android:padding="10dip" >
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FFFFFF"
android:padding="5dip" >
<TextView android:id="@+id/txtMensaje"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textColor="#000000"
android:text="Mi Primer Widget" />
</FrameLayout>
</FrameLayout>
</LinearLayout>
Cabe destacar aqu que, debido a que el layout de los widgets de Android est basado en un tipo
especial de componentes llamados RemoteViews, no es posible utilizar en su interfaz todos los
contenedores y controles que hemos visto en artculos anteriores sino unos pocos bsicos que se
indican a continuacin:
Aunque la lista de controles soportados no deja de ser curiosa (al menos en mi humilde opinin),
debera ser suficiente para crear todo tipo de widgets.
Como segundo paso del proceso de construccin vamos a crear un nuevo fichero XML donde
definiremos un conjunto de propiedades del widget, como por ejemplo su tamao en pantalla o su
frecuencia de actualizacin. Este XML se deber crear en la carpeta \res\xml de nuestro
proyecto. En nuestro caso de ejemplo lo llamaremos miwidget_wprovider.xml y tendr la
siguiente estructura:
Existen varias propiedades ms que se pueden definir. En el siguiente artculo utilizaremos alguna
de ellas, el resto se pueden consultar en la documentacin oficial de la clase
AppWidgetProviderInfo.
Como sabemos, la pantalla inicial de Android se divide en 44 celdas donde se pueden colocar
aplicaciones, accesos directos y widgets. Teniendo en cuenta las diferentes dimensiones de estas
celdas segn la orientacin de la pantalla, existe una frmula sencilla para ajustar las dimensiones
de nuestro widget para que ocupe un nmero determinado de celdas sea cual sea la orientacin:
Atendiendo a esta frmula, si queremos que nuestro widget ocupe un tamao mnimo de 2 celdas de
ancho por 1 celda de alto, deberemos indicar unas dimensiones de 146dp x 72dp.
Vamos ahora con el tercer paso. ste consiste en implementar la funcionalidad de nuestro widget en
su clase java asociada. Esta clase deber heredar de AppWidgetProvider, que a su vez no es
ms que una clase auxiliar derivada de BroadcastReceiver, ya que los widgets de Android no
son ms que un caso particular de este tipo de componentes.
En esta clase deberemos implementar los mensajes a los que vamos a responder desde nuestro
widget, entre los que destacan:
En la mayora de los casos, tendremos que implementar como mnimo el evento onUpdate(). El
resto de mtodos dependern de la funcionalidad de nuestro widget. En nuestro caso particular no
nos har falta ninguno de ellos ya que el widget que estamos creando no contiene ningn dato
actualizable, por lo que crearemos la clase, llamada MiWidget, pero dejaremos vaco por el
momento el mtodo onUpdate(). En el siguiente artculo veremos qu cosas podemos hacer
dentro de estos mtodos.
package net.sgoliver.android;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
<application>
...
<receiver android:name=".MiWidget" android:label="Mi Primer Widget">
<intent-filter>
<action
android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/miwidget_wprovider" />
</receiver>
</application>
Atributo name: Referencia a la clase java de nuestro widget, creada en el paso anterior.
Elemento <intent-filter>, donde indicaremos los eventos a los que responder
nuestro widget, normalmente aadiremos el evento APPWIDGET_UPDATE, para detectar la
accin de actualizacin.
Elemento <meta-data>, donde haremos referencia con su atributo resource al XML
de configuracin que creamos en el segundo paso del proceso.
Con esto habramos terminado de escribir los distintos elementos necesarios para hacer funcionar
nuestro widget bsico de ejemplo. Para probarlo, podemos ejecutar el proyecto de Eclipse en el
emulador de Android, esperar a que se ejecute la aplicacin principal (que estar vaca, ya que no
hemos incluido ninguna funcionalidad para ella), ir a la pantalla principal del emulador y aadir
nuestro widget al escritorio tal cmo lo haramos en nuestro telfono (pulsacin larga sobre el
escritorio o tecla Men, seleccionar la opcin Widgets, y por ltimo seleccionar nuestro Widget).
Os dejo una demostracin en video.
Como podis ver en el video, ya hemos conseguido la funcionalidad bsica de un widget, es posible
aadir varias instancias al escritorio, desplazarlos por la pantalla y eliminarlos envindolos a la
papelera.
En el prximo artculo veremos cmo podemos mejorar este widget aadiendo una pantalla de
configuracin inicial, mostraremos algn dato que se actualice peridicamente, y aadiremos la
posibilidad de capturar eventos de pulsacin sobre el widget.
Como sabis, intento simplificar al mximo todos los ejemplos que utilizo en este curso para que
podamos centrar nuestra atencin en los aspectos realmente importantes. En esta ocasin utilizar el
mismo criterio y las nicas caractersticas (aunque suficientes para demostrar los tres conceptos
anteriores) que aadiremos a nuestro widget sern las siguientes:
1. Aadiremos una pantalla de configuracin inicial del widget, que aparecer cada vez que se
aada una nueva instancia del widget a nuestro escritorio. En esta pantalla podr
configurarse nicamente el mensaje de texto a mostrar en el widget.
2. Aadiremos un nuevo elemento de texto al widget que muestre la hora actual. Esto nos
servir para comprobar que el widget se actualiza periodicamente.
3. Aadiremos un botn al widget, que al ser pulsado forzar la actualizacin inmediata del
mismo.
Empecemos por el primer punto, la pantalla de configuracin inicial del widget. Y procederemos
igual que para el diseo de cualquier otra actividad android, definiendo su layout xml. En nuestro
caso ser muy sencilla, un cuadro de texto para introducir el mensaje a personalizar y dos botones,
uno para aceptar la configuracin y otro para cancelar (en cuyo caso el widget no se aade al
escritorio). En esta ocasin llamaremos a este layout widget_config.xml. Veamos como queda:
Una vez diseada la interfaz de nuestra actividad de configuracin tendremos que implementar su
funcionalidad en java. Llamaremos a la clase WidgetConfig, su estructura ser anloga a la de
cualquier actividad de Android, y las acciones a realizar sern las comentadas a continuacin. En
primer lugar nos har falta el identificador de la instancia concreta del widget que se configurar
con esta actividad. Este ID nos llega como parmetro del intent que ha lanzado la actividad. Como
ya vimos en un artculo anterior del curso, este intent se puede recuperar mediante el mtdo
getIntent() y sus parmetros mediante el mtodo getExtras(). Conseguida la lista de
parmetros del intent, obtendremos el valor del ID del widget accediendo a la clave
AppWidgetManager.EXTRA_APPWIDGET_ID. Veamos el cdigo hasta este momento:
En el cdigo tambin podemos ver como aprovechamos este momento para establecer el resultado
por defecto a devolver por la actividad de configuracin mediante el mtodo setResult(). Esto
es importante porque las actividades de configuracin de widgets deben devolver siempre un
resultado (RESULT_OK en caso de aceptarse la configuracin, o RESULT_CANCELED en caso de
salir de la configuracin sin aceptar los cambios). Estableciendo aqu ya un resultado
RESULT_CANCELED por defecto nos aseguramos de que si el usuario sale de la configuracin
pulsando el botn Atrs del telfono no aadiremos el widget al escritorio, mismo resultado que si
pulsramos el botn Cancelar de nuestra actividad.
Como siguiente paso recuperamos las referencias a cada uno de los controles de la actividad de
configuracin:
Por ltimo, implementaremos las acciones de los botones Aceptar y Cancelar. En principio, el
botn Cancelar no tendra por qu hacer nada, tan slo finalizar la actividad mediante una llamada
al mtodo finish() ya que el resultado CANCELED ya se ha establecido por defecto
anteriormente:
1
//Implementacin del botn "Cancelar"
2btnCancelar.setOnClickListener(new OnClickListener() {
3 @Override
4 public void onClick(View arg0) {
5 //Devolvemos como resultado: CANCELAR (RESULT_CANCELED)
6 finish();
}
7});
8
Para el primer punto nos ayudaremos de la API de Preferencias que describimos en el artculo
anterior. En nuestro caso, guardaremos una sla preferencia cuya clave seguir el patrn
msg_IdWidget, esto nos permitir distinguir el mensaje configurado para cada instancia del
widget que aadamos a nuestro escritorio de Android.
El segundo paso indicado es necesario debido a que si definimos una actividad de configuracin
para un widget, ser sta la que tenga la responsabilidad de realizar la primera actualizacin del
mismo en caso de ser necesario. Es decir, tras salir de la actividad de configuracin no se lanzar
automticamente el evento onUpdate() del widget (s se lanzar posteriormente y de forma
peridica segn la configuracin del parmetro updatePeriodMillis del provider que
veremos ms adelante), sino que tendr que ser la propia actividad quien fuerce la primera
actualizacin. Para ello, simplemente obtendremos una referencia al widget manager de nuestro
contexto mediente el mtodo AppWidgetManager.getInstance() y con esta referencia
llamaremos al mtodo esttico de actualizacin del widget MiWidget.actualizarWidget(),
que actualizar los datos de todos los controles del widget (lo veremos un poco ms adelante).
Con estas indicaciones, veamos cmo quedara el cdigo del botn Aceptar:
1
2 //Implementacin del botn "Aceptar"
3 btnAceptar.setOnClickListener(new OnClickListener() {
@Override
4 public void onClick(View arg0) {
5 //Guardamos el mensaje personalizado en las preferencias
6 SharedPreferences prefs =
7 getSharedPreferences("WidgetPrefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
8
editor.putString("msg_" + widgetId,
9 txtMensaje.getText().toString());
10 editor.commit();
11
12 //Actualizamos el widget tras la configuracin
13 AppWidgetManager appWidgetManager =
AppWidgetManager.getInstance(WidgetConfig.this);
14 MiWidget.actualizarWidget(WidgetConfig.this, appWidgetManager,
15widgetId);
16
17 //Devolvemos como resultado: ACEPTAR (RESULT_OK)
18 Intent resultado = new Intent();
19 resultado.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
setResult(RESULT_OK, resultado);
20 finish();
21 }
22});
23
1
2<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
3 android:initialLayout="@layout/miwidget"
4 android:minWidth="146dip"
5 android:minHeight="146dip"
6 android:label="Mi Primer Widget"
android:updatePeriodMillis="1800000"
7 android:configure="net.sgoliver.android.WidgetConfig"
8/>
9
Con esto, ya tenemos todo listo para que al aadir nuestro widget al escritorio se muestre
automticamente la pantalla de configuracin que hemos construido. Podemos ejecutar el proyecto
en este punto y comprobar que todo funciona correctamente.
Como siguiente paso vamos a modificar el layout del widget que ya construimos en el artculo
anterior para aadir una nueva etiqueta de texto donde mostraremos la hora actual, y un botn que
nos servir para forzar la actualizacin de los datos del widget:
Como ya dijimos, los componentes de un widget se basan en un tipo especial de vistas que
llamamos Remote Views. Pues bien, para acceder a la lista de estos componentes que constituyen la
interfaz del widget construiremos un nuevo objeto RemoteViews a partir del ID del layout de
cada widget. Obtenida la lista de componentes, tendremos disponibles una serie de mtodos set (uno
para cada tipo de datos bsicos) para establecer las propiedades de cada control del widget. Estos
mtodos reciben como parmetros el ID del control, el nombre del mtodo que queremos ejecutar
sobre el control, y el valor a establecer. Adems de estos mtodos, contamos adicionalmente con
una serie de mtodos ms especficos para establecer directamente el texto y otras propiedades
sencillas de los controles TextView, ImageView, ProgressBar y Chronometer, como por
ejemplo setTextViewText(idControl, valor) para establecer el textode un control
TextView. Pueden consultarse todos los mtodos disponibles en la documentacin oficial de la
clase RemoteViews. De esta forma, si por ejemplo queremos establecer el texto del control cuyo
id es LblMensaje haramos lo siguiente:
El proceso de actualizacin habr que realizarlo por supuesto para todas las instancias del widget
que se hayan aadido al escritorio. Recordemos aqu que el evento onUpdate() recibe como
parmetro la lista de widgets que hay que actualizar.
Dicho esto, creo que ya podemos mostrar cmo quedara el cdigo de actualizacin de nuestro
widget:
1
2 @Override
3 public void onUpdate(Context context,
4 AppWidgetManager appWidgetManager,
5 int[] appWidgetIds) {
6
//Iteramos la lista de widgets en ejecucin
7 for (int i = 0; i < appWidgetIds.length; i++)
8 {
9 //ID del widget actual
10 int widgetId = appWidgetIds[i];
11
12 //Actualizamos el widget actual
actualizarWidget(context, appWidgetManager, widgetId);
13 }
14}
15
16public static void actualizarWidget(Context context,
17 AppWidgetManager appWidgetManager, int widgetId)
{
18 //Recuperamos el mensaje personalizado para el widget actual
19 SharedPreferences prefs =
20 context.getSharedPreferences("WidgetPrefs", Context.MODE_PRIVATE);
21 String mensaje = prefs.getString("msg_" + widgetId, "Hora actual:");
22
23 //Obtenemos la lista de controles del widget actual
RemoteViews controles =
24 new RemoteViews(context.getPackageName(), R.layout.miwidget);
25
26 //Actualizamos el mensaje en el control del widget
27 controles.setTextViewText(R.id.LblMsg, mensaje);
28
29 //Obtenemos la hora actual
Calendar calendario = new GregorianCalendar();
30 String hora = calendario.getTime().toLocaleString();
31
32 //Actualizamos la hora en el control del widget
33 controles.setTextViewText(R.id.LblHora, hora);
34
35 //Notificamos al manager de la actualizacin del widget actual
36 appWidgetManager.updateAppWidget(widgetId, controles);
}
37
38
39
40
41
Como vemos, todo el trabajo de actualzacin para un widget lo hemos extraido a un mtodo esttico
independiente, de forma que tambin podamos llamarlo desde otras partes de la aplicacin (como
hacemos por ejemplo desde la actividad de configuracin para forzar la primera actualizacin del
widget).
Adems quiero destacar la ltima linea del cdigo, donde llamamos al mtodo
updateAppWidget() del widget manager. Esto es importante y necesario, ya que de no hacerlo
la actualizacin de los controles no se reflejar correctamente en la interfaz del widget.
Tras esto, ya slo nos queda implementar la funcionalidad del nuevo botn que hemos incluido en
el widget para poder forzar la actualizacin del mismo. A los controles utilizados en los widgets de
Android, que ya sabemos que son del tipo RemoteView, no podemos asociar eventos de la forma
tradicional que hemos visto en mltiples ocasiones durante el curso. Sin embargo, en su lugar,
tenemos la posibilidad de asociar a un evento (por ejemplo, el click sobre un botn) un determinado
mensaje (Pending Intent) de tipo broadcast que ser lanzado cada vez que se produzca dicho
evento. Adems, podremos configurar el widget (que como ya indicamos no es ms que un
componente de tipo broadcast receiver) para que capture esos mensajes, e implementar en el evento
onReceive() del widget las acciones necesarias a ejecutar tras capturar el mensaje. Con estas
tres acciones simularemos la captura de eventos sobre controles de un widget.
Vamos por partes. En primer lugar hagamos que se lance un intent broadcast cada vez que se pulse
el botn del widget. Para ello, en el mtodo actualizarWidget() construiremos un nuevo
Intent asocindole una accin personalizada, que en nuestro caso llamaremos por ejemplo
net.sgoliver.ACTUALIZAR_WIDGET. Como parmetro del nuevo Intent insertaremos
mediante putExtra() el ID del widget actual de forma que ms tarde podamos saber el widget
concreto que ha lanzado el mensaje. Por ltimo crearemos el PendingIntent mediante el
mtodo getBroadcast() y lo asociaremos al evento onClick del control llamando a
setOnClickPendingIntent() pasndole el ID del control, en nuestro caso el botn de
Actualizar. Veamos cmo queda todo esto:
1
//Asociamos los 'eventos' al widget
2 Intent intent = new Intent("net.sgoliver.ACTUALIZAR_WIDGET");
3 intent.putExtra(
4 AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
5
6 PendingIntent pendingIntent =
7 PendingIntent.getBroadcast(context, widgetId,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
8
9 controles.setOnClickPendingIntent(R.id.BtnActualizar, pendingIntent);
10
Ahora vamos a declarar en el Android Manifest este mensaje personalizado, de forma que el widget
sea capaz de capturarlo. Para ello, aadiremos simplemente un nuevo elemento <intent-filter> con
nuestro nombre de accin personalizado:
1<intent-filter>
2 <action android:name="net.sgoliver.ACTUALIZAR_WIDGET"/>
3</intent-filter>
Por ltimo, vamos a implementar el evento onReceive() del widget para actuar en caso de
recibir nuestro mensaje de actualizacin personalizado. Dentro de este evento comprobaremos si la
accin del menasje recibido es la nuestra, y en ese caso recuperaremos el ID del widget que lo ha
lanzado, obtendremos una referencia al widget manager, y por ltimo llamaremos nuestro mtodo
esttico de actualizacin pasndole estos datos.
1
2 @Override
3 public void onReceive(Context context, Intent intent) {
4 if (intent.getAction().equals("net.sgoliver.ACTUALIZAR_WIDGET")) {
//Obtenemos el ID del widget a actualizar
5
int widgetId = intent.getIntExtra(
6 AppWidgetManager.EXTRA_APPWIDGET_ID,
7 AppWidgetManager.INVALID_APPWIDGET_ID);
8
9 //Obtenemos el widget manager de nuestro contexto
10 AppWidgetManager widgetManager =
AppWidgetManager.getInstance(context);
11
12 //Actualizamos el widget
13 if (widgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
14 actualizarWidget(context, widgetManager, widgetId);
15 }
16}
17
Con esto, por fin, hemos ya finalizado la construccin de nuestro widget android y podemos
ejecutar el proyecto de Eclipse para comprobar que todo funciona correctamente, tanto para una
sola instancia como para varias instancias simultaneas.
Como siempre, tenis disponible el cdigo fuente del artculo pulsando este enlace.
Preferencias en Android I: Shared Preferences
Por sgoliver on 14/03/2011 en Android, Programacin
En el artculo anterior del Curso de Programacin en Android vimos como construir un widget
bsico y prometimos dedicar un segundo artculo a comentar otras funcionalidades ms avanzadas
de este tipo de componentes. Sin embargo, antes de esto he decidido hacer un pequeo alto en el
camino para hablar de un tema que nos ser de ayuda ms adelante, y no slo para la construccin
de widgets, sino para cualquier tipo de aplicacin Android. Este tema es la administracin de
preferencias.
Las preferencias no son ms que datos que una aplicacin debe guardar para personalizar la
experiencia del usuario, por ejemplo informacin personal, opciones de presentacin, etc. En
artculos anteriores vimos ya uno de los mtodos disponibles en la plataforma Android para
almacenar datos, como son las bases de datos SQLite. Las preferencias de una aplicacin se podran
almacenar por su puesto utilizando este mtodo, y no tendra nada de malo, pero Android
proporciona otro mtodo alternativo diseado especficamente para administrar este tipo de datos:
las preferencias compartidas o shared preferences. Cada preferencia se almacenar en forma de
clave-valor, es decir, cada una de ellas estar compuesta por un identificador nico (p.e. email) y
un valor asociado a dicho identificador (p.e. prueba@email.com). Adems, y a diferencia de
SQLite, los datos no se guardan en un fichero de binario de base de datos, sino en ficheros XML
como veremos al final de este artculo.
La API para el manejo de estas preferencias es muy sencilla. Toda la gestin se centraliza en la
clase SharedPrefences, que representar a una coleccin de preferencias. Una aplicacin
Android puede gestionar varias colecciones de preferencias, que se diferenciarn mediante un
identificador nico. Para obtener una referencia a una coleccin determinada utilizaremos el mtodo
getSharedPrefences() al que pasaremos el identificador de la coleccin y un modo de
acceso. El modo de acceso indicar qu aplicaciones tendrn acceso a la coleccin de preferencias y
qu operaciones tendrn permitido realizar sobre ellas. As, tendremos tres posibilidades
principales:
Teniedo todo esto en cuenta, para obtener una referencia a una coleccin de preferencias llamada
por ejemplo MisPreferencias y como modo de acceso exclusivo para nuestra aplicacin haramos
lo siguiente:
1SharedPreferences prefs =
2 getSharedPreferences("MisPreferencias",Context.MODE_PRIVATE);
Una vez hemos obtenido una referencia a nuestra coleccin de preferencias, ya podemos obtener,
insertar o modificar preferencias utilizando los mtodos get o put correspondientes al tipo de dato
de cada preferencia. As, por ejemplo, para obtener el valor de una preferencia llamada email de
tipo String escribiramos lo siguiente:
1SharedPreferences prefs =
2 getSharedPreferences("MisPreferencias",Context.MODE_PRIVATE);
3
4String correo = prefs.getString("email", "por_defecto@email.com");
Para actualizar o insertar nuevas preferencias el proceso ser igual de sencillo, con la nica
diferencia de que la actualizacin o insercin no la haremos directamente sobre el objeto
SharedPreferences, sino sobre su objeto de edicin SharedPreferences.Editor. A
este ltimo objeto accedemos mediante el mtodo edit() de la clase SharedPreferences.
Una vez obtenida la referencia al editor, utilizaremos los mtodos put correspondientes al tipo de
datos de cada preferencia para actualizar/insertar su valor, por ejemplo putString(clave,
valor), para actualizar una preferencia de tipo String. De forma anloga a los mtodos get que
ya hemos visto, tendremos disponibles mtodos put para todos los tipos de datos bsicos:
putInt(), putFloat(), putBoolean(), etc. Finalmente, una vez actualizados/insertados
todos los datos necesarios llamaremos al mtodo commit() para confirmar los cambios. Veamos
un ejemplo sencillo:
1SharedPreferences prefs =
2 getSharedPreferences("MisPreferencias",Context.MODE_PRIVATE);
3
4SharedPreferences.Editor editor = prefs.edit();
5editor.putString("email", "modificado@email.com");
6editor.putString("nombre", "Prueba");
editor.commit();
7
Donde se almacenan estas preferencias compartidas? Como dijimos al comienzo del artculo, las
preferencias no se almacenan en ficheros binarios como las bases de datos SQLite, sino en ficheros
XML. Estos ficheros XML se almacenan en una ruta con el siguiente patrn:
/data/data/paquetejava/shared_prefs/nombre_coleccion.xml
As, por ejemplo, en nuestro caso encontraramos nuestro fichero de preferencias en la ruta:
/data/data/net.sgoliver.android/shared_prefs/MisPreferencias.xml
Si descargamos este fichero desde el DDMS y lo abrimos con cualquier editor de texto veremos un
contenido como el siguiente:
En este XML podemos observar cmo se han almacenado las dos preferencias de ejemplo que
insertamos anteriormente, con sus claves y valores correspondientes.
Y nada ms, as de fcil y prctico. Con esto hemos aprendido una forma sencilla de almacenar
determinadas opciones de nuestra aplicacin sin tener que recurrir para ello a definir bases de datos
SQLite, que aunque tampoco aaden mucha dificultad s que requieren algo ms de trabajo por
nuestra parte.
En una segunda parte de este tema dedicado a las preferencias veremos cmo Android nos ofrece
otra forma de gestionar estos datos, que se integra adems fcilmente con la interfaz grfica
necesaria para solicitar los datos al usuario.
Ya hemos visto durante el curso algn artculo dedicado a las preferencias compartidas (shared
preferences), un mecanismo que nos permite gestionar fcilmente las opciones de una aplicacin
permitindonos guardarlas en XML de una forma transparente para el programador. En aquel
momento slo vimos cmo hacer uso de ellas mediante cdigo, es decir, creando nosotros mismos
los objetos necesarios (SharedPreferences) y aadiendo, modificando y/o recuperando a
mano los valores de las opciones a travs de los mtodos correspondientes (getString(),
putString(), ). Sin embargo, ya avisamos de que Android ofrece una forma alternativa de
definir mediante XML un conjunto de opciones para una aplicacin y crear por nosotros las
pantallas necesarias para permitir al usuario modificarlas a su antojo. A esto dedicaremos este
segundo artculo sobre preferencias.
Si nos fijamos en cualquier pantalla de preferencias estandar de Android veremos que todas
comparten una interfaz comun, similar por ejemplo a la que se muestra en la imagen siguiente
(correpondiente a la pantalla de opciones de la galera de imgenes de Android).
Si obsservamos la imagen antterior vemoss cmo las diferentes
d oppciones se organizan
o deentro de la
pantallla de opciones en variaas categoras (Generall Settings y Slideshoww Settings). Dentro de
cada categora
c puueden apareecer varias opciones dee diversos tipos,
t como por ejempplo de tipo
checkbbox (Confifirm deletionns) o de tippo lista de seleccin (Display sizze). He ressaltado las
palabrras pantallaa de opcionnes, categooras, y tipos de opccin porquue sern estoos los tres
elemenntos principaales con los que vamos a definir el conjunto
c de opciones o preferencias
p de nuestra
aplicaccin. Empeccemos.
Como hemos indiccado, nuestrra pantalla dee opciones laa vamos a deefinir mediaante un XML L, de forma
similaar a como deefinimos cuaalquier layouut, aunque enn este caso deberemos colocarlo
c enn la carpeta
/res/ /xml. El contenedor principal de d nuestra pantalla dee preferencias ser ell elemento
<Pref ferenceSc creen>. Este elementoo representarr a la pantaalla de opcioones en s, dentro
d de la
cual inncluiremos el
e resto de elementos.
e D
Dentro de sste podremoos incluir nuuestra lista de
d opciones
organiizadas por caategoras, quue se represeentar mediaante el elemeento <Pref ferenceCa ategory>
al quee daremos unu texto desscriptivo utiilizando su atributo an ndroid:ti itle. Dentrro de cada
categoora podremoos aadir cuualquier nm mero de opciiones, las cuuales puedenn ser de distiintos tipos,
entre los
l que destaacan:
Check
kBoxPrefereence
Representa un tipoo de opcin que slo pueede tomar doos valores diistintos: actiivada o desacctivada. Es
el equuivalente a un
u control dee tipo checkkbox. En estte caso tan slo
s tendremmos que espeecificar los
atributos: nombre interno de la opcin (android:key), texto a mostrar (android:title) y
descripcin de la opcin (android:summary). Veamos un ejemplo:
1<CheckBoxPreference
2 android:key="opcion1"
3 android:title="Preferencia 1"
android:summary="Descripcin de la preferencia 1" />
4
EditTextPreference
Representa un tipo de opcin que puede contener como valor una cadena de texto. Al pulsar sobre
una opcin de este tipo se mostrar un cuadro de dilogo sencillo que solicitar al usuario el texto a
almacenar. Para este tipo, adems de los tres atributos comunes a todas las opciones (key, title
y summary) tambin tendremos que indicar el texto a mostrar en el cuadro de dilogo, mediante el
atributo android:dialogTitle. Un ejemplo sera el siguiente:
1<EditTextPreference
2 android:key="opcion2"
3 android:title="Preferencia 2"
4 android:summary="Descripcin de la preferencia 2"
android:dialogTitle="Introduce valor" />
5
ListPreference
Representa un tipo de opcin que puede tomar como valor un elemento, y slo uno, seleccionado
por el usuario entre una lista de valores predefinida. Al pulsar sobre una opcin de este tipo se
mostrar la lista de valores posibles y el usuario podr seleccionar uno de ellos. Y en este caso
seguimos aadiendo atributos. Adems de los cuatro ya comentados (key, title, summary y
dialogTitle) tendremos que aadir dos ms, uno de ellos indicando la lista de valores a
visualizar en la lista y el otro indicando los valores internos que utilizaremos para cada uno de los
valores de la lista anterior (Ejemplo: al usuario podemos mostrar una lista con los valores Espaol
y Francs, pero internamente almacenarlos como ESP y FRA).
Estas listas de valores las definiremos tambin como ficheros XML dentro de la carpeta
/res/xml. Definiremos para ello los recursos de tipos <string-array> necesarios, en este
caso dos, uno para la lista de valores visibles y otro para la lista de valores internos, cada uno de
ellos con su ID nico correspondiente. Veamos cmo quedaran dos listas de ejemplo, en un fichero
llamado codigospaises.xml:
1
<ListPreference
2 android:key="opcion3"
3 android:title="Preferencia 3"
4 android:summary="Descripcin de la preferencia 3"
5 android:dialogTitle="Indicar Pais"
android:entries="@array/pais"
6 android:entryValues="@array/codigopais" />
7
MultiSelectListPreference
[A partir de Android 3.0.x / Honeycomb] Las opciones de este tipo son muy similares a las
ListPreference, con la diferencia de que el usuario puede seleccionar varias de las opciones de la
lista de posibles valores. Los atributos a asignar son por tanto los mismos que para el tipo anterior.
1
<MultiSelectListPreference
2 android:key="opcion4"
3 android:title="Preferencia 4"
4 android:summary="Descripcin de la preferencia 4"
5 android:dialogTitle="Indicar Pais"
android:entries="@array/pais"
6 android:entryValues="@array/codigopais" />
7
Como ejemplo completo, veamos cmo quedara definida una pantalla de opciones con las 3
primeras opciones comentadas (ya que probar con Android 2.2), divididas en 2 categoras llamadas
por simplicidad Categora 1 y Categora 2. Llamaremos al fichero opciones.xml.
1 <PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
2 <PreferenceCategory android:title="Categora 1">
3 <CheckBoxPreference
4 android:key="opcion1"
5 android:title="Preferencia 1"
android:summary="Descripcin de la preferencia 1" />
6 <EditTextPreference
7 android:key="opcion2"
8 android:title="Preferencia 2"
9 android:summary="Descripcin de la preferencia 2"
10 android:dialogTitle="Introduce valor" />
</PreferenceCategory>
11 <PreferenceCategory android:title="Categora 2">
12 <ListPreference
13 android:key="opcion3"
14 android:title="Preferencia 3"
android:summary="Descripcin de la preferencia 3"
15 android:dialogTitle="Indicar Pais"
16 android:entries="@array/pais"
17 android:entryValues="@array/codigopais" />
18 </PreferenceCategory>
</PreferenceScreen>
19
20
21
22
23
Ya tenemos definida la estructura de nuestra pantalla de opciones, pero an nos queda un paso ms
para poder hacer uso de ella desde nuestra aplicacin. Adems de la definicin XML de la lista de
opciones, debemos implementar una nueva actividad, que ser a la que hagamos referencia cuando
queramos mostrar nuestra pantalla de opciones y la que se encargar internamente de gestionar
todas las opciones, guardarlas, modificarlas, etc, a partir de nuestra definicin XML.
Android nos facilita las cosas ofrecindonos una clase de la que podemos derivar facilmente la
nuestra propia y que hace todo el trabajo por nosotros. Esta clase se llama
PreferenceActivity. Tan slo deberemos crear una nueva actividad (yo la he llamado
PantallaOpciones) que extienda a esta clase e implementar su evento onCreate() para
aadir una llamada al mtodo addPreferencesFromResource(), mediante el que
indicaremos el fichero XML en el que hemos definido la pantalla de opciones. Lo vemos mejor
directamente en el cdigo:
1
public class PantallaOpciones extends PreferenceActivity {
2 @Override
3 public void onCreate(Bundle savedInstanceState) {
4 super.onCreate(savedInstanceState);
5
6 addPreferencesFromResource(R.xml.opciones);
}
7}
8
Por supuesto, tendremos que aadir esta actividad al fichero AndroidManifest.xml, al igual que
cualquier otra actividad que utilicemos en la aplicacin.
1<activity android:name=".PantallaOpciones"
2 android:label="@string/app_name">
3 </activity>
Ya slo nos queda aadir a nuestra applicacin alggn mecaniismo para mostrar m la pantalla
p de
prefereencias. Estaa opcin suele estar en un
u men, peero por simpplificar el ejemplo vamoos a aadir
simpleemente un bo otn (btnPrreferenci ias) que llaame a la venntana de prefferencias.
1
btnP
Preferencia
as = (Button)findVie ewById(R.id.BtnPrefeerencias);
2
3btnPPreferencia
as.setOnClickListene er(new OnCl
lickListeneer() {
4 @Override
5 public void onClick(View v) {
6 startA
Activity(new Intent(AndroidPre efScreensAcctivity.th
his,
PanttallaOpcionnes.class));
7
}
8});
9
Y estoo es todo, yay slo nos queda ejeccutar la apliicacin en el e emulador y pulsar ell botn de
prefereencias para mostrar
m nuesstra nueva pantalla de oppciones. Debbe quedar coomo muestraa la imagen
siguiennte:
v establecidos los valores de lass preferenciaas podemoss salir de laa ventana dee opciones
Una vez
simpleemente pulssando el botnb Atrss del dispoositivo o del d emuladoor. Nuestra actividad
Panta allaOpcio ones se haabr ocupadoo por nosotroos de guardaar correctam mente los vallores de las
opcionnes haciend do uso de la l API de preferencias
p s compartidaas (Shared Preferencess). Y para
comprrobarlo vamo os a aadir otro
o botn (b btnObtene erOpcione es) a la apliicacin de ejjemplo que
recupeere el valor actual
a de las 3 preferenciias y los escriba al log de
d la aplicaciin.
1
2 btnObtenerPreferencias.setOnClickListener(new OnClickListener() {
@Override
3
public void onClick(View v) {
4 SharedPreferences pref =
5 PreferenceManager.getDefaultSharedPreferences(
6 AndroidPrefScreensActivity.this);
7
8 Log.i("", "Opcin 1: " + pref.getBoolean("opcion1", false));
Log.i("", "Opcin 2: " + pref.getString("opcion2", ""));
9 Log.i("", "Opcin 3: " + pref.getString("opcion3", ""));
10 }
11});
12
Y hasta aqu hemos llegado con el tema de las preferencias, un tema muy interesante de controlar ya
que casi ninguna aplicacin se libra de hacer uso de ellas.
Podis descargar el cdigo fuente completo del ejemplo pulsando este enlace.
En los siguientes artculos de este tutorial de programacin Android, nos vamos a detener en
describir las distintas opciones de acceso a datos que proporciona la plataforma y en cmo podemos
realizar las tareas ms habituales dentro de este apartado.
En estos prximos artculos nos centraremos en la primera opcin, SQLite, que abarcar todas las
tareas relacionadas con el almacenamiento de los datos propios de nuestra aplicacin. El segundo de
los mecanismos, los Content Providers, que trataremos ms adelante, nos facilitarn la tarea de
hacer visibles esos datos a otras aplicaciones y, de forma recproca, de permitir la consulta de datos
publicados por terceros desde nuestra aplicacin.
SQLite es un motor de bases de datos muy popular en la actualidad por ofrecer caractersticas tan
interesantes como su pequeo tamao, no necesitar servidor, precisar poca configuracin, ser
transaccional y por supuesto ser de cdigo libre.
Android incorpora de serie todas las herramientas necesarias para la creacin y gestin de bases de
datos SQLite, y entre ellas una completa API para llevar a cabo de manera sencilla todas las tareas
necesarias. Sin embargo, en este primer artculo sobre bases de datos en Android no vamos a entrar
en mucho detalle con esta API. Por el momento nos limitaremos a ver el cdigo necesario para crear
una base de datos, insertaremos algn dato de prueba, y veremos cmo podemos comprobar que
todo funciona correctamente.
En Android, la forma tpica para crear, actualizar, y conectar con una base de datos SQLite ser a
travs de una clase auxiliar llamada SQLiteOpenHelper, o para ser ms exactos, de una clase
propia que derive de ella y que debemos personalizar para adaptarnos a las necesidades concretas de
nuestra aplicacin.
Como ejemplo, nosotros vamos a crear una base de datos muy sencilla llamada BDUsuarios, con
una sla tabla llamada Usuarios que contendr slo dos campos: nombre e email. Para ellos,
vamos a crear una clase derivada de SQLiteOpenHelper que llamaremos
UsuariosSQLiteHelper, donde sobrescribiremos los mtodos onCreate() y
onUpgrade() para adaptarlos a la estructura de datos indicada:
1 package net.sgoliver.android;
2
import android.content.Context;
3 import android.database.sqlite.SQLiteDatabase;
4 import android.database.sqlite.SQLiteDatabase.CursorFactory;
5 import android.database.sqlite.SQLiteOpenHelper;
6
7 public class UsuariosSQLiteHelper extends SQLiteOpenHelper {
8
//Sentencia SQL para crear la tabla de Usuarios
9 String sqlCreate = "CREATE TABLE Usuarios (codigo INTEGER, nombre TEXT)";
10
11 public UsuariosSQLiteHelper(Context contexto, String nombre,
12 CursorFactory factory, int version) {
13 super(contexto, nombre, factory, version);
14 }
15
@Override
16 public void onCreate(SQLiteDatabase db) {
17 //Se ejecuta la sentencia SQL de creacin de la tabla
18 db.execSQL(sqlCreate);
19 }
20
@Override
21 public void onUpgrade(SQLiteDatabase db, int versionAnterior, int
22versionNueva) {
23 //NOTA: Por simplicidad del ejemplo aqu utilizamos directamente la
24 opcin de
// eliminar la tabla anterior y crearla de nuevo vaca con el
25nuevo formato.
26 // Sin embargo lo normal ser que haya que migrar datos de la
27tabla antigua
28 // a la nueva, por lo que este mtodo debera ser ms elaborado.
29
30 //Se elimina la versin anterior de la tabla
db.execSQL("DROP TABLE IF EXISTS Usuarios");
31
32 //Se crea la nueva versin de la tabla
33 db.execSQL(sqlCreate);
34 }
35}
36
37
Lo primero que hacemos es definir una variable llamado sqlCreate donde almacenamos la
sentencia SQL para crear una tabla llamada Usuarios con los campos alfanumricos nombre e
email. NOTA: No es objetivo de este tutorial describir la sintaxis del lenguaje SQL ni las
particularidades del motor de base de datos SQLite, por lo que no entrar a describir las sentencias
SQL utilizadas. Para ms informacin sobre SQLite puedes consultar la documentacin oficial o
empezar por leer una pequea introduccin que hice en este mismo blog cuando trat el tema de
utilizar SQLite desde aplicaciones .NET
Por su parte, el mtodo onUpgrade() se lanzar automticamente cuando sea necesaria una
actualizacin de la estructura de la base de datos o una conversin de los datos. Un ejemplo
prctico: imaginemos que publicamos una aplicacin que utiliza una tabla con los campos
usuario e email (llammoslo versin 1 de la base de datos). Ms adelante, ampliamos la
funcionalidad de nuestra aplicacin y necesitamos que la tabla tambin incluya un campo adicional
por ejemplo con la edad del usuario (versin 2 de nuestra base de datos). Pues bien, para que todo
funcione correctamente, la primera vez que ejecutemos la versin ampliada de la aplicacin
necesitaremos modificar la estructura de la tabla Usuarios para aadir el nuevo campo edad.
Pues este tipo de cosas son las que se encargar de hacer automticamente el mtodo
onUpgrade() cuando intentemos abrir una versin concreta de la base de datos que an no
exista. Para ello, como parmetros recibe la versin actual de la base de datos en el sistema, y la
nueva versin a la que se quiere convertir. En funcin de esta pareja de datos necesitaremos realizar
unas acciones u otras. En nuestro caso de ejemplo optamos por la opcin ms sencilla: borrar la
tabla actual y volver a crearla con la nueva estructura, pero como se indica en los comentarios del
cdigo, lo habitual ser que necesitemos algo ms de lgica para convertir la base de datos de una
versin a otra y por supuesto para conservar los datos registrados hasta el momento.
Una vez definida nuestra clase helper, la apertura de la base de datos desde nuestra aplicacin
resulta ser algo de lo ms sencillo. Lo primero ser crear un objeto de la clase
UsuariosSQLiteHelper al que pasaremos el contexto de la aplicacin (en el ejemplo una
referencia a la actividad principal), el nombre de la base de datos, un objeto CursorFactory
que tpicamente no ser necesario (en ese caso pasaremos el valor null), y por ltimo la versin de
la base de datos que necesitamos. La simple creacin de este objeto puede tener varios efectos:
Ahora que ya hemos conseguido una referencia a la base de datos (objeto de tipo
SQLiteDatabase) ya podemos realizar todas las acciones que queramos sobre ella. Para nuestro
ejemplo nos limitaremos a insertar 5 registros de prueba, utilizando para ello el mtodo ya
comentado execSQL() con las sentencias INSERT correspondientes. Por ltimo cerramos la
conexin con la base de datos llamando al mtodo close().
1 package net.sgoliver.android;
2
3 import android.app.Activity;
import android.database.sqlite.SQLiteDatabase;
4 import android.os.Bundle;
5
6 public class AndroidBaseDatos extends Activity
7 {
8 @Override
public void onCreate(Bundle savedInstanceState)
9
{
10 super.onCreate(savedInstanceState);
11 setContentView(R.layout.main);
12
13 //Abrimos la base de datos 'DBUsuarios' en modo escritura
14 UsuariosSQLiteHelper usdbh =
15 new UsuariosSQLiteHelper(this, "DBUsuarios", null, 1);
16
SQLiteDatabase db = usdbh.getWritableDatabase();
17
18 //Si hemos abierto correctamente la base de datos
19 if(db != null)
20 {
21 //Insertamos 5 usuarios de ejemplo
for(int i=1; i<=5; i++)
22 {
23 //Generamos los datos
24 int codigo = i;
25 String nombre = "Usuario" + i;
26
27 //Insertamos los datos en la tabla Usuarios
db.execSQL("INSERT INTO Usuarios (codigo, nombre) " +
28 "VALUES (" + codigo + ", '" + nombre +"')");
29 }
30
31 //Cerramos la base de datos
32 db.close();
}
33 }
34}
35
36
37
38
39
40
Vale, y ahora qu? dnde est la base de datos que acabamos de crear? cmo podemos
comprobar que todo ha ido bien y que los registros se han insertado correctamente? Vayamos por
partes.
En primer lugar veamos dnde se ha creado nuestra base de datos. Todas las bases de datos SQLite
creadas por aplicaciones Android se almacenan en la memoria del telfono en un fichero con el
mismo nombre de la base de datos situado en una ruta que sigue el siguiente patrn:
/data/data/paquete.java.de.la.aplicacion/databases/nombre_base_dat
os
En el caso de nuestro ejemplo, la base de datos se almacenara por tanto en la ruta siguiente:
/data/data/net.sgoliver.android/databases/DBUsuarios
Para comprobar esto podemos hacer lo siguiente. Una vez ejecutada por primera vez desde Eclipse
la aplicacin de ejemplo sobre el emulador de Android (y por supuesto antes de cerrarlo) podemos
ir a la perspectiva DDMS (Dalvik Debug Monitor Server) de Eclipse y en la solapa File
Explorer podremos acceder al sistema de archivos del emulador, donde podremos buscar la ruta
indicada de la base de datos. Podemos ver esto en la siguiente imagen (click para ampliar):
Con esto ya comprobamos al menos que el fichero de nuestra base de datos se ha creado en la ruta
correcta. Ya slo nos queda comprobar que tanto las tablas creadas como los datos insertados
tambin se han incluido correctamente en la base de datos. Para ello podemos recurrir a dos
posibles mtodos:
El primero de los mtodos es sencillo. El fichero de la base de datos podemos transferirlo a nuestro
PC utilizando el botn de descarga situado en la esquina superior derecha del explorador de
archivos (remarcado en rojo en la imagen anterior). Junto a este botn aparecen otros dos para hacer
la operacin contraria (copiar un fichero local al sistema de archivos del emulador) y para eliminar
ficheros del emulador. Una vez descargado el fichero a nuestro sistema local, podemos utilizar
cualquier administrador de SQLite para abrir y consultar la base de datos, por ejemplo SQLite
Administrator (freeware).
El segundo mtodo utiliza una estrategia diferente. En vez de descargar la base de datos a nuestro
sistema local, somos nosotros los que accedemos de forma remota al emulador a travs de su
consola de comandos (shell). Para ello, con el emulador de Android an abierto, debemos abrir una
consola de MS-DOS y utilizar la utilidad adb.exe (Android Debug Bridge) situada en la carpeta
platform-tools del SDK de Android (en mi caso: c:\android-sdk-
windows\platform-tools\). En primer lugar consultaremos los identificadores de todos los
emuladores en ejecucin mediante el comando adb devices. Esto nos debe devolver una
nica instancia si slo tenemos un emulador abierto, que en mi caso particular se llama
emulator-5554.
Tras conocer el identificador de nuestro emulador, vamos a acceder a su shell mediante el comando
adb -s identificador-del-emulador shell. Una vez conectados, ya podemos
acceder a nuestra base de datos utilizando el comando sqlite3 pasndole la ruta del fichero, para
nuestro ejemplo sqlite3
/data/data/net.sgoliver.android/databases/DBUsuarios. Si todo ha ido bien,
debe aparecernos el prompt de SQLite sqlite>, lo que nos indicar que ya podemos escribir las
consultas SQL necesarias sobre nuestra base de datos. Nosotros vamos a comprobar que existe la
tabla Usuarios y que se han insertado los cinco registros de ejemplo. Para ello haremos la
siguiente consulta: SELECT * FROM Usuarios;. Si todo es correcto esta instruccin debe
devolvernos los cinco usuarios existentes en la tabla. En la imagen siguiente se muestra todo el
proceso descrito (click para ampliar):
Con esto ya hemos comprobado que nuestra base de datos se ha creado correctamente, que se han
insertado todos los registros de ejemplo y que todo funciona segn se espera.
En los siguientes artculos comentaremos las distintas posibilidades que tenemos a la hora de
manipular los datos de la base de datos (insertar, eliminar y modificar datos) y cmo podemos
realizar consultas sobre los mismos, ya que [como siempre] tendremos varias opciones disponibles.
En el artculo anterior del curso de programacin en Android vimos cmo crear una base de datos
para utilizarla desde nuestra aplicacin Android. En este segundo artculo de la serie vamos a
describir las posibles alternativas que proporciona la API de Android a la hora de insertar, actualizar
y eliminar registros de nuestra base de datos SQLite.
La API de SQLite de Android proporciona dos alternativas para realizar operaciones sobre la base
de datos que no devuelven resultados (entre ellas la insercin/actualizacin/eliminacin de registros,
pero tambin la creacin de tablas, de ndices, etc).
1//Insertar un registro
db.execSQL("INSERT INTO Usuarios (usuario,email) VALUES
2('usu1','usu1@email.com') ");
3
4//Eliminar un registro
5db.execSQL("DELETE FROM Usuarios WHERE usuario='usu1' ");
6
7//Actualizar un registro
db.execSQL("UPDATE Usuarios SET email='nuevo@email.com' WHERE usuario='usu1'
8");
La segunda de las alternativas disponibles en la API de Android es utilizar los mtodos insert(),
update() y delete() proporcionados tambin con la clase SQLiteDatabase. Estos
mtodos permiten realizar las tareas de insercin, actualizacin y eliminacin de registros de una
forma algo ms paramtrica que execSQL(), separando tablas, valores y condiciones en
parmetros independientes de estos mtodos.
Empecemos por el mtodo insert() para insertar nuevos registros en la base de datos. Este
mtodo recibe tres parmetros, el primero de ellos ser el nombre de la tabla, el tercero sern los
valores del registro a insertar, y el segundo lo obviaremos por el momento ya que tan slo se hace
necesario en casos muy puntuales (por ejemplo para poder insertar registros completamente vacos),
en cualquier otro caso pasaremos con valor null este segundo parmetro.
Los valores a insertar los pasaremos como elementos de una coleccin de tipo ContentValues.
Esta coleccin es de tipo diccionario, donde almacenaremos parejas de clave-valor, donde la clave
ser el nombre de cada campo y el valor ser el dato correspondiente a insertar en dicho campo.
Veamos un ejemplo:
Los mtodos update() y delete() se utilizarn de forma muy parecida a sta, con la salvedad
de que recibirn un parmetro adicional con la condicin WHERE de la sentencia SQL. Por
ejemplo, para actualizar el email del usuario de nombre usu1 haramos lo siguiente:
Como podemos ver, como tercer parmetro del mtodo update() pasamos directamente la
condicin del UPDATE tal como lo haramos en la clusula WHERE en una sentencia SQL normal.
El mtodo delete() se utilizara de forma anloga. Por ejemplo para eliminar el registro del
usuario usu2 haramos lo siguiente:
1//Eliminamos el registro del usuario 'usu2'
2db.delete("Usuarios", "usuario='usu2'");
Como vemos, volvemos a pasar como primer parmetro el nombre de la tabla y en segundo lugar la
condicin WHERE. Por supuesto, si no necesitramos ninguna condicin, podramos dejar como
null en este parmetro.
Un ltimo detalle sobre estos mtodos. Tanto en el caso de execSQL() como en los casos de
update() o delete() podemos utilizar argumentos dentro de las condiones de la sentencia
SQL. Esto no son ms que partes variables de la sentencia SQL que aportaremos en un array de
valores aparte, lo que nos evitar pasar por la situacin tpica en la que tenemos que construir una
sentencia SQL concatenando cadenas de texto y variables para formar el comando SQL final. Estos
argumentos SQL se indicarn con el smbolo ?, y los valores de dichos argumentos deben pasarse
en el array en el mismo orden que aparecen en la sentencia SQL. As, por ejemplo, podemos
escribir instrucciones como la siguiente:
1
//Eliminar un registro con execSQL(), utilizando argumentos
2 String[] args = new String[]{"usu1"};
3 db.execSQL("DELETE FROM Usuarios WHERE usuario=?", args);
4
5 //Actualizar dos registros con update(), utilizando argumentos
6 ContentValues valores = new ContentValues();
7 valores.put("email","usu1_nuevo@email.com");
8
String[] args = new String[]{"usu1", "usu2"};
9 db.update("Usuarios", valores, "usuario=? OR usuario=?", args);
10
Esta forma de pasar a la sentencia SQL determinados datos variables puede ayudarnos adems a
escribir cdigo ms limpio y evitar posibles errores.
En el siguiente artculo veremos cmo consultar la base de datos para recuperar registros segn un
determinado criterio.
En el anterior artculo del curso vimos todas las opciones disponibles a la hora de insertar,
actualizar y eliminar datos de una base de datos SQLite en Android. En esta nueva entrega vamos a
describir la ltima de las tareas importantes de tratamiento de datos que nos queda por ver, la
seleccin y recuperacin de datos.
De forma anloga a lo que vimos para las sentencias de modificacin de datos, vamos a tener dos
opciones principales para recuperar registros de una base de datos SQLite en Android. La primera
de ellas utilizando directamente un comando de seleccin SQL, y como segunda opcin utilizando
un mtodo especfico donde parametrizaremos la consulta a la base de datos.
Como en el caso de los mtodos de modificacin de datos, tambin podemos aadir a este mtodo
una lista de argumentos variables que hayamos indicado en el comando SQL con el smbolo ?,
por ejemplo as:
Ms adelante en este artculo veremos cmo podemos manipular el objeto Cursor para recuperar
los datos obtenidos.
Como segunda opcin para recuperar datos podemos utilizar el mtodo query() de la clase
SQLiteDatabase. Este mtodo recibe varios parmetros: el nombre de la tabla, un array con los
nombre de campos a recuperar, la clusula WHERE, un array con los argumentos variables
incluidos en el WHERE (si los hay, null en caso contrario), la clusula GROUP BY si existe, la
clusula HAVING si existe, y por ltimo la clusula ORDER BY si existe. Opcionalmente, se puede
incluir un parmetro al final ms indicando el nmero mximo de registros que queremos que nos
devuelva la consulta. Veamos el mismo ejemplo anterior utilizando el mtodo query():
Como vemos, los resultados se devuelven nuevamente en un objeto Cursor que deberemos
recorrer para procesar los datos obtenidos.
Para recorrer y manipular el cursor devuelto por cualquiera de los dos mtodos mencionados
tenemos a nuestra disposicin varios mtodos de la clase Cursor, entre los que destacamos dos de
los dedicados a recorrer el cursor de forma secuencial y en orden natural:
Una vez posicionados en cada registro podremos utilizar cualquiera de los mtodos
getXXX(ndice_columna) existentes para cada tipo de dato para recuperar el dato de cada
campo del registro actual del cursor. As, si queremos recuperar por ejemplo la segunda columna
del registro actual, y sta contiene un campo alfanumrico, haremos la llamada getString(1)
[NOTA: los ndices comienzan por 0, por lo que la segunda columna tiene ndice 1], en caso de
contener un dato de tipo real llamaramos a getDouble(1), y de forma anloga para todos los
tipos de datos existentes.
Con todo esto en cuenta, veamos cmo podramos recorrer el cursor devuelto por el ejemplo
anterior:
1
2 String[] campos = new String[] {"usuario", "email"};
3 String[] args = new String[] {"usu1"};
4
Cursor c = db.query("Usuarios", campos, "usuario=?", args, null, null, null);
5
6 //Nos aseguramos de que existe al menos un registro
7 if (c.moveToFirst()) {
8 //Recorremos el cursor hasta que no haya ms registros
9 do {
String usuario = c.getString(0);
10 String email = c.getString(1);
11 } while(c.moveToNext());
12}
13
Adems de los mtodos comentados de la clase Cursor existen muchos ms que nos pueden ser
tiles en muchas ocasiones. Por ejemplo, getCount() te dir el nmero total de registros
devueltos en el cursor, getColumnName(i) devuelve el nombre de la columna con ndice i,
moveToPosition(i) mueve el puntero del cursor al registro con ndice i, etc. Podis consultar
la lista completa de mtodos disponibles en la clase Cursor en la documentacin oficial de Android.
Con esto, terminamos la serie de artculos bsicos dedicados a las tareas de mantenimiento de datos
en aplicaciones Android mediante bases de datos SQLite. Soy consciente de que dejamos en el
tintero algunos temas algo ms avanzados (como por ejemplo el uso de transacciones, que intentar
tratar ms adelante), pero con los mtodos descritos podremos realizar un porcentaje bastante alto
de todas las tareas necesarias relativas al tratamiento de datos estructurados en aplicaciones
Android.
Lo primero que hay que tener en cuenta es dnde queremos almacenar los ficheros y el tipo de
acceso que queremos tener a ellos. As, podremos leer y escribir ficheros localizados en:
Veamos en primer lugar cmo trabajar con la memoria interna del dispositivo. Cuando
almacenamos ficheros en la memoria interna debemos tener en cuenta las limitaciones de espacio
que tienen muchos dispositivos, por lo que no deberamos abusar de este espacio utilizando ficheros
de gran tamao.
Escribir ficheros en la memoria interna es muy sencillo. Android proporciona para ello el mtodo
openFileOutput(), que recibe como parmetros el nombre del fichero y el modo de acceso
con el que queremos abrir el fichero. Este modo de acceso puede variar entre MODE_PRIVATE (por
defecto) para acceso privado desde nuestra aplicacin, MODE_APPEND para aadir datos a un
fichero ya existente, MODE_WORLD_READABLE para permitir a otras aplicaciones leer el fichero, o
MODE_WORLD_WRITABLE para permitir a otras aplicaciones escribir sobre el fichero.
Este mtodo devuelve una referencia al stream de salida asociado al fichero (en forma de objeto
FileOutputStream), a partir del cual ya podremos utilizar los mtodos de manipulacin de
ficheros tradicionales del lenguaje java (api java.io). Como ejemplo, convertiremos este stream
a un OutputStreamWriter para escribir una cadena de texto al fichero.
try
{
OutputStreamWriter fout=
new OutputStreamWriter(
openFileOutput("prueba_int.txt", Context.MODE_PRIVATE));
fout.write("Texto de prueba.");
fout.close();
}
catch (Exception ex)
{
Log.e("Ficheros", "Error al escribir fichero a memoria interna");
}
Est bien,
b ya hemmos creado un
u fichero dee texto en laa memoria interna, perro dnde exaactamente?
Tal coomo ocurra con las basees de datos SQLite,
S Andrroid almacenna por defeccto los ficherros creados
en unaa ruta determ
minada, que en
e este caso seguir el siiguiente patrrn:
/data
a/data/pa
aquete_java/files/nombre
e_fichero
o
En mi caso particu
ular, la ruta ser
s
a/data/ne
/data et.sgoliver.andr
roid/file
es/prueba
a_int.txt
t
try
{
Buffere
edReader fiin =
new Buffe
eredReader((
ne
ew InputSt
treamReader
r(
openFileInput
t("prueba_i
_int.txt")));
String texto = fi
in.readLin
ne();
fin.clo
ose();
}
catch
h (Exceptio
on ex)
{
Log.e("
"Ficheros",
, "Error al
a leer fic
chero desd
de memoria interna");
}
La seggunda formaa de almacennar ficheros en la memooria interna del disposittivo es incluuirlos como
recursso en la pro opia aplicaccin. Aunquue este mtoodo es til en muchos casos, sloo debemos
utilizaarlo cuando no necesiteemos realizaar modificacciones sobree los ficheros, ya que tendremos
limitaddo el acceso a slo lectuura.
Para incluir un fichero como recursoo de la applicacin deebemos colocarlo en la carpeta
/ress/raw de nuestro
n proyyecto de Eclipse. Esta caarpeta no sueele estar creada por defeecto, por lo
que deeberemos crrearla manuaalmente en Eclipse desde el men contextual con la opciin New /
Folderr.
Una vez
v creada laa carpeta /r raw podremmos colocar ene ella cualqquier ficheroo que querammos que se
incluyya con la ap plicacin en tiempo de compilacinn en forma de recurso. Nosotros inncluiremos
como ejemplo un fichero de textot llamado prueba_rraw.txt. Ya en tiempo de d ejecucinn podremos
accedeer a este fich
hero, slo enn modo de lectura, de unaa forma simiilar a la que ya hemos viisto para el
resto de
d ficheros ene memoria interna.
i
Para acceder
a al ficchero, accedderemos en primer
p lugarr a los recurssos de la apllicacin conn el mtodo
getRe esources( () y sobre stos utilizaaremos el mtodo openRawResou urce(id_dell_recurso)
para abrir
a el ficheero en modoo lectura. Este
E mtodo devuelve un u objeto In nputStrea am, que ya
podrem mos manipu ular como quueramos mediante los mtodos
m de la
l API java a.io. Com mo ejemplo,
nosotrros convertirremos el streeam en un objeto
o Buffe eredRead der para leerr el texto coontenido en
el fichhero de ejem
mplo (por suppuesto los fiicheros de reecurso tambin pueden ser binarios,, como por
ejemplo ficheros de d imagen, video,
v etc). Veamos
V cm
mo quedara el e cdigo:
try
{
InputSt
tream fraw =
getResour
rces().open
nRawResour
rce(R.raw.p
prueba_raw
w);
edReader br
Buffere rin =
new Buffe
eredReader(
(new Input der(fraw));
tStreamRead
String linea = br
rin.readLi
ine();
lose();
fraw.cl
}
catch
h (Exceptio
on ex)
{
Log.e("
"Ficheros",
, "Error al
a leer fic de recurso raw");
chero desd
}
Como puede versse en el cddigo anteriorr, al mtodoo openRaw wResource e() le pasaamos como
parm
metro el ID del fichero inncluido coomo recurrso, que seguir el e patrn
R.raw del_fichero, por lo que en
w.nombre_d e nuestro caaso particulaar ser R.ra
aw.prueba a_raw.
Para no
n alargar mu ucho el artcculo, dejamoos la gestin de ficheros en la memorria externa (tarjeta SD)
para el
e prximo artculo,
a donnde tambin publicar unna sencilla aplicacin
a d ejemplo que
de q incluya
toda laa funcionalid
dad que hemmos comentaddo sobre la gestin
g de ficcheros.
Ficcheross en An
ndroid
d (II): Mem
moria Extern
E na
(Taarjeta SD)
Por sggoliver on 06
6/07/2011 enn Android, Programacinn
Una nota
n rpida antes
a de emppezar con estte tema. Parra poder probbar aplicacioones que hagan uso de
la memmoria extern na en el emuulador de Anndroid necessitamos teneer configuraddo en Eclipsse un AVD
que tennga estableccido correctaamente el tam
mao de la taarjeta SD. Enn mi caso, he
h definido por
p ejemplo
un tam
mao de tarjeeta de 10 Mbb:
Para esto
e la API de Androidd proporcionna (como mtodo esttico dela classe Environ nment) el
mtoddo getExte ernalStor rageStatu us(), que no
n dice si la memoria exxterna est diisponible y
si se puede
p leer y escribir en ella. Este mtodo
m devueelve una serrie de valorees que nos inndicarn el
estadoo de la memo oria externa, siendo los ms
m importanntes los siguuientes:
MEDIA_MO OUNTED, quue indica quue la memoriia externa esst disponible y podemoss tanto leer
como escriibir en ella.
MEDIA_MO OUNTED_R READ_ONLY Y, que indicaa que la memmoria externna est dispoonible pero
slo podemmos leer de ella.
e
Otra serie de valores que
q indicarnn que existe algn probleema y que poor tanto no podemos
p ni
leer ni esccribir en la memoria externa
e (MEDDIA_UNMOUNTED, ME EDIA_REMO OVED, ).
Podis connsultar todos estos estadoos en la docuumentacin oficial
o de la clase Enviroonment.
Con todo esto en cuenta, podramos realizar un chequeo previo del estado de la memoria externa del
dispositivo de la siguiente forma:
1
2
boolean sdDisponible = false;
3 boolean sdAccesoEscritura = false;
4
5 //Comprobamos el estado de la memoria externa (tarjeta SD)
6 String estado = Environment.getExternalStorageState();
7
8 if (estado.equals(Environment.MEDIA_MOUNTED))
9 {
sdDisponible = true;
10 sdAccesoEscritura = true;
11}
12else if (estado.equals(Environment.MEDIA_MOUNTED_READ_ONLY))
13{
sdDisponible = true;
14 sdAccesoEscritura = false;
15}
16else
17{
18 sdDisponible = false;
sdAccesoEscritura = false;
19}
20
21
Una vez chequeado el estado de la memoria externa, y dependiendo del resultado obtenido, ya
podremos leer o escribir en ella cualquier tipo de fichero.
Empecemos por la escritura. Para escribir un fichero a la tarjeta SD tenemos que obtener en primer
lugar la ruta al directorio raz de esta memoria. Para ello utilizaremos el mtodo
getExternalStorageDirectory() de la clase Environment, que nos devolver un
objeto File con la ruta de dicho directorio. A partir de este objeto, podremos construir otro con el
nombre elegido para nuestro fichero (como ejemplo prueba_sd.txt), creando un nuevo objeto
File que combine ambos elementos. Tras esto, ya slo queda encapsularlo en algn objeto de
escritura de ficheros de la API de java y escribir algn dato de prueba. En nuestro caso de ejemplo
lo convertiremos una vez ms a un objeto OutputStreamWriter para escribir al fichero un
mensaje de texto. Veamos cmo quedara el cdigo:
try
1 {
2 File ruta_sd = Environment.getExternalStorageDirectory();
3
4 File f = new File(ruta_sd.getAbsolutePath(), "prueba_sd.txt");
5
6 OutputStreamWriter fout =
7 new OutputStreamWriter(
new FileOutputStream(f));
8
9 fout.write("Texto de prueba.");
10 fout.clos
se();
11 }
cat
tch (Exception ex)
12{
13 icheros", "Error al escribir fichero a tarjeta SD
Log.e("Fi D");
14}
15
16
17
1
2 <maanifest xmlns:androidd="http://schemas.anndroid.com//apk/res/a
android"
3 package
e="net.sgoliver.andr roid"
4 android
d:versionCode="1"
android
d:versionName="1.0"> >
5 <uses-sdk
k android:mminSdkVersion="7" />
6
7 <uses-per
rmission
8 oid:name="android.pe
andro ermission.WRITE_EXTEERNAL_STORAAGE" />
9 </uses-pe
ermission>
10
<applicat
tion an
ndroid:icoon="@drawab
ble/icon"
11anddroid:label
l="@string/app_name" ">
12 <acti
ivity androoid:name=".AndroidFiicheros"
13 android:label=="@string/app_name">>
14 <intent-fi
< lter>
15 <action android:name="andrroid.intentt.action.M
MAIN" />
<category android:name="anndroid.inteent.catego
ory.LAUNCHE
ER" />
16 </intent-f
< ilter>
17 </act
tivity>
18
19 </applica
ation>
20 </m
manifest>
21
Si ejeecutamos ah
hora el cdiigo y nos vamos
v al exxplorador dee archivos del DDMS podremos
comprrobar cmosse ha creado correctamennte el ficherro en el direectorio raiz de
d nuestra SD
S (carpeta
/sdca ard/).
Por su parte, leer un fichero desde la tarjeta SD es igual de sencillo. Obtenemos el directorio raiz de
la memoria externa con getExternalStorageDirectory(), creamos un objeto File que
combine esa ruta con el nombre del fichero a leer y lo encapsulamos dentro de algn objeto que
facilite la lectura lectura, nosotros para leer texto utilizaremos como siempre un
BufferedReader.
1
2 try
3 {
4 File ruta_sd = Environment.getExternalStorageDirectory();
5
File f = new File(ruta_sd.getAbsolutePath(), "prueba_sd.txt");
6
7
BufferedReader fin =
8 new BufferedReader(
9 new InputStreamReader(
10 new FileInputStream(f)));
11
12 String texto = fin.readLine();
fin.close();
13}
14catch (Exception ex)
15{
16 Log.e("Ficheros", "Error al leer fichero desde tarjeta SD");
17 }
18
Como vemos, el cdigo es anlogo al que hemos visto para la escritura de ficheros.
Dej pendiente poneros disponible el cdigo de alguna aplicacin sencilla que contenga todos los
temas vistos en estos dos ltimos artculos sobre gestin de ficheros en Android. Y como lo
prometido es deuda, os dejo aqu el enlace al cdigo fuente completo de una aplicacin de ejemplo,
que simplemente habilita un botn para realizar cada una de las tareas comentadas, mostrando el
resultado de cada accin en un cuadro de texto superior de la pantalla proncipal. Os dejo una
imagen para que os hagis una idea:
Traatamieento de
d XM
ML en Andro
A oid (I): SAX
X
Por sggoliver on 18
8/01/2011 enn Android, Programacinn
Un documento RS
SS de este feeed tiene la estructura sigguiente:
<rss version="2
2.0">
<
<channel>
<title>
>Europa Press</title
e>
<link>h
http://www
w.europapre
ess.es/</link>
<descri
iption>Noticias de Portada.</
P descriptio
on>
<image>
<url>http://s01.europapress.net/eplogo.gif</url>
<title>Europa Press</title>
<link>http://www.europapress.es</link>
</image>
<language>es-ES</language>
<copyright>Copyright</copyright>
<pubDate>Sat, 25 Dec 2010 23:27:26 GMT</pubDate>
<lastBuildDate>Sat, 25 Dec 2010 22:47:14 GMT</lastBuildDate>
<item>
<title>Ttulo de la noticia 1</title>
<link>http://link_de_la_noticia_2.es</link>
<description>Descripcin de la noticia 2</description>
<guid>http://identificador_de_la_noticia_2.es</guid>
<pubDate>Fecha de publicacin 2</pubDate>
</item>
<item>
<title>Ttulo de la noticia 2</title>
<link>http://link_de_la_noticia_2.es</link>
<description>Descripcin de la noticia 2</description>
<guid>http://identificador_de_la_noticia_2.es</guid>
<pubDate>Fecha de publicacin 2</pubDate>
</item>
...
</channel>
</rss>
En estos artculos vamos a describir cmo leer este XML mediante cada una de las tres alternativas
citadas, y para ello lo primero que vamos a hacer es definir una clase java para almacenar los datos
de una noticia. Nuestro objetivo final ser devolver una lista de objetos de este tipo, con la
informacin de todas las noticias. Por comodidad, vamos a almacenar todos los datos como cadenas
de texto:
Una vez conocemos la estructura del XML a leer y hemos definido las clases auxiliares que nos
hacen falta para almacenar los datos, pasamos ya a comentar el primero de los modelos de
tratamiento de XML.
SAX en Android
En el modelo SAX, el tratamiento de un XML se basa en un analizador (parser) que a medida que
lee secuencialmente el documento XML va generando diferentes eventos con la informacin de
cada elemento leido. Asi, por ejemplo, a medida que lee el XML, si encuentra el comienzo de una
etiqueta <title> generar un evento de comienzo de etiqueta, startElement(), con su
informacin asociada, si despus de esa etiqueta encuentra un fragmento de texto generar un
evento characters() con toda la informacin necesaria, y as sucesivamente hasta el final del
documento. Nuestro trabajo consistir por tanto en implementar las acciones necesarias a ejecutar
para cada uno de los eventos posibles que se pueden generar durante la lectura del documento
XML.
Los principales eventos que se pueden producir son los siguientes (consultar aqu la lista completa):
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
if (this.notciaActual != null)
builder.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String name)
throws SAXException {
if (this.notciaActual != null) {
if (localName.equals("title")) {
noticiaActual.setTitulo(sbTexto.toString());
} else if (localName.equals("link")) {
noticiaActual.setLink(sbTexto.toString());
} else if (localName.equals("description")) {
noticiaActual.setDescripcion(sbTexto.toString());
} else if (localName.equals("guid")) {
noticiaActual.setGuid(sbTexto.toString());
} else if (localName.equals("pubDate")) {
noticiaActual.setFecha(sbTexto.toString());
} else if (localName.equals("item")) {
noticias.add(noticiaActual);
}
sbTexto.setLength(0);
}
}
@Override
public void startDocument() throws SAXException {
super.startDocument();
if (localName.equals("item")) {
noticiaActual = new Noticia();
}
}
}
Como se puede observar en el cdigo de anterior, lo primero que haremos ser incluir como
miembro de la clase la lista de noticias que pretendemos construir, List<Noticia>
noticias, y un mtodo getNoticias() que permita obtenerla tras la lectura completa del
documento. Tras esto, implementamos directamente los eventos SAX necesarios.
Tras ste, el evento startElement() se lanza cada vez que se encuentra una nueva etiqueta de
apertura. En nuestro caso, la nica etiqueta que nos interesar ser <item>, momento en el que
inicializaremos un nuevo objeto auxiliar de tipo Noticia donde almacenaremos posteriormente
los datos de la noticia actual.
El siguiente evento relevante es characters(), que se lanza cada vez que se encuentra un
fragmento de texto en el interior de una etiqueta. La tcnica aqu ser ir acumulando en una variable
auxiliar, sbTexto, todos los fragmentos de texto que encontremos hasta detectarse una etiqueta de
cierre.
Por ltimo, en el evento de cierre de etiqueta, endElement(), lo que haremos ser almacenar en
el atributo apropiado del objeto noticiaActual (que conoceremos por el parmetro
localName devuelto por el evento) el texto que hemos ido acumulando en la variable sbTexto
y limpiaremos el contenido de dicha variable para comenzar a acumular el siguiente dato. El nico
caso especial ser cuando detectemos el cierre de la etiqueta <item>, que significar que hemos
terminado de leer todos los datos de la noticia y por tanto aprovecharemos para aadir la noticia
actual a la lista de noticias que estamos construyendo.
Una vez implementado nuestro handler, vamos a crear una nueva clase que haga uso de l para
parsear mediante SAX un documento XML concreto. A esta clase la llamaremos RssParserSax.
Ms adelante crearemos otras clases anlogas a sta que hagan lo mismo pero utilizando los otros
dos mtodos de tratamiento de XML ya mencionados. Esta clase tendr nicamente un constructor
que reciba como parmetro la URL del documento a parsear, y un mtodo pblico llamado
parse() para ejecutar la lectura del documento, y que devolver como resultado una lista de
noticias. Veamos cmo queda esta clase:
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.net.URL;
import javax.xml.parsers.SAXParser;
import java.net.MalformedURLException;
import javax.xml.parsers.SAXParserFactory;
try
{
SAXParser parser = factory.newSAXParser();
RssHandler handler = new RssHandler();
parser.parse(this.getInputStream(), handler);
return handler.getNoticias();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
Como se puede observar en el cdigo anterior, el constructor de la clase se limitar a aceptar como
parmetro la URL del documento XML a parsear a controlar la validez de dicha URL, generando
una excepcin en caso contrario.
Por su parte, el mtodo parse() ser el encargado de crear un nuevo parser SAX mediante s
fbrica correspondiente [lo que se consigue obteniendo una instancia de la fbrica con
SAXParserFactory.newInstance() y creando un nuevo parser con
factory.newSaxParser()] y de iniciar el proceso pasando al parser una instancia del handler
que hemos creado anteriormente y una referencia al documento a parsear en forma de stream.
Para esto ltimo, nos apoyamos en un mtodo privado auxiliar getInputStream(), que se
encarga de abrir la conexin con la URL especificada [mediante openConnection()] y obtener
el stream de entrada [mediante getInputStream()].
Con esto ya tenemos nuestra aplicacin Android preparada para parsear un documento XML online
utilizando el modelo SAX. Veamos lo simple que sera ahora llamar a este parser por ejemplo desde
nuestra actividad principal:
RssParserSax saxparser =
new RssParserSax("http://www.europapress.es/rss/rss.aspx");
Las lineas 6 y 9 del cdigo anterior son las que hacen toda la magia. Primero creamos el parser
SAX pasndole la URL del documento XML y posteriormente llamamos al mtodo parse() para
obtener una lista de objetos de tipo Noticia que posteriormente podremos manipular de la forma
que queramos. As de sencillo.
Tan slo una anotacin final. Para que este ejemplo funcione debemos aadir previamente permisos
de acceso a internet para la aplicacin. Esto se hace en el fichero AndroidManifest.xml, que
quedara de la siguiente forma:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.sgoliver"
android:versionCode="1"
android:versionName="1.0">
<uses-permission
android:name="android.permission.INTERNET" />
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".AndroidXml"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="7" />
</manifest>
En la linea 6 del cdigo podis ver cmo aadimos el permiso de acceso a la red mediante el
elemento <uses-permission> con el parmetro android.permission.INTERNET
En los siguientes artculos veremos los otros dos mtodos de tratamiento XML en Android que
hemos comentado (DOM y StAX) y por ltimo intentaremos comentar las diferencias entre ellos
dependiendo del contexto de la aplicacin.
ACTUALIZACIN: Android propone un modelo SAX alternativo que puede ayudar a simplicar
algunas acciones y disminuir la complejidad del handler necesario. En este artculo puedes aprender
a utilizar esta nueva variante de SAX para Android.
En el artculo anterior del tutorial vimos cmo realizar la lectura y tratamiento de un documento
XML utilizando el modelo SAX clsico. Vimos cmo implementar un handler SAX, donde se
definan las acciones a realizar tras recibirse cada uno de los posibles eventos generados por el
parser XML.
Este modelo, a pesar de funcionar perfectamente y de forma bastante eficiente, tiene claras
desventajas. Por un lado se hace necesario definir una clase independiente para el handler.
Adicionalmente, la naturaleza del modelo SAX implica la necesidad de poner bastante atencin a la
hora de definir dicho handler, ya que los eventos SAX definidos no estan ligados de ninguna forma
a etiquetas concretas del documento XML sino que se lanzarn para todas ellas, algo que obliga
entre otras cosas a realizar la distincin entre etiquetas dentro de cada evento y a realizar otros
chequeos adicionales.
Veamos cmo queda nuestro parser XML utilizando esta variante simplificada de SAX para
Android y despus comentaremos los aspectos ms importantes del mismo.
1 import java.io.IOException;
import java.io.InputStream;
2
import java.net.MalformedURLException;
3 import java.net.URL;
4 import java.util.ArrayList;
5 import java.util.List;
6
7 import org.xml.sax.Attributes;
8
import android.sax.Element;
9 import android.sax.EndElementListener;
10 import android.sax.EndTextElementListener;
11 import android.sax.RootElement;
12 import android.sax.StartElementListener;
13 import android.util.Xml;
14
public class RssParserSax2
15 {
16 private URL rssUrl;
17 private Noticia noticiaActual;
18
19 public RssParserSax2(String url)
{
20 try
21 {
22 this.rssUrl = new URL(url);
23 }
24 catch (MalformedURLException e)
{
25 throw new RuntimeException(e);
26 }
27 }
28
29 public List<Noticia> parse()
{
30 final List<Noticia> noticias = new ArrayList<Noticia>();
31
32 RootElement root = new RootElement("rss");
33 Element channel = root.getChild("channel");
34 Element item = channel.getChild("item");
35
36 item.setStartElementListener(new StartElementListener(){
public void start(Attributes attrs) {
37 noticiaActual = new Noticia();
38 }
39 });
40
41 item.setEndElementListener(new EndElementListener(){
42 public void end() {
noticias.add(noticiaActual);
43 }
44 });
45
46 item.getChild("title").setEndTextElementListener(
47 new EndTextElementListener(){
public void end(String body) {
48 noticiaActual.setTitulo(body);
49 }
50 });
51
52 item.getChild("link").setEndTextElementListener(
53 new EndTextElementListener(){
public void end(String body) {
54 noticiaActual.setLink(body);
55 }
56 });
57
58 item.getChild("description").setEndTextElementListener(
new EndTextElementListener(){
59 public void end(String body) {
60 noticiaActual.setDescripcion(body);
61 }
62 });
63
64 item.getChild("guid").setEndTextElementListener(
new EndTextElementListener(){
65 public void end(String body) {
66 noticiaActual.setGuid(body);
67 }
68 });
69
item.getChild("pubDate").setEndTextElementListener(
70
new EndTextElementListener(){
71 public void end(String body) {
72 noticiaActual.setFecha(body);
73 }
74 });
75
try
76 {
77 Xml.parse(this.getInputStream(),
78 Xml.Encoding.UTF_8,
79 root.getContentHandler());
80 }
catch (Exception ex)
81 {
82 throw new RuntimeException(ex);
83 }
84
85 return noticias;
}
86
87 private InputStream getInputStream()
88
89 {
90 try
{
91 return rssUrl.openConnection().getInputStream();
92 }
93 catch (IOException e)
94 {
throw new RuntimeException(e);
95 }
96 }
97 }
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
Debemos atender principalmente al mtodo parse(). En el modelo SAX clsico nos limitamos a
instanciar al handler definido en una clase independiente y llamar al correspondiente mtodo
parse() de SAX. Por el contrario, en este nuevo modelo SAX simplificado de Android, las
acciones a realizar para cada evento las vamos a definir en esta misma clase y adems asociadas a
etiquetas concretas del XML. Y para ello lo primero que haremos ser navegar por la estructura del
XML hasta llegar a las etiquetas que nos interesa tratar y una vez all, asignarle algunos de los
listeners disponibles [de apertura (StartElementListener) o cierre
(EndElementListener) de etiqueta] incluyendo las acciones oportunas. De esta forma, para el
elemento <item> navegaremos hasta l obteniendo en primer lugar el elemento raz del XML
(<rss>) declarando un nuevo objeto RootElement y despus accederemos a su elemento hijo
<channel> y a su vez a su elemento hijo <item>, utilizando en cada paso el mtodo
getChild(). Una vez heos llegado a la etiqueta deseada, asignaremos los listeners necesarios, en
nuestro caso uno de apertura de etiqueta y otro de cierre, donde inicializaremos la noticia actual y la
aadiremos a la lista final respectivamente, de forma anloga a lo que hacamos para el modelo
SAX clsico. Para el resto de etiquetas actuaremos de la misma forma, accediendo a ellas con
getChild() y asignado los listeners necesarios.
Como vemos, este modelo SAX alternativo simplifica la elaboracin del handler necesario y puede
ayudar a evitar posibles errores en el handler y disminuir la complejidad del mismo para casos en
los que el documento XML no sea tan sencillo como el utilizado para estos ejemplos. Por supuesto,
el modelo clsico es tan vlido y eficiente como ste, por lo que la eleccin entre ambos es cuestin
de gustos.
En el artculo anterior del curso de programacin para Android hablamos sobre SAX, el primero de
los mtodos disponibles en Android para leer ficheros XML desde nuestras aplicaciones. En este
segundo artculo vamos a centrarnos en DOM, otro de los mtodos clsicos para la lectura y
tratamiento de XML.
Cuando comentbamos la filosofa de SAX ya vimos cmo con dicho modelo el tratamiento del
fichero XML se realizaba de forma secuencial, es decir, se iban realizando las acciones necesarias
durante la propia lectura del documento. Sin embargo, con DOM la estrategia cambia radicalmente.
Con DOM, el documento XML se lee completamente antes de poder realizar ninguna accin en
funcin de su contenido. Esto es posible gracias a que, como resultado de la lectura del documento,
el parser DOM devuelve todo su contenido en forma de una estructura de tipo rbol, donde los
distintos elementos del XML se representa en forma de nodos y su jerarqua padre-hijo se establece
mediante relaciones entre dichos nodos.
Como ejemplo, vemos un ejemplo de XML sencillo y cmo quedara su representacin en forma de
rbol:
1
2 <noticias>
<noticia>
3 <titulo>T1</titulo>
4 <link>L1</link>
5 </noticia>
6 <noticia>
7 <titulo>T2</titulo>
<link>L2</link>
8 </noticia>
9 <noticias>
10
Este XML se traducira en un rbol parecido al siguiente:
Como vemos, este rbol conserva la misma informacin contenida en el fichero XML pero en
forma de nodos y transiciones entre nodos, de forma que se puede navegar fcilmente por la
estructura. Adems, este rbol se conserva persistente en memoria una vez leido el documento
completo, lo que permite procesarlo en cualquier orden y tantas veces como sea necesario (a
diferencia de SAX, donde el tratamiento era secuencial y siempre de principio a fin del documento,
no pudiendo volver atrs una vez finalizada la lectura del XML).
Para todo esto, el modelo DOM ofrece una serie de clases y mtodos que permiten almacenar la
informacin de la forma descrita y facilitan la navegacin y el tratamiento de la estructura creada.
Veamos cmo quedara nuestro parser utilizando el modelo DOM y justo despus comentaremos
los detalles ms importantes.
1 public
{
class RssParserDom
2 private URL rssUrl;
3
4 public RssParserDom(String url)
5 {
6 try
{
7 this.rssUrl = new URL(url);
8 }
9 catch (MalformedURLException e)
10 {
11 throw new RuntimeException(e);
}
12 }
13
14 public List<Noticia> parse()
15 {
16 //Instanciamos la fbrica para DOM
DocumentBuilderFactory factory =
17 DocumentBuilderFactory.newInstance();
18 List<Noticia> noticias = new ArrayList<Noticia>();
19
20 try
21 {
//Creamos un nuevo parser DOM
22 DocumentBuilder builder = factory.newDocumentBuilder();
23
24 //Realizamos lalectura completa del XML
25 Document dom = builder.parse(this.getInputStream());
26
27 //Nos posicionamos en el nodo principal del rbol (<rss>)
Element root = dom.getDocumentElement();
28
29 //Localizamos todos los elementos <item>
30 NodeList items = root.getElementsByTagName("item");
31
32 //Recorremos la lista de noticias
33 for (int i=0; i<items.getLength(); i++)
34 {
Noticia noticia = new Noticia();
35
36 //Obtenemos la noticia actual
37 Node item = items.item(i);
38
39 //Obtenemos la lista de datos de la noticia actual
40 NodeList datosNoticia = item.getChildNodes();
41
//Procesamos cada dato de la noticia
42 for (int j=0; j<datosNoticia.getLength(); j++)
43 {
44 Node dato = datosNoticia.item(j);
45 String etiqueta = dato.getNodeName();
46
47 if (etiqueta.equals("title"))
{
48 String texto = obtenerTexto(dato);
49 noticia.setTitulo(texto);
50 }
51 else if (etiqueta.equals("link"))
{
52
noticia.setLink(dato.getFirstChild().getNodeValue());
53 }
54 else if (etiqueta.equals("description"))
55 {
56 String texto = obtenerTexto(dato);
noticia.setDescripcion(texto);
57 }
58 else if (etiqueta.equals("guid"))
59 {
60 noticia.setGuid(dato.getFirstChild().getNodeValue());
61 }
else if (etiqueta.equals("pubDate"))
62 {
63 noticia.setFecha(dato.getFirstChild().getNodeValue())
64 ;
65 }
}
66
67
68 noticias.add(noticia);
69 }
}
70 catch (Exception ex)
71 {
72 throw new RuntimeException(ex);
73 }
74
return noticias;
75 }
76
77 private String obtenerTexto(Node dato)
78 {
79 StringBuilder texto = new StringBuilder();
80 NodeList fragmentos = dato.getChildNodes();
81
for (int k=0;k<fragmentos.getLength();k++)
82 {
83 texto.append(fragmentos.item(k).getNodeValue());
84 }
85
86 return texto.toString();
}
87
88 private InputStream getInputStream()
89 {
90 try
91 {
92 return rssUrl.openConnection().getInputStream();
}
93 catch (IOException e)
94 {
95 throw new RuntimeException(e);
96 }
}
97
}
98
99
10
0
10
1
10
2
10
3
10
4
10
5
10
6
10
7
10
8
10
9
11
0
11
1
11
2
11
3
Nos centramos una vez ms en el mtodo parse(). Al igual que hacamos para SAX, el primer
paso ser instanciar una nueva fbrica, esta vez de tipo DocumentBuilderFactory, y
posteriormente crear un nuevo parser a partir de ella mediante el mtodo
newDocumentBuilder().
Tras esto, ya podemos realizar la lectura del documento XML llamando al mtod parse() de
nuestro parser DOM, pasndole como parmetro el stream de entrada del fichero. Al hacer esto, el
documento XML se leer completo y se generar la estructura de rbol equivalente, que se
devolver como un objeto de tipo Document. ste ser el objeto que podremos navegar para
realizar eltratamiento necesario del XML.
Para ello, lo primero que haremos ser acceder al nodo principal del rbol (en nuestro caso, la
etiqueta <rss>) utilizando el mtodo getDocumentElement(). Una vez posicionados en
dicho nodo, vamos a buscar todos los nodos cuya etiqueta sea <item>. Esto lo conseguimos
utilizando el mtodo de bsqueda por nombre de etiqueta,
getElementsByTagName(nombre_de_etiqueta), que devolver una lista (de tipo
NodeList) con todos los nodos hijos del nodo actual cuya etiqueta coincida con la pasada como
parmetro.
Una vez tenemos localizados todos los elementos <item>, que representan a cada noticia, los
vamos a recorrer uno a uno para ir generando todos los objetos Noticia necesarios. Para cada uno
de ellos, se obtendrn los nodos hijos del elemento mediante getChildNodes() y se recorrern
stos obteniendo su texto y almacenndolo en el atributo correspondiente del objeto Noticia.
Para saber a qu etiqueta corresponde cada nodo hijo utilizamos el mtodo getNodeName().
Merece la pena pararnos un poco en comentar la forma de obtener el texto contenido en un nodo.
Como vimos al principio del artculo en el ejemplo grfico de rbol DOM, el texto de un nodo
determinado se almacena a su vez como nodo hijo de dicho nodo. Este nodo de texto suele ser
nico, por lo que la forma habitual de obtener el texto de un nodo es obtener su primer nodo hijo y
de ste ltimo obtener su valor:
1
2 private String obtenerTexto(Node dato)
{
3 StringBuilder texto = new StringBuilder();
4 NodeList fragmentos = dato.getChildNodes();
5
6 for (int k=0;k<fragmentos.getLength();k++)
7 {
texto.append(fragmentos.item(k).getNodeValue());
8
}
9
10 return texto.toString();
11}
12
Como vemos, el modelo DOM nos permite localizar y tratar determinados elementos concretos del
documento XML, sin la necesidad de recorrer todo su contenido de principio a fin. Adems, a
diferencia de SAX, como tenemos cargado en memoria el documento completo de forma persistente
(en forma de objeto Document), podremos consultar, recorrer y tratar el documento tantas veces
como sea necesario sin necesidad de volverlo a parsear. En un artculo posterior veremos como
todas estas caractersticas pueden ser ventajas o inconvenientes segn el contexto de la aplicacin y
el tipo de XML tratado.
El cdigo fuente completo de este artculo se puede descargar desde este enlace.
En los artculos anteriores dedicados al tratamiento de XML en aplicaciones Android (parte 1, parte
2, parte 3) dentro de nuestro tutorial de programacin Android hemos comentado ya los modelos
SAX y DOM, los dos mtodos ms comunes de lectura de XML soportados en la plataforma.
En este cuarto artculo nos vamos a centrar en el ltimo mtodo menos conocido, aunque igual de
vlido segn el contexto de la aplicacin, llamado XmlPull. Este mtodo es una versin similar al
modelo StAX (Streaming API for XML), que en esencia es muy parecido al modelo SAX ya
comentado. Y digo muy parecido porque tambin se basa en definir las acciones a realizar para cada
uno de los eventos generados durante la lectura secuencial del documento XML. Cul es la
diferencia entonces?
La diferencia radica principalmente en que, mientras que en SAX no tenamos control sobre la
lectura del XML una vez iniciada (el parser lee automticamente el XML de principio a fin
generando todos los eventos necesarios), en el modelo XmlPull vamos a poder guiar o intervenir en
la lectura del documento, siendo nosotros los que vayamos pidiendo de forma explcita la lectura
del siguiente elemento del XML y respondiendo al resultado ejecutando las acciones oportunas.
Centrndonos una vez ms en el mtodo parse(), vemos que tras crear el nuevo parser XmlPull y
establecer el fichero de entrada en forma de stream [mediante XmlPull.newPullParser() y
parser.setInput(...)] nos metemos en un buble en el que iremos solicitando al parser en
cada paso el siguiente evento encontrado en la lectura del XML, utilizando para ello el mtodo
parser.next(). Para cada evento devuelto como resultado consultaremos su tipo mediante el
mtodo parser.getEventType() y responderemos con las acciones oportunas segn dicho
tipo (START_DOCUMENT, END_DOCUMENT, START_TAG, END_TAG). Una vez identificado el
tipo concreto de evento, podremos consultar el nombre de la etiqueta del elemento XML mediante
parser.getName() y su texto correspondiente mediante parser.nextText(). En cuanto a
la obtencin del texto, con este modelo tenemos la ventaja de no tener que preocuparnos por
recolectar todos los fragmentos de texto contenidos en el elemento XML, ya que nextText()
devolver todo el texto que encuentre hasta el prximo evento de fin de etiqueta (ENT_TAG).
Y sobre este modelo de tratamiento no queda mucho ms que decir, ya que las acciones ejecutadas
en cada caso son anlogas a las que ya vimos en los artculos anteriores.
Un artculo rpido antes de seguir con ms temas del Curso de Programacin Android.
Hace ya algn tiempo dedicamos varios artculos (I, II, III, IV) al tratamiento de ficheros XML en
Android y vimos varias alternativas a la hora de leer (parsear) ficheros de este tipo. En todos los
ejemplos decid utilizar como entrada un fichero online, que obtenamos desde una determinada
URL, porque pens que sera el caso ms tpico que bamos a necesitar en la mayora de
aplicaciones. Sin embargo, desde entonces han sido muchas las consultas que me han llegado
relativas a cmo utilizar estos mtodos para leer un fichero XML que se encuentre en un recurso
local. Adicionalmente tambin he recibido muchos comentarios consultando las alternativas
existentes para escribir ficheros XML. Pues bien, aunque las alternativas son muchas, voy a dedicar
este pequeo artculo a intentar resolver ambas dudas.
Para escribir ficheros XML sin meternos en muchas complicaciones innecesarias se me ocurren
bsicamente dos formas. La primera de ellas, y la ms sencilla y directa, es construir manualmente
el XML utilizando por ejemplo un objeto StringBuilder y tras esto escribir el resultado a un
fichero en memoria interna o externa tal como ya vimos en los artculos dedicados a tratamiento de
ficheros (I y II). Esto quedara de una forma similar a la siguiente:
1
2 //Creamos un fichero en la memoria interna
3 OutputStreamWriter fout =
4 new OutputStreamWriter(
openFileOutput("prueba.xml",
5 Context.MODE_PRIVATE));
6
7 StringBuilder sb = new StringBuilder();
8
9 //Construimos el XML
10sb.append("");
11sb.append("" + "Usuario1" + "");
sb.append("" + "ApellidosUsuario1" + "");
12sb.append("");
13
14//Escribimos el resultado a un fichero
15fout.write(sb.toString());
16fout.close();
17
Como mtodo alternativo tambin podemos utilizar el serializador XML incluido con la API
XmlPull. Aunque no es un mtodo tan directo como el anterior no deja de ser bastante intuitivo.
Los pasos para conseguir esto sern crear el objeto XmlSerializer, crear el fichero de salida,
asignar el fichero como salida del serializador y construir el XML mediante los mtodos
startTag(), text() y endTag() pasndoles los nombres de etiqueta y contenidos de texto
correspondientes (aunque existen ms mtodos, por ejemplo para escribir atributos de una etiqueta,
stos tres son los principales). Finalmente deberemos llamar a endDocument() para finalizar y
cerrar nuestro documento XML. Un ejemplo equivalente al anterior utilizando este mtodo sera el
siguiente:
1
2
3 //Creamos el serializer
XmlSerializer ser = Xml.newSerializer();
4
5 //Creamos un fichero en memoria interna
6 OutputStreamWriter fout =
7 new OutputStreamWriter(
8 openFileOutput("prueba_pull.xml",
Context.MODE_PRIVATE));
9
10//Asignamos el resultado del serializer al fichero
11ser.setOutput(fout);
12
13//Construimos el XML
14ser.startTag("", "usuario");
15
16ser.startTag("", "nombre");
ser.text("Usuario1");
17ser.endTag("", "nombre");
18
19ser.startTag("", "apellidos");
20ser.text("ApellidosUsuario1");
21ser.endTag("", "apellidos");
22
ser.endTag("", "usuario");
23
24ser.endDocument();
25
26fout.close();
27
28
Para leer ficheros XML desde un recurso local se me ocurren varias posibilidades, por ejemplo
leerlo desde la memoria interna/externa del dispositivo, leer un XML desde un recurso XML
(carpeta /res/xml), desde un recurso Raw (carpeta /res/raw), o directamente desde la carpeta /assets
de nuestro proyecto. Salvo en el segundo caso (recurso XML), en todos los dems la solucin va a
pasar por conseguir una referencia de tipo InputStream (os recuerdo que cualquiera de los
mtodos que vimos para leer un XML partan de una referencia de este tipo) a partir de nuestro
fichero o recurso XML, sea cual sea su localizacin.
As, por ejemplo, si el fichero XML est almacenado en la memoria interna de nuestro dispositivo,
podramos acceder a l mediante el mtodo openFileInput() tal como vimos en los artculos
dedicados a tratamiento de ficheros. Este mtodo devuelve un objeto de tipo FileInputStream,
que al derivar de InputStream podemos utilizarlo como entrada a cualquiera de los mecanismos
de lectura de XML (SAX, DOM, XmlPull).
1
2 //Obtenemos la referencia al fichero XML de entrada
3 FileInputStream fil = openFileInput("prueba.xml");
4
//DOM (Por ejemplo)
5 DocumentBuilderFactory factory =
6 DocumentBuilderFactory.newInstance();
7
8 DocumentBuilder builder = factory.newDocumentBuilder();
9 Document dom = builder.parse(fil);
10
//A partir de aqu se tratara el rbol DOM como siempre.
11//Por ejemplo:
12Element root = dom.getDocumentElement();
13
14//...
15
En el caso de encontrarse el fichero como recurso Raw, es decir, en la carpeta /res/raw, tendramos
que obtener la referencia al fichero mediante el mtodo getRawResource() pasndole como
parmetro el ID de recurso del fichero. Esto nos devuelve directamente el stream de entrada en
forma de InputStream.
1
2 //Obtenemos la referencia al fichero XML de entrada
3 InputStream is = getResources().openRawResource(R.raw.prueba);
4
//DOM (Por ejemplo)
5 DocumentBuilderFactory factory =
6 DocumentBuilderFactory.newInstance();
7
8 DocumentBuilder builder = factory.newDocumentBuilder();
9 Document dom = builder.parse(is);
10
//A partir de aqu se tratara el rbol DOM como siempre.
11//Por ejemplo:
12Element root = dom.getDocumentElement();
13
14//...
15
Por ltimo, si el XML se encontrara en la carpeta /assets de nuestra aplicacin, accederamos a l a
travs del AssetManager, el cual podemos obtenerlo con el mtodo getAssets() de nuestra
actividad. Sobre este AssetManager tan slo tendremos que llamar al mtodo open() con el
nombre del fichero para obtener una referencia de tipo InputStream a nuestro XML.
1
2 //Obtenemos la referencia al fichero XML de entrada
3 AssetManager assetMan = getAssets();
InputStream is = assetMan.open("prueba_asset.xml");
4
5 //DOM (Por ejemplo)
6 DocumentBuilderFactory factory =
7 DocumentBuilderFactory.newInstance();
8
9 DocumentBuilder builder = factory.newDocumentBuilder();
10Document dom = builder.parse(is);
11
//A partir de aqu se tratara el rbol DOM como siempre.
12//Por ejemplo:
13Element root = dom.getDocumentElement();
14
15//...
16
El ltimo caso, algo distinto a los anteriores, pasara por leer el XML desde un recurso XML
localizado en la carpeta /res/xml de nuestro proyecto. Para acceder a un recurso de este tipo
debemos utilizar el mtodo getXml() al cual debemos pasarle como parmetro el ID de recurso
del fichero XML. Esto nos devolver un objeto XmlResourceParser, que no es ms que un
parser de tipo XmlPull como el que ya comentamos en su momento. Por tanto, la forma de trabajar
con este parser ser idntica a la que ya conocemos, por ejemplo:
1
//Obtenemos un parser XmlPull asociado a nuestro XML
2 XmlResourceParser xrp = getResources().getXml(R.xml.prueba);
3
4 //A partir de aqu utilizamos la variable 'xrp' como
5 //cualquier otro parser de tipo XmlPullParser. Por ejemplo:
6
7 int evento = xrp.getEventType();
8
9 if(evento == XmlPullParser.START_DOCUMENT)
Log.i("XmlTips", "Inicio del documento");
10
11//...
12
Por ltimo, indicar que todas estas formas de acceder a un fichero en Android no se limitan
nicamente a los de tipo XML, sino que pueden utilizarse para leer cualquier otro tipo de ficheros.
Como siempre, podis descargar el cdigo fuente completo de un sencillo programa de ejemplo que
utiliza cada una de las alternativas comentadas.
Localizacin geogrfica en Android (I)
Por sgoliver on 27/04/2011 en Android, Programacin
La localizacin geogrfica en Android es uno de esos servicios que, a pesar de requerir poco cdigo
para ponerlos en marcha, no son para nada intuitivos ni fciles de llegar a comprender por
completo. Y esto no es debido al diseo de la plataforma Android en s, sino a la propia naturaleza
de este tipo de servicios. Por un lado, existen multitud de formas de obtener la localizacin de un
dispositivo mvil, aunque la ms conocida y popular es la localizacin por GPS, tambin es posible
obtener la posicin de un dispositivo por ejemplo a travs de las antenas de telefona mvil o
mediante puntos de acceso Wi-Fi cercanos, y todos cada uno de estos mecanismos tiene una
precisin, velocidad y consumo de recursos distinto. Por otro lado, el modo de funcionamiento de
cada uno de estos mecanismos hace que su utilizacin desde nuestro cdigo no sea todo lo directa e
intuitiva que se deseara. Iremos comentando todo esto a lo largo del artculo, pero vayamos paso a
paso.
Lo primero que debe conocer una aplicacin que necesite obtener la localizacin geogrfica es qu
mecanismos de localizacin (proveedores de localizacin, o location providers) tiene disponibles en
el dispositivo. Como ya hemos comentado, los ms comunes sern el GPS y la localizacin
mediante la red de telefona, pero podran existir otros segn el tipo de dispositivo.
1LocationManager locManager
(LocationManager)getSystemService(LOCATION_SERVICE);
=
2List<String> listaProviders = locManager.getAllProviders();
Una vez obtenida la lista completa de proveedores disponibles podramos acceder a las propiedades
de cualquiera de ellos (precisin, coste, consumo de recursos, o si es capaz de obtener la altitud, la
velocidad, ). As, podemos obtener una referencia al provider mediante su nombre llamando al
mtodo getProvider(nombre) y posteriormente utilizar los mtodos disponibles para conocer
sus propiedades, por ejemplo getAccuracy() para saber su precisin (tenemos disponibles las
constantes Criteria.ACCURACY_FINE para precisin alta, y
Criteria.ACCURACY_COARSE para precisin media), supportsAltitude() para saber si
obtiene la altitud, o getPowerRequirement() para obtener el nivel de consumo de recursos
del proveedor. La lista completa de mtodos para obtener las caractersticas de un proveedor se
puede consultar en la documentacin oficial de la clase LocationProvider.
1LocationManager locManager =
2(LocationManager)getSystemService(LOCATION_SERVICE);
3List<String> listaProviders = locManager.getAllProviders();
4
5LocationProvider provider = locManager.getProvider(listaProviders.get(0));
int precision = provider.getAccuracy();
6boolean obtieneAltitud = provider.supportsAltitude();
7int consumoRecursos = provider.getPowerRequirement();
Al margen de esto, hay que tener en cuenta que la lista de proveedores devuelta por el mtodo
getAllProviders() contendr todos los proveedores de localizacin conocidos por el
dispositivo, incluso si stos no estn permitidos (segn los permisos de la aplicacin) o no estn
activados, por lo que esta informacin puede que no nos sea de mucha ayuda.
Android proporciona un mecanismo alternativo para obtener los proveedores que cumplen unos
determinados requisitos entre todos los disponibles. Para ello nos permite definir un criterio de
bsqueda, mediante un objeto de tipo Criteria, en el que podremos indicar las caractersticas
mnimas del proveedor que necesitamos utilizar (podis consultar la documentacin oficial de la
clase Criteria para saber todas las caractersticas que podemos definir). As, por ejemplo, para
buscar uno con precisin alta y que nos proporcione la altitud definiramos el siguiente criterio de
bsqueda:
Con esto, ya tenemos una forma de seleccionar en cada dispostivo aquel proveedor que mejor se
ajusta a nuestras necesidades.
En el cdigo anterior, verificamos si el GPS est activado y en caso negativo mostramos al usuario
un mensaje de advertencia. Este mensaje podramos mostralo sencillamente en forma de
notificacin de tipo toast, pero en el prximo artculo sobre localizacin veremos cmo podemos,
adems de informar de que el GPS est desactivado, invitar al usuario a activarlo dirigindolo
automticamente a la pantalla de configuracin del dispositivo.
Una vez que sabemos que nuestro proveedor de localizacin favorito est activado, ya estamos en
disposicin de intentar obtener nuestra localizacin actual. Y aqu es donde las cosas empiezan a ser
menos intuitivas. Para empezar, en Android no existe ningn mtodo del tipo
obtenerPosicinActual(). Obtener la posicin a travs de un dispositivo de localizacin como por
ejemplo el GPS no es una tarea inmediata, sino que puede requerir de un cierto tiempo de
procesamiento y de espera, por lo que no tendra sentido proporcionar un mtodo de ese tipo.
Entonces, de qu forma podemos obtener la posicin real actualizada? Pues la forma correcta de
proceder va a consistir en algo as como activar el proveedor de localizacin y suscribirnos a sus
notificaciones de cambio de posicin. O dicho de otra forma, vamos a suscribirnos al evento que se
lanza cada vez que un proveedor recibe nuevos datos sobre la localizacin actual. Y para ello,
vamos a darle previamente unas indicaciones (que no ordenes, ya veremos esto en el prximo
artculo) sobre cada cuanto tiempo o cada cuanta distacia recorrida necesitaramos tener una
actualizacin de la posicin.
Todo esto lo vamos a realizar mediante una llamada al mtodo requestLocationUpdates(),
al que deberemos pasar 4 parmetros distintos:
Tanto el tiempo como la distancia entre actualizaciones pueden pasarse con valor 0, lo que indicara
que ese criterio no se tendr en cuenta a la hora de decidir la frecuencia de actualizaciones. Si
ambos valores van a cero, las actualizaciones de posicin se recibirn tan pronto y tan
frecuentemente como estn disponibles. Adems, como ya hemos indicado, es importante
comprender que tanto el tiempo como la distancia especificadas se entendern como simples
indicaciones o pistas para el proveedor, por lo que puede que no se cumplan de forma estricta. En
el prximo artculo intentaremos ver esto con ms detalle para entenderlo mejor. Por ahora nos
basta con esta informacin.
En cuanto al listener, ste ser del tipo LocationListener y contendr una serie de mtodos
asociados a los distintos eventos que podemos recibir del proveedor:
Por nuestra parte, tendremos que implementar cada uno de estos mtodos para responder a los
eventos del proveedor, sobre todo al ms interesante, onLocationChanged(), que se ejecutar
cada vez que se recibe una nueva localizacin desde el proveedor. Veamos un ejemplo de cmo
implementar un listener de este tipo:
Como podis ver,, en nuestro caso de ejeemplo nos liimitamos a mostrar al usuario u la innformacin
recibidda en el eveento, bien seea un simplee cambio dee estado, o una u nueva posicin,
p en cuyo caso
llamammos al mtodo auxiliar mostrarPo
m osicion() ) para refrescar todos loos datos de lal posicin
en la pantalla.
p Parra este ejempplo hemos construido
c unna interfaz muy
m sencillaa, donde se muestran
m 3
datos de
d la posicin (latitud, loongitud y prrecisin) y un
u campo parra mostrar el estado del proveedor.
Ademms, se incluy yen dos botoones para coomenzar y detener
d la reccepcin de nuevas
n actuaalizaciones
de la posicin.
p Noo incluyo aqqu el cdigoo de la interffaz para no alargar
a ms el artculo, pero
p puede
consulltarse en el cdigo
c fuentte suministrrado al final del texto. ElE aspecto dee nuestra venntana es el
siguiennte:
En el mtodo mo ostrarPos sicion() nos vamos a limitar a mostrar loss distintos datos d de la
posicin pasada como
c parmetro en los controles coorrespondienntes de la innterfaz, utilizzando para
ello los
l mtodoss proporcioonados por la clase Location
L . En nuesttro caso uttilizaremos
getLa atitude() ), getAlt titude() y getAccu uracy() para
p obtenerr la latitud, longitud y
precisiin respectiv
vamente. Por supuesto, hay
h otros mtodos dispoonibles en estta clase paraa obtener la
altura,, orientacin
n, velocidad, etc que se pueden coonsultar en laa documentaacin oficial de la clase
Locatiion. Veamoss el cdigo:
1
2
3 private void comenzarLocalizacion()
{
4 //Obtenemos una referencia al LocationManager
5 locManager =
6 (LocationManager)getSystemService(Context.LOCATION_SERVICE);
7
8 //Obtenemos la ltima posicin conocida
Location loc =
9
locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
10
11 //Mostramos la ltima posicin conocida
12 mostrarPosicion(loc);
13
14 //Nos registramos para recibir actualizaciones de la posicin
15 locListener = new LocationListener() {
public void onLocationChanged(Location location) {
16 mostrarPosicion(location);
17 }
18
19 //Resto de mtodos del listener
20 //...
21 };
22
locManager.requestLocationUpdates(
23 LocationManager.GPS_PROVIDER, 30000, 0, locListener);
24}
25
26
Como se puede observar, al comenzar la recepcin de posiciones, mostramos en primer lugar la
ltima posicin conocida, y posteriormente solicitamos al GPS actualizaciones de poscin cada 30
segundos.
Por ltimo, nos quedara nicamente comentar cmo podemos detener la recepcin de nuevas
actualizaciones de posicin. Algo que es tan sencillo como llamar al mtodo removeUpdates()
del location manager. De esta forma, la implementacin del botn Desactivar sera tan sencilla
como esto:
1btnDesactivar.setOnClickListener(new OnClickListener() {
2 @Override
3 public void onClick(View v) {
4 locManager.removeUpdates(locListener);
5 }
});
6
Con esto habramos concluido nuestra aplicacin de ejemplo. Sin embargo, si descargis el cdigo
completo del artculo y ejecutis la aplicacin en el emulador veris que, a pesar funcionar todo
correctamente, slo recibiris una lectura de la posicin (incluso puede que ninguna). Esto es
debido a que la ejecucin y prueba de aplicaciones de este tipo en el emulador de Android, al no
tratarse de un dispositivo real y no estar disponible un receptor GPS, requiere de una serie de pasos
adicionales para simular cambios en la posicin del dispositivo.
Todo esto, adems de algunas aclaraciones que nos han quedado pendientes en esta primera entrega
sobre localizacin, lo veremos en el prximo artculo. Por ahora os dejo el cdigo fuente completo
para que podis hacer vuestras propias pruebas.
En el artculo anterior del curso de programacin en Android comentamos los pasos bsicos
necesarios para construir aplicaciones que accedan a la posicin geogrfica del dispositivo. Ya
comentamos algunas particularidades de este servicio de la API de Android, pero dejamos en el
tintero algunas aclaraciones ms detalladas y un tema importante y fundamental, como es la
depuracin de este tipo de aplicaciones que manejan datos de localizacin. En este nuevo artculo
intentar abarcar estos temas y dejar para un tercer artculo algunas otras caractersticas ms
avanzadas de los servicios de localizacin.
Como base para este artculo voy a utilizar la misma aplicacin de ejemplo que construimos en la
anterior entrega, haciendo tan slo unas pequeas modificaciones:
En nuestro caso de ejemplo slo vamos a generar mensajes de log cuando ocurran dos
ciscunstancias:
Pues bien,
b para hacer
h esto tenemos variaas opciones. La primeraa de ellas, y la ms senncilla, es el
envo manual de una nueva posicin al a emulador de Androidd, para sim mular que sste hubiera
cambiado su locaalizacin. Essto se puedee realizar deesde la persspectiva de DDMS, en la pestaa
Emulaator Controll, donde poddemos enconntrar una secccin llamadda Location Controls, mostrada
m en
la imaagen siguientte (click paraa ampliar):
Con estos
e controles podemoos enviar dee forma maanual al emuulador en ejecucin
e unnas nuevas
coordeenadas de posicin,
p parra simular que
q stas se hubieran reecibido a traavs del prooveedor de
localizzacin utilizzado. De estta forma, sii introducim
mos unas cooordenadas de
d longitud y latitud y
pulsammos el botn n Send mienntras nuestraa aplicacin se ejecuta en el emulaador, esto prrovocar la
ejecuccin del eveento onLocationChanged() y por consiguuiente se moostrarn estoos mismos
datos en sus conttroles corresspondientes de la interfaz, como vemosv en laa siguiente captura de
pantalla:
Por elllo, Android proporciona otro mtoddo algo mennos manual de simular cambios freecuentes de
posicin para probar nuestrass aplicacionees. Este mttodo consistte en proporrcionar, en vez
v de una
sla coordenada
c cada vez, una
u lista de coordenadaas que se irran envianddo automticcamente al
emulaador una traas otra a unna determinnada velociddad, de form ma que poddamos simuular que el
dispossitivo se mueve constantemente y queq nuestra aplicacin
a reesponde de forma correcta y en el
momeento adecuad do a esos cammbios de poosicin. Y essta lista de coordenadas
c se puede prroporcionar
de dos formas diistintas, en formato GP PX o como fichero KM ML. Amboss tipos de fichero
f son
ampliaamente utilizzados por applicaciones y dispositivoos de localizzacin, comoo GPS, apliccaciones de
cartoggrafa y map pas, etc. Loos ficheros KML podeemos generaarlos por ejemplo a traavs de la
aplicaccin Googlee Earth o maanualmente con cualquiier editor dee texto, peroo recomiendoo consultar
los doos enlaces an
nteriores para obtener ms
m informacin sobre caada formato.. Para este ejemplo,
ej yo
he gennerado un ficchero KML ded muestra con
c una listaa de 1000 posiciones geoogrficas al azar.
a
Para utilizar
u este fichero
f comoo fuente de datos para simular
s cambbios en la poosicin del dispositivo,
d
accedeemos nuevam mente a los Location
L Coontrols y pullsamos sobree la pestaa GPX o KML L, segn el
formatto que hayamos elegidoo, que en nuuestro caso ser s KML. Pulsamos
P el botn Loaad KML
para seeleccionar nuestro
n ficherro y veremos la lista de coordenadas
c s como en laa siguiente im
magen:
Una vez cargado el
e fichero, teendremos dissponibles loss cuatro botoones inferiorres para (de izquierda
i a
derechha):
1 05-08 10:50:3
37.921: INFO/LocAndr
roid(251): 7.0 - -11
1.9999983333333334
05-08 10:50:3
38.041: INFO/LocAndr
roid(251): Provider Status: 2
2 05-08 10:5
50:38.901: INFO/L
LocAndroid
d(251): 7.000001666666666 - -
3 11.9999966666
666668
4 05-08 10:5
50:39.941: INFO/L
LocAndroid
d(251): 7.000001666666666 - -
5 11.9999966666
666668
6 05-08 10:5
50:41.011: INFO/L
LocAndroid
d(251): 7.000003333333333 - -
11.9999950000
000002
7 05-08 10:5
50:43.011: INFO/L
LocAndroid
d(251): 7.000005000000001 - -
8 11.9999933333
333334
9 05-08 10:50:45.001: INFO/LocAndroid(251): 7.000006666666667 - -
10 11.999991666666665
05-08 10:50:46.061: INFO/LocAndroid(251): 7.000008333333333 - -
1111.999989999999999
1205-08 10:50:47.131: INFO/LocAndroid(251): 7.000008333333333 - -
1311.999989999999999
1405-08 10:50:47.182: INFO/LocAndroid(251): Provider Status: 1
05-08 10:51:02.232: INFO/LocAndroid(251): 7.000023333333333 - -11.999975
1505-08 10:51:02.812: INFO/LocAndroid(251): 7.000023333333333 - -
1611.999973333333333
1705-08 10:51:02.872: INFO/LocAndroid(251): Provider Status: 2
1805-08 10:51:03.872: INFO/LocAndroid(251): 7.000024999999999 - -
1911.999973333333333
05-08 10:51:04.912: INFO/LocAndroid(251): 7.000026666666668 - -
2011.999971666666665
2105-08 10:51:05.922: INFO/LocAndroid(251): 7.000026666666668 - -
2211.999971666666665
2305-08 10:51:06.982: INFO/LocAndroid(251): 7.000028333333334 - -11.99997
2405-08 10:51:08.032:
11.999968333333333
INFO/LocAndroid(251): 7.000028333333334 - -
2505-08 10:51:09.062: INFO/LocAndroid(251): 7.00003 - -11.999968333333333
2605-08 10:51:10.132: INFO/LocAndroid(251): 7.000031666666667 - -
2711.999966666666667
2805-08 10:51:12.242: INFO/LocAndroid(251): 7.000033333333333 - -
11.999965000000001
2905-08 10:51:13.292: INFO/LocAndroid(251): 7.000033333333333 - -
3011.999963333333335
3105-08 10:51:13.342: INFO/LocAndroid(251): Provider Status: 1
3205-08 10:51:28.372: INFO/LocAndroid(251): 7.000048333333333 - -
11.999950000000002
33
05-08 10:51:28.982: INFO/LocAndroid(251): 7.000048333333333 - -
3411.999950000000002
3505-08 10:51:29.032: INFO/LocAndroid(251): Provider Status: 2
05-08 10:51:30.002: INFO/LocAndroid(251): 7.000050000000001 - -
11.999948333333334
05-08 10:51:31.002: INFO/LocAndroid(251): 7.000051666666667 - -
11.999946666666665
05-08 10:51:33.111: INFO/LocAndroid(251): 7.000053333333333 - -
11.999944999999999
05-08 10:51:34.151: INFO/LocAndroid(251): 7.000053333333333 - -
11.999944999999999
05-08 10:51:35.201: INFO/LocAndroid(251): 7.000055 - -11.999943333333333
05-08 10:51:36.251: INFO/LocAndroid(251): 7.0000566666666675 - -
11.999941666666667
05-08 10:51:37.311: INFO/LocAndroid(251): 7.0000566666666675 - -
11.999941666666667
05-08 10:51:38.361: INFO/LocAndroid(251): 7.0000583333333335 - -11.99994
05-08 10:51:38.431: INFO/LocAndroid(251): Provider Status: 1
Estudiemos un poco este log. Si observamos las marcas de fecha hora vemos varias cosas:
Un primer grupo de actualizaciones entre las 10:50:37 y las 10:50:47, con 8 lecturas.
Un segundo grupo de actualizaciones entre las 10:51:02 y las 10:51:13, con 11 lecturas.
Un tercer grupo de actualizaciones entre las 10:51:28 y las 10:51:38, con 10 lecturas.
Entre cada grupo de lecturas transcurren aproximadamente 15 segundos.
Los grupos estn formados por un nmero variable de lecturas.
Por tanto ya podemos sacar algunas conclusiones. Indicar al location listener una frecuencia de 15
segundos entre actualizaciones no quiere decir que vayamos a tener una sola lectura cada 15
segundos, sino que al menos tendremos una nueva con dicha frecuencia. Sin embargo, como
podemos comprobar en los logs, las lecturas se recibirn por grupos separados entre s por el
intervalo de tiempo indicado.
Ms conclusiones, ahora sobre el estado del proveedor de localizacin. Si buscamos en el log los
momentos donde cambia el estado del proveedor vemos dos cosas importantes:
Estos cambios en el estado de los proveedores de localizacin pueden ayudarnos a realizar diversas
tareas. Un ejemplo tpico es utilizar el cambio de estado a 1 (es decir, cuando se ha terminado de
recibir un grupo de lecturas) para seleccionar la lectura ms precisa del grupo recibido, algo
especialmente importante cuando se estn utilizando varios proveedores de localizacin
simultneamente, cada uno con una precisin distinta.
A modo de resumen, en este artculo hemos visto cmo podemos utilizar las distintas herramientas
que proporciona la plataforma Android y el entorno de desarrollo Eclipse para simular cambios de
posicin del dispositivo durante la ejecucin de nuestras aplicaciones en el emulador. Tambi
hemos visto cmo la generacin de mensajes de log pueden ayudarnos a depurar este tipo de
aplicaciones, y finalmente hemos utilizado esta herramienta de depuracin para entender mejor el
funcionamiento de los location listener y el comportamiento de los proveedores de localizacin.
Podes descargar el cdigo fuente utilizado en este artculo desde este enlace.
Siguiendo con nuestro Curso de Programacin en Android, tras haber hablado en los dos ltimos
artculos sobre localizacin geogrfica mediante GPS (aqu y aqu), en este nuevo artculo vamos a
empezar a hablar de un complemento ideal para este tipo de servicios y aplicaciones, como es la
utilizacin de mapas en nuestras aplicaciones haciendo uso de la API Android de Google Maps.
Antes de empezar a utilizar el servicio de mapas de Google es necesario realizar algunas tareas
previas. En primer lugar, debemos asegurarnos de que tenemos instalado el paquete correspondiente
a la versin de Android para la que desarrollamos enriquecido con las APIs de Google. Estos
paquetes se llaman normalmente Google APIs by Google, Android API x, revisin y. Esto
podemos comprobarlo y descargarlo si es necesario desde Eclipse accediendo al Android SDK and
AVD Manager. En mi caso, utilizar el paquete correspondiente a Android 2.1 (API 7) + APIs de
Google:
Para poder
p probarr nuestras aplicaciones
a en el emullador tambin tendremoos que crearr un nuevo
e paquete como targett:
AVD que utilice este
Y porr supuesto, al
a crear nuesstro proyectto de Eclipse tambin teendremos quue seleccionnarlo como
target en las propiiedades:
Con toodo esto ya tendramos
t c
creado nuesttro proyecto de Eclipse y estaramos preparados para poder
ejecutaar aplicacion mulador sobrre la versinn correcta dee Android y las APIs neccesarias de
nes en el em
Googlle. Por tanto,, ya podemos centrarnoss en la utilizaacin de dichhas APIs.
Esto ltimo
tambiin requiere algunos passos previos. Para poder utilizar
u la API
A de Google Maps se
requieere la obtencin previa de
d una clave de uso (APII Key), que estar
e asociada al certificcado con el
que firrmamos digiitalmente nuuestra aplicaccin. Esto quuiere decir que
q si cambiaamos el certiificado con
el quee firmamos nuestra applicacin (allgo que se hace norm malmente como paso previo a la
publiccacin de la aplicacin en
e el Markeet) tendremoos que cambbiar tambinn la clave dee uso de la
API.
Para ello,
e en prim
mer lugar deebemos locallizar el fichhero donde se
s almacenann los datos de nuestro
certificcado de dep
puracin, llaamado debug.keystore. Podem mos saber la ruta de este fichero
accediiendo a las preferencias
p de Eclipse, seccin Anddroid, apartaado Build (m
mostrado en la
l siguiente
imagen), y copiar la ruta que aparece
a en ell campo Deefault Debugg Keystore:
Una vez
v conocem mos la localiizacin del fichero
f debuug.keystore, vamos a acccder a l mediante
m la
herram
mienta keyt tool.exe de java parra obtener el hash MD55 del certificcado. Esto lo l haremos
desde la linea de comandos
c dee Windows mediante
m el siguiente
s com
mando (clickk para ampliiar):
Copiarremos el datto que aparece identificaado como H Huella digitaal de certificaado (MD5) y con ste
accedeeremos a laa web de Google (htttp://code.gooogle.com/anndroid/maps--api-signup.hhtml) para
solicittar una clavee de uso de la
l API de Google Maps para depuraar nuestras aplicaciones
a (Insisto, si
posterriormente vaamos a publlicar nuestraa aplicacin en el Markket deberem mos solicitar otra clave
q utilicemos). Dicha web
asociaada al certifiicado real que w nos sollicitar la marcam MD5 de nuestro
certificcado y nos proporcionar
p r la clave dee uso de la API,
A como see muestra enn la siguientee imagen:
Con esto, terminamos con todos los pasos previos para la utilizacin de los servicios de Google
Maps dentro de nuestras aplicaciones Android.
Una vez contamos con la clave de uso de la API, la inclusin de mapas en nuestra aplicacin es una
tarea relativamente sencilla y directa. En este primer artculo sobre mapas nos vamos a limitar a
mostrar el mapa en la pantalla inicial de la aplicacin, habilitaremos su manipulacin
(desplazamientos y zoom), y veremos cmo centrarlo en una coordenadas concretas. En prximos
artculos aprenderemos a manipular de forma dinmica estos mapas, a mostrar marcas sobre ellos, y
a realizar otras operaciones ms avanzadas.
Para incluir un mapa de Google Maps en una ventana de nuestra aplicacin utilizaremos el control
MapView. Estos controles se pueden aadir al layout de la ventana como cualquier otro control
visto hasta el momento, tan slo teniendo en cuenta que tendremos que indicar la clave de uso de
Google Maps en su propiedad android:apiKey como se muestra a cotinuacin:
<com.google.android.maps.MapView
android:id="@+id/mapa"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:apiKey="0ss-5q6s3FKYkkp3atMUH..."
android:clickable="true" />
</LinearLayout>
Este tipo de controles tienen la particularidad de que slo pueden ser aadidos a una actividad de
tipo MapActivity, por lo que pantalla de la aplicacin en la que queramos incluir el mapa debe
heredar de esta clase. As, para nuestro caso de ejemplo, vamos a hacer que la clase principal herede
de MapActivity, como vemos en el siguiente cdigo:
package net.sgoliver.android;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import android.os.Bundle;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
@Override
protected boolean isRouteDisplayed() {
return false;
}
}
Para ejecutar la aplicacin de ejemplo sobre el emulador de Android an nos queda un paso ms,
modificar el fichero AndroidManifest.xml. Por un lado, tendremos que especificar que vamos a
hacer uso de la API de Google Maps (mediante la clusula <uses-library>), y en segundo
lugar necesitaremos habilitar los permisos necesarios para acceder a Internet (mediante la clusula
<uses-permission>). En el siguiente fragmento de cdigo vemos cmo quedara nuestro
AndroidManifest tras aadir estas dos lineas:
<application
android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".AndroidMapas"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:
:name="android.inten
nt.category
y.LAUNCHER
R" />
</i
intent-filter>
</activ
vity>
<
</applicati
ion>
<
<uses-permi
ission and
droid:name=
="android.permission
n.INTERNET"
" />
</man
nifest>
Tras estos dos cambios yaa podemos ejecutar ell proyecto de Eclipsee sobre el emulador.
Comprobaremos cmo podeemos moveernos por el e mapa y hacer zoom con los controles
correspondientes:
Podiss descargar el
e cdigo fueente del artcculo desde esste enlace.
Maapas en And
droid (II):
( C
Contro
ol Map
pView
Por sggoliver on 27
7/05/2011 enn Android, Programacinn
setSatellite(true)
setStreetView(true)
setTraffic(true)
Por supuesto existen tambin otros tres mtodos para consultar el estado de cada uno de estos
modos: isSatellite(), isStreetView() e isTraffic().
En este artculo voy a continuar ampliando la aplicacin de ejemplo que construimos en el artculo
anterior, al que aadiremos varios botones para realizar diferentes acciones. As, por ejemplo vamos
a incluir un botn para alternar en nuestro mapa entre vista normal y vista de satlite.
1
2 private Button btnSatelite = null;
3 //...
4 btnSatelite = (Button)findViewById(R.id.BtnSatelite);
//...
5 btnSatelite.setOnClickListener(new OnClickListener() {
6 @Override
7 public void onClick(View arg0) {
8 if(mapa.isSatellite())
mapa.setSatellite(false);
9 else
10 mapa.setSatellite(true);
11 }
12});
13
Si ejecutamos la aplicacin en este momento y probamos el nuevo botn veremos cmo se visualiza
sin problemas nuestro mapa en vista de satlite:
Ademms de la elecccin del tipo de vista dee mapa a moostrar en el control,
c tambbin podemoos elegir si
querem
mos mostraar controless de zoom m sobre el mapa. Paara ello llaamaremos al a mtodo
setBuuiltInZoo omControls() indiccando si queeremos que se s muestrenn o no los coontroles de
zoom:
1//Moostramos lo
os controles de zoomm sobre el mapa
2 mapa
a.setBuiltI
InZoomCont rols(true)
);
Al margen
m de las
l propieddades para personalizaar el aspeccto grfico del controol tambin
disponndremos de varios
v mtoddos para connsultar la infformacin geeogrfica vissualizada enn el mismo.
As,poor ejemplo, podremos
p saaber las coorrdenadas geoogrficas en las que est centrado actualmente
el maapa [mtod do getMap pCenter()] y el nivel de zooom que esst aplicadoo [mtodo
getZo oomLevel( ()].
1
//Ob
btenemos el
l centro del
d mapa
2GeoPPoint loc = mapa.getMapCenter(
();
3
4//Laatitud y Lo
ongitud (en microgra
ados)
5int lat
l = loc.getLatitud
deE6();
6int lon
l = loc.getLongitu
udeE6();
7
//Nivel de zoo
om actual
8int zoom
z = mapa.getZoomL
Level();
9
Sin embargo, si miramos la documentacin de la API de la clase MapView veremos que no existen
mtodos para modificar estos valores de centro y zoom del mapa. Cmo podemos entonces
modificar la localizacin mostrada en el mapa? Pues bien, para esto habr que acceder en primer
lugar al controlador del mapa, algo que conseguimos mediante el mtodo getController().
Este mtodo nos devolver un objeto MapController con el que s podremos modificar los
datos indicados. As, contaremos con los mtodos setCenter() y setZoom() al que podremos
indicar las coordenadas centrales del mapa y el nivel de zoom deseado.
Vamos a incluir en la aplicacin de ejemplo un nuevo botn para centrar el mapa sobre un punto
determinado (Sevilla) y aplicaremos un nivel de zoom (10) que nos permita distinguir algo de
detalle.
1
2 private Button btnCentrar = null;
3 private MapController controlMapa = null;
4 //...
5 btnCentrar = (Button)findViewById(R.id.BtnCentrar);
6 //...
controlMapa = mapa.getController();
7 //...
8 btnCentrar.setOnClickListener(new OnClickListener() {
9 @Override
10 public void onClick(View arg0) {
Double latitud = 37.40*1E6;
11
Double longitud = -5.99*1E6;
12
13 GeoPoint loc =
14 new GeoPoint(latitud.intValue(), longitud.intValue());
15
16 controlMapa.setCenter(loc);
17 controlMapa.setZoom(10);
}
18});
19
20
Como vemos, para establecer el punto central del mapa construiremos un objeto GeoPoint a
partir de los valores de latitud y longitud y lo pasaremos como parmetro al mtodo
setCenter().
Si ejeccutis la apliicacin en ell emulador podris
p compprobar que funciona
fu perffectamente de
d la forma
esperaada, sin emb bargo, el despplazamientoo y zoom a la
l posicin y nivel indiccados se hace de forma
instanttanea, sin ningn tipo dee animacin..
Para resolver
r estoo, la API noss ofrece otraa serie de mtodos
m que nos
n permitirrn desplazaarnos a una
posicin especficca de forma progresiva
p y a subir o bajar
b el nivell de zoom dee forma aniimada, tal
como se realizaa al pulsar los botonnes de zooom del proopio mapa. Estos mtodos son
anima ateTo(Geo oPoint), que despplazar el mapa hassta un puunto determ minado, y
zoomI In()/zoom mOut() quee aumentar o disminuir en 1 el nivvel de zoom de d forma proogresiva.
Por ltimo, disponemos de otro mtodo que en ocasiones puede ser interesante y que nos permitir
desplazar el mapa, no a una posicin especfica, sino un determinado nmero de pixeles en una u
otra direccin, al igual que conseguiramos mediante gestos con el dedo sobre el mapa. Este mtodo
se llama scrollBy() y recibe como parmetros el nmero de pixeles que queremos desplazarnos
en horizontal y en vertical.
As, por ejemplo, podemos aadir un nuevo botn a la aplicacin de ejemplo, que desplace el mapa
40 pxeles hacia la derecha y hacia abajo, de la siguiente forma:
1
2 private Button btnMover = null;
//...
3 btnMover = (Button)findViewById(R.id.BtnMover);
4 //...
5 btnMover.setOnClickListener(new OnClickListener() {
6 @Override
7 public void onClick(View arg0) {
controlMapa.scrollBy(40, 40);
8 }
9 });
10
En este artculo hemos aprendido a manipular desde nuestro cdigo un control MapView, tanto a
travs de sus propiedades como de las acciones disponibles desde su controlador. En el prximo
artculo veremos cmo podemos aadir capas a nuestro mapa de forma que podamos dibujar sobre
l por ejemplo marcas de posicin, o responder eventos de pulsacin sobre el control.
Seguimos con nuestro Curso de Programacin Android. En los dos artculos anteriores dedicados a
la visualizacin y manipulacin de mapas en nuestras aplicaciones Android (parte I, parte II) ya
vimos cmo mostrar de forma bsica un mapa y cmo manipularlo desde nuestro cdigo para
realizar las acciones ms frecuentes, como por ejemplo centrarlo o desplazarlo a una posicin
determinada o establecer el nivel de zoom. Nos qued pendiente comentar cmo podemos aadir
nuestra propia informacin a un mapa y cmo podemos responder a eventos de pulsacin sobre el
control. En este artculo nos ocuparemos de ambas cosas.
Empecemos por el principio. Para incluir informacin personalizada sobre un control MapView
necesitaremos aadir sobre ste nuevas capas (overlays), donde dibujaremos toda la informacin
adicional. Se puede incluir cualquier tipo de informacin en estas nuevas capas, por ejemplo
indicaciones de ruta, marcadores, notas de texto
El primer paso para definir una nueva capa de informacin ser crear una clase java que derive de la
clase Overlay. En esta nueva clase sobrescribiremos el mtodo draw(), que es el lugar donde
deberemos dibujar toda la informacin a incluir sobre el mapa en esta capa (por supuesto podemos
aadir varias capas al mapa).
Por mostrar un ejemplo vamos a seguir trabajando con la misma apliacin de ejemplo del artculo
anterior, al que aadiremos algn marcador sobre el mapa. Para no complicar mucho el ejemplo,
aadiremos tan slo un marcador sobre unas coordenadas fijas (las mismas coordenadas sobre las
que aparece centrado el mapa cuando se inicia la aplicacin).
El mtodo draw() recibe como parmetro un objeto Canvas sobre el que podemos dibujar
directamente utilizando los mtodos de dibujo que ya hemos utilizado en otras ocasiones
(drawLine(), drawCircle(), drawText(), drawBitmap()). Sin embargo, a todos
estos mtodos de dibujo hay que indicarles las coordenadas en pixels relativos a los bordes del
control sobre el que se va a dibujar. Sin embargo, nosotros lo que tenemos en principio son unas
coordenadas de latitud y longitud expresadas en grados. Cmo podemos traducir entre unas
unidades y otras para saber dnde dibujar nuestros marcadores? Pues bien, para solucionar esto
Android nos proporciona la clase Projection, con la cual podremos hacer conversiones precisas
entre ambos sistemas de referencia.
Veamos lo sencillo que es utilizar esta clase Projection. Partiremos de nuestros valores de
latitud y longitud expresados en grados y a partir de ellos crearemos un objeto GeoPoint que los
encapsule. Por otro lado, crearemos un nuevo objeto Projection mediante el mtod
getProjection() de la clase MapView (objeto recibido tambin como parmetro del mtodo
draw()). Este nuevo objeto Projection creado tendr en cuenta la posicin sobre la que est
centrada el mapa en este momento y el nivel de zoom aplicado para convertir convenientemente
entre latitud-longitud en grados y coordenadas x-y en pixeles. Para hacer la conversin, llamaremos
al mtodo toPixels() que devolver el resultado sobre un objeto Point de salida.
1
Double latitud = 37.40*1E6;
2Double longitud = -5.99*1E6;
3
4Projection projection = mapView.getProjection();
5GeoPoint geoPoint =
6 new GeoPoint(latitud.intValue(), longitud.intValue());
7
Point centro = new Point();
8projection.toPixels(geoPoint, centro);
9
Una vez que tenemos las coordenadas convertidas a pixeles, ya podemos dibujar sobre ellas
utilizando cualquier mtodo de dibujo. Veamos en primer lugar cmo podramos por ejemplo aadir
un crculo y una etiqueta de texto sobre las coordenadas calculadas:
Para que nadie se pierda, veamos el cdigo completo de nuestra clase derivada de Overlay (en un
alarde de creatividad la he llamado OverlayMapa) hasta el momento:
1
2
3 public class OverlayMapa extends Overlay {
private Double latitud = 37.40*1E6;
4 private Double longitud = -5.99*1E6;
5
6 @Override
7 public void draw(Canvas canvas, MapView mapView, boolean shadow)
8 {
9 Projection projection = mapView.getProjection();
GeoPoint geoPoint =
10 new GeoPoint(latitud.intValue(), longitud.intValue());
11
12 if (shadow == false)
13 {
14 Point centro = new Point();
projection.toPixels(geoPoint, centro);
15
16
//Definimos el pincel de dibujo
17 Paint p = new Paint();
18 p.setColor(Color.BLUE);
19
20 //Marca Ejemplo 1: Crculo y Texto
21 canvas.drawCircle(centro.x, centro.y, 5, p);
canvas.drawText("Sevilla", centro.x+10, centro.y+5, p);
22 }
23 }
24}
25
26
Una vez definida nuestra capa de informacin personalizada debemos aadirla al mapa que se va a
mostrar en la aplicacin de ejemplo. En nuestro caso esto lo haremos desde el mtodo
onCreate() de la actividad principal, tras obtener la referencia al control MapView. Para ello,
obtendremos la lista de capas del mapa mediante el mtodo getOverlays(), crearemos una
nueva instancia de nuestra capa personalizada OverlayMapa, y la aadiremos a la lista mediante
el mtodo add(). Finalmente llamaremos al mtodo postInvalidate() para redibujar el
mapa y todas sus capas.
Una posible
p variaante podra ser
s incluir allgn tipo de marcador grfico
g sobree el mapa, al
a estilo del
propioo Google Maps.
M Para conseguir
c essto, deberemmos colocarr la imagenn del marcaddor en las
distinttas carpetas /res/drawabble y dibujaarlo sobre laa capa utilizaando el mtoodo drawBi itmap().
Para cargar
c el bitm
map a partir del fichero de
d imagen colocado
c en lal carpeta dee recursos uttilizaremos
la classe BitmapF Factory y su mtodo decodeRes
d source(),, al que tenddremos que pasarp como
parmmetro el ID del d recurso. Veamos cmo quedara esto en el e cdigo deel mtodo draw() de
nuestrra capa perssonalizada [ppara mayor claridad, ms m abajo podis descaargar como siempre el
cdigoo fuente com mpleto]:
1//Ma
arca Ejempl
lo 2: Bitm
map
2Bitm
map bm = Bi
itmapFactory.decodeR
Resource(
3 mapVie
ew.getResources(),
4 R.draw
wable.marcador_googl
le_maps);
5
6canv
vas.drawBit
tmap(bm, centro.x - bm.getWidt
th(),
o.y - bm.getHeight()
centro ), p);
7
En mii caso, como milar al que se muestra en Google
o imagen paara el marcaddor he utilizzado una sim
Maps. Si ejecutam
mos ahora la aplicacin, veremos cmo hemos sustituido
s la marca textuual anterior
por el nuevo marccador grficoo:
Para conseguir
c essto, tendrem
mos que sobbrescribir tam mbin el mtodo onTa ap() de nuuestra capa
personnalizada. Esste mtodo nos proporcciona directaamente com mo parmetrro las coorddenadas de
latitudd y longitud sobre las quue el usuarioo ha pulsadoo (en forma de
d objeto Ge
eoPoint), con lo que
podrem mos actuar en
e consecuenncia desde nuuestra aplicaacin.
1
2 @Ovverride
pub
blic boolean onTap(GeoPoint poiint, MapView mapVieww)
3 {
4 Context contexto
c = mapView.ggetContext();
5 sg = "Lat: " + point.getLatitu
String ms udeE6()/1E66 + " - "+
6 "Lon:
: " + point
t.getLongi tudeE6()/1
1E6;
7
ast = Toast.makeText
Toast toa t(contexto, msg, Toaast.LENGTH_
_SHORT);
8
toast.sho
ow();
9
10 return true;
11}
12
Podiss descargar el
e cdigo fueente completto de este arttculo pulsanndo este enlaace.
Con esto
e terminaamos por el momento la l serie de artculos
a deddicados a laa geolocalizzacin y la
visualiizacin de mapas
m en nueestras aplicaaciones Andrroid, aunquee no descartoo dedicar algguno ms a
este teema un poco o ms adelannte. Como siempre
s estooy abierto a propuestas sobre el conntenido del
curso, podis utilizar los comeentarios del blog o mi diireccin de correo
c electrrnico para hacrmelas
h
llegar..
Content Proviiders en
e And
droid (I):
Construcccin
Por sggoliver on 28
8/08/2011 enn Android, Programacinn
En estte nuevo arttculo del Curso de Proogramacin en e Android que estamoos publicanddo vamos a
tratar el
e temido [o a veces incoomprendido]] tema de loss Content Prroviders.
Un Coontent Proviider no es ms
m que el mecanismo
m p
proporcionad do por la plaataforma Anndroid para
permittir compartirr informacin entre apliccaciones. Unna aplicacin que desee que todo o parte de la
inform
macin que almacena
a estt disponiblee de una form
ma controladda para el reesto de aplicaaciones del
sistem
ma deber prooporcionar un
u content prrovider a travvs del cul se pueda reaalizar el acceeso a dicha
inform
macin. Estee mecanism mo es utilizado por muuchas de laas aplicacioones estandaard de un
dispositivo Android, como por ejemplo la lista de contactos, la aplicacin de SMS,
o el calendario/agenda. Esto quiere decir que podramos acceder a los datos gestionados por estas
aplicaciones desde nuestras propias aplicaciones Android haciendo uso de los content providers
correspondientes.
Son por tanto dos temas los que debemos tratar en este apartado, por un lado a construir nuevos
content providers personalizados para nuestras aplicaciones, y por otro utilizar un content provider
ya existente para acceder a los datos publicados por otras aplicaciones.
En gran parte de la bibliografa sobre programacin en Android se suele tratar primero el tema del
acceso a content providers ya existentes (como por ejemplo el acceso a la lista de contactos de
Android) para despus pasar a la construccin de nuevos content providers personalizados. Yo sin
embargo voy a tratar de hacerlo en orden inverso, ya que me parece importante conocer un poco el
funcionamiento interno de un content provider antes de pasar a utilizarlo sin ms dentro de nuestras
aplicaciones. As, en este primer artculo sobre el tema veremos cmo crear nuestro propio content
provider para compartir datos con otras aplicaciones, y en el prximo artculo veremos como
utilizar este mecanismo para acceder directamente a datos de terceros.
Empecemos a entrar en materia. Para aadir un content provider a nuestra aplicacin tendremos
que:
Por supuesto nuestra aplicacin tendr que contar previamente con algn mtodo de
almacenamiento interno para la informacin que queremos compartir. Lo ms comn ser disponer
de una base de datos SQLite, por lo que ser esto lo que utilizar para todos los ejemplos de este
artculo, pero internamente podramos tener los datos almacenados de cualquier otra forma, por
ejemplo en ficheros de texto, ficheros XML, etc. El content provider sera el mecanismo que nos
permita publicar esos datos a terceros de una forma homogenea y a travs de una interfaz
estandarizada.
Un primer detalle a tener en cuenta es que los registros de datos proporcionados por un content
provider deben contar siempre con un campo llamado _ID que los identifique de forma unvoca del
resto de registros. Como ejemplo, los registros devueltos por un content provider de clientes podra
tener este aspecto:
Sabiendo esto, es interesante que nuestros datos tambin cuenten internamente con este campo _ID
(no tiene por qu llamarse igual) de forma que nos sea ms sencillo despus generar los resultados
del content provider.
Con todo esto, y para tener algo desde lo que partir, vamos a construir en primer lugar una
aplicacin de ejemplo muy sencilla con una base de datos SQLite que almacene los datos de una
serie de clientes con una estructura similar a la tabla anterior. Para ello seguiremos los mismos
pasos que ya comentamos en los artculos dedicados al tratamiento de bases de datos SQLite en
Android (consultar ndice del curso).
Por volver a recordarlo muy brevemente, lo que haremos ser crear una nueva clase que extienda a
SQLiteOpenHelper, definiremos las sentencias SQL para crear nuestra tabla de clientes, e
implementaremos finalmente los mtodos onCreate() y onUpgrade(). El cdigo de esta
nueva clase, que yo he llamado ClientesSqliteHelper, quedara como sigue:
Ntese el campo _id que hemos incluido en la base de datos de clientes por lo motivos
indicados un poco ms arriba. Este campo lo declaramos como INTEGER PRIMARY KEY
AUTOINCREMENT, de forma que se incremente automticamente cada vez que insertamos
un nuevo registro en la base de datos.
En el mtodo onCreate(), adems de ejecutar la sentencia SQL para crear la tabla
Clientes, tambin inserta varios registros de ejemplo.
Para simplificar el ejemplo, el mtodo onUpgrade() se limita a eliminar la tabla actual y
crear una nueva con la nueva estructura. En una aplicacin real habra que hacer
probblemente la migracin de los datos a la nueva base de datos.
Dado que la clase anterior ya se ocupa de todo, incluso de insertar algunos registro de ejemplo con
los que podamos hacer pruebas, la aplicacin principal de ejemplo no mostrar en principio nada en
pantalla ni har nada con la informacin. Esto lo he decidido as para no complicar el cdigo de la
aplicacin innecesariamente, ya que no nos va a interesar el tratamiento directo de los datos por
parte de la aplicacin principal, sino su utilizacin a travs del content provider que vamos a
construir.
Una vez que ya contamos con nuestra aplicacin de ejemplo y su base de datos, es hora de empezar
a construir el nuevo content provider que nos permitir compartir sus datos con otras aplicaciones.
Lo primero que vamos a comentar es la forma con que se hace referencia en Android a los content
providers. El acceso a un content provider se realiza siempre mediante una URI. Una URI no es ms
que una cadena de texto similar a cualquiera de las direcciones web que utilizamos en nuestro
navegador. Al igual que para acceder a mi blog lo hacemos mediante la direccin
http://www.sgoliver.net, para acceder a un content provider utilizaremos una direccin similar a
content://net.sgoliver.android.ejemplo/clientes.
Las direcciones URI de los content providers estn formadas por 3 partes. En primer lugar el prefijo
content:// que indica que dicho recurso deber ser tratado por un content provider. En segundo
lugar se indica el identificador en s del content provider, tambin llamado authority. Dado que este
dato debe ser nico es una buena prctica utilizar un authority de tipo nombre de clase java
invertido, por ejemplo en mi caso net.sgoliver.android.ejemplo. Por ltimo, se indica la entidad
concreta a la que queremos acceder dentro de los datos que proporciona el content provider. En
nuestro caso ser simplemente la tabla de clientes, ya que ser la nica existente, pero dado que
un content provider puede contener los datos de varias entidades distintas en este ltimo tramo de la
URI habr que especificarlo. Indicar por ltimo que en una URI se puede hacer referencia
directamente a un registro concreto de la entidad seleccionada. Esto se hara indicando al final de la
URI el ID de dicho registro. Por ejemplo la uri content://net.sgoliver.android.ejemplo/clientes/23
hara referencia directa al cliente con _ID = 23.
Todo esto es importante ya que ser nuestro content provider el encargado de interpretar/parsear la
URI completa para determinar los datos que se le estn solicitando. Esto lo veremos un poco ms
adelante.
onCreate()
query()
insert()
update()
delete()
getType()
El primero de ellos nos servir para inicializar todos los recursos necesarios para el funcionamiento
del nuevo content provider. Los cuatro siguientes sern los mtodos que permitirn acceder a los
datos (consulta, insercin, modificacin y eliminacin, respectivamente) y por ltimo, el mtodo
getType() permitir conocer el tipo de datos devueltos por el content provider (ms tade
intentaremos explicar algo mejor esto ltimo).
Adems de implementar estos mtodos, tambin definiremos una serie de constantes dentro de
nuestra nueva clase provider, que ayudarn posteriormente a su utilizacin. Veamos esto paso a
paso. Vamos a crear una nueva clase ClientesProvider que extienda de
ContentProvider.
Lo primero que vamos a definir es la URI con la que se acceder a nuestro content provider. En mi
caso he elegido la siguiente: content://net.sgoliver.android.ejemplo/clientes. Adems, para seguir
la prctica habitual de todos los content providers de Android, encapsularemos adems esta
direccin en un objeto esttico de tipo Uri llamado CONTENT_URI.
A continuacin vamos a definir varias constantes con los nombres de las columnas de los datos
proporcionados por nuestro content provider. Como ya dijimos antes existen columnas predefinidas
que deben tener todos los content providers, por ejemplo la columna _ID. Estas columnas estandar
estn definidas en la clase BaseColumns, por lo que para aadir la nuevas columnas de nuestro
content provider definiremos una clase interna pblica tomando como base la clase BaseColumns
y aadiremos nuestras nuevas columnas.
1
2 //Clase interna para declarar las constantes de columna
public static final class Clientes implements BaseColumns
3 {
4 private Clientes() {}
5
6 //Nombres de columnas
7 public static final String COL_NOMBRE = "nombre";
public static final String COL_TELEFONO = "telefono";
8 public static final String COL_EMAIL = "email";
9 }
10
Por ltimo, vamos a definir varios atributos privados auxiliares para almacenar el nombre de la base
de datos, la versin, y la tabla a la que acceder nuestro content provider.
1//Base de datos
2private ClientesSqliteHelper clidbh;
3private static final String BD_NOMBRE = "DBClientes";
4private static final int BD_VERSION = 1;
private static final String TABLA_CLIENTES = "Clientes";
5
Como se indic anteriormente, la primera tarea que nuestro content provider deber hacer cuando se
acceda a l ser interpretar la URI utilizada. Para facilitar esta tarea Android proporciona una clase
llamada UriMatcher, capaz de interpretar determinados patrones en una URI. Esto nos ser til para
determinar por ejemplo si una URI hace referencia a una tabla genrica o a un registro concreto a
travs de su ID. Por ejemplo:
Para conseguir esto definiremos tambin como miembro de la clase un objeto UriMatcher y dos
nuevas constantes que representen los dos tipos de URI que hemos indicado: acceso genrico a
tabla (lo llamar CLIENTES) o acceso a cliente por ID (lo llamar CLIENTES_ID). A
continuacin inicializaremos el objeto UriMatcher indicndole el formato de ambos tipos de
URI, de forma que pueda diferenciarlos y devolvernos su tipo (una de las dos constantes definidas,
CLIENTES o CLIENTES_ID).
1 //UriMatcher
2 private static final int CLIENTES = 1;
private static final int CLIENTES_ID = 2;
3 private static final UriMatcher uriMatcher;
4
5 //Inicializamos el UriMatcher
6 static {
7 uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
8 uriMatcher.addURI("net.sgoliver.android.ejemplo", "clientes", CLIENTES);
uriMatcher.addURI("net.sgoliver.android.ejemplo", "clientes/#",
9 CLIENTES_ID);
10}
11
Bien, pues ya tenemos definidos todos los miembros necesarios para nuestro nuevo content
provider. Ahora toca implementar los mtodos comentados anteriormente.
1@Override
2public boolean onCreate() {
3
4 clidbh = new ClientesSqliteHelper(
5 getContext(), BD_NOMBRE, null, BD_VERSION);
6
7 return true;
}
8
La parte interesante llega con el mtodo query(). Este mtodo recibe como parmetros una URI,
una lista de nombres de columna, un criterio de seleccin, una lista de valores para las variables
utilizadas en el criterio anterior, y un criterio de ordenacin. Todos estos datos son anlogos a los
que comentamos cuando tratamos la consulta de datos en SQLite para Android, artculo que
recomiendo releer si no tenis muy frescos estos conocimientos. El mtodo query deber devolver
los datos solicitados segn la URI indicada y los criterios de seleccin y ordenacin pasados como
parmetro. As, si la URI hace referencia a un cliente concreto por su ID se deber ser el nico
registro devuelto. Si por el contrario es un acceso genrico a la tabla de clientes habr que realizar
la consulta SQL correspondiente a la base de datos respetanto los criterios pasados como parmetro.
Para disitinguir entre los dos tipos de URI posibles utilizaremos como ya hemos indicado el objeto
uriMatcher, utilizando su mtodo match(). Si el tipo devuelto es CLIENTES_ID, es decir,
que se trata de un acceso a un cliente concreto, sustituiremos el criterio de seleccin por uno que
acceda a la tabla de clientes slo por el ID indicado en la URI. Para obtener este ID utilizaremos el
mtodo getLastPathSegment() del objeto uri que extrae el ltimo elemento de la URI, en
este caso el ID del cliente.
Hecho esto, ya tan slo queda realizar la consulta a la base de datos mediante el mtodo query()
de SQLiteDatabase. Esto es sencillo ya que los parmetros son anlogos a los recibidos en el
mtodo query() del content provider.
1
2 @Override
3 public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
4
5
//Si es una consulta a un ID concreto construimos el WHERE
6 String where = selection;
7 if(uriMatcher.match(uri) == CLIENTES_ID){
8 where = "_id=" + uri.getLastPathSegment();
9 }
10
SQLiteDatabase db = clidbh.getWritableDatabase();
11
12 Cursor c = db.query(TABLA_CLIENTES, projection, where,
13 selectionArgs, null, null, sortOrder);
14
15 return c;
16 }
17
Como vemos, los resultados se devuelven en forma de Cursor, una vez ms exactamente igual a
como lo hace el mtodo query() de SQLiteDatabase.
Por su parte, los mtodos update() y delete() son completamente anlogos a ste, con la
nica diferencia de que devuelven el nmero de registros afectados en vez de un cursor a los
resultados. Vemos directamente el cdigo:
@Override
1 public int update(Uri uri, ContentValues values,
2 String selection, String[] selectionArgs) {
3
4 int cont;
5
6 //Si es una consulta a un ID concreto construimos el WHERE
String where = selection;
7 if(uriMatcher.match(uri) == CLIENTES_ID){
8 where = "_id=" + uri.getLastPathSegment();
9 }
10
11 SQLiteDatabase db = clidbh.getWritableDatabase();
12
13 cont = db.update(TABLA_CLIENTES, values, where, selectionArgs);
14
return cont;
15}
16
17@Override
18public int delete(Uri uri, String selection, String[] selectionArgs) {
19
20 int cont;
21
//Si es una consulta a un ID concreto construimos el WHERE
22 String where = selection;
23 if(uriMatcher.match(uri) == CLIENTES_ID){
24 where = "_id=" + uri.getLastPathSegment();
}
25
26 SQLiteDatabase db = clidbh.getWritableDatabase();
27
28 cont = db.delete(TABLA_CLIENTES, where, selectionArgs);
29
30 return cont;
31}
32
33
34
35
36
El mtodo insert() s es algo diferente, aunque igual de sencillo. La diferencia en este caso
radica en que debe devolver la URI que hace referencia al nuevo registro insertado. Para ello,
obtendremos el nuevo ID del elemento insertado como resultado del mtodo insert() de
SQLiteDatabase, y posteriormente construiremos la nueva URI mediante el mtodo auxiliar
ContentUris.withAppendedId() que recibe como parmetro la URI de nuestro content
provider y el ID del nuevo elemento.
1
@Override
2 public Uri insert(Uri uri, ContentValues values) {
3
4 long regId = 1;
5
6 SQLiteDatabase db = clidbh.getWritableDatabase();
7
8 regId = db.insert(TABLA_CLIENTES, null, values);
9
10 Uri newUri = ContentUris.withAppendedId(CONTENT_URI, regId);
11
return newUri;
12}
13
Por ltimo, tan slo nos queda implementar el mtodo getType(). Este mtodo se utiliza para
identificar el tipo de datos que devuelve el content provider. Este tipo de datos se expresar como
un MIME Type, al igual que hacen los navegadores web para determinar el tipo de datos que estn
recibiendo tras una peticin a un servidor. Identificar el tipo de datos que devuelve un content
provider ayudar por ejemplo a Android a determinar qu aplicaciones son capaces de procesar
dichos datos.
Una vez ms existirn dos tipos MIME distintos para cada entidad del content provider, uno de
ellos destinado a cuando se devuelve una lista de registros como resultado, y otro para cuando se
devuelve un registro nico concreto. De esta forma, seguiremos los siguientes patrones para definir
uno y otro tipo de datos:
vnd.android.cursor.item/vnd.xxxxxx > Registro nico
vnd.android.cursor.dir/vnd.xxxxxx > Lista de registros
vnd.android.cursor.item/vnd.sgoliver.cliente
vnd.android.cursor.dir/vnd.sgoliver.cliente
Con esto en cuenta, la implementacin del mtodo getType() quedara como sigue:
1
2 @Override
3 public String getType(Uri uri) {
4
int match = uriMatcher.match(uri);
5
6
switch (match)
7 {
8 case CLIENTES:
9 return "vnd.android.cursor.dir/vnd.sgoliver.cliente";
10 case CLIENTES_ID:
return "vnd.android.cursor.item/vnd.sgoliver.cliente";
11 default:
12 return null;
13 }
14}
15
Como se puede observar, utilizamos una vez ms el objeto UriMatcher para determinar el tipo de
URI que se est solicitando y en funcin de sta devolvemos un tipo MIME u otro.
Pues bien, con esto ya hemos completado la implementacin del nuevo content provider. Pero an
nos queda un paso ms, como indicamos al principio del artculo. Debemos declarar el content
provider en nuestro fichero AndroidManifest.xml de forma que una vez instalada la
aplicacin en el dispositivo Android conozca la existencia de dicho recurso.
1<application android:icon="@drawable/icon"
2 android:label="@string/app_name">
3
4 ...
5
6 <provider android:name="ClientesProvider"
7 android:authorities="net.sgoliver.android.ejemplo"/>
8
</application>
9
Ahora s hemos completado totalmente la construccin de nuestro nuevo content provider mediante
el cual otras aplicaciones del sistema podrn acceder a los datos almacenados por nuestra
aplicacin.
En el siguiente artculo veremos cmo utilizar este nuevo content provider para acceder a los datos
de nuestra aplicacin de ejemplo, y tambin veremos cmo podemos utilizar alguno de los content
provider predefinidos por Android para consultar datos del sistema, como por ejemplo la lista de
contactos o la lista de llamadas realizadas.
El cdigo fuente completo de la aplicacin lo publicar junto al siguiente artculo, pero por el
momento podis descargar desde este enlace los fuentes de las dos clases implementadas en este
artculo y el fichero AndroidManifest.xml modificado.
En el artculo anterior del Curso de Programacin en Android vimos como construir un content
provider personalizado para permitir a nuestras aplicaciones Android compartir datos con otras
aplicaciones del sistema. En este nuevo artculo vamos a ver el tema desde el punto de vista
opuesto, es decir, vamos a aprender a hacer uso de un content provider ya existente para acceder a
datos de otras aplicaciones. Adems, veremos cmo tambin podemos acceder a datos del propio
sistema Android (logs de llamadas, lista de contactos, agenda telefnica, bandeja de entrada de sms,
etc) utilizando este mismo mecanismo.
Vamos a comenzar explicando cmo podemos utilizar el content provider que implementamos en el
artculo anterior para acceder a los datos de los clientes. Para no complicar mucho el ejemplo ni
hacer ms dificil las pruebas y la depuracin en el emulador de Android vamos a hacer uso el
content provider desde la propia aplicacin de ejemplo que hemos creado. De cualquier forma, el
cdigo necesario sera exactamente igual si lo hiciramos desde otra aplicacin distinta.
Utilizar un content provider ya existente es muy sencillo, sobre todo comparado con el laborioso
proceso de construccin de uno nuevo. Para comenzar, debemos obtener una referencia a un
Content Resolver, objeto a travs del que realizaremos todas las acciones necesarias sobre el content
provider. Esto es tan fcil como utilizar el mtodo getContentResolver() desde nuestra
actividad para obtener la referencia indicada. Una vez obtenida la referencia al content resolver,
podremos utilizar sus mtodos query(), update(), insert() y delete() para realizar las
acciones equivalentes sobre el content provider. Por ver varios ejemplos de la utilizacin de estos
mtodos aadiremos a nuestra aplicacin de ejemplo tres botones en la pantalla principal, uno para
hacer una consulta de todos los clientes, otro para insertar registros nuevos, y el ltimo para
eliminar todos los registros nuevos insertados con el segundo botn.
Empecemos por la consulta de clientes. El procedimiento ser prcticamente igual al que vimosen
los artculos de acceso a bases de datos SQLite (consultar el ndice del curso). Comenzaremos por
definir un array con los nombres de las columnas de la tabla que queremos recuperar en los
resultados de la consulta, que en nuestro caso sern el ID, el nombre, el telfono y el email. Tras
esto, obtendremos como dijimos antes una referencia al content resolver y utilizaremos su mtodo
query() para obtener los resultados en forma de cursor. El mtodo query() recibe, como ya
vimos en el artculo anterior, la Uri del content provider al que queremos acceder, el array de
columnas que queremos recuperar, el criterio de seleccin, los argumentos variables, y el criterio de
ordenacin de los resultados. En nuestro caso, para no complicarnos utilizaremos tan slo los dos
primeros, pasndole el CONTENT_URI de nuestro provider y el array de columnas que acabamos
de definir.
1
2 //Columnas de la tabla a recuperar
3 String[] projection = new String[] {
4 Clientes._ID,
Clientes.COL_NOMBRE,
5 Clientes.COL_TELEFONO,
6 Clientes.COL_EMAIL };
7
8 Uri clientesUri = ClientesProvider.CONTENT_URI;
9
10ContentResolver cr = getContentResolver();
11
12//Hacemos la consulta
Cursor cur = cr.query(clientesUri,
13 projection, //Columnas a devolver
14 null, //Condicin de la query
15 null, //Argumentos variables de la query
16 null); //Orden de los resultados
17
Hecho esto, tendremos que recorrer el cursor para procesar los resultados. Para nuestro ejemplo,
simplemente los escribiremos en un cuadro de texto (txtResultados) colocado bajo los tres botones
de ejemplo. Una vez ms, si tienes dudas sobre cmo recorrer un cursor, puedes consultar los
artculos del curso dedicados al tratamiento de bases de datos SQLite, por ejemplo ste. Veamos
cmo quedara el cdigo:
1 if
{
(cur.moveToFirst())
2 String nombre;
3 String telefono;
4 String email;
5
6 int colNombre = cur.getColumnIndex(Clientes.COL_NOMBRE);
int colTelefono = cur.getColumnIndex(Clientes.COL_TELEFONO);
7 int colEmail = cur.getColumnIndex(Clientes.COL_EMAIL);
8
9 txtResultados.setText("");
10
11 do
12 {
nombre = cur.getString(colNombre);
13 telefono = cur.getString(colTelefono);
14 email = cur.getString(colEmail);
15
16 txtResultados.append(nombre + " - " + telefono + " - " + email + "\n");
17
18 } while (cur.moveToNext());
}
19
20
21
22
Para insertar nuevos registros, el trabajo ser tambin exactamente igual al que se hace al tratar
directamente con bases de datos SQLite. Rellenaremos en primer lugar un objeto ContentValues
con los datos del nuevo cliente y posteriormente utilizamos el mtodo insert() pasndole la
URI del content provider en primer lugar, y los datos del nuevo registro como segundo parmetro.
1ContentResolver cr = getContentResolver();
2
3cr.delete(ClientesProvider.CONTENT_URI,
4 Clientes.COL_NOMBRE + " = 'ClienteN'", null);
Como muestra grfica, veamos por ejemplo el resultado de la consulta de clientes (primer botn) en
la aplicacin de ejemplo.
Con esto,
e hemos visto lo senncillo que reesulta accedeer a los datoos proporcioonados por un content
providder. Pues bieen, ste es el mismo meccanismo que podemos uttilizar para acceder
a a muuchos datos
de la propia
p platafforma Androoid. En la documentaci
d n oficial deel paquete android.p
a provider
podemmos consultaar los datos que tenem mos disponibbles a travss de este mecanismo,
m entre ellos
enconttramos por ejemplo: el e historial de llamadaas, la agendda de contaactos y telfonos, las
media (audio y video), o el historial y la lista de favoritos
bibliottecas multim f dell navegador.
Por veer un ejempplo de acceso a este tipoo de datos, vamos a realizar una consulta
c al historial
h de
llamaddas del dispositivoo, para lo que accedereemos al content provider
andro oid.provi ider.CallLog.
Decidiido esto, acttuaremos igual que antees. Definiremmos el arrayy con las coolumnas quee queremos
recupeerar, obtendrremos la referencia al content resoolver y ejeccutaremos laa consulta lllamando al
mtoddo query() ). Por ltim
mo, recorrem
mos el cursorr obtenido y procesamoos los resultaados. Igual
que anntes, lo nicco que haremos ser esscribir los reesultados al cuadro de texto situaddo bajo los
botonees. Veamos ele cdigo:
Lo nico fuera de lo normal que hacemos en el cdigo anterior es la decodificacin del valor del
tipo de llamada recuperado, que la hacemos comparando el resultado con las constantes
Calls.INCOMING_TYPE (entrante), Calls.OUTGOING_TYPE (saliente),
Calls.MISSED_TYPE (perdida) proporcionadas por la propia clase provider.
Un ltimo detalle importante. Para que nuestra aplicacin pueda acceder al historial de llamadas del
dispositivo tendremos que incluir en el fichero AndroidManifest.xml el permiso READ_CONTACTS
utilizando la clusula <uses-permission> correspondiente.
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-
1permission>
Podiss descargar ele cdigo fueente compleeto de la apliicacin de ejjemplo construida a lo laargo de los
dos artticulos anterriores pulsanndo este enlaace.
Notificacciones en An
ndroid
d (I): Toast
T
Por sggoliver on 09
9/06/2011 enn Android, Programacinn
Un temma rpido anntes de seguuir con el Cuurso de Progrramacin Anndroid que estamos
e reallizando. En
Androoid existen varias
v formaas de notificaar mensajes al usuario, como por ejemplo
e los cuadros
c de
diloggo modales o las notificcaciones de la bandeja del sistema (o barra dee estado). Peero en este
artcullo nos vamo
os a centrar en
e primer lugar en la forrma ms senncilla de notificacin: loos llamados
Toast.
Un toaast es un mensaje
m que se muestra en pantalla durante unoos segundos al usuario para luego
volverr a desapareecer automtticamente siin requerir ningnn tipo de actuacin por su parte,
p y sin
recibirr el foco en ningn mom mento (o dicho de otra forma, sin interferir enn las accionees que est
realizaando el usuaario en ese momento). Aunque sonn personalizzables, apareecen por deffecto en la
parte inferior de la pantalla, sobre un rectngulo
r g ligeram
gris mente translcido. Por sus
s propias
caracteersticas, estte tipo de nootificacioness son idealess para mostrrar mensajess rpidos y sencillos
s al
usuario, pero por el contrario,, al no requeerir confirmacin por suu parte, no deberan
d utilizarse para
hacer notificacion
n es demasiaddo importantees.
Su utilizacin es muy
m sencillaa, concentrnndose toda la funcionaliidad en la clase Toast. Esta clase
disponne de un mtodo estticoo makeTexxt() al que deberemos pasar como parmetro el e contexto
de la actividad, el
e texto a mostrar,
m y la duracinn del mensaaje, que pueede tomar losl valores
LENGT TH_LONG o LENGTH_ _SHORT, dependiendo del d tiempo que
q queram mos que la notificacin
n
aparezzca en pantalla. Tras obttener una refferencia al objeto
o Toast a travs de
d este mtoddo, ya slo
nos quuedara mosttrarlo en panntalla mediannte el mtodoo show().
1
2 btn
nDefecto.se
etOnClickListener(ne ew OnClickL
Listener() {
@Override
e
3 public void onClick
k(View arg00) {
4 Toast
t toast1 =
5 Toast.make
T Text(getAp
pplicationContext(),,
6 "T oast por d
defecto", Toast.LENG
GTH_SHORT);
;
7
t1.show();
toast
8 }
9 });
10
Si ejeccutamos estaa sencilla applicacin en el emuladorr y pulsamoss el botn quue acabamoss de aadir
verem
mos como en n la parte infferior de la pantalla
p apaarece el mennsaje Toast por defectoo, que tras
varios segundos desaparecer automticam mente.
Como hemos com mentado, stte es el commportamientoo por defecto de las nootificacioness toast, sin
embarrgo tambin podemos peersonalizarloo un poco caambiando suu posicin enn la pantallaa. Para esto
utilizaaremos el mtodo
m setGGravity() ), al que podremos
p indicar en quu zona deseeamos que
aparezzca la notificcacin. La zona
z deberem
mos indicarla con alguna de las connstantes definnidas en la
clase Gravity:
G CENTER,
C LE
EFT, BOTTO OM, o conn alguna commbinacin de stas.
Para nuestro
n ejem
mplo vamos a colocar laa notificacin en la zonna central izqquierda de la
l pantalla.
Para ello,
e aadam mos un segunndo botn a la aplicacin de ejempplo que muestre un toastt con estas
caracteersticas:
1
2 btn
nGravity.se
etOnClickListener(neew OnClickL
Listener() {
@Override
e
3 public void onClick
k(View arg0
0) {
4 t toast2 =
Toast
5 Toast.make
T Text(getAp
pplicationContext(),,
6 "Toast con gravity",
g Toast.LENG
GTH_SHORT);
;
7
8 t2.setGrav
toast vity(Gravit
ty.CENTER|Gravity.LEEFT,0,0);
9
toast
t2.show();
10 }
11});
12
1 <?x
xml version="1.0" encoding="utf
f-8"?>
<LinearLayout
t
2 x
xmlns:andro
oid="http://schemas..android.com/apk/res
s/android"
3 a
android:id=
="@+id/lytLayout"
4 a
android:lay
yout_width="fill_parrent"
5 android:layout_height="fill_parent"
6 android:orientation="horizontal"
android:background="#555555"
7 android:padding="5dip" >
8
9 <ImageView android:id="@+id/imgIcono"
10 android:layout_height="wrap_content"
11 android:layout_width="wrap_content"
android:src="@drawable/marcador" />
12
13 <TextView android:id="@+id/txtMensaje"
14 android:layout_width="wrap_content"
15 android:layout_height="wrap_content"
16 android:layout_gravity="center_vertical"
17 android:textColor="#FFFFFF"
android:paddingLeft="10dip" />
18
19</LinearLayout>
20
21
22
23
Para asignar este layout a nuestro toast tendremos qeu actuar de una forma algo diferente a las
anteriores. En primer lugar deberemos inflar el layout mediante un objeto LayoutInflater,
como ya vimos en varias ocasiones al tratar los artculos de interfaz grfica. Una vez construido el
layout modificaremos los valores de los distintos controles para mostrar la informacin que
queramos. En nuestro caso, tan slo modificaremos el mensaje de la etiqueta de texto, ya que la
imagen ya la asignamos de forma esttica en el layout XML mediante el atributo android:src.
Tras esto, slo nos quedar establecer la duracin de la notificacin con setDuration() y
asignar el layout personalizado al toast mediante el mtodo setView(). Veamos cmo quedara
todo el cdigo incluido en un tercer botn de ejemplo:
1 btnLayout.setOnClickListener(new
@Override
OnClickListener() {
2 public void onClick(View arg0) {
3 Toast toast3 = new Toast(getApplicationContext());
4
5 LayoutInflater inflater = getLayoutInflater();
6 View layout = inflater.inflate(R.layout.toast_layout,
(ViewGroup) findViewById(R.id.lytLayout));
7
8 TextView txtMsg = (TextView)layout.findViewById(R.id.txtMensaje);
9 txtMsg.setText("Toast Personalizado");
10
11 toast3.setDuration(Toast.LENGTH_SHORT);
12 toast3.setView(layout);
toast3.show();
13 }
14});
15
16
17
Si ejeccutamos aho
ora la aplicaacin de ejem mplo y pulssamos el nueevo botn, veremos
v com
mo nuestro
toast aparece
a con la
l estructuraa definida enn nuestro layoout personallizado.
Como podis commprobar, mosstrar notificaaciones de tiipo Toast enn nuestras applicaciones Android
A es
algo de
d lo ms senncillo, y a veeces resultann un elemennto de lo mss interesantee para avisarr al usuario
de determinados eventos.
e
Podiss descargar el
e cdigo de ejemplo de este artculoo pulsando esste enlace.
Notificacciones en An
ndroid
d (II): Barraa de
Esttado
Por sggoliver on 20
0/10/2011 enn Android, Programacinn
Hace algn tiemp po, ya trataamos dentro de este cuurso un prim mer mecanissmo de nottificaciones
disponnibles en la plataforma Android: los llamadoss Toast. Como ya com mentamos, esste tipo de
notificcaciones, aun
nque resultaan tiles y prrcticas en muchas
m ocasiiones, no deberamos utilizarlas en
situaciiones en lass que necessitemos aseggurarnos la atencin deel usuario ya y que no requiere
r de
ningunna intervencin por su parte, se muestran y desapparecen autoomticamentte de la pantalla.
Pues bien,
b aprend
damos a utilizar este tiipo de notifficaciones enn nuestras aplicaciones
a . Vamos a
constrruir para ello
o una aplicaccin de ejemmplo, como siempre
s lo ms
m sencilla posible paraa centrar la
atencin en lo reaalmente impoortante. En este
e caso, el ejemplo vaa a consistir en un nico botn que
generee una notificacin de ejeemplo en la barra
b de estaado, con todoos los elemenntos comenttados y con
la posiibilidad de dirigirnos
d a la propia apliicacin de ejjemplo cuanndo se pulse sobre ella.
Para generar
g notifficaciones en
e la barra de
d estado deel sistema, lo
l primero que
q debemoos hacer es
obteneer una refe
ferencia al servicio ded notificaciones de Android,
A a travs dee la clase
Notif ficationM Manager. Utilizarem mos para ello
e el mtodo gettSystemSe ervice()
indicaando como parmetro el identificador dell servicio corresponddiente, en este caso
Conte ext.NOTIF FICATION_SERVICE.
1//Ob
btenemos un
na referencia al serrvicio de notificaci
iones
2String ns = Co
ontext.NOTIFICATION__SERVICE;
3NotificationMa
anager notManager =
tionManager) getSyst
(Notificat temService(ns);
4
Para esto ltimo debemos construir un objeto PendingIntent, que ser el que contenga la
informacin de la actividad asociada a la notificacin y que ser lanzado al pulsar sobre ella. Para
ello definiremos en primer lugar un objeto Intent, indicando la clase de la actividad concreta a
lanzar, que en nuestro caso ser la propia actividad principal de ejemplo
(AndroidNotificacionesActivity.class). Este intent lo utilizaremos para construir el
PendingIntent final mediante el mtodo PendingIntent.getActivity().
1
2 //Configuramos el Intent
Context contexto = getApplicationContext();
3 CharSequence titulo = "Mensaje de Alerta";
4 CharSequence descripcion = "Ejemplo de notificacin.";
5
6 Intent notIntent = new Intent(contexto,
7 AndroidNotificacionesActivity.class);
8
9 PendingIntent contIntent = PendingIntent.getActivity(
contexto, 0, notIntent, 0);
10
11notif.setLatestEventInfo(
12 contexto, titulo, descripcion, contIntent);
13
Como opciones adicionales, tambin podemos indicar por ejemplo que nuestra notificacin
desaparezca automticamente de la bandeja del sistema cuando se pulsa sobre ella. Esto lo hacemos
aadiendo al atributo flags de nuestra notificacin el valor
Notification.FLAG_AUTO_CANCEL.
Tambin podramos indicar que al generarse la notificacin el dispositivo debe emitir un sonido,
vibrar o encender el LED de estado presente en muchos terminales. Esto lo conseguiramos
aadiendo al atributo defaults de la notificacin los valores DEFAULT_SOUND,
DEFAULT_VIBRATE o DEFAULT_LIGHTS.
1//AuutoCancel: cuando se pulsa la notificain sta de
esaparece
2notif.flags |== Notification.FLAG__AUTO_CANCEL;
3
4//Aadir sonid
do, vibracin y lucees
5 //no
otif.defaul
lts |= Not ification.
.DEFAULT_SOUND;
6 //no
otif.defaul
lts |= Not ification.
.DEFAULT_VIBRATE;
//no
otif.defaul
lts |= Notification..DEFAULT_LIGHTS;
7
Existeen otras mucchas opcionees y personaalizaciones ded stas ltim mas, que se pueden
p conssultar en la
docummentacin official de la cllase Notificaation de Anddroid (atributtos flags y defaults s).
1//En
nviar notif
ficacin
2notM
Manager.not
tify(NOTIF_ALERTA_ID
D, notif);
Ya esttamos en dissposicin de probar nuesstra aplicacin de ejempplo. Para elloo vamos a ejecutarla en
el emuulador de An ndroid y pulsamos el bootn que hemmos implemeentado con todo el cdiggo anterior.
Si todoo va bien, deebera apareecer en ese mismo
m momeento nuestra notificacinn en la barraa de estado,
con el icono y textto definidos..
Si ahoora salimos de
d la aplicaccin y despleegamos la bandeja del sistema
s podrremos verificcar el resto
de infoormacin dee la notificacin tal comoo muestra la siguiente im
magen:
Por ltimo,
si pu
ulsamos sobbre la notifficacin se debera abrrir de nuevo automticcamente la
aplicaccin de ejem
mplo. Adem ms, la notificacin tammbin deberra desaparecer de la bandeja
b del
sistem mo lo habamos configuraado en el cddigo con la opcin
ma, tal y com o FLAG
G_AUTO_CA ANCEL.
En definitiva, com
mo podis comprobar
c e bastante sencillo
es s gennerar notificaaciones en la
l barra de
estadoo de Android
d desde nuestras aplicacciones. Os animo
a a utiliizar este mecanismo parra notificar
determ
minados evenntos al usuarrio de forma bastante vissual e intuitivva.
Como siempre, po
odis descarggar el cdigoo fuente com
mpleto de estte artculo puulsando este enlace.
Notificacciones en An
ndroid
d (III): Dilogos
Por sggoliver on 05
5/11/2011 enn Android, Programacinn
Mostrar un n mensaje.
Pedir una confirmacin
c n rpida.
Solicitar all usuario unaa eleccin (ssimple o mlltiple) entre varias
v alternnativas.
El primero de estos mtodos recibe como parmetro el ID del dilogo que queremos crear. Podemos
asignar estos ID a nuestro antojo pero deben identificar de forma nica a cada dilogo de nuestra
aplicacin. La forma habitual de proceder ser definir una constante en la actividad con algn
nombre identificativo y utilizar sta en el resto del cdigo. Como en el caso de los mens, dentro de
este mtodo construiremos cada dilogo segn el ID recibido y lo devolveremos como valor de
retorno. Seguiramos el siguiente esquema:
1
2
private static final int DIALOGO_TIPO_1 = 1;
3 private static final int DIALOGO_TIPO_2 = 2;
4 //...
5 protected Dialog onCreateDialog(int id) {
6 Dialog dialogo = null;
7
8 switch(id)
{
9 case DIALOGO_TIPO_1:
10 dialogo = crearDialogo1();
11 break;
12 case DIALOGO_TIPO_2:
dialogo = crearDialogo2();
13
break;
14 //...
15 default:
16 dialogo = null;
17 break;
}
18
19 return dialogo;
20}
21
22
Dilogo de Alerta
Este tipo de dilogo se limita a mostrar un mensaje sencillo al usuario, y un nico botn de OK para
confirma su lectura. Este tipo de dilogos los construiremos mediante la clase AlertDialog, y
ms concretamente su subclase AlertDialog.Builder. Su utilizacin es muy sencilla, bastar
con crear un objeto de tipo AlertDialog.Builder y establecer las propiedades del dilogo
mediante sus mtodos correspondientes: ttulo [setTitle()], mensaje [setMessage()] y el
texto y comportamiento del botn [setPositiveButton()]. Veamos un ejemplo:
1
2 private Dialog crearDiaalogoAlerta()
3 {
log.Builder builder = new Aler
AlertDial rtDialog.Buuilder(thi
is);
4
5 builder.s
setTitle("Informacio on");
6 builder.s
setMessage("Esto es un mensaje de alert ta.");
7 builder.s
setPositivveButton("OOK", new On
nClickListeener() {
8 publi
ic void onClick(DialoogInterface dialog, int which) {
9 dialog.can
d cel();
}
10 });
11
12 return builder.creaate();
13}
14
Diloggo de Confirmacin
Un dilogo de con nfirmacin es muy similaar al anteriorr, con la difeerencia de quue lo utilizarremos para
solicittar al usuario que nos confirme
c unaa determinaada accin, por p lo que lasl posibles respuestas
sern del
d tipo S/NNo.
La im
mplementaci
n de estos dilogos
d mente igual a la ya comeentada para las alertas,
serr prcticam
salvo que en estaa ocasin aadiremos
a dos botoness, uno de ellos
e para laa respuesta afirmativa
(setPPositiveB Button())), y el segunndo paraa la respuesta negativa
(setNNegativeB Button())). Veamos unn ejemplo:
Diloggo de Selecccin
Cuanddo las opcio ones a selecccionar por el usuario no son sllo dos, com
mo en los dilogos
d de
confirm
macin, sin no que el coonjunto es mayor podeemos utilizaar los diloggos de seleccin para
mostraar una lista de
d opciones entre
e las quee el usuario pueda
p elegirr.
Para ello
e tambin n utilizaremos la clase AlertDia alog, pero esta vez noo asignarem mos ningn
mensaaje ni definiremos las acciones
a a reealizar por cada
c botn individual, sino que dirrectamente
indicaaremos la listta de opciones a mostrarr (mediante el
e mtodo se etItems() )) y proporccionaremos
la impplementaci n del evennto onClic ck() sobree dicha lista (mediantee un listeneer de tipo
Dialo ogInterfa ace.OnClickListener), evennto en el que realizzaremos lass acciones
oportuunas segn lal opcin eleegida. La lissta de opcionnes la definiremos commo un array tradicional.
t
Veamoos cmo:
En esste caso el dilogo tenndr un asppecto similaar a la interrfaz mostrada para loss controles
Spinn ner.
Este dilogo
d perm
mite al usuarrio elegir enntre las opciiones disponnibles cada vez
v que se muestra
m en
pantalla. Pero, y si quisirammos recordarr cul es la opcin
o u opcciones selecccionadas porr el usuario
para que
q aparezcaan marcadas al visualizzar de nuevvo el cuadroo de dilogoo? Para elloo podemos
utilizaar los mtodo
os setSing gleChoice eItems() o setMult tiChiceItems(), enn vez de el
setIt tems() uttilizado anteeriormente. La L diferencia entre ammbos mtodoos, tal comoo se puede
suponeer por su noombre, es quue el primeroo de ellos peermitir una seleccin sim mple y el seegundo una
seleccin mltiplee, es decir, dee varias opciiones al mism
mo tiempo, mediante
m coontroles Che
eckBox.
La forma
f de utilizarlos es muy similar a la ya comentada. En el caso de
setSi ingleChoi iceItems(), el mtoodo tan slo se diferenciia de setIt tems() en que recibe
un seggundo parm
metro adicioonal que inddica el ndicce de la oppcin marcada por defeecto. Si no
querem
mos tener nin
nguna de elllas marcadass inicialmentte pasaremoss el valor -1..
builder.setSin
ngleChoiceItems(itemms, -1, new
w DialogInt
terface.On
nClickListe
ener() {
1 public void onClick(DialogInte
erface dialog, int it
tem) {
2 Log.i(
("Dialogos", "Opcin
n elegida: " + items[
[item]);
3 }
});
4
5
Si porr el contrario
o optamos poor la opcin de seleccinn mltiple, laa diferencia principal esttar en que
tendreemos que implemenntar un listener del tipo
Dialo ogInterfa ace.OnMultiChoic ceClickLi istener. En este caso, c en el evento
onCli ick recibireemos tanto la
l opcin seeleccionada (item) com mo el estado en el que ha h quedado
(isCh hecked). Adems,
A en esta
e ocasin, el segundoo parmetro adicional quue indica el estado por
defectto de las opcciones ya noo ser un simmple nmerro entero, sinno que tendrr que ser un u array de
booleaanos. En caaso de no querer
q ninguuna opcin seleccionada
s a por defectto pasaremoos el valor
null.
Y el dilogo
d nos quedara
q de la
l siguiente forma:
f
Tanto si utilizamo os la opcinn de selecciin simple como
c la de seleccin mltiple,
m parra salir del
diloggo tendremoss que pulsar la tecla Attrs de nuestro disposittivo, pero noo perderemoos el estado
de las opciones. Si
S volvemos a abrir el dilogo
d stas debern continuar en ele mismo esttado que la
ltimaa vez que se cerr. Esto por
p supuestoo se cumple mientras
m no se cierre la actividad
a asoociada o se
salga de
d la aplicaccin.
Como siempre, po
odis descarggar el cdigoo fuente de este
e artculo a travs de este
e enlace.
En estte primer arttculo que vaamos a dediccar a los serrvicios web dentro del Curso
C de Proogramacin
Androoid nos vamo os a centrar en los serviccios web que utilizan el estndar SO
OAP como mecanismo
m
de com
municacin.
A difeerencia de ottros tutorialees, no slo vaamos describbir cmo accceder a este tipo de serviicios desde
una applicacin An ndroid, sino que tambin veremos como crear un servicioo web SOAP P mediante
ASP.NNET para acceder
a a unna base de datos SQL L Server. Dee esta form ma pretendo ilustrar la
arquiitectura com
mpleta de unna aplicacinn Android quue acceda a datos almaccenados en un u servidor
de base de datoss externo. Nota:N Aunquue intentar aportar el mximo nmero de detalles,
d es
imposible abarcarrlo todo en un solo arttculo, por lo que el texxto supondrr unos conocimientos
mnim
mos de Visuaal Studio y deel lenguaje C#. C
Vamos comenzar por la basee de todo el sistema, y esto es la base b de datos a la que acceder
a el
serviciio web y, a travs de sste, tambinn la aplicacin Android que crearem
mos ms adeelante. Para
ello abbrimos SQL Server Mannagement Stuudio, nos connectamos a nuestro
n servidor SQL Seerver local,
y pulsamos sobre la seccin D Databases del rbol dee objetos quee aparece a la
l izquierda. Sobre esta
carpetta podemos acceder
a a la opcin Neew Databasee del menn contextuaal para crear una nueva
base de
d datos.
En el cuadro de dilogo
d que aparece
a tan slo
s indicareemos el nom mbre de la nuueva base ded datos, en
mi casso la llamar DBCLIENTTES, y dejareemos el restoo de opcionees con sus vaalores por deefecto.
Despleegamos el rbol de carppetas de nuestra recin creada
c base de datos DB
BCLIENTESS y sobre la
carpetta Tables ejecutamos
e la opcin N
New table para crear una
u nueva tabbla.
Vamos a aadir s
lo 3 camposs a la tabla:
Marcaaremos adem ms el camppo IdCliente como clave princcipal de la taabla, y tambbin como
campoo de identid dad autoincrremental, de
d modo que se calculee automticcamente cadda vez que
inserteemos un nueevo cliente.
Con esto ya tenemmos nuestra tabla finalizzada, por lo que slo noos queda guuardarla con el nombre
que deeseemos, quee para este ejjemplo ser Clientes.
Hechoo, ya tenemo os nuestra baase de datos SQL Serverr creada y unau tabla preeparada para almacenar
los dattos asociadoos a nuestross clientes. Ell siguiente paso
p ser creear el serviciio web que manipular
m
los dattos de esta taabla.
Para crear
c el serv
vicio abrirem
mos Visual Studio
S 20100 y crearemoos un nuevoo proyecto web
w en C#
utilizaando la planntilla ASP.NET Emptyty Web Appplication. En
E un alardde de origiinalidad lo
llamarremos ServiicioWebSoapp.
Una vez
v creado ell proyecto, aadiremos
a a ste un nuuevo servicioo web mediaante el men Project /
Add neew item. Lo llamarem
mos ServiciioClientes.assmx.
Una vez
v aadido aparecer en pantalla el e cdigo fueente por deffecto del nuuevo servicioo web, que
contiene un nico mtodo de ejemplo llam mado Hello oWorld(). Este mtoddo podemos eliminarlo
ya quee no nos servvir de nadaa, y adems modificarem mos el atribuuto WebSer rvice de laa clase para
indicaar que el nam
mespace ser http://sgolliver.net/ (een vuestro caaso podis inndicar otro valor).
v Con
esto, nos
n quedara un cdigo base
b como sste:
1
2 nammespace ServicioWebSooap
3 {
4 /// <summ
mary>
/// Summa
ary description for ServicioClientes
5 /// </sum
mmary>
6 [WebServi
ice(Namespace = "htttp://sgoliv
ver.net/")]
7 [WebServi
iceBinding(ConformsTTo = WsiPro
ofiles.Bas
sicProfile1
1_1)]
8 [System.C
ComponentMModel.Toolb
boxItem(false)]
9
10 public class ServicioClientes
s : System.Web.Servi
ices.WebSer
rvice
{
11 //Mt
todos del servicio web
w
12 //...
.
13 }
14 }
15
Pues bien,
b dentroo de esta claase Servic cioClient tes es dondde aadirem mos todos loos mtodos
pbliccos que queramos tener accesibles
a a travs de nuuestro serviccio web, siem
mpre preceddidos por el
atributto [WebMet thod] como veremos en e breve. Parra nuestro ejemplo vamoos a crear trees mtodos,
el prim
mero para ob btener el listtado completto de clientees almacenaddos en la basse de datos, y los otros
dos paara insertar nuevos
n clienntes (ms addelante expliicar por quu dos, aunquue adelanto que es tan
slo por motivos didcticos).
d
Antes de crear esttos mtodos,, vamos a crrear una nuevva clase sencilla que nos sirva para encapsular
los dattos de un cliiente. La aaadiremos meediante la oppcin Projeect / Add class de Vissual Studio
y la llaamaremos Cliente.cs. Esta clase contendr
c nnicamente loos 3 campos que ya com mentamos al
crear lal base de datos
d y dos constructorees, uno de ellos
e por defecto que taan solo iniciializar los
campoos y otro con n parmetross para crear clientes a partir de sus datos identificativos. Ell cdigo de
la classe es muy sencillo, y tan solo caabe mencionnar que deffiniremos suus tres atribuutos como
propieedades autommticas de C# utilizando para ello la notacin abreviada {ge et; set;} }
1
2
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
using System.Web;
6
7 namespace ServicioWebSoap
8 {
9 public class Cliente
10 {
public int Id {get; set;}
11
public string Nombre {get; set;}
12 public int Telefono {get; set;}
13
14 public Cliente()
15 {
16 this.Id = 0;
this.Nombre = "";
17 this.Telefono = 0;
18 }
19
20 public Cliente(int id, string nombre, int telefono)
21 {
22 this.Id = id;
this.Nombre = nombre;
23 this.Telefono = telefono;
24 }
25 }
26}
27
28
Vamos ahora a escribir el primero de los mtodos que haremos accesible a travs de nuestro
servicio web. Lo llamaremos NuevoCliente(), recibir como parmetros de entrada un nombre
y un telfono, y se encargar de insertar un nuevo registro en nuestra tabla de clientes con dichos
datos. Recordemos que el ID del cliente no ser necesario insertarlo de forma explcita ya que lo
hemos definido en la base de datos como campo autoincremental. Para el trabajo con la base de
datos vamos a utilizar la API clsica de ADO.NET, aunque podramos utilizar cualquier otro
mcanismo de acceso a datos, como por ejemplo Entity Framework, NHibernate, etc.
De esta forma, el primer paso ser crear una conexin a SQL Server mediante la clase
SQLConnection, pasando como parmetro la cadena de conexin correspondiente (en vuestro
caso tendris que modificarla para adaptarla a vuestro entorno). Tras esto abriremos la conexin
mediante una llamada al mtodo Open(), definiremos el comando SQL que queremos ejecutar
creando un objeto SQLCommand. Ejecutaremos el comando llamando al mtodo
ExecuteNonQuery() recogiendo el resultado en una variable, y finalmente cerraremos la
conexin llamando a Close(). Por ltimo devolveremos el resultado del comando SQL como
valor de retorno del mtodo web.
Como podis ver en el cdigo siguiente, los valores a insertar en la base de datos los hemos
especificado en la consulta SQL como parmetros variable (precedidos por el carcter @). Los
valores de estos parmetros los definimos y aadimos al comando SQL mediante el mtodo Add()
de su propiedad Parameters. Esta opcin es ms recomendable que la opcin clsica de
concatenar directamente la cadena de texto de la sentencia SQL con los parmetros variables, ya
que entre otras cosas servir para evitar [en gran medida] posibles ataques de inyeccin SQL. El
resultado devuelto por este mtodo ser el nmero de registros afectados por la sentencia SQL
ejecutada, por lo que para verificar si se ha ejecutado correctamente bastar con comprobar que el
resultado es igual a 1.
[WebMethod]
1 public int NuevoClienteSimple(string nombre, int telefono)
2 {
3 SqlConnection con =
4 new SqlConnection(
@"Data Source=SGOLIVERPC\SQLEXPRESS;Initial
5
Catalog=DBCLIENTES;Integrated Security=True");
6
7 con.Open();
8
9 string sql = "INSERT INTO Clientes (Nombre, Telefono) VALUES (@nombre,
10@telefono)";
11
12 SqlCommand cmd = new SqlCommand(sql, con);
13
cmd.Parameters.Add("@nombre", System.Data.SqlDbType.NVarChar).Value =
14nombre;
15 cmd.Parameters.Add("@telefono", System.Data.SqlDbType.Int).Value =
16telefono;
17
18 int res = cmd.ExecuteNonQuery();
19
20 con.Close();
21
return res;
22}
En el cdigo anterior, podis ver que hemos precedido el mtodo con el atributo [WebMethod].
Con este atributo estamos indicando que el mtodo ser accesible a travs de nuestro servicio web y
podr ser llamado desde cualquier aplicacin que se conecte con ste.
La siguiente operacin que vamos a aadir a nuestro servicio web ser la que nos permita obtener el
listado completo de clientes registrados en la base de datos. Llamaremos al mtodo
ListadoClientes() y devolver un array de objetos de tipo Cliente. El cdigo del mtodo
ser muy similar al ya comentado para la operacin de insercin, con la nica diferencia de que en
esta ocasin la sentencia SQL ser obviamente un SELECT y que utilizaremos un objeto
SqlDataReader para leer los resultados devueltos por la consulta. Los registros ledos los
iremos aadiendo a una lista de tipo List<Clientes> y una vez completada la lectura
convertiremos esta lista en un array de clientes llamando al mtodo ToArray(). Este ltimo array
ser el que devolveremos como resultado del mtodo. Veamos el cdigo completo del mtodo:
1
2 [WebMethod]
3 public Cliente[] ListadoClientes()
4 {
5 SqlConnection con =
new SqlConnection(
6 @"Data Source=SGOLIVERPC\SQLEXPRESS;Initial
7 Catalog=DBCLIENTES;Integrated Security=True");
8
9 con.Open();
10
11 string sql = "SELECT IdCliente, Nombre, Telefono FROM Clientes";
12
13 SqlCommand cmd = new SqlCommand(sql, con);
14
SqlDataReader reader = cmd.ExecuteReader();
15
16 List<Cliente> lista = new List<Cliente>();
17
18 while (reader.Read())
19 {
20 lista.Add(
new Cliente(reader.GetInt32(0),
21 reader.GetString(1),
22 reader.GetInt32(2)));
23 }
24
25 con.Close();
26
27 return lista.ToArray();
}
28
29
Por ltimo, como dijimos al principio, vamos a aadir un tercer mtodo web con fines
puramente didcticos. Si os fijis en los dos mtodos anteriores, veris que en uno de los casos
devolvemos como resultado un valor simple, un nmero entero, y en el otro caso un objeto
complejo, en concreto un array de objetos de tipo Cliente. Sin embargo, ninguno de ellos recibe
como parmetro un tipo complejo, tan slo valores simples (enteros y strings). Esto no tiene mucha
relevancia en el cdigo de nuestro servicio web, pero s tiene ciertas peculiaridades a la hora de
realizar la llamada al servicio desde la aplicacin Android. Por lo que para poder explicar esto ms
adelante aadiremos un nuevo mtodo de insercin de clientes que, en vez de recibir los parmetros
de nombre y telfono por separado, recibir como dato de entrada un objeto Cliente.
1 [WebMethod]
public int NuevoClienteObjeto(Cliente cliente)
2 {
3 SqlConnection con = new SqlConnection(@"Data
4 Souurce=SGOLIV
VERPC\SQLEXPRESS;Ini itial Ca
atalog=DBC
CLIENTES;Inntegrated
5 Sec
curity=True
e");
6
con.Open(
();
7
8 string sqql = "INSSERT INTO Clientes (Nombre, Telefono) VALUES (@nombre,
(
9 @teelefono)";
10
11 nd cmd = new SqlComm
SqlComman mand(sql, con);
c
12
13 cmd.Param
meters.Addd("@nombre"", Systeem.Data.Sq
qlDbType.NVVarChar).V
Value =
cliente.Nombr
re;
14 cmd.Param
meters.Addd("@telefonno", S
System.Dataa.SqlDbTyp
pe.Int).Vallue =
15cliente.Teleffono;
16
17 int res = cmd.ExecuuteNonQuery();
18
19 con.Close
e();
20
return res;
}
Y conn esto hemo os finalizadoo nuestro seervicio web. Podemos probar su funcionamien
f nto con la
pginaa de prueba que
q proporciiona ASP.NET al ejecuttar el proyeccto en Visuall Studio. Si ejecutamos
e
el proyecto se abrrir automtticamente unn exploradorr web que mostrar
m unaa pgina conn todas las
operacciones que hemos
h definiddo en el servvicio web.
Si pulssamos sobree cualquiera de ellas pasaaremos a una nueva pgina que nos permitir daar valores a
sus paarmetros y ejecutar
e el mtodo
m correespondiente para
p visualizzar sus resultados. Si pulsamos por
ejemplo en la operracin Nuev voCliente e(string, , int) lleggaremos a essta pgina:
Aqu podemos
p dar valores a los
l dos parmetros y ejeecutar el mtodo (botnn Invoke), lo que nos
devolvver la respu
uesta codificaada en un XM
ML segn el estandar SOOAP.
En el siguiente
s d la construcccin de unaa aplicacin Android quee sea capaz
arttculo nos occuparemos de
de connectarse a este servicio web y de llamar
l a los mtodos quue hemos deefinido paraa insertar y
recupeerar clientes de nuestra base de datoos. Veremoss adems cmo podemoos ejecutar y probar en
local todo
t el sistem
ma de formaa que podamoos comprobaar que todo funciona
f com
mo esperamoos.
Podiss descargar el
e cdigo fueente completto del servicio web desdde este enlacee.
En el artculo antterior del cuurso vimos cmo constrruir un servvicio web SO OAP hacienndo uso de
ASP.N NET y una base de daatos externaa SQL Server. En este segundo arrtculo verem mos cmo
podemmos acceder a este serviccio web desdde una aplicaacin Androiid y probaremos todo el sistema en
local para
p verificaar su correctoo funcionam
miento.
En priimer lugar hay que em mpezar dicieendo que Anndroid no inncluye de serie ningn tipo de
soportte para el accceso a serviicios web dee tipo SOAP P. Es por estto por lo quue vamos a utilizar
u una
librera externa paara hacernoss ms fcil esta
e tarea. Entre
E la ofertta actual, la opcin mss popular y
ms utilizada
u es la
l librera kssoap2-androoid. Esta librrera es un fork,
f especiaalmente adaaptado para
Androoid, de la anttigua libreraa kSOAP2. Este
E framew work nos perm mitir de forrma relativammente fcil
y cmmoda utilizar servicios weeb que utiliccen el estnddar SOAP. La ltima verrsin de estaa librera en
el mommento de esccribir este arrtculo es la 2.6.0,
2 que puuede descarggarse desde este
e enlace.
Agreggar esta libreera a nuesttro proyectoo Android ese muy senccillo. Una vez v tenemoss creado el
proyeccto en Andro oid, accederremos al meen Projectt / Propertiees y en la ventana
v de propiedades
accedeeremos a la seccin Javva Build Patth. En esta seccin acceederemos a la l solapa Libraries y
pulsarremos el bootn Add External
E JARRs. Aqu seleccionaamos el fichhero jar de la librera
ksoap22-android (een este caso ksoap2-anndroid-assem mbly-2.6.0-jaar-with-depeendencies.jarr) y listo,
ya tenemos nuestrro proyecto preparado
p paara hacer usoo de la funcioonalidad apoortada por laa librera.
Como aplicacin de ejemplo,, vamos a crrear una apllicacin senccilla que perrmita aadirr un nuevo
usuario a la base de datos. ParaP ello aadiremos a la vista priincipal dos cuadros de texto para
introduucir el nommbre y telfono del nuuevo clientee (en mi caso c se llam
marn txtN Nombre y
txtTe elefono respectivame
r ente) y un botn
b (en mii caso btnE
Enviar) quue realice la llamada al
mtoddo NuevoCl w pasndolle como parrmetros los datos introdducidos en
liente dell servicio web
los cuaadros de texto anterioress.
No vooy a mostrarr todo el cdigo necesaario para creear esta vistaa y obtener las referenccias a cada
controol porque no o tiene ningguna particuularidad sobbre lo ya viisto en mulltitud de ocasiones en
artcullos anteriorees del curso (en
( cualquieer caso al finnal del artcuulo podis deescargar todoo el cdigo
fuentee para su co onsulta). Loo que nos interesa
i en este caso es e la implementacin del d evento
onCli ick del botn btnEnv viar, que ser el encaargado de coomunicarse con el serviicio web y
processar el resultaado.
En la imagen anteerior se mueestran resaltaados en rojo los valores de las cuatrro constantes a definir,
que enn nuestro casso concreto quedaran
q dee la siguientee forma:
1String NAMESPA
ACE = "http://sgoliv
ver.net/";
2String URL="ht
ttp://10.0.2.2:1473/
/ServicioClientes.as
smx";
3String METHOD_
_NAME = "NuevoClient
teSimple";
4String SOAP_AC
CTION = "http://sgol
liver.net/NuevoClien
nteSimple";
;
Los siiguientes passos del proceeso sern crrear la peticin SOAP all servicio weeb, enviarla al servidor
y recibir la respuuesta. Aunquue ya dijimoos que todoo este proceeso sera cassi transparennte para el
prograamador, por ser sta la primera vez que
q hablamoos del tema mem voy a dettener un poco ms para
intentaar que enten
ndamos lo quue estamos haciendo
h y no
n solo nos limitemos
l a copiar/pegaar trozos de
cdigoo que no sabbemos lo quee hacen.
El segundo paso ser crear el contenedor SOAP (envelope) y asociarle nuestra peticin. Para ello
crearemos un nuevo objeto SoapSerializationEnvelope indicando la versin de SOAP que
vamos a usar (versin 1.1 en nuestro caso, como puede verse en la imagen anterior). Indicaremos
adems que se trata de un servicio web .NET activando su propiedad dotNet. Por ltimo,
asociaremos la peticin antes creada a nuestro contenedor llamando al mtodo
setOutputSoapObject().
1SoapSerializationEnvelope envelope =
2 new SoapSerializationEnvelope(SoapEnvelope.VER11);
3
4envelope.dotNet = true;
5
6envelope.setOutputSoapObject(request);
Como tercer paso crearemos el objeto que se encargar de realizar la comunicacin HTTP con el
servidor, de tipo HttpTransportSE, al que pasaremos la URL de conexin a nuestro servicio
web. Por ltimo, completaremos el proceso realizando la llamada al servicio web mediante el
mtodo call().
1
2 HttpTransportSE transporte = new HttpTransportSE(URL);
3
4 try
{
5 transporte.call(SOAP_ACTION, envelope);
6
7 //Se procesa el resultado devuelto
8 //...
9 }
catch (Exception e)
10{
11 txtResultado.setText("Error!");
12}
13
Tras la llamada al servicio ya estamos en disposicin de obtener el resultado devuelto por el mtodo
web llamado. Esto lo conseguimos mediante el mtodo getResponse(). Dependiendo del tipo
de resultado que esperemos recibir deberemos convertir esta respuesta a un tipo u otro. En este caso,
como el resultado que esperamos es un valor simple (un nmero entero) convertiremos la respuesta
a un objeto SoapPrimitive, que directamente podremos convertir a una cadena de caracteres
llamado a toString(). Ms adelante veremos cmo tratar valores de retorno ms complejos.
1<use
es-permissi
ion android
d:name="android.perm
mission.INT
TERNET"/>
Pues bien, parra probar lo que llevamos hasta ahoora podem mos ejecutaar ambos
proyecctos simultn neamente, ene primer luugar el de Visual Studdio para iniciar la ejeccucin del
serviddor local quee alberga nueestro servicioo web (hay que dejar abbierto el explorador una vez que se
abra), y posteriorm mente el de Eclipse paraa iniciar nueestra aplicaccin Androidd en el Emuulador. Una
vez esstn los doss proyectos en ejecucin, podemoss rellenar loos datos de nuestro cliiente en la
aplicaccin Androiid y pulsar el
e botn Enviar para realizar la llaamada al serrvicio web e insertar el
clientee en la base de datos (quue por supueesto tambin deber estar iniciada). Si S todo va bien y no se
producce ningn errror, deberaamos poder consultar la tabla de Cl lientes a travs del SQL
S Server
Managgement Stud dio para verifficar que el cliente
c se ha insertado coorrectamentee.
En este caso, la llamada al mtodo web se realizar de forma totalmente anloga a la ya comentada.
Donde llegarn las diferencias ser a la hora de tratar el resultado devuelto por el servicio,
comenzando por el resultado del mtodo getResponse() de ksoap. En esta ocasin, dado que el
resultado esperado no es ya un valor simple sino un objeto ms complejo, convertiremos el
resultado de getResponse() al tipo SoapObject, en vez de SoapPrimitive como hicimos
anteriormente. Nuestro objetivo ser generar un array de objetos Cliente (lo llamaremos
listaClientes) a partir del resultado devuelto por la llamada al servicio.
Como sabemos que el resultado devuelto por el servicio es tambin un array, lo primero que
haremos ser crear un array local con la misma longitud que el devuelto, lo que conseguiremos
mediante el mtodo getPropertyCount(). Tras esto, iteraremos por los distintos elementos
del array devuelto mediante el mtodo getProperty(ind), donde ind ser el ndice de cada
ocurrencia. Cada uno de estos elementos ser a su vez otro objeto de tipo SoapObject, que
representar a un Cliente. Adicionalmente, para cada elemento accederemos a sus propiedades
(Id, Nombre, y Telefono) una vez ms mediante llamadas a getProperty(), con el ndice de
cada atributo, que seguir el mismo orden en que se definieron. As, getProperty(0)
recuperar el Id del cliente, getProperty(1) el nombre, y getProperty(2) el telfono. De
esta forma podremos crear nuestros objetos Cliente locales a partir de estos datos. Al final de
cada iteracin aadimos el nuevo cliente recuperado a nuestro array. Veamos como quedara todo
esto en el cdigo, donde seguro que se entiende mejor:
1
2 SoapObject resSoap =(SoapObject)envelope.getResponse();
3
4 Cliente[] listaClientes = new Cliente[resSoap.getPropertyCount()];
5
for (int i = 0; i < listaClientes.length; i++)
6 {
7 SoapObject ic = (SoapObject)resSoap.getProperty(i);
8
9 Cliente cli = new Cliente();
10 cli.id = Integer.parseInt(ic.getProperty(0).toString());
cli.nombre = ic.getProperty(1).toString();
11 cli.telefono = Integer.parseInt(ic.getProperty(2).toString());
12
13 listaClientes[i] = cli;
14}
15
En nuestra aplicacin de ejemplo aadimos un nuevo botn y un control tipo lista (lo llamo
lstClientes), de forma que al pulsar dicho botn rellenemos la lista con los nombres de todos
los clientes recuperados. La forma de rellenar una lista con un array de elementos ya la vimos en los
artculos dedicados a los controles de seleccin, por lo que no nos pararemos a comentarlo. El
cdigo sera el siguiente (Nota: s que todo esto se podra realizar de forma ms eficiente sin
necesidad de crear distintos arrays para los clientes y para el adaptador de la lista, pero lo dejo as
para no complicar el tutorial con temas ya discutidos en otros artculos):
1
//Rellenamos la lista con los nombres de los clientes
2 final String[] datos = new String[listaClientes.length];
3
4 for(int i=0; i<listaClientes.length; i++)
5 datos[i] = listaClientes[i].nombre;
6
7 ArrayAdapter<String> adaptador =
8 new ArrayAdapter<String>(ServicioWebSoap.this,
android.R.layout.simple_list_item_1, datos);
9
10lstClientes.setAdapter(adaptador);
11
Por ltimo, vamos a ver cmo llamar a un mtodo web que recibe como parmetro algn objeto
complejo. Para ilustrarlo haremos una llamada al segundo mtodo de insercin de clientes que
implementamos en el servicio, NuevoClienteObjeto(). Recordemos que este mtodo reciba
como parmetro de entrada un objeto de tipo Cliente.
Para poder hacer esto, lo primero que tendremos que hacer ser modificar un poco nuestra clase
Cliente, de forma que ksoap sepa cmo serializar nuestros objetos Cliente a la hora de
generar las peticiones SOAP correspondientes. Y para esto, lo que haremos ser implementar la
interfaz KvmSerializable en nuestra clase Cliente. Para ello, adems de aadir la clusula
implements correspondiente tendremos que implementar los siguientes mtodos:
getProperty(int indice)
getPropertyCount()
getPropertyInfo(int indice, HashTable ht, PropertyInfo info)
setProperty(int indice, Object valor)
El primero de ellos deber devolver el valor de cada atributo de la clase a partir de su ndice de
orden. As, para el ndice 0 se devolver el valor del atributo Id, para el ndice 1 el del atributo
Nombre, y para el 2 el atributo Telfono.
1 @Override
2 public Object getProperty(int arg0) {
3
switch(arg0)
4 {
5 case 0:
6 return id;
7 case 1:
return nombre;
8 case 2:
9 return telefono;
10 }
11
12 return null;
13}
14
15
El segundo de los mtodos, deber devolver simplemente el nmero de atributos de nuestra clase,
que en nuestro caso ser 3 (Id, Nombre y Telefono):
1@Override
2public int getPropertyCount() {
3 return 3;
4 }
El objetivo del tercero ser informar, segn el ndice recibido como parmetro, el tipo y nombre del
atributo correspondiente. El tipo de cada atributo se devolver como un valor de la clase
PropertyInfo.
1
2
@Override
3 public void getPropertyInfo(int ind, Hashtable ht, PropertyInfo info) {
4 switch(ind)
5 {
6 case 0:
info.type = PropertyInfo.INTEGER_CLASS;
7
info.name = "Id";
8 break;
9 case 1:
10 info.type = PropertyInfo.STRING_CLASS;
11 info.name = "Nombre";
break;
12 case 2:
13 info.type = PropertyInfo.INTEGER_CLASS;
14 info.name = "Telefono";
15 break;
16 default:break;
}
17}
18
19
Por ltimo, el mtodo setProperty() ser el encargado de asignar el valor de cada atributo
segn su ndice y el valor recibido como parmetro.
@Override
1 public void setProperty(int ind, Object val) {
2 switch(ind)
3 {
4 case 0:
5 id = Integer.parseInt(val.toString());
break;
6 case 1:
7 nombre = val.toString();
8 break;
case 2:
9 telefono = Integer.parseInt(val.toString());
10 break;
11 default:
12 break;
}
13}
14
15
16
17
Mediante estos mtodos, aunque de forma transparente para el programados, ksoap ser capaz de
transformar nuestros objetos Cliente al formato XML correcto de forma que pueda pasarlos como
parmetro en las peticiones SOAP a nuestro servicio.
Por su parte, la llamada al servicio tambin difiere un poco de lo ya comentado a la hora de asociar
los parmetros de entrada del mtodo web. En este caso, construiremos en primer lugar el objeto
Cliente que queremos insertar en la base de datos a partir de los datos introducidos en la pantalla
de nuestra aplicacin de ejemplo. Tras esto crearemos un nuevo objeto PropertyInfo, al que
asociaremos el nombre, valor y tipo de nuestro cliente mediante sus mtodos setName(),
setValue() y setClass() respectivamente. Por ltimo, asociaremos este cliente como
parmetro de entrada al servicio llamando al metodo addProperty() igual que hemos hecho en
las anteriores ocasiones, con la diferencia de que esta vez lo llamaremos pasndole el objeto
PropertyInfo que acabamos de crear. Adems de esto, tendremos tambin que llamar
finalmente al mtodo addMapping() para asociar de alguna forma nuestro espacio de nombres y
nombre de clase Cliente con la clase real java. Veamos el cdigo para entenderlo mejor:
1
2 Cliente cli = new Cliente();
3 cli.nombre = txtNombre.getText().toString();
4 cli.telefono = Integer.parseInt(txtTelefono.getText().toString());
5
PropertyInfo pi = new PropertyInfo();
6 pi.setName("cliente");
7 pi.setValue(cli);
8 pi.setType(cli.getClass());
9
10request.addProperty(pi);
11
SoapSerializationEnvelope envelope =
12 new SoapSerializationEnvelope(SoapEnvelope.VER11);
13envelope.dotNet = true;
14
15envelope.setOutputSoapObject(request);
16
17envelope.addMapping(NAMESPACE, "Cliente", cli.getClass());
18
Todo esto
e lo harem mos en un nuevo
n botn aadido a laa aplicacin de ejemplo (Enviar2), cuyo
c efecto
tendr que serr idntico al que ya cream mos para la llamada al mttodo web
Nuevo oClienteS Simple(), aunque com mo acabamoos de ver suu implementaacin es algo diferente
debidoo a los distin
ntos parmetrros de entradda utilizadoss.
En loss dos artculoos anterioress (ste y stee) del Curso de Programaacin Androoid nos hemoos ocupado
de desscribir la forrma de consttruir un sisteema formadoo por un servvicio web SOOAP que acccede a una
base de
d datos exteerna y una applicacin Anndroid que, a travs de esste servicio, es capaz dee manipular
dichoss datos.
En estte nuevo arttculo vamoos a crear unn sistema siimilar, pero esta vez haaciendo uso de la otra
alternaativa por exccelencia a la hora de creaar servicios web, y no ess otra de utillizar servicioos web tipo
REST T. Las famosas APIs quue publican muchos de los sitios web w actualm mente no sonn ms que
serviciios web de este
e tipo, aunnque en la mayora
m de loos casos conn medidas dee seguridad adicionales
a
tales como
c autentiicacin OAuuth o similarees.
REST tambin se asienta sobbre el protoccolo HTTP como
c mecannismo de transporte entrre cliente y
serviddor, ya verem
mos despuss en qu meddida. Y en cuanto
c al forrmato de los datos transsmitidos, a
diferenncia de SOAAP, no se im mpone ninguuno en conccreto, aunquue lo ms haabitual actualmente es
intercaambiar la in
nformacin ene formato XML o JSO ON. Ya quee en el casoo de SOAP utilizamos
XML, en este nuev vo artculo utilizaremos
u JSON para construir nuuestro ejempllo.
Tambiin vamos a utilizar unn frameworkk distinto para construiir el servicio, aunque seguiremos
s
hacinndolo en Vissual Studio y en lenguajje C#. En esste caso, en vez de utilizzar ASP.NEET a secas,
vamoss a utilizar el
e frameworkk especfico ASP.NET MVC M d direccionamiento se
3, cuyyo sistema de
ajusta mejor a loss principios de REST, donde
d cada recurso
r [en nuestro casoo cada cliente] debera
ser accesible med diante su proopia URL nnica. Podiss descargar MVC3
M desdde su pginaa oficial de
Microsoft.
En estte primer arttculo sobre servicios REEST vamos a describir laa construccin del servicio web en
s, y dedicaremos
d un segundoo artculo a explicar
e mo podemos acceder a este
cm e servicio desde una
aplicaccin Android.
Una vez
v que ya teenemos prepparada toda lal estructuraa de nuestro proyecto em
mpecemos a aadir los
elemenntos necesarrios. Lo primmero que vaamos a creaar ser una nueva clasee Cliente,, igual que
hicimoos en el ejem
mplo anterioor con SOAP
P. La colocarremos en la carpeta Appi/Models y el cdigo
es el mismo
m que ya
y vimos:
1
2name
espace Serv
vicioWebRes st.Areas.A
Api.Models
{
3 public class Cliente
4 {
5 publicc int Id { get; set; }
6 publicc string Nombre { gett; set; }
publicc int Telefono { get;; set; }
7 }
8}
9
El siguiente elemento a aadir ser una nueva clase que contenga todas las operaciones que
queramos realizar sobre nuestra base de datos de clientes. Llamaremos a la clase
ClienteManager. En este caso s vamos a aadir las cuatro operaciones bsicas sobre clientes, y
una adicional para obtener el listado completo, de forma que ms tarde podamos mostrar la
implementacin en Android de todos los posibles tipos de llamada al servicio. Los mtodos que
aadiremos sern los siguientes:
Los dos primeros mtodos nos servirn para recuperar clientes de la base de datos, tanto por su ID
para obtener un cliente concreto, como el listado completo que devolver una lista de clientes. Los
otros tres mtodos permitirn insertar, actualizar y eliminar clientes a partir de su ID y los datos de
entrada (si aplica). El cdigo de todos estos mtodos es anlogo a los ya implementados en el caso
de SOAP, por lo que no nos vamos a parar en volverlos a comentar, tan slo decir que utilizan la api
clsica de ADO.NET para el acceso a SQL Server. En cualquier caso, al final del artculo tenis
como siempre el cdigo fuente completo para poder consultar lo que necesitis. A modo de ejemplo
veamos la implementacin de los mtodos ObtenerClientes() e InsertarCliente().
Hasta ahora, todo el cdigo que hemos escrito es bastante genrico y nada tiene que ver con que
nuestro proyecto sea de tipo MVC. Sin embargo, los dos siguientes elementos s que estn
directamente relacionados con el tipo de proyecto que tenemos entre manos.
Lo siguiente que vamos a aadir ser un controlador a nuestro servicio web. Este controlador (clase
ClientesController) ser el encargado de contener las diferentes acciones que se podrn
llamar segn la URL y datos HTTP que recibamos como peticin de entrada al servicio. Para
nuestro ejemplo, aadiremos tan slo dos acciones, una primera dirigida a gestionar todas las
peticiones que afecten a un nico cliente (insertar, actualizar, eliminar y obtener por ID), y otra que
trate la peticin del listado completo de clientes. Las llamaremos Clientes() y Cliente()
respectivamente. Estas acciones harn uso de una instancia de la clase ClienteManager creada
anteriormente para realizar las acciones necesarias contra la base de datos. Cada accin ser
tambin responsable de formatear sus resultados al formato de comunicacin que hayamos elegido,
en nuestro caso JSON.
[HttpGet]
1public JsonResult Clientes()
2{
3 return Json(this.clientesManager.ObtenerClientes(),
JsonRequestBehavior.AllowGet);
4}
5
6
Habris notado tambin que hemos precedido el mtodo con el atributo [HttpGet]. Para intentar
explicar esto me hace falta seguir hablando de los principios de diseo de REST. Este tipo de
servicios utiliza los propios tipos de peticin definidos por el protocolo HTTP para diferenciar entre
las operaciones a realizar por el servicio web. As, el propio tipo de peticin HTTP realizada (GET,
POST, PUT o DELETE), junto con la direccin URL especificada en la llamada, nos determinar la
operacin a ejecutar por el servicio web. En el caso ya visto, el atributo [HttpGet] nos indica
que dicho mtodo se podr ejecutar al recibirse una peticin de tipo GET.
Entenderemos todo esto mejor ahora cuando veamos el cdigo de la accin Cliente(). En esta
accin, dependiente del tipo de peticin HTTP recibida, tendremos que llamar a un mtodo u otro
del servicio web. As, usaremos POST para las inserciones de clientes, PUT para las
actualizaciones, GET para la consulta por ID y DELETE para las eliminaciones. En este caso no
precedemos el mtodo por ningn atributo, ya que la misma accin se encargar de tratar diferentes
tipos de peticin.
Algunos de vosotros seguro que os estis preguntando cmo distinguir el servicio cundo llamar a
la accin Clientes() para obtener el listado completo, o a la accin Cliente() para obtener
un nico cliente por su ID, ya que para ambas operaciones hemos indicado que se recibir el tipo de
peticin http GET.
Pues bien, aqu es donde nos va a ayudar el ltimo elemento a aadir al servicio web. Realmente no
lo aadiremos, sino que lo modificaremos, ya que es un fichero que ya ha creado Visual Studio por
nosotros. Se trata de la clase ApiAreaRegistration. La funcin de esta clase ser la de dirigir
las peticiones recibidas hacia una accin u otra del controlador segn la URL utilizada al realizarse
la llamada al servicio web.
En nuestro caso de ejemplo, vamos a reconocer dos tipos de URL. Una de ellas para acceder a la
lista completa de cliente, y otra para realizar cualquier accin sobre un cliente en concreto:
Cada uno de estos patrones tendremos que registrarlos mediante el mtodo MapRoute() dentro
del mtodo RegisterArea() que ya tendremos creado dentro de la clase
ApiAreaRegistration. As, para registrar el primer tipo de URL haremos lo siguiente:
1
2context.MapRoute(
"AccesoClientes",
3 "Api/Clientes",
4 new
5 {
6 controller = "Clientes",
action = "Clientes"
7 }
8);
9
Como primer parmetro de MapRoute() indicamos un nombre descriptivo para el patrn de URL.
El segundo parmetro es el patrn en s, que en este caso no tiene partes variables. Por ltimo
indicamos el controlador al que se dirigirn las peticiones que sigan este patrn eliminando el sufijo
Controller (en nuestro caso ser el controlador ClientesController) y la accin concreta a
ejecutar dentro de dicho controlador (en nuestro caso la accin Clientes()).
Para el segundo tipo de URL ser muy similar, con la nica diferencia de que ahora habr una parte
final variable que se corresponder con el ID del cliente y que asignaremos al parmetro id de la
accin. En este caso adems, dirigiremos la peticin hacia la accin Cliente(), en vez de
Clientes().
1
2 context.MapRoute(
"AccesoCliente",
3 "Api/Clientes/Cliente/{id}",
4 new
5 {
6 controller = "Clientes",
7 action = "Cliente",
id = UrlParameter.Optional
8 }
9 );
10
Como todo esto en cuenta, y por recapitular un poco, las posibles llamadas a nuestro servicio sern
las siguientes:
GET /Api/Clientes
GET /Api/Clientes/Cliente/3
Insertar un nuevo cliente con los datos aportados en la peticin en formato JSON.
Actualizar el cliente con el ID indicado en la URL con los datos aportados en la peticin en
formato JSON.
DELETE /Api/Clientes/Cliente/3
Llegados aqu, tan slo tenemos que ejecutar nuestro proyecto y esperar a que se abra el navegador
web. En principio no se mostrar un error por no encontrar la pgina principal de la aplicacin, ya
que no hemos creado ninguna, pero nos asegurar que el servidor de prueba est funcionando, por
lo que nuestro servicio debera responder a peticiones.
As, si escribimos en la barra de direcciones por ejemplo la siguiente direccin (el puerto puede
variar):
http://localhost:1234/Api/Clientes/Cliente/4
deberamos recibir un fichero en formato JSON que contuviera los datos del cliente con ID = 4 de
nuestra base de datos. Sera un fichero con contenido similar al siguiente:
{"Id":4,"Nombre":"cliente4","Telefono":4444}
En el siguiente artculo veremos cmo construir una aplicacin Android capaz de acceder a este
servicio y procesar los resultados recibidos. Podis descargar el cdigo fuente completo de este
artculo desde este enlace.
Y tal como
c hicimoos en el casoo de SOAP, vamos a crrear una apliicacin de ejjemplo que llame l a las
distinttas funcioness de nuestroo servicio weeb. En este casoc mpondr de 5 botones,
la apliccacin se com
uno poor cada una de las accioones que hem mos implemeentado en ell servicio weeb (insertar, actualizar,
eliminnar, recuperaar un cliente,, y listar todoos los clientees).
http:
://10.0.2
2.2:2731/Api/Clientes/Cl
liente
{Nomb
bre:cccc
c, Telefono:12345678}
Pues bien,
b onseguir estoo comenzaremos por crear un nuevo objeto Htt
para co tpClient, que ser el
encarggado de reallizar la comuunicacin HTTP
H con el servidor a partir
p de loss datos que nosotros
n le
proporrcionemos. Tras esto crrearemos la peticin POOST creanddo un nuevoo objeto Htt tpPost e
indicando la URL de llamada al servicio. Modificaremos mediante setHeader() el atributo http
content-type para indicar que el formato de los datos que utilizaremos en la comunicacin, que
como ya indicamos ser JSON (cuyo MIME-Type correspondiente es application/json).
El siguiente paso ser crear el objeto JSON a incluir con la peticin, que deber contener los datos
del nuevo cliente a insertar. Para ello creamos un nuevo objeto JSONObject y le aadimos
mediante el mtodo put() los dos atributos necesarios (nombre y telfono) con sus valores
correspondientes, que los obtenemos de los cuadros de texto de la interfaz, llamados txtNombre y
txtTelefono.
Por ltimo asociaremos este objeto JSON a nuestra peticin HTTP convirtindolo primero al tipo
StringEntity e incluyndolo finalmente en la peticin mediante el mtodo setEntity().
Una vez creada nuestra peticin HTTP y asociado el dato de entrada, tan slo nos queda realizar la
llamada al servicio mediante el mtodo execute() del objeto HttpClient y recuperar el
resultado mediante getEntity(). Este resultado lo recibimos en forma de objeto HttpEntity,
pero lo podemos convertir fcilmente en una cadena de texto mediante el mtodo esttico
EntityUtils.toString().
http://10.0.2.2:2731/Api/Clientes/Cliente
Pero en este caso, el objeto JSON a enviar como entrada deber contener no slo los nuevos valores
de nombre y telfono sino tambin el ID del cliente a actualizar, por lo que tendra una estructura
anloga a la siguiente:
Para actualizar el cliente procederemos de una forma muy similar a la ya comentada para la
insercin, con las nicas diferencias de que en este caso la accin HTTP utilizada ser PUT (objeto
HttpPut) y que el objeto JSON de entrada tendr el campo ID adicional.
1
2
3 HttpClient httpClient = new DefaultHttpClient();
4
HttpPut put = new HttpPut("http://10.0.2.2:2731/Api/Clientes/Cliente");
5 put.setHeader("content-type", "application/json");
6
7 try
8 {
9 //Construimos el objeto cliente en formato JSON
JSONObject dato = new JSONObject();
10
11
dato.put("Id", Integer.parseInt(txtId.getText().toString()));
12 dato.put("Nombre", txtNombre.getText().toString());
13 dato.put("Telefono", Integer.parseInt(txtTelefono.getText().toString()));
14
15 StringEntity entity = new StringEntity(dato.toString());
16 put.setEntity(entity);
17
HttpResponse resp = httpClient.execute(put);
18 String respStr = EntityUtils.toString(resp.getEntity());
19
20 if(respStr.equals("true"))
21 lblResultado.setText("Actualizado OK.");
22}
23catch(Exception ex)
{
24 Log.e("ServicioRest","Error!", ex);
25}
26
27
Eliminacin de un cliente
http://10.0.2.2:2731/Api/Clientes/Cliente/id_cliente
donde id_cliente ser el ID del cliente a eliminar. Adems, utilizaremos la accin http
DELETE (objeto HttpDelete) para identificar la operacin que queremos realizar. En este caso
no ser necesario pasar ningn objeto de entrada junto con la peticin, por lo que el cdigo quedar
an ms sencillo que los dos casos anteriores.
1
2 HttpClient httpClient = new DefaultHttpClient();
3
4 String id = txtId.getText().toString();
5
6 HttpDelete del =
7 new HttpDelete("http://10.0.2.2:2731/Api/Clientes/Cliente/" + id);
8
del.setHeader("content-type", "application/json");
9
10try
11{
12 HttpResponse resp = httpClient.execute(del);
13 String respStr = EntityUtils.toString(resp.getEntity());
14
15 if(respStr.equals("true"))
lblResultado.setText("Eliminado OK.");
16}
17catch(Exception ex)
18{
19 Log.e("ServicioRest","Error!", ex);
}
20
21
Como podis ver, al principio del mtodo obtenemos el ID del cliente desde la interfaz de la
aplicacin y lo concatenamos con la URL base para formar la URL completa de llamada al servicio.
Obtener un cliente
Esta operacin es un poco distinta a las anteriores, ya que en este caso el resultado devuelto por el
servicio ser un objeto JSON y no un valor simple como en los casos anteriores. Al igual que en el
caso de eliminacin de clientes, la URL a utilizar ser del tipo:
http://10.0.2.2:2731/Api/Clientes/Cliente/id_cliente
En este caso utilizaremos un tipo de peticin http GET (objeto HttpGet) y la forma de realizar la
llamada ser anloga a las anteriores. Donde aparecern las diferencias ser a la hora de tratar el
resultado devuelto por el servicio tras llamar al mtodo getEntity(). Lo que haremos ser crear
un nuevo objeto JSONObject a partir del resultado textual de getEntity(). Hecho esto,
podremos acceder a los atributos del objeto utilizando para ello los mtodos get()
correspondientes, segn el tipo de cada atributo (getInt(), getString(), etc). Tras esto
mostraremos los datos del cliente recuperado en la etiqueta de resultados de la interfaz
(lblResultados).
1
2
HttpClient httpClient = new DefaultHttpClient();
3
4 String id = txtId.getText().toString();
5
6 HttpGet del =
7 new HttpGet("http://10.0.2.2:2731/Api/Clientes/Cliente/" + id);
8
9 del.setHeader("content-type", "application/json");
10
11try
{
12 HttpResponse resp = httpClient.execute(del);
13 String respStr = EntityUtils.toString(resp.getEntity());
14
15 JSONObject respJSON = new JSONObject(respStr);
16
17 int idCli = respJSON.getInt("Id");
String nombCli = respJSON.getString("Nombre");
18 int telefCli = respJSON.getInt("Telefono");
19
20 lblResultado.setText("" + idCli + "-" + nombCli + "-" + telefCli);
21}
22catch(Exception ex)
23{
Log.e("ServicioRest","Error!", ex);
24}
25
26
Una vez ms como podis comprobar el cdigo es muy similar al ya visto para el resto de
operaciones.
Por ltimo vamos a ver cmo podemos obtener el listado completo de clientes. El inters de esta
operacin est en que el resultado recuperado de la llamada al servicio ser un array de objetos de
tipo cliente, por supuesto en formato JSON. La accin http utilizada ser una vez ms la accin
GET, y la URL para recuperar el listado de clientes ser:
http://10.0.2.2:2731/Api/Clientes
De nuevo, la forma de llamar al servicio ser anloga a las anteriores hasta la llamada a
getEntity() para recuperar los resultados. En esta ocasin, dado que recibimos un array de
elementos, convertiremos este resultado a un objeto JSONArray, y hecho esto podremos acceder a
cada uno de los elementos del array mediante una llamada a getJSONObject(), al que iremos
pasando el ndice de cada elemento. Para saber cuntos elementos contiene el array podremos
utilizar el mtodo length() del objeto JSONArray. Por ltimo, el acceso a los atributos de
cada elemento del array lo realizamos exactamente igual como ya lo hicimos en la operacin
anterior de obtencin de cliente por ID.
1
2
3 HttpClient httpClient = new DefaultHttpClient();
4
5 HttpGet del =
6 new HttpGet("http://10.0.2.2:2731/Api/Clientes");
7
8 del.setHeader("content-type", "application/json");
9
10try
{
11 HttpResponse resp = httpClient.execute(del);
12 String respStr = EntityUtils.toString(resp.getEntity());
13
14 JSONArray respJSON = new JSONArray(respStr);
15
16 String[] clientes = new String[respJSON.length()];
17
for(int i=0; i<respJSON.length(); i++)
18 {
19 JSONObject obj = respJSON.getJSONObject(i);
20
21 int idCli = obj.getInt("Id");
22 String nombCli = obj.getString("Nombre");
23 int telefCli = obj.getInt("Telefono");
24
clientes[i] = "" + idCli + "-" + nombCli + "-" + telefCli;
25 }
26
27 //Rellenamos la lista con los resultados
28 ArrayAdapter<String> adaptador =
29 new ArrayAdapter<String>(ServicioWebRest.this,
android.R.layout.simple_list_item_1, clientes);
30
31 lstClientes.setAdapter(adaptador);
32}
33catch(Exception ex)
34{
35 Log.e("ServicioRest","Error!", ex);
}
36
37
38
Tras obtener nuestro array de clientes, para mostrar los resultados hemos aadido a la interfas de
nuestra aplicacin de ejemplo un control tipo ListView (llamado lstClientes) que hemos
rellenado a travs de su adaptador con los datos de los clientes recuperados.
Como siempre, po
odis descarggar el cdigoo fuente com
mpleto de estte artculo mediante
m este enlace.
Para conseguir
c essto se nos podran
p ocurrrir varias soluciones, por
p ejemplo mantener abierta
a una
conexiin permaneente con el servidor dee forma quee ste le puudiera comuunicar inmediatamente
cualquuier nuevo evento
e a nueestra aplicaciin. Esta tccnica, aunquue es viable y efectiva, requiere
r de
muchos recursos abiertos constantemente en nuestro dispositivo, aumentando por tanto el consumo
de CPU, memoria y datos de red necesarios para ejecutar la aplicacin. Otra solucin utilizada
habitualmente sera hacer que nuestra aplicacin mvil revise de forma peridica en el servidor si
existe alguna novedad que notificar al usuario. Esto se denomina polling, y requiere muchos menos
recursos que la opcin anterior, pero tiene un inconveniente que puede ser importante segn el
objetivo de nuestra aplicacin: cualquier evento que se produzca en el servidor no se notificar al
usuario hasta la prxima consulta al servidor que haga la aplicacin cliente, que podra ser mucho
tiempo despus.
Para solventar este problema Google introdujo en Android, a partir de la versin 2.2 (Froyo), la
posibilidad de implementar notificaciones de tipo push, lo que significa que es el servidor el que
inicia el proceso de notificacin, pudiendo realizarla en el mismo momento que se produce el
evento, y el cliente se limita a esperar los mensaje sin tener que estar periodicamente consultando
al servidor para ver si existen novedades, y sin tener que mantener una conexin permanentemente
abierta con ste. En la arquitectura de Google, todo esto se consigue introduciendo un nuevo actor
en el proceso, un servidor de mensajera push o cloud to device (que se traducira en algo as
como mensajes de la nube al dispositivo), que se situara entre la aplicacin web y la aplicacin
mvil. Este servidor intermedio se encargar de recibir las notificaciones enviadas desde las
aplicaciones web y hacerlas llegar a las aplicaciones mviles instaladas en los dispositivos
correspondientes. Para ello, deber conocer la existencia de ambas aplicaciones, lo que se consigue
mediante un protocolo bien definido de registros y autorizaciones entre los distintos actores que
participan en el proceso. Veremos ms adelante cmo implementar este proceso.
Este servicio de Google recibi en sus comienzos las siglas C2DM (Cloud to Device Messaging),
pero recientemente y coincidiendo con su salida de fase beta ha modificado su nombre a GCM
(Google Cloud Messaging). Y lo primero que debemos comentar es la forma de darnos de alta en el
servicio, que a pesar de ser gratuito requiere de un proceso previo de registro y la generacin de una
ApiKey, algo similar a lo que ya vimos al hablar de la utilizacin de mapas en Android. Para hacer
esto debemos acceder a la consola de APIs de Google, en siguiente direccin:
https://code.google.com/apis/console
Suponiendo que es la primera vez que accedemos a esta consola, nos aparecer la pantalla de
bienvenida y la opcin de crear un nuevo proyecto.
Googlle API Conso
ole Create Project
Al nm mero que apparece tras laa etiqueta ##project: loo llamaremoss Sender ID
D y debemoos anotarlo
ya que nos har falta ms adelante
a commo identificaador nico de la aplicaacin web emisora
e de
nuestrros mensajess.
Se noss presentar entonces unna ventana donde tendrem mos que aceeptar las conndiciones dell servicio y
tras ssto el serviciio quedar activado,
a aunnque an noos faltar un ltimo pasoo para poderr utilizarlo.
Como en el caso de la utilizzacin de la api de Gooogle Maps, para p hacer uso
u del servvicio GCM
tendreemos que geenerar una ApiKey
A que nos sirva posteriormentte como ideentificacin de acceso.
Para ello
e accedem
mos al menn Api Acccess y pulsaremos sobbre el botnn Create new
n Server
Key.
Nos apparecer un dilogo llammado Conffigure Serveer Key for API A Project, que aceptaaremos sin
ms puulsando el botn Createe, sin necessidad de relleenar nada.
Y ya tendramos
t creada
c nuesttra API Key,, que aparecer en la secccin Simpple API Acceess con el
ttulo Key
for serrver apps (with IP lockinng).
Con esto
e ya nos habramos registrado
r coorrectamentee en el servvicio GCM y habramoss generado
nuestrra API Key para identifficarnos, conn lo que esttaramos en disposicinn de construuir nuestras
aplicacciones clien
nte y serviddor, algo quue veremos en los dos prximos artculos.
a Enn ste nos
parareemos un pocco ms para hacer una descripcin
d a grandes raasgos de la arquitectura
a que tendr
nuestrro sistema y de cmo fluir
f la infoormacin enntre cada unno de los seervicios y applicaciones
impliccados.
Como hemos indicado antes, para p aseguraar la correctaa comunicaccin entre loos tres sistem
mas se hace
necesaario un protoocolo de reggistros y autoorizaciones que proporccione seguriddad y calidad a todo el
processo. Este procceso se resum
me en el siguuiente grficco (click paraa ampliar), que
q intentar explicar a
continnuacin.
Comenntemos brev
vemente cadaa uno de los pasos indicaados en el diiagrama.
El prim
mer paso, au unque no apaarece en el grfico,
g seraa la autenticaacin de nueestra aplicaciin web en
el servvicio GCM. En la anterior versinn del servicio GCM (llaamada C2DM M) esto debba hacerse
mediaante la utilizaacin de otraa API de Gooogle llamadda ClientLoggin, o a travs del protoccolo OAuth
2.0, ammbas dirigiddas a obteneer un token de autorizaacin que deeba enviarsee posteriorm mente en el
resto de
d llamadas al servicio. Sin embarggo, con la lleegada de Gooogle Cloud Messaging, esto se ha
simpliificado mediiante la obttencin y usso de una API A Key, quue es precissamente lo que q hemos
comenntado unos prrafos
p ms arriba. Commo pasaba coon el token de d autorizaciin, nuestra nueva API
Key deber
d acommpaar a cadda una de laas llamadas que hagamoos al serviciio GCM dessde nuestra
aplicaccin web.
1. El siguientte paso es ell equivalentee al ya comeentado para el servidor pero esta veez desde el
punto de vista de la aplicacin cliente. La aplicacin Android deebe registraarse en los
servidores GCM comoo cliente cappaz de recibiir mensajes desde dichoo servicio. Para P esto es
necesario que
q el dispossitivo/emulaador cumplann una serie de d requisitos:
o Dissponer de An ndroid 2.2 o superior.
o Ten ner configurrada una cueenta de Googgle en el disppositivo o em mulador. Coonfigurable
dessde Ajustes / Cuentas y sincronizacin.
o Si se trata de un u dispositivvo real debee estar instaalada la Gooogle Play Store. Por el
conntrario si esttamos ejecuttando la apliicacin desdde el emuladdor bastar con
c usar un
targget que incluuya las APIss de Google.
2. Si el registro se finaliiza correctammente se reccibir un cdigo de regiistro (Registtration ID)
que la apllicacin clieente deber conservar. Adems, laa aplicacin Android deeber estar
preparada para recibir peridicameente refrescoos de este cdigo de regiistro, ya quee es posible
que el serrvidor GCM M invalide peeridicamennte este ID, genere unoo nuevo y loo vuelva a
notificar a la aplicacinn cliente.
3. Este nuevo o paso consiiste en enviaar, desde la aplicacin cliente
c a la aplicacin servidor,
s el
cdigo de registro GCM M recibido, el cual har las veces dee identificaddor nico dell cliente en
el servidor de forma que ste pueda indicar ms tarde el dispositivo mvil concreto al que
desea enviar un mensaje. La aplicacin servidora tendr que ser capaz por tanto de
almacenar y mantener todos los ID de registro de los distintos dispositivos mviles que se
registren como clientes capaces de recibir mensajes.
4. El ltimo paso ser obviamente el envo en s de un mensaje desde el servidor hasta un
cliente determinado, algo que se har a travs del servidor GCM (paso 4.1) y desde ste se
dirigir al cliente concreto que debe recibirlo (paso 4.2).
En el artculo anterior del curso hicimos una introduccin al servicio Google Cloud Messaging
(GCM), vimos cmo registrarnos y obtener la API Key necesaria para enviar mensajes y
describimos a alto nivel la arquitectura que tendr un sistema capaz de gestionar mensajera de tipo
push a travs de este servicio de Google. Este segundo artculo lo vamos a dedicar a la
implementacin de una aplicacin web capaz de enviar mensajes o notificaciones push a
dispositivos Android. En el prximo artculo veremos cmo desarrollar la aplicacin Android
cliente capaz de recibir estos mensajes.
Como ya viene siendo habitual en el curso, el sistema elegido para desarrollar la aplicacin web
ser ASP.NET, utilizando C# como lenguaje, y SQL Server como base de datos.
Como ya comentamos en el artculo anterior, la aplicacin web ser responsable de las siguientes
tareas:
En cuanto al punto 1, la aplicacin deber ser capaz de recibir los datos de registro de cada cliente
que se d de alta para recibir mensajes y almacenarlos en la base de datos. Esto lo haremos
mediante la creacin de un servicio web que exponga un mtodo capaz de recoger y almacenar los
datos de registro de un cliente. La aplicacin Android se conectar directamente a este servicio web
y llamar al mtodo con sus datos identificativos para registrarse como cliente capaz de recibir
notificaciones. Por supuesto que para esto se podra utilizar cualquier otro mecanismo distinto a
servicios web, por ejemplo una simple peticin HTTP al servidor pasando los datos como
parmetros, pero no nos vendr mal para seguir practicando con servicios web en android, que en
este caso ser de tipo SOAP.
Por su lado, el punto 2 lo resolveremos a modo de ejemplo con una pgina web sencilla en la que
podamos indicar el nombre de usuario de cualquiera de los dispositivos registrados en la base de
datos y enviar un mensaje de prueba a dicho cliente.
Vamos a empezar creando la base de datos, aunque no nos detendremos mucho porque ya vimos el
procedimiento por ejemplo en el primer artculo dedicado a servicios web SOAP. Tan slo decir
que crearemos una nueva base de datos llamada DBUSUARIOS, que tendr dos campos:
NombreUsuario y CodigoC2DM, el primero de ellos destinado a almacenar un nombre de
usuario identificativo de cada cliente registrado, y el segundo para almacenar el
RegistrationID de GCM recibido desde dicho cliente a travs del servicio web (recomiendo
consultar el artculo anterior para entender bien todo este protocolo requerido por GCM).
Una vez creada la base de datos vamos a crear en Visual Studio 2010 un nuevo proyecto C# de tipo
ASP.NET Web Application al que llamaremos GCMServer, y aadiremos a este proyecto un
nuevo componente de tipo Web Service llamado ServicioRegistroGCM.asmx. Todo este
procedimiento tambin se puede consultar en el artculo sobre servicios web SOAP en Android.
1 [WebMethod]
2 public
{
int RegistroCliente(string usuario, string regGCM)
3 SqlConnection con =
4 new SqlConnection(
5 @"Data Source=SGOLIVERPC\SQLEXPRESS;Initial
6 Catalog=DBUSUARIOS;Integrated Security=True");
7
con.Open();
8
9 string cod = CodigoCliente(usuario);
10
11 int res = 0;
12 string sql = "";
13
14 if (cod == null)
15 sql = "INSERT INTO Usuarios (NombreUsuario, CodigoC2DM) VALUES
(@usuario, @codigo)";
16 else
17 sql = "UPDATE Usuarios SET CodigoC2DM = @codigo WHERE NombreUsuario
18= @usuario";
19
20 SqlCommand cmd = new SqlCommand(sql, con);
21
cmd.Parameters.Add("@usuario", System.Data.SqlDbType.NVarChar).Value =
22usuario;
23 cmd.Parameters.Add("@codigo", System.Data.SqlDbType.NVarChar).Value =
24regGCM;
25
26 res = cmd.ExecuteNonQuery();
27
28 con.Close();
29
return res;
30}
El cdigo es sencillo, pero por qu es necesario considerar el caso del UPDATE? Como ya
advertimos en el artculo anterior, el servidor GCM puede en ocasiones refrescar (actualizar) el ID
de registro de un cliente comunicndoselo de nuevo a ste, por lo que a su vez la aplicacin cliente
tendr que hacer tambin la misma actualizacin contra la aplicacin web. Para ello, el cliente
simplemente volver a llamar al mtodo RegistroCliente() del servicio web pasando el
mismo nombre de usuario pero con el ID de registro actualizado. Para saber si el cliente est ya
registrado o no el mtodo se apoya en un mtodo auxiliar llamado CodigoCliente() que
realiza una bsqueda de un nombre de usuario en la base de datos para devolver su ID de registro en
caso de encontrarlo. El cdigo de este mtodo es igual de sencillo que el anterior:
Con esto ya tendramos implementado nuestro servicio web para el registro de clientes.
Para el envo de los mensajes utilizaremos directamente la pgina Default.aspx creada por defecto
al generar el proyecto de Visual Studio. Modificaremos esta pgina para aadir tan slo un cuadro
de texto donde podamos introducir el nombre de usuario asociado al cliente al que queremos enviar
un mensaje, un botn Enviar con el realizar el envo, y una etiqueta donde mostrar el estado del
envo. Quedara algo como lo siguiente:
Web Envo
E GCM
El bottn de envo
o, realizar una
u bsquedda en la basee de datos deld nombre de d usuario inntroducido,
recupeerar su Registration ID y enviar unn mensaje dee prueba conn la fecha-hoora actual.
1
2 prootected void Button3_
_Click(objeect sender,
, EventArgs
s e)
{
3 oRegistroG
Servicio GCM svc = new
n Servici ioRegistroG
GCM();
4
5 string codUsuario = svc.CoddigoClientee(TxtUsuari
io.Text);
6
7 bool res = enviarMMensajePrueba(codUsuuario);
8
9 if (res == true)
LblR
ResultadoMMensaje.Texxt = "Envoo OK";
10
else
11 LblR
ResultadoMMensaje.Texxt = "Envo NOK";
12}
13
https:///android.goo
ogleapis.com
m/gcm/send
La cabbecera de essta peticin debe contenner dos datoos esencialess. Por un laddo debemoss indicar la
API Key
K que gen neramos en el primer artculo
a (atriibuto Autho orization n), y por ottro lado el
formatto del contennido (en este caso, los parmetros
p d la API) que
de q vamos a incluir con la peticin
(atribuuto Conten nt-Type). GCM
G permitte formatearr los datos coomo JSON (para
( lo que habra que
indicaar el valor applicat
tion/json n) o como texto plano (para lo quue debemos utilizar el
valor applicat
tion/x-ww ww-form-u urlencode ed). En nuestro caso de ejemplo uttilizaremos
la seguunda opcinn.
Y eso es todo, largo de contar pero sencillo en el fondo. Veamos cmo podemos implementar esto
en C#, y para ello vamos a ver el cdigo del mtodo que dejamos antes pendiente,
enviarMensajePrueba(), y justo despus lo comentamos.
Como vemos el mtodo recibe directamente como parmetro el Registration ID del cliente al que se
va a enviar el mensaje. En primer lugar configuro todos los parmetros que pasar en la llamada a la
API, que en este caso de ejemplo tan slo sern, adems del registration_id ya comentado,
el colapse_key, y una dato adicional que llamar data.msg (recordemos el prefijo data.
obligatorio para este tipo de datos adicionales) con un mensaje de prueba que contenga la
fecha/hora actual. Toda la cadena con estos parmetros la construyo utilizando un objeto
StringBuilder. Lo nico reseable hasta ahora sera la forma de aadir el parmetro adicional
data.msg, que lo hago mediante la creacin de un objeto Dictionary y su mtodo add()
para aadir el dato, para poco despus generar la cadena final recorriendo este diccionario en un
bucle foreach. En este caso no sera necesaria toda esta parafernalia dado que slo vamos a
aadir un dato adicional, pero lo he dejado as para que tengis un ejemplo de patrn mediante el
cual podeis aadir ms de un dato adicional de una forma sencilla y organizada.
Una vez creada la cadena de parmetros y datos que incluiremos como contenido de la peticin
creamos dicha peticin como un objeto HttpWebRequest indicando la URL del servicio.
Indicamos que la peticin ser de tipo POST asignando la propiedad Method, y configuramos la
cabecera con los dos datos que ya hemos comentado antes en el artculo (Authorization y
Content-Type). El primero de ellos al ser personalizado debemos aadirlo utilizando el
mtodo Add() de la coleccin Headers de la peticin. En cambio para el segundo existe una
propiedad del objeto HttpWebRequest con la que podemos establecerlo directamente, llamada
ContentType. Hecho esto, tan slo nos queda aadir el contenido a la peticin, lo que
conseguimos obteniendo el stream de escritura de la peticin mediante GetRequestStream() y
escribiendo en l nuestra cadena de parmetros mediante el mtodo Write().
Y con esto habramos terminado la implementacin del servidor. Haremos las pruebas pertinentes y
mostrar el resultado cuando implementemos la aplicacin Android cliente en el prximo artculo.
Podis descargar el cdigo fuente completo de este artculo desde este enlace.
En los dos anteriores (I y II) artculos del curso hemos hablado sobre el servicio Google Cloud
Messaging y hemos visto como implementar una aplicacin web que haga uso de dicho servicio
para enviar mensajes a dispositivos Android. Para cerrar el crculo, en este nuevo artculo nos
centraremos en la aplicacin Android cliente.
Esta aplicacin cliente, como ya hemos comentado en alguna ocasin ser responsable de:
1. Registrarse contra los servidores de GCM como cliente capaz de recibir mensajes.
2. Almacenar el Registration ID recibido como resultado del registro anterior.
3. Comunicar a la aplicacin web el Registration ID de forma que sta pueda enviarle
mensajes.
4. Recibir y procesar los mensajes desde el servidor de GCM.
Para las tareas 1 y 4 utilizaremos una librera especfica de GCM que nos proporciona Google para
facilitarnos la vida como desarrolladores (es posible hacerlo sin el uso de libreras externas, aunque
requiere ms trabajo). El punto 2 lo resolveremos fcilmente mediante el uso de SharedPreferences.
Y por ltimo el punto 3 lo implementaremos mediante la conexin al servicio web SOAP que
creamos en el artculo anterior, sirvindonos para ello de la librera ksoap2, tal como ya describimos
en los artculos sobre servicios web SOAP en Android.
Durante el artculo construiremos una aplicacin de ejemplo muy sencilla con el siguiente aspecto:
Aplicaacin Ejemplo Android GCM
G
En estta aplicacin
n, el usuario podr introdducir un nom mbre de usuaario identificcativo y pulssar el botn
Acepptar para quue quede guaardado en las preferenciaas de la apliicacin. Trass esto podr registrarse
como cliente capaaz de recibirr mensajes desde
d GCM pulsando el botn Reggistrar GCM M. En caso
de reaalizarse de fo
orma correctta este regisstro la aplicaacin enviarr automticamente el Registration
R
ID reccibido y el nombre de usuario alm macenado a la aplicacin web a traavs del serrvicio web.
Igualmmente el usu uario podr des-registraarse en el servicio
s GCM para no recibir mss mensajes
pulsanndo el botnn Des-registrar GCM. Obviamentte todo este proceso de registro y des-registro
d
debera hacerse de
d forma trannsparente paara el usuarrio de una aplicacin
a reeal, en esta ocasin he
colocaado botones para ello sllo por motivvos didcticoos.
Para ello
e vamos a crear un nuuevo proyectto Android sobre s un tarrget de Andrroid 2.2 o suuperior que
incluyya las libreraas de Googlee, y vamos a incluir en su
s carpeta /libs las librerras de ksoapp2 (esto ya
vimos como haceerlo en el artculo
a sobrre servicios web) y la librera clieente de GCM M llamada
gcm.jjar. Cmo o podemos obtener estta librera? Para conseguirla debem mos instalaar desde el
Androoid SDK Man nager el paqquete extra lllamado Gooogle Cloud Messaging
M fo Android Library.
for L
Librerra Google Cloud
C Messagging for Anddroid
Una vez
v instalado
o podemos irr a la ruta RAIZ_SDKK_ANDROID D/extras/ /google/g gcm/gcm-
clien nt/dist donde debber apareceer la librera gcm.jar que debem
mos aadir a nuestro
proyeccto.
1<use
es-sdk
2 android:m
minSdkVersion="8"
3 android:t
targetSdkV
Version="16
6" />
A conttinuacin a
adiremos loos permisos necesarios
n paara ejecutar la aplicacinn y utilizar GCM:
G
1<per
rmission an
ndroid:name
e="net.sgo
oliver.andr
roid.permi
ission.C2D__MESSAGE"
2andr
roid:protec
ctionLevel="signaturre" />
<use
es-permissi
ion android
d:name="net.sgoliver
r.android.p
permissionn.C2D_MESSA
AGE" />
3<use
es-permissi
ion android
d:name="com.google.a
android.c2d
dm.permisssion.RECEIV
VE" />
4<use
es-permissi
ion android
d:name="android.perm
mission.INT
TERNET" />
5<use
es-permissi
ion android
d:name="android.perm
mission.WAK
KE_LOCK" />>
1
2 <application
android:icon="@drawable/ic_launcher"
3 android:label="@string/app_name"
4 android:theme="@style/AppTheme" >
5
6 ...
7
8 <receiver android:name="com.google.android.gcm.GCMBroadcastReceiver"
9 android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
10 <action android:name="com.google.android.c2dm.intent.RECEIVE" />
11 <action android:name="com.google.android.c2dm.intent.REGISTRATION"
12/>
13 <category android:name="net.sgoliver.android" />
</intent-filter>
14 </receiver>
15
16 <service android:name=".GCMIntentService" />
17
18</application>
19
Una vez definido nuestro AndroidManifest con todos los elementos necesarios vamos a empezar a
implementar la funcionalidad de nuestra aplicacin de ejemplo.
1
2 btnGuardarUsuario.setOnClickListener(new OnClickListener() {
@Override
3 public void onClick(View v) {
4 SharedPreferences prefs =
5 getSharedPreferences("MisPreferencias", Context.MODE_PRIVATE);
6
7 SharedPreferences.Editor editor = prefs.edit();
8 editor.putString("usuario", txtUsuario.getText().toString());
editor.commit();
9 }
10});
11
Como podis comprobar nos limitamos a almacenar una nueva propiedad llamada usuario con el
texto introducido en el cuadro de texto de la interfaz.
El siguiente botn es el de registro del cliente en GCM, y aqu s nos detendremos un poco para
comentar primero cmo funciona internamente este procedimiento.
La aplicacin android debe solicitar el registro en el servicio GCM mediante una peticin HTTP
POST similar a la que ya vimos en el artculo anterior para la aplicacin web. Por suerte, este
procedimiento se ve simplificado enormemente con el uso de la librera gcm.jar, ya que el montaje
y ejecucin de esta peticin queda encapsulado como una simple llamada a un mtodo esttico de la
clase GCMRegistrar, definida en la librera. Por su parte, tanto la respuesta a esta peticin de
registro como la posterior recepcin de mensajes se reciben en la aplicacin Android en forma de
intents. Y aqu es donde entran en juego los dos componentes que hemos definido anteriormente en
nuestro AndroidManifest. El receiver GCMBroadcastReceiver ser el encargado de esperar y
capturar estos intents cuando se reciban y posteriormente lanzar el servicio
GCMIntentService donde se procesarn en un hilo independiente estas respuestas segn el
intent recibido. Como ya dijimos, el broadcast receiver no ser necesario crearlo ya que
utilizaremos el ya proporcionado por la librera. En cambio la implementacin del
IntentService s ser de nuestra responsabilidad. Aunque una vez ms la librera de GCM nos
facilitar esta tarea como ya veremos ms adelante.
Veamos primero cmo realizar el registro de nuestra aplicacin en GCM al pulsar el botn de
registro. Como hemos dicho esto se limitar a llamar a un mtodo esttico, llamado register(),
de la clase GCMRegistrar. La nica precaucin que tomaremos es verificar previamente si
estamos ya registrados, algo que podremos hacer fcilmente llamando al mtodo
getRegistrationId() de la misma clase. El mtodo register() recibir dos parmetros,
el primero de ellos una referencia al contexto de la aplicacin (normalmente la actividad desde la
que se llama) y en segundo lugar el Sender ID que obtuvimos cuando creamos el nuevo proyecto
en la Google API Console.
1 btnRegistrar.setOnClickListener(new OnClickListener() {
2 @Override
3 public void onClick(View v) {
4
//Si no estamos registrados --> Nos registramos en GCM
5 final String regId =
6 GCMRegistrar.getRegistrationId(GcmActivity.this);
7 if (regId.equals("")) {
8 GCMRegistrar.register(GcmActivity.this, "224338875065");
9 //Sender ID
} else {
10 Log.v("GCMTest", "Ya registrado");
11 }
12 }
13});
As de sencillo y rpido. Pero esto es slo la peticin de registro, ahora nos tocar esperar la
respuesta, algo que veremos en breve.
Por su parte, el botn de des-registro se implementar de forma anloga, con la nica diferencia
que esta vez utilizaremos el mtodo unregister() de la clase GCMRegistrar.
1
btnDesRegistrar.setOnClickListener(new OnClickListener() {
2 @Override
3 public void onClick(View v) {
4
5 //Si estamos registrados --> Nos des-registramos en GCM
6 final String regId =
GCMRegistrar.getRegistrationId(GcmActivity.this);
7 if (!regId.equals("")) {
8 GCMRegistrar.unregister(GcmActivity.this);
9 } else {
10 Log.v("GCMTest", "Ya des-registrado");
11 }
}
12});
13
Ahora toca procesar las respuestas. Como hemos dicho, para hacer esto tendremos que implementar
el servicio GCMIntentService. Pero no lo haremos desde cero, ya que la librera de GCM nos
proporciona una clase base GCMBaseIntentService de la cual podemos extender la nuestra,
con la ventaja de que tan slo tendremos que sobrescribir unos pocos mtodos a modo de callbacks,
uno por cada posible respuesta o mensaje que podemos recibir desde el servicio GCM. Estos
mtodos son:
1
2
3 private void registroServidor(String usuario, String regId)
4 {
final String NAMESPACE = "http://sgoliver.net/";
5 final String URL="http://10.0.2.2:1634/ServicioRegistroGCM.asmx";
6 final String METHOD_NAME = "RegistroCliente";
7 final String SOAP_ACTION = "http://sgoliver.net/RegistroCliente";
8
9 SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
10
request.addProperty("usuario", usuario);
11 request.addProperty("regGCM", regId);
12
13 SoapSerializationEnvelope envelope =
14 new SoapSerializationEnvelope(SoapEnvelope.VER11);
15
16 envelope.dotNet = true;
17
18 envelope.setOutputSoapObject(request);
19
HttpTransportSE transporte = new HttpTransportSE(URL);
20
21 try
22 {
23 transporte.call(SOAP_ACTION, envelope);
24
25 SoapPrimitive resultado_xml =(SoapPrimitive)envelope.getResponse();
String res = resultado_xml.toString();
26
27 if(res.equals("1"))
28 Log.d("GCMTest", "Registro WS: OK.");
29 }
30 catch (Exception e)
31 {
Log.d("GCMTest", "Registro WS: NOK. " + e.getCause() + " || " +
32e.getMessage());
33 }
34}
35
36
Por su parte, en los mtodos de des-registro y de error me limitar a escribir un mensaje en el log de
la aplicacin para no complicar demasiado el ejemplo, pero en una aplicacin real deberamos
verificar estas respuestas.
1@Override
2protected void onUnregistered(Context context, String regId) {
3 Log.d("GCMTest", "REGISTRATION: Desregistrado OK.");
4 }
5
@Override
6protected void onError(Context context, String errorId) {
7 Log.d("GCMTest", "REGISTRATION: Error -> " + errorId);
8}
9
Por ltimo, en el mtodo onMessage() procesaremos el intent con los datos recibidos en el
mensaje y mostraremos una notificacin en la barra de estado de Android. El intent recibido
contendr un elemento en su coleccin de extras por cada dato adicional que se haya incluido en la
peticin que hizo la aplicacin servidor al enviar el mensaje. Recordis? Aquellos datos
adicionales que haba que preceder con el prefijo data.. Si hacis memoria, en nuestros mensajes
de ejemplo tan slo incluamos un dato llamado data.msg con un mensaje de prueba. Pues bien,
estos datos se recuperarn de la coleccin de extras del intent llamado al mtodo getString()
con el nombre del dato, pero esta vez eliminando el prefijo data.. Veamos cmo quedara todo
esto:
1@Override
2protected void onMessage(Context context, Intent intent) {
3 String msg = intent.getExtras().getString("msg");
4 Log.d("GCMTest", "Mensaje: " + msg);
5 mostrarNotificacion(context, msg);
}
6
Simple, no?. Al final del mtodo llamamos a un mtodo auxiliar mostrarNotificacion() que ser el
encargado de mostrar la notificacin en la barra de estado de Android. Esto tambin vimos como
hacerlo en detalle en un artculo anterior por lo que tampoco comentaremos el cdigo:
Y slo una indicacin ms, adems de sobrescribir estos mtodos en nuestra clase
GCMIntentService, tambin tendremos que aadir un nuevo constructor sin parmetros que llame
directamente al constructor de la clase base pasndole de nuevo el Sender ID que obtuvimos al crear
el nuevo proyecto en la Google API Console. Quedara algo as:
1public GCMIntentService() {
2 super("224338875065");
3}
Si no ha quedado claro del todo cmo quedara la clase GCMIntentService completa puede
descargarse y consultarse el cdigo fuente completo al final del artculo.
Y con esto habramos terminado de implementar nuestra aplicacin Android capaz de recibir
mensajes push desde nuestra aplicacin web de ejemplo. Si ejecutamos ambas y todo ha ido bien,
introducimos un nombre de usuario en la aplicacin Android, pulsamos Aceptar para guardarlo,
nos registramos como clientes en GCM pulsando el botn Registrar GCM, y seguidamente desde
la aplicacin web introducimos el mismo nombre de usuario del cliente, pulsamos el botn Enviar
GCM y en breves segundos nos debera aparecer la notificacin en la barra de estado de nuestro
emulador como se observa en las imgenes siguientes:
Aplicaacin Ejemplo Android GCM
G
Notificcacin GCM
M Barra de Estado
E
Podiss descargar el
e cdigo fueente del ejem
mplo construuido en este artculo
a desdde este enlacce.
Tarreas en
n segu
undo plano
p en An
ndroid (I):
Thread y Asyn ncTask
k
Por sggoliver on 29
9/07/2012 enn Android, Programacinn
Obviamente, stos son el tipo de errores que nadie quiere ver al utilizar su aplicacin, y en este
artculo y los siguientes vamos a ver varias formas de evitarlo utilizando procesos en segundo plano
para ejecutar las operaciones de larga duracin. En este primer artculo de la serie nos vamos a
centrar en dos de las alternativas ms directas a la hora de ejecutar tareas en segundo plano en
Android:
1. Crear nosotros mismos de forma explcita un nuevo hilo para ejecutar nuestra tarea.
2. Utilizar la clase auxiliar AsyncTask proporcionada por Android.
Mi idea inicial para este artculo era obviar la primera opcin, ya que normalmente la segunda
solucin nos es ms que suficiente, y adems es mas sencilla y ms limpia de implementar. Sin
embargo, si no comentamos al menos de pasada la forma de crear a mano nuevos hilos y los
problemas que surgen, quiz no se viera demasiado claro las ventajas que tiene utilizar las
AsyncTask. Por tanto, finalmente voy a pasar muy rpidamente por la primera opcin para
despus centrarnos un poco ms en la segunda. Adems, aprovechando el tema de la ejecucin de
tareas en segundo plano, vamos a ver tambin cmo utilizar un control (el ProgressBar) y un
tipo de dilogo (el ProgressDialog) que no vimos en los primeros temas del curso dedicados a
la interfaz de usuario.
Y para ir paso a paso, vamso a empezar por crear una aplicacin de ejemplo en cuya actividad
principal colocaremos un control ProgressBar (en mi caso llamado pbarProgreso) y un
botn (btnSinHilos) que ejecute una tarea de larga duracin. Para simular una operacin de
larga duracin vamos a ayudarnos de un mtodo auxiliar que lo nico que haga sea esperar 1
segundo, mediante una llamada a Thread.sleep().
Haremos que nuestro botn ejecute este mtodo 10 veces, de forma que nos quedar una ejecucin
de unos 10 segundos en total:
1
2 btnSinHilos.setOnClickListener(new OnClickListener() {
3 @Override
public void onClick(View v) {
4
pbarProgreso.setMax(100);
5 pbarProgreso.setProgress(0);
6
7 for(int i=1; i<=10; i++) {
8 tareaLarga();
9 pbarProgreso.incrementProgressBy(10);
}
10
11 Toast.makeText(MainHilos.this, "Tarea finalizada!",
12 Toast.LENGTH_SHORT).show();
13 }
14});
15
Como veis, aqu todava no estamos utilizando nada especial, por lo que todo el cdigo se ejecutar
en el hilo principal de la aplicacin. En cuanto a la utilizacin del control ProgressBar vemos
que es muy sencilla y no requiere apenas configuracin. En nuestro caso tan slo hemos establecido
el valor mximo que alcanzar (el valor en el que la barra de progreso estar rellena al mximo)
mediante el mtodo setMax(100), posteriormente la hemos inicializado al valor mnimo
mediante una llamada a setProgress(0) de forma que inicialmente aparezca completamente
vaca, y por ltimo en cada iteracin del bucle incrementamos su valor en 10 unidades llamando a
incrementProgressBy(10), de tal forma que tras la dcima iteracin la barra llegue al valor
mximo de 100 que establecimos antes. Finalmente mostramos un mensaje Toast para informar
de la finalizacin de la tarea. Pues bien, ejecutemos la aplicacin, pulsemos el botn, y veamos qu
ocurre.
He colocado un pequeo vdeo al final del artculo donde puede verse el resultado final de todas las
pruebas que haremos durante este tutorial. En concreto esta primera prueba puede verse entre los
segundos 00:00 00:12
No era eso lo que esperbamos verdad? Lo que ha ocurrido es que desde el momento que hemos
pulsado el botn para ejecutar la tarea, hemos bloqueado completamente el resto de la aplicacin,
incluida la actualizacin de la interfaz de usuario, que ha debido esperar a que sta termine
mostrando directamente la barra de progreso completamente llena. En definitiva, no hemos sido
capaces de ver el progreso de la tarea. Pero como decamos, este efecto puede empeorar. Probemos
ahora a pulsar el botn de la tarea y mientras sta se ejecuta realicemos cualquier accin sobre la
pantalla, un simple click sobre el fondo nos basta. Veamos qu ocurre ahora.
Hasta aqu todo sencillo y relativamente limpio. Los problemas aparecen cuando nos damos cuenta
que desde este hilo secundario que hemos creado no podemos hacer referencia directa a
componentes que se ejecuten en el hilo principal, entre ellos los controles que forman nuestra
interfaz de usuario, es decir, que desde el mtodo run() no podramos ir actualizando
directamente nuestra barra de progreso de la misma forma que lo hacamos antes. Para solucionar
esto, Android proporciona varias alternativas, entre ellas la utilizacin del mtodo post() para
actuar sobre cada control de la interfaz, o la llamada al mtodo runOnUiThread() para enviar
operaciones al hilo principal desde el hilo secundario [Nota: S, vale, s que no he nombrado la
opcin de los Handler, pero no quera complicar ms el tema por el momento]. Ambas opciones
requieren como parmetro un nuevo objeto Runnable del que nuevamente habr que implementar
su mtodo run() donde se acte sobre los elementos de la interfaz. Por ver algn ejemplo, en
nuestro caso hemos utilizado el mtodo post() para actuar sobre el control ProgressBar, y el
mtodo runOnUiThread()para mostrar el mensaje toast.
Utilicemos este cdigo dentro de un nuevo botn de nuestra aplicacin de ejemplo y vamos a
probarlo en el emulador.
Ahora s podemos ver el progreso de nuestra tarea reflejado en la barra de progreso. La creacin de
un hilo secundario nos ha permitido mantener el hilo principal libre de forma que nuestra interfaz de
usuario de actualiza sin problemas durante la ejecucin de la tarea en segundo plano. Sin embargo
miremos de nuevo el cdigo anterior. Complicado de leer, verdad? Y eso considerando que tan
slo estamos actualizando un control de nuestra interfaz. Si el nmero de controles fuera mayor, o
necesitramos una mayor interaccin con la interfaz el cdigo empezara a ser inmanejable, difcil
de leer y mantener, y por tanto tambin ms propenso a errores. Pues bien, aqu es donde Android
llega en nuestra ayuda y nos ofrece la clase AsyncTask, que nos va a permitir realizar esto mismo
pero con la ventaja de no tener que utilizar artefactos del tipo runOnUiThread() y de una forma
mucho ms organizada y legible. La forma bsica de utilizar la clase AsyncTaskconsiste en crear
una nueva clase que extienda de ella y sobrescribir varios de sus mtodos entre los que repartiremos
la funcionalidad de nuestra tarea. Estos mtodos son los siguientes:
Estos mtodos tienen una particularidad esencial para nuestros intereses. El mtodo
doInBackground() se ejecuta en un hilo secundario (por tanto no podremos interactuar con la
interfaz), pero sin embargo todos los dems se ejecutan en el hilo principal, lo que quiere decir que
dentro de ellos podremos hacer referencia directa a nuestros controles de usuario para actualizar la
interfaz. Por su parte, dentro de doInBackground() tendremos la posibilidad de
llamar peridicamente al mtodo publishProgress() para que automticamente desde el
mtodo onProgressUpdate() se actualice la interfaz si es necesario. Al extender una nueva
clase de AsyncTaskindicaremos tres parmetros de tipo:
En nuestro caso de ejemplo, extenderemos de AsyncTask indicando los tipos Void, Integer y
Booleanrespectivamente, lo que se traducir en que:
Dicho esto, cmo repartiremos la funcionalidad de nuestra tarea entre los distintos mtodos. Pues
sencillo, en onPreExecute() inicializaremos la barra de progreso estableciendo su valor
mximo y ponindola a cero para comenzar. En doInBackground() ejecutaremos nuestro bucle
habitual llamando a publishProgress() tras cada iteracin indicando el progreso actual. En
onProgressUpdate() actualizaremos el estado de la barra de progreso con el valor recibido
como parmetro. y por ltimo en onPostExecute() mostraremos el mensaje Toast de
finalizacin de la tarea. Veamos el cdigo completo:
Mucho mejor que las alternativas anteriores, verdad? Pero vamos a mostrar una opcin ms. Si
queremos que el usuario pueda ver el progreso de nuestra tarea en segundo plano, pero no queremos
que interacte mientras tanto con la aplicacin tenemos la opcin de mostrar la barra de progreso
dentro de un dilogo. Android nos proporciona directamente un componente de este tipo en forma
de un tipo especial de dilogo llamado ProgressDialog.
Configurar un cuadro de dilogo de este tipo es muy sencillo. Para ello vamos a aadir un botn
ms a nuestra aplicacin de ejemplo, donde inicializaremos el dilogo y lanzaremos la tarea en
segundo plano. Para inicializar el dilogo comenzaremos por crear un nuevo objeto
ProgressDialog pasndole como parmetro el contexto actual. Tras esto estableceremos su
estilo: STYLE_HORIZONTAL para una barra de progreso tradicional, o STYLE_SPINNER para un
indicador de progreso de tipo indeterminado.
ProgressDialog horizontal
ProgressDialog spinner
1
2 btnAsyncDialog.setOnClickListener(new OnClickListener() {
3
4 @Override
public void onClick(View v) {
5
6 pDialog = new ProgressDialog(MainHilos.this);
7 pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
8 pDialog.setMessage("Procesando...");
9 pDialog.setCancelable(true);
10 pDialog.setMax(100);
11
tarea2 = new MiTareaAsincronaDialog();
12 tarea2.execute();
13 }
14});
15
La AsyncTask ser muy similar a la que ya implementamos. De hecho el mtodo
doInBackground() no sufrir cambios.
El cdigo de onPreExecute() s tendr algn cambio ms. Aprovecharemos este mtodo para
implementar el evento onCancel del dilogo, dentro del cual cancelaremos tambin la tarea en
segundo plano llamando al mtodo cancel(). Adems, inicializaremos el progreso del dilogo a
0 y lo mostraremos al usuario mediante el mtodo show().
Si ahora ejecutamos nuestro proyecto y pulsamos sobre el ltimo botn incluido veremos cmo el
dilogo aparece por encima de nuestra actividad mostrando el progreso de la tarea asncrona. Tras
finalizar, el dilogo desaparece y se muestra el mensaje toast de finalizacin. Si en cambio, se pulsa
el botn Atrs del dispositivo antes de que la tarea termine el dilogo se cerrar y se mostrar el
mensaje de cancelacin.
Y con esto habramos concluido este primer artculo sobre hilos y tareas en segundo plano. Os dejo
a continuacin el vdeo de demostracin de la aplicacin de ejemplo construida durante el tema, y
como siempre el cdigo fuente completo del ejemplo.
Empezaremos extendiendo una nueva clase derivada de IntentService, que para ser originales
llamaremos MiIntentService. Lo primero que tendremos que hacer ser implementar un
constructor por defecto. Este constructor lo nico que har ser llamar al constructor de la clase
padre pasndole el nombre de nuestra nueva clase.
Como ya hemos comentado, para comunicar este progreso vamos a hacer uso de mensajes
broadcast. Para enviar este tipo de mensajes necesitamos construir un Intent, asociarle un
nombre de accin determinada que lo identifique mediante setAction(), e incluir los datos que
necesitemos comunicar aadiendo tantos extras como sean necesarios mediante el mtodo
putExtra(). Los nombres de las acciones se suelen preceder con el paquete java de nuestra
aplicacin de forma que nos aseguremos que es un identificador nico. En nuestro caso lo
llamaremos net.sgoliver.intent.action.PROGRESO y lo definiremos como atributo
esttico de la clase para mayor comodidad, llamado ACTION_PROGRESO. Por su parte, los datos a
comunicar en nuestro ejemplo ser solo el nivel de progreso, por lo que slo aadiremos un extra a
nuestro intent con dicho dato. Por ltimo enviaremos el mensaje llamando al mtodo
sendBroadcast() pasndole como parmetro el intent recin creado. Veamos cmo quedara el
cdigo completo.
1
2
3
4 public class MiIntentService extends IntentService {
5 public static final String ACTION_PROGRESO =
6 "net.sgoliver.intent.action.PROGRESO";
7 public static final String ACTION_FIN =
8 "net.sgoliver.intent.action.FIN";
9
10 public MiIntentService() {
super("MiIntentService");
11 }
12
13 @Override
14 protected void onHandleIntent(Intent intent)
15 {
int iter = intent.getIntExtra("iteraciones", 0);
16
17
for(int i=1; i<=iter; i++) {
18 tareaLarga();
19
20 //Comunicamos el progreso
21 Intent bcIntent = new Intent();
22 bcIntent.setAction(ACTION_PROGRESO);
bcIntent.putExtra("progreso", i*10);
23 sendBroadcast(bcIntent);
24 }
25
26 Intent bcIntent = new Intent();
27 bcIntent.setAction(ACTION_FIN);
28 sendBroadcast(bcIntent);
}
29
30 private void tareaLarga()
31 {
32 try {
33 Thread.sleep(1000);
} catch(InterruptedException e) {}
34 }
35}
36
37
38
Como podis comprobar tambin he aadido un nuevo tipo de mensaje broadcast (ACTION_FIN),
esta vez sin datos adicionales, para comunicar a la aplicacin principal la finalizacin de la tarea en
segundo plano.
Adems de la implementacin del servicio, recordemos que tambin tendremos que declararlo en el
AndroidManifest.xml, dentro de la seccin <application>:
1<service android:name=".MiIntentService"></service>
Y con esto ya tendramos implementado nuestro servicio. El siguiente paso ser llamar al servicio
para comenzar su ejecucin. Esto lo haremos desde una actividad principal de ejemplo en la que tan
slo colocaremos una barra de progreso y un botn para lanzar el servicio. El cdigo del botn para
ejecutar el servicio ser muy sencillo, tan slo tendremos que crear un nuevo intent asociado a la
clase MiIntentService, aadir los datos de entrada necesarios mediante putExtra() y
ejecutar el servicio llamando a startService() pasando como parmetro el intent de entrada.
Como ya dijimos, el nico dato de entrada que pasaremos ser el nmero de iteraciones a ejecutar.
1btnEjecutar.setOnClickListener(new OnClickListener() {
2
3 @Override
4 public void onClick(View v) {
Intent msgIntent = new Intent(MainActivity.this,
5MiIntentService.class);
6 msgIntent.putExtra("iteraciones", 10);
7 startService(msgIntent);
8 }
9});
Con esto ya podramos ejecutar nuestra aplicacin y lanzar la tarea, pero no podramos ver el
progreso de sta ni saber cundo ha terminado porque an no hemos creado el
BroadcastReceiver necesario para capturar los mensajes broadcast que enva el servicio
durante su ejecucin.
Para ello, como clase interna a nuestra actividad principal definiremos una nueva clase que extienda
a BroadcastReceiver y que implemente su mtodo onReceive() para gestionar los
mensajes ACTION_PROGRESO y ACTION_FIN que definimos en nuestro IntentService. En
el caso de recibirse ACTION_PROGRESO extraeremos el nivel de progreso del intent recibido y
actualizaremos consecuentemente la barra de progreso mediante setProgress(). En caso de
recibirse ACTION_FIN mostraremos un mensaje Toast informando de la finalizacin de la tarea.
Android IntentService
Como siempre, podis descargar el cdigo completo de la aplicacin de ejemplo construida en este
artculo.
Depuracin en Android: Logging
Por sgoliver on 28/04/2011 en Android, Programacin
Una de las tcnicas ms tiles a la hora de depurar y/o realizar el seguimiento de aplicaciones sobre
cualquier plataforma es la creacin de logs de ejecucin. Android por supuesto no se queda atrs y
nos proporciona tambin su propio servicio y API de logging a travs de la clase
android.util.Log.
De forma similar a como ocurre con otros frameworks de logging, en Android los mensajes de log
se van a clasificar por su criticidad, existiendo as varias categorias (ordenadas de mayor a menor
criticidad):
1. Error
2. Warning
3. Info
4. Debug
5. Verbose
Para cada uno de estos tipos de mensaje existe un mtodo esttico independiente que permite
aadirlo al log de la aplicacin. As, para cada una de las categoras anteriores tenemos disponibles
los mtodos e(), w(), i(), d() y v() respectivamente.
Cada uno de estos mtodos recibe como parmetros la etiqueta (tag) y el texto en s del mensaje.
Como etiqueta de los mensajes, aunque es un campo al que podemos pasar cualquier valor, suele
utilizarse el nombre de la aplicacin o de la actividad concreta que genera el mensaje. Esto nos
permitir ms tarde crear filtros personalizados para identificar y poder visualizar nicamente los
mensajes de log que nos interesan, entre todos los generados por Android [que son muchos] durante
la ejecucin de la aplicacin.
Hagamos un miniprograma de ejemplo para ver cmo fuenciona esto. El programa ser tan simple
como aadir varios mensajes de log dentro del mismo onCreate de la actividad principal y ver qu
ocurre. Os muestro el cdigo completo:
1
2 pub
blic class LogsAndroi
L d extends Activity {
3
4 private static final String LOGTAG = "LLogsAndroidd";
5
@Overridee
6
public void onCreate(Bundle savedInsta
s nceState) {
7 superr.onCreate(savedInsttanceState);
8 setCoontentView
w(R.layout..main);
9
10 Log.ee(LOGTAG, "Mensaje de
d error");
11 Log.ww(LOGTAG, "Mensaje de
d warning");
Log.ii(LOGTAG, "Mensaje de
d informacin");
12 Log.dd(LOGTAG, "Mensaje de
d depuracin");
13 Log.vv(LOGTAG, "Mensaje de
d verbose" ");
14 }
15}
16
Si ejeccutamos la aplicacin
a annterior en el emulador veremos
v cmmo se abre laa pantalla priincipal que
crea Eclipse
E por defecto
d y apparentementee no ocurre nada ms. Dnde
podeemos ver los mensajes
que heemos aadid do al log? Puues para ver los
l mensajess de log nos tenemos quue ir a la persspectiva de
Eclipsse llamada DDMS.
D Una vez en esta perspectiva,, podemos acceder
a a loss mensajes de
d log en la
parte inferior
i de la
l pantalla, en
e una vistaa llamada LoogCat. En estae ventana se muestrann todos los
mensaajes de log que
q genera Android
A duraante la ejecucin de la applicacin, quue son muchhos, pero si
buscammos un pocco en la listta encontrareemos los geenerados poor nuestra applicacin, taal como se
muestrra en la sigu
uiente imagenn (click paraa ampliar):
Por ejjemplo, pod demos restrinngir la listaa para que slo muestrre mensajes con una deeterminada
criticiddad mnima.. Esto se connsigue pulsaando alguno de los 5 primeros botonnes que se observan en
la partte superior derecha
d de la
l ventana ded log. As, si por ejempplo pulsamoos sobre el botn
b de la
categoora Info (en verde), en la s mostrarnn los mensajes con criticcidad Error, Warning e
l lista slo se
Info.
Otro mtodo
m de filtrado ms innteresante es la definicin de filtros personalizaados (botn +
verde),
donde podemos filtrar la lista para mostrarr nicamentee los mensajjes con un PID o Tag deeterminado.
Si hemmos utilizado como etiqqueta de los mensajes el e nombre dee nuestra applicacin o de d nuestras
actividdades esto noos proporcioonar una forrma sencillaa de visualizaar slo los mensajes
m gennerados por
nuestrra aplicacin
n.
Esto crear
c ueva ventanna de log conn el nombre que hayam
una nu mos especificado en el filltro, donde
slo apparecern nu
uestros 5 meensajes de log de ejemploo (click paraa ampliar):
1
try
2{
3 int a = 1/0;
4}
5catc ch(Exceptio
on ex)
6 {
Log.e(LOGT
TAG, "Divisin por cero!",
c ex);
7}
8