Archivo para julio 2009
Desarrollo en Android: acelerómetro, magnetómetro y sensores de orientación y temperatura en el HTC Magic
Android permite acceder a los sensores internos del dispositivo a través de las clases Sensor, SensorEvent y SensorManager, y de la interfaz SensorEventListener, del paquete android.hardware.
La clase Sensor acepta ocho tipos de sensores, como se puede ver en la referencia. Los sensores disponibles varían en función del aparato utilizado.
Listar los sensores del dispositivo
Para ver de qué sensores dispone nuestro dispositivo usamos la clase SensorManager:
1: // Solicitamos al sistema el servicio que gestiona los sensores
2: SensorManager mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
3: // Peimos la lista con todos los sensores disponibles
4: List<Sensor> listSensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
5: // Iteramos y mostramos
6: for(Sensor sensor:listSensors)
7: {
8: Log.i("SENSOR", sensor.getName());
9: }
La nueva versión de la SDK marca como obsoleto (deprecated) el método getSensors de la clase SensorManager, así como la interfaz SensorListener. Aunque los métodos antiguos son necesarios para versiones anteriores de la SDK, los ejemplos de este artículos se basan en la última versión.
Los sensores del HTC Magic
Esta es una lista de los valores devueltos por el código anterior ejecutándose en el HTC Magic:
- AK8976A 3-axis Accelerometer
- AK8976A 3-axis Magnetic field sensor
- AK8976A Orientation sensor
- AK8976A Temperature sensor
El AK8976A (arriba a la izquierda en esta figura, que muestra el hardware del HTC Dream) es una combinación de acelerómetro de tres ejes y magnetómetro de tres ejes. Combinando la lectura de los campos gravitatorio y magnético terrestres proporciona también información de orientación. Incluye además un sensor interno de temperatura, útil para comprobar si el móvil se está calentado demasiado.
Acceso a los datos del sensor
Para tener acceso a los datos del sensor debemos indicárselo al SensorManager con el método registerListener:
1: public class MiActividad extends Activity {
2: MiVista mVista; // Clase que implemente SensorEventListener
3: // ...
4:
5:
6: @Override
7: protected void onCreate(Bundle savedInstanceState) {
8: super.onCreate(savedInstanceState);
9:
10: // En esta clase recibiré los eventos y usaré el resultado para lo que quiera
11: mVista = new MiVista(this);
12: SensorManager mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
13:
14: // Cada sensor se registra por separado
15: List<Sensor> listSensors = mSensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
16: Sensor orientationSensor = listSensors.get(0);
17: mSensorManager.registerListener(mTop, orientationSensor, SensorManager.SENSOR_DELAY_UI);
18:
19: listSensors = mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
20: Sensor acelerometerSensor = listSensors.get(0);
21: mSensorManager.registerListener(mTop, acelerometerSensor, SensorManager.SENSOR_DELAY_UI);
22:
23: listSensors = mSensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
24: Sensor magneticSensor = listSensors.get(0);
25: mSensorManager.registerListener(mTop, magneticSensor, SensorManager.SENSOR_DELAY_UI);
26:
27: listSensors = mSensorManager.getSensorList(Sensor.TYPE_TEMPERATURE);
28: Sensor temperatureSensor = listSensors.get(0);
29: mSensorManager.registerListener(mTop, temperatureSensor, SensorManager.SENSOR_DELAY_UI);
30: ...
31: }
32: ...
33: }
34:
35: class MiVista extends View implements SensorEventListener {
36: ...
37: }
Es necesario registrar cada tipo de sensor por separado para poder obtener información de todos ellos. El método registerListener toma como primer parámetro la instancia de la clase que implementa el SensorEventListener, y que veremos a continuación. El tercer parámetro acepta cuatro posibles valores, que indican al sistema con qué frecuencia nos gustaría recibir actualizaciones del sensor. Esta indicación sirve para que el sistema estime cuánta atención necesitan los sensores, pero no garantiza una frecuencia concreta.
Obtención de datos
Para recibir los datos tenemos que implementar dos métodos de SensorEventListener:
1: class MiVista extends View implements SensorEventListener {
2: private float mOrientationValues[] = new float[3];
3: private float mAccelerometerValues[] = new float[3];
4: private float mMagneticValues[] = new float[3];
5: private float mTemperatureValues;
6:
7: /*
8: * El resto del código de la clase para mostrar los datos
9: */
10:
11: // En este ejemplo no necesitamos enterarnos de las variaciones de
12: // precisión del sensor
13: @Override
14: public void onAccuracyChanged(Sensor sensor, int accuracy) {
15: // TODO Auto-generated method stub
16:
17: }
18:
19: @Override
20: public void onSensorChanged(SensorEvent event) {
21: // Cada sensor puede provocar que un thread pase por aquí, así
22: // que sincronizamos el acceso
23: synchronized (this) {
24: switch(event.sensor.getType()) {
25: case Sensor.TYPE_ORIENTATION:
26: for (int i=0 ; i<3 ; i++) {
27: mOrientationValues[i] = event.values[i];
28: }
29: break;
30: case Sensor.TYPE_ACCELEROMETER:
31: for (int i=0 ; i<3 ; i++) {
32: mAccelerometerValues[i] = event.values[i];
33: }
34: break;
35: case Sensor.TYPE_MAGNETIC_FIELD:
36: for (int i=0 ; i<3 ; i++) {
37: mMagneticValues[i] = event.values[i];
38: }
39: break;
40: default:
41: for (int i=0 ; i<event.values.length ; i++) {
42: mTemperatureValues = event.values[i];
43: }
44: }
45:
46: invalidate();
47: }
48: }
49: }
Cuando el evento se dispara en el método onSensorChanged comprobamos qué sensor lo ha causado y leemos los datos. Los posibles valores devueltos se indican en la documentación de la clase SensorEvent.
La clase SensorManager tiene además tres métodos (getInclination, getOrientation y getRotationMatrix), usados para calcular transformaciones de coordenadas. De ellos hablaremos en un próximo artículo.
Errores emulando el GPS en Android
Existen dos formas de enviar datos al GPS del emulador de Android: con el DDMS (Dalvik Debug Monitor Service) y con la instrucción ‘geo’ de la consola del emulador (como se explica en la documentación). El DDMS es especialmente útil, ya que permite cargar archivos kml o gpx a partir de los cuales enviar actualizaciones periódicas al emulador. Sin embargo, en la versión 1.5 r2 de la SDK de Android hay dos bugs que resultan bastante incómodos.
El GPS no recibe ninguna señal
El primero de ellos está relacionado con el formato de las coordenadas enviadas al emulador. Por algún motivo (probablemente relacionado con el carácter usado para separa los decimales, la coma en español y punto en inglés) el emulador sólo recibe correctamente las actualizaciones si la configuración regional está establecida a idioma inglés.
Para solucionar este problema basta cambiar el locale del runtime de java. Usando DDMS, se establece la siguiente variable de entorno: ‘java_debug=-Duser.language=en’, o, si usamos el plugin para Eclipse, se añade ‘-Duser.language=en’ al archivo eclipse.ini. Más información en la incidencia abierta en Google Code.
El GPS sólo recibe la primera localización
| Actualización: La versión 1.5 release 3 de la SDK ya corrige este error |
El segundo error se produce cuando intentamos enviar varias actualizaciones al GPS del emulador. Por algún motivo las localizaciones enviadas después de la primera no llegan al emulador. Esto significa que el método onLocationChanged sólo será invocado una vez. En la discusión sobre la incidencia abierta se indica una solución provisional: cancelar la subscripción al LocationListener y volverla a subscribir:
public void onLocationChanged(Location location) {
mLocationManager.removeUpdates(locationListener);
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
3000,
1,
locationListener);
...
}
No he podido confirmar que este error afecte a todo el mundo que usa la SDK 1.5 r2, aunque sí está bastante extendido. Es de suponer que Google lo corregirá en la próxima revisión de la SDK.
Notificaciones ‘push’ en dispositivos móviles
¿Qué es una notificación push?
Es un mensaje que una aplicación servidora envía a una aplicación cliente indicándole que tiene algún tipo de información nueva disponible. Lo que distingue a las notificaciones push es que el servidor inicia la comunicación, no espera a que el cliente pregunte si hay algo nuevo. La ventaja es la inmediatez: la información llega al cliente (y por tanto al usuario) en cuanto está disponible en el servidor.
El ejemplo más clásico de este tipo de notificaciones es el correo electrónico. Un usuario tiene una aplicación de correo electrónico ejecutándose en su dispositivo. Alguien le envía un correo, que queda almacenado en el servidor. A partir de ahí pueden ocurrir dos cosas:
- El cliente de correo electrónico está configurado para consultar al servidor, con una frecuencia predeterminada, si hay nuevos correos. Si la frecuencia es de una vez cada 5 minutos el usuario puede tardar ese tiempo en enterarse de que ha recibido un correo nuevo.
- El cliente (y el servidor) soportan notificaciones push. En este caso el servidor envía un mensaje al cliente para avisarle de que ha llegado un nuevo correo, y el cliente a su vez se lo notifica de alguna forma al usuario para que lo lea si le interesa.
En el caso de las aplicaciones de mensajería instantánea la necesidad de la inmediatez es aún más manifiesta: para que la conversación sea fluida necesitamos que los mensajes nos lleguen en cuanto son enviados, e incluso queremos saber cuándo nuestro interlocutor está escribiendo algo.
¿Cómo se implementa una notificación push?
La manera habitual de implementar una notificación push es establecer una conexión TCP de larga duración. El cliente abre una conexión TCP con el servidor y la deja abierta. Sobre esta conexión TCP el servidor enviará las notificaciones al cliente, usando algún protocolo de aplicación. IMAP y XMPP son ejemplos de protocolos estándar usados para correo y mensajería respectivamente, aunque existen múltiples protocolos propietarios.
Notificaciones push en BlackBerry
El éxito de las BlackBerry se debe en parte a su sistema de push email. Cuando leer el correo desde el móvil era una experiencia dolorosa en otros sistemas, las BlackBerry ya conseguían que sus usuarios recibiesen el correo de manera instantánea. RIM subscribe un acuerdo con los operadores de telefonía, por el cual se establece una conexión permanente con unos servidores especiales operados por RIM. Estos servidores reciben el correo del usuario desde los servidores BES, y lo notifican de forma inmediata a las BlackBerry.
La tecnología de notificaciones push de RIM está disponible para terceros a través de la BlackBerry Push API.
Notificaciones push en iPhone
Apple incorporó desde el principio un sistema de notificaciones push para el correo electrónico de Yahoo. La aparición de MobileMe añadió notificaciones push tanto para el correo como para el calendario y los contactos.
Cuando Apple diseñó la SDK del iPhone tomó la decisión de no permitir a los desarrolladores crear aplicaciones que se ejecutasen en segundo plano. Como consecuencia, las aplicaciones desarrolladas por terceros sólo se ejecutan mientras están en primer plano, deteniéndose cuando el usuario quiere realizar otra actividad.
La nueva versión de la SDK incorpora el Apple Push Notification service, un servicio que envía notificaciones a las aplicaciones del iPhone aunque estas no se estén ejecutando. Para ello la notificación pasa primero por los servidores de Apple, con los que el móvil mantiene siempre una conexión abierta. La notificación se envía al móvil y se muestra al usuario como un mensaje de texto o como una ventana de notificación, que servirán además para lanzar la aplicación y procesar la notificación recibida.
Notificaciones push en Android
Android proporciona notificaciones push para el correo electrónico, calendario y contactos, tanto en el HTC Dream como en el HTC Magic. Como en los casos anteriores, el sistema establece un canal siempre abierto con los los servidores del proveedor (en este caso Google).
Aunque los desarrolladores de Android tienen la opción de programar aplicaciones que se ejecuten en segundo plano el sistema se reserva el derecho de detener cualquier aplicación cuando así lo decida, en función de los recursos disponibles en la máquina. Así que podemos desarrollar un cliente que mantenga conexiones TCP de larga duración con el servidor ejecutándose en bakcground, pero no podemos garantizar que la aplicación se esté ejecutando para mantener esta conexión abierta. Claro que este problema se presenta aunque queramos traernos la información usando pull, es decir, consultando al servidor de periódicamente.
En las versiones beta de la SDK se incluía un servicio llamada XMPPService o GTalkService, que permitía a los desarrolladores enviar notificaciones utilizando la infraestructura de Google Talk. Este servicio fue retirado de la versión final de la SDK por problemas de seguridad, como se explica aquí: Some information on APIs removed in the Android 0.9 SDK beta. No sería extraño que Google incluyese una versión mejorada en alguna de las próximas versiones de Android.
Impresiones sobre el HTC Magic
Ha pasado un mes y medio desde que tengo el HTC Magic. Me pasé de Orange a Vodafone por este móvil, un mes antes de que Orange anunciaran en exclusiva el HTC Hero. En fin…
Como Internet está lleno de análisis del Magic me limito a soltar algunas impresiones personales. No he tenido ocasión de usar de forma continuada otros móviles de gama similar, así que no voy a hacer comparaciones.
- La pantalla es mejor de lo que esperaba. Brillante y nítida. Hubiese sido aún mejor con 3,5 pulgadas. Para evitar llevar la funda puesta le compré un protector de pantalla.
- La interfaz de usuario resulta cómoda y agradable. Fácil de configurar. Echo en falta un sistema rápido para desinstalar aplicaciones.
- No hay multitouch. Podría ser útil para ampliar imágenes o páginas web.
- El sistema es razonablemente estable, pero de vez en cuando se ralentiza sin motivo aparente. No demasiado a menudo ni durante mucho tiempo, pero resulta molesto. Probablemente se deba a alguna de las aplicaciones instaladas. Aún no se me ha quedado colgado.
- El Market contiene una buena cantidad de aplicaciones, tanto de pago como gratuitas. La interfaz es simple pero un poco limitada. Me gustaría que hubiese un sistema para actualizar varias aplicaciones de golpe.
- El GPS es muy rápido. El magnetómetro es interesante. El acelerómetro es un pelín lento, o al menos es lenta la transición entre las dos orientaciones posibles de la interfaz.
- Por algún motivo no se puede tener el home en formato landscape.
- La cámara no tiene flash. Y además es muy mala.
- El trackball funciona bien, pero nunca lo uso.
- La batería no va sobrada, pero no tengo problemas para que me dure todo el día.
- La calidad del sonido en las llamadas en buena. Tener toda la información de los contactos centralizada (y sincronizada con los contactos de GMail) resulta muy cómodo.
- El WiFi va bastante bien. El bluetooth no lo he probado.
- Me he aficionado a Twitter. Aún no me he decidido entre Twidroid y TwitterRide.


