Bluetooth (IV): Creando el hilo de conexión


Como vimos en anteriores artículos, los elementos necesarios para realizar una comunicación entre dos dispositivos a través de Bluetooth van a ser:

  • Cliente
  • Servidor
  • Conexión

Codificaremos cada uno de estos elementos como hilos independientes encargados de centrarse exclusivamente en su tarea. Así, el hilo servidor se encargará de escuchar conexiones entrantes, aceptarlas y establecer la conexión, mientras que el hilo cliente será el encargado de solicitar la conexión y establecerla una vez que haya sido aceptada por el servidor. Por lo tanto, el hilo encargado de establecer la conexión será lanzado tanto por el cliente como por el servidor.

Comenzaremos codificando el hilo encargado de establecer la conexión. Crearemos una clase específica que contendrá los tres hilos, de modo que la funcionalidad entre interfaz de usuario y lógica de la aplicación se mantenga separada.

Crearemos la clase añadiéndole unas cuantas constantes para definir los posibles estados y mensajes a manejar por el handler y un par de atributos privados para almacenar el estado actual de la conexión, el socket de la conexión y el handler que se encargará de comunicar los datos a la interfaz de usuario.


	public class BluetoothService {
		private static final String TAG = "org.danigarcia.examples.bluetooth.BluetoothService";

		public static final int	ESTADO_NINGUNO			= 0;
		public static final int	ESTADO_CONECTADO		= 1;
		public static final int	ESTADO_REALIZANDO_CONEXION	= 2;
		public static final int	ESTADO_ATENDIENDO_PETICIONES= 3;

		public static final int MSG_LEER = 11;
		public static final int MSG_ESCRIBIR = 12;

		private final Handler handler;
		private BluetoothSocket socket;

		private int 			estado;

	}

A continuación codificaremos el hilo encargado de mantener la conexión. Como dijimos en artículos anteriores, la conexión tendrá asociados dos flujos: uno de entrada destinado a leer los datos recibidos y otro de salida, en el que se escribirán los datos que se le quieren enviar al otro dispositivo. Así, declaramos nuestro hilo dentro de la clase BluetoothService.


	// Hilo encargado de mantener la conexion y realizar las lecturas y escrituras
	// de los mensajes intercambiados entre dispositivos.
	private class HiloConexion extends Thread
	{
		private final BluetoothSocket 	socket;			// Socket
		private final InputStream		inputStream;	// Flujo de entrada (lecturas)
		private final OutputStream		outputStream;	// Flujo de salida (escrituras)

	}

Vemos que los tres atributos privados son constantes (calificados como final) para que sus valores no puedan modificarse una vez inicializados. El constructor recibirá un BluetoothSocket como parámetro, que será creado bien por el servidor como respuesta a la aceptación de una conexión entrante, bien por el cliente como respuesta del intento de conexión a un servidor.


	public HiloConexion(BluetoothSocket socket)
	{
		this.socket = socket;
		setName(socket.getRemoteDevice().getName() + " [" + socket.getRemoteDevice().getAddress() + "]");
	}

Dado que usamos atributos finales para definir los flujos de entrada y salida, no podremos reasignar su valor una vez que este haya sido inicializado, por lo que crearemos dos referencias temporales a los flujos de entrada y salida para inicializarlos a null antes de intentar obtener los flujos del socket.


	// Se usan variables temporales debido a que los atributos se declaran como final
	// no seria posible asignarles valor posteriormente si fallara esta llamada
	InputStream tmpInputStream = null;
	OutputStream tmpOutputStream = null;

	// Obtenemos los flujos de entrada y salida del socket.
	try {
		tmpInputStream = socket.getInputStream();
		tmpOutputStream = socket.getOutputStream();
		}
		catch(IOException e){
			Log.e(TAG, "HiloConexion(): Error al obtener flujos de E/S", e);
		}
			inputStream = tmpInputStream;
			outputStream = tmpOutputStream;
	}

Recibiendo datos

