Sie sind auf Seite 1von 167

Modulo 6

Eventos Pantalla tctil y sensores


Contenido del mdulo

Hasta el momento, los perifricos de entrada ms habituales para interactuar con los ordenadores han
sido el teclado y el ratn. Sin embargo, esto ha cambiado en los dispositivos mviles actuales; se han
desarrollado nuevas formas de interaccin con el usuario mediante gestos directos sobre la pantalla, por
lo que nuestras aplicaciones han de adaptarse a estas nuevas formas de interaccin. Aprenderemos a
manejar los eventos que puedan darse.
Tambin se han integrado gran cantidad de sensores que permiten ampliar muchsimo la funcionalidad de
nuestros programas, como la posibilidad de localizar la ubicacin del dispositivo, acelermetros, etc.
Veremos cmo integrarlos en nuestras aplicaciones.

6.1.- Capturando eventos


Alternativas
1.- Ya conocemos la posibilidad de asociar un mtodo al evento onClick de un View que forme parte de
nuestro interfaz de usuario. Simplemente utilizaremos el atributo android:onclick del control incluido en el
archivo XML que define la interfaz, dndole un valor Stringigual al nombre del mtodo que debe
ejecutarse.
Adems, hay que cumplir:

El mtodo estar implementado dentro de la activity que visualiza la vista.

La cabecera debe seguir esta sintaxis:

