前一篇文章现已介绍了怎么扫描和配对邻近的蓝牙设备,本篇文章介绍下怎么运用Bluetooth API为两台蓝牙设备树立衔接并传输数据。

官方文档

树立衔接

蓝牙设备间经过Socket通讯,两台设备树立衔接,需求其间一台设备作为服务端,另一台设备作为客户端,当服务端和客户端在同一 RFCOMM 通道上分别具有已衔接的BluetoothSocket时,即可将二者视为彼此衔接。

服务端

经过BluetoothAdapterlistenUsingRfcommWithServiceRecord获取BluetoothServerSocket,调用BluetoothServerSocketaccept办法监听衔接请求,衔接后调用BluetoothServerSocket的close办法。

class BluetoothTransferController(private val bluetoothAdapter: BluetoothAdapter {
    private val bluetoothUUID = UUID.fromString("fc5deb71-9d4b-460b-b725-b06ea79bda5a")
    private var acceptThread: AcceptThread? = null
    fun startAtAcceptClient() {
        if (acceptThread != null) {
            acceptThread?.cancel()
            acceptThread = null
        }
        acceptThread = AcceptThread()
        acceptThread?.start()
    }
    inner class AcceptThread() : Thread() {
        private val bluetoothServerSocket: BluetoothServerSocket? by lazy(LazyThreadSafetyMode.NONE) {
            bluetoothAdapter.listenUsingRfcommWithServiceRecord("ExampleDemo", bluetoothUUID)
        }
        override fun run() {
            super.run()
            var waitConnect = true
            while (waitConnect) {
                try {
                    // accept为阻塞办法,不能在主线程中调用
                    val bluetoothSocket = bluetoothServerSocket?.accept()
                    if (bluetoothSocket != null) {
                        // 后续可以经过bluetoothSocket传输数据
                        connected(this)
                        bluetoothServerSocket?.close()
                        waitConnect = false
                    }
                } catch (e: IOException) {
                    waitConnect = false
                }
            }
        }
        fun cancel() {
            try {
                bluetoothServerSocket?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }
}

客户端

经过BluetoothDevicecreateRfcommSocketToServiceRecord办法创建BluetoothSocket,调用BluetoothSocketconnect办法发起衔接。

class BluetoothTransferController() {
    // 与服务端运用相同的UUID
    private val bluetoothUUID = UUID.fromString("fc5deb71-9d4b-460b-b725-b06ea79bda5a")
    private var connectThread: ConnectThread? = null
    fun connectToOtherBluetoothDevice(bluetoothDevice: BluetoothDevice) {
        if (connectThread != null) {
            connectThread?.cancel()
            connectThread = null
        }
        connectThread = ConnectThread(bluetoothDevice)
        connectThread?.start()
    }
    inner class ConnectThread(private val bluetoothDevice: BluetoothDevice) : Thread() {
        private val bluetoothSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
            bluetoothDevice.createRfcommSocketToServiceRecord(bluetoothUUID)
        }
        override fun run() {
            super.run()
            bluetoothSocket?.run {
                try {
                    // connect为阻塞办法,不能在主线程中调用
                    connect()
                    // 后续可以经过bluetoothSocket传输数据
                    connected(this)
                } catch (e: IOException) {
                    try {
                        close()
                    } catch (e: IOException) {
                        e.printStackTrace()
                    }
                }
            }
        }
        fun cancel() {
            try {
                bluetoothSocket?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }
}

传输数据

树立衔接后,经过BluetoothSocket获取InpuStreamOutputStream来传输数据。

class BluetoothTransferController(private val bluetoothAdapter: BluetoothAdapter, private val connectedCallback: () -> Unit, private val receiveCallback: (byteArray: ByteArray) -> Unit) {
    private var connectedThread: ConnectedThread? = null
    fun writeData(sendData: ByteArray) {
        connectedThread?.writeData(sendData)
    }
    private fun connected(bluetoothSocket: BluetoothSocket) {
        if (connectedThread != null) {
            connectedThread?.cancel()
            connectedThread = null
        }
        connectedThread = ConnectedThread(bluetoothSocket)
        connectedThread?.start()
    }
    inner class ConnectedThread(private val bluetoothSocket: BluetoothSocket) : Thread() {
        private var inputStream: InputStream? = null
        private var outputStream: OutputStream? = null
        private var connected = false
        init {
            connected = bluetoothSocket.isConnected
            try {
                if (bluetoothSocket.isConnected) {
                    inputStream = bluetoothSocket.inputStream
                    outputStream = bluetoothSocket.outputStream
                }
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
        override fun run() {
            super.run()
            connectedCallback.invoke()
            val buffer = ByteArray(1024)
            while (connected) {
                inputStream?.let {
                    try {
                        var length: Int
                        while (it.read(buffer).also { length = it } != -1) {
                            val dataByteArray = buffer.copyOf(length)
                            receiveCallback.invoke(dataByteArray)
                        }
                    } catch (e: IOException) {
                        e.printStackTrace()
                    }
                }
            }
        }
        fun writeData(sendData: ByteArray) {
            try {
                outputStream?.write(sendData)
                outputStream?.flush()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
        fun cancel() {
            try {
                inputStream?.close()
                outputStream?.close()
                bluetoothSocket.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }
}

需求留意的是,发送的数据或许会被拆分为多个字节数组,所以在接纳端需求保证承受一切字节数组并正确排序。

示例

发送字符串效果如图:

服务端 客户端

演示代码已在示例Demo中增加。

ExampleDemo github

ExampleDemo gitee