A continuación codificaremos el comportamiento que se espera del hilo al invocar su método start(). Éste consistirá en un bucle que mantendrá al hilo en un bucle infinito realizando dos operaciones: mantenerse a la espera de datos en el flujo de entrada y, en el momento en el que se reciban datos, utilizar un Handler para enviárselos a la capa de interfaz, volviendo a comenzar el proceso.


	// Metodo principal del hilo, encargado de realizar las lecturas
	public void run()
	{
		byte[] buffer = new byte[1024];
		int bytes;
		setEstado(ESTADO_CONECTADO);
		// Mientras se mantenga la conexion el hilo se mantiene en espera ocupada
		// leyendo del flujo de entrada
		while(true)
		{
			try {
				// Leemos del flujo de entrada del socket
				bytes = inputStream.read(buffer);

				// Enviamos la informacion a la actividad a traves del handler.
				// El metodo handleMessage sera el encargado de recibir el mensaje
				// y mostrar los datos recibidos en el TextView
				handler.obtainMessage(MSG_LEER, bytes, -1, buffer).sendToTarget();
			}
			catch(IOException e) {
				Log.e(TAG, "HiloConexion.run(): Error al realizar la lectura", e);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

Enviando datos

Una vez que tenemos codificado el comportamiento esperado a la hora de leer el flujo de entrada, escribiremos un método que se encargue de la segunda parte de la comunicación: la escritura.


	public void escribir(byte[] buffer)
	{
		try {
			// Escribimos en el flujo de salida del socket
			outputStream.write(buffer);

			// Enviamos la informacion a la actividad a traves del handler.
			// El metodo handleMessage sera el encargado de recibir el mensaje
			// y mostrar los datos enviados en el Toast
			handler.obtainMessage(MSG_ESCRIBIR, -1, -1, buffer).sendToTarget();
		}
		catch(IOException e) {
			Log.e(TAG, "HiloConexion.escribir(): Error al realizar la escritura", e);
		}
	}

Usando un handler para la comunicación

Posteriormente veremos cómo crear los hilos servidor y cliente que se encargan de lanzar el hilo destinado a la conexión. Sin embargo, hay algo de este hilo que todavía no hemos explicado: el funcionamiento del Handler.

Un Handler tiene dos funciones principales: programar mensajes que serán ejecutados en algún momento en el futuro o encolar una acción que será ejecutada en un hilo distinto al actual, que es el caso que nos ocupa. Por lo tanto, será el método que utilizaremos para comunicar el hilo encargado de la interfaz de usuario y el hilo encargado de la conexión.

Por lo tanto, definiremos un Handler en nuestra Activity que responda a los eventos de lectura y escritura notificados por los métodos run() y write() en el hilo que acabamos de programar. Para ello sobrecargaremos el método handleMessage(), llamado cada vez que otro hilo invoca el método obtainMessage() del handler.

Este método recibe como parámetro un objeto de la clase Message, del cual podremos extraer la información enviada mediante los atributos what, arg1, arg2 y obj. Si recordamos por ejemplo la invocación realizada en el método run(), la correspondencia de estos parámetros sería la siguiente:


	handler.obtainMessage(MSG_LEER, 		// Message.what (int)
				bytes, 			// Message.arg1 (int)
				-1, 			// Message.arg2 (int)
				buffer).sendToTarget();	// Message.obj	(byte[])

Por lo tanto, el parámetro what definirá el tipo de mensaje, el parámetro arg1 el número de bytes que se han leido y el parámetro obj el array de bytes con el mensaje. En este caso, el parámetro arg2 no se utiliza.
Lo que haremos será filtrar por el tipo de mensaje y realizar en cada caso las acciones deseadas. Lo que haremos será mostrar el mensaje recibido en un TextView y, en el caso de que se reciba información de que se ha enviado un mensaje, mostrar una notificación por el Toast.


	// Handler que obtendrá informacion de BluetoothService
	private final Handler handler = new Handler() {

	@Override
	public void handleMessage(Message msg)
	{
		byte[] buffer 	= null;
		String mensaje 	= null;

		// Atendemos al tipo de mensaje
		switch(msg.what)
		{
			// Mensaje de lectura: se mostrara en un TextView
			case BluetoothService.MSG_LEER:
			{
				buffer = (byte[])msg.obj;
				mensaje = new String(buffer, 0, msg.arg1);
				tvMensaje.setText(mensaje);
				break;
			}

			// Mensaje de escritura: se mostrara en el Toast
			case BluetoothService.MSG_ESCRIBIR:
			{
				buffer = (byte[])msg.obj;
				mensaje = new String(buffer);
				mensaje = "Enviando mensaje: " + mensaje;
				Toast.makeText(getApplicationContext(), mensaje, Toast.LENGTH_SHORT).show();
				break;
			}

			default:
				break;
		}
	}

¡No olvidemos, por supuesto, pasar el handler como parámetro al constructor de la clase BluetoothService!

Más adelante veremos cómo codificar el hilo servidor para aceptar las conexiones entrantes y el hilo cliente para solicitarlas.

Artículos relacionados

Bluetooth (I): Activando y desactivando el Bluetooth en Android

Bluetooth (II): Descubriendo dispositivos cercanos

Bluetooth (III): El esquema cliente-servidor

2 comments

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s