public void nombreMetodo(View v) {

Es una forma muy cmoda de establecer, desde el archivo XML que define la interfaz, un
comportamiento que responda al clic del usuario sobre los diferentes objetos que la componen;
normalmente se actuar con el dedo o un puntero, sobre la pantalla tctil.

Pero slo tenemos la posibilidad de definir este evento y adems, habr ocasiones en que
queramos definir estos comportamientos desde el cdigo Java. Puedes localizar algn ejemplo
de esta modalidad en los mdulos anteriores.
El usuario genera eventos cuando interacta con los elementos que aparecen en su interfaz; es
decir, siempre ser un objeto View el que recoja el evento y se lo pase a la clase encargada de
manejarlo. Podemos gestionarlo de dos formas: con los event handler o mediante los event
listener.
2.- Event handler: La clase View dispone de una serie de mtodos que se ejecutan cuando se
produce un evento sobre la vista. As que, si estamos definiendo un control personalizado, ser
descendiente de View, por lo que podemos sobreeescribir alguno de estos mtodos.

Sin embargo, no resulta muy prctico el tener que extender cada control que utilicemos en
nuestra interfaz para poder implementar los eventos que recibe; esta es la razn por la que la
clase View dispone de una coleccin de interfaces anidadas que se pueden implementar con
mayor eficacia; son los llamados event listener:

3.- Event listener: Un escuchador de eventos no es ms que una interfaz definida dentro de la
clase View que contiene un nico mtodo que gestionar un evento determinado.

Implementaremos los event listener que necesitemos en cada caso.


Habitualmente se utiliza cuando un layout agrupa varias views en las que queremos capturar las
interacciones del usuario; en este caso, no es demasiado operativo tener que extender cada una
de ellas, sino que sea la clase que utiliza el layout la que implemente los interfaces necesarios y,
si es necesario, dentro de cada mtodo se determina cul ha sido la vista que ha recibido el
evento y se acta en consecuencia.

Hilo principal e hilos secundarios

Cuando se lanza una aplicacin, el sistema crea un nuevo hilo de ejecucin (thread) para esta
aplicacin conocido como hilo principal. Este hilo es muy importante, dado que se encarga de
dibujar la interfaz de usuario y de atender a los eventos de los distintos componentes. Es decir,
este hilo ejecuta los mtodos onCreate(), onDraw(), onKeyDown(), Por esta razn al hilo
principal tambin se le conoce como hilo del interfaz de usuario.

Mientras no programemos lo contrario, este hilo se ocupa de todas las tareas; por ello, debemos
evitar encomendarle tareas que necesiten mucho tiempo de ejecucin. De otra forma, la
aplicacin quedara bloqueada mientras no finalice la tarea (por ejemplo, estara "sorda" a los
eventos que intentara el usuario). Es ms, si el hilo principal permanece bloqueado ms de 5
segundos, el sistema automticamente muestra un cuadro de dialogo al usuario indicndole
que La aplicacin no responde y permitindole decidir si quiere esperar o forzar la detencin de
la aplicacin.

La solucin debe implementarla el programador, encargndole a un hilo secundario que se


ocupe de esta tarea pesada. De esta forma, se completar en paralelo y el hilo principal estar
libre para redibujar los componentes de la interfaz del usuario y escuchar sus eventos.
NOTA: Los hilos que se ejecutan concurrentemente comparten las variables de la aplicacin.
Para evitar la posibilidad de incongruencias que podran darse si un hilo intenta modificar el valor
de una variable mientras otro intenta leerla, Java integra el mecanismo de la seccin crtica,
fragmento de cdigo "peligroso" donde solo puede entrar un hilo cada vez y que se define con la
palabra reservada synchronized.

De manera que el hilo principal es el nico que puede encargarse de la interfaz de usuario. Hay
que tener claro que no se permite manipular nada dibujado en pantalla desde un hilo secundario.

Por convencin, los eventos que va a manejar el hilo principal comienzan por On... (por
ejemplo, onClick) y los que manejar un hilo secundario por Do... (por
ejemplo, DoInBackground).

NOTA: Puedes ampliar la informacin sobre cmo programar en multitarea para Android en estos
enlaces: http://jarroba.com/multitarea-en-android/ y http://jarroba.com/asynctask-en-android/

6.2.- Event Handler


Cuando estemos creando un descendiente personalizado de View, podremos utilizar directamente varios
mtodos callback de esta clase que se usan como manejadores de eventos (event handler); slo habr
que sobreescribirlos.
En esta lista se incluyen, entre otros:

onKeyDown(int keyCode, KeyEvent e): Llamado cuando una tecla es pulsada.

onKeyUp(int keyCode, KeyEvent e): Cuando una tecla deja de ser pulsada.

onTouchEvent(MotionEvent me): Cuando se pulsa en la pantalla tctil.

onFocusChanged(boolean obtengoFoco, int direccion, Rect prevRectanguloFoco): Llamado


cuando la vista obtiene o pierde el foco.

Es interesante indicar que existen event handlers en otro tipo de objetos, adems de los descendientes
de View. Por ejemplo:

Activity.dispatchTouchEvent(MotionEvent): Se ejecuta antes de que el evento se transmita a los


componentes de la interfaz, lo cual permite interceptar cualquier toque sobre la pantalla y actuar
en consecuencia.

ViewGroup.onInterceptTouchEvent(MotionEvent): Similar a lo anterior, pero sobre un Layout (en


realidad sobre el ViewGroupsubyacente). Le permite interceptar el evento de toque antes de que
se transmita a sus componentes.

Cdigo
El esquema que habra que seguir para definir un view personalizado sera el siguiente:
public class MiVista extends view {

@Override public boolean onKeyDown (int codigoTecla, KeyEvent evento) {


super.onKeyDown(codigoTecla, evento);

return true; // Hemos procesado el evento


}
@Override public boolean onTouchEvent(MotionEvent evento) {
super.onTouchEvent(evento);

return true;
}
}
Debemos devolver true cuando queremos indicar que ya hemos tratado el evento y no queremos que se
propague al nivel superior.
Fjate que el argumento que recibe el mtodo onTouchEvent es de tipo MotionEvent; dada su relevancia
en los dispositivos Android, trataremos este tema en el siguiente punto sobre la pantalla tctil.

Actividad guiada
Vamos a crear un EditText personalizado, de manera que cuando tenga el foco se vea con un color de
fondo blanco, pero que cuando lo pierda, adquiera un bonito color gris.
Inicie un nuevo proyecto llamado ManejaEventos.
Deberemos crear una nueva clase que defina el nuevo control; la llamaremos MiTexto y deber extender
de EditText. Si al crearla, marcas la casilla de verificacin "Constructores de superclase", te facilitar la
implementacin crendolos automticamente.
Podemos indicar en los tres constructores que le aplique inicialmente un fondo gris, para que todos los
controles de este tipo que aparezcan en la interfaz, lo hagan con este color.
Para ello, incluye en ellos la sentencia:
this.setBackgroundColor(Color.GRAY);
Ahora basta con que sobreescribamos el mtodo onFocusChanged; se ejecutar cada vez que el usuario
cambie el foco de un control a otro.
Habr que detectar si esta instancia del objeto es la que ha adquirido el foco, lo cual se recibe en el
argumento focused, y actuar en consecuencia:
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (focused)
this.setBackgroundColor(Color.WHITE);
else
this.setBackgroundColor(Color.GRAY);
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
Cuando guardemos esta clase, ya podemos ir al activity_main.xml e insertar varios controles de este tipo
para comprobar su funcionamiento. Recuerda que lo encontrars en la categora Custom & Library
Views (es posible que tengas que pulsar el botnRefresh).

Pantalla tctil
El perifrico ms importante en los dispositivos mviles es su pantalla tctil, ya que podemos utilizarla de
mltiples formas:

Permite seleccionar, arrastrar y soltar cualquier elemento de forma sencilla

Se pueden pulsar muchos controles como botones, desplegables,...

Configura un teclado en aquellos dispositivos que no disponen de teclado fsico

A partir del SDK 1.6 se han incluido las gestures, de manera que se pueden configurar ciertos
dibujos en la pantalla para asociarlos a una orden determinada

Otro abanico de nuevas posibilidades se abre con el multi-touch, soportado a partir del SDK 2.0,
que posibilita que haya ms de un puntero (o dedo) actuando simultneamente.

Vamos a comprobar que para controlar los eventos bsicos que recoge la pantalla tctil podemos
sobreescribir el mtodo onTouchEventdefinido en la clase View, el cual recibe un parmetro de
tipo MotionEvent que representa el evento acaecido.
La clase MotionEvent contiene un cdigo que representa la accin que se ha producido y un conjunto de
valores para indicar la posicin, el movimiento efectuado y otras informaciones. Algunos de los mtodos
disponibles son:

public final int getAction ()


Devuelve un entero que representa el tipo de accin realizada. Puede ser 0 (ACTION_DOWN), 1
(ACTION_UP), 2 (ACTION_MOVE) o 3 (ACTION_CANCEL)

public final float getX (), public final float getY ()


Devuelven la posicin de la pulsacin.

public final long getDownTime ()


Tiempo en ms desde que el usuario presion por primera vez en una cadena de eventos de posicin.

public final long getEventTime ()


Tiempo en ms del evento actual.

public final float getPressure ()


Estima la presin de la pulsacin. El valor 0 es el mnimo, el valor 1 representa una pulsacin normal.

public final float getSize ()


Valor escalado entre 0 y 1 que estima el grosor de la pulsacin.
NOTA: El correcto funcionamiento de estos dos ltimos mtodos depende del tipo de dispositivo en el que
se est ejecutando, ya que algunas pantallas estn preparadas para detectar estos parmetros y otras no.

Hemos considerado estos mtodos sin tener en cuenta el multi-touch, ya que a partir del API level 5 se
puede indicar como parmetro un ndice que represente uno de los punteros que estn actuando.

Actividad guiada
Para estudiar en la prctica el manejo y funcionamiento de la pantalla tctil, vamos a considerar la
siguiente actividad dentro de un proyecto llamado PantallaTactil:

Crearemos un layout dividido en dos zonas: la parte superior


ser el "sensor" donde recogeremos las diferentes pulsaciones
y escribiremos informacin sobre stas en la parte inferior,
como la accin efectuada, el grosor de la pulsacin y la
presin efectuada.
Tendr el aspecto mostrado en la imagen.
Empezaremos creando una nueva clase llamada MiTextView
para definir el view de la parte superior. Como ya hemos
estudiado, va a extender la clase TextView y podremos
sobreescribir el mtodoonTouchEvent.
Como en la generacin de la clase principal necesitamos asignar el textView de la parte inferior
del layout a la variable interna llamada salida de la nueva clase, ten la precaucin de generar los mtodos
de obtencin y establecimiento para esta variable (aunque utilizaremos solo el
mtodo setSalida (unTextView)).

El cdigo simplificado de esta clase sera:


package com.example.pantallatactil;
// importaciones necesarias
public class MiTextView extends TextView {
// Ser el textView donde vamos a escribir la informacin
private TextView salida;
// Con ff slo mostraremos 4 decimales para el tamao de la pulsacin
DecimalFormat ff=new DecimalFormat("0.0000");

//Constructores de la superclase

@Override
public boolean onTouchEvent(MotionEvent evento) {
// Es necesario que hayamos asignado el textView de salida desde el exterior de la clase
if (salida!=null) {
salida.append(" " + traduce(evento.getAction())+" " );
float f = evento.getSize();
float p = evento.getPressure();
salida.append("Tam:" + ff.format(f) + " Pres:" + p);
}
return true;
}
public TextView getSalida() {
return salida;
}
public void setSalida(TextView salida) {
this.salida = salida;
}
private String traduce (int codigo) {
switch (codigo) {
case 0: return "DOWN";
case 1: return "UP ";
case 2: return "MOVE";
default: return "CANCEL";
}
}
}
Hemos implementado el mtodo traduce para visualizar un cdigo entendible.

Ya podemos generar la vista principal; el archivo activity_main puede disponerse en un linear-layout que
contenga nuestro textViewmodificado en la parte superior y un scrollView con un textView interno en la
parte inferior. El esquema sera ste:

Siendo el cdigo generado:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<com.example.pantallatactil.MiTextView
android:id="@+id/entrada"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_margin="2dp"
android:layout_weight="1"
android:background="#0000FF"
android:gravity="center"
android:text="@string/tmiTV"
android:textSize="@dimen/tamTexto" />
<ScrollView
android:id="@+id/scrollView1"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1" >
<TextView
android:id="@+id/salida"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="@string/tSalida" />
</ScrollView>
</LinearLayout>
Bien; ahora vamos a finalizar la clase MainActivity.java:
import android.view.Menu;
import android.widget.TextView;

public class MainActivity extends Activity {


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MiTextView pizarra = (MiTextView) findViewById(R.id.entrada);
TextView info = (TextView) findViewById(R.id.salida);
pizarra.setSalida(info);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Como ves, nicamente tenemos que referenciar ambas views para finalmente definir la salida en la
variable pizarra de tipoMiTextView

6.3.- Event Listener


Un escuchador de eventos o Event Listener es una interfaz de la clase View que contiene un nico
mtodo que ser ejecutado por el framework Android cuando la vista a la cual el Listener haya sido
registrado reciba esta interaccin con el usuario.
Es decir, adems de implementar el interfaz, tenemos que registrarlo asocindolo a la vista o vistas que le
van a llamar pasando una instancia de la implementacin al respectivo mtodo View.set...Listener ().
En esta tabla podemos ver la denominacin de las interfazes definidas, el nombre del mtodo que
contienen y una descripcin de la accin que desencadena su ejecucin, teniendo en cuenta que esta
accin puede ser efectuada mediante la pantalla tctil, con las teclas de navegacin+Enter o con un
trackball+pulsacin.
Event Listener

Mtodo

View.OnClickListener

void onClick(view)

View.OnLongClickListener

boolean onLongClick(view)

View.OnFocusChangeListener

Descripcin del evento


El usuario ha seleccionado el
elemento
La seleccin ha durado ms de un
segundo

void onFocusChange(view,

El elemento ha recibido el foco o

hasFocus)

lo ha perdido
El view tiene el foco y el usuario

View.OnKeyListener

boolean onKey(view, keyCode,


keyEvent)

ha pulsado o soltado una tecla del


keyboard fsico (no se da cuando
se enva mediante cdigo)

View.OnTouchListener

boolean onTouch(view,

Se ha pulsado, soltado o

motionEvent)

desplazado un puntero dentro de

los lmites del view


void
View.OnCreateContextMenuListener onCreateContextMenu(menu,
view, menuInfo)

Se ha creado un men de
contexto (el que resulta de una
pulsacin larga en un elemento)

Fjate que slo tres de estos mtodos devuelven un valor; en general, devolveremos true cuando
queremos indicar que ya hemos tratado el evento y no queremos que se propague a posibles listeners de
nivel superior, aunque con el mtodo onTouch debemos tener cuidado pues, como hemos podido
comprobar en el ejemplo implementado antes, lo habitual es que este evento recoja mltiples acciones
seguidas, as que si devolvemos false ante un ACTION_DOWN, le estamos indicando que no hemos
tratado el evento y que no estamos interesados en recoger las siguientes acciones.
Para implementar una o varias de estas interfaces, Java nos presenta dos alternativas:
1.- Mediante un objeto annimo dentro de la clase
Podemos implementarlo de esta forma:
private OnClickListener escuchadorClick = new OnClickListener()
{
public void onClick(View v) {
// acciones a realizar ante el click
}
};
protected void onCreate(Bundle savedValues) {
...
// Obtenemos una referencia al botn
Button button = (Button)findViewById(R.id.boton1);
// Registramos the onClickListener al boton
button.setOnClickListener(escuchadorClick);
...
}
aunque lo habitual es hacerlo as:
protected void onCreate(Bundle savedValues) {
...
Button boton = (Button)findViewById(R.id.boton1);
boton.setOnClickListener( new OnClickListener() {
public void onClick(View v) {
// acciones a realizar ante el click

}
});
...
}
2.- Que sea la propia clase la que implemente el interfaz (implements...)
public class Ejemplo extends Activity implements OnClickListener{
protected void onCreate(Bundle savedValues) {
...
Button boton = (Button)findViewById(R.id.boton);
boton.setOnClickListener(this);
}

public void onClick(View v) {


// Acciones a realizar
}
...
}
En ambos casos, slo se puede definir un mtodo onClick que compartirn todas las views susceptibles
de recibirlo, aunque como se recibe como argumento de entrada la vista sobre la que se ha actuado,
dentro del cdigo del mtodo podemos diferenciar las acciones asociadas a cada una.

Actividad guiada
Nuevo Proyecto: Colorines
En un LinearLayout con orientacin horizontal metemos dos botones llamados boton1 y boton2 de
manera que ocupen todo el espacio vertical disponible y el espacio horizontal se lo distribuyan a partes
iguales (recuerda, Weight=1 en ambos).
Define el texto "Click para cambiar o desliza hacia la izquierda" para el segundo botn. Metiendo a los
botones unos mrgenes de 20dp deberas conseguir este aspecto:

Queremos que mientras el usuario haga clic sobre el botn derecho, ste vaya adoptando colores
aleatorios y cuando haga el movimiento de arrastre de derecha a izquierda, le "transmita" el color de
fondo actual al botn izquierdo.
Vamos a emplear OnTouchListener para detectar, tanto el click, como el desplazamiento, ya que en
ambos casos se ejecutar el mtodoonTouch. Como utilizaremos un objeto annimo no es necesario que
la actividad principal implemente el interfaz.
Los comentarios explican suficientemente el cdigo:
package org.example.colorines;
import ...;
public class MainActivity extends Activity {
public Button bDcho, bIzdo;
private Random rnd = new Random();
public int colorIzq, colorDcho;
// xIni e yIni guardarn las coordenadas donde el usuario ha iniciado el movimiento.
private int xIni, yIni;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bIzdo = (Button) findViewById(R.id.boton1);
bDcho = (Button) findViewById(R.id.boton2);
//Asignamos un primer color aleatorio al botn derecho y el blanco al botn izquierdo
colorDcho=rnd.nextInt();
bDcho.setBackgroundColor(colorDcho);

colorIzq=16777215; // Equivalente en decimal a FFFFFF


bIzdo.setBackgroundColor(colorIzq);
bDcho.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
if (arg1.getAction() == MotionEvent.ACTION_DOWN) {
// El usuario ha posado el puntero
xIni = (int) arg1.getX();
yIni = (int) arg1.getY();
}
if (arg1.getAction() == MotionEvent.ACTION_UP) {
/* El usuario ha levantado el puntero de la pantalla. Es el momento de detectar
* el tipo de movimiento realizado. Recuerda que el punto (0,0) en 2D est en
* el vrtice superior izquierdo
*/
if (Math.abs((int) arg1.getX() - xIni) < 10 && Math.abs((int) arg1.getY() - yIni) < 10) {
// Se considera un click
colorDcho = rnd.nextInt();
bDcho.setBackgroundColor(colorDcho);
} else {
if ((int) arg1.getX() < xIni && Math.abs((int) arg1.getY() - yIni) < 50) {
/* Se considera un movimiento lo suficientemente horizontal de
* derecha a izquierda.
*/
colorIzq=colorDcho;
bIzdo.setBackgroundColor(colorIzq);
}
}
}
return true;
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}

6.4.- Sensores
La mayora de los dispositivos bajo Android han incorporado sensores que miden el movimiento, la
orientacin y otras condiciones ambientales; son muy tiles para detectar los movimientos del terminal, de
manera que puedas utilizarlos como una va ms de entrada y para monitorizar el entorno cercano.

Como vamos a ver, se manipulan de una forma homognea, ya que accederemos a los sensores del
dispositivo a travs de las clases Sensor, SensorEvent, SensorManager, y la
interfaz SensorEventListener, del paquete android.hardware.
NOTA: No se consideran sensores la cmara, el micrfono, ni el GPS
Android agrupa los sensores en tres grandes categoras:

De movimiento

Miden las fuerzas de aceleracin y de rotacin en torno a tres ejes. Dentro de esta categora
encontramos el acelermetro, sensor de gravedad, giroscopio y sensor del vector de rotacin.

Ambientales

Con ellos se obtienen diversos parmetros del ambiente, como la temperatura, presin, iluminacin
y humedad. Aqu tenemos el barmetro, el fotmetro y el termmetro.

De posicin

Estos sensores miden la posicin fsica de un dispositivo. Forman parte de esta categora el sensor
de orientacin y el magnetmetro.
Hay que tener en cuenta que no todos los sensores estn incluidos en todos los dispositivos. Tambin
podemos encontrarnos con que un terminal dispone de ms de un sensor de determinado tipo actuando
en rangos diferentes.
NOTA: Asumimos que tienes la posibilidad de conectar un dispositivo real en tu ordenador. Existen
emuladores software como el que se puede obtener de http://www.openintents.org/en/node/6, pero
habra que adaptar el cdigo de los ejemplos para poder ajustarlo a sus requerimientos.
La plataforma Android soporta los siguientes tipos de sensores:

Tipo (CONSTANTE)

Utilidad

Dimensiones

Desde
API

todos (TYPE_ALL)
acelermetro (TYPE_ACCELEROMETER)
campo magntico (TYPE_MAGNETIC_FIELD)

mide aceleraciones por gravedad


y cambios de movimiento
brjula, detecta campos

giroscopio (TYPE_GYROSCOPE)

magnticos
detecta giros

orientacin (TYPE_ORIENTATION)

indica direccin a la que apunta


el dispositivo (deprecated desde

luz ambiental (TYPE_LIGHT)

API 8)
ajustar iluminacin pantalla

Booleano

proximidad (TYPE_PROXIMITY)

si hay un objeto a menos de 5


cm (al hablar por telfono)

presin atmosfrica (TYPE_PRESSURE)

altmetro, barmetro
para evitar

temperatura interna (TYPE_TEMPERATURE)

sobrecalentamientos (deprecated
desde API14)

gravedad (TYPE_GRAVITY)
acelermetro

mide la aceleracin debida a la


gravedad
mide aceleraciones descontando

lineal (TYPE_LINEAR_ACCELERATION)
la gravedad
vector de rotacin (TYPE_ROTATION_VECTOR) detecta giros
temperatura
ambiental (TYPE_AMBIENT_TEMPERATURE)

mide la temperatura del aire

humedad relativa (TYPE_RELATIVE_HUMIDITY)

mide el punto de roco, humedad


absoluta y relativa.

14

Deteccin y manejo de sensores


Aunque existen aplicaciones gratuitas que nos informan sobre los sensores disponibles como Z-Device
Test, haremos una aplicacin que nos informe sobre los sensores que tenemos instalados en nuestro
terminal.
En el paquete android.hardware se define todo lo necesario:

la clase Sensor, que representa uno de estos aparatos

la clase SensorManager, que nos permite acceder a los sensores de los que disponemos en el
dispositivo

la clase SensorEvent, que representa un evento recogido en un sensor y con el que se maneja
informacin tal como el tipo de sensor, el momento en que ha sucedido, la precisin y los datos
recogidos

y la interfaz SensorEventListener, que registra los cambios hechos en el sensor indicado

Primeros pasos
Iniciamos un nuevo proyecto llamado Sensores. Podemos indicarle al asistente que queremos heredar
de Activity e implementar SensorEventListener, con lo que deberemos implementar los
mtodos onAccuracyChanged y onSensorChanged.
Implementaremos un LinearLayout con cuatroTextViews en el activity_main.xml; dos de ellos sern
etiquetas y los otros dos los utilizaremos para mostrar los cambios recogidos por los sensores.
El cdigo es:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="@+id/etiqSensorDeMovimiento"
android:layout_width="fill_parent"

android:layout_height="wrap_content"
android:text="@string/etiqSensorDeMovimiento" >
</TextView>
<TextView
android:id="@+id/sensorDeMovimiento"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello_world" >
</TextView>
<TextView
android:id="@+id/etiqSensorDeOrientacion"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/etiqSensorDeOrientacion" >
</TextView>
<TextView
android:id="@+id/sensorDeOrientacion"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello_world" >
</TextView>
</LinearLayout>
con lo que se genera la siguiente vista:

Dentro de la clase principal declaramos variables para los sensores y los textViews:
private SensorManager sensorManager = null;
private Sensor sensorDeTemperatura = null;
private Sensor sensorDeProximidad = null;
private Sensor sensorDeLuz = null;
private Sensor sensorAcelerometro = null;
private Sensor sensorDeOrientacion = null;

private TextView textViewAcelerometro = null;


private TextView textViewOrientacion = null;

Deteccin de los sensores


Para poder obtener instancias de los sensores incluidos en el terminal Android, se
utiliza SensorManager a travs de la llamada al mtodo getSystemService() que solicita al sistema
servicios especficos. Este mtodo pertenece a la clase Context (como somos Activity, tambin
somos Context) y ser muy utilizado para acceder a gran cantidad de servicios del sistema. Al indicar
como parmetro SENSOR_SERVICE, indicamos que queremos utilizar los sensores.
Una vez inicializado sensorManager, el mtodo getDefaultSensor() nos permite solicitar instancias de los
diferentes tipo de sensores indicando en el argumento el tipo de sensor que queremos referenciar.
rotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
sensorDeProximidad = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
sensorDeTemperatura = sensorManager.getDefaultSensor(Sensor.TYPE_TEMPERATURE);
sensorDeLuz = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
sensorAcelerometro = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
sensorDeOrientacion = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

Registro del manejador de eventos


Como hemos dicho, no podemos saber a priori si el dispositivo donde se ejecuta la aplicacin dispone de
todos estos sensores, por lo que es recomendable hacer un filtro que nos permita identificar con cules
contamos y aadir el manejador de eventos en caso afirmativo. Para ello, usamos el
mtodo registerListener() del objeto sensorManager, al que se le pasa como parmetros, la clase que
est implementando la interfaz SensorEventListener, el sensor que se quiera registrar y la velocidad de
registro de cambios en el sensor.
if (sensorAcelerometro == null) {
Toast.makeText(getApplicationContext(), "No hay Sensor movimiento",Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(), "Hay Sensor de movimiento",Toast.LENGTH_SHORT).show();
sensorManager.registerListener(this,
sensorAcelerometro,SensorManager.SENSOR_DELAY_NORMAL);
}
if (sensorDeProximidad == null) {
Toast.makeText(getApplicationContext(),"No hay Sensor de Proximidad",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(), "Hay Sensor de Proximidad",Toast.LENGTH_SHORT).show();
sensorManager.registerListener(this,
sensorDeProximidad,SensorManager.SENSOR_DELAY_NORMAL);
}

if (sensorDeLuz == null) {
Toast.makeText(getApplicationContext(), "No hay Sensor de Luz",Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(), "Hay Sensor de Luz",Toast.LENGTH_SHORT).show();
sensorManager.registerListener(this, sensorDeLuz,SensorManager.SENSOR_DELAY_NORMAL);
}
if (sensorDeTemperatura == null) {
Toast.makeText(getApplicationContext(),"No hay sensor de Temperatura",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(),"Hay sensor de Temperatura", Toast.LENGTH_SHORT).show();
sensorManager.registerListener(this,
sensorDeTemperatura,SensorManager.SENSOR_DELAY_NORMAL);
}
if (sensorDeOrientacion == null) {
Toast.makeText(getApplicationContext(),"No hay sensor de Orientacion",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(),"Hay sensor de Orientacion", Toast.LENGTH_SHORT).show();
sensorManager.registerListener(this,
sensorDeOrientacion,SensorManager.SENSOR_DELAY_NORMAL);
}
Obtenemos la consabida referencia a los TextView:
setContentView(R.layout.activity_main);
textViewAcelerometro = (TextView) findViewById(R.id.sensorDeMovimiento);
textViewAcelerometro.setTextSize(30);
textViewOrientacion = (TextView) findViewById(R.id.sensorDeOrientacion);
textViewOrientacion.setTextSize(30);

Implementacin de los mtodos de la interfaz


El mtodo onAccuracyChanged(Sensor sensor, int accuracy) se ejecuta cuando se cambia la precisin de
un sensor. En este ejemplo lo dejaremos en blanco.
Ser en el mtodo onSensorChanged(SensorEvent event), donde implementamos las acciones a realizar
cuando un sensor registra un cambio. Fjate que recibimos como argumento un objeto de
tipo SensorEvent:
@Override
public void onAccuracyChanged(Sensor arg0, int arg1) {
}

@Override
public void onSensorChanged(SensorEvent arg0) {
synchronized (this) {
float[] masData;
float x, y, z;
switch (arg0.sensor.getType()) {
case Sensor.TYPE_PROXIMITY:
masData = arg0.values;
if (masData[0] == 0) {
textViewAcelerometro.setTextSize(textViewAcelerometro.getTextSize() + 10);
} else {
textViewAcelerometro.setTextSize(textViewAcelerometro.getTextSize() - 10);
}
break;
case Sensor.TYPE_ACCELEROMETER:
masData = arg0.values;
x = masData[0];
y = masData[1];
z = masData[2];
textViewAcelerometro.setText("x: " + x + " y: " + y + " z: "+ z);
break;
case Sensor.TYPE_ORIENTATION:
masData = arg0.values;
x = masData[0];
y = masData[1];
textViewOrientacion.setText("x: " + x + " y: " + y);
break;
default:
break;
}
}
}
El sistema de coordenadas se define en relacin a la pantalla del telfono en su orientacin vertical
(portrait) segn la siguiente imagen. Los ejes no se cambian aunque vare la orientacin de la pantalla del
dispositivo.

Mdulo 7.Ciclo de vida de las actividades


Contenidos del mdulo
En este mdulo vamos a abordar un aspecto vital en
la programacin de aplicaciones en Android: el ciclo
de vida de las actividades. Es decir, los diferentes
estados por los que pasa una actividad desde que es
creada hasta que se destruye y los mtodos que se
ejecutan en las transiciones entre estados.
Es fundamental dominar este aspecto porque en muchas ocasiones el programador tiene que prever
ciertas acciones cuando una actividad determinada deje de estar en primer plano, o cuando vuelve a
activarse, o quizs cuando el dispositivo cambia de orientacin, ya que en esta situacin la aplicacin (y
por tanto, la actividad) debe reiniciarse en el nuevo entorno grfico. Por ejemplo, es habitual que haya
que guardar el estado de la actividad para poder volver a l cuando la actividad vuelve a estar activa;
aunque podra hacerse utilizando un fichero u otro mtodo de almacenamiento, veremos la herramienta
que propone Android y que podemos utilizar en la mayora de los casos.
Aprenderemos tambin cul es la poltica que se utiliza para elegir la aplicacin que va a destruirse
cuando se requiere ms memoria.

7.1.- Eventos y estados en el ciclo de vida de las actividades


El ciclo de vida de las actividades en Android es bastante particular; la singularidad ms evidente es que
algunos cambios de estado son controlados por el sistema, no por el usuario. Por ejemplo, aunque
salgamos de una aplicacin, no necesariamente se destruyen las actividades y servicios que estaban
ejecutndose sino que, mientras no se requiera la memoria que ocupan, siguen presentes en ella en
previsin de que el usuario vuelva a abrir la aplicacin. Cuando otra aplicacin requiere ms memoria de
la que se dispone, el sistema decide qu procesos destruir hasta obtenerla, segn un algoritmo que
estudiaremos posteriormente con el que se intenta valorar la importancia de los mismos.

Ciclo de vida de las aplicaciones


Para cada aplicacin, el sistema genera un proceso Linux en memoria donde
se encapsulan, aislados del resto de procesos, todos los componentes que
conforman la propia aplicacin, incluida una mquina virtual Dalvik exclusiva
para ella.
NOTA: Recuerda que los threads s comparten el espacio en memoria asignado
a las variables.
Como ya sabemos, los intents permiten que una actividad lance a otra o inicie
un servicio; pero stos se ejecutan en segundo plano, por lo que son las
actividades las que conforman los componentes visuales de la aplicacin. Como veremos enseguida,

cuando una actividad deja de estar visible (incluso parcialmente) experimenta un cambio de estado y
tambin cuando vuelve a a primer plano.
Tambin los servicios tienen su propio ciclo de vida (lo estudiaremos en el mdulo 13), pero sobretodo
habr que tener en cuenta las transiciones de estado que experimentan las actividades ya que habr que
actuar no slo cuando el usuario cambie de aplicacin, sino siempre que cambie de actividad. Para definir
estas acciones slo tendremos que sobreescribir esos mtodos en la clase que define la actividad,
teniendo presente que es obligatoria la llamada al mtodo que estamos sobreescribiendo (con super).

Las actividades que son llamadas se van acumulando sucesivamente en una pila global, de manera que
si el usuario pulsa la tecla de retorno del dispositivo, vuelve a mostrarse la actividad anterior. Si el proceso
asociado ya no estuviera vivo porque el sistema ha requerido ms memoria y lo ha destruido, deber
volver a inicializarse, junto con la actividad solicitada.

El ciclo de vida de las actividades

Estados
Como vemos en esta imagen, las actividades atraviesan por
una serie de estados representados por los rectngulos.
Previamente no podemos saber cuntas veces pasar por
cada uno de ellos; de hecho, el recorrido puede variar cada
vez que se ejecute la actividad. Pero s que sabemos qu
mtodo o mtodos capturan el evento cada vez que se d una
transicin entre estados.
Activa (Running): La actividad es visible y tiene el foco.
Visible (Paused): La actividad es visible pero no tiene el foco;
se alcanza este estado cuando pasa a primer plano otra
actividad con alguna parte transparente o que no ocupa toda la
pantalla (si la actividad se ocultara completamente, pasara a
Parada). En este estado, excepto en situaciones muy poco
frecuentes con poqusima memoria, la actividad mantiene toda
la informacin de su estado y la de sus miembros. Incluso
permanece asociada al gestor de ventanas (Window Manager)
Parada (Stopped): La actividad no es visible en absoluto
(el Windows Manager ya no la controla). Sin embargo, seguir viva en memoria mientras haya recursos
suficientes.
Destruida (Destroyed): La actividad termina como respuesta al mtodo finish(), o el sistema ha matado su
proceso.
Vemos en la parte inferior otro diagrama donde se remarcan los mtodos que capturan los eventos de
transicin. En l podemos deducir exactamente qu deberamos hacer en cada uno de ellos.

Mtodos
onCreate(Bundle): Se llama en la creacin de la
actividad. Se utiliza para realizar todo tipo de
inicializaciones, como la creacin de la interfaz
de usuario o la inicializacin de estructuras de
datos. Veremos despus cmo puede recibir
informacin de estado (en el objeto Bundle) si
anteriormente ha sido destruida.
onStart(): Nos indica que la actividad est a
punto de ser mostrada al usuario.
onResume(): Se llama cuando la actividad se ha
hecho visible, por lo que va a comenzar a
interactuar con el usuario. Es un buen lugar para
lanzar las animaciones y la msica.
onPause(): Indica que la actividad est a punto
de ser lanzada a segundo plano, normalmente
porque otra actividad es lanzada. Es el lugar
adecuado para detener animaciones, msica o
almacenar los datos que estaban en edicin.

onStop(): La actividad ya no va a ser visible


para el usuario. Fjate que en algunos casos, es posible que la actividad se destruya sin pasar
por este mtodo.

onRestart(): Indica que la actividad va a volver a ser representada despus de haber pasado
por onStop(). Vuelve a estar en lo alto de la pila.

onDestroy(): Es la ltima llamada que va a poder recibir la actividad. Puede darse porque
hemos invocado a finish() o porque el sistema ha requerido la memoria ocupada por el
proceso; puede detectarse cul ha sido el motivo con el mtodo isFinishing()

Actividades

Actividad guiada con el proyecto Configuracin


En este ejercicio vamos a rescatar el proyecto llamado Configuracin para poder ver los cambios de
estado que experimenta la aplicacin en su ciclo de vida. Es bastante conveniente porque intervienen tres
actividades diferentes: la actividad principal, la actividad AcercaDe ,que simplemente mostraba

un textView provocando que la actividad principal quede detrs, pero an visible y la actividad
SacaPrefs que ocultaba totalmente la actividad principal.
Implementaremos todos los mtodos del ciclo de vida de la actividad MainActivity metiendo un toast en
cada uno de ellos que nos informar de cundo se ejecutan. As podremos hacer diferentes pruebas.
1.- Abre la actividad MainActivity del proyecto Configuracin
2.- Aade en el mtodo onCreate() el siguiente cdigo:

Toast.makeText(this, "onCreate", Toast.LENGTH_SHORT).show();

3.- Aade los siguientes mtodos a MainActivity:


@Override
protected void onStart() {
super.onStart();
Toast.makeText(this, "onStart", Toast.LENGTH_SHORT).show();
}
@Override
protected void onResume() {
super.onResume();
Toast.makeText(this, "onResume", Toast.LENGTH_SHORT).show();
}
@Override
protected void onPause() {
Toast.makeText(this, "onPause", Toast.LENGTH_SHORT).show();
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
Toast.makeText(this, "onStop", Toast.LENGTH_SHORT).show();
}
@Override
protected void onRestart() {
super.onRestart();
Toast.makeText(this, "onRestart", Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
Toast.makeText(this, "onDestroy", Toast.LENGTH_SHORT).show();
}

4.- Comprueba la secuencia de mensajes Toast en estos casos:


- Ejecuta el programa y sal del mismo pulsando el botn "Atrs" del dispositivo
- Ejecuta el programa y ahora sal pulsando el botn "Inicio" del dispositivo
- Ahora prueba a pulsar el botn Acerca de y luego regresa a la actividad principal
- Pulsando el botn Configuracin y regresando posteriormente a la actividad.

7.2.- Guardando el estado de una actividad


Hemos visto que antes de que desaparezcan algunas actividades de la memoria es muy recomendable
guardar determinados parmetros de su estado para que, en caso de tener que crearse nuevamente, lo
haga a partir de ellos. Tambin cuando la actividad es sensible al cambio de orientacin del telfono se
presenta esta situacin, ya que la actividad es destruida para reiniciarse con las nuevas dimensiones de
pantalla y por lo tanto se llama de nuevo al mtodo onCreate().
El mtodo ms fiable, si se trata de informacin muy sensible, es guardarla en un fichero o una base de
datos en el mtodo onPause(), para luego recuperarla en el mtodo onResume(). En el mdulo 14
aprenderemos a utilizar ficheros.
Si no son datos tan imprescindibles, podemos utilizar un mecanismo propuesto por Android que permite
guardarlos fcilmente en un objeto de tipo Bundle para reutilizarlos en la posterior recreacin de la misma.
La clave es sobreescribir estos mtodos:

onSaveInstanceState(Bundle): Normalmente se invoca justo antes de llevar la actividad a un


segundo plano (antes de onStop) y podemos utilizarlo para guardar el estado de la actividad en
el objeto Bundle recibido como parmetro..
Para ello, la clase Bundle dispone de varios mtodos put... segn el tipo de variable a guardar.

onRestoreInstanceState(Bundle): Se invoca despus de onStart para recuperar el estado


guardado en el mtodo onSaveInstanceState. Si no hay un estado guardado anteriormente,
el Bundle recibido vale null.

Lo haremos con los diversos mtodos get... disponibles en la clase Bundle sobre el objeto recibido
como parmetro.
NOTA: Estos mtodos no sern invocados en todas las situaciones como los mtodos estudiados
en el punto anterior que configuran el ciclo de vida. Por ejemplo, cuando un usuario retrocede con el
botn "Atrs" de una actividad B hacia una actividad A, no hay necesidad de llamar
a onSaveInstanceState (Bundle) en la actividad B, porque nunca ser restaurada. En este caso, s
se llama a los mtodosonPause, onStop y onDestroy de la actividad B, pero el sistema evita llamar
a onSaveInstanceState.

Actividad

Actividad
En el proyecto que hemos dado en llamar Colorines nos suceda que, al girar el dispositivo, los botones
volvan a tener el color de fondo inicial. Ahora ya sabemos por qu suceda sto y vamos a solucionarlo
guardando los colores de ambos botones antes de que se destruya la actividad, recuperndolos luego del
objeto Bundle.
Bastar con sobreescribir los mtodos onSaveInstanceState y onRestoreInstanceState:
@Override
protected void onSaveInstanceState(Bundle estadoGuardado) {
super.onSaveInstanceState(estadoGuardado);
estadoGuardado.putInt("colorI", colorIzq);
estadoGuardado.putInt("colorD", colorDcho);
}
@Override
protected void onRestoreInstanceState(Bundle estadoGuardado) {
super.onRestoreInstanceState(estadoGuardado);
if (estadoGuardado != null) {
colorIzq=estadoGuardado.getInt("colorI");
bIzdo.setBackgroundColor(colorIzq);
colorDcho=estadoGuardado.getInt("colorD");
bDcho.setBackgroundColor(colorDcho);
}
}

7.3.- Gestin Android de los procesos en memoria


Cuando el sistema no dispone de la suficiente memoria para ejecutar una aplicacin, debe destruir alguno
de los procesos Linux que la ocupan para poder obtenerla. Pero, cmo elige el ms adecuado?.
Android ordena todos los procesos en una lista segn la importancia asignada a cada uno. La
determinacin del peso o importancia de cada proceso depende de los componentes (actividades y
servicios) que estn ejecutndose en su seno y del estado de estos componentes (recuerda el diagrama
de estados).
Los clasifica en estas categoras de proceso (de mayor a menor importancia):

Proceso en primer plano (Foreground process): Una de sus actividades es con la que est
interactuando el usuario (es la actividadactiva en ese momento). Slo puede haber un proceso
de este tipo y ser eliminado slo si la memoria es tan escasa que ni siquiera ste puede
mantenerse de forma simultnea al proceso que va a iniciarse.

Proceso visible (Visible process): Hospeda una actividad que est visible en la pantalla, pero no
en el primer plano (su mtodo onPause() ha sido llamado). Considerado importante, no ser
eliminado a menos que sea necesario para mantener el proceso en primer plano.

Proceso de servicio: (Service process) Hospeda un servicio inicializado con el


mtodo startService(). Aunque no sea visible al usuario, generalmente este tipo de servicios
estn haciendo tareas que para el usuario son importantes (tales como reproducir un archivo
mp3 o mantener una conexin con un servidor de contenidos).

Proceso de fondo: (Background process) Hospeda una actividad parada, por lo que
actualmente no es visible al usuario (su mtodo onStop() ha sido llamado). El eliminar uno de
estos procesos no debera repercutir notablemente en la experiencia de usuario. Como
generalmente hay abundancia de estos procesos, el sistema los ordena segn el tiempo
transcurrido desde la ltima vez que han sido visualizados.

Proceso vaco: (Empty process) No hospeda ningn componente activo. Este tipo de procesos
se mantienen para disponer de un "cach" que permita mejorar el tiempo de activacin en la
prxima ejecucin.

Mdulo 8.Integracin de elementos multimedia en nuestras


aplicaciones

La introduccin de elementos acsticos y de vdeo tiene mucha importancia en la programacin de


aplicaciones. Para poder escuchar una msica de fondo, insertar efectos de sonido o visualizar un vdeo
utilizaremos las diferentes clases y mtodos definidos en el paquete android.media, el cual compone el
API que nos va a permitir interactuar con los elementos hardware de los dispositivos implicados en
aspectos multimedia.
Con las clases de android.media, siempre que el hardware lo permita, podremos reproducir y guardar
audio y vdeo. En este API tambin se incluyen unas clases con las que podemos detectar caras de
personas sobre un bitmap (FaceDetector) o manejar los tonos de llamada y alertas, as como la vibracin
del dispositivo (AudioManager).
Los archivos multimedia pueden residir en:

una unidad de almacenamiento del dispositivo.

un recurso incrustado en la aplicacin (carpeta res/raw).

un stream que es ledo desde una conexin de red

En este enlace puedes ver especificados la gran variedad de formatos multimedia que soporta por defecto
la plataforma Android.

Como puedes ver en este diagrama, en el Application Framework (que implementa el Entorno de
Aplicacin, nivel inferior al deAplicaciones dentro de la arquitectura de Android) reside la implementacin
de los codecs multimedia y el paquete android.media.
Es una de las libreras nativas (escritas en C/C++ y compiladas en cdigo nativo del procesador y que,
junto al Runtime de Android, componen el nivel situado entre el Entorno de Aplicacin y el propio Ncleo
Linux), el Native Multimedia Framework, la cual, gracias a su motor de grabacin y reproduccin de audio
y vdeo llamado Stagefright, mantiene esta lista predeterminada de codecs de software compatibles.
La capa de integracin OpenMax est diseada para que los programadores puedan utilizar codecs
diferentes y el motor Stagefrightreconozca y pueda utilizar formatos no incluidos en la tabla (por
ejemplo DivX).
En este mdulo daremos una breve descripcin de las clases ms representativas que componen el
API android.media estudiando a fondo la clase MediaPlayer, utilizada para controlar la reproduccin de
streams o de archivos multimedia. Tambin trabajaremos con la clase VideoView que pertenece al
API android.widget y hereda de SurfaceView, ya que configura la vista donde vamos a reproducir el vdeo.

API android.media
Entre las clases definidas en este paquete, destacamos:

AsyncPlayer: Reproduce una lista de audios desde un thread secundario, lo que permite liberar
el hilo principal para otras tareas.

AudioManager: Gestiona las fuentes de audio y sus salidas, adems de distintas propiedades
del sistema (volumen, tonos).

AudioRecord: Permite grabar audio mediante un buffer que obtiene sus datos de alguna entrada
de audio del dispositivo.

AudioTrack: Reproduce un bfer de audio PCM (o MIC, Modulacin por Impulsos


Codificados) directamente por hardware. Es muy rpido (eficaz para asociar efectos de sonido,
etc)

CamcorderProfile y CameraProfile: Para obtener diferentes perfiles y configuraciones


predefinidas de la cmara. Para utilizarla y poder tomar fotos o vdeo, utilizaremos la
clase Camera, del paquete android.hardware

FaceDetector: Identifica la cara de las personas en un bitmap.

JetPlayer: Reproduce audio interactivo creado con JetCreator.

MediaPlayer: Para controlar la reproduccin de audio/vdeo desde ficheros o streams.

MediaRecorder: Permite grabar audio y vdeo.

Ringtone y RingtoneManager: Clases con las que podemos manejar los tonos de llamada, de
notificacin,...

SoundPool: Maneja una coleccin de audioTracks.

La clase MediaPlayer
Uno de los componentes ms importantes del media framework es la clase MediaPlayer, ya que
permite reproducir audio y vdeo con una configuracin mnima y posibilita al programador el control en
todos sus estados.
Como hemos visto, podremos recuperar los ficheros multimedia de tres fuentes diferentes:

Archivos locales almacenados en el sistema de ficheros

URI internas

URLs externas (reproduccin en streaming)

Un objeto de tipo MediaPlayer pasa por una gran variedad de estados. Podemos verlos representados por
elipses en el siguiente diagrama donde las flechas representan las operaciones de control de la
reproduccin o mtodos que causan la transicin entre dos estados. De stos, los mtodos sncronos
(que se ejecutan en el hilo actual) estn representados por flechas con una sola punta, mientras que los
asncronos (los que son ejecutados por otro hilo; en este caso por el motor interno del sistema) tienen
doble punta.

Es reseable indicar que algunos mtodos pueden ser llamados cuando el objeto est en cualquier
estado (el sistema puede llamar aonError o el usuario puede destruir el objeto con release en cualquier
momento), pero la gran mayora slo pueden ser ejecutados desde unos estados concretos; en caso
contrario, se produce una IllegalStateException. Por ejemplo, no podemos pausarlo (con el mtodopause)
si est parado (en Stopped).
Vamos a describir los estados por los que puede pasar un objeto MediaPlayer y algunas otras
consideraciones.

Idle

Cuando reservamos espacio en memoria para un MediaPlayer mediante new, o se ejecuta el


mtodo reset(), el cual reinicializa el objeto, ste entra en el estado Idle; literalmente, libre, sin utilizar.
Mientras no se ejecute el mtodo release(), con el que se finaliza su ciclo de vida, el objeto permanecer
vivo.

Initialized
Cuando se asigna la fuente de los datos multimedia con alguno de los mtodos setDataSource(...), el
objeto entra en su estado Initialized. Esto slo puede hacerse desde Idle.
En este punto puede ser conveniente capturar las excepciones IllegalArgumentException e IOException.

Preparing y Prepared
La primera vez que se vaya a reproducir el archivo (con start) el objeto debe estar en el estado Prepared.
Una vez que ha sido indicada la fuente de datos multimedia, tenemos dos vas para alcanzarlo: de forma
sncrona, mediante el mtodo prepare(), con lo que el hilo principal se ocupa del trabajo, o de forma
asncrona con prepareAsync(), en cuyo caso es el motor interno el que se ocupa y el que lanza el
mtodo onPrepared cuando ya ha finalizado.
NOTA: Aunque es posible implementar onPrepared registrando la
interfaz onPreparedListener consetOnPreparedListener(android.media.MediaPlayer.OnPreparedList
ener), hay que tener presente que el estado Preparing puede no ser consistente.
En el estado Prepared, ya es posible invocar a los mtodos que permiten ajustar el volumen (setVolume),
fijar la pantalla (setScreenOnWhilePlaying), o definir la reproduccin continua (setLooping). Desde este
estado tambin podemos situarnos en un punto determinado del media mediante seekTo; posteriormente,
volveramos al mismo estado.
NOTA: Si se utiliza alguno de los mtodos create(...), el objeto entra en
estado Prepared directamente. Sera vlido, por ejemplo, el siguiente cdigo:
MediaPlayer mp = MediaPlayer.create(this, R.raw.audio);
mp.start();

Started
Reproduciendo. Hemos llegado a este estado mediante el mtodo start(), el cual solo puede ser invocado
desde algunos estados, como muestra el diagrama.
Podemos saber si el objeto se encuentra en estado Started mediante la funcin isPlaying(). Tambin
podemos realizar un seguimiento del buffer de almacenamiento (sobretodo cuando estamos
reproduciendo un stream) implementando OnBufferingUpdateListener.onBufferingUpdate().
Vemos en el diagrama que en este estado podemos volver a invocar a start; esto no dara error, pero
tampoco tendra ningn efecto.
Cuando termina el flujo de datos, si se ha establecido la reproduccin continua con setLooping(true), la
reproduccin comienza desde su inicio y el objeto se mantiene el estado Started.

Tambin podemos reposicionarnos en otro punto con seekTo.


NOTA: La ejecucin del mtodo seekTo(mseg) es asincrnica, de manera que, aunque el flujo de
control retorne inmediatamente, la finalizacin de la tarea puede tardar un tiempo, especialmente
cuando estamos trabajando con streams. En ese momento, el motor interno llama al
mtodo OnSeekComplete.onSeekComplete(), siempre que el programador haya implementado el
event listener adecuado, el cual se registra
con setOnSeekCompleteListener(OnSeekCompleteListener)

Paused
Como ya sabemos, en este estado el reproductor est detenido.
La transicin de Started a Paused (con pause) y a la inversa (con start), es posible que tarde unos
segundos (sobretodo en streaming) dado que, aunque la llamada al mtodo es sncronica, finalmente la
ejecuta el motor interno en modo asncrono; esto puede originar que el valor devuelto por isPlaying no se
actualice instantneamente.
En este estado es posible llamar de nuevo al mtodo pause, aunque no tendr ningn efecto. Tambin
podemos invocar a seekTo(), volviendo despus al mismo estado.

Stopped
Al ejecutar stop(), el objeto detiene su reproduccin. Desde este estado slo se puede viajar al
estado Prepared, de nuevo de manera sincrnica o asincrnica con prepare() o prepareAsync(),
respectivamente.
Es posible re-invocar al mtodo stop(), de nuevo sin efecto alguno.

PlaybackCompleted
Se alcanza este estado cuando termina la reproduccin y no est establecida la reproduccin continua
(isLooping() devuelve false).
Si el programador ha registrado
un OnCompletionListener (con setOnCompletionListener(OnCompletionListener)) se ejecutar el
mtodoonCompletion (el nico mtodo definido en la interfaz del mismo nombre).
Aunque tambin en este estado podemos reposicionar el punto de reproduccin con seekTo (el
objeto MediaPlayer guarda en una variable su posicin actual en el timeline, la cual siempre podemos
obtener mediante el mtodo getCurrentPosition()) aunque invocramos el mtodo start(), la reproduccin
comenzara desde el principio. La otra opcin es ejecutar stop(), lo cual situara al objeto en el
estadoStopped.

Error
Cuando ocurre algn fallo, el objeto entra en el estado Error. Son posibles causas un formato o resolucin
no soportados, un intercalado errneo (en un AVI) o que ha finalizado el timeout de transmisin. Tambin
se da cuando el programador errneamente ha llamado a un mtodo que no poda ser ejecutado desde el
estado actual.

Es conveniente que la aplicacin implemente el mtodo onError() de la


interfaz android.media.MediaPlayer.OnErrorListener. De esa forma, adems de manejar
convenientemente el error, se puede inicializar el objeto con reset() para posteriormente indicarle un
nuevo origen de los datos.

End
Es el estado final, al que se llega cuando se ejecuta el mtodo release(), con lo que el objeto deja de
ocupar memoria y desaparece. Por esta razn. es una buena prctica utilizarlo cuando no se vaya a
utilizar ms.

Ejemplo de MediaPlayer
Crearemos una aplicacin que nos muestre el uso de un objeto MediaPlayer para reproducir y detener un
archivo de audio relativamente grande.
Tambin introduciremos la clase SoundPool, que permite manejar y reproducir de forma rpida una
coleccin de recursos de audio. Esta clase utiliza el servicio MediaPlayer para decodificar el audio
previamente, lo que le permite evitar una sobrecarga de trabajo de CPU en su reproduccin, ya que se
evita el tener que decodificarlo cada vez. Se utiliza habitualmente en las aplicaciones para reproducir
efectos de sonido instantneamente.
Puedes utilizar cualquier archivo de sonido de tu coleccin (preferiblemente pequeo para el SoundPool)
o emplear los que estn en la carpeta Recursos/Necesarios de tu DropBox. Se
llaman main_theme.ogg y sonido_acierto.ogg
Vamos a incluirlos como recursos internos al proyecto; para ello, utiliza el botn derecho sobre la
carpeta res para crear dentro de ella una nueva carpeta llamada raw. Como recordars, esta carpeta est
destinada a contener ficheros adicionales que no se encuentran en formato XML. Copia en ella los
archivos de sonido.

Interfaz
Vamos a disponer de un interfaz muy sencillo; disalo as:

APARIENCIA

CDIGO

<LinearLayout
xmlns:android="http://schemas.android.com/apk/
res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertic
al_margin"
android:paddingLeft="@dimen/activity_horizonta
l_margin"
android:paddingRight="@dimen/activity_horizon
tal_margin"
android:paddingTop="@dimen/activity_vertical_
margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/playMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtPlay" />
<Button
android:id="@+id/pauseMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtPause" />
<Button
android:id="@+id/stopMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtStop" />
<Button
android:id="@+id/playSp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="@string/txtEfecto" />
<TextView
android:id="@+id/txtEstado"
android:layout_width="match_parent"

android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/txtTexto"
android:textAppearance="?android:attr/textAppe
aranceLarge" />
</LinearLayout>

La idea es evidente; pulsando el primer botn (llamado playMP) empezar la reproduccin del
archivo main_theme.ogg. El usuario tiene la opcin de pulsar Pausa o Stop. Ojo; habr que controlar por
cdigo que desde el estado Stop no se puede acceder al estado Pausa. Adems, en cualquier momento
podremos escuchar el efecto de sonido pulsando el botn al efecto. Mientras tanto, iremos visualizando el
estado en el que se encuentra el objeto MediaPlayer en la etiqueta txtEstado.

Cdigo para el SoundPool


Primero nos centraremos en el objeto de tipo SoundPool; veremos cmo se define y asociaremos su
reproduccin al evento onClick del botn Efecto.
Necesitamos declarar dos variables dentro de la clase MainActivity; el propio objeto SoundPool y un
entero que servir para identificar este recurso en concreto. Esto es porque, aunque en este ejemplo slo
reproducimos un sonido, con un solo objeto SoundPool se pueden manejar varios recursos, e incluso
escuchar algunos simultneamente. De hecho, en el constructor hay que especificar el mximo de pistas
que se pueden reproducir simultneamente.
public class MainActivity extends Activity {
private SoundPool sp;
private int intEfecto;
Este cdigo lo incluiremos dentro del mtodo OnCreate, de manera que se inicialice el objeto a la par que
la actividad. Tambin obtendremos la referencia al TextView en la variable estado:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
estado=(TextView)findViewById(R.id.txtEstado);
// SoundPool
sp = new SoundPool(8, AudioManager.STREAM_MUSIC, 0);
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
intEfecto= sp.load(this,R.raw.sonido_acierto,1);
}

El constructor de SoundPool recibe tres parmetros:

int maxStreams: indica el nmero mximo de pistas que podemos reproducir simultneamente
con este objeto. No tiene por qu coincidir con el nmero de pistas cargadas. Veremos
enseguida que, cuando se pone una pista en reproduccin (con el mtodo play()) hay que indicar
una prioridad. Esta prioridad se utiliza para decidir qu se har cuando el nmero de
reproducciones activas exceda el valor de maxStreams. Es decir, si el recurso que se est
cargando ya "no cabe", se detendr el flujo con la prioridad mas baja, y si hay varios
coincidentes, se detendr el ms antiguo. En caso de que el nuevo flujo sea el de menor
prioridad, este no se reproducir.

int streamType: se indica el tipo de flujo que vamos a usar mediante una de las constantes
definidas en la clase AudioManager. Por ejemplo, se suele utilizar STREAM_MUSIC en los
efectos de los juegos

int srcQuality: indica la calidad de reproduccin. Este atributo actualmente no tiene efecto; se
suele poner el 0.

La siguiente orden (setVolumeControlStream) la hemos incluido para que se pueda utilizar el botn de
control de volumen del dispositivo.
Finalmente, deberamos carqar cada una de las pistas con el mtodo load; en nuestro ejemplo slo
utilizamos una. Estamos utilizando la versin de load que recoge tres argumentos: el contexto (la
propia activity), el recurso que reside en la carpeta raw (fjate que no se incluye la extensin del mismo,
por lo que es imposible introducir dos archivos del mismo nombre aunque tengan diferente extensin) y la
prioridad.
Ya slo nos resta crear un mtodo llamado por ejemplo pulsaEfecto, donde se reproduzca el sonido.
public void pulsaEfecto (View v) {
sp.play(intEfecto, 1, 1, 0, 0, 1);;
}

En el mtodo play hay que indicar el identificador de pista, el volumen para el canal izquierdo y derecho
(0.0 a 1.0), la prioridad, el nmero de repeticiones (-1= siempre, 0=solo una vez, 1=repetir una vez, ) y
el ratio de reproduccin, con el que podremos modificar la velocidad o pitch (siendo 1.0 la reproduccin
normal, en un rango de 0.5 a 2.0)
Para asociar este mtodo al evento onClick del botn playSp podemos utilizar la propiedad On Click del
objeto en el activity_main.xml

android:onClick="pulsaEfecto"

Cdigo para el MediaPlayer


Vamos con el MediaPlayer; inicialmente vamos a utilizar los mtodos apropiados para reproducir el
archivo y detenerla o pausarla a voluntad.
Definimos los mtodos dentro de la clase principal para poder asociarlos al evento OnClick de los
respectivos botones. Podemos ir probando este cdigo:

public void pulsaPlay (View v) {


mp= MediaPlayer.create(this, R.raw.main_theme);
mp.start();
estado.setText("Playing");
}
public void pulsaPause (View v) {
mp.pause();
estado.setText("Pausa");
}

public void pulsaStop (View v) {


mp.stop();
estado.setText("Stop");
}
Como ya sabemos, podramos implementar OnClickListener, pero es mucho ms rpido y sencillo utilizar
la propiedad onclick de los respectivos botones definidos en el XML.
Bien; podemos comprobar que tenemos varios fallos; vemoslos:
1.

Podemos pulsar repetidamente el botn Play, con lo que se van acumulando las reproducciones
(recuerda que, una vez que se lanza, se ocupa de la reproduccin el motor interno del Media
Framework de un modo asncrnico). Por supuesto, la pulsacin de Stop slo detiene la ltima
instancia del objeto MediaPlayer.

2.

Si pulsamos Play, Pause y de nuevo Play, la msica vuelve a empezar desde el principio, porque

3.

se vuelve a crear el objeto de nuevo. Debera continuar en el punto donde la pausamos.


Si durante la reproduccin pulsamos Stop y luego Pausa, la ventana de LogCat muestra un error
de ejecucin. Como ya sabemos, esto es debido a que no se puede invocar al
mtodo pause estando en estado Stopped.

4.

Si el primer botn que pulsamos es el de Stop, no puede ejecutar mp.stop(), porque el


objeto mp an no est creado (ocasiona una NullPointerException).

5.

Sucede exactamente lo mismo si el primer botn pulsado es el de Pausa. En este caso, la


excepcin salta en la orden mp.pause.

Los dos primeros errores los resolveremos de la misma forma; bastar con crear el
objeto MediaPlayer slo cuando no exista previamente o vengamos de pulsar Stop. Para ello, definimos la
variable booleana inicializado, que ponemos a false al inicio y en el mtodo pulsaStop. Luego, incluimos el
siguiente if en pulsaPlay:
if (!inicializado) {
preparaCancion();
}
de manera que preparaCancion contenga este cdigo, que slo se ejecutar en los casos previstos:

private void preparaCancion () {


mp= MediaPlayer.create(this, R.raw.main_theme);
inicializado=true;
}
El siguiente error (producido cuando pulsamos Stop y a continuacin Pausa) podemos gestionarlo de la
misma forma. Una variable booleana llamada pausado, que pondremos a true al entrar
en pulsaPause y a false al entrar en pulsaPlay. Slo queda comprobar su valor en el mtodo pulsaStop;
inicialmente, el cdigo de este mtodo quedara as:
public void pulsaStop (View v) {
if(!pausado) {
mp.stop();
mp.reset(); //aunque no es estrictamente necesario, es buena prctica para reinicializar correctamente
el objeto
inicializado=false;
estado.setText("Stop");
}
}

Para resolver el cuarto error podemos utilizar la variable ya definida inicializado, ya que solo debemos
ejecutar el cdigo asociado al click en el botn Stop cuando el objeto MediaPlayer haya sido
perfectamente creado (estar en el estado Prepared o Started). La condicin del ifquedar as:
public void pulsaStop(View v) {
if (inicializado && !pausado) {
...
}
}
El ltimo error es muy parecido al anterior, slo que debemos asegurarnos que estamos en el
estado Started, ya que de otra manera, no es posible invocar al mtodo pause(). Para ello, bastara con
asegurarnos de que mp.isPlaying() vale true, pero tampoco podremos hacerlo si el objeto mp an no ha
sido an creado. De manera que debemos utilizar un and en cortocircuito (con el operador && va
comprobando las condiciones que encuentra de izquierda a derecha slo hasta que hay una que
devuelve false) que detecte esta posibilidad:
public void pulsaPause(View v) {
if (inicializado && mp.isPlaying()) {
pausado = true;
mp.pause();
estado.setText("Pause");
}
}

Para terminar..

An hay algunos detalles que podramos limar y que nos servirn para repasar algunos conceptos
importantes.
Por ejemplo, no hemos necesitado implementar ninguno de los interfaces de programacin disponibles en
esta clase. Podemos ampliar un poco el ejemplo aadiendo en nuestra pantalla una casilla de verificacin
(CheckBox) para definir la reproduccin continua, de manera que, si est marcada, cuando finalice la
reproduccin la retomaremos desde el principio y si no, situaremos el MediaPlayer en Stopping. Para
controlar el final de la reproduccin, implementaremos el mtodo onCompletion de la
interfaz MediaPlayer.OnCompletionListener.
NOTA: Otra posibilidad sera controlar el evento de actualizacin sobre el CheckBox para que,
dependiendo del valor conseguido, definamos el valor de looping (con el mtodo setLooping).
Aade la casilla de verificacin en activity_main.xml. Podemos referenciarla en el
mtodo onCreate llamndola loop, variable CheckBoxque previamente habremos definido.
loop=(CheckBox)findViewById(R.id.checkBox1);
Bien; indicaremos que nuestra clase MainActivity va a implementar la interface OnCompletionListener:
public class MainActivity extends Activity implements MediaPlayer.OnCompletionListener
y dentro del mtodo preparaCancion asociamos el listener al objeto MediaPlayer:
mp.setOnCompletionListener(this);
Perfecto; en la implementacin del mtodo onCompletion iremos a Started o Stopped, segn el valor
del checkbox loop. Para ello, podemos utilizar nuestros mtodos pulsaPlay y pulsaStop.
@Override
public void onCompletion(MediaPlayer mp) {
if (loop.isChecked()) {
pulsaPlay(null);
} else {
pulsaStop(null);
}
}
Vamos a detenernos un momento en el hecho de que la reproduccin contine, incluso cuando la
aplicacin pasa a segundo plano o finaliza. Como ya hemos comentado, esto es debido a que el que se
ocupa de la misma es el motor interno del framework multimedia, por lo que, si no recibe ninguna seal de
paro, contina la reproduccin hasta el final. Cmo podramos solucionar esto?.
La respuesta siempre es la misma; debemos detectar de alguna manera el cambio de escenario o el
cambio en las condiciones a consecuencia de las cuales queremos intervenir. En este caso, tendremos
que sobreescribir los mtodos relacionados con el ciclo de vida de la activity, que nos indicarn que la
actividad ha sido destruida (onDestroy) por lo que deberemos liberar los recursos, ha dejado de estar
activa (onPause) con lo que habr que detener la reproduccin, o ha ido a primer plano, tanto en su
construccin, como volviendo de su inactividad (onResume), en cuyo caso habra que seguir con la
reproduccin si sta ya estaba en marcha.

Debemos tener en cuenta los errores que habamos detectado anteriormente, por lo que habr que definir
bien los if que los evitan. Aadiremos el siguiente cdigo:
@Override
protected void onDestroy() {
super.onDestroy();
if (inicializado) {
mp.release(); // Liberamos recursos
}
}
@Override
public void onPause() {
super.onPause();
if (inicializado && mp.isPlaying()) {
mp.pause(); // podramos tambin stop, pero as podremos guardar la posicin de la reproduccin
// Ojo; la variable pausado debe seguir valiendo false para retomar la reproduccin
}
}
@Override
public void onResume() {
super.onResume();
if (inicializado && !pausado) {
mp.start();
}
}
Si probamos este cdigo, vemos que todo funciona como es de esperar, excepto un pequeo detalle.
Cuando la reproduccin est en marcha, si llevamos la aplicacin a un segundo plano con el botn de
inicio (ya sabes que es diferente a utilizar el de retroceso, lo cual la destruira), al volver a abrir el
programa la reproduccin comienza desde el principio, en vez de continuar en el punto en el que estaba,
lo cual sera ms deseable.
Una posible solucin es guardar el punto en el que est la reproduccin dentro del mtodo onPause, para
poder retomarlo en el mtodoonResume. Bastara con aadir, adems de la lgica declaracin de la
variable entera a nivel de la clase, las siguientes instrucciones en los mtodos indicados:
@Override
public void onPause() {
super.onPause();
if (inicializado && mp.isPlaying()) {
posicion = mp.getCurrentPosition();
mp.pause();
}
}
@Override
public void onResume() {
super.onResume();
if (inicializado) {

mp.seekTo(posicion);
if (!pausado) {
mp.start();
}
}
}

y funcionara perfectamente... excepto en una situacin, por lo dems muy comn: el cambio de
orientacin del dispositivo. En este caso, recuerda que la activity se destruye para volver a iniciarse con la
nueva disposicin grfica. Si guardramos el valor de la variable posicionen un archivo, podramos
recuperarlo de nuevo al iniciar el programa, pero recuerda que tenemos una solucin intermedia (incluso
ms lgica en este caso); los mtodos onSaveInstanceState y onRestoreInstanceState. En ellos,
guardaremos tanto la posicin de reproduccin, como el valor de las variables inicializado y pausado, para
poder retomar el mismo estado en el que dejamos la activity:
@Override
protected void onSaveInstanceState(Bundle guardarEstado) {
super.onSaveInstanceState(guardarEstado);
if (inicializado) {
int pos = mp.getCurrentPosition();
guardarEstado.putInt("posicionGuardada", pos);
guardarEstado.putBoolean("estaPausada", pausado);
guardarEstado.putBoolean("estaInicializado", inicializado);
}
}
@Override
protected void onRestoreInstanceState(Bundle recEstado) {
super.onRestoreInstanceState(recEstado);
if (recEstado != null) {
posicion = recEstado.getInt("posicionGuardada");
pausado = recEstado.getBoolean("estaPausada");
inicializado = recEstado.getBoolean("estaInicializado");
if (inicializado) {
preparaCancion();
}
}
}

Reproduccin de vdeo
Si lo que quieres es reproducir vdeo, el procedimiento es muy similar, pero hay una serie de conceptos a
tener en cuenta:

Se utiliza un objeto VideoView para obtener su superficie de visualizacin (surfaceHolder) y


poder asignrsela al objeto MediaPlayer

As, en el XML descriptor del entorno de usuario tendramos algo as:

<LayoutPrincipal>

<VideoView android:id="@+id/surfaceView"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_below="@+id/ButonsLayout"/>

</LayoutPrincipal>
para luego, desde la clase principal obtener el surfaceHolder:
private MediaPlayer mediaPlayer;
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;

public void onCreate(Bundle bundle) {


super.onCreate(bundle);
setContentView(R.layout.main);
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
surfaceHolder = surfaceView.getHolder();
que asignaremos al mediaPlayer en su inicializacin:
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(path);
mediaPlayer.setDisplay(surfaceHolder);
mediaPlayer.prepare();
// mMediaPlayer.prepareAsync(); Para streaming

Adems, el objeto SurfaceHolder nos permitira implementar su interface


SurfaceHolder.Callback para poder gestionar cundo la superficie (y por ende, la actividad) es
creada, modificada o destruida con sus
mtodos surfaceCreated, surfaceChanged ysurfaceDestroyed.

En el caso de querer implementar, tanto esta interface, como las asociadas a la clase MediaPlayer habra
que hacer lo siguiente:
En la cabecera de la clase:
public class VideoPlayer extends Activity implements
OnBufferingUpdateListener, OnCompletionListener,
MediaPlayer.OnPreparedListener, SurfaceHolder.Callback {
...}
registramos el objeto surfaceHolder que va a recoger los eventos en esta actividad (por ejemplo, despus
de obtener la referencia al mismo):
surfaceHolder.addCallback(this);
Haramos lo mismo con el resto de interfaces para el MediaPlayer (despus de su inicializacin):
mediaPlayer.setOnBufferingUpdateListener(this);
mediaPlayer.setOnCompletionListener(this);
mediaPlayer.setOnPreparedListener(this);

para finalmente implementar los mtodos como necesitemos; un ejemplo podra ser:
public void onBufferingUpdate(MediaPlayer arg0, int percent) {
estado.setText("onBufferingUpdate percent:" + percent);
}
public void onCompletion(MediaPlayer arg0) {
estado.setText("onCompletion called");
}
public void onPrepared(MediaPlayer mediaplayer) {
estado.setText("onPrepared called");
int mVideoWidth = mediaPlayer.getVideoWidth();

int mVideoHeight = mediaPlayer.getVideoHeight();


if (mVideoWidth != 0 && mVideoHeight != 0) {
surfaceHolder.setFixedSize(mVideoWidth, mVideoHeight);
mediaPlayer.start();
}
}
public void surfaceCreated(SurfaceHolder holder) {
estado.setText("surfaceCreated called");
preparaVideo();
}
public void surfaceChanged(SurfaceHolder surfaceholder, int i, int j, int k) {
estado.setText("surfaceChanged called");
... }
public void surfaceDestroyed(SurfaceHolder surfaceholder) {
estado.setText("surfaceDestroyed called");
... }

Ejemplo de MediaPlayer
Crearemos una aplicacin que nos muestre el uso de un objeto MediaPlayer para reproducir y detener un
archivo de audio relativamente grande.
Tambin introduciremos la clase SoundPool, que permite manejar y reproducir de forma rpida una
coleccin de recursos de audio. Esta clase utiliza el servicio MediaPlayer para decodificar el audio
previamente, lo que le permite evitar una sobrecarga de trabajo de CPU en su reproduccin, ya que se
evita el tener que decodificarlo cada vez. Se utiliza habitualmente en las aplicaciones para reproducir
efectos de sonido instantneamente.
Puedes utilizar cualquier archivo de sonido de tu coleccin (preferiblemente pequeo para el SoundPool)
o emplear los que estn en la carpeta Recursos/Necesarios de tu DropBox. Se
llaman main_theme.ogg y sonido_acierto.ogg
Vamos a incluirlos como recursos internos al proyecto; para ello, utiliza el botn derecho sobre la
carpeta res para crear dentro de ella una nueva carpeta llamada raw. Como recordars, esta carpeta est

destinada a contener ficheros adicionales que no se encuentran en formato XML. Copia en ella los
archivos de sonido.

Interfaz
Vamos a disponer de un interfaz muy sencillo; disalo as:
APARIENCIA

CODIGO
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/androi
d"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin
"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/playMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtPlay" />

<Button
android:id="@+id/pauseMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtPause" />

<Button
android:id="@+id/stopMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtStop" />

<Button
android:id="@+id/playSp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="@string/txtEfecto" />
<TextView
android:id="@+id/txtEstado"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/txtTexto"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
La idea es evidente; pulsando el primer botn (llamado playMP) empezar la reproduccin del
archivo main_theme.ogg. El usuario tiene la opcin de pulsar Pausa o Stop. Ojo; habr que controlar por
cdigo que desde el estado Stop no se puede acceder al estado Pausa. Adems, en cualquier momento
podremos escuchar el efecto de sonido pulsando el botn al efecto. Mientras tanto, iremos visualizando el
estado en el que se encuentra el objeto MediaPlayer en la etiqueta txtEstado.

Cdigo para el SoundPool


Primero nos centraremos en el objeto de tipo SoundPool; veremos cmo se define y asociaremos su
reproduccin al evento onClick del botn Efecto.
Necesitamos declarar dos variables dentro de la clase MainActivity; el propio objeto SoundPool y un
entero que servir para identificar este recurso en concreto. Esto es porque, aunque en este ejemplo slo
reproducimos un sonido, con un solo objeto SoundPool se pueden manejar varios recursos, e incluso
escuchar algunos simultneamente. De hecho, en el constructor hay que especificar el mximo de pistas
que se pueden reproducir simultneamente.
public class MainActivity extends Activity {
private SoundPool sp;
private int intEfecto;

Este cdigo lo incluiremos dentro del mtodo OnCreate, de manera que se inicialice el objeto a la par que
la actividad. Tambin obtendremos la referencia al TextView en la variable estado:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
estado=(TextView)findViewById(R.id.txtEstado);
// SoundPool

sp = new SoundPool(8, AudioManager.STREAM_MUSIC, 0);


this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
intEfecto= sp.load(this,R.raw.sonido_acierto,1);
}
El constructor de SoundPool recibe tres parmetros:

int maxStreams: indica el nmero mximo de pistas que podemos reproducir simultneamente
con este objeto. No tiene por qu coincidir con el nmero de pistas cargadas. Veremos
enseguida que, cuando se pone una pista en reproduccin (con el mtodo play()) hay que indicar
una prioridad. Esta prioridad se utiliza para decidir qu se har cuando el nmero de
reproducciones activas exceda el valor de maxStreams. Es decir, si el recurso que se est
cargando ya "no cabe", se detendr el flujo con la prioridad mas baja, y si hay varios
coincidentes, se detendr el ms antiguo. En caso de que el nuevo flujo sea el de menor
prioridad, este no se reproducir.

int streamType: se indica el tipo de flujo que vamos a usar mediante una de las constantes
definidas en la clase AudioManager. Por ejemplo, se suele utilizar STREAM_MUSIC en los
efectos de los juegos

int srcQuality: indica la calidad de reproduccin. Este atributo actualmente no tiene efecto; se
suele poner el 0.

La siguiente orden (setVolumeControlStream) la hemos incluido para que se pueda utilizar el botn de
control de volumen del dispositivo.
Finalmente, deberamos carqar cada una de las pistas con el mtodo load; en nuestro ejemplo slo
utilizamos una. Estamos utilizando la versin de load que recoge tres argumentos: el contexto (la
propia activity), el recurso que reside en la carpeta raw (fjate que no se incluye la extensin del mismo,
por lo que es imposible introducir dos archivos del mismo nombre aunque tengan diferente extensin) y la
prioridad.
Ya slo nos resta crear un mtodo llamado por ejemplo pulsaEfecto, donde se reproduzca el sonido.
public void pulsaEfecto (View v) {
sp.play(intEfecto, 1, 1, 0, 0, 1);;
}
En el mtodo play hay que indicar el identificador de pista, el volumen para el canal izquierdo y derecho
(0.0 a 1.0), la prioridad, el nmero de repeticiones (-1= siempre, 0=solo una vez, 1=repetir una vez, ) y
el ratio de reproduccin, con el que podremos modificar la velocidad o pitch (siendo 1.0 la reproduccin
normal, en un rango de 0.5 a 2.0)
Para asociar este mtodo al evento onClick del botn playSp podemos utilizar la propiedad On Click del
objeto en el activity_main.xml

android:onClick="pulsaEfecto"

Cdigo para el MediaPlayer

Vamos con el MediaPlayer; inicialmente vamos a utilizar los mtodos apropiados para reproducir el
archivo y detenerla o pausarla a voluntad.
Definimos los mtodos dentro de la clase principal para poder asociarlos al evento OnClick de los
respectivos botones. Podemos ir probando este cdigo:
public void pulsaPlay (View v) {
mp= MediaPlayer.create(this, R.raw.main_theme);
mp.start();
estado.setText("Playing");
}
public void pulsaPause (View v) {
mp.pause();
estado.setText("Pausa");
}

public void pulsaStop (View v) {


mp.stop();
estado.setText("Stop");
}
Como ya sabemos, podramos implementar OnClickListener, pero es mucho ms rpido y sencillo utilizar
la propiedad onclick de los respectivos botones definidos en el XML.
Bien; podemos comprobar que tenemos varios fallos; vemoslos:
1.

Podemos pulsar repetidamente el botn Play, con lo que se van acumulando las reproducciones
(recuerda que, una vez que se lanza, se ocupa de la reproduccin el motor interno del Media
Framework de un modo asncrnico). Por supuesto, la pulsacin de Stop slo detiene la ltima
instancia del objeto MediaPlayer.

2.

Si pulsamos Play, Pause y de nuevo Play, la msica vuelve a empezar desde el principio, porque

3.

se vuelve a crear el objeto de nuevo. Debera continuar en el punto donde la pausamos.


Si durante la reproduccin pulsamos Stop y luego Pausa, la ventana de LogCat muestra un error
de ejecucin. Como ya sabemos, esto es debido a que no se puede invocar al
mtodo pause estando en estado Stopped.

4.

Si el primer botn que pulsamos es el de Stop, no puede ejecutar mp.stop(), porque el


objeto mp an no est creado (ocasiona una NullPointerException).

5.

Sucede exactamente lo mismo si el primer botn pulsado es el de Pausa. En este caso, la


excepcin salta en la orden mp.pause.

Los dos primeros errores los resolveremos de la misma forma; bastar con crear el
objeto MediaPlayer slo cuando no exista previamente o vengamos de pulsar Stop. Para ello, definimos la
variable booleana inicializado, que ponemos a false al inicio y en el mtodo pulsaStop. Luego, incluimos el
siguiente if en pulsaPlay:
if (!inicializado) {

preparaCancion();
}

de manera que preparaCancion contenga este cdigo, que slo se ejecutar en los casos previstos:
private void preparaCancion () {
mp= MediaPlayer.create(this, R.raw.main_theme);
inicializado=true;
}
El siguiente error (producido cuando pulsamos Stop y a continuacin Pausa) podemos gestionarlo de la
misma forma. Una variable booleana llamada pausado, que pondremos a true al entrar
en pulsaPause y a false al entrar en pulsaPlay. Slo queda comprobar su valor en el mtodo pulsaStop;
inicialmente, el cdigo de este mtodo quedara as:
public void pulsaStop (View v) {
if(!pausado) {
mp.stop();
mp.reset(); //aunque no es estrictamente necesario, es buena prctica para reinicializar correctamente
el objeto
inicializado=false;
estado.setText("Stop");
}
}

Para resolver el cuarto error podemos utilizar la variable ya definida inicializado, ya que solo debemos
ejecutar el cdigo asociado al click en el botn Stop cuando el objeto MediaPlayer haya sido
perfectamente creado (estar en el estado Prepared o Started). La condicin del ifquedar as:
public void pulsaStop(View v) {
if (inicializado && !pausado) {
...
}
}
El ltimo error es muy parecido al anterior, slo que debemos asegurarnos que estamos en el
estado Started, ya que de otra manera, no es posible invocar al mtodo pause(). Para ello, bastara con
asegurarnos de que mp.isPlaying() vale true, pero tampoco podremos hacerlo si el objeto mp an no ha
sido an creado. De manera que debemos utilizar un and en cortocircuito (con el operador && va
comprobando las condiciones que encuentra de izquierda a derecha slo hasta que hay una que
devuelve false) que detecte esta posibilidad:
public void pulsaPause(View v) {
if (inicializado && mp.isPlaying()) {
pausado = true;
mp.pause();

estado.setText("Pause");
}
}

Para terminar..
An hay algunos detalles que podramos limar y que nos servirn para repasar algunos conceptos
importantes.
Por ejemplo, no hemos necesitado implementar ninguno de los interfaces de programacin disponibles en
esta clase. Podemos ampliar un poco el ejemplo aadiendo en nuestra pantalla una casilla de verificacin
(CheckBox) para definir la reproduccin continua, de manera que, si est marcada, cuando finalice la
reproduccin la retomaremos desde el principio y si no, situaremos el MediaPlayer en Stopping. Para
controlar el final de la reproduccin, implementaremos el mtodo onCompletion de la
interfaz MediaPlayer.OnCompletionListener.
NOTA: Otra posibilidad sera controlar el evento de actualizacin sobre el CheckBox para que,
dependiendo del valor conseguido, definamos el valor de looping (con el mtodo setLooping).
Aade la casilla de verificacin en activity_main.xml. Podemos referenciarla en el
mtodo onCreate llamndola loop, variable CheckBoxque previamente habremos definido.
loop=(CheckBox)findViewById(R.id.checkBox1);

Bien; indicaremos que nuestra clase MainActivity va a implementar la interface OnCompletionListener:


public class MainActivity extends Activity implements MediaPlayer.OnCompletionListener
y dentro del mtodo preparaCancion asociamos el listener al objeto MediaPlayer:
mp.setOnCompletionListener(this);
Perfecto; en la implementacin del mtodo onCompletion iremos a Started o Stopped, segn el valor
del checkbox loop. Para ello, podemos utilizar nuestros mtodos pulsaPlay y pulsaStop.
@Override
public void onCompletion(MediaPlayer mp) {
if (loop.isChecked()) {
pulsaPlay(null);
} else {
pulsaStop(null);
}
}

Vamos a detenernos un momento en el hecho de que la reproduccin contine, incluso cuando la


aplicacin pasa a segundo plano o finaliza. Como ya hemos comentado, esto es debido a que el que se

ocupa de la misma es el motor interno del framework multimedia, por lo que, si no recibe ninguna seal de
paro, contina la reproduccin hasta el final. Cmo podramos solucionar esto?.
La respuesta siempre es la misma; debemos detectar de alguna manera el cambio de escenario o el
cambio en las condiciones a consecuencia de las cuales queremos intervenir. En este caso, tendremos
que sobreescribir los mtodos relacionados con el ciclo de vida de la activity, que nos indicarn que la
actividad ha sido destruida (onDestroy) por lo que deberemos liberar los recursos, ha dejado de estar
activa (onPause) con lo que habr que detener la reproduccin, o ha ido a primer plano, tanto en su
construccin, como volviendo de su inactividad (onResume), en cuyo caso habra que seguir con la
reproduccin si sta ya estaba en marcha.
Debemos tener en cuenta los errores que habamos detectado anteriormente, por lo que habr que definir
bien los if que los evitan. Aadiremos el siguiente cdigo:
@Override
protected void onDestroy() {
super.onDestroy();
if (inicializado) {
mp.release(); // Liberamos recursos
}
}
@Override
public void onPause() {
super.onPause();
if (inicializado && mp.isPlaying()) {
mp.pause(); // podramos tambin stop, pero as podremos guardar la posicin de la reproduccin
// Ojo; la variable pausado debe seguir valiendo false para retomar la reproduccin
}
}
@Override
public void onResume() {
super.onResume();
if (inicializado && !pausado) {
mp.start();
}
}
Si probamos este cdigo, vemos que todo funciona como es de esperar, excepto un pequeo detalle.
Cuando la reproduccin est en marcha, si llevamos la aplicacin a un segundo plano con el botn de
inicio (ya sabes que es diferente a utilizar el de retroceso, lo cual la destruira), al volver a abrir el
programa la reproduccin comienza desde el principio, en vez de continuar en el punto en el que estaba,
lo cual sera ms deseable.
Una posible solucin es guardar el punto en el que est la reproduccin dentro del mtodo onPause, para
poder retomarlo en el mtodoonResume. Bastara con aadir, adems de la lgica declaracin de la
variable entera a nivel de la clase, las siguientes instrucciones en los mtodos indicados:
@Override
public void onPause() {

super.onPause();
if (inicializado && mp.isPlaying()) {
posicion = mp.getCurrentPosition();
mp.pause();
}
}
@Override
public void onResume() {
super.onResume();
if (inicializado) {
mp.seekTo(posicion);
if (!pausado) {
mp.start();
}
}
}
y funcionara perfectamente... excepto en una situacin, por lo dems muy comn: el cambio de
orientacin del dispositivo. En este caso, recuerda que la activity se destruye para volver a iniciarse con la
nueva disposicin grfica. Si guardramos el valor de la variable posicionen un archivo, podramos
recuperarlo de nuevo al iniciar el programa, pero recuerda que tenemos una solucin intermedia (incluso
ms lgica en este caso); los mtodos onSaveInstanceState y onRestoreInstanceState. En ellos,
guardaremos tanto la posicin de reproduccin, como el valor de las variables inicializado y pausado, para
poder retomar el mismo estado en el que dejamos la activity:
@Override
protected void onSaveInstanceState(Bundle guardarEstado) {
super.onSaveInstanceState(guardarEstado);
if (inicializado) {
int pos = mp.getCurrentPosition();
guardarEstado.putInt("posicionGuardada", pos);
guardarEstado.putBoolean("estaPausada", pausado);
guardarEstado.putBoolean("estaInicializado", inicializado);
}
}
@Override
protected void onRestoreInstanceState(Bundle recEstado) {
super.onRestoreInstanceState(recEstado);
if (recEstado != null) {
posicion = recEstado.getInt("posicionGuardada");
pausado = recEstado.getBoolean("estaPausada");
inicializado = recEstado.getBoolean("estaInicializado");
if (inicializado) {
preparaCancion();
}
}
}

Reproduccin de vdeo

Si lo que quieres es reproducir vdeo, el procedimiento es muy similar, pero hay una serie de conceptos a
tener en cuenta:

Se utiliza un objeto VideoView para obtener su superficie de visualizacin (surfaceHolder) y


poder asignrsela al objeto MediaPlayer

As, en el XML descriptor del entorno de usuario tendramos algo as:


<LayoutPrincipal>

<VideoView android:id="@+id/surfaceView"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_below="@+id/ButonsLayout"/>

</LayoutPrincipal>

para luego, desde la clase principal obtener el surfaceHolder:


private MediaPlayer mediaPlayer;
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;

public void onCreate(Bundle bundle) {


super.onCreate(bundle);
setContentView(R.layout.main);
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
surfaceHolder = surfaceView.getHolder();

que asignaremos al mediaPlayer en su inicializacin:

mediaPlayer = new MediaPlayer();


mediaPlayer.setDataSource(path);
mediaPlayer.setDisplay(surfaceHolder);
mediaPlayer.prepare();
// mMediaPlayer.prepareAsync(); Para streaming

Adems, el objeto SurfaceHolder nos permitira implementar su interface


SurfaceHolder.Callback para poder gestionar cundo la superficie (y por ende, la actividad) es
creada, modificada o destruida con sus
mtodos surfaceCreated, surfaceChanged ysurfaceDestroyed.

En el caso de querer implementar, tanto esta interface, como las asociadas a la clase MediaPlayer habra
que hacer lo siguiente:
En la cabecera de la clase:
public class VideoPlayer extends Activity implements
OnBufferingUpdateListener, OnCompletionListener,
MediaPlayer.OnPreparedListener, SurfaceHolder.Callback {
...}
registramos el objeto surfaceHolder que va a recoger los eventos en esta actividad (por ejemplo, despus
de obtener la referencia al mismo):
surfaceHolder.addCallback(this);
Haramos lo mismo con el resto de interfaces para el MediaPlayer (despus de su inicializacin):
mediaPlayer.setOnBufferingUpdateListener(this);
mediaPlayer.setOnCompletionListener(this);
mediaPlayer.setOnPreparedListener(this);
para finalmente implementar los mtodos como necesitemos; un ejemplo podra ser:
public void onBufferingUpdate(MediaPlayer arg0, int percent) {
estado.setText("onBufferingUpdate percent:" + percent);
}
public void onCompletion(MediaPlayer arg0) {

estado.setText("onCompletion called");
}
public void onPrepared(MediaPlayer mediaplayer) {
estado.setText("onPrepared called");
int mVideoWidth = mediaPlayer.getVideoWidth();
int mVideoHeight = mediaPlayer.getVideoHeight();
if (mVideoWidth != 0 && mVideoHeight != 0) {
surfaceHolder.setFixedSize(mVideoWidth, mVideoHeight);
mediaPlayer.start();
}
}
public void surfaceCreated(SurfaceHolder holder) {
estado.setText("surfaceCreated called");
preparaVideo();
}
public void surfaceChanged(SurfaceHolder surfaceholder, int i, int j, int k) {
estado.setText("surfaceChanged called");
... }
public void surfaceDestroyed(SurfaceHolder surfaceholder) {
estado.setText("surfaceDestroyed called");
... }

Ejemplo de MediaPlayer
Crearemos una aplicacin que nos muestre el uso de un objeto MediaPlayer para reproducir y detener un
archivo de audio relativamente grande.
Tambin introduciremos la clase SoundPool, que permite manejar y reproducir de forma rpida una
coleccin de recursos de audio. Esta clase utiliza el servicio MediaPlayer para decodificar el audio
previamente, lo que le permite evitar una sobrecarga de trabajo de CPU en su reproduccin, ya que se

evita el tener que decodificarlo cada vez. Se utiliza habitualmente en las aplicaciones para reproducir
efectos de sonido instantneamente.
Puedes utilizar cualquier archivo de sonido de tu coleccin (preferiblemente pequeo para el SoundPool)
o emplear los que estn en la carpeta Recursos/Necesarios de tu DropBox. Se
llaman main_theme.ogg y sonido_acierto.ogg
Vamos a incluirlos como recursos internos al proyecto; para ello, utiliza el botn derecho sobre la
carpeta res para crear dentro de ella una nueva carpeta llamada raw. Como recordars, esta carpeta est
destinada a contener ficheros adicionales que no se encuentran en formato XML. Copia en ella los
archivos de sonido.

Interfaz
Vamos a disponer de un interfaz muy sencillo; disalo as:

APARIENCIA

CODIGO

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/playMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtPlay" />
<Button
android:id="@+id/pauseMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtPause" />
<Button
android:id="@+id/stopMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtStop" />

<Button
android:id="@+id/playSp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="@string/txtEfecto" />
<TextView
android:id="@+id/txtEstado"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/txtTexto"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
La idea es evidente; pulsando el primer botn (llamado playMP) empezar la reproduccin del
archivo main_theme.ogg. El usuario tiene la opcin de pulsar Pausa o Stop. Ojo; habr que controlar por
cdigo que desde el estado Stop no se puede acceder al estado Pausa. Adems, en cualquier momento
podremos escuchar el efecto de sonido pulsando el botn al efecto. Mientras tanto, iremos visualizando el
estado en el que se encuentra el objeto MediaPlayer en la etiqueta txtEstado.

Cdigo para el SoundPool


Primero nos centraremos en el objeto de tipo SoundPool; veremos cmo se define y asociaremos su
reproduccin al evento onClick del botn Efecto.
Necesitamos declarar dos variables dentro de la clase MainActivity; el propio objeto SoundPool y un
entero que servir para identificar este recurso en concreto. Esto es porque, aunque en este ejemplo slo
reproducimos un sonido, con un solo objeto SoundPool se pueden manejar varios recursos, e incluso
escuchar algunos simultneamente. De hecho, en el constructor hay que especificar el mximo de pistas
que se pueden reproducir simultneamente.
public class MainActivity extends Activity {
private SoundPool sp;
private int intEfecto;
Este cdigo lo incluiremos dentro del mtodo OnCreate, de manera que se inicialice el objeto a la par que
la actividad. Tambin obtendremos la referencia al TextView en la variable estado:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
estado=(TextView)findViewById(R.id.txtEstado);
// SoundPool
sp = new SoundPool(8, AudioManager.STREAM_MUSIC, 0);

this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
intEfecto= sp.load(this,R.raw.sonido_acierto,1);
}
El constructor de SoundPool recibe tres parmetros:

int maxStreams: indica el nmero mximo de pistas que podemos reproducir simultneamente
con este objeto. No tiene por qu coincidir con el nmero de pistas cargadas. Veremos
enseguida que, cuando se pone una pista en reproduccin (con el mtodo play()) hay que indicar
una prioridad. Esta prioridad se utiliza para decidir qu se har cuando el nmero de
reproducciones activas exceda el valor de maxStreams. Es decir, si el recurso que se est
cargando ya "no cabe", se detendr el flujo con la prioridad mas baja, y si hay varios
coincidentes, se detendr el ms antiguo. En caso de que el nuevo flujo sea el de menor
prioridad, este no se reproducir.

int streamType: se indica el tipo de flujo que vamos a usar mediante una de las constantes
definidas en la clase AudioManager. Por ejemplo, se suele utilizar STREAM_MUSIC en los
efectos de los juegos

int srcQuality: indica la calidad de reproduccin. Este atributo actualmente no tiene efecto; se
suele poner el 0.

La siguiente orden (setVolumeControlStream) la hemos incluido para que se pueda utilizar el botn de
control de volumen del dispositivo.
Finalmente, deberamos carqar cada una de las pistas con el mtodo load; en nuestro ejemplo slo
utilizamos una. Estamos utilizando la versin de load que recoge tres argumentos: el contexto (la
propia activity), el recurso que reside en la carpeta raw (fjate que no se incluye la extensin del mismo,
por lo que es imposible introducir dos archivos del mismo nombre aunque tengan diferente extensin) y la
prioridad.
Ya slo nos resta crear un mtodo llamado por ejemplo pulsaEfecto, donde se reproduzca el sonido.

public void pulsaEfecto (View v) {


sp.play(intEfecto, 1, 1, 0, 0, 1);;
}
En el mtodo play hay que indicar el identificador de pista, el volumen para el canal izquierdo y derecho
(0.0 a 1.0), la prioridad, el nmero de repeticiones (-1= siempre, 0=solo una vez, 1=repetir una vez, ) y
el ratio de reproduccin, con el que podremos modificar la velocidad o pitch (siendo 1.0 la reproduccin
normal, en un rango de 0.5 a 2.0)
Para asociar este mtodo al evento onClick del botn playSp podemos utilizar la propiedad On Click del
objeto en el activity_main.xml

android:onClick="pulsaEfecto"

Cdigo para el MediaPlayer

Vamos con el MediaPlayer; inicialmente vamos a utilizar los mtodos apropiados para reproducir el
archivo y detenerla o pausarla a voluntad.
Definimos los mtodos dentro de la clase principal para poder asociarlos al evento OnClick de los
respectivos botones. Podemos ir probando este cdigo:
public void pulsaPlay (View v) {
mp= MediaPlayer.create(this, R.raw.main_theme);
mp.start();
estado.setText("Playing");
}
public void pulsaPause (View v) {
mp.pause();
estado.setText("Pausa");
}

public void pulsaStop (View v) {


mp.stop();
estado.setText("Stop");
}
Como ya sabemos, podramos implementar OnClickListener, pero es mucho ms rpido y sencillo utilizar
la propiedad onclick de los respectivos botones definidos en el XML.
Bien; podemos comprobar que tenemos varios fallos; vemoslos:
1.

Podemos pulsar repetidamente el botn Play, con lo que se van acumulando las reproducciones
(recuerda que, una vez que se lanza, se ocupa de la reproduccin el motor interno del Media
Framework de un modo asncrnico). Por supuesto, la pulsacin de Stop slo detiene la ltima
instancia del objeto MediaPlayer.

2.

Si pulsamos Play, Pause y de nuevo Play, la msica vuelve a empezar desde el principio, porque

3.

se vuelve a crear el objeto de nuevo. Debera continuar en el punto donde la pausamos.


Si durante la reproduccin pulsamos Stop y luego Pausa, la ventana de LogCat muestra un error
de ejecucin. Como ya sabemos, esto es debido a que no se puede invocar al
mtodo pause estando en estado Stopped.

4.

Si el primer botn que pulsamos es el de Stop, no puede ejecutar mp.stop(), porque el


objeto mp an no est creado (ocasiona una NullPointerException).

5.

Sucede exactamente lo mismo si el primer botn pulsado es el de Pausa. En este caso, la


excepcin salta en la orden mp.pause.

6.

Los dos primeros errores los resolveremos de la misma forma; bastar con crear el
objeto MediaPlayer slo cuando no exista previamente o vengamos de pulsar Stop. Para ello,
definimos la variable booleana inicializado, que ponemos a false al inicio y en el
mtodo pulsaStop. Luego, incluimos el siguiente if en pulsaPlay:

if (!inicializado) {
preparaCancion();

}
7.

de manera que preparaCancion contenga este cdigo, que slo se ejecutar en los casos
previstos:

private void preparaCancion () {


mp= MediaPlayer.create(this, R.raw.main_theme);
inicializado=true;
}
8.

El siguiente error (producido cuando pulsamos Stop y a continuacin Pausa) podemos


gestionarlo de la misma forma. Una variable booleana llamada pausado, que pondremos
a true al entrar en pulsaPause y a false al entrar en pulsaPlay. Slo queda comprobar su valor en
el mtodo pulsaStop; inicialmente, el cdigo de este mtodo quedara as:

public void pulsaStop (View v) {


if(!pausado) {
mp.stop();
mp.reset(); //aunque no es estrictamente necesario, es buena prctica para reinicializar correctamente
el objeto
inicializado=false;
estado.setText("Stop");
}
}
9.

Para resolver el cuarto error podemos utilizar la variable ya definida inicializado, ya que solo
debemos ejecutar el cdigo asociado al click en el botn Stop cuando el
objeto MediaPlayer haya sido perfectamente creado (estar en el estado Prepared o Started). La
condicin del ifquedar as:

public void pulsaStop(View v) {


if (inicializado && !pausado) {
...
}
}

El ltimo error es muy parecido al anterior, slo que debemos asegurarnos que estamos en el
estado Started, ya que de otra manera, no es posible invocar al mtodo pause(). Para ello, bastara con
asegurarnos de que mp.isPlaying() vale true, pero tampoco podremos hacerlo si el objeto mp an no ha
sido an creado. De manera que debemos utilizar un and en cortocircuito (con el operador && va
comprobando las condiciones que encuentra de izquierda a derecha slo hasta que hay una que
devuelve false) que detecte esta posibilidad:
public void pulsaPause(View v) {
if (inicializado && mp.isPlaying()) {
pausado = true;
mp.pause();

estado.setText("Pause");
}
}

Para terminar..
An hay algunos detalles que podramos limar y que nos servirn para repasar algunos conceptos
importantes.
Por ejemplo, no hemos necesitado implementar ninguno de los interfaces de programacin disponibles en
esta clase. Podemos ampliar un poco el ejemplo aadiendo en nuestra pantalla una casilla de verificacin
(CheckBox) para definir la reproduccin continua, de manera que, si est marcada, cuando finalice la
reproduccin la retomaremos desde el principio y si no, situaremos el MediaPlayer en Stopping. Para
controlar el final de la reproduccin, implementaremos el mtodo onCompletion de la
interfaz MediaPlayer.OnCompletionListener.
NOTA: Otra posibilidad sera controlar el evento de actualizacin sobre el CheckBox para que,
dependiendo del valor conseguido, definamos el valor de looping (con el mtodo setLooping).
Aade la casilla de verificacin en activity_main.xml. Podemos referenciarla en el
mtodo onCreate llamndola loop, variable CheckBoxque previamente habremos definido.
loop=(CheckBox)findViewById(R.id.checkBox1);
Bien; indicaremos que nuestra clase MainActivity va a implementar la interface OnCompletionListener:
public class MainActivity extends Activity implements MediaPlayer.OnCompletionListener
y dentro del mtodo preparaCancion asociamos el listener al objeto MediaPlayer:
mp.setOnCompletionListener(this);
Perfecto; en la implementacin del mtodo onCompletion iremos a Started o Stopped, segn el valor
del checkbox loop. Para ello, podemos utilizar nuestros mtodos pulsaPlay y pulsaStop.
@Override
public void onCompletion(MediaPlayer mp) {
if (loop.isChecked()) {
pulsaPlay(null);
} else {
pulsaStop(null);
}
}
Vamos a detenernos un momento en el hecho de que la reproduccin contine, incluso cuando la
aplicacin pasa a segundo plano o finaliza. Como ya hemos comentado, esto es debido a que el que se
ocupa de la misma es el motor interno del framework multimedia, por lo que, si no recibe ninguna seal de
paro, contina la reproduccin hasta el final. Cmo podramos solucionar esto?.

La respuesta siempre es la misma; debemos detectar de alguna manera el cambio de escenario o el


cambio en las condiciones a consecuencia de las cuales queremos intervenir. En este caso, tendremos
que sobreescribir los mtodos relacionados con el ciclo de vida de la activity, que nos indicarn que la
actividad ha sido destruida (onDestroy) por lo que deberemos liberar los recursos, ha dejado de estar
activa (onPause) con lo que habr que detener la reproduccin, o ha ido a primer plano, tanto en su
construccin, como volviendo de su inactividad (onResume), en cuyo caso habra que seguir con la
reproduccin si sta ya estaba en marcha.
Debemos tener en cuenta los errores que habamos detectado anteriormente, por lo que habr que definir
bien los if que los evitan. Aadiremos el siguiente cdigo:
@Override
protected void onDestroy() {
super.onDestroy();
if (inicializado) {
mp.release(); // Liberamos recursos
}
}
@Override
public void onPause() {
super.onPause();
if (inicializado && mp.isPlaying()) {
mp.pause(); // podramos tambin stop, pero as podremos guardar la posicin de la reproduccin
// Ojo; la variable pausado debe seguir valiendo false para retomar la reproduccin
}
}
@Override
public void onResume() {
super.onResume();
if (inicializado && !pausado) {
mp.start();
}
}
Si probamos este cdigo, vemos que todo funciona como es de esperar, excepto un pequeo detalle.
Cuando la reproduccin est en marcha, si llevamos la aplicacin a un segundo plano con el botn de
inicio (ya sabes que es diferente a utilizar el de retroceso, lo cual la destruira), al volver a abrir el
programa la reproduccin comienza desde el principio, en vez de continuar en el punto en el que estaba,
lo cual sera ms deseable.
Una posible solucin es guardar el punto en el que est la reproduccin dentro del mtodo onPause, para
poder retomarlo en el mtodoonResume. Bastara con aadir, adems de la lgica declaracin de la
variable entera a nivel de la clase, las siguientes instrucciones en los mtodos indicados:
@Override
public void onPause() {
super.onPause();
if (inicializado && mp.isPlaying()) {
posicion = mp.getCurrentPosition();

mp.pause();
}
}
@Override
public void onResume() {
super.onResume();
if (inicializado) {
mp.seekTo(posicion);
if (!pausado) {
mp.start();
}
}
}
y funcionara perfectamente... excepto en una situacin, por lo dems muy comn: el cambio de
orientacin del dispositivo. En este caso, recuerda que la activity se destruye para volver a iniciarse con la
nueva disposicin grfica. Si guardramos el valor de la variable posicionen un archivo, podramos
recuperarlo de nuevo al iniciar el programa, pero recuerda que tenemos una solucin intermedia (incluso
ms lgica en este caso); los mtodos onSaveInstanceState y onRestoreInstanceState. En ellos,
guardaremos tanto la posicin de reproduccin, como el valor de las variables inicializado y pausado, para
poder retomar el mismo estado en el que dejamos la activity:
@Override
protected void onSaveInstanceState(Bundle guardarEstado) {
super.onSaveInstanceState(guardarEstado);
if (inicializado) {
int pos = mp.getCurrentPosition();
guardarEstado.putInt("posicionGuardada", pos);
guardarEstado.putBoolean("estaPausada", pausado);
guardarEstado.putBoolean("estaInicializado", inicializado);
}
}
@Override
protected void onRestoreInstanceState(Bundle recEstado) {
super.onRestoreInstanceState(recEstado);
if (recEstado != null) {
posicion = recEstado.getInt("posicionGuardada");
pausado = recEstado.getBoolean("estaPausada");
inicializado = recEstado.getBoolean("estaInicializado");
if (inicializado) {
preparaCancion();
}
}
}

Reproduccin de vdeo
Si lo que quieres es reproducir vdeo, el procedimiento es muy similar, pero hay una serie de conceptos a
tener en cuenta:

Se utiliza un objeto VideoView para obtener su superficie de visualizacin (surfaceHolder) y


poder asignrsela al objeto MediaPlayer

As, en el XML descriptor del entorno de usuario tendramos algo as:


<LayoutPrincipal>

<VideoView android:id="@+id/surfaceView"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_below="@+id/ButonsLayout"/>

</LayoutPrincipal>

para luego, desde la clase principal obtener el surfaceHolder:


private MediaPlayer mediaPlayer;
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;

public void onCreate(Bundle bundle) {


super.onCreate(bundle);
setContentView(R.layout.main);
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
surfaceHolder = surfaceView.getHolder();
que asignaremos al mediaPlayer en su inicializacin:
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(path);

mediaPlayer.setDisplay(surfaceHolder);
mediaPlayer.prepare();
// mMediaPlayer.prepareAsync(); Para streaming

Adems, el objeto SurfaceHolder nos permitira implementar su interface


SurfaceHolder.Callback para poder gestionar cundo la superficie (y por ende, la actividad) es
creada, modificada o destruida con sus
mtodos surfaceCreated, surfaceChanged ysurfaceDestroyed.

En el caso de querer implementar, tanto esta interface, como las asociadas a la clase MediaPlayer habra
que hacer lo siguiente:
En la cabecera de la clase:
public class VideoPlayer extends Activity implements
OnBufferingUpdateListener, OnCompletionListener,
MediaPlayer.OnPreparedListener, SurfaceHolder.Callback {
...}

registramos el objeto surfaceHolder que va a recoger los eventos en esta actividad (por ejemplo, despus
de obtener la referencia al mismo):
surfaceHolder.addCallback(this);
Haramos lo mismo con el resto de interfaces para el MediaPlayer (despus de su inicializacin):
mediaPlayer.setOnBufferingUpdateListener(this);
mediaPlayer.setOnCompletionListener(this);
mediaPlayer.setOnPreparedListener(this);
para finalmente implementar los mtodos como necesitemos; un ejemplo podra ser:
public void onBufferingUpdate(MediaPlayer arg0, int percent) {
estado.setText("onBufferingUpdate percent:" + percent);
}
public void onCompletion(MediaPlayer arg0) {
estado.setText("onCompletion called");
}

public void onPrepared(MediaPlayer mediaplayer) {


estado.setText("onPrepared called");
int mVideoWidth = mediaPlayer.getVideoWidth();
int mVideoHeight = mediaPlayer.getVideoHeight();
if (mVideoWidth != 0 && mVideoHeight != 0) {
surfaceHolder.setFixedSize(mVideoWidth, mVideoHeight);
mediaPlayer.start();
}
}
public void surfaceCreated(SurfaceHolder holder) {
estado.setText("surfaceCreated called");
preparaVideo();
}
public void surfaceChanged(SurfaceHolder surfaceholder, int i, int j, int k) {
estado.setText("surfaceChanged called");
... }
public void surfaceDestroyed(SurfaceHolder surfaceholder) {
estado.setText("surfaceDestroyed called");
... }

Ejemplo de MediaPlayer
Crearemos una aplicacin que nos muestre el uso de un objeto MediaPlayer para reproducir y detener un
archivo de audio relativamente grande.
Tambin introduciremos la clase SoundPool, que permite manejar y reproducir de forma rpida una
coleccin de recursos de audio. Esta clase utiliza el servicio MediaPlayer para decodificar el audio
previamente, lo que le permite evitar una sobrecarga de trabajo de CPU en su reproduccin, ya que se
evita el tener que decodificarlo cada vez. Se utiliza habitualmente en las aplicaciones para reproducir
efectos de sonido instantneamente.

Puedes utilizar cualquier archivo de sonido de tu coleccin (preferiblemente pequeo para el SoundPool)
o emplear los que estn en la carpeta Recursos/Necesarios de tu DropBox. Se
llaman main_theme.ogg y sonido_acierto.ogg
Vamos a incluirlos como recursos internos al proyecto; para ello, utiliza el botn derecho sobre la
carpeta res para crear dentro de ella una nueva carpeta llamada raw. Como recordars, esta carpeta est
destinada a contener ficheros adicionales que no se encuentran en formato XML. Copia en ella los
archivos de sonido.

Interfaz
Vamos a disponer de un interfaz muy sencillo; disalo as:
APARIENCIA

CODIGO
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/playMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtPlay" />

<Button
android:id="@+id/pauseMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtPause" />
<Button
android:id="@+id/stopMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtStop" />
<Button
android:id="@+id/playSp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

android:layout_margin="20dp"
android:text="@string/txtEfecto" />
<TextView
android:id="@+id/txtEstado"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/txtTexto"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
La idea es evidente; pulsando el primer botn (llamado playMP) empezar la reproduccin del
archivo main_theme.ogg. El usuario tiene la opcin de pulsar Pausa o Stop. Ojo; habr que controlar por
cdigo que desde el estado Stop no se puede acceder al estado Pausa. Adems, en cualquier momento
podremos escuchar el efecto de sonido pulsando el botn al efecto. Mientras tanto, iremos visualizando el
estado en el que se encuentra el objeto MediaPlayer en la etiqueta txtEstado.

Cdigo para el SoundPool


Primero nos centraremos en el objeto de tipo SoundPool; veremos cmo se define y asociaremos su
reproduccin al evento onClick del botn Efecto.
Necesitamos declarar dos variables dentro de la clase MainActivity; el propio objeto SoundPool y un
entero que servir para identificar este recurso en concreto. Esto es porque, aunque en este ejemplo slo
reproducimos un sonido, con un solo objeto SoundPool se pueden manejar varios recursos, e incluso
escuchar algunos simultneamente. De hecho, en el constructor hay que especificar el mximo de pistas
que se pueden reproducir simultneamente.
public class MainActivity extends Activity {
private SoundPool sp;
private int intEfecto;

Este cdigo lo incluiremos dentro del mtodo OnCreate, de manera que se inicialice el objeto a la par que
la actividad. Tambin obtendremos la referencia al TextView en la variable estado:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
estado=(TextView)findViewById(R.id.txtEstado);
// SoundPool
sp = new SoundPool(8, AudioManager.STREAM_MUSIC, 0);
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
intEfecto= sp.load(this,R.raw.sonido_acierto,1);
}

El constructor de SoundPool recibe tres parmetros:

int maxStreams: indica el nmero mximo de pistas que podemos reproducir simultneamente
con este objeto. No tiene por qu coincidir con el nmero de pistas cargadas. Veremos
enseguida que, cuando se pone una pista en reproduccin (con el mtodo play()) hay que indicar
una prioridad. Esta prioridad se utiliza para decidir qu se har cuando el nmero de
reproducciones activas exceda el valor de maxStreams. Es decir, si el recurso que se est
cargando ya "no cabe", se detendr el flujo con la prioridad mas baja, y si hay varios
coincidentes, se detendr el ms antiguo. En caso de que el nuevo flujo sea el de menor
prioridad, este no se reproducir.

int streamType: se indica el tipo de flujo que vamos a usar mediante una de las constantes
definidas en la clase AudioManager. Por ejemplo, se suele utilizar STREAM_MUSIC en los
efectos de los juegos

int srcQuality: indica la calidad de reproduccin. Este atributo actualmente no tiene efecto; se
suele poner el 0.

La siguiente orden (setVolumeControlStream) la hemos incluido para que se pueda utilizar el botn de
control de volumen del dispositivo.
Finalmente, deberamos carqar cada una de las pistas con el mtodo load; en nuestro ejemplo slo
utilizamos una. Estamos utilizando la versin de load que recoge tres argumentos: el contexto (la
propia activity), el recurso que reside en la carpeta raw (fjate que no se incluye la extensin del mismo,
por lo que es imposible introducir dos archivos del mismo nombre aunque tengan diferente extensin) y la
prioridad.
Ya slo nos resta crear un mtodo llamado por ejemplo pulsaEfecto, donde se reproduzca el sonido.
public void pulsaEfecto (View v) {
sp.play(intEfecto, 1, 1, 0, 0, 1);;
}
En el mtodo play hay que indicar el identificador de pista, el volumen para el canal izquierdo y derecho
(0.0 a 1.0), la prioridad, el nmero de repeticiones (-1= siempre, 0=solo una vez, 1=repetir una vez, ) y
el ratio de reproduccin, con el que podremos modificar la velocidad o pitch (siendo 1.0 la reproduccin
normal, en un rango de 0.5 a 2.0)
Para asociar este mtodo al evento onClick del botn playSp podemos utilizar la propiedad On Click del
objeto en el activity_main.xml
android:onClick="pulsaEfecto"

Cdigo para el MediaPlayer


Vamos con el MediaPlayer; inicialmente vamos a utilizar los mtodos apropiados para reproducir el
archivo y detenerla o pausarla a voluntad.

Definimos los mtodos dentro de la clase principal para poder asociarlos al evento OnClick de los
respectivos botones. Podemos ir probando este cdigo:
public void pulsaPlay (View v) {
mp= MediaPlayer.create(this, R.raw.main_theme);
mp.start();
estado.setText("Playing");
}
public void pulsaPause (View v) {
mp.pause();
estado.setText("Pausa");
}

public void pulsaStop (View v) {


mp.stop();
estado.setText("Stop");
}

Como ya sabemos, podramos implementar OnClickListener, pero es mucho ms rpido y sencillo utilizar
la propiedad onclick de los respectivos botones definidos en el XML.
Bien; podemos comprobar que tenemos varios fallos; vemoslos:
1.

Podemos pulsar repetidamente el botn Play, con lo que se van acumulando las reproducciones
(recuerda que, una vez que se lanza, se ocupa de la reproduccin el motor interno del Media
Framework de un modo asncrnico). Por supuesto, la pulsacin de Stop slo detiene la ltima
instancia del objeto MediaPlayer.

2.
3.

Si pulsamos Play, Pause y de nuevo Play, la msica vuelve a empezar desde el principio, porque
se vuelve a crear el objeto de nuevo. Debera continuar en el punto donde la pausamos.
Si durante la reproduccin pulsamos Stop y luego Pausa, la ventana de LogCat muestra un error

4.

de ejecucin. Como ya sabemos, esto es debido a que no se puede invocar al


mtodo pause estando en estado Stopped.
Si el primer botn que pulsamos es el de Stop, no puede ejecutar mp.stop(), porque el
objeto mp an no est creado (ocasiona una NullPointerException).

5.

Sucede exactamente lo mismo si el primer botn pulsado es el de Pausa. En este caso, la


excepcin salta en la orden mp.pause.

Los dos primeros errores los resolveremos de la misma forma; bastar con crear el
objeto MediaPlayer slo cuando no exista previamente o vengamos de pulsar Stop. Para ello, definimos la
variable booleana inicializado, que ponemos a false al inicio y en el mtodo pulsaStop. Luego, incluimos el
siguiente if en pulsaPlay:
if (!inicializado) {
preparaCancion();

de manera que preparaCancion contenga este cdigo, que slo se ejecutar en los casos previstos:
private void preparaCancion () {
mp= MediaPlayer.create(this, R.raw.main_theme);
inicializado=true;
}
El siguiente error (producido cuando pulsamos Stop y a continuacin Pausa) podemos gestionarlo de la
misma forma. Una variable booleana llamada pausado, que pondremos a true al entrar
en pulsaPause y a false al entrar en pulsaPlay. Slo queda comprobar su valor en el mtodo pulsaStop;
inicialmente, el cdigo de este mtodo quedara as:
public void pulsaStop (View v) {
if(!pausado) {
mp.stop();
mp.reset(); //aunque no es estrictamente necesario, es buena prctica para reinicializar correctamente
el objeto
inicializado=false;
estado.setText("Stop");
}
}

El ltimo error es muy parecido al anterior, slo que debemos asegurarnos que estamos en el
estado Started, ya que de otra manera, no es posible invocar al mtodo pause(). Para ello, bastara con
asegurarnos de que mp.isPlaying() vale true, pero tampoco podremos hacerlo si el objeto mp an no ha
sido an creado. De manera que debemos utilizar un and en cortocircuito (con el operador && va
comprobando las condiciones que encuentra de izquierda a derecha slo hasta que hay una que
devuelve false) que detecte esta posibilidad:

public void pulsaPause(View v) {


if (inicializado && mp.isPlaying()) {
pausado = true;
mp.pause();
estado.setText("Pause");
}
}

Para terminar..
An hay algunos detalles que podramos limar y que nos servirn para repasar algunos conceptos
importantes.
Por ejemplo, no hemos necesitado implementar ninguno de los interfaces de programacin disponibles en
esta clase. Podemos ampliar un poco el ejemplo aadiendo en nuestra pantalla una casilla de verificacin

(CheckBox) para definir la reproduccin continua, de manera que, si est marcada, cuando finalice la
reproduccin la retomaremos desde el principio y si no, situaremos el MediaPlayer en Stopping. Para
controlar el final de la reproduccin, implementaremos el mtodo onCompletion de la
interfaz MediaPlayer.OnCompletionListener.
NOTA: Otra posibilidad sera controlar el evento de actualizacin sobre el CheckBox para que,
dependiendo del valor conseguido, definamos el valor de looping (con el mtodo setLooping).
Aade la casilla de verificacin en activity_main.xml. Podemos referenciarla en el
mtodo onCreate llamndola loop, variable CheckBoxque previamente habremos definido.

loop=(CheckBox)findViewById(R.id.checkBox1);

Bien; indicaremos que nuestra clase MainActivity va a implementar la interface OnCompletionListener:


public class MainActivity extends Activity implements MediaPlayer.OnCompletionListener
y dentro del mtodo preparaCancion asociamos el listener al objeto MediaPlayer:
mp.setOnCompletionListener(this);
Perfecto; en la implementacin del mtodo onCompletion iremos a Started o Stopped, segn el valor
del checkbox loop. Para ello, podemos utilizar nuestros mtodos pulsaPlay y pulsaStop.
@Override
public void onCompletion(MediaPlayer mp) {
if (loop.isChecked()) {
pulsaPlay(null);
} else {
pulsaStop(null);
}
}
Vamos a detenernos un momento en el hecho de que la reproduccin contine, incluso cuando la
aplicacin pasa a segundo plano o finaliza. Como ya hemos comentado, esto es debido a que el que se
ocupa de la misma es el motor interno del framework multimedia, por lo que, si no recibe ninguna seal de
paro, contina la reproduccin hasta el final. Cmo podramos solucionar esto?.
La respuesta siempre es la misma; debemos detectar de alguna manera el cambio de escenario o el
cambio en las condiciones a consecuencia de las cuales queremos intervenir. En este caso, tendremos
que sobreescribir los mtodos relacionados con el ciclo de vida de la activity, que nos indicarn que la
actividad ha sido destruida (onDestroy) por lo que deberemos liberar los recursos, ha dejado de estar
activa (onPause) con lo que habr que detener la reproduccin, o ha ido a primer plano, tanto en su
construccin, como volviendo de su inactividad (onResume), en cuyo caso habra que seguir con la
reproduccin si sta ya estaba en marcha.
Debemos tener en cuenta los errores que habamos detectado anteriormente, por lo que habr que definir
bien los if que los evitan. Aadiremos el siguiente cdigo:

@Override
protected void onDestroy() {
super.onDestroy();
if (inicializado) {
mp.release(); // Liberamos recursos
}
}
@Override
public void onPause() {
super.onPause();
if (inicializado && mp.isPlaying()) {
mp.pause(); // podramos tambin stop, pero as podremos guardar la posicin de la reproduccin
// Ojo; la variable pausado debe seguir valiendo false para retomar la reproduccin
}
}
@Override
public void onResume() {
super.onResume();
if (inicializado && !pausado) {
mp.start();
}
}
Si probamos este cdigo, vemos que todo funciona como es de esperar, excepto un pequeo detalle.
Cuando la reproduccin est en marcha, si llevamos la aplicacin a un segundo plano con el botn de
inicio (ya sabes que es diferente a utilizar el de retroceso, lo cual la destruira), al volver a abrir el
programa la reproduccin comienza desde el principio, en vez de continuar en el punto en el que estaba,
lo cual sera ms deseable.
Una posible solucin es guardar el punto en el que est la reproduccin dentro del mtodo onPause, para
poder retomarlo en el mtodoonResume. Bastara con aadir, adems de la lgica declaracin de la
variable entera a nivel de la clase, las siguientes instrucciones en los mtodos indicados:
@Override
public void onPause() {
super.onPause();
if (inicializado && mp.isPlaying()) {
posicion = mp.getCurrentPosition();
mp.pause();
}
}
@Override
public void onResume() {
super.onResume();
if (inicializado) {
mp.seekTo(posicion);
if (!pausado) {
mp.start();

}
}
}
y funcionara perfectamente... excepto en una situacin, por lo dems muy comn: el cambio de
orientacin del dispositivo. En este caso, recuerda que la activity se destruye para volver a iniciarse con la
nueva disposicin grfica. Si guardramos el valor de la variable posicionen un archivo, podramos
recuperarlo de nuevo al iniciar el programa, pero recuerda que tenemos una solucin intermedia (incluso
ms lgica en este caso); los mtodos onSaveInstanceState y onRestoreInstanceState. En ellos,
guardaremos tanto la posicin de reproduccin, como el valor de las variables inicializado y pausado, para
poder retomar el mismo estado en el que dejamos la activity:
@Override
protected void onSaveInstanceState(Bundle guardarEstado) {
super.onSaveInstanceState(guardarEstado);
if (inicializado) {
int pos = mp.getCurrentPosition();
guardarEstado.putInt("posicionGuardada", pos);
guardarEstado.putBoolean("estaPausada", pausado);
guardarEstado.putBoolean("estaInicializado", inicializado);
}
}
@Override
protected void onRestoreInstanceState(Bundle recEstado) {
super.onRestoreInstanceState(recEstado);
if (recEstado != null) {
posicion = recEstado.getInt("posicionGuardada");
pausado = recEstado.getBoolean("estaPausada");
inicializado = recEstado.getBoolean("estaInicializado");
if (inicializado) {
preparaCancion();
}
}
}

Reproduccin de vdeo
Si lo que quieres es reproducir vdeo, el procedimiento es muy similar, pero hay una serie de conceptos a
tener en cuenta:

Se utiliza un objeto VideoView para obtener su superficie de visualizacin (surfaceHolder) y


poder asignrsela al objeto MediaPlayer

As, en el XML descriptor del entorno de usuario tendramos algo as:


<LayoutPrincipal>

<VideoView android:id="@+id/surfaceView"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_below="@+id/ButonsLayout"/>

</LayoutPrincipal>

para luego, desde la clase principal obtener el surfaceHolder:


private MediaPlayer mediaPlayer;
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;

public void onCreate(Bundle bundle) {


super.onCreate(bundle);
setContentView(R.layout.main);
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
surfaceHolder = surfaceView.getHolder();
que asignaremos al mediaPlayer en su inicializacin:
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(path);
mediaPlayer.setDisplay(surfaceHolder);
mediaPlayer.prepare();
// mMediaPlayer.prepareAsync(); Para streaming

Adems, el objeto SurfaceHolder nos permitira implementar su interface


SurfaceHolder.Callback para poder gestionar cundo la superficie (y por ende, la actividad) es
creada, modificada o destruida con sus
mtodos surfaceCreated, surfaceChanged ysurfaceDestroyed.

En el caso de querer implementar, tanto esta interface, como las asociadas a la clase MediaPlayer habra
que hacer lo siguiente:
En la cabecera de la clase:
public class VideoPlayer extends Activity implements
OnBufferingUpdateListener, OnCompletionListener,
MediaPlayer.OnPreparedListener, SurfaceHolder.Callback {
...}
registramos el objeto surfaceHolder que va a recoger los eventos en esta actividad (por ejemplo, despus
de obtener la referencia al mismo):
surfaceHolder.addCallback(this);
Haramos lo mismo con el resto de interfaces para el MediaPlayer (despus de su inicializacin):
mediaPlayer.setOnBufferingUpdateListener(this);
mediaPlayer.setOnCompletionListener(this);
mediaPlayer.setOnPreparedListener(this);
para finalmente implementar los mtodos como necesitemos; un ejemplo podra ser:
public void onBufferingUpdate(MediaPlayer arg0, int percent) {
estado.setText("onBufferingUpdate percent:" + percent);
}
public void onCompletion(MediaPlayer arg0) {
estado.setText("onCompletion called");
}
public void onPrepared(MediaPlayer mediaplayer) {
estado.setText("onPrepared called");
int mVideoWidth = mediaPlayer.getVideoWidth();
int mVideoHeight = mediaPlayer.getVideoHeight();

if (mVideoWidth != 0 && mVideoHeight != 0) {


surfaceHolder.setFixedSize(mVideoWidth, mVideoHeight);
mediaPlayer.start();
}
}
public void surfaceCreated(SurfaceHolder holder) {
estado.setText("surfaceCreated called");
preparaVideo();
}
public void surfaceChanged(SurfaceHolder surfaceholder, int i, int j, int k) {
estado.setText("surfaceChanged called");
... }
public void surfaceDestroyed(SurfaceHolder surfaceholder) {
estado.setText("surfaceDestroyed called");
... }

Ejemplo de MediaPlayer
Crearemos una aplicacin que nos muestre el uso de un objeto MediaPlayer para reproducir y detener un
archivo de audio relativamente grande.
Tambin introduciremos la clase SoundPool, que permite manejar y reproducir de forma rpida una
coleccin de recursos de audio. Esta clase utiliza el servicio MediaPlayer para decodificar el audio
previamente, lo que le permite evitar una sobrecarga de trabajo de CPU en su reproduccin, ya que se
evita el tener que decodificarlo cada vez. Se utiliza habitualmente en las aplicaciones para reproducir
efectos de sonido instantneamente.
Puedes utilizar cualquier archivo de sonido de tu coleccin (preferiblemente pequeo para el SoundPool)
o emplear los que estn en la carpeta Recursos/Necesarios de tu DropBox. Se
llaman main_theme.ogg y sonido_acierto.ogg
Vamos a incluirlos como recursos internos al proyecto; para ello, utiliza el botn derecho sobre la
carpeta res para crear dentro de ella una nueva carpeta llamada raw. Como recordars, esta carpeta est
destinada a contener ficheros adicionales que no se encuentran en formato XML. Copia en ella los
archivos de sonido.

Interfaz
Vamos a disponer de un interfaz muy sencillo; disalo as:

APARIENCIA

CODIGO
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/andro
id"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margi
n"
android:paddingLeft="@dimen/activity_horizontal_margin
"
android:paddingRight="@dimen/activity_horizontal_margi
n"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/playMP"
android:layout_width="wrap_content"

android:layout_height="wrap_content"
android:text="@string/txtPlay" />
<Button
android:id="@+id/pauseMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtPause" />
<Button
android:id="@+id/stopMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtStop" />
<Button
android:id="@+id/playSp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="@string/txtEfecto" />

<TextView
android:id="@+id/txtEstado"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/txtTexto"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
La idea es evidente; pulsando el primer botn (llamado playMP) empezar la reproduccin del
archivo main_theme.ogg. El usuario tiene la opcin de pulsar Pausa o Stop. Ojo; habr que controlar por
cdigo que desde el estado Stop no se puede acceder al estado Pausa. Adems, en cualquier momento
podremos escuchar el efecto de sonido pulsando el botn al efecto. Mientras tanto, iremos visualizando el
estado en el que se encuentra el objeto MediaPlayer en la etiqueta txtEstado.

Cdigo para el SoundPool


Primero nos centraremos en el objeto de tipo SoundPool; veremos cmo se define y asociaremos su
reproduccin al evento onClick del botn Efecto.
Necesitamos declarar dos variables dentro de la clase MainActivity; el propio objeto SoundPool y un
entero que servir para identificar este recurso en concreto. Esto es porque, aunque en este ejemplo slo
reproducimos un sonido, con un solo objeto SoundPool se pueden manejar varios recursos, e incluso
escuchar algunos simultneamente. De hecho, en el constructor hay que especificar el mximo de pistas
que se pueden reproducir simultneamente.
public class MainActivity extends Activity {
private SoundPool sp;
private int intEfecto;
Este cdigo lo incluiremos dentro del mtodo OnCreate, de manera que se inicialice el objeto a la par que
la actividad. Tambin obtendremos la referencia al TextView en la variable estado:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
estado=(TextView)findViewById(R.id.txtEstado);
// SoundPool
sp = new SoundPool(8, AudioManager.STREAM_MUSIC, 0);
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
intEfecto= sp.load(this,R.raw.sonido_acierto,1);
}

El constructor de SoundPool recibe tres parmetros:

int maxStreams: indica el nmero mximo de pistas que podemos reproducir simultneamente
con este objeto. No tiene por qu coincidir con el nmero de pistas cargadas. Veremos
enseguida que, cuando se pone una pista en reproduccin (con el mtodo play()) hay que indicar
una prioridad. Esta prioridad se utiliza para decidir qu se har cuando el nmero de
reproducciones activas exceda el valor de maxStreams. Es decir, si el recurso que se est
cargando ya "no cabe", se detendr el flujo con la prioridad mas baja, y si hay varios
coincidentes, se detendr el ms antiguo. En caso de que el nuevo flujo sea el de menor
prioridad, este no se reproducir.

int streamType: se indica el tipo de flujo que vamos a usar mediante una de las constantes
definidas en la clase AudioManager. Por ejemplo, se suele utilizar STREAM_MUSIC en los
efectos de los juegos

int srcQuality: indica la calidad de reproduccin. Este atributo actualmente no tiene efecto; se
suele poner el 0.

La siguiente orden (setVolumeControlStream) la hemos incluido para que se pueda utilizar el botn de
control de volumen del dispositivo.
Finalmente, deberamos carqar cada una de las pistas con el mtodo load; en nuestro ejemplo slo
utilizamos una. Estamos utilizando la versin de load que recoge tres argumentos: el contexto (la
propia activity), el recurso que reside en la carpeta raw (fjate que no se incluye la extensin del mismo,
por lo que es imposible introducir dos archivos del mismo nombre aunque tengan diferente extensin) y la
prioridad.
Ya slo nos resta crear un mtodo llamado por ejemplo pulsaEfecto, donde se reproduzca el sonido.

public void pulsaEfecto (View v) {


sp.play(intEfecto, 1, 1, 0, 0, 1);;
}
En el mtodo play hay que indicar el identificador de pista, el volumen para el canal izquierdo y derecho
(0.0 a 1.0), la prioridad, el nmero de repeticiones (-1= siempre, 0=solo una vez, 1=repetir una vez, ) y
el ratio de reproduccin, con el que podremos modificar la velocidad o pitch (siendo 1.0 la reproduccin
normal, en un rango de 0.5 a 2.0)
Para asociar este mtodo al evento onClick del botn playSp podemos utilizar la propiedad On Click del
objeto en el activity_main.xml

android:onClick="pulsaEfecto"

Cdigo para el MediaPlayer


Vamos con el MediaPlayer; inicialmente vamos a utilizar los mtodos apropiados para reproducir el
archivo y detenerla o pausarla a voluntad.
Definimos los mtodos dentro de la clase principal para poder asociarlos al evento OnClick de los
respectivos botones. Podemos ir probando este cdigo:

public void pulsaPlay (View v) {


mp= MediaPlayer.create(this, R.raw.main_theme);
mp.start();
estado.setText("Playing");
}
public void pulsaPause (View v) {
mp.pause();
estado.setText("Pausa");
}

public void pulsaStop (View v) {


mp.stop();
estado.setText("Stop");
}
Como ya sabemos, podramos implementar OnClickListener, pero es mucho ms rpido y sencillo utilizar
la propiedad onclick de los respectivos botones definidos en el XML.
Bien; podemos comprobar que tenemos varios fallos; vemoslos:
1.

Podemos pulsar repetidamente el botn Play, con lo que se van acumulando las reproducciones
(recuerda que, una vez que se lanza, se ocupa de la reproduccin el motor interno del Media
Framework de un modo asncrnico). Por supuesto, la pulsacin de Stop slo detiene la ltima
instancia del objeto MediaPlayer.

2.

Si pulsamos Play, Pause y de nuevo Play, la msica vuelve a empezar desde el principio, porque

3.

se vuelve a crear el objeto de nuevo. Debera continuar en el punto donde la pausamos.


Si durante la reproduccin pulsamos Stop y luego Pausa, la ventana de LogCat muestra un error
de ejecucin. Como ya sabemos, esto es debido a que no se puede invocar al
mtodo pause estando en estado Stopped.

4.

Si el primer botn que pulsamos es el de Stop, no puede ejecutar mp.stop(), porque el


objeto mp an no est creado (ocasiona una NullPointerException).

5.

Sucede exactamente lo mismo si el primer botn pulsado es el de Pausa. En este caso, la


excepcin salta en la orden mp.pause.

Los dos primeros errores los resolveremos de la misma forma; bastar con crear el
objeto MediaPlayer slo cuando no exista previamente o vengamos de pulsar Stop. Para ello, definimos la
variable booleana inicializado, que ponemos a false al inicio y en el mtodo pulsaStop. Luego, incluimos el
siguiente if en pulsaPlay:
if (!inicializado) {
preparaCancion();
}
de manera que preparaCancion contenga este cdigo, que slo se ejecutar en los casos previstos:

private void preparaCancion () {


mp= MediaPlayer.create(this, R.raw.main_theme);
inicializado=true;
}
El siguiente error (producido cuando pulsamos Stop y a continuacin Pausa) podemos gestionarlo de la
misma forma. Una variable booleana llamada pausado, que pondremos a true al entrar
en pulsaPause y a false al entrar en pulsaPlay. Slo queda comprobar su valor en el mtodo pulsaStop;
inicialmente, el cdigo de este mtodo quedara as:
public void pulsaStop (View v) {
if(!pausado) {
mp.stop();
mp.reset(); //aunque no es estrictamente necesario, es buena prctica para reinicializar correctamente
el objeto
inicializado=false;
estado.setText("Stop");
}
}
Para resolver el cuarto error podemos utilizar la variable ya definida inicializado, ya que solo debemos
ejecutar el cdigo asociado al click en el botn Stop cuando el objeto MediaPlayer haya sido
perfectamente creado (estar en el estado Prepared o Started). La condicin del ifquedar as:
public void pulsaStop(View v) {
if (inicializado && !pausado) {
...
}
}

El ltimo error es muy parecido al anterior, slo que debemos asegurarnos que estamos en el
estado Started, ya que de otra manera, no es posible invocar al mtodo pause(). Para ello, bastara con
asegurarnos de que mp.isPlaying() vale true, pero tampoco podremos hacerlo si el objeto mp an no ha
sido an creado. De manera que debemos utilizar un and en cortocircuito (con el operador && va
comprobando las condiciones que encuentra de izquierda a derecha slo hasta que hay una que
devuelve false) que detecte esta posibilidad:
public void pulsaPause(View v) {
if (inicializado && mp.isPlaying()) {
pausado = true;
mp.pause();
estado.setText("Pause");
}
}

Para terminar..

An hay algunos detalles que podramos limar y que nos servirn para repasar algunos conceptos
importantes.
Por ejemplo, no hemos necesitado implementar ninguno de los interfaces de programacin disponibles en
esta clase. Podemos ampliar un poco el ejemplo aadiendo en nuestra pantalla una casilla de verificacin
(CheckBox) para definir la reproduccin continua, de manera que, si est marcada, cuando finalice la
reproduccin la retomaremos desde el principio y si no, situaremos el MediaPlayer en Stopping. Para
controlar el final de la reproduccin, implementaremos el mtodo onCompletion de la
interfaz MediaPlayer.OnCompletionListener.
NOTA: Otra posibilidad sera controlar el evento de actualizacin sobre el CheckBox para que,
dependiendo del valor conseguido, definamos el valor de looping (con el mtodo setLooping).
Aade la casilla de verificacin en activity_main.xml. Podemos referenciarla en el
mtodo onCreate llamndola loop, variable CheckBoxque previamente habremos definido.
loop=(CheckBox)findViewById(R.id.checkBox1);
Bien; indicaremos que nuestra clase MainActivity va a implementar la interface OnCompletionListener:
public class MainActivity extends Activity implements MediaPlayer.OnCompletionListener
y dentro del mtodo preparaCancion asociamos el listener al objeto MediaPlayer:
mp.setOnCompletionListener(this);
Perfecto; en la implementacin del mtodo onCompletion iremos a Started o Stopped, segn el valor
del checkbox loop. Para ello, podemos utilizar nuestros mtodos pulsaPlay y pulsaStop.
@Override
public void onCompletion(MediaPlayer mp) {
if (loop.isChecked()) {
pulsaPlay(null);
} else {
pulsaStop(null);
}
}
Vamos a detenernos un momento en el hecho de que la reproduccin contine, incluso cuando la
aplicacin pasa a segundo plano o finaliza. Como ya hemos comentado, esto es debido a que el que se
ocupa de la misma es el motor interno del framework multimedia, por lo que, si no recibe ninguna seal de
paro, contina la reproduccin hasta el final. Cmo podramos solucionar esto?.
La respuesta siempre es la misma; debemos detectar de alguna manera el cambio de escenario o el
cambio en las condiciones a consecuencia de las cuales queremos intervenir. En este caso, tendremos
que sobreescribir los mtodos relacionados con el ciclo de vida de la activity, que nos indicarn que la
actividad ha sido destruida (onDestroy) por lo que deberemos liberar los recursos, ha dejado de estar
activa (onPause) con lo que habr que detener la reproduccin, o ha ido a primer plano, tanto en su
construccin, como volviendo de su inactividad (onResume), en cuyo caso habra que seguir con la
reproduccin si sta ya estaba en marcha.

Debemos tener en cuenta los errores que habamos detectado anteriormente, por lo que habr que definir
bien los if que los evitan. Aadiremos el siguiente cdigo:
@Override
protected void onDestroy() {
super.onDestroy();
if (inicializado) {
mp.release(); // Liberamos recursos
}
}
@Override
public void onPause() {
super.onPause();
if (inicializado && mp.isPlaying()) {
mp.pause(); // podramos tambin stop, pero as podremos guardar la posicin de la reproduccin
// Ojo; la variable pausado debe seguir valiendo false para retomar la reproduccin
}
}
@Override
public void onResume() {
super.onResume();
if (inicializado && !pausado) {
mp.start();
}
}
Si probamos este cdigo, vemos que todo funciona como es de esperar, excepto un pequeo detalle.
Cuando la reproduccin est en marcha, si llevamos la aplicacin a un segundo plano con el botn de
inicio (ya sabes que es diferente a utilizar el de retroceso, lo cual la destruira), al volver a abrir el
programa la reproduccin comienza desde el principio, en vez de continuar en el punto en el que estaba,
lo cual sera ms deseable.
Una posible solucin es guardar el punto en el que est la reproduccin dentro del mtodo onPause, para
poder retomarlo en el mtodoonResume. Bastara con aadir, adems de la lgica declaracin de la
variable entera a nivel de la clase, las siguientes instrucciones en los mtodos indicados:
@Override
public void onPause() {
super.onPause();
if (inicializado && mp.isPlaying()) {
posicion = mp.getCurrentPosition();
mp.pause();
}
}
@Override
public void onResume() {
super.onResume();
if (inicializado) {

mp.seekTo(posicion);
if (!pausado) {
mp.start();
}
}
}
y funcionara perfectamente... excepto en una situacin, por lo dems muy comn: el cambio de
orientacin del dispositivo. En este caso, recuerda que la activity se destruye para volver a iniciarse con la
nueva disposicin grfica. Si guardramos el valor de la variable posicionen un archivo, podramos
recuperarlo de nuevo al iniciar el programa, pero recuerda que tenemos una solucin intermedia (incluso
ms lgica en este caso); los mtodos onSaveInstanceState y onRestoreInstanceState. En ellos,
guardaremos tanto la posicin de reproduccin, como el valor de las variables inicializado y pausado, para
poder retomar el mismo estado en el que dejamos la activity:
@Override
protected void onSaveInstanceState(Bundle guardarEstado) {
super.onSaveInstanceState(guardarEstado);
if (inicializado) {
int pos = mp.getCurrentPosition();
guardarEstado.putInt("posicionGuardada", pos);
guardarEstado.putBoolean("estaPausada", pausado);
guardarEstado.putBoolean("estaInicializado", inicializado);
}
}
@Override
protected void onRestoreInstanceState(Bundle recEstado) {
super.onRestoreInstanceState(recEstado);
if (recEstado != null) {
posicion = recEstado.getInt("posicionGuardada");
pausado = recEstado.getBoolean("estaPausada");
inicializado = recEstado.getBoolean("estaInicializado");
if (inicializado) {
preparaCancion();
}
}
}

Reproduccin de vdeo
Si lo que quieres es reproducir vdeo, el procedimiento es muy similar, pero hay una serie de conceptos a
tener en cuenta:

Se utiliza un objeto VideoView para obtener su superficie de visualizacin (surfaceHolder) y


poder asignrsela al objeto MediaPlayer

As, en el XML descriptor del entorno de usuario tendramos algo as:


<LayoutPrincipal>

<VideoView android:id="@+id/surfaceView"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_below="@+id/ButonsLayout"/>

</LayoutPrincipal>

para luego, desde la clase principal obtener el surfaceHolder:


private MediaPlayer mediaPlayer;
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;

public void onCreate(Bundle bundle) {


super.onCreate(bundle);
setContentView(R.layout.main);
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
surfaceHolder = surfaceView.getHolder();
que asignaremos al mediaPlayer en su inicializacin:
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(path);
mediaPlayer.setDisplay(surfaceHolder);
mediaPlayer.prepare();
// mMediaPlayer.prepareAsync(); Para streaming

Adems, el objeto SurfaceHolder nos permitira implementar su interface


SurfaceHolder.Callback para poder gestionar cundo la superficie (y por ende, la actividad) es

creada, modificada o destruida con sus


mtodos surfaceCreated, surfaceChanged ysurfaceDestroyed.
En el caso de querer implementar, tanto esta interface, como las asociadas a la clase MediaPlayer habra
que hacer lo siguiente:
En la cabecera de la clase:
public class VideoPlayer extends Activity implements
OnBufferingUpdateListener, OnCompletionListener,
MediaPlayer.OnPreparedListener, SurfaceHolder.Callback {
...}
registramos el objeto surfaceHolder que va a recoger los eventos en esta actividad (por ejemplo, despus
de obtener la referencia al mismo):
surfaceHolder.addCallback(this);
Haramos lo mismo con el resto de interfaces para el MediaPlayer (despus de su inicializacin):
mediaPlayer.setOnBufferingUpdateListener(this);
mediaPlayer.setOnCompletionListener(this);
mediaPlayer.setOnPreparedListener(this);

para finalmente implementar los mtodos como necesitemos; un ejemplo podra ser:
public void onBufferingUpdate(MediaPlayer arg0, int percent) {
estado.setText("onBufferingUpdate percent:" + percent);
}
public void onCompletion(MediaPlayer arg0) {
estado.setText("onCompletion called");
}
public void onPrepared(MediaPlayer mediaplayer) {
estado.setText("onPrepared called");
int mVideoWidth = mediaPlayer.getVideoWidth();

int mVideoHeight = mediaPlayer.getVideoHeight();


if (mVideoWidth != 0 && mVideoHeight != 0) {
surfaceHolder.setFixedSize(mVideoWidth, mVideoHeight);
mediaPlayer.start();
}
}
public void surfaceCreated(SurfaceHolder holder) {
estado.setText("surfaceCreated called");
preparaVideo();
}
public void surfaceChanged(SurfaceHolder surfaceholder, int i, int j, int k) {
estado.setText("surfaceChanged called");
... }
public void surfaceDestroyed(SurfaceHolder surfaceholder) {
estado.setText("surfaceDestroyed called");
... }

Ejemplo de MediaPlayer
Crearemos una aplicacin que nos muestre el uso de un objeto MediaPlayer para reproducir y detener un
archivo de audio relativamente grande.
Tambin introduciremos la clase SoundPool, que permite manejar y reproducir de forma rpida una
coleccin de recursos de audio. Esta clase utiliza el servicio MediaPlayer para decodificar el audio
previamente, lo que le permite evitar una sobrecarga de trabajo de CPU en su reproduccin, ya que se
evita el tener que decodificarlo cada vez. Se utiliza habitualmente en las aplicaciones para reproducir
efectos de sonido instantneamente.
Puedes utilizar cualquier archivo de sonido de tu coleccin (preferiblemente pequeo para el SoundPool)
o emplear los que estn en la carpeta Recursos/Necesarios de tu DropBox. Se
llaman main_theme.ogg y sonido_acierto.ogg
Vamos a incluirlos como recursos internos al proyecto; para ello, utiliza el botn derecho sobre la
carpeta res para crear dentro de ella una nueva carpeta llamada raw. Como recordars, esta carpeta est

destinada a contener ficheros adicionales que no se encuentran en formato XML. Copia en ella los
archivos de sonido.

Interfaz
Vamos a disponer de un interfaz muy sencillo; disalo as:

APARIENCIA

CODIGO
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/playMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtPlay" />
<Button
android:id="@+id/pauseMP"

android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtPause" />
<Button
android:id="@+id/stopMP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtStop" />
<Button
android:id="@+id/playSp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="@string/txtEfecto" />
<TextView
android:id="@+id/txtEstado"
android:layout_width="match_parent"

android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/txtTexto"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
La idea es evidente; pulsando el primer botn (llamado playMP) empezar la reproduccin del
archivo main_theme.ogg. El usuario tiene la opcin de pulsar Pausa o Stop. Ojo; habr que controlar por
cdigo que desde el estado Stop no se puede acceder al estado Pausa. Adems, en cualquier momento
podremos escuchar el efecto de sonido pulsando el botn al efecto. Mientras tanto, iremos visualizando el
estado en el que se encuentra el objeto MediaPlayer en la etiqueta txtEstado.

Cdigo para el SoundPool


Primero nos centraremos en el objeto de tipo SoundPool; veremos cmo se define y asociaremos su
reproduccin al evento onClick del botn Efecto.
Necesitamos declarar dos variables dentro de la clase MainActivity; el propio objeto SoundPool y un
entero que servir para identificar este recurso en concreto. Esto es porque, aunque en este ejemplo slo
reproducimos un sonido, con un solo objeto SoundPool se pueden manejar varios recursos, e incluso
escuchar algunos simultneamente. De hecho, en el constructor hay que especificar el mximo de pistas
que se pueden reproducir simultneamente.
public class MainActivity extends Activity {
private SoundPool sp;
private int intEfecto;
Este cdigo lo incluiremos dentro del mtodo OnCreate, de manera que se inicialice el objeto a la par que
la actividad. Tambin obtendremos la referencia al TextView en la variable estado:

Mdulo 9.La seguridad en Android


En este mdulo vamos a tratar de la poltica de seguridad
que se ha implantado en el sistema Android. Veremos que
se basa en tres pilares fundamentales:
1.- Se crea un usuario Linux por cada aplicacin instalada;
realmente, el que recibe los permisos y ejecuta la aplicacin
es este usuario.
2.- Cada aplicacin debe estar firmada por un certificado
digital vlido que identifica sin lugar a dudas al responsable
de la misma y asegura a la vez que no ha sido modificada
desde su firma.

3.- En el proceso de instalacin de cualquier aplicacin, se le debe indicar al usuario qu permisos


necesita y ste es el que debe dar su beneplcito.
Estudiaremos estos conceptos para conocer sus implicaciones. Como vemos, el usuario final slo puede
intervenir aceptando o denegando estos permisos, lo cual cancela el proceso de instalacin.

Poltica de seguridad
Las aplicaciones que corren en dispositivos mviles tienen la facultad de acceder y manejar los
dispositivos y datos que contienen; as, un programa malicioso podra ver y enviar por Internet nuestros
contactos, fotos, ubicacin geogrfica, autopropagarse por correo electrnico, etc
Para evitar este tipo de tropelas (aunque siempre se puede colar malware en las tiendas de
aplicaciones), cada compaa sigue una poltica de seguridad concreta:

Apple valida cuidadosamente todas las aplicaciones como paso previo a su publicacin en Apple
Store.

Windows Phone Store tambin revisa exhaustivamente todas las aplicaciones antes de que sean
publicadas. Las plataformasWindows Mobile basan los niveles de acceso en los certificados
digitales con que se ha firmado el software, distinguiendo entre aplicaciones firmadas
privilegiadas, firmadas no privilegiadas y no firmadas, dejndole al usuario la responsabilidad de
elegir la poltica de seguridad a emplear en su dispositivo.

Android propone un esquema de seguridad ms abierta. Antes de 2012, las aplicaciones no se


analizaban hasta que algn usuario avisara de su mala intencin, pero a partir de esta fecha ha
incluido un escaneo automtico del software, previo a su publicacin. Adems, se basa en tres
conceptos, que desarrollamos a continuacin:

Se define un usuario para cada aplicacin

Como Android est basado en Linux, se incorporan las medidas de seguridad


implementadas en este sistema operativo. As, se puede impedir que las
aplicaciones tengan acceso directo al hardware o interfieran con recursos de
otras aplicaciones. Incluso, un fichero creado por una aplicacin no puede ser
ledo ni alterado por otra aplicacin, a no ser que el programador lo haya
explicitado as; para ello, al crear el fichero se usan los
modos MODE_WORLD_READABLE y/oMODE_WORLD_WRITEABLE; de
esa manera, permites a otras aplicaciones que puedan leer o escribir en el
fichero.
NOTA: Aunque otras aplicaciones puedan escribir en el fichero, el propietario siempre
ser el usuario asignado a la aplicacin que lo cre.
La seguridad se define a nivel de proceso, lo que implica que se crea una cuenta de usuario Linux con un
identificador nico, para cada aplicacin que se instala, de manera que es a esa cuenta a quien se le
asignan los permisos de que va a disponer la aplicacin. Es como si el proceso tuviera a su alcance
nicamente sus propios recursos, estando totalmente aislado del resto del sistema.

Por ello, habitualmente el cdigo de dos paquetes no puede ejecutarse en el mismo proceso. Para
solventar estos casos excepcionales es necesario usar el mismo usuario, lo cual se consigue mediante el
atributo sharedUserId en el AndroidManifest.xml. Adems, por razones de seguridad, ambas aplicaciones
han de estar firmadas con el mismo certificado digital (a efectos de seguridad es como si fueran una sola
aplicacin).
Cuando el usuario desinstala una aplicacin, la cuenta de usuario asociada se elimina.

Toda aplicacin ha de ser firmada con un certificado digital vlido


Para poder publicar una aplicacin en Play Store, ha de estar
firmada con un certificado digital.
Esto asegura la identidad del autor y tambin garantiza que el
fichero APK no ha sido modificado. Si se desea realizar
cualquier modificacin en la aplicacin (por ejemplo actualizarla
a una nueva versin) tendr que ser firmada de nuevo.

El usuario debe aceptar los permisos requeridos por la aplicacin


Lgicamente, para poder cumplir sus requerimientos, muchas aplicaciones requieren el acceso a
recursos, componentes o herramientas que podran complicar la seguridad del sistema. En estos casos,
antes de poder instalarlas, se muestran los permisos necesarios y es el usuario el que debe acatarlos; en
caso contrario, el programa no se instala.
No existe la posibilidad de otorgar algunos permisos y otros no; o se aceptan todos o se deniegan todos.
NOTA: Existen aplicaciones que permiten restringir de forma selectiva
los permisos a los que accede cada aplicacin, pero slo se pueden
ejecutar en sistemas rooteados.
Si una aplicacin intentara ejecutar una accin comprometedora sin haber
pedido el permiso necesario, se generara una excepcin y el programa quedara
automticamente interrumpido.
Cada vez que instalemos una aplicacin, deberamos leer detenidamente qu
permisos nos est solicitando, de manera que slo deberamos finalizar el
proceso si son necesarios para realizar las acciones que esperamos de ella. Por
ejemplo, un solitario no debera pedirnos permiso para acceder a nuestros
contactos, pero si se trata de un juego de mus on-line donde puedes retar a tus conocidos eligindolos de
la agenda, s ser razonable.
NOTA: En el proceso de instalacin, se puede encontrar una descripcin algo ms detallada de las
implicaciones de los permisos pulsando sobre cada uno de ellos.
Tambin puedes ver los permisos que ha otorgado a las aplicaciones cuando ya estn disponibles en el
dispositivo. Para ello, desde Ajustes accede a Administrador de aplicaciones; al seleccionar una
aplicacin puedes ver los permisos que le has otorgado a la misma.
Debemos ser conscientes de que siempre se puede camuflar un malware con una apariencia inocente y
adecuada a los permisos que se solicitan, por lo que la mejor forma de asegurarse es leer los comentarios

de usuarios anteriores e investigar en sitios especializados. En cualquier caso, conviene saber


exactamente cules son los riesgos que estamos asumiendo; enseguida desarrollaremos minuciosamente
la poltica de permisos.

Acciones que no requieren de permisos


Es interesante descubrir que hay una serie de acciones que no requieren de permisos por parte del
usuario. Aunque en principio no comprometen la seguridad del sistema, s que pueden llegar a
comprometer nuestra privacidad. Vemoslas:

Memorias externas
Con software desarrollado para Android 3.0 y anteriores se pueden leer los
archivos no ocultos de las memorias de almacenamiento externo, como la
tarjeta SD.
Aunque la mayora de archivos los guardamos en la memoria interna (sta
s que est protegida), podemos tener fotos, contactos o incluso
aplicaciones que pueden ser ejecutadas desde la SD Card.
NOTA: Si tu dispositivo corre con Android 4.1 o superior, puedes hacer este
permiso obligatorio para todas las aplicaciones marcando la opcin Proteger
tarjeta SD que encontrars dentro deAjustes/Opciones de desarrollador.

Aplicaciones instaladas
Se puede saber qu aplicaciones tenemos instaladas en nuestro dispositivo, ya que se puede acceder sin
permiso a la carpeta /data/system/packages.list

Alguna informacin del dispositivo


Podemos obtener determinada informacin sin disponer del permiso READ_PHONE_STATE. Sin
embargo, es bastante limitada ya que nicamente podemos conocer el proveedor de red y la versin de
la ROM. El IMEI o la versin de software no son accesibles sin este permiso.

Modelo de permisos Android


En este enlace puedes consultar todos los permisos definidos por la plataforma Android, aunque quizs
prefieras leerlos traducidos al castellano en esta wiki (gracias al creador y a los organizadores de
"Utilidades y Aplicaciones Informticas")
A efectos didcticos resulta ms eficaz centrarnos, como usuarios, en aquellas acciones ms "peligrosas",
ya que son las que tendremos que controlar especialmente. Las he agrupado en:

Comunicacin con el exterior

Informacin confidencial

Sistema

Uso del hardware

Aado entre parntesis el nombre e informacin que aparece en la relacin de permisos al instalar la
aplicacin.

Comunicacin con el exterior


Tienen una importancia fundamental, ya que componen la "frontera" de nuestro dispositivo. Cualquier
programa malicioso quedar muy limitado si no tiene la facultad de enviarse a s mismo o transmitir al
exterior la informacin que haya podido recopilar.

Los permisos que controlan el envo de datos son:

INTERNET (Comunicacin por red - Acceso a Internet sin lmites)


Permite a una aplicacin abrir sockets. Lgicamente es la va de comunicacin principal para el software
malicioso.
Lo cierto es que es un permiso que se solicita habitualmente; por ejemplo, es muy habitual en
aplicaciones gratuitas que se financian introduciendo banners publicitarios, los cuales se descargan
dinmicamente de un servidor.
NOTA: Virus como Gingermaster utilizan este permiso (junto con otros) para enviar informacin del
terminal infectado a un servidor remoto mediante peticiones HTTP; con estos datos, el servidor responde
enviando ciertos parmetros de configuracin que le permiten establecerse como superusuario e instalar
nuevo malware.

SEND_SMS (Servicios por los que tienes que pagar Envo SMS/MMS)
Permite a una aplicacin enviar SMS.

CALL_PHONE (Servicios por los que tienes que pagar Llamar


directamente a nmeros de telfono)
Permite realizar llamadas de telfono directamente; sin necesidad de usar el dial.
Tanto este permiso, como el anterior, pueden ocasionar un aumento en el coste de nuestra factura, segn
la tarifa que tengamos contratada.

Informacin confidencial

Algunos tipos de malware estn diseados para capturar datos personales, costumbres de navegacin o
bsquedas por Internet, etc. Estos son los permisos que les daran acceso a esta informacin

READ_SMS (Tus mensajes - Leer SMS)


Permite a una aplicacin leer los sms.
Se ha detectado que este permiso ha sido utilizado por algunas aplicaciones maliciosas para dar de alta
inadvertidamente al usuario en servicios premium. En concreto, servicios que envan un cdigo de
confirmacin dentro de un SMS; la aplicacin fraudulenta, despus de obtenerlo, lo inserta en la web
automticamente finalizando el proceso de alta. Por otra parte, otras aplicaciones lcitas (como Facebook)
tambin lo utilizan cuando el usuario introduce el nmero de telfono en los datos de su cuenta, para
poder confirmarlo automticamente, ya que este proceso tambin se apoya en el envo de un cdigo va
SMS.

READ_PHONE_STATE (Llamadas de telfono -Leer estado del telfono)


Acceso al estado del telfono.
Aunque parezca inofensivo al ser un permiso de solo lectura, hay que tener en cuenta que tambin le
otorga acceso al IMEI, IMSI y al identificador nico de 64 bits que Google asigna a cada terminal. Por otra
parte, es un permiso bastante comn, ya que le permite a la aplicacin detectar que estamos recibiendo
una llamada y as dar curso a la misma.

READ_CALENDAR (Tu informacin personal -Leer datos de calendario)


Permite leer las citas y eventos almacenados en el calendario del usuario.

READ_CONTACTS (Tu informacin personal -leer datos de contacto)


Permiso para que la aplicacin pueda leer los datos de tus contactos
Un virus podra utilizarlo para autopropagarse a los dispositivos de todos sus conocidos (con los permisos
adecuados de comunicacin con el exterior)

READ_CALL_LOG (Leyendo informacin de interaccin - Leer registro de


llamadas)
Permite a la aplicacin leer el registro de llamadas del usuario

READ_PROFILE (Su informacin personal - Leer sus datos de perfil)


Con este permiso, las aplicaciones pueden leer los datos personales del perfil del usuario

READ_HISTORY_BOOKMARKS (Lectura de favoritos e historial de


Internet - Leer en favoritos e historial de Internet)
Permite el acceso a ambos elementos del navegador de Internet.

Acceso a las cuentas


Los dispositivos Android tienen capacidad de guardar y gestionar cuentas creadas por el usuario. Hay
varios permisos que dan acceso a las mismas en diferentes grados. Son:

AUTHENTICATE_ACCOUNTS (Sus cuentas - Actuar como


autenticador de cuentas)
Una aplicacin que disfrute de este permiso puede emular un identificador de cuentas para intentar
romper la contrasea en un ataque de fuerza bruta, de diccionario o cualquier otro mtodo de cracking.

GET_ACCOUNTS (Sus cuentas - Descubrir cuentas conocidas)


Permite acceder al listado de cuentas registradas en el sistema. Este sera el menos peligroso en cuestin
de seguridad de las cuentas, aunque podra utilizar estos datos para recopilaciones estadsticas

MANAGE_ACCOUNTS (Sus cuentas - Aadir o eliminar cuentas)


Permite a una aplicacin administrar las cuentas, lo cual lo convierte en el permiso ms relevante.

USE_CREDENTIALS (Sus cuentas - Use credenciales de


autenticacin de cuenta)
Permite a la aplicacin solicitar tokens de autentificacin almacenados en el Gestor de Cuentas.
Hay servidores que soportan este tipo de elementos, que son cadenas de texto que permiten autenticar
solicitudes al servidor sin necesidad de enviar la contrasea real del usuario. Cuando se crean s que se
usan los datos de login pero normalmente, se guardan para su reutilizacin en el gestor de cuentas
(AccountManager), lo cual podra utilizar un programa malicioso para su beneficio.

Localizacin geogrfica del dispositivo


Hay tres modos posibles de localizar el dispositivo. Estn limitados por estos tres permisos:

ACCESS_COARSE_LOCATION (Tu ubicacin Ubicacin comn


(basada en red) )
Localizacin por ip; se basa en los accesos de redes inalmbricas que es capaz de detectar el dispositivo,
como por ejemplo redes wifi.

ACCESS_FINE_LOCATION (Tu ubicacin Precisar la ubicacin


(GPS) )
Permiso necesario para poder usar el GPS del dispositivo. Este mtodo de localizacin utiliza satlites
geoestacionarios y requiere visin directa, por lo que solo es viable en el exterior. Como contrapartida es
mucho ms precisa que la anterior.

ACCESS_LOCATION_EXTRA_COMMANDS (Herramientas del


sistema - Acceso a comandos de ubicacin adicionales del
proveedor)
Localizacin utilizando datos de los operadores de telefona.

Sistema
Agruparemos en esta seccin aquellos permisos que pueden comprometer la estabilidad del sistema.

CHANGE_CONFIGURATION (Herramientas del sistema - Cambiar interfaz


de usuario)
Permite a la app cambiar nuestras fuentes de letra, el idioma, el tamao de la letra,

WRITE_EXTERNAL_STORAGE (Almacenamiento Modificar/borrar


archivos en SD)
Permite a una aplicacin leer y escribir en el almacenamiento externo.

WRITE_SETTINGS (Herramientas del sistema Modificar los ajustes del


sistema global)
Una aplicacin que disponga de este permiso puede leer y escribir en los ajustes del sistema

Otros permisos de sistema no disponibles en aplicaciones de terceros


Existen permisos que pueden ser muy peligrosos para el sistema, y por esa razn no se pueden aplicar
en aplicaciones creadas por terceros (third-party applications), por lo que slo los encontraremos
puntualmente en algn programa suministrado directamente por Google. En esta categora estaran:

BRICK
Deshabilita completamente el dispositivo

CLEAR_APP_USER_DATA
Una aplicacin con este permiso puede borrar los datos de usuario

DELETE_PACKAGES
Permite el borrado de aplicaciones

MOUNT_FORMAT_FILESYSTEMS
Posibilita el formateo y montaje de sistemas de ficheros en almacenamientos extrables

READ_LOGS

Con l se podran leer los logs (fichero que registra los eventos acaecidos) creados por el sistema, los
cuales pueden contener informacin confidencial

WRITE_SECURE_SETTINGS
Permite leer y escribir en los ajustes de seguridad.

MASTER_CLEAR
Permite a la aplicacin realizar un reseteo total con los valores de fabrica del dispositivo.

Uso del hardware


Los permisos que hemos insertado en esta categora no son especialmente peligrosos en cuanto a la
seguridad de los datos, ni del sistema; pero s que podran afectar a la privacidad del usuario, al uso de la
batera o simplemente al uso inadecuado de las herramientas hardware del dispositivo. Son los ms
incongruentes cuando son solicitados sin necesidad.

CAMERA (Cmara- Hacer fotos/grabar vdeos)


Requerido para acceder a la cmara del dispositivo y realizar as fotos o grabaciones de vdeo

FLASHLIGHT (Afecta a la batera - Controlar linterna)


Permite a la aplicacin el uso del flash

RECORD_AUDIO (Micrfono - Grabar audio)


Permite realizar grabaciones de audio

VIBRATE (Afecta a la batera - Vibrador de control)


Requerido para acceder al vibrador

Otros permisos de uso del harrdware no disponibles en aplicaciones de


terceros

MODIFY_PHONE_STATE
Este permiso posibilita la modificacin del estado del telfono. Necesario por ejemplo, para bloquear
llamadas o mensajes entrantes

SET_TIME
Posibilita el cambio de hora del dispositivo

Cmo solicitar permisos en una aplicacin

Es sencillo solicitar permisos en las aplicaciones; basta con incluir una etiqueta <uses-permission> en el
fichero AndroidManifest.xml. En el siguiente ejemplo estaramos solicitando permiso para utilizar el GPS y
para enviar SMS:

<manifest package="com.ejemplos.un_programa" >

<usespermission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
</manifest>

Consejos finales
Nunca est de ms repetir los consabidos consejos de seguridad. Estoy seguro de que sigues muchos de
ellos; pero quiz te replantees hacer algo ms por la seguridad y el mantenimiento de tus datos.

Recomendaciones a la hora de instalar aplicaciones Android


1.- Lee detenidamente los permisos que solicita asegurndose de que los necesita para hacer su funcin.
2.- Infrmate sobre la aplicacin antes de instalarla; visita el sitio web del creador y sobretodo lea las
opiniones de otros usuarios.
3.- Descarga aplicaciones nicamente de Google Play. Es posible que las que encuentres en otros
proveedores estn troyanizadas; es decir, que se haya modificado el cdigo fuente de una aplicacin
legtima, de manera que el malware aprovecha los permisos que sta ha solicitado para sus propios fines.

Consejos generales de seguridad


1.- Aunque procedamos con cautela, no se puede asegurar que estamos totalmente a salvo de una
infeccin, ya que se utilizan tcnicas muy variadas y efectivas. Por ello, se recomienda disponer de
un antivirus efectivo y que se actualice automticamente.
2.- El smartphone puede estropearse con relativa facilidad pues est muy expuesto a golpes o descuidos.
Deberas disponer de una copia de seguridad de tus datos y archivos principales en la nube.
3.- Tambin deberas protegerte frente a posibles robos o prdidas del dispositivo. Es muy aconsejable
instalar algn software anti-roboque permita recoger datos desde Internet como su ubicacin GPS, la
emisin de una alarma o incluso el bloqueo total del telfono.
4.- Conviene revisar que la configuracin de seguridad de tu mvil sea adecuada. Considera si efectuar, o
no, los siguientes ajustes:
4.1.- Definir una opcin de bloqueo de pantalla.
Esto te asegura de que no podr usarlo ninguna persona que desconozca el patrn de desbloqueo.
En algunos dispositivos se puede especificar desbloqueo facial o mediante cara y voz, pero los ms
habituales y seguros son el dibujo de un patrn en la pantalla, mediante un PIN o con contrasea.

4.2.- Activar la opcin de bloqueo de tarjeta SIM


De esta forma hay que introducir el PIN adecuado cada vez que se enciende el mvil
4.3.- Algunos mviles disponen la opcin de activar el rastreador de mvil
As, cuando se inserta una nueva tarjeta SIM o USIM en el smartphone, se enva automticamente
el nmero de contacto de la nueva tarjeta SIM introducida al destinatarios especificado. Esto puede
ayudarnos a localizar y recuperar el dispositivo en caso de prdida o robo.
5.- Actualizacin automtica, tanto del sistema operativo, como de las apps instaladas
En las actualizaciones se suelen corregir fallos en la seguridad (bugs). Tratndose de aplicaciones,
tambin es habitual introducir nuevas funcionalidades o mejorar las ya existentes.
6.- Es conveniente activar el wifi y el bluetooth slo cuando se necesiten.
Si vas a utilizar una red wifi abierta, evita introducir datos de login o datos personales; desde este tipo de
redes nunca deberas acceder a tus cuentas del Banco, por ejemplo.

Mdulo 10.- Localizacin geogrfica en Android


Introduccin y Objetivos
Como ya sabemos existen apps que ofrecen servicios
LBS (Location Based Services) que dependen de la
ubicacin del dispositivo. Algunos ejemplos de estos
servicios son la publicidad selectiva, localizacin de
emergencias, seguimiento de personas o mercancas
en movimiento, avisos climatolgicos,...
En este mdulo describiremos el API que incorpora
Android para conocer la posicin geogrfica del
dispositivo. Veremos que se puede utilizar el GPS y/o
los proveedores de Localizacin de Red de Android, los
cuales se basan en la red de telefona mvil y en las
redes Wi-Fi al alcance del dispositivo. El primero es
ms exacto, pero slo funciona en espacios abiertos y
consume mucha ms batera que el segundo. Como
podremos comprobar, hay que solicitar el permisoACCESS_FINE_LOCATION para utilizar el GPS y
el ACCESS_COARSE_LOCATION para utilizar la localizacin basada en redes.
Tambin describiremos cmo podemos incorporar a nuestra aplicacin servicios realizados por terceros;
en concreto instalaremos una vista que nos permitir representar un mapa de Google Maps.
As, los objetivos planteados para este mdulo son:

Describir los diferentes sistemas de posicionamiento disponibles en los dispositivos mviles


actuales.

Describir las APIs de Android para la geolocalizacin.

Mostrar estrategias para elegir un proveedor de localizacin.

Aprender a incorporar en nuestra aplicacin el servicio de un tercero. En este caso, Google


Maps.

Descripcin de los sistemas de geolocalizacin


La posibilidad de detectar dnde se encuentra el dispositivo, ampla enormemente las posibilidades que
podemos generar en nuestras aplicaciones.
Vamos a describir brevemente cmo funcionan los tres sistemas disponibles para entender sus
limitaciones y comprender as que son complementarios, de manera que ser habitual hacer un uso
conjunto de ellos.
Las alternativas son:

utilizar satlites (comnmente llamado GPS, aunque como veremos enseguida, es ms exacto
llamarlo GNSS o Sistema Global de Navegacin por Satlite)

utilizar las redes de telefona mvil (Cell-ID)

basarnos en las redes WiFi que detecta el dispositivo en un momento dado

NOTA: Los dos ltimos se suelen emplear simultneamente; en general, se denomina "Sistema de
localizacin basado en redes"

Sistemas basados en satlites


Funcionamiento y sistemas basados en GNSS
El funcionamiento es el siguiente; se dispone de una constelacin de satlites en rbita que cubren toda
la superficie terrestre; los sistemas que actualmente estn en uso utilizan 24, aunque se estn
desarrollando otros sistemas (Galileo, Beidou) que utilizarn 30.
Cada uno de los satlites, transmite su posicin y el momento exacto en que enva la seal mediante un
reloj atmico; basta con recibir esta informacin de tres satlites para que el dispositivo pueda deducir su
posicin.
Inicialmente se estima la distancia a cada
satlite segn el tiempo transcurrido entre
la emisin de la seal y su recepcin. A
continuacin se realiza una triangulacin:
si trazamos una esfera con centro en
cada satlite y radio igual a la distancia a
la que se encuentra, las esferas
intersectan en el punto donde se
encuentra el receptor. Por eso se
necesitan al menos tres satlites (la
interseccin de dos esferas dibuja una
circunferencia).

Se producen errores en la estimacin por varias causas, como el retraso que sufre la seal en la ionosfera
y en la troposfera (aunque se tiene en cuenta un retraso medio), la posicin del satlite o incluso el error
que pueda llevar el reloj interno del dispositivo.
Aunque solemos llamar GPS a este sistema de geolocalizacin, se denomina Sistema Global de
Navegacin por Satlite (GNSS); el GPS o Sistema de Posicionamiento Global es el que fue desarrollado,
instalado y empleado por el Departamento de Defensa de los Estados Unidos y el ms conocido por
nosotros. Alcanz "capacidad operacional total" en abril de 1995
De hecho, existen otros sistemas basados en GNSS: desde Enero de 1996 la entonces Unin Sovitica y
ahora la Federacin Rusa mantiene operativo su sistema llamado GLONASS (GLObal'naya
NAvigatsionnaya Sputnikovaya Sistema).
Hoy en da la mayora de telfonos inteligentes son compatibles con estos dos sistemas. Para evitar
depender de ellos, la Unin Europea y la Agencia Espacial Europea estn desarrollando su propio
sistema denominado Galileo que, a diferencia de los anteriores, ser de uso civil. Utilizar 30 satlites
apoyados por una red mundial de estaciones terrestres y se espera que est completamente operativo a
partir de 2019.
Otros sistemas de navegacin satelital que estn en proceso de desarrollo son
el Beidou, Compass o BNTS (BeiDou/Compass Navigation Test System) de la Repblica Popular China,
el QZSS (Quasi-Zenith Satellite System) de Japn y el IRNSS (Indian Regional Navigation Satellite
System) de India.

Tcnicas de correccin: GPS Diferencial (DGPS), Sistema de


Aumentacin Basado en Satlites (SBAS) y GPS Asistido (A-GPS)
Debido a los errores intrnsecos del sistema y a la llamada Disponiblidad Selectiva (SA), una degradacin
intencionada de la seal generada por el Gobierno de Estados Unidos hasta el ao 2000, se desarrollaron
dos tcnicas para aumentar la precisin en el clculo de la posicin mediante satlites: DGPS y SBAS.

GPS Diferencial (DGPS)


Se basa en que los errores que se producen en dos receptores muy prximos son prcticamente iguales,
por lo que si tenemos cercano un punto receptor que adems est localizado exactamente, como una
estacin base, puede comparar la estimacin generada por el GPS con su posicin conocida de
antemano y transmitir el desfase para que los receptores prximos puedan corregir el error producido en
su propia estimacin.
Existen varias formas de obtener las correcciones DGPS. Las ms usadas son:

Recibidas por radio, a travs de algn canal preparado para ello, como el RDS en una emisora
de FM.

Descargadas de Internet

Proporcionadas por una red SBAS (ver el punto siguiente).

Sistema de Aumentacin Basado en Satlites (SBAS)


Es un sistema de correccin de los errores generados por el GNSS; se compone de una red de
estaciones terrestres de referencia distribuidas por una amplia zona geogrfica (pases o continentes

enteros) que recogen los datos de los satlites que tienen a la vista y los retransmiten a una instalacin de
procesamiento central, la cual evala la validez de las seales y calcula las correcciones. Esta
informacin se transmite a los receptores mediante un satlite geoestacionario.
Se desarroll inicialmente para mejorar la seguridad en los aviones, ya que otorga mucha fiabilidad en el
clculo de su posicin, tanto horizontal como vertical. As, el receptor de a bordo puede ajustar la
informacin recibida directamente de los satlites GPS para corregir la ruta, realizar mejores
aproximaciones,... actualmente se ha generalizado su uso a otros entornos que tambin requieren de una
alta precisin.
Como se ve en la imagen, existen varios sistemas SBAS. En Estados Unidos existe el WAAS,
en Europa el EGNOS y en Japn el MSAS, todos compatibles entre s.

GPS Asistido (A-GPS o aGPS)


Esta tecnologa permite acelerar el proceso de arranque del GPS.
Cuando encendemos nuestro GPS, lo primero que tiene que hacer es determinar qu satlites tiene a la
vista y tratar de engancharse a ellos segn los datos orbitales que stos envan. Este proceso,
llamado Posicionamiento inicial, puede resultar bastante largo; incluso, si las condiciones son
desfavorables (el aparato est rodeado de edificios altos que provocan mltiples seales rebotadas o la
seal del satlite se recibe atenuada por obstculos) puede ser de varios minutos.
Para acortar este tiempo, el telfono calcula su posicin aproximada segn la celda de telefona mvil a la
cual est conectado y recoge la informacin que necesita de unos servidores dedicados. Los dispositivos
que estn fabricados para disponer habitualmente de conexin de datos, como los mviles, lo harn online (siempre que se disponga de una conexin activa), con lo que dispondrn de los datos prcticamente
en tiempo real. Los dispositivos que funcionan habitualmente en modo off-line descargan un fichero con
los datos cuando tienen un acceso a la red (ya sea a travs de una conexin de
datos GPRS, Ethernet, WiFi, ActiveSync o similar). Este archivo ser utilizado durante varios das hasta
que la informacin quede obsoleta; en ese momento, se genera un aviso para que se vuelvan a actualizar
los datos. Tambin hay dispositivos que pueden funcionar tanto on-line, como off-line, dependiendo de si
existe en ese momento una conexin activa.
NOTA: En este punto resulta muy interesante leer la entrevista que le realizaron en Junio de 2013
al Director general de la compaa GLONASS, Alexandr Gurk.

Sistemas basados en redes


Redes de telefona mvil
Existen varias tcnicas aplicables utilizando la infraestructura de la red telefnica y habitualmente se
combinan varias de ellas. A la hora de aplicar dichos procedimientos hemos de tener en cuenta varias
limitaciones intrnsecas al dispositivo sobre el que estamos trabajando; stas seran el nmero de antenas
disponibles, procesamiento y almacenamiento limitados y sobre todo los datos de la red a los que se tiene
acceso (slo los que pueda proveer el telfono mvil).
Las que ms se utilizan son:
CGI (Cell Global Identity). Tambin denominada CID (Cell ID)
Cada estacin base tiene asociado un cdigo nico llamado Identificador de
Celda (Cell Id) y cada terminal mvil recibe el identificador de la torre que le da
cobertura . Si el dispositivo dispone de un mapa donde figuren todas las
estaciones base de una regin, puede saber que est ubicado bajo la zona
geogrfica de cobertura de la estacin base. Por tanto, la precisin de la
localizacin es de un error tan grande como el radio de cobertura de la clula;
es por esto por lo que se hace necesario combinarla con otras tcnicas.

CGI + Timing Advance (TA)


El Timing Advance es una tcnica que permite estimar aproximadamente la
distancia entre el dispositivo y la estacin base; consiste en medir los retardos
de propagacin de las seales radioelctricas que intervienen en la
comunicacin entre ambos, sabiendo que dichas seales viajan a una
velocidad cercana a la de la luz. Esto nos permite trazar una circunferencia
con el centro situado en la torre de telefona donde estar el mvil.

Enhanced Observed Time Difference (E-OTD)


Mtodo que estima la diferencia entre los tiempos de llegada de la seal
radioelctrica desde un conjunto de estaciones base. No es necesario en este
caso conocer los tiempos de llegada; tan slo basta con conocer la diferencia
de dichos tiempos. Para ello, una de las tcnicas aplicables consiste en medir
las diferencias de fase entre las seales. Para poder aplicar el E-OTD, es
necesario que el dispositivo mvil incorpore esta tecnologa.
NOTA: Si quieres ampliar esta informacin consulta este extenso artculo de kriptopolis

Redes Wifi
Cada punto de acceso WiFi emite su direccin MAC. Adems, el dispositivo receptor conoce el nivel de
intensidad de la seal.

NOTA: Es posible detectar esta seal a 500 metros de distancia (aunque para conectarse hay que
estar a 50 metros, como mximo).
El problema es que no existe una base de datos suficientemente fiable que contenga la ubicacin de
estos PA, ya que es muy variable (pueden trasladarse, eliminarse o aadirse nuevos puntos de acceso
con mucha rapidez). Se intenta subsanar esta circunstancia realizando una "fase de entrenamiento"
mediante un vehculo equipado de GPS que recorre la ciudad y va guardando la identificacin y posicin
de todas las redes WiFi que encuentra.
Hay empresas como Skyhook, especializadas en este tipo de servicios.

Resumen de sus caractersticas


GPS

Cobertura mundial

Alta precisin:

GPS estndar: 10-12 m. el 95% del tiempo.

GPS Diferencial: 2,5 m. el 95% del tiempo

Usando SBAS: 1 m el 97% del tiempo

No funcionan en el interior de edificios

Gran consumo de batera

Algunos dispositivos (sobretodo tabletas) no incorporan el hardware necesario

Telefona mvil

Cobertura igual a la red telefnica

Precisin muy mala: 200 m en reas urbanas y 2-4 Km en reas suburbanas y rurales

Funciona en el interior de edificios.

Consumo mnimo

Redes Wifi
Slo puede haber cobertura en reas entrenadas como zonas urbanas de algunos
pases, aeropuertos, museos, universidades,
La precisin es muy variable ya que depende de la densidad de Puntos de Acceso
Wifi cercanos y del entrenamiento de la zona donde ests
Funciona en el interior de los edificios
Moderado consumo de batera
Esta disponible en todos los dispositivos con detector de Wifi

El API de localizacin de Android


La plataforma Android dispone de un sistema de posicionamiento que integra la tecnologa GNSS y la
basada en redes.
Haremos una aplicacin que mostrar la localizacin del dispositivo y la actualizar cada vez que detecte
un cambio.

Actividad guiada
Inicio
Crea un nuevo proyecto con los siguientes datos:

Project Name: Ubicacion

Package Name: example.ubicacion

Minimun Requiered SDK: API 8: Android 2.2 (Froyo)

Compile With: API 19: Android 4.4

Permisos
El acceso a la localizacin del dispositivo requiere de permisos, para asegurar la privacidad del usuario:

ACCESS_FINE_LOCATION permite acceder a cualquier tipo de sistema de localizacin

ACCESS_COARSE_LOCATION permite acceder nicamente al sistema de localizacin basado


en redes.

NOTA: Solicitar el primero implica una solicitud implcita del segundo.


Recuerda que los permisos se solicitan en el Manifest; puedes hacerlo en el entorno visual, desde la
pestaa Permissions. Asegrate de que seleccionas Uses Permission cuando te pregunta por el tipo de
elemento:

Fjate que se utiliza la constante definida en android.permission; puedes seleccionarla del desplegable:

NOTA: Puedes consultar los permisos, sus usos y los nombres de sus constantes en este enlace de
la documentacin oficial de Android
Esta accin origina la correspondiente entrada en el tag uses-permission:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Diseo de la pantalla
En este caso, sustituye el RelativeLayout por un ScrollView que contenga un TextView llamado salida con
su propiedad Text vaca; adems, pon su Width="match_parent". Obtendremos este cdigo en
el activity_main.xml:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ScrollView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<TextView
android:id="@+id/salida"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
Con esto, la aplicacin podr mostrar en modo texto la informacin disponible

Cdigo
1.- Necesitamos definir un objeto LocationManager, que es el que nos va a permitir acceder a los datos de
ubicacin del dispositivo.

Para instanciarlo, utilizaremos el mtodo getSystemService(LOCATION_SERVICE).


As que declaramos dentro de MainActivity las variables que vamos a utilizar con el TextView y
el LocationManager:
private LocationManager manejador;
private TextView salida;
e instanciamos ambas dentro del mtodo OnCreate:
salida = (TextView) findViewById(R.id.salida);
manejador = (LocationManager) getSystemService(LOCATION_SERVICE);
2.- Lo primero que vamos a escribir es la informacin disponible de los proveedores de localizacin
conocidos. Para ello, mediante el mtodo getAllProviders (), obtendremos un List<String> con sus
nombres. A partir del nombre de cada proveedor, podemos obtener su LocationProvider, del que
podemos obtener sus caractersticas y condiciones bajo las que debe ser utilizado, como por ejemplo si
requiere acceso a satlites o a redes de datos, si es capaz de devolver la altitud, etc.
Para que la programacin sea ms limpia, definiremos un mtodo llamado muestraProveedores () en
nuestra clase que desencadene todo esto para aadir una llamada al mismo en OnCreate. Tambin
implementaremos el mtodo escribe (String), para que el envo de texto al TextView salida sea ms
sencillo:
private void muestraProveedores() {
escribe("Proveedores de localizacin: ");
List<String> proveedores = manejador.getAllProviders();
for (String proveedor : proveedores) {
LocationProvider info = manejador.getProvider(proveedor);
escribe("Nombre: " + info.getName()
+ ", Est activo?="+ manejador.isProviderEnabled(proveedor)
+ ", Precisin horizontal="+ A[Math.max(0, info.getAccuracy())]
+ ", Consumo de batera="+ P[Math.max(0, info.getPowerRequirement())]
+ ", Vale pasta?=" + info.hasMonetaryCost()
+ ", Requiere acceso a la red telefnica?=" + info.requiresCell()
+ ", Requiere acceso a Internet?=" + info.requiresNetwork()
+ ", Requiere acceso a satlites?=" + info.requiresSatellite()
+ ", Puede obtener la altitud?=" + info.supportsAltitude()
+ ", Puede indicar velocidad?=" + info.supportsSpeed() + " ] ");
}
}

private void escribe(String cadena) {


salida.append(cadena + " ");
}
Como los mtodos getAccuracy() y getPowerRequirement() devuelven constantes (definidas en la
clase Criteria), hemos definido dos arrays de Strings para facilitar la lectura:

private static final String[] A = { "n/d", "Preciso", "Impreciso" };


private static final String[] P = { "n/d", "Bajo", "Medio", "Alto" };
Comprueba la aplicacin
3.- El siguiente paso va a ser elegir el mejor de los proveedores de los que disponemos; comenzamos
creando un objeto de la clase Criteria, donde podemos indicar las caractersticas que buscamos. En este
caso, vamos a exigir que sea gratis, capaz de obtener la altura y un nivel de precisin fina.
Criteria criterio = new Criteria();
criterio.setCostAllowed(false);
criterio.setAltitudeRequired(false);
criterio.setAccuracy(Criteria.ACCURACY_FINE);
proveedor = manejador.getBestProvider(criterio, true);
escribe("El mejor proveedor es: " + proveedor + " ");
No olvides declarar la variable proveedor de tipo String.
El mtodo getBestProvider() sirve para seleccionar el mejor proveedor disponible. En este mtodo hay
que indicar el criterio de seleccin y un valor booleano, donde indicamos si solo nos interesa los sistemas
que el usuario tenga actualmente habilitados (puedes comprobar el resultado activando y desactivando el
GPS). Devuelve un String con el nombre del proveedor seleccionado.
NOTA: Puedes indicar otras restricciones; consulta la documentacin de la clase Criteria.
Una vez que sabemos cul es el mejor proveedor, vamos a escribir la ltima localizacin que ha
detectado.
Primero, implementaremos un mtodo llamado dimeLocalizacion, que escribir el objeto Location recibido
como parmetro o un mensaje si lo que recibe es null.
private void dimeLocalizacion(Location localizacion) {
if (localizacion == null)
escribe("Localizacin desconocida ");
else
escribe(localizacion.toString() + " ");
}
Un objeto Location representa un punto del globo terrestre; puede incluir informacin como la latitud,
longitud, sello de tiempo, rumbo, altitud o velocidad.
Incluiremos este cdigo a continuacin
Location localizacion = manejador.getLastKnownLocation(proveedor);
dimeLocalizacion(localizacion);
Como puedes ver, el mtodo getLastKnownLocation pertenece a la clase LocationManager.
La interfaz LocationListener contiene los mtodos que se van a ejecutar cuando haya un cambio de
posicin o cuando el proveedor experimenta algn cambio (activacin, desactivacin, fuera de servicio,...).

Hay que registrar el LocationListener con el mtodorequestLocationUpdates(), el cual recibe 4


parmetros: el nombre del proveedor, el tiempo entre actualizaciones en ms (se recomienda valores
mayores de 60.000 ms), la distancia mnima a partir de la cual recibiremos que ha habido una variacin
de ubicacin y el escuchador de eventos que lo va a implementar, que ser la propia actividad. Haremos
esto en el mtodoonResume() y desactivaremos las notificaciones (con removeUpdates) en el
mtodo onPause(). de esta manera, podremos ahorrar batera si la aplicacin no est activa.
Empecemos indicando que la activity va a implementar LocationListener:
public class MainActivity extends Activity implements LocationListener {
Al aadir el implements, puedes utilizar el aviso que aparecer a la izquierda para aadir los mtodos que
hay que implementar:
@Override
public void onLocationChanged(Location location) {
escribe("Nueva localizacin: ");
dimeLocalizacion(location);
}
@Override
public void onProviderDisabled(String provider) {
escribe("Proveedor deshabilitado: " + proveedor + " ");
}
@Override
public void onProviderEnabled(String provider) {
escribe("Proveedor Habilitado: " + proveedor + " ");
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
escribe("Cambia estado proveedor: " + proveedor + ", estado="+ E[Math.max(0, status)] + ", extras=" +
extras + " ");
}
siendo la matriz de Strings E como sigue:
private static final String[] E = { "Fuera de servicio", "Temporalmente no disponible ", "Disponible" };
Solo falta activar y desactivar el escuchador, que ser la propia activity:

@Override
protected void onResume() {
super.onResume();
manejador.requestLocationUpdates(proveedor, TIEMPO_MIN,
DISTANCIA_MIN, this);

}
@Override
protected void onPause() {
super.onPause();
manejador.removeUpdates(this);
}
Hemos definido las constantes TIEMPO_MIN, DISTANCIA_MIN as:

private static final long TIEMPO_MIN = 10 * 1000; // 10 segundos


private static final long DISTANCIA_MIN = 5; // 5 metros

Emulacin del GPS con Eclipse


Es posible probar el programa desde el ordenador, ya que el plug-in de Android para Eclipse proporciona
un sistema de emulacin del GPS.
Se activa de esta manera:
1.- Selecciona Window > Show View >Others > Emulator Control (dentro de la categora Android)

En la ficha Manual podemos enviar nuevas posiciones al dispositivo virtual activo. Ojo; si tienes el PC
configurado en espaol, debes utilizar la coma decimal.
Las lengetas GPX y KML permiten utilizar ficheros GPX o KML, donde hemos registrado una secuencia
de localizaciones. Existen programas (por ejemplo, Google Earth) que permiten grabar este tipo de
ficheros. Despus podremos reproducir esta secuencia las veces que sea necesario.

Integracin de Google Maps


Son evidentes las ventajas que conseguiremos al incluir la herramienta Google Maps, un conocido
servicio de cartografa on-line, en nuestras aplicaciones.
Vamos a desarrollar una aplicacin que lo haga; esto nos va a permitir estudiar este API y aprender todo
lo necesario para integrar esta biblioteca externa.
La versin 2 del API incorpora interesantes ventajas; entre ellas, destacan la menor cantidad de
informacin que se intercambia con el servidor y la utilizacin de fragments y grficos 3D. Como
inconveniente, resear que esta versin solo funciona si el dispositivo tiene instalado Google Play.

Conviene destacar que a diferencia de Android, Google Maps no es un software libre, por lo que est
limitado a una serie de condiciones de servicio:

Se puede usar gratuitamente, siempre que no se superen 15.000 solicitudes al da.

Se puede incluir propaganda en los mapas

Se puede usar en aplicaciones mviles de pago (a diferencia de sitios Web donde se cobren los
servicios).

Puedes encontrar informacin del uso de este API


en https://developers.google.com/maps/documentation/android/
Desarrollaremos esta actividad en tres pasos:
1.- Importacin de las libreras de Google Maps v2 a nuestro proyecto
2.- Inclusin en el AndroidManifest del API Key obtenido con el certificado digital que estamos
usando para el modo debug o de desarrollo de aplicaciones. El API Key es un cdigo nico que
genera Google asociado a un certificado digital y que nos vincula al uso de esta API. Si queremos
publicar la aplicacin para su distribucin, deberemos generar el API Key con un certificado digital
propio. Como la generacin de este cdigo es algo complicado, lo estudiaremos a continuacin.
Tambin incluiremos en el Manifest los permisos necesarios y la indicacin de que se requiere
OpenGL
3.- Desarrollo del cdigo

Obtencin del API Key


Bsicamente, deberemos acceder a una pgina web determinada donde generaremos el APY Key a partir
de la huella digital (en codificacin SHA1) de nuestro certificado digital de depuracin y del nombre de la
aplicacin. El comando keytool es una herramienta de Java para trabajar con certificados digitales y que
nos indicar cul es esa huella digital; pero antes debemos descubrir dnde est guardado este
certificado que estamos utilizando automticamente para firmar todas nuestras aplicaciones mientras
estn en fase de desarrollo.
1.- Localizacin del Key Store
Buscamos la ruta desde Eclipse seleccionando Ventana/Preferencias/Android/Build se ve el keystore por
defecto para modo Debug (para firmar las aplicaciones en desarrollo). Suele
ser C:\Users\Usuario\.android\debug.keystore; copia esta ruta al portapapeles.

2.- keytool desde lnea de comandos nos indicara el SHA1 de este fichero
Abre la consola de comandos escribiendo cmd en el men Inicio.
Nos tenemos que situar en la carpeta donde tengas instalado java y entrar en bin. La ruta de instalacin
por defecto de java es C:\Archivos de programa\java, por lo que escribiremos:

Ejecutamos keytool con estos parmetros: keytool -v -list -keystore C:\ruta keystore\debug.keystore

Dejamos la contrasea en blanco; al pulsar Intro, obtenemos informacin sobre el almacn de


certificados, de la que nicamente nos interesa el SHA1

NOTA: Puedes dejar la ventana abierta, ya que la vamos a necesitar enseguida; recuerda que para
poder copiar la huella digital, puedes pulsar el botn derecho sobre el texto y seleccionar Marcar. A
continuacin, podrs seleccionar lo que te interese con el ratn y pulsando Ctrl+C, tendrs una
copia en el portapapeles.
3.- Obtencin de la API Key

Con un explorador de Internet solicita la pgina https://code.google.com/apis/console. Al introducir tus


datos de usuario Google, ya puedes iniciar un nuevo proyecto

Vemos que se ha creado un nuevo proyecto llamado API Project y estamos viendo su descripcin
general:

Vamos a renombrarlo utilizando el mismo nombre que usaremos despus en Eclipse; le


llamaremos Ejemplo Mapas. Para ello, pulsamos en Settings/Rename project

A continuacin, comprobaremos los servicios que estn activados pulsando a la izquierda en APIs & auth;
aqu, buscamos Google Maps Android API v2 y lo ponemos en ON pulsando en el propio botn OFF
NOTA: Ojo; no te confundas con otro muy similar (Google Maps API v2)

Ya slo nos queda generar el API Key; entramos en Credentials y, a continuacin, Create new Key.

A continuacin, especificamos Android Key. En el recuadro inferior pegamos la huella digital del
certificado y el nombre del paquete donde vamos a aplicar el servicio, separados por un punto y coma y
pulsamos Create

Listo; ya disponemos del API key.

1.- Importacin de las libreras Google Maps


1.- En primer lugar, vamos a asegurarnos de que el paquete Google Play Services ya est
instalado. Abre el Android SDK Manager (
instalado:

) y asegrate de que Google Play Services ya est

Si no es as (como en la imagen), seleccinalo y pulsa el botn de Install. Tras aceptar la licencia


correspondiente, el paquete quedar instalado en nuestro sistema
2.- A continuacin, importaremos a nuestro workspace un proyecto interno al paquete recin instalado que
contiene las libreras que definen el API de Google Maps. Para ello, selecciona File > Import > Android >
Existing Android Code Into Workspace.
3.- Pulsa en Browse, y accede a la carpeta donde tienes instalado el Android SDK Manager. Aqu,
selecciona la carpeta /android-sdk/extras/google/ google_play_services/libprojects/google-playservices_lib. Debera aparecer seleccionado un proyecto llamado google-play-services_lib. Pulsa
en Finish. No es necesario que copies el proyecto al Workspace

4.- Iniciamos nuevo proyecto con estos datos:


Application Name: Ejemplo Mapas
Project Name: EjemploMapas

Package Name: com.example.ejemplomapas


Minimun Requiered SDK: API 8: Android 2.2 (Froyo)
Compile With: API 19: Android 4.4.2
NOTA: Prueba a modificar el icono, en el cuadro de dilogo correspondiente del asistente,
pulsando Clipart/Choose
5.- Haz click en el botn derecho en este nuevo proyecto y selecciona Properties > Android. En el
grupo Library pulsa en Add y selecciona el proyecto google-play-services_lib.

Con esto, tal y como se ve en la siguiente imagen, ya tenemos las libreras que necesitamos:

2.- AndroidManifest
1.- API Key
En AndroidManifest.xml, dentro del elemento <application>, vamos a indicar dos metadatos; el primero es
el API Key y el segundo, indica la versin de Google Play Services:

<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="AIzaSyCdgI-LxkOkxCM33rvRMps3MLGC1y6jUlE" />
<meta-data
android:name="com.google.android.gms.version"

android:value="@integer/google_play_services_version" />
Por supuesto, el parmetro value del API Key debe contener el valor obtenido antes por ti.
2.- Permisos
Aade los siguientes permisos en el AndroidManifest dentro del elemento <manifest>:
<permission
android:name="com.example.ejemplomapas_permission.MAPS_RECEIVE"
android:protectionLevel="signature" />
<uses-permission android:name="com.example.ejemplomapas.permission.MAPS_RECEIVE" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
El definir y aplicar un nuevo permiso (MAPS_RECEIVE) es para evitar que otro componente pueda
suplantar la app y utilizar los permisos asignados a nuestra actividad en su beneficio, ya que nunca
dispondra del permiso MAPS_RECEIVE.
El tag <uses-feature> sirve para indicar que la aplicacin (a travs de Google maps) requiere OpenGL.

3.- Desarrollo del cdigo


1.- Utilizaremos un Fragment dentro del layout; reemplaza el contenido de activity_main.xml por:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<fragment
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.SupportMapFragment" />
</RelativeLayout>

2.- Indicamos que la actividad principal hereda de FragmentActivity:

public class MainActivity extends FragmentActivity {


...

En este punto prueba la aplicacin en un dispositivo real. Si lo intentas ejecutar en un emulador no


funcionar porque debe tener instalado Google Play:

Bien; a partir de aqu aprenderemos a manejar el mapa y aadir marcadores desde cdigo.
3.- Aadimos tres botones dentro del <RelativeLayout> del activity_main.xml (tras el <fragment >). Este
es el cdigo XML que resulta:
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toLeftOf="@+id/button2"
android:onClick="moveCamera"
android:text="@string/txtB1" />

<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:onClick="animateCamera"
android:text="@string/txtB2" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@+id/button2"
android:onClick="sumaMarcador"
android:text="@string/txtB3" />
El texto de los tres botones es "Ir a Egillor", "Mi Posicin" y "Marcador", respectivamente.
NOTA: Lgicamente, puedes elegir cualquier localidad en vez de Egillor (que es mi pueblo); slo
necesitas saber sus coordenadas.Google Maps te muestra cules son pinchando sobre el lugar elegido
con el botn derecho y seleccionando Qu hay aqu?
4.- Sustituye el contenido de MainActivity.java por el siguiente:
package com.example.ejemplomapas;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMapClickListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
public class MainActivity extends FragmentActivity implements OnMapClickListener {
private final LatLng Egillor = new LatLng(42.847614, -1.810432);
private GoogleMap mapa;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mapa = ((SupportMapFragment)

getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
mapa.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
mapa.moveCamera(CameraUpdateFactory.newLatLngZoom(Egillor, 15));
mapa.setMyLocationEnabled(true);
mapa.getUiSettings().setZoomControlsEnabled(false);
mapa.addMarker(new MarkerOptions()
.position(Egillor)
.title("Egillor")
.snippet("Casa Musurbil")
.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_launcher))
.anchor(0.5f, 0.5f));
mapa.setOnMapClickListener(this);
}
public void moveCamera(View view) {
mapa.moveCamera(CameraUpdateFactory.newLatLng(Egillor));
}
public void animateCamera(View view) {
Location miPos = mapa.getMyLocation();
if (miPos != null)
mapa.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(miPos.getLatitude(),
miPos.getLongitude()), 15));
}
public void sumaMarcador(View view) {
mapa.addMarker(new MarkerOptions().position(new LatLng(mapa.getCameraPosition().target.latitude,
mapa.getCameraPosition().target.longitude)));
}
@Override
public void onMapClick(LatLng puntoPulsado) {
mapa.addMarker(new MarkerOptions().position(puntoPulsado).icon(BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW)));
}
}
Comenzamos declarando dos objetos: Egillor para hacer referencia a la posicin geogrfica de un pueblo
de ese nombre perteneciente a la cuenca de Pamplona y mapa, que nos permitir acceder al
objeto GoogleMap que hemos insertado en el fragment de nuestro Layout.
Este objeto es inicializado al comienzo de onCreate() para, seguidamente, configurarlo:

setMapType() permite seleccionar el tipo de mapa (normal, foto de satlite, hbrido o en relieve).

El mtodo moveCamera() desplaza el rea de visualizacin a una determinada posicin (definida


en la constante Egillor) a la vez que define el nivel de zoom; este nivel ha de estar en un rango
entre 2 (continente) y 21 (calle).

El mtodo setMyLocationEnabled(true) activa la visualizacin de la posicin del dispositivo (capa


mi-localizacin) con una marca triangular azul.

El mtodo getUiSettings() nos permite configurar el interfaz de usuario; en este ejemplo slo
hemos desactivado los botones del zoom pero puedes consultar todas las posibles
configuraciones estudiando la clase UiSettings.

Con el mtodo addMarker() aadimos un marcador en la posicin Egillor utilizando el mismo drawable del
icono. Un valor de (0, 0) lo situara ajustando la esquina superior izquierda del icono a la posicin indicada
y (1, 1) ajustara la esquina inferior derecha. Como el icono elegido tiene forma de crculo, hemos indicado
el valor (0.5, 0.5) para que coincida con su centro.
Posteriormente, registramos un escuchador de evento para detectar las pulsaciones sobre la pantalla; el
escuchador ser la propia aplicacin (this), de manera que en su declaracin hemos aadido el
correspondiente implements OnMapClickListener, lo cual nos impele a incluir el mtodo onMapClick(), el
cual aade un marcador amarillo en el punto que toque el usuario.
A continuacin, hemos incluido los tres mtodos asociados al clic sobre los botones incluidos en el layout:

moveCamera() desplaza el punto de visualizacin a Egillor, manteniendo el zoom que el usuario


est utilizando.

animateCamera(), nos desplaza hasta nuestra posicin actual utilizando una animacin .

sumaMarcador() aade un nuevo marcador en el centro del mapa (obtenido


por getCameraPosition()). En este caso usaremos eldrawable por defecto, sin informacin
adicional.

Mdulo 11.- Servicios


Hasta el momento hemos creado aplicaciones formadas por una serie de actividades, cada una de las
cuales tena asociado un archivo XML que permita construir un elemento de interaccin con el usuario.
Como veremos en este mdulo, una aplicacin puede tener componentes que se ejecutan siempre en
segundo plano, ya que no disponen de interfaz de usuario; son los servicios.
Cuando se lanza un servicio, el sistema lo instancia y llama al mtodo onCreate(). Corresponde al
programador implementar el comportamiento deseado. Es habitual que se defina un hilo de
ejecucin secundario donde se realice el trabajo; de otra manera, lo ejecutar el hilo principal de la
aplicacin que lo ha creado. Para automatizar y facilitar este trabajo, Android propone la
clase IntentService.
De manera que podemos crear un servicio extendiendo la clase Service, con lo que tendremos que
ocuparnos de gestionar el hilo secundario o extendiendo la clase IntentService, que ya lanzar
automticamente el servicio en su propio hilo; esta segunda opcin exige implementar el
mtodo onHandleIntent() que recibir el intent de cada peticin de inicio.

Dos usos diferentes


Podemos utilizarlos con dos objetivos diferentes:

Son denominados servicios started; se implementan cuando queremos constituir un componente


software que no requiere de interaccin con el usuario y que se va a ejecutar en segundo plano,
normalmente durante un largo perodo de tiempo, como por ejemplo la descarga de un archivo,
sincronizar datos de la aplicacin, etc. Este tipo de servicios son iniciados mediante el
mtodo Context.startService(intent), que indica al sistema que lo ejecute, y lo har
indefinidamente, aunque la aplicacin que lo ha generado ya hubiera sido destruida, o hasta que

alguien le indique que lo detenga con Context.stopService(intent). El servicio started tambin


puede detenerse a s mismo (normalmente cuando ha finalizado su tarea) con Service.stopSelf()
o Service.stopSelfResult().
El mtodo startService puede invocarse varias veces, incluso desde diferentes activitys; si ya est
ejecutndose, seguir ejecutndose sin ningn cambio. Sin embargo, una sola llamada
a stopService lo detendr.
Lo ms habitual es que cada servicio realice una sola operacin en su propio hilo y, cuando
finalice, no devuelva ningn resultado al objeto llamador; simplemente se auto-detiene.
NOTA: Hay ocasiones en que no est muy claro si, para realizar una tarea en segundo plano,
deberas implementar un hilo o lanzar un servicio. La clave suele estar en si quieres que el
trabajo slo se realice si la aplicacin que lo va a lanzar est activa (en cuyo caso deberas
implementar un hilo) o si quieres que se haga ese trabajo independientemente de si la
aplicacin que lo ha lanzado sigue viva o no.

Los llamados servicios bound implementan una forma de que nuestra aplicacin se comunique
con otras aplicaciones, en el sentido de que se pueden definir ciertos mtodos que podrn ser
llamados desde otras aplicaciones; es una especie de cliente-servidor. Los servicios bound son
iniciados mediante el mtodo bindService(), que permite establecer una conexin e invocar estos
mtodos. Mltiples componentes pueden enlazar con el servicio a la vez, pero cuando todos
ellos de desenlacen, se destruir.

Es posible definir un servicio de manera que trabaje en ambas vas; bastar con implementar los dos
mtodos implicados: onStartCommand() para permitir que un componente lo lance y onBind() para poder
enlazarlo.

El ciclo de vida de los Servicios


Cuando se inicia un servicio started, sigue un ciclo de vida independiente al del componente que lo ha
lanzado; el servicio podra seguir corriendo en segundo plano indefinidamente, incluso aunque el
componente que lo ha generado hubiera sido destruido. Por eso, es recomendable que se destruya a s
mismo cuando termine su tarea, aunque tambin es posible destruirlo desde el exterior.
Sin embargo, si hemos generado el servicio mediante bindService (y ningn otro componente llama
a startService), como sabemos podrn conectarse al mismo componentes de diferentes aplicaciones y
seguir vivo mientras permanezca alguna de ellas. El servicio se destruir cuando no quede ninguna
conexin activa.
Como puedes comprobar en la imagen, el sistema llama a mtodos diferentes si el servicio se genera
con startService o con bindService:

Para definir un servicio hay que crear una nueva clase descendiente de Service (o de una de sus
subclases) y en ella, sobreescribir estos mtodos:

onCreate()
El sistema llama a este mtodo cuando un componente crea el servicio por primera vez, tanto
con startService, como con bindService. Si el servicio ya est en funcionamiento, este mtodo no se
ejecuta, por lo que deberemos incluir las rdenes que slo queremos que se ejecuten una sola vez, en la
generacin del servicio.

onStartCommand()
Se ejecuta este mtodo cada vez que un componente solicite la ejecucin del servicio
mediante startService. En cuanto se ejecuta este mtodo el sistema mantiene el servicio activo en
segundo plano hasta que l mismo se auto-detiene (con stopSelf o stopSelfResult) u otro lo hace
(con stopService).
NOTA: Si se producen varias llamadas a startService() no supondr la creacin de varios servicios,
aunque s que se realizarn mltiples llamadas a onStartCommand().
Si ests generando un servicio bound al que nicamente vamos a enlazar (con bindService), no es
necesario implementar este mtodo.

onBind()

El sistema llama a este mtodo cuando otro componente quiere enlazar con el servicio llamando
a bindService(Intent servicio, ServiceConnection conexion, int flags). En la implementacin del
mtodo onBind, se debe generar una interfaz que es devuelta por el mtodo mediante un objeto de
tipo IBinder y que los clientes utilizarn para comunicarse con el servicio. Esta interfaz se escribe
en AIDL(Android Interface Definition Language) y permite el intercambio de objetos entre aplicaciones que
corren en procesos separados.
El servicio permanecer en ejecucin tanto tiempo como haya alguna conexin establecida.
A diferencia de lo que sucede con onStartCommand, siempre hay que implementar el mtodo onBind,
pero si no se va a utilizar como servicio bound, devolver null.
NOTA: En este curso no entraremos ms a fondo en el uso de servicios bound, pero si necesitas
ms informacin, puedes consultareste enlace

onDestroy()
Es la ltima llamada que recibe el servicio, ya que el sistema llama a este mtodo justo antes de ser
destruido.
Por esto, dentro del mtodo onDestroy deberamos liberar cualquier recurso empleado, como hilos,
listeners,...

Lanzando un servicio started


Como ya hemos indicado, lanzaremos el servicio cuando un componente de la aplicacin, como por
ejemplo una actividad, llame al mtodo StartService () de Context, pasndole un Intent que especifica el
servicio e incluye todos los datos necesarios. El servicio recibe este intent en el
mtodo onStartCommand (adems de unos flags y el identificador de la llamada), pero si el servicio no
existe previamente, se ejecutar el mtodo onCreate.
Por ejemplo, supn que queremos actualizar algunos datos de una database que est en un servidor de
Internet; la actividad podra generar un Intent metindole esos datos e iniciar el servicio, que se
encargara de establecer la conexin, ejecutar la transaccin y autodestruirse. Sin embargo, hay que
tener muy en cuenta que el servicio estar incluido en el mismo proceso que la aplicacin y, si no
indicamos lo contrario, sera el hilo principal el encargado de ejecutarlo, con lo que el rendimiento de la
aplicacin se vera seriamente afectado.
De manera que, o bien extendemos Service gestionando el hilo nosotros mismos, o bien
extendemos IntentService, que desarrolla automticamente el servicio en un hilo secundario. sta es la
mejor opcin si no se requiere que el servicio maneje mltiples peticiones simultneamente; nicamente,
hay que implementar onHandleIntent(), que es el mtodo que va a recibir el intent. La
clase IntentService automticamente realiza:

La generacin de un hilo secundario para cada Intent que recibe onStartComand

Crea una cola para gestionar estos intents y as pasar uno cada vez al mtodo onHandleIntent.
De esa manera, el programador no tiene que preocuparse de problemas de concurrencia.

Detiene el servicio cuando todas las peticiones de inicio han sido atendidas, de manera que
podemos ahorrarnos el stopSelf.

Inserta la implementacin por defecto del mtodo onBind (recuerda que es obligatoria), para que
devuelva null.

Tambin genera una implementacin del mtodo onStartComand que es el que gestiona
los Intents recibidos, metindolos en la cola conforme llegan y envindolos sucesivamente al
mtodo onHandleIntent, donde se encuentra el cdigo que determina el servicio.

Qu sucede si el sistema decide destruir un servicio para liberar


memoria?
Como comentbamos en un mdulo anterior, si el sistema necesita recursos de memoria para poder
ejecutar una aplicacin en primer plano, deba eliminar alguno de los procesos residentes en memoria
segn ciertas prioridades; por ejemplo, conservar un servicio siempre ser menos prioritario que la
actividad visible en pantalla, aunque ms prioritario que otras actividades en segundo plano y un
servicio startedque lleve mucho tiempo ejecutndose, se eliminar antes que un servicio bound, ya que
an tiene conexiones activas por parte de alguna aplicacin.
Se puede controlar la forma en que queremos que reaccione el sistema cuando vuelve a disponer de
recursos y puede volver a reactivar el servicio detenido.
NOTA: Si trabajas con IntentService, esta clase ya gestiona este comportamiento, aunque tambin
puede ser modificado.
La clave est en el valor que devuelve el mtodo onStartCommand. Tenemos tres constantes definidas
en la clase Service que nos otorgan tres posibilidades:

START_NOT_STICKY: el servicio ser creado de nuevo solo cuando llegue una nueva solicitud
de creacin

START_STICKY: el servicio vuelve a crearse y se llama a onStartCommand, pero no se utiliza


el intent que lo gener, sino uno igual a null.

START_REDELIVER_INTENT: el servicio vuelve a crearse con el mismo intent que lo gener

Declaracin de un servicio en el Manifest


Debemos declarar todos los servicios que vamos a utilizar en nuestra aplicacin mediante la
etiqueta <service> dentro de la de<application> del AndroidManifest.xml:

<manifest ... >


...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>

El atributo android:name es el nico requerido; se indica el nombre de la clase donde se define el servicio
(que es algo que no deberamos cambiar nunca porque pueden existir intents explcitos en otras
aplicaciones que hagan uso de l).
Disponemos de otros parmetros opcionales, como por ejemplo:

android:permission: Para indicar el permiso del que deben disponer las aplicaciones que vayan a
usarlo; es decir, estas aplicaciones debern declararlo con el correspondiente <usespermission> en su propio manifiesto. Si este atributo no est especificado dentro del tag service,
se le aplicarn los mismos permisos que a la aplicacin donde est definido.

android:exported: si vale true, componentes de otras aplicaciones podrn invocarlo o interactuar


con l; si vale false, slo podrn hacerlo componentes de aplicaciones con el mismo identificador
de usuario.

NOTA: Si el servicio est destinado a ser utilizado nicamente por nuestra aplicacin, no debemos
incluir intent-filters y siempre lo lanzaremos mediante intents explcitos (indicando el nombre del
servicio); en este caso, el valor del atributo android:exported por defecto vale false. En el caso
contrario, si el servicio va a ser enlazado o iniciado por otra aplicacin, deberemos definir intentsfiltersque declaren sus capacidades, ya que la otra aplicacin no conocer el nombre del servicio y
deber utilizar un intent implcito para lanzarlo. En ese caso, el valor por defecto del
atributo android:exported vale true.

Actividad guiada
Bien; vamos a las demostraciones prcticas. Como un servicio puede ser tratado de formas diferentes,
vamos a ir desarrollando la actividad sucesivamente pasando por varias etapas.
Iniciaremos un proyecto llamado Servicios, que no realizar ningn trabajo; nicamente enviaremos
mensajes conforme vaya pasando por sucesivos estados y as podremos probar qu sucede en distintas
circunstancias (cuando la aplicacin pasa a segundo plano, cuando finaliza, cuando coexisten varias
instancias de la aplicacin, cuando se solicita varias veces el servicio en la misma aplicacin,...); para ello
no necesitaremos manejar un hilo secundario. Ser el hilo principal el que lleve los dos componentes
(actividad y servicio).
Posteriormente simularemos que el servicio realiza un trabajo que necesita mucho tiempo de
procesamiento y la forma de solventarlo heredando de Service y de IntentService.
NOTA: Haba considerado contemplar que el servicio reprodujera una cancin independientemente
de si la aplicacin sigue activa o no, que es un ejemplo bastante recurrente. Sin embargo, creo
preferible centrarme en los aspectos relativos a los servicios, nicamente; si metemos un
objeto MediaPlayer, ya entra en juego el framework media que es el que finalmente reproduce el
sonido y la propia clase tiene mtodos que evita el posible acaparamiento del hilo principal mientras
se obtiene el recurso (el mtodoprepareAsync, MediaPlayer.onPreparedListener,...). En cualquier
caso, puedes consultar este enlace, que documenta esta situacin.

Paso 1.- Estudiando el ciclo de vida de un servicio


1.- Inicia un nuevo proyecto llamado Servicios
2.- Definiremos un layout muy sencillo; contendr dos botones que inicien y detengan el servicio,
respectivamente. Para ello, usaremos su atributo android:onClick, para asociarles los
mtodos arrancarServicio, al clic del primer botn y detenerServicio, al del segundo.

El cdigo xml necesario es el siguiente:


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="@+id/boton_arrancar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:onClick="arrancarServicio"
android:text="@string/txtB1" />
<Button
android:id="@+id/boton_detener"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:onClick="detenerServicio"
android:text="@string/txtB2" />
</LinearLayout>
Como ves, les he llamado txtB1 y txtB2 a los Strings que muestran los botones
3.- As, el cdigo de la clase principal, ser tan sencillo como ste:

package example.servicios;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void arrancarServicio(View v) {
Intent servicio = new Intent(this, MiServicio.class);
startService(servicio); }
public void detenerServicio(View v) {
Intent servicio=new Intent(this, MiServicio.class);
stopService(servicio);
}
}
4.- Implementamos el servicio, sobreescribiendo los mtodos necesarios, en esta ocasin heredando
de Service, pero sin utilizar ningn hilo secundario:
La implementacin por defecto del mtodo onBind, en la que se devuelve null, se crea automticamente
(al indicar al asistente que heredamos de Service). Podemos pedir a Eclipse que sobreescriba los
mtodos onCreate, onDestroy y onStartCommand, mediante la orden Source/Override-Implement
Methods. En ellos, nicamente mostraremos un Toast con un breve mensaje. ste es el cdigo generado:
package example.servicios;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;
public class MiServicio extends Service {

@Override
public void onCreate() {
super.onCreate();
Toast.makeText(this, "Dentro de onCreate", Toast.LENGTH_SHORT).show();
}
@Override
public void onDestroy() {
Toast.makeText(this,"Servicio detenido", Toast.LENGTH_SHORT).show();
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "Servicio arrancado " + startId, Toast.LENGTH_SHORT).show();
return START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
}
5.- Registramos el nuevo servicio en el Manifest:
<application
...
<activity
android:name="example.servicios.MainActivity"
...
</activity>
<service android:name="MiServicio"></service>
</application>
Puedes hacer diferentes pruebas para estudiar el funcionamiento del servicio en varios escenarios.
Tambin puedes pararlo desde los Ajustes del dispositivo/Gestor de aplicaciones/en ejecucin.

Paso 2.- Gestionando el servicio con un hilo


Ahora vamos a simular que el servicio es el encargado de realizar una operacin que requiere de mucho
tiempo de CPU. Incluiremos en el mtodo onStartCommand la instruccin Thread.sleep para dormir el hilo
durante 10 segundos, lo cual simular el cdigo problemtico. Tras cumplirse, el servicio se detendr a s

mismo con stopSelf. Mantendremos el botn Detener Servicio para poder destruirlo en cualquier
momento, aunque no haya finalizado.
Ahora el mtodo onStartCommand es as:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "Servicio arrancado " + startId,
Toast.LENGTH_SHORT).show();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.stopSelf();
return START_NOT_STICKY;
}

Al lanzar ahora el servicio, es evidente que el usuario percibe como si la aplicacin hubiera quedado
colgada, porque no responde a ninguno de sus requerimientos. Incluso, si dormimos el hilo durante ms
tiempo, obtendremos un dilogo APN (Application Not Responding) y la aplicacin se detendr.
La solucin que proponemos aqu es definir un hilo que gestione el cdigo:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "Servicio arrancado " + startId, Toast.LENGTH_SHORT).show();
Thread hilo = new Thread(new Runnable() {
@Override
public void run() {
// El hilo slo ejecuta una vez el try-catch
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}
MiServicio.this.stopSelf();
}
});
hilo.start();
return START_NOT_STICKY;
}

Paso 3.- Implementamos el servicio extendiendo


IntentService
Un IntentService no proporciona mtodos que se ejecuten en el hilo principal de la aplicacin y que
podamos aprovechar para comunicarnos con nuestra activity durante la ejecucin (a diferencia de otras
herramientas como AsincTask para gestionar hilos). Para ello, deberamos utilizar mensajes broadcast (y
por supuesto su BroadcastReceiver asociado capaz de procesar los mensajes) para comunicar eventos al
hilo principal, como por ejemplo la necesidad de actualizar controles de la interfaz o simplemente para
comunicar la finalizacin de la tarea ejecutada. Veremos un ejemplo en el mdulo 13, cuando estudiemos
los receptores de anuncios.
Al extender la clase de IntentService, es obligatorio escribir el constructor (en este ejemplo nicamente
llamar al constructor padre) y sobrescribir el mtodo onHandleIntent() que es el encargado de contener
el cdigo que va a implementar el servicio.
Tambin sobreescribiremos el mtodo onDestroy para que aparezca un mensaje en la ventana
de Log indicando que el servicio ha sido destruido (bien porque el usuario ha pulsado el botn o porque
ha finalizado su trabajo). No utilizamos Toast porque esto slo es posible desde el hilo principal (como
todo lo relacionado con la interfaz de usuario).
Aadiremos un poco de cdigo para comprobar el paso de parmetros en el intent: el servicio realizar un
nmero de iteraciones aleatorio de un bucle que contiene Thread.sleep (1000) y ese nmero lo recibir en
el intent.
As es como quedan en la clase principal, los mtodos arrancarServicio y detenerServicio asociados a la
pulsacin en los botones:

public void arrancarServicio(View v) {


int numeroIteraciones;
Intent servicio = new Intent(this, MiServicio.class);
numeroIteraciones=(int)(Math.random()*10)+1;
servicio.putExtra("iteraciones", numeroIteraciones);
startService(servicio);
Toast.makeText(this, "Iniciamos MiServicio con
"+numeroIteraciones+", Toast.LENGTH_SHORT).show();
}
public void detenerServicio(View v) {
Intent servicio=new Intent(this, MiServicio.class);
if (stopService(servicio))
Toast.makeText(this, "MiServicio detenido desde el botn", Toast.LENGTH_SHORT).show();
}
Fjate que en detenerServicio hemos comprobado si efectivamente haba algn servicio activo , mediante
el booleano que devuelve el mtodo stopService. De otro modo, siempre escribe el mensaje ante la
pulsacin del botn, aunque no haya servicios en marcha.
La nueva clase MiServicio resulta mucho ms simplificada y elegante al heredar de IntentService:

package example.servicios;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
public class MiServicio extends IntentService {
public MiServicio() {
super("MiServicio");
}
@Override
protected void onHandleIntent(Intent intent) {
int iter = intent.getIntExtra("iteraciones", 1);
for(int i=1; i<=iter; i++) {
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
}
}
@Override
public void onDestroy() {
Log.d("MiServicio", "Servicio destruido");
super.onDestroy();
}
}

Mdulo 12
.- Notificaciones
Hasta el momento, cuando necesitbamos mostrar un aviso en pantalla, recurramos al objeto Toast.
Como hemos podido comprobar, se trata de un mensaje que se mantiene durante un corto perodo de
tiempo en pantalla.
Si necesitamos gestionar alguna accin de respuesta por parte del usuario como consecuencia del
mensaje o simplemente, si queremos que el mensaje se mantenga de forma permanente, una posibilidad
muy poco invasiva es la utilizacin de las Notificaciones. Cuando se crean, aparece un mensaje
desplazndose en el rea de notificaciones, para que posteriormente sea un pequeo icono el que
permanezca.

Cuando el usuario hace el gesto de desplazar hacia abajo el rea de notificaciones, aparece el llamado
buzn de notificaciones (notification drawer) donde puede gestionar la notificacin con un clic o
directamente borrar el contenido de la barra.

A partir de Android 4.1 se ha aadido otra posibilidad de visualizacin ms para el buzn de


notificaciones; una big view, donde se aade un campo ms de informacin (el n 7 en la imagen):

Las notificaciones pueden ser lanzadas por una actividad, por un servicio o por un receptor de anuncios;
veremos stos ltimos en el siguiente mdulo. Tambin es habitual que una aplicacin que est
ejecutndose en segundo plano comunique al usuario una alerta o un aviso lanzando una notificacin.
Se representan como pequeos iconos en la barra superior de la pantalla. Se trata de una comunicacin
que no requiere una interaccin inmediata del usuario; ste puede estar utilizando otra aplicacin sin ser
interrumpido o puede no estar utilizando el telfono en ese momento. Este hecho hace de las
notificaciones un mecanismo de comunicacin ideal para un servicio o para un receptor de anuncios.
NOTA: El diseo de las notificaciones tambin debe seguir una gua de estilo para mantener una
coherencia visual entre todas las aplicaciones Android. Puedes consultarla
en http://developer.android.com/design/patterns/notifications.html

Definiendo una notificacin


Como es habitual, demostraremos el uso de la notificaciones generando una aplicacin que se centre
nicamente en su utilizacin.
Nuestra actividad principal, mostrar nicamente un botn:

cuya pulsacin generar una nueva notificacin:

Si mostramos el buzn de Notificaciones, tendremos:

Y, finalmente, al pulsar sobre el mensaje, aparecer una nueva ventana con un simple TextView:

Definir y mostrar la notificacin


Primero nos centraremos en definir y mostrar la notificacin y en el siguiente punto le daremos
comportamiento al clic del usuario en la misma.
Los primeros pasos ya los tienes claros:
1.- Nuevo proyecto: Podemos llamarlo, por ejemplo, Notificaciones; tambin le he asignado un clipart (
) como icono.

2.- Definimos en activity_main.xml el botn llamado btnNotificacion y le asociamos el


mtodo lanzaNotificacion al evento click. El cdigo que resulta es:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/btnNotificacion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="lanzaNotificacion"
android:text="@string/txtBoton" />
</RelativeLayout>
3.- Vmonos al meollo de la cuestin; el cdigo
En la clase principal:
3.1.- Declaramos un objeto llamado nm de tipo NotificationManager, que va a ser el encargado
de gestionar las notificaciones del terminal y una constante ID_NOTIFICACION_PERSONAL que
valdr por ejemplo 1 y nos va a servir para identificar a nuestra notificacin.
NotificationManager nm;
private static final int ID_NOTIFICACION_PERSONAL=1;
3.2.- Dentro del mtodo OnCreate, instanciamos el NotificationManager:
nm=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
Como puedes ver, para poder inicializar un objeto de esta clase, debemos solicitar al sistema que nos
entregue este servicio, por medio del mtodo getSystemService(String service), el cual devuelve el
manejador del sistema para el servicio que indiquemos como parmetro.
3.3.- Bien; nos queda implementar el mtodo que se va a ejecutar cuando el usuario toque el botn:
public void lanzaNotificacion (View v) {
// Creamos la notificacin y configuramos sus parmetros
NotificationCompat.Builder builder = new NotificationCompat.Builder(getBaseContext())
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("Nuevo aviso!!!")
.setContentText("Si haces clic, vers tu mensaje")

.setWhen(System.currentTimeMillis());
// Enviamos al sistema la notificacin que devuelve el mtodo build
nm.notify(ID_NOTIFICACION_PERSONAL, builder.build());
}
Hemos configurado la notificacin utilizando la inicializacin del builder. Como mnimo, debe contener un
icono pequeo (setSmallIcon), el ttulo (setContentTitle) y el texto descriptivo (setContentText); adems
hemos asignado la hora de la notificacin con la hora actual del dispositivo (setWhen).
NOTA: Hasta hace poco, este proceso se haca directamente a travs de la clase Notification. Sin
embargo, con las ltimas versiones de Android, muchos mtodos se han marcado como obsoletos.
La clase NotificationCompat asegura la compatibilidad con versiones antiguas de Android y dispone
de todos los mtodos de las nuevas versiones.
Si pruebas la aplicacin, todo debera funcionar correctamente; a continuacin le daremos
comportamiento al clic del usuario en la notificacin.
Aunque se pulse repetidamente el botn, no se acumulan nuevas notificaciones, porque al enviarla con el
mismo identificador, se sobreescribe.

Dndole comportamiento
Lo habitual es lanzar una nueva actividad cuando el usuario haga clic en la notificacin.
1.- Primero nos ocupamos de definir el UI y la clase que va a permitir su visualizacin
Creamos un nuevo archivo xml llamado respuesta con un TextView centrado
con padding superior, padding inferior y mrgenes=20

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


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:gravity="center"
android:paddingBottom="20dp"
android:paddingTop="20dp"
android:text="@string/txtNot"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
Creamos ahora la nueva activity llamada Respuesta que es la que va a mostrar el layout definido:

package example.notificaciones;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
public class Resultado extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.respuesta);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
y la registramos en el AndroidManifest:
<activity android:name="Resultado" >
</activity>
2.- Empezaremos definiendo un Intent asociado a la Activity que queremos abrir.
Pero no podemos utilizar este objeto directamente, ya que el responsable de lanzarlo ser el sistema, a
travs del gestor de notificaciones, cuando el usuario haga clic en la notificacin (incluso, la actividad que
la ha generado puede haber finalizado). Debemos definir un objeto de tipo PendingIntent. Esta clase
permite a componentes externos a nuestra aplicacin, generalmente desarrollados por terceros, que
ejecuten un trozo de cdigo que nosotros indicamos (mediante un Intent normal). Para dar una idea ms
sencilla de lo que hace un PendingIntent, se puede entender como un Intent que mandamos al sistema,
pendiente de una ejecucin futura que no sabemos cundo se producir.
Introduciremos este cdigo en el mtodo lanzaNotificacion, justo antes de nm.notify:
// Creamos el Intent que llamar a nuestra Activity
Intent targetIntent = new Intent(getBaseContext(),Resultado.class);
// Creamos el PendingIntent
PendingIntent contentIntent = PendingIntent.getActivity(
getBaseContext(), 0, targetIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
Vemos que el mtodo getActivity recibe 4 parmetros:

Context: El contexto en que se crea el PendingIntent, nos lo facilita el mtodo getBaseContext.

RequestCode: En este caso no necesitamos ningn cdigo de referencia, por lo tanto pasamos
0.

Intent: El Intent que se lanzar cuando se ejecute el PendingIntent.

int: Flags que definen el comportamiento del PendingIntent. En este caso,


pasamos FLAG_UPDATE_CURRENT para indicar que se actualizar la informacin
del PendingIntent en el caso de que se lance y ya existiera uno previo.

3.- Solo queda asociar el PendingIntent al evento clic de la notificacin; para ello, seguimos trabajando
con el builder, ya que llamaremos a su mtodo setContentIntent
Despus, el mtodo setAutoCancel, indica que la notificacin se cancelar a s misma cuando se pulse
sobre ella.
builder.setContentIntent(contentIntent);
builder.setAutoCancel(true);
NOTA: Si no tuviera sentido mantener la notificacin cuando la aplicacin deja de estar viva,
deberamos sobreescribir el mtodoonDestroy de la activity e incluir, despus de la llamada al
mtodo onDestroy de la superclase, lo siguiente:
nm.cancel(ID_NOTIFICACION_PERSONAL);

Parmetros de configuracin
En este punto vamos a comentar algunos mtodos de la clase Notification.Builder que nos van a permitir
personalizar la notificacin. Para ello, suponemos definida e inicializada una variable de este tipo
llamada builder

Valores predeterminados
Si queremos asociar los valores predeterminados del sistema utilizaremos builder.setDefaults(int). El valor
entero que recibe debe ser una de estas contantes o la combinacin de varias de ellas mediante el
operador OR a nivel de bit: DEFAULT_SOUND, DEFAULT_VIBRATE, DEFAULT_LIGHTS.
Por ejemplo, para indicar que la notificacin sea con el sonido y luz predeterminada, haremos:

builder.setDefaults(DEFAULT_SOUND | DEFAULT_LIGHTS);

Asociar un sonido
Para asociar un sonido diferente al predeterminado, habra que almacenarlo en una carpeta y usar su
URI:

builder.setSound(Uri.parse("file:///sdcard/carpeta/tono.mp3"));

Definir la vibracin
Podemos definir un tipo de vibracin personalizada, mediante un patrn de vibracin y el mtodo
setVibrate:

// Patrn de vibracin: 1 segundo vibra, 05 segundos para, 0'5 segundos vibra


long[] pattern = new long[]{1000,500,500};
builder.setVibrate(pattern);
Se requiere permiso de vibracin, por lo que habr que indicar en el manifest:
<uses-permission android:name="android.permission.VIBRATE" />

Personalizacin del LED


Debemos definir tres parmetros: el color del led (como cada dispositivo tiene diferentes limitaciones en
este aspecto, se usar la mejor aproximacin posible), y 2 parmetros, ledOnMS y ledOffMS, que son dos
enteros que indican la frecuencia de parpadeo (los milisegundos encendido y los milisegundos que estar
apagado); aunque segn la documentacin, la mayora de dispositivos ignoran estos valores. Usaremos 1
y 0 respectivamente, con lo que lo mantendremos siempre encendido:
builder.setLights(Color.RED, 1, 0);

Mdulo 13
.- Receptores de anuncios
Un receptor de anuncios (Broadcast Receiver) detecta anuncios globales de tipo broadcast y puede
ejecutar acciones como respuesta; por eso, posibilitan un cierto mimetismo de tus aplicaciones con el
entorno donde se ejecutan.
Existen muchos anuncios de este tipo originados por el sistema, como por ejemplo Batera baja, Llamada
entrante,... aunque tambin las aplicaciones tambin pueden definir y lanzar su propio anuncio broadcast.
Los receptores de anuncios no disponen de interfaz de usuario, por lo que, si necesitan interactuar con l,
deben lanzar una actividad o crear una notificacin. Si hubiera que realizar una tarea persistente en el
tiempo, sin ms intervencin con el usuario, tambin pueden iniciar servicios started (nunca
servicios bound).

Ciclo de vida
Como vamos a ver enseguida, un receptor de anuncios es una clase que extiende la
clase BroadcastReceiver y para la que hay que sobreescribir el mtodo onReceive, donde introduciremos
las acciones a realizar.
Cuando se d el evento para el que hemos registrado el receptor (incluso aunque la aplicacin donde se
ha definido ste no est viva), el sistema inicia el ciclo de vida de todos los Receivers registrados para
ese evento. El ciclo de vida de estos objetos nicamente consta del mtodo onReceive. De hecho, el
componente slo existir mientras se est ejecutando este mtodo; en cuanto termina, el sistema lo
destruye. Por eso, hay que evitar lanzar operaciones asncronas, ya que es posible que el sistema mate al
proceso asociado al receptor de anuncios antes de que stas terminen. Por otro lado, tambin hay que
tener presente que va a ser el hilo principal el que ejecute el mtodoonReceive, por lo que no debemos

bloquearlo absorbiendo demasiado tiempo de ejecucin. Como hemos indicado anteriormente, lo


recomendable es lanzar, segn el caso, una actividad, un servicio started o una notificacin.

Implementacin del BroadcastReceiver


Para mostrar los dos pasos necesarios en la creacin de un receptor de anuncios, vamos a desarrollar
una aplicacin que mostrar unToast cuando se conecte el cargador del dispositivo

Crear la clase que herede de la clase BroadcastReceiver


En un nuevo proyecto llamado AvisoCarga, define una nueva clase llamada ReceptorCarga que
extienda BroadcastReceiver. Si le indicas al asistente que debe heredar de BroadcastReceiver,
automticamente implementa el mtodo onReceive, donde nicamente definiremos el Toast:
package example.avisocarga;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class ReceptorCarga extends BroadcastReceiver {
@Override
public void onReceive(Context contexto, Intent arg1) {
Toast.makeText(contexto, "Ha conectado el cargador.", Toast.LENGTH_SHORT).show();
}
}

Registro del BroadcastReceiver


Como hemos dicho, es imprescindible registrarlo en el Manifest: Adems del nombre del nuevo
componente, tenemos que indicar ante qu anuncio va a reaccionar; esto se realiza con el tag intent-filter.
Veamos la parte del cdigo del Manifest entre <application>:
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="example.avisocarga.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<receiver android:name="ReceptorCarga">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
</intent-filter>
</receiver>
</application>
NOTA: En este caso, no es importante lo que hacemos en el MainActivity, por lo que podemos dejar
las instrucciones que inserta el ADT. Lo importante es la primera instalacin y ejecucin del
proyecto; a partir de ah, el BroadcastReceiver que hemos llamadoReceptorCarga queda habilitado
para cuando se d el evento de sistema. Incluso, funcionar aunque la aplicacin no est en
memoria.
Tambin se puede registrar el receptor de anuncios en tiempo de ejecucin llamando al
mtodo registerReceiver(BroadcastReceiver receiver, IntentFilter filter). Incluso es el nico mtodo
permitido para registrar los receptores que vayan a detectar determinados anuncios.
Algunos BroadcastReceivers requieren solicitar permisos para recibir y reaccionar ante determinados
mensajes Broadcast.
Puedes consultar los nombres de todos los anuncios Broadcast definidos en el sistema en el archivo
llamado broadcast_actions.txt que localizars en la carpeta ...\sdk\platforms\android-nn\data. Tambin he
subido un archivo a la plataforma Moodle llamado anuncios broadcast.pdf realizado por Jess Toms,
autor del libro "El gran libro de Android" y profesor de la UPV. En l puedes encontrar los anuncios ms
comunes organizados por temas; en la primera columna se indican los nombres de las constantes
(definidas en la claseIntent) que utilizaramos para indicar el IntentFilter por cdigo y el valor de la
constante que utilizaramos en el <intent-filter> del Manifest en su parmetro action. En la segunda
columna se indica la descripcin del evento, los permisos que hay que solicitar y la informacin extra que
transporta el Intent que recibe como argumento el mtodo onReceive.

Lanzando una notificacin


Realizaremos a continuacin un proyecto donde registraremos un BroadcastReceiver que reaccionar
ante una llamada entrante y que adems recibir y enviar informacin sobre ella; en este caso, el
nmero de telfono que ha llamado. Ser responsable de lanzar una notificacin de manera que, al hacer
clic en ella, se mostrar este nmero. Tendremos que solicitar el permiso para leer el estado del telfono
(READ_PHONE_STATE).
Crea un nuevo proyecto llamado Llaman.

Preparacin Previa
En este caso, queremos que cuando el usuario haga clic en la notificacin, se visualice el nmero
llamador, as que requerimos unos pasos previos:
1.- Definimos un nuevo archivo llamado respuesta.xml donde definiremos un layout con un TextView.
Tendremos este cdigo:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:gravity="center"
android:paddingBottom="20dp"
android:paddingTop="20dp"
android:text="@string/hello_world"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
NOTA: Al string hello_world le he asignado un espacio en blanco. As, tampoco veremos ningn
texto en la actividad principal.
2.- Creamos la activity MuestraNumero que utilizar el layout definido en respuesta.xml. Necesitamos
obtener la informacin recibida en elIntent (el nmero de telfono) para despus asignrsela al TextView.
Este es el cdigo:
package example.llaman;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
public class MuestraNumero extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle info = getIntent().getExtras();
String num = info.getString("numTfno");
setContentView(R.layout.respuesta);
TextView txt = (TextView)findViewById(R.id.textView1);
txt.setText("Le ha llamado el nmero: "+num);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}

