Cómo transferir datos por Bluetooth

Después de conectarte a un dispositivo Bluetooth de forma correcta, cada uno tiene un BluetoothSocket conectado. Ahora puedes compartir información entre dispositivos. Con BluetoothSocket, el procedimiento general para transferir datos es el siguiente:

  1. Obtén el InputStream y el OutputStream que controlan las transmisiones a través del socket mediante getInputStream() y getOutputStream(), respectivamente.

  2. Lee y escribe datos en las transmisiones mediante read(byte[]) y write(byte[]).

Por supuesto, existen detalles relacionados con la implementación que deben tenerse en cuenta. En particular, debes usar un subproceso dedicado para leer desde la transmisión y escribir en ella. Esto es importante porque los métodos read(byte[]) y write(byte[]) están bloqueando llamadas. El método read(byte[]) se bloquea hasta que haya algo para leer de la transmisión. Por lo general, el método write(byte[]) no se bloquea, pero puede hacerlo para el control de flujo si el dispositivo remoto no llama a read(byte[]) lo suficientemente rápido y, como resultado, los búferes intermedios se llenan. Por lo tanto, debes dedicar tu bucle principal a leer desde InputStream. Puedes usar un método público separado en el subproceso para iniciar escrituras en OutputStream.

Ejemplo

El siguiente es un ejemplo de cómo puedes transferir datos entre dos dispositivos conectados mediante Bluetooth:

Kotlin

private const val TAG = "MY_APP_DEBUG_TAG"

// Defines several constants used when transmitting messages between the
// service and the UI.
const val MESSAGE_READ: Int = 0
const val MESSAGE_WRITE: Int = 1
const val MESSAGE_TOAST: Int = 2
// ... (Add other message types here as needed.)

class MyBluetoothService(
       // handler that gets info from Bluetooth service
       private val handler: Handler) {

   private inner class ConnectedThread(private val mmSocket: BluetoothSocket) : Thread() {

       private val mmInStream: InputStream = mmSocket.inputStream
       private val mmOutStream: OutputStream = mmSocket.outputStream
       private val mmBuffer: ByteArray = ByteArray(1024) // mmBuffer store for the stream

       override fun run() {
           var numBytes: Int // bytes returned from read()

           // Keep listening to the InputStream until an exception occurs.
           while (true) {
               // Read from the InputStream.
               numBytes = try {
                   mmInStream.read(mmBuffer)
               } catch (e: IOException) {
                   Log.d(TAG, "Input stream was disconnected", e)
                   break
               }

               // Send the obtained bytes to the UI activity.
               val readMsg = handler.obtainMessage(
                       MESSAGE_READ, numBytes, -1,
                       mmBuffer)
               readMsg.sendToTarget()
           }
       }

       // Call this from the main activity to send data to the remote device.
       fun write(bytes: ByteArray) {
           try {
               mmOutStream.write(bytes)
           } catch (e: IOException) {
               Log.e(TAG, "Error occurred when sending data", e)

               // Send a failure message back to the activity.
               val writeErrorMsg = handler.obtainMessage(MESSAGE_TOAST)
               val bundle = Bundle().apply {
                   putString("toast", "Couldn't send data to the other device")
               }
               writeErrorMsg.data = bundle
               handler.sendMessage(writeErrorMsg)
               return
           }

           // Share the sent message with the UI activity.
           val writtenMsg = handler.obtainMessage(
                   MESSAGE_WRITE, -1, -1, mmBuffer)
           writtenMsg.sendToTarget()
       }

       // Call this method from the main activity to shut down the connection.
       fun cancel() {
           try {
               mmSocket.close()
           } catch (e: IOException) {
               Log.e(TAG, "Could not close the connect socket", e)
           }
       }
   }
}

Java

public class MyBluetoothService {
   private static final String TAG = "MY_APP_DEBUG_TAG";
   private Handler handler; // handler that gets info from Bluetooth service

   // Defines several constants used when transmitting messages between the
   // service and the UI.
   private interface MessageConstants {
       public static final int MESSAGE_READ = 0;
       public static final int MESSAGE_WRITE = 1;
       public static final int MESSAGE_TOAST = 2;

       // ... (Add other message types here as needed.)
   }

   private class ConnectedThread extends Thread {
       private final BluetoothSocket mmSocket;
       private final InputStream mmInStream;
       private final OutputStream mmOutStream;
       private byte[] mmBuffer; // mmBuffer store for the stream

       public ConnectedThread(BluetoothSocket socket) {
           mmSocket = socket;
           InputStream tmpIn = null;
           OutputStream tmpOut = null;

           // Get the input and output streams; using temp objects because
           // member streams are final.
           try {
               tmpIn = socket.getInputStream();
           } catch (IOException e) {
               Log.e(TAG, "Error occurred when creating input stream", e);
           }
           try {
               tmpOut = socket.getOutputStream();
           } catch (IOException e) {
               Log.e(TAG, "Error occurred when creating output stream", e);
           }

           mmInStream = tmpIn;
           mmOutStream = tmpOut;
       }

       public void run() {
           mmBuffer = new byte[1024];
           int numBytes; // bytes returned from read()

           // Keep listening to the InputStream until an exception occurs.
           while (true) {
               try {
                   // Read from the InputStream.
                   numBytes = mmInStream.read(mmBuffer);
                   // Send the obtained bytes to the UI activity.
                   Message readMsg = handler.obtainMessage(
                           MessageConstants.MESSAGE_READ, numBytes, -1,
                           mmBuffer);
                   readMsg.sendToTarget();
               } catch (IOException e) {
                   Log.d(TAG, "Input stream was disconnected", e);
                   break;
               }
           }
       }

       // Call this from the main activity to send data to the remote device.
       public void write(byte[] bytes) {
           try {
               mmOutStream.write(bytes);

               // Share the sent message with the UI activity.
               Message writtenMsg = handler.obtainMessage(
                       MessageConstants.MESSAGE_WRITE, -1, -1, mmBuffer);
               writtenMsg.sendToTarget();
           } catch (IOException e) {
               Log.e(TAG, "Error occurred when sending data", e);

               // Send a failure message back to the activity.
               Message writeErrorMsg =
                       handler.obtainMessage(MessageConstants.MESSAGE_TOAST);
               Bundle bundle = new Bundle();
               bundle.putString("toast",
                       "Couldn't send data to the other device");
               writeErrorMsg.setData(bundle);
               handler.sendMessage(writeErrorMsg);
           }
       }

       // Call this method from the main activity to shut down the connection.
       public void cancel() {
           try {
               mmSocket.close();
           } catch (IOException e) {
               Log.e(TAG, "Could not close the connect socket", e);
           }
       }
   }
}

Después de que el constructor obtiene los flujos necesarios, el subproceso espera a que lleguen los datos mediante InputStream. Cuando read(byte[]) muestra datos de la transmisión, estos se envían a la actividad principal mediante un miembro Handler de la clase superior. Luego, el subproceso espera a que se lean más bytes desde InputStream.

Para enviar datos salientes, debes llamar al método write() del subproceso desde la actividad principal y pasar los bytes que se enviarán. Este método llama a write(byte[]) para enviar los datos al dispositivo remoto. Si se arroja una IOException cuando se llama a write(byte[]), el subproceso envía un aviso a la actividad principal y le explica al usuario que el dispositivo no pudo enviar los bytes especificados al otro dispositivo (conectado).

El método cancel() del subproceso te permite cerrar la BluetoothSocket para finalizar la conexión en cualquier momento. Siempre llama a este método cuando termines de usar la conexión Bluetooth.

Para ver una demostración de cómo usar las APIs de Bluetooth, consulta la app de ejemplo de chat de Bluetooth en GitHub.