Bluetooth データの転送

Bluetooth デバイスへの接続に成功すると、各デバイスに BluetoothSocket が接続されます。これで、デバイス間で情報を共有できるようになりました。BluetoothSocket を使用してデータを転送する一般的な手順は次のとおりです。

  1. getInputStream()getOutputStream() をそれぞれ使用して、ソケットを介した通信を処理する InputStreamOutputStream を取得します。

  2. read(byte[])write(byte[]) を使用して、ストリームへのデータの読み取りと書き込みを行う。

もちろん、検討すべき実装の詳細もあります。特に、ストリームからの読み取りとストリームへの書き込みには、専用のスレッドを使用する必要があります。read(byte[]) メソッドと write(byte[]) メソッドの両方が呼び出しをブロックしているため、これは重要です。read(byte[]) メソッドは、ストリームから読み取るものが見つかるまでブロックします。write(byte[]) メソッドは通常ブロックしませんが、リモート デバイスによる read(byte[]) の呼び出し速度が十分でなく、その結果中間バッファがいっぱいになった場合はフロー制御をブロックできます。そのため、スレッド内のメインループを InputStream からの読み取り専用にする必要があります。スレッド内の個別のパブリック メソッドを使用して、OutputStream への書き込みを開始できます。

Bluetooth 経由で接続された 2 つのデバイス間でデータを転送する方法の例を次に示します。

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);
           }
       }
   }
}

コンストラクタが必要なストリームを取得した後、スレッドは InputStream からデータが来るのを待ちます。read(byte[]) がストリームのデータを返すと、親クラスのメンバー Handler を使用して、データがメイン アクティビティに送信されます。その後、スレッドは InputStream からさらにバイトが読み取られるのを待ちます。

送信データを送信するには、メイン アクティビティからスレッドの write() メソッドを呼び出し、送信するバイトを渡します。このメソッドは write(byte[]) を呼び出して、リモート デバイスにデータを送信します。write(byte[]) の呼び出し時に IOException がスローされると、スレッドはメイン アクティビティにトーストを送信し、所定のバイトを他のデバイス(接続済み)に送信できなかったことをユーザーに伝えます。

スレッドの cancel() メソッドを使用すると、BluetoothSocket を閉じることで、いつでも接続を終了できます。Bluetooth 接続の使用が終了したら、必ずこのメソッドを呼び出します。

Bluetooth API の使用方法を示す例については、GitHub の Bluetooth Chat サンプルアプリをご覧ください。