y la registramos en el Manifest:
<activity android:name="MuestraNumero"></activity>

Cdigo del receptor de anuncios


Nuestro BroadcastReceiver tendr el siguiente cdigo:
package example.llaman;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.telephony.TelephonyManager;
import android.util.Log;
public class ReceptorLlamadas extends BroadcastReceiver {
NotificationManager nm;
private static final int ID_NOTIFICACION_LLAMADA = 1;
@Override
public void onReceive(Context context, Intent intent) {
// Sacamos informacin del intent
String estado = "", numero = "";
Bundle extras = intent.getExtras();
if (extras != null) {
estado = extras.getString(TelephonyManager.EXTRA_STATE);
numero = extras.getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
}
String info = estado + " " + numero;
Log.d("Receptor Llamadas", info);
// Creamos Notificacin
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setWhen(System.currentTimeMillis());
builder.setContentTitle("Llamada entrante...");
// Creamos el Intent que llamar a nuestra Activity
Intent targetIntent = new Intent(context,MuestraNumero.class);
targetIntent.putExtra("numTfno", numero);
// Creamos el PendingIntent
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, targetIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(contentIntent);
builder.setAutoCancel(true);
// Enviamos al sistema la notificacin

nm.notify(ID_NOTIFICACION_LLAMADA, builder.build());
}

Registro en el Android Manifest


En este caso, no solo habr que registrarlo, asocindolo al evento android.intent.action.PHONE_STATE,
sino que adems habr que indicar que requiere del permiso READ_PHONE_STATE.
As que aadiremos en el Llaman Manifest lo siguiente:
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<uses-sdk ... />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application
...
<activity
android:name="example.llaman.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="ReceptorLlamadas" >
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>
<activity android:name="MuestraNumero"></activity>
</application>
</manifest>

Lanzando un servicio
En esta ocasin, vamos a crear un servicio que reproduzca una msica de fondo, pero nuestra intencin
es que se inicie tras cargar el sistema operativo; un buen ejemplo que ilustra cmo resolver la necesidad
de implementar alguna accin en segundo plano cada vez que el usuario reinicie su smartphone.
Para ello, crearemos un nuevo proyecto llamado Al Inicio, donde registraremos un Receptor de anuncios
asociado al arranque del sistema, el cual iniciar el servicio. En la actividad principal nicamente
aadiremos un botn para detenerlo y as parar la msica.

1.- Creamos el servicio


Vamos a definir una clase que herede de Service donde un reproductor de tipo MediaPlayer reproducir
una msica.
Antes de nada, creamos una nueva carpeta llamada raw dentro de res, que va a contener la cancin de
"La guerra de las Galaxias" que vamos a reproducir llamada audio.mid. Puedes descargar este fichero de
la plataforma Moodle.
Como ves, he llamado a la clase ServicioReproductor; su cdigo es:
package example.alinicio;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.widget.Toast;
public class ServicioReproductor extends Service {
MediaPlayer reproductor;
@Override
public void onCreate() {
reproductor = MediaPlayer.create(this, R.raw.audio);
}
@Override
public int onStartCommand(Intent intenc, int flags, int idArranque) {
reproductor.start();
return START_STICKY;
}
@Override
public void onDestroy() {
Toast.makeText(this, "Bufff... qu descanso!",Toast.LENGTH_SHORT).show();
reproductor.stop();
}
@Override
public IBinder onBind(Intent intencion) {
return null;
}
}

2.- Creamos el receptor de anuncios

Lo llamaremos por ejemplo, ReceptorArranque; su cdigo es muy sencillo, ya que nicamente inicia el
servicio y muere:
package example.alinicio;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class ReceptorArranque extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
context.startService(new Intent(context, ServicioReproductor.class));
}
}

