Ejemplo de handler en Android

Mi primer contacto con Android, ya hace algún tiempo, fue para portar un juego que había hecho para la plataforma J2ME. Como Android y J2ME comparten lenguaje (Java) me las prometía muy felices pensando que el port sería casi directo. Me equivocaba.
Normalmente, el bucle principal de un juego suele correr en un thread “de fondo”. Si intentas actualizar la pantalla (en este caso un objeto Canvas) verás como Android te devuelve una bonita excepción como ésta:

android.view.ViewRoot$CalledFromWrongThreadException: 
Only the original thread that created a view hierarchy can touch its views.

Cuando lanzas un programa en Android, la ejecución se lleva a cabo en el llamado UIThread, que es el hilo principal de la aplicación y el que se encarga de actualizar la interfaz de usuario. ¿Por qué no actualizar la pantalla de juego desde el UIThread? Mi consejo: ni lo intentes. La lógica de un juego suele ser un trabajo bastante “pesado” y hará que tu aplicación se vuelva lenta y no responda bien cuando el usuario toque la pantalla. Además, a partir de la versión 4.0 de Android el propio sistema te impide hacerlo. La solución pasa, como ya se ha dicho, por usar un thread para la lógica del juego y repintado de la pantalla. Queda el pequeño detalle de repintar la pantalla desde este thread. Aquí los Handlers vienen en nuestra ayuda.

Los Handlers implementan un mecanismo de paso de mensajes entre threads de forma que nos permiten enviar mensajes desde nuestro hilo al UIThread. Para ello se utiliza el método sendMessage de la clase Handler para avisar al UIThread de que tiene que hacer el repintado de la pantalla. Os dejo un pequeño ejemplo, bastante sencillito, de cómo funciona.

En la clase principal definimos el handler e implementamos el thread.

public class EjemploHandler extends Activity {

	protected static final int MENSAJEID = 0x100;

	Thread threadVista = null;
	MiVista miVista=null;

	Handler vistaHandler = new Handler() {
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case EjemploHandler.MENSAJEID:
				// Invalidar vista para repintado
				miVista.invalidate();
				break;
			}
			super.handleMessage(msg);
		}
	};


	@Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);

		// crear la vista
		this.miVista = new MiVista(this);
		this.setContentView(this.miVista);

		// Thread para actualizar la vista
		new Thread(new ActualizaVista()).start();
	}

	class ActualizaVista implements Runnable {

		public void run() {
			while (! Thread.currentThread().isInterrupted()) {
				// Enviar mensaje al handler para hacer invalidate
				Message message = new Message();
				message.what = EjemploHandler.MENSAJEID;
				EjemploHandler.this.vistaHandler.sendMessage(message);

				try {
					Thread.sleep(100); 
				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
				}
			}
		}
	}
}

Ahora sólo tenemos que usar nuestra clase descendiente de View como habitualmente.

public class MiVista extends View {

	int posx, posy, deltax, deltay;
	Paint fondo = new Paint();
	Paint bola = new Paint();

	public MiVista(Context context) {
		super(context);
		posx=10; posy=10; deltax=2; deltay=2;
		fondo.setColor(Color.GRAY);
		bola.setColor(Color.BLUE);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		canvas.drawRect(0, 0, canvas.getWidth(), 
						canvas.getHeight(), fondo);

		if (posx > this.getWidth() || posx < 0) {
			deltax=deltax*-1;
		}

		if (posy > this.getHeight() || posy < 0) {
			deltay=deltay*-1;
		}          

		posx=posx+deltax;
		posy=posy+deltay;

		canvas.drawCircle(posx, posy, 10, bola);
	}
}

Hay alternativa a los Handlers, como el uso de la clase AsyncTask, que nos permite realizar tareas de fondo con bastante control. Decir, antes de cerrar el post, que si quisiéramos hacer un juego medianamente complejo, es más interesante usar la clase SurfaceView en vez de la clásica vista con View como la del ejemplo anterior.

 

Tags: , , ,

Comentarios: 4

Responder »

 
  • OOOOH DIOOOS!!
    Mil gracias!! estaba a un paso de suicidarme.. xD

     
     
     
  • Alberto

    Hola, ¿por qué en lugar de:

    switch (msg.what) {
    case EjemploHandler.MENSAJEID:
    // Invalidar vista para repintado
    miVista.invalidate();
    break;
    }

    No usas simplemente

    miVista.invalidate();
    ?

     
     
     
  • Alberto García

    Buenas Alberto,

    La idea es que, en un programa más complejo, puedas utilizar distintos tipos de mensaje. Por ejemplo, uno para repintar la pantalla, otro para lanzar un thread en el que necesites hacer algo, etc. En un programa real seguramente necesitarás utilizar otros mensajes, por eso lo puse así.
    Para este ejemplo, como bien dices, bastaría con miVista.invalidate();

    Saludos.

     
     
     
  • Francisco

    Por fin, uno clarito y sin adornos florales.

     
     
     
  • Dejar un comentario
     
    Tu gravatar
    Tu nombre