前一篇文章已经介绍了如何扫描和配对附近的蓝牙设备,本篇文章介绍下如何使用Bluetooth API为两台蓝牙设备建立连接并传输数据。
建立连接
蓝牙设备间通过Socket通信,两台设备建立连接,需要其中一台设备作为服务端,另一台设备作为客户端,当服务端和客户端在同一 RFCOMM 通道上分别拥有已连接的 BluetoothSocket 时,即可将二者视为彼此连接。
服务端
通过BluetoothAdapter的listenUsingRfcommWithServiceRecord获取BluetoothServerSocket,调用BluetoothServerSocket的accept方法监听连接请求,连接后调用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()
}
}
}
}
客户端
通过BluetoothDevice的createRfcommSocketToServiceRecord方法创建BluetoothSocket,调用BluetoothSocket的connect方法发起连接。
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 获取InpuStream和OutputStream来传输数据。
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中添加。