3.- Modificamos el AndroidManifest


Debemos registrar el servicio ServicioReproductor y el receptor de anuncios ReceptorArranque,
especificando con <intent-filter> que debe responder al arranque del sistema.
Adems, se requiere del permiso RECEIVE_BOOT_COMPLETED para poder detectar este mensaje del
sistema.
Quedar as:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="example.alinicio"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="example.alinicio.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />


</intent-filter>
</activity>
<service android:name="ServicioReproductor" >
</service>
<receiver android:name="ReceptorArranque" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
</manifest>
NOTA: Es imprescindible que el permiso RECEIVE_BOOT_COMPLETED se asocie a todo el proyecto
situndolo dentro de <manifest>. Si se sita dentro del tag <receiver>, no funciona en absoluto.

4.- Botn para detener la reproduccin


Necesitamos incorporar una forma de parar la msica; bastar con incluir un botn en
el activivity_main.xls asociando su evento onClick al mtodo detenerServicio . El cdigo
de activity_main.xls es:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding...
tools:context=".MainActivity" >
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:onClick="detenerServicio"
android:text="@string/txtBoton" />
</LinearLayout>
Define el string txtBoton con la cadena de texto que prefieras.
El cdigo del mtodo detenerServicio es:
public void detenerServicio (View v) {
stopService(new Intent(MainActivity.this,ServicioReproductor.class));
}

