Beruflich Dokumente
Kultur Dokumente
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.
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.
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.
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/
onKeyUp(int keyCode, KeyEvent e): Cuando una tecla deja de ser pulsada.
Es interesante indicar que existen event handlers en otro tipo de objetos, adems de los descendientes
de View. Por ejemplo:
Cdigo
El esquema que habra que seguir para definir un view personalizado sera el siguiente:
public class MiVista extends view {
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:
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:
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:
//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:
Mtodo
View.OnClickListener
void onClick(view)
View.OnLongClickListener
boolean onLongClick(view)
View.OnFocusChangeListener
void onFocusChange(view,
hasFocus)
lo ha perdido
El view tiene el foco y el usuario
View.OnKeyListener
View.OnTouchListener
boolean onTouch(view,
Se ha pulsado, soltado o
motionEvent)
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);
}
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);
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)
giroscopio (TYPE_GYROSCOPE)
magnticos
detecta giros
orientacin (TYPE_ORIENTATION)
API 8)
ajustar iluminacin pantalla
Booleano
proximidad (TYPE_PROXIMITY)
altmetro, barmetro
para evitar
sobrecalentamientos (deprecated
desde API14)
gravedad (TYPE_GRAVITY)
acelermetro
lineal (TYPE_LINEAR_ACCELERATION)
la gravedad
vector de rotacin (TYPE_ROTATION_VECTOR) detecta giros
temperatura
ambiental (TYPE_AMBIENT_TEMPERATURE)
14
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
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;
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);
@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.
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.
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.
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
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:
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);
}
}
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 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.
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.
Ringtone y RingtoneManager: Clases con las que podemos manejar los tonos de llamada, de
notificacin,...
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:
URI internas
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
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.
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.
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.
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"
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.
4.
5.
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:
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:
<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;
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();
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.
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
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"
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");
}
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.
4.
5.
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);
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:
<VideoView android:id="@+id/surfaceView"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_below="@+id/ButonsLayout"/>
</LayoutPrincipal>
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.
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.
android:onClick="pulsaEfecto"
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");
}
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.
4.
5.
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:
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:
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?.
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:
<VideoView android:id="@+id/surfaceView"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_below="@+id/ButonsLayout"/>
</LayoutPrincipal>
mediaPlayer.setDisplay(surfaceHolder);
mediaPlayer.prepare();
// mMediaPlayer.prepareAsync(); Para streaming
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");
}
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.
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);
}
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"
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");
}
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.
5.
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:
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);
@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:
<VideoView android:id="@+id/surfaceView"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_below="@+id/ButonsLayout"/>
</LayoutPrincipal>
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();
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.
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.
android:onClick="pulsaEfecto"
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.
4.
5.
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:
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:
<VideoView android:id="@+id/surfaceView"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_below="@+id/ButonsLayout"/>
</LayoutPrincipal>
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();
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.
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.
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.
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
Informacin confidencial
Sistema
Aado entre parntesis el nombre e informacin que aparece en la relacin de permisos al instalar la
aplicacin.
SEND_SMS (Servicios por los que tienes que pagar Envo SMS/MMS)
Permite a una aplicacin enviar SMS.
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
Sistema
Agruparemos en esta seccin aquellos permisos que pueden comprometer la estabilidad del sistema.
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.
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
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:
<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.
utilizar satlites (comnmente llamado GPS, aunque como veremos enseguida, es ms exacto
llamarlo GNSS o Sistema Global de Navegacin por Satlite)
NOTA: Los dos ltimos se suelen emplear simultneamente; en general, se denomina "Sistema de
localizacin basado en redes"
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.
Recibidas por radio, a travs de algn canal preparado para ello, como el RDS en una emisora
de FM.
Descargadas de Internet
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.
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.
Cobertura mundial
Alta precisin:
Telefona mvil
Precisin muy mala: 200 m en reas urbanas y 2-4 Km en reas suburbanas y rurales
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
Actividad guiada
Inicio
Crea un nuevo proyecto con los siguientes datos:
Permisos
El acceso a la localizacin del dispositivo requiere de permisos, para asegurar la privacidad del usuario:
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.
@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:
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.
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 en aplicaciones mviles de pago (a diferencia de sitios Web donde se cobren los
servicios).
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
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
Vemos que se ha creado un nuevo proyecto llamado API Project y estamos viendo su descripcin
general:
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
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.
<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>
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 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:
animateCamera(), nos desplaza hasta nuestra posicin actual utilizando una animacin .
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.
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,...
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.
START_NOT_STICKY: el servicio ser creado de nuevo solo cuando llegue una nueva solicitud
de creacin
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.
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.
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.
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;
}
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.
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
Y, finalmente, al pulsar sobre el mensaje, aparecer una nueva ventana con un simple TextView:
.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
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:
RequestCode: En este caso no necesitamos ningn cdigo de referencia, por lo tanto pasamos
0.
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:
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
<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.
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>
nm.notify(ID_NOTIFICACION_LLAMADA, builder.build());
}
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.
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));
}
}
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).
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
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.
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);
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.
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.
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.
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).
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:
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:
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.
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:
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
}
}
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.
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;
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:
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: