RxJava Android Bluetooth 經(jīng)典藍牙 SDK 項目開發(fā)

Android 經(jīng)典藍牙項目開發(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

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容