Android 经典蓝牙项目开发:
分三部分
一: btcore (服务端和客户端的核心代码)
二: 服务端使用实例
三: 客户端使用实例
btcore 分服务端和客户端
服务端:
1: 启动服务监听客户端请求
2: 处理客户端数据请求并返回数据
客户端:
1: 连接服务端 (扫描)
2: 数据处理(数据组转、队列管理、超时处理、并发)
3: 重连
使用蓝牙前需要判断蓝牙是否可用、开关是否开启、是否授权
/**
* 蓝牙是否可用
*/
fun bluetoothEnable() : Boolean {
return bluetoothAdapter != null
}
/**
* 蓝牙是否打开
*/
fun bluetoothIsOpen() : Boolean {
return bluetoothAdapter.isEnabled ?: false
}
/**
* 检查权限
*/
fun havePermission() : Boolean {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
ActivityCompat.checkSelfPermission(content, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
} else {
ActivityCompat.checkSelfPermission(content, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED
}
}
服务端设置可被扫描连接 (0表示持续可扫描)
// 属性
private lateinit var searchDeviceLauncher: ActivityResultLauncher<Intent>
// onCreate 中初始化
searchDeviceLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
bluetoothManager!!.startAdvertiser()
}
// 需要启动时执行
private fun startDiscoverable() {
var requestDiscoverIntent = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)
requestDiscoverIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0)
searchDeviceLauncher.launch(requestDiscoverIntent)
}
一: 服务端核心类
1: AcceptThread 等待客户端连接线程(阻塞式)
// 核心代码
override fun run() {
super.run()
var shouldLoop : Boolean = true
while (shouldLoop) {
btSocket = try {
Log.i("AcceptThread", " 服务正在等待监听..... ")
serverSocket?.accept()
} catch (e: Exception) {
shouldLoop = false
null
}
btSocket?.also {
serverSocket?.close()
shouldLoop = false
// 通知有客户端设备连接
acceptSubject.onNext(it)
Log.i("BluetoothSocket === : ", "$it")
}
}
}
2: CommunicationThread 通讯句柄类(客户端也需要), 建立连接后的 BluetoothSocket 可以打开 输入输出两个数据流,读和写数据
// 核心代码
init {
try {
inputStream = DataInputStream(socket.inputStream)
outputStream = DataOutputStream(socket.outputStream)
Log.i(TAG, "会话建立完成")
} catch (e: IOException) {
Log.e(TAG, "获取输出输入流失败")
}
}
override fun run() {
super.run()
var numBytes: Int
while (true) {
val mmBuffer: ByteArray = ByteArray(2000)
// 阻塞 收数据
if (inputStream != null && socket.isConnected) {
numBytes = try {
Log.i(TAG, "run: reading")
inputStream!!.read(mmBuffer)
} catch (e: Exception) {
Log.e(TAG, "读取数据异常 $e")
readStateSubject.onNext(true)
break
}
if (numBytes > 0) {
val resultByteArray: ByteArray = ByteArray(numBytes)
System.arraycopy(mmBuffer, 0, resultByteArray, 0, numBytes)
dataSubject.onNext(Pair<Int, ByteArray>(numBytes, resultByteArray))
}
}
}
}
/**
* 写数据
*/
fun write(bytes: ByteArray) : Boolean {
if (!socket.isConnected) {
return false
}
return try {
outputStream?.write(bytes)
outputStream?.flush()
true
} catch (e: IOException) {
false
}
}
3: BluetoothServerManger 管理连接、数据处理、状态监听
// 核心代码
/**
* 作为服务端启动监听(广播)
*/
fun startAdvertiser() {
if (acceptThread == null) {
acceptThread = AcceptThread(uuid = BluetoothServerManger.serverUUID)
acceptThread!!.acceptSubject
.subscribeBy(
onNext = {
bluetoothSocket = it
startServerCommunication(it)
}
)
acceptThread!!.start()
}
}
/**
* 有客户端接入,建立连接
*/
private fun startServerCommunication(bs: BluetoothSocket) {
serverCommThread = CommunicationThread(bs)
serverCommThread!!.readStateSubject
.subscribeBy(onNext = {
reStartAdvertiser()
}, onError = {
})
// 收到客户端数据
serverCommThread!!.dataSubject
.subscribeBy(
onNext = {
val valueString = it.second.toString(Charset.defaultCharset())
var d = String(it.second)
val string2 = String(it.second, Charsets.UTF_16)
Log.i(BluetoothClientManager.TAG, "服务端收到数据 size: ${it.first}; data: $d $valueString ${it.second} $string2")
val receiverData = it.second
// 解析报文头为: AABB, 获取到 taskId 用于返回
if (receiverData.size > 4 &&
receiverData[0] == 0xAA.toByte() &&
receiverData[1] == 0xBB.toByte()) {
tempTask = BTTask()
val rTaskId = (receiverData[3].toInt() and 0xFF shl 8) or (receiverData[2].toInt() and 0xFF)
tempTask!!.taskId = rTaskId
}
if (tempTask == null) {
return@subscribeBy
}
tempTask?.addBuffer(receiverData)
// 检测数据接受完成, 返回给客户端
if (tempTask?.checkComplete() == true) {
val responseByte = byteArrayOf(1, 2, 3, 4, 5)
responseData(tempTask!!.buildCmdContent(responseByte))
Log.i(BluetoothClientManager.TAG, "服务端回复了数据")
}
},
onError = {
Log.i(BluetoothClientManager.TAG, "startServerCommunication: 出错了")
}
)
serverCommThread!!.start()
}
/**
* 监听手机蓝牙开关变化,做出处理
*/
private fun registerBluetoothStateEvent() {
val intent = IntentFilter()
intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
intent.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
intent.addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
val stateReceiver = object : BroadcastReceiver() {
override fun onReceive(p0: Context?, intent: Intent?) {
if (intent == null) {
return
}
val action = intent!!.action
action?.let {
when (it) {
BluetoothAdapter.ACTION_STATE_CHANGED -> {
when (val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0)) {
BluetoothAdapter.STATE_OFF -> stateOff()
BluetoothAdapter.STATE_ON -> stateOn() // 重启监听服务
else -> Log.i(BluetoothClientManager.TAG, "unSupport $state")
}
}
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
Log.i(TAG, "BluetoothDevice: ACTION_ACL_DISCONNECTED ")
}
BluetoothDevice.ACTION_ACL_CONNECTED -> {
Log.i(TAG, "BluetoothDevice: ACTION_ACL_CONNECTED ")
}
else -> {}
}
}
}
}
content.registerReceiver(stateReceiver, intent)
}
二: 客户端核心类
1:ConnectThread 用于连接服务端的线程
// 核心代码
override fun run() {
super.run()
// 连接前,取消扫描
BluetoothAdapter.getDefaultAdapter()?.cancelDiscovery()
if (socket != null) {
try {
socket!!.connect() // 5 秒会自动超时
connectSubject.onNext(socket)
} catch (e: IOException) {
Log.i(TAG, "请求连接异常: $e")
socket!!.close()
connectSubject.onError(Error())
}
}
}
2:BluetoothClientManager 客户端管理器, 处理客户端发起连接请求、队列、重连
注册系统蓝牙开关状态:
/**
* 注册蓝牙状态变化
*/
private fun registerBluetoothStateEvent() {
val intent = IntentFilter()
intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
intent.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
intent.addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
val stateReceiver = object : BroadcastReceiver() {
override fun onReceive(p0: Context?, intent: Intent?) {
if (intent == null) {
return
}
val action = intent!!.action
action?.let {
when (it) {
BluetoothAdapter.ACTION_STATE_CHANGED -> {
when (val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0)) {
BluetoothAdapter.STATE_OFF -> stateOff()
BluetoothAdapter.STATE_ON -> stateOn()
else -> Log.i(TAG, "unSupport $state")
}
}
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
Log.i(BluetoothServerManger.TAG, "onReceive:ACTION_ACL_DISCONNECTED ")
handleDisconnect()
}
BluetoothDevice.ACTION_ACL_CONNECTED -> {
Log.i(BluetoothServerManger.TAG, "onReceive:ACTION_ACL_DISCONNECTED ")
connected = true
}
else -> {}
}
}
}
}
content.registerReceiver(stateReceiver, intent)
}
客户端扫描部分:
/**
* 注册扫描事件、获取扫描结果
*/
private val receiver = object : BroadcastReceiver() {
override fun onReceive(p0: Context?, intent: Intent?) {
intent?.let {
when(intent.action) {
BluetoothDevice.ACTION_FOUND -> {
val device: BluetoothDevice? = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
device?.let {
val deviceHardwareAddress = device.address
Log.i(BluetoothClientManager.TAG, "onReceive: $deviceHardwareAddress")
scanSubject.onNext(Pair(BluetoothDevice.ACTION_FOUND, it))
}
}
BluetoothAdapter.ACTION_DISCOVERY_STARTED -> {
Log.i(BluetoothClientManager.TAG,"scan start")
scanSubject.onNext(Pair(BluetoothAdapter.ACTION_DISCOVERY_STARTED, null))
}
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
unRegisterBluetoothEvent() // 取消注册
Log.i(BluetoothClientManager.TAG,"scan end")
scanSubject.onNext(Pair(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, null))
}
else -> {}
}
}
}
}
/**
* 开始扫描
*/
@SuppressLint("MissingPermission")
fun startDiscoverDevice() : Boolean {
if (!bluetoothEnable()) {
return false
}
if (!bluetoothIsOpen()) {
return false
}
if (!havePermission()) {
return false
}
registerBluetoothEvent()
return bluetoothAdapter.startDiscovery()
}
连接部分:在ACTION_FOUND 过滤到设备,即可发起连接
// 核心代码
/**
* 通过对象连接
*/
@Synchronized
fun connectDevice(device: BluetoothDevice, timeout: Long) : Observable<Boolean> {
if (connecting) {
return Observable.error(BTException("正在连接"))
}
connecting = true
// 记录连接历史,用于重连
historyAddress = device.address
unRegisterBluetoothEvent()
cancelDiscovery()
return Observable.create<Boolean> { emitter ->
if (connected) {
connecting = false
Log.i(TAG, "connectDevice: 已连接 ")
emitter.onNext(true)
return@create
}
val connectThread = ConnectThread(device)
// connect 会阻塞 5 秒后超时, 无需自己加超时处理
connectThread.connectSubject
.subscribeBy(onNext = {
connecting = false
startClientCommunication(it)
emitter.onNext(true)
}, onError = {
connecting = false
Log.i(BluetoothClientManager.TAG, "尝试连接失败")
emitter.onError(it)
})
connectThread.start()
}
}
/**
* 客户端建立可读写通道
*/
private fun startClientCommunication(bs: BluetoothSocket) {
clientCommThread = CommunicationThread(bs)
clientCommThread!!.dataSubject
.subscribeBy(
onNext = {
val valueString = it.second.toString(Charset.defaultCharset())
var d = String(it.second)
Log.i(BluetoothClientManager.TAG, "客户端收到数据 size: ${it.first}; $d ${it.second} data: $valueString ")
val source = it.second
val taskId = (source[3].toInt() and 0xFF shl 8) or (source[2].toInt() and 0xFF)
val ct = currentTasks.first { t -> t.taskId == taskId }
ct.addBuffer(source)
if ( ct.checkComplete()) {
// 接受数据完成,next
val type = ct.type
ct.subscribe?.onNext(true)
ct.subscribe?.onComplete()
ct.complete() // 完成 停止 超时记时
if (currentTasks.contains(ct)) currentTasks.remove(ct)
if (taskList.contains(ct)) taskList.remove(ct)
Log.i(BluetoothClientManager.TAG, "该任务已完成 task id: $taskId")
//下一个
trigger(type)
}
},
onError = {
Log.i(BluetoothClientManager.TAG, "爆发异常")
}
)
clientCommThread!!.start()
}
数据处理:队列处理,先进先出
// 核心代码
/**
* 数据发送
*/
@Synchronized
fun sendData(timeout: Long, message: String, type: Int = 0) : Observable<Boolean> {
if (!bluetoothEnable()) {
return Observable.error(BTException("蓝牙不可用"))
}
if (!bluetoothIsOpen()) {
return Observable.error(BTException("蓝牙未开启"))
}
// 发起重连
if (!connected) {
if (historyAddress == null) {
return Observable.error(BTException("蓝牙未连接"))
}
connectDeviceWithAddress(historyAddress!!)
}
return Observable.create<Boolean> { emitter ->
// 存入队列中
taskList.add(BTTask(timeout = timeout, sourceData = message.toByteArray(), subscribe = emitter))
if (connected) {
trigger(type)
}
}
}
/**
* 检测是否空闲, 空闲时即可发送
*
* type:任务类别,只有不同的type 才会堵塞,(如所有任务都按顺序,使用同一type即可)
*/
private fun trigger(type: Int) {
if (!connected) {
return
}
// 判断是否有同类型的指令正在执行
if (currentTasks.any { it.type == type }) {
return
}
if (taskList.size <= 0) {
return
}
val btTask = taskList.first { it.type == type }
btTask?.let {
currentTasks.add(it)
val cmdContent = btTask.buildCmdContent(it.sourceData!!)
btTask.begin()
btTask.timeoutSubject.subscribeBy(
onError = { e ->
// 指令超时 下一条
btTask.subscribe?.onError(e)
btTask.subscribe?.onComplete()
if (currentTasks.contains(btTask)) currentTasks.remove(btTask)
if (taskList.contains(btTask)) taskList.remove(btTask)
Log.i(BluetoothClientManager.TAG, "该任务已超时 task id: ${btTask.taskId}")
trigger(type)
}
)
val sendResult = clientCommThread!!.write(cmdContent)
Log.i(BluetoothClientManager.TAG, "客户端返回发送结果: $sendResult ")
}
}
完整代码:
https://github.com/tlin2011/android-bluetooth-sdk.git