Lanzando una actividad


En este nuevo proyecto, llamado Saca, ilustraremos dos conceptos nuevos:

El primero es que nuestro receptor de anuncios va a lanzar una actividad principal; hemos
elegido el proyecto Rebotaimplementado en el mdulo 5 (de ah el nombre elegido para esta
nueva aplicacin).

El segundo es que vamos a definir nuestro propio


anuncio broadcast (llamado FRONTON_RECEIVER) y lo difundiremos mediante el
mtodo sendBroadcast de Context, con lo que lo recibirn todas las aplicaciones que hayan
definido un receptor de anuncios con ese intent-filter; en nuestro caso, slo tendremos la
aplicacin Rebota.

1.- Generando el UI en Saca


Empezamos en esta ocasin modificando el activity_main.xlm. Insertaremos un botn en
nuestro LinearLayout cuya pulsacin lanzar el mensaje broadcast; para ello, utilice el
atributo onClick asocindolo al mtodo pulsaBoton.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:onClick="pulsaBoton"
android:text="@string/txtBoton" />
</LinearLayout>

2.- Mtodo pulsaBoton


Su cdigo difundir el mensaje Broadcast:
public void pulsaBoton(View v) {

Intent i = new Intent();


i.setAction("org.example.FRONTON_RECEIVER");
sendBroadcast(i);
}

