Android 經典藍牙項目開發(fā):
分三部分
一: btcore (服務端和客戶端的核心代碼)
二: 服務端使用實例
三: 客戶端使用實例
btcore 分服務端和客戶端
服務端:
1: 啟動服務監(jiān)聽客戶端請求
2: 處理客戶端數(shù)據(jù)請求并返回數(shù)據(jù)
客戶端:
1: 連接服務端 (掃描)
2: 數(shù)據(jù)處理(數(shù)據(jù)組轉、隊列管理纠脾、超時處理丧诺、并發(fā))
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表示持續(xù)可掃描)
// 屬性
private lateinit var searchDeviceLauncher: ActivityResultLauncher<Intent>
// onCreate 中初始化
searchDeviceLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
bluetoothManager!!.startAdvertiser()
}
// 需要啟動時執(zhí)行
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", " 服務正在等待監(jiān)聽..... ")
serverSocket?.accept()
} catch (e: Exception) {
shouldLoop = false
null
}
btSocket?.also {
serverSocket?.close()
shouldLoop = false
// 通知有客戶端設備連接
acceptSubject.onNext(it)
Log.i("BluetoothSocket === : ", "$it")
}
}
}
2: CommunicationThread 通訊句柄類(客戶端也需要), 建立連接后的 BluetoothSocket 可以打開 輸入輸出兩個數(shù)據(jù)流芥驳,讀和寫數(shù)據(jù)
// 核心代碼
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)
// 阻塞 收數(shù)據(jù)
if (inputStream != null && socket.isConnected) {
numBytes = try {
Log.i(TAG, "run: reading")
inputStream!!.read(mmBuffer)
} catch (e: Exception) {
Log.e(TAG, "讀取數(shù)據(jù)異常 $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))
}
}
}
}
/**
* 寫數(shù)據(jù)
*/
fun write(bytes: ByteArray) : Boolean {
if (!socket.isConnected) {
return false
}
return try {
outputStream?.write(bytes)
outputStream?.flush()
true
} catch (e: IOException) {
false
}
}
3: BluetoothServerManger 管理連接介粘、數(shù)據(jù)處理、狀態(tài)監(jiān)聽
// 核心代碼
/**
* 作為服務端啟動監(jiān)聽(廣播)
*/
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 = {
})
// 收到客戶端數(shù)據(jù)
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, "服務端收到數(shù)據(jù) 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)
// 檢測數(shù)據(jù)接受完成, 返回給客戶端
if (tempTask?.checkComplete() == true) {
val responseByte = byteArrayOf(1, 2, 3, 4, 5)
responseData(tempTask!!.buildCmdContent(responseByte))
Log.i(BluetoothClientManager.TAG, "服務端回復了數(shù)據(jù)")
}
},
onError = {
Log.i(BluetoothClientManager.TAG, "startServerCommunication: 出錯了")
}
)
serverCommThread!!.start()
}
/**
* 監(jiān)聽手機藍牙開關變化,做出處理
*/
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() // 重啟監(jiān)聽服務
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 客戶端管理器婚瓜, 處理客戶端發(fā)起連接請求、隊列刑棵、重連
注冊系統(tǒng)藍牙開關狀態(tài):
/**
* 注冊藍牙狀態(tài)變化
*/
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 過濾到設備,即可發(fā)起連接
// 核心代碼
/**
* 通過對象連接
*/
@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, "客戶端收到數(shù)據(jù) 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()) {
// 接受數(shù)據(jù)完成,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, "爆發(fā)異常")
}
)
clientCommThread!!.start()
}
數(shù)據(jù)處理:隊列處理碍舍,先進先出
// 核心代碼
/**
* 數(shù)據(jù)發(fā)送
*/
@Synchronized
fun sendData(timeout: Long, message: String, type: Int = 0) : Observable<Boolean> {
if (!bluetoothEnable()) {
return Observable.error(BTException("藍牙不可用"))
}
if (!bluetoothIsOpen()) {
return Observable.error(BTException("藍牙未開啟"))
}
// 發(fā)起重連
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)
}
}
}
/**
* 檢測是否空閑柠座, 空閑時即可發(fā)送
*
* type:任務類別手报,只有不同的type 才會堵塞浩销,(如所有任務都按順序,使用同一type即可)
*/
private fun trigger(type: Int) {
if (!connected) {
return
}
// 判斷是否有同類型的指令正在執(zhí)行
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, "客戶端返回發(fā)送結果: $sendResult ")
}
}
完整代碼:
https://github.com/tlin2011/android-bluetooth-sdk.git