前一篇文章现已介绍了怎么扫描和配对邻近的蓝牙设备,本篇文章介绍下怎么运用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中增加。
ExampleDemo github
ExampleDemo gitee