3.- Definimos el receptor de anuncios en Rebota


Lo llamamos, por ejemplo, Sacador:
package org.example.pelota;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class Sacador extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, org.example.pelota.MainActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
}
NOTA: Es indispensable activar el flag FLAG_ACTIVITY_NEW_TASK del intent, porque para iniciar
una actividad principal (que tiene su <intent-filter> / <action>=MAIN) se requiere iniciar un nuevo
proceso

4.- Lo registramos en AndroidManifest de Rebota


<receiver
android:name="Sacador"
android:exported="true" >
<intent-filter>
<action android:name="org.example.FRONTON_RECEIVER" />
</intent-filter>
</receiver>
Como componentes de otras aplicaciones van a llamarlo, indicamos android:exported="true". Esto
provoca un warning, ya que este receptor es pblico y cualquiera podra llamarlo, lo que podra acarrear
problemas de seguridad (por ejemplo, una aplicacin que no ha solicitado determinados permisos, podra
realizar acciones de malware mediante este servicio, que disfruta de los permisos asignados al proyecto
en el que se encuentra). Podramos solventarlo definiendo un nuevo permiso a medida (con permission)
debiendo solicitarlo para ejecutar el servicio.

5.- Ejecuta

Ten presente que debes reinstalar el proyecto Rebota; para ello, basta con ejecutarlo una vez. A
continuacin, cirralo.
Ahora puedes ejecutar Saca y comprobar que la pulsacin en el botn inicia de nuevo la aplicacin de la
pelota azul

Un receptor de anuncios como mecanismo de comunicacin para


un IntentService
En el Mdulo 11, vimos que la clase IntentService facilita enormemente la creacin de un servicio que se
ejecuta automticamente en un hilo secundario. Pero esto impeda la interaccin con la interfaz de
usuario desde el servicio, ya que como sabes, sta es tarea del hilo principal.
Vamos a ilustrar la manera de solventar este inconveniente implementando un receptor de anuncios a
medida, destinado nicamente a esta aplicacin, que vamos a utilizar para comunicar algn dato desde el
servicio al hilo principal, de manera que ste pueda mostrarlo al exterior. Adems, en vez de emplear
el AndroidManifest, lo registraremos desde cdigo; de esta manera, ilustraremos todas las posibilidades
de estos componentes.
Si recordamos nuestra aplicacin Servicios, podemos pulsar el botn de Arrancar Servicio para iniciar un
nuevo servicio que consiste en un nmero aleatorio de iteraciones de un bucle donde se duerme al hilo 1
segundo. Podemos parar todos los servicios activos antes de que finalicen, pulsando el botn Detener
Servicio. La informacin de lo que va sucediendo es muy escasa; solo disponemos de dos Toastpara
indicar que arranca el servicio con un nmero determinado de iteraciones y cuando se detienen los
servicios activos por causa del botn de parada. Ambos mensajes, lgicamente, son lanzados desde el
hilo principal.

Nuestra intencin es que el servicio emita mensajes que recoger el hilo principal donde se informe de los
bucles que se van completando y tambin de que ha terminado el servicio.

1.- Definicin del BroadcastReceiver


Como slo lo vamos a utilizar en este proyecto, lo podemos definir dentro de la clase MainActivity;
aadimos este cdigo:
public class ReceptorPropio extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int i = intent.getIntExtra("nIteracion", 0);
int n = intent.getIntExtra("totalIteraciones", 1);
if (i==0) // i valdr 0 cuando termine el servicio
Toast.makeText(context, "Ha terminado el servicio de "+n+" iteraciones",
Toast.LENGTH_SHORT).show();
else Toast.makeText(context, "Ha finalizado el bucle "+i+"/"+n, Toast.LENGTH_SHORT).show();
}
}
Como vemos, recibiremos dos extras; el nmero de bucle que ha finalizado y el total de bucles que deben
completarse. En el mtodoonDestroy del servicio le daremos un valor de 0 al primer extra, con lo que
detectaremos que el servicio se ha destruido.

2.- Registro del receptor de anuncios

Como hemos dicho, en esta ocasin lo vamos a hacer por cdigo; aadiremos estas lneas al
mtodo onCreate de la clase MainActivity:
IntentFilter filtro = new IntentFilter("example.servicios.MI_ACTION_FILTER");
filtro.addCategory(Intent.CATEGORY_DEFAULT);
registerReceiver(new ReceptorPropio(), filtro);

3.- Emisin de los mensajes broadcast


Slo nos falta que el servicio difunda los mensajes apropiados; el cdigo quedar como sigue:
package example.servicios;
import android.app.IntentService;
import android.content.Intent;
public class MiServicio extends IntentService {
int iter;
boolean termina = false;
public MiServicio() {
super("MiServicio");
}
@Override
protected void onHandleIntent(Intent intent) {
iter = intent.getIntExtra("iteraciones", 1);
for (int i = 1; i <= iter; i++) {
if (termina) break;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
Intent miIntent = new Intent();
miIntent.setAction("example.servicios.MI_ACTION_FILTER");
miIntent.addCategory(Intent.CATEGORY_DEFAULT);
miIntent.putExtra("nIteracion", i);
miIntent.putExtra("totalIteraciones", iter);
if (!termina) sendBroadcast(miIntent);
}
}
@Override
public void onDestroy() {
termina = true;
super.onDestroy();
Intent miIntent = new Intent();
miIntent.setAction("example.servicios.MI_ACTION_FILTER");
miIntent.addCategory(Intent.CATEGORY_DEFAULT);

miIntent.putExtra("nIteracion", 0);
miIntent.putExtra("totalIteraciones", iter);
sendBroadcast(miIntent);
}
}
La variable boolena termina se emplea para poder salir del bucle al detectar la finalizacin del servicio
debido a la pulsacin del botn.
NOTA: En vez de utilizar Toast, quizs sera ms apropiado el uso de un TextView en el interfaz de
usuario.

Mdulo 14.- El almacenamiento de datos


En muchas ocasiones se necesita guardar informacin de forma permanente. Disponemos de varias
alternativas:

Preferencias
compartidas (Shared
Preferences): Las estudiamos
en el mdulo 4. Es un
mecanismo que permite
almacenar y recuperar datos
primitivos en la forma de
pares (clave, valor). Es comn
utilizarlo para almacenar los
parmetros de configuracin
de una aplicacin.

Ficheros: Pueden ser

gestionados con las clases


estndar incluidas en Java,
aunque se han aadido
nuevas clases para cubrir las
peculiaridades de Android. Podemos almacenar los ficheros en la memoria interna del dispositivo
o en un medio de almacenamiento externo como una tarjeta SD.

XML: Formato de archivo que sigue un determinado estndar y que nos permite almacenar la
informacin de manera estructurada. Android dispone de dos alternativas para su
manejo: SAX y DOM. En el presente mdulo estudiaremos la librera SAX.

Bases de datos: Aprenderemos a manejar las clases de la librera SQLite que incorpora Android.

Proveedores de Contenido: Los estudiaremos en el siguiente mdulo

Estas tcnicas, adems de asegurar la permanencia de los datos, permiten compartirlos con otras
aplicaciones.

Ficheros
Como ya hemos dicho, podemos utilizar la memoria interna del
telfono o la memoria externa (normalmente una tarjeta SD).

El sistema de ficheros se sustenta en la capa Linux, por lo que Android hereda su estructura y
caractersticas. Recuerda que, cuando se instala una aplicacin, se crea un nuevo usuario Linux asociado
a la misma. Pues bien, slo este usuario podr acceder a los ficheros y recursos propios de la aplicacin,
cuando residen en la memoria interna. Para que dos aplicaciones puedan compartir recursos, deben
tener el mismo ID de usuario Linux y estar firmadas por el mismo certificado.
Sin embargo, para acceder a los ficheros almacenados en una memoria externa, basta con tener el
permiso adecuado (READ_EXTERNAL_STORAGE), el cual es otorgado por el usuario en el momento de
la instalacin.
NOTA: Si se solicita WRITE_EXTERNAL_STORAGE, implcitamente se obtiene tambin el de
lectura.
Recuerda que tambin podemos incorporar ficheros a los recursos de la aplicacin copindolos
en res/raw, pero son de solo lectura por lo que no podemos almacenar informacin en ellos desde la
aplicacin. Se accedera a ellos usando Resources.openRawResource(R.raw.nombrefichero).

Almacenamiento en la memoria interna


Cada aplicacin dispone de una carpeta especial para almacenar ficheros
(/data/data/nombre_del_paquete/files). As se posibilita que los ficheros que haya podido crear una
aplicacin, se eliminen cuando sta se desinstala.
Aunque se pueden utilizar los mtodos del paquete java.io para trabajar con ficheros, se han creado
mtodos adicionales asociados a la clase Context para facilitarnos el trabajo con ficheros almacenados en
la memoria interna.
En particular los mtodos openFileInput() y openFileOutput() permiten abrir un fichero para lectura o
escritura respectivamente. Con estos mtodos el nombre del archivo no puede contener paths; el fichero
siempre es almacenado en la carpeta reservada para tu aplicacin. Es importante cerrar siempre los
ficheros (con el mtodo close()).
En el siguiente ejemplo podemos ver cmo crear un fichero de extensin txt:
public void PruebaFicheros() {
String fichero = "UnFichero.txt";
String texto = "Texto para guardar";
FileOutputStream fos;
try {
fos = openFileOutput(fichero, Context.MODE_PRIVATE);
fos.write(texto.getBytes());
fos.close();
} catch (FileNotFoundException e) {
Log.e("Desde la Aplicacin de ficheros", e.getMessage(), e);
} catch (IOException e) {
Log.e("Desde la Aplicacin de ficheros", e.getMessage(), e);
}
}
Como puedes ver, es importante gestionar cuidadosamente las posibles excepciones que puedan darse.
De hecho, el acceso a ficheros ha de realizarse de forma obligatoria dentro de un try-catch.

Adems de los dos mtodos indicados otros relacionados son:

getFilesDir(): devuelve la ruta absoluta donde se estn guardando los ficheros.

getDir(): crea un directorio en tu almacenamiento interno (o lo abre si existe).

deleteFile(): borra un fichero.

fileList(): devuelve un array con los ficheros almacenados por tu aplicacin.

Almacenamiento en la memoria externa


Los telfonos Android habitualmente tienen la posibilidad de utilizar una memoria adicional
conocida como almacenamiento externo, lo cual aporta mucha capacidad al aparato. Aunque en
algunos dispositivos el almacenamiento externo no es extrable, lo usual es disponer de una
memoria extrable, como una tarjeta SD. Algunos modelos incorporan los dos tipos de

almacenamiento externo: extrable y no extrable.


Cuando conectamos el dispositivo Android a travs del cable USB tambin permitimos el acceso
a la memoria externa. Como puedes comprobar, los ficheros que tenemos en ella pueden ser
ledos, modificados o borrados por cualquier usuario.

Para acceder al almacenamiento externo por cdigo, en principio podramos utilizar como
directorio raz del mismo /sdcard, que es un enlace simblico a /storage/sdcard0, pero Android
recomienda utilizar el mtodo getExternalStorageDirectory() de Environment para obtenerlo. Por
ejemplo:

String fichero = Environment.getExternalStorageDirectory() + "/datos.txt";

Tambin podemos utilizar este mtodo para verificar el estado de la memoria externa, ya que
puede estar protegida contra escritura o simplemente haber sido extrada. Haramos:

String estadoSD = Environment.getExternalStorageState();


if (estadoSD.equals(Environment.MEDIA_MOUNTED)) {
// Podemos leer y escribir
} else if (estadoSD.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
// Podemos leer
} else {
// No podemos leer y ni escribir
}

NOTA: Para ver la estructura real de carpetas y ficheros en un dispositivo Android hay que
utilizar un explorador tipo Root Explorer o ES File Explore.

A partir del API 4 resulta necesario declarar el


permiso WRITE_EXTERNAL_STORAGE en AndroidManifest.xml para poder escribir en la
memoria externa. Pero tal y como aparece en la documentacin, a partir del nivel de API 19 no
se requiere este permiso para leer / escribir archivos en los directorios que
devuelven getExternalFilesDir (String) y getExternalCacheDir (). Ojo; para acceder a otras

carpetas del almacenamiento externo, s es obligatorio.


El permiso READ_EXTERNAL_STORAGE es obligatorio a partir del nivel 19 de API siempre que
el dispositivo tenga activada la opcin de "Proteger tarjeta SD" en las Opciones de
desarrollador dentro de los Ajustes (el dispositivo debe disponer de Android 4.1 o superior).

Archivos XML
XML es un lenguaje de marcas desarrollado por el W3C, aunque no se utiliza nicamente para Internet.
Ha llegado a ser uno de los estndares ms utilizados en la actualidad para codificar, almacenar e
intercambiar informacin estructurada, incluso entre diferentes plataformas, ya que permite la
compatibilidad entre sistemas de una manera segura, fiable y fcil. Como hemos mostrado a lo largo de
este libro, tambin est integrado perfectamente en el SDK de Android.
El SDK de Android no acaba de ofrecer todo lo que est
disponible en el entorno de ejecucin estndar de Java
(JRE), pero es compatible con una fraccin muy
significativa del mismo. Tambin sucede esto a la hora
de trabajar con XML, ya que Java dispone de gran
cantidad de APIs con este propsito, pero no todas
estn disponibles en Android.
Disponemos de dos libreras/enfoques para trabajar con archivos XML desde Android:

SAX (Javas Simple API for XML): contenida en org.xml.sax.*

DOM (Document Object Model): contenida en org.w3c.dom.*

NOTA: La librera desarrollada en org.xmlpull.v1.XmlPullParser otorga la misma funcionalidad que


el StAX de Java (Streaming API for XML) pero no tenemos nada para la implementacin
de JAXB (Java Architecture for XML Binding); resultara demasiado pesada para Android.

Procesando XML con SAX


SAX facilita la creacin de un parser (analizador) que permitir recoger la informacin almacenada en un
documento XML determinado.SAX no almacena los datos; necesitaremos una estructura de datos donde
guardar la informacin extrada del XML.
El parser detecta una serie de eventos, causados por las sucesivas etiquetas XML que va encontrando en
el documento. Por ejemplo, supn que tenemos este documento XML donde se guardan las puntuaciones
de un juego:

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


<lista_puntuaciones>
<puntuacion fecha="1288122023410">
<nombre>Bonifacio Aguirre</nombre>
<puntos>45000</puntos>
</puntuacion>
<puntuacion fecha="1288122428132">
<nombre>Marisol Ochoa</nombre>
<puntos>31000</puntos>
</puntuacion>
</lista_puntuaciones>
Al analizar este documento XML, SAX generar los siguientes eventos:
Comienza elemento: lista_puntuaciones

Comienza elemento: puntuacion, con atributo fecha="1288122023410"


Comienza elemento: nombre
Texto de nodo: Bonifacio Aguirre
Finaliza elemento: nombre
Comienza elemento: puntos
Texto de nodo: 45000
Finaliza elemento: puntos
Finaliza elemento: puntuacion
Comienza elemento: puntuacion, con atributo fecha="1288122428132"
Comienza elemento: nombre
Texto de nodo: Marisol Ochoa
Finaliza elemento: nombre
Comienza elemento: puntos
Texto de nodo: 31000

Finaliza elemento: puntos


Finaliza elemento: puntuacion
Finaliza elemento: lista_puntuaciones
De manera que nuestro parser va a ser una clase que extiende DefaultHandler, lo cual nos obligar a
reescribir los siguientes mtodos, que sern llamados a medida que ocurran los eventos del proceso de
lectura secuencial del documento:

startDocument(): Comienza el Documento XML.

endDocument(): Finaliza documento XML.

startElement(String uri, String nombreLocal, String nombreCualif, Attributes atributos): Comienza


una nueva etiqueta; los parmetros de entrada representan

uri: La uri del espacio de nombres o vaco, si no se ha definido.

nombreLocal: nombre local de la etiqueta sin prefijo.

nombreCualif: nombre cualificado de la etiqueta con prefijo.

atributos: lista de atributos de la etiqueta

endElement(String uri, String nombreLocal, String nombreCualif): Termina una etiqueta.

characters(char ch[], int comienzo, int longitud): Devuelve los caracteres dentro de una etiqueta.
Es decir en <etiqueta> caracteres </etiqueta> devolvera caracteres.

Actividad guiada

Vamos a crear una clase capaz de leer y almacenar informacin en un archivo XML; queda de tu
parte utilizarla en un proyecto de ejemplo para comprobar su funcionamiento.

package example.ejxmlsax;
import java.io.FileNotFoundException;
import java.util.Vector;
import android.content.Context;
import android.util.Log;
public class AlmacenPuntuacionesXML_SAX {
private static String FICHERO = "puntuaciones.xml";
private Context contexto;
private ListaPuntuaciones lista;
private boolean cargadaLista;
public AlmacenPuntuacionesXML_SAX(Context contexto) {
this.contexto = contexto;
lista = new ListaPuntuaciones();
cargadaLista = false;
}
public void guardarPuntuacion(int puntos, String nombre, long fecha) {
try {
if (!cargadaLista) lista.leerXML(contexto.openFileInput(FICHERO));
} catch (FileNotFoundException e) {
} catch (Exception e) {
Log.e("EjXmlSax", e.getMessage(), e);
}
lista.nuevo(puntos, nombre, fecha);
try {
lista.escribirXML(contexto.openFileOutput(FICHERO,Context.MODE_PRIVATE));
} catch (Exception e) {
Log.e("EjXmlSax", e.getMessage(), e);
}
}
public Vector<String> leerPuntuaciones() {
try {
if (!cargadaLista) lista.leerXML(contexto.openFileInput(FICHERO));
} catch (Exception e) {
Log.e("EjXmlSax", e.getMessage(), e);
}
return lista.aVectorString();

}
}
Dentro de la clase AlmacenPuntuacionesXML_SAX hemos definido la variable lista de
tipo ListaPuntuaciones (clase que implementaremos a continuacin) que va a permitir la lectura y escritura
del fichero XML y la constante FICHERO, que contiene el nombre del propio fichero donde finalmente se
guardan los datos; con el valor indicado, se almacenar en/data/data/example.ejxmlsax/
files/puntuaciones.xml. La variable booleana cargadaLista nos indica si lista ya ha sido leda desde el
fichero.
Luego se implementa el mtodo guardarPuntuacion, para introducir una nueva puntuacin, tanto en
memoria (en la variable lista) como en el fichero y el mtodo leerPuntuaciones, que devuelve
un Vector de Strings con el contenido de la variable lista.
Ntese que, si el programa se est ejecutando por primera vez, el fichero no existir, por lo que se
producir unaFileNotFoundException al tratar de abrirlo para su lectura. Esta excepcin es capturada,
pero no realizamos ninguna accin dado que no se trata de un verdadero error.
Vamos ahora con la clase ListaPuntuaciones:
package example.ejxmlsax;
import ...
public class ListaPuntuaciones {
private class Puntuacion {
int puntos;
String nombre;
long fecha;
}
private List<Puntuacion> listaPuntuaciones;
public ListaPuntuaciones() {
listaPuntuaciones = new ArrayList<Puntuacion>();
}
public void nuevo(int puntos, String nombre, long fecha) {
Puntuacion puntuacion = new Puntuacion();
puntuacion.puntos = puntos;
puntuacion.nombre = nombre;
puntuacion.fecha = fecha;
listaPuntuaciones.add(puntuacion);
}
public Vector<String> aVectorString() {
Vector<String> result = new Vector<String>();
for (Puntuacion puntuacion : listaPuntuaciones) {
result.add(puntuacion.nombre + " " + puntuacion.puntos);

}
return result;
}
public boolean leerXML(InputStream entrada) throws Exception {
SAXParserFactory fabrica = SAXParserFactory.newInstance();
SAXParser parser = fabrica.newSAXParser();
XMLReader lector = parser.getXMLReader();
ManejadorXML manejadorXML = new ManejadorXML();
lector.setContentHandler(manejadorXML);
lector.parse(new InputSource(entrada));
return true;
}
public void escribirXML(OutputStream salida) {
XmlSerializer serializador = Xml.newSerializer();
try {
serializador.setOutput(salida, "UTF-8");
serializador.startDocument("UTF-8", true);
serializador.startTag("", "lista_puntuaciones");
for (Puntuacion puntuacion : listaPuntuaciones) {
serializador.startTag("", "puntuacion");
serializador.attribute("", "fecha", String.valueOf(puntuacion.fecha));
serializador.startTag("", "nombre");
serializador.text(puntuacion.nombre);
serializador.endTag("", "nombre");
serializador.startTag("", "puntos");
serializador.text(String.valueOf(puntuacion.puntos));
serializador.endTag("", "puntos");
serializador.endTag("", "puntuacion");
}
serializador.endTag("", "lista_puntuaciones");
serializador.endDocument();
} catch (Exception e) {
Log.e("EjXmlSax", e.getMessage(), e);
}
}
}
Como puedes ver, hemos definido una clase interna llamada Puntuacion que recoge los atributos puntos,
nombre y fecha.
Los tres mtodos siguientes tampoco tienen mayor misterio: el constructor, el mtodo nuevo, que aade
una nueva Puntuacin a la variable interna listaPuntuaciones y aVectorString, que devuelve un vector
de strings con el contenido de listaPuntuaciones.
Los dos mtodos siguientes son los que tienen ms inters; el primero, permite leer un documento XML.
Para ello, comenzamos creando una instancia de la clase SAXParserFactory, lo que nos permite crear un
nuevo parser XML de tipo SAXParser. Luego creamos un lector, de la clase XMLReader, asociado a

este parser. Creamos manejadorXML de la clase ManejadorXML (clase que implementaremos a


continuacin, que extiende DefaultHandler) y asociamos este manejador al XMLReader. Para finalizar le
indicamos al XMLReader qu entrada tiene para que realice el proceso de parser.
Todo el trabajo del mtodo escribirXML se realiza por medio de un objeto de la clase XmlSerializer al que
se le asigna como salida el OuputStream que hemos pasado como parmetros.
Veamos finalmente el cdigo de la clase ManejadorXML; como su implementacin depende del formato
del fichero XML que vamos a leer, tiene sentido que la definamos internamente a la
clase ListaPuntuaciones, por lo que puedes insertarla antes de la ltima llave:
class ManejadorXML extends DefaultHandler {
private StringBuilder cadena;
private Puntuacion puntuacion;
@Override
public void startDocument() throws SAXException {
listaPuntuaciones = new ArrayList<Puntuacion>();
cadena = new StringBuilder();
}
@Override
public void startElement(String uri, String nombreLocal, String nombreCualif, Attributes atr) throws
SAXException {
cadena.setLength(0);
if (nombreLocal.equals("puntuacion")) {
puntuacion = new Puntuacion();
puntuacion.fecha = Long.parseLong(atr.getValue("fecha"));
}
}
@Override
public void characters(char ch[], int comienzo, int lon) {
cadena.append(ch, comienzo, lon);
}
@Override
public void endElement(String uri, String nombreLocal,
String nombreCualif) throws SAXException {
if (nombreLocal.equals("puntos")) {
puntuacion.puntos = Integer.parseInt(cadena.toString());
} else if (nombreLocal.equals("nombre")) {
puntuacion.nombre = cadena.toString();
} else if (nombreLocal.equals("puntuacion")) {
listaPuntuaciones.add(puntuacion);
}
}
@Override
public void endDocument() throws SAXException {

}
}
Esta clase define un manejador que captura los cinco eventos generados en el proceso de parsing en
SAX. En startDocument() nos limitamos a inicializar variables. En startElement() verificamos que hemos
llegado a una etiqueta <puntuacion>. En tal caso, creamos un nuevo objeto de la clase Puntuacion e
inicializamos el campo fecha con el valor indicado en uno de los atributos.
El mtodo characters() es llamado cuando aparece texto dentro de una etiqueta (<etiqueta> caracteres
</etiqueta>). Nos limitamos a almacenar este texto en la variable cadena para utilizarlo en el siguiente
mtodo. SAX no nos garantiza que nos pasar todo el texto en un solo evento, si el texto es muy extenso
se realizaran varias llamadas a este mtodo. Por esta razn, el texto se va acumulando en cadena.
El mtodo endElement() resulta ms complejo, dado que en funcin de la etiqueta que est acabando
realizaremos una tarea diferente. Si se trata de </puntos> o de </nombre> utilizaremos el valor de la
variable cadena para actualizar el valor correspondiente. Si se trata de </puntuacion> aadimos el
objeto puntuacion a la lista.

Bases de datos relacionales


Android incorpora la librera SQLite que nos permite utilizar bases de datos relacionales mediante
comandos SQL. Se utilizan muy pocos recursos del sistema y es relativamente sencillo de usar y muy
potente.
Crearemos bases de datos nuevas mediante la clase SQLiteOpenHelper.
Las bases de datos se almacenan en la carpeta /data/data/nombre_paquete/databases, con lo que
tambin se borran al desinstalar la aplicacin,.

Actividad guiada
Crearemos una clase con los mtodos necesarios para crear y gestionar una base de datos de
puntuaciones similar al ejemplo anterior realizado con XML:
Le llamaremos ManejaBD. Al extender de SQLiteOpenHelper, habr que definir un constructor y
sobreescribir onCreate(SQLiteDatabase), onUpgrade(SQLiteDatabase, int, int) y,
opcionalmente, onOpen(SQLiteDatabase).
Por nuestra parte, le aadiremos los mtodos guardarPuntuacion y leerPuntuacion, pasando a este ltimo
un entero que indica el nmero de registros a devolver.

package example.ejbd;
import java.util.Vector;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class ManejaBD extends SQLiteOpenHelper {


public ManejaBD(Context context) {
super(context, "puntuaciones", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE puntuaciones (id INTEGER PRIMARY KEY AUTOINCREMENT,
puntos INTEGER, nombre TEXT, fecha LONG)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// En caso de una nueva versin habra que actualizar las tablas
}
public void guardarPuntuacion(int puntos, String nombre, long fecha) {
SQLiteDatabase db = getWritableDatabase();
db.execSQL("INSERT INTO puntuaciones VALUES (null, " + puntos + ", '"+ nombre + "', " + fecha +
")");
db.close();
}
public Vector<String> leerPuntuaciones(int cantidad) {
Vector<String> result = new Vector<String>();
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT puntos, nombre FROM puntuaciones ORDER BY puntos
DESC LIMIT " + cantidad, null);
while (cursor.moveToNext()) {
result.add(cursor.getInt(0) + " " + cursor.getString(1));
}
cursor.close();
Al llamar al super-constructor le enviamos el contexto en el que va a ser creada la base, el nombre de la
base de datos, un cursor de uso predeterminado (en este caso, null) y la versin de la base de datos (en
cuanto al diseo de la misma). Si la base de datos actual tuviera una versin ms antigua se llamara
a onUpgrade() donde habra que escribir las rdenes necesarias para actualizar la estructura de las tablas
a la nueva versin.
El mtodo onCreate recibe la base de datos de tipo SQLiteDatabase y en ella tendremos que definir todas
las tablas componiendo sentencias SQL y envindolas mediante execSQL, ya que este mtodo slo se va
a ejecutar una vez, cuando el sistema detecta que la base de datos an no est creada.
El mtodo guardarPuntuacin obtiene la referencia a la base de datos con getWritableDatabase y de
nuevo vuelve a ejecutar la orden SQL necesaria para aadir los datos recibidos como argumentos
mediante execSQL. Fjate que se enva null al campo autonumrico para que sea el sistema el que
calcule el valor y que los valores de texto deben ir encerrados entre comillas simples.

En el mtodo leerPuntuaciones usaremos getReadableDatabase para obtener la referencia al


objeto SQLiteDatabase en modo lectura y llamaremos a rawQuery para que nos devuelva el resultado de
la consulta SELECT en un objeto de tipo Cursor.

El mtodo rawQuery
Conviene estudiar un poco ms a fondo el mtodo rawQuery; como hemos visto, se pueden componer las
sentencias SQL mediante el operador + para concatenar Strings con valores. Este mtodo contempla otra
forma de crear este tipo de consultas; permite definir una serie de atributos, que seran los valores, y
luego sustituirlos en las sentencias por los ? que se va encontrando en el String.
En el ejemplo anterior no hemos utilizado esta opcin, ya que hemos construido la sentencia haciendo:

db.rawQuery("SELECT puntos, nombre FROM puntuaciones ORDER BY puntos


DESC LIMIT " + cantidad, null);
Como puedes ver, el segundo parmetro, donde se indican los atributos, es null y utilizamos el valor de la
variable cantidad para concatenarlo a la sentencia. La alternativa sera:
String[] param = new String[1];
param[0]=Integer.toString(cantidad,10);
Cursor cursor = db.rawQuery("SELECT puntos, nombre FROM puntuaciones ORDER BY puntos DESC
LIMIT ?", param);

El mtodo query
Existe otra alternativa al mtodo rawQuery, que es la que se describe en la documentacin Android: el
mtodo query. No lo hemos utilizado porque el primero resulta mucho ms sencillo de utilizar, sobretodo
si ests acostumbrado al lenguaje SQL.
De hecho es un mtodo sobrecargado para admitir distintos parmetros; uno de ellos tiene esta forma:
public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit)
Es una forma de disgregar la sentencia SQL en partes; los argumentos representan las siguientes:

table: el nombre de la tabla (FROM...)

columns: los campos a devolver (SELECT ...)

selection: criterios de la consulta (WHERE ...)

selectionArgs: para reemplazar los ? de selection

groupBy: agrupado por (GROUP BY ...)

having: condicin a aplicar a la funcin de resumen de cada grupo (HAVING ...)

orderBy: campo de ordenacin (ORDER BY ...)

limit: cantidad mxima de registros (TOP ...)

En nuestro ejemplo sera:

String[] campos = {"puntos", "nombre"};


Cursor cursor=db.query("puntuaciones", campos, null, null, null, null, "puntos", Integer.toString(cantidad));

Das könnte Ihnen auch gefallen