Ble掃描和Gatt通信(2022兼容多sdk版本)

Ble掃描

Ble權(quán)限適配

權(quán)限問題點(diǎn)總結(jié):

  1. 如何適配不同sdk版本,以確保獲取到藍(lán)牙權(quán)限?

  2. 如何過谷歌權(quán)限隱私審核要求?即以掃描標(biāo)準(zhǔn)進(jìn)行權(quán)限申請?

清單文件配置

針對不同sdk版本權(quán)限,需要定制不同的清單文件,以確保清單文件的權(quán)限能被谷歌審核通過,因此需要按照官方要求進(jìn)行適配。官方鏈接:https://developer.android.com/guide/topics/connectivity/bluetooth#Permissions

舊版本所需權(quán)限如下:

  1. android.permission.BLUETOOTH:用于藍(lán)牙連接

  2. android.permission.BLUETOOTH_ADMIN: 用于藍(lán)牙掃描發(fā)現(xiàn),即找到其他藍(lán)牙設(shè)備所需權(quán)限

  3. android.permission.ACCESS_COARSE_LOCATION:網(wǎng)絡(luò)定位權(quán)限,只兼容android9 即sdk28以下

  4. android.permission.ACCESS_FINE_LOCATION: Gps精準(zhǔn)定位,兼容所有版本

所以需要在清單文件中指令權(quán)限最大使用的sdk版本,android11 即sdk30,需要在舊權(quán)限上申明此信息

<?xml version="1.0" encoding="utf-8"?><manifest <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.typhur.module.ble">
    <!--舊版藍(lán)牙權(quán)限 BLUETOOTH藍(lán)牙連接,BLUETOOTH_ADMIN藍(lán)牙掃描發(fā)現(xiàn)-->
    <uses-permission
        android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />
    <uses-permission
        android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" />

    <!--Android6-11低版本藍(lán)牙權(quán)限兼容,ACCESS_FINE_LOCATION精準(zhǔn)的GPS定位,ACCESS_COARSE_LOCATION網(wǎng)絡(luò)定位-->
    <uses-permission
        android:name="android.permission.ACCESS_FINE_LOCATION"
        android:maxSdkVersion="30" />
    <uses-permission
        android:name="android.permission.ACCESS_COARSE_LOCATION"
        android:maxSdkVersion="30" />
</manifest>="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    package="com.typhur.module.ble">    <!--舊版藍(lán)牙權(quán)限 BLUETOOTH藍(lán)牙連接,BLUETOOTH_ADMIN藍(lán)牙掃描發(fā)現(xiàn)-->    <uses-permission        android:name="android.permission.BLUETOOTH"        android:maxSdkVersion="30" />    <uses-permission        android:name="android.permission.BLUETOOTH_ADMIN"        android:maxSdkVersion="30" />    <!--Android6-11低版本藍(lán)牙權(quán)限兼容,ACCESS_FINE_LOCATION精準(zhǔn)的GPS定位,ACCESS_COARSE_LOCATION網(wǎng)絡(luò)定位-->    <uses-permission        android:name="android.permission.ACCESS_FINE_LOCATION"        android:maxSdkVersion="30" />    <uses-permission        android:name="android.permission.ACCESS_COARSE_LOCATION"        android:maxSdkVersion="30" /></manifest>

新版本所需如下:

  1. android.permission.BLUETOOTH_CONNECT: 用于android12 藍(lán)牙設(shè)備通信和檢測藍(lán)牙是否打開

  2. android.permission.BLUETOOTH_SCAN: 掃描權(quán)限,最好申明不獲取位置信息,即neverForLocation

  3. android.permission.BLUETOOTH_ADVERTISE: 當(dāng)前手機(jī)藍(lán)牙能被檢測掃描權(quán)限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.typhur.module.ble">
    <!--Android12 的藍(lán)牙權(quán)限 如果您的應(yīng)用與已配對的藍(lán)牙設(shè)備通信或者獲取當(dāng)前手機(jī)藍(lán)牙是否打開-->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <!--Android12 的藍(lán)牙權(quán)限 如果您的應(yīng)用查找藍(lán)牙設(shè)備(如藍(lán)牙低功耗 (BLE) 外圍設(shè)備)-->
    <uses-permission
        android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation"
        tools:targetApi="s" />
    <!--Android12 的藍(lán)牙權(quán)限 如果您的應(yīng)用使當(dāng)前設(shè)備可被其他藍(lán)牙設(shè)備檢測到-->
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
</manifest>

動態(tài)權(quán)限申請

總結(jié)要點(diǎn):

  1. 區(qū)分android12以上和以下權(quán)限android12PermissionList 和 android12LowerPermissionList

  2. 獲取當(dāng)前設(shè)備sdk版本,并進(jìn)行權(quán)限判斷,分別為isAndroid12()和isPermissionGrant()

  3. 進(jìn)行權(quán)限申請,使用官方的權(quán)限申請新方法ActivityResultContract

  4. 用safeRun()確保權(quán)限申請獲得后,執(zhí)行掃描代碼

object BlePermissionHelp {
    /**
    @description android12高版本權(quán)限
     */
    @RequiresApi(Build.VERSION_CODES.S)
    private val android12PermissionList = arrayOf(
        Manifest.permission.BLUETOOTH_SCAN,
        Manifest.permission.BLUETOOTH_CONNECT,
    )

    /**
    @description 低版本權(quán)限
     */
    private val android12LowerPermissionList = arrayOf(
        Manifest.permission.BLUETOOTH,
        Manifest.permission.BLUETOOTH_ADMIN,
        Manifest.permission.ACCESS_FINE_LOCATION,
    )

    @SuppressLint("AnnotateVersionCheck")
    private fun isAndroid12() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S

    /**
    @description 權(quán)限安全執(zhí)行
     */
    fun safeRun(
        helper: ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>,
        block: () -> Unit
    ) {
        if (isAndroid12()) {
            requestPermission(helper, android12PermissionList) {
                block.invoke()
            }
        } else {
            requestPermission(helper, android12LowerPermissionList) {
                block.invoke()
            }
        }
    }

    /**
    @description 是權(quán)限否授權(quán)
     */
    fun isPermissionGrant(): Boolean {
        return if (isAndroid12()) {
            android12PermissionList.hasPermission()
        } else {
            android12LowerPermissionList.hasPermission()
        }
    }

    /**
    @description 檢測權(quán)限列表是否授權(quán),如果未授權(quán),遍歷請求授權(quán)
     */
    private fun requestPermission(
        permission: ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>,
        permissionList: Array<String>,
        block: () -> Unit
    ) {
        permission.launch(permissionList) {
            // 檢測權(quán)限完整
            var hasPermission = false
            for (entry in it) {
                if (!entry.value) {
                    hasPermission = false
                    break
                } else {
                    hasPermission = true
                }
            }
            if (hasPermission) block.invoke()
        }
    }
}

Ble掃描代碼實現(xiàn)

藍(lán)牙通識:

通常藍(lán)牙掃描會根據(jù)藍(lán)牙芯片進(jìn)行區(qū)分,即為普通藍(lán)牙和低功耗藍(lán)牙,在4.0之前藍(lán)牙只有一種模式,在4.0時則區(qū)分了普通藍(lán)牙和低功耗藍(lán)牙,4.1/4.2/5.0的藍(lán)牙芯片即包含經(jīng)典藍(lán)牙(BT)又包含低功耗藍(lán)牙(Ble)

技術(shù)說明:

針對google的藍(lán)牙Api,區(qū)分了廣播注冊方式獲取和使用藍(lán)牙設(shè)配器BluetoothManager.BluetoothAdapter中的BluetoothLeScanner,基于api的新舊,筆者優(yōu)先考慮了后者BluetoothLeScanner,因此在此不對廣播注冊方式的藍(lán)牙掃描做過多的展開。如需廣播注冊方式,請參考官方鏈接:https://developer.android.com/guide/topics/connectivity/bluetooth#FindDevices

ble掃描所需的api分別為startScan(List <scanfilter>filters, ScanSettings settings, ScanCallback callback) 和 stopScan(ScanCallback callback), 由于需要對掃描的ble進(jìn)行條件過濾和配置,所以用了一個輔助類去管理此職責(zé)</scanfilter>

BleScanHelper (藍(lán)牙掃描輔助類)

思路整理:

  1. 掃描過程會掃到許多藍(lán)牙設(shè)備,為了確保性能需要對此進(jìn)行過濾,通常利用ScanFilter類,對service uuid進(jìn)行過濾,其中藍(lán)牙標(biāo)準(zhǔn)的uuid 就是"0000ffff-0000-1000-8000-00805F9B34FB",當(dāng)然也可以針對名稱等其他條件進(jìn)行過濾

  2. 對于不同版本sdk,由于api的差異,我們需要配置不同的ScanSettings,對于android6.0以上的藍(lán)牙版本,可以設(shè)置平衡模式和全匹配規(guī)則,以便更快捷掃描;對于低版本的智能使用低功耗模式用于節(jié)電

  3. 正式開始掃描startScan(),為了防止權(quán)限問題,對權(quán)限進(jìn)行了二次校驗,并且為了防止重復(fù)掃描,在executeScan()中打了標(biāo)記isScanning,確保只會執(zhí)行以此掃描

  4. 由于藍(lán)牙掃描是耗時行為,所以通常需要定義超時概念,來結(jié)果藍(lán)牙掃描,因此需要stopScan()停止此掃描行為

@SuppressLint("MissingPermission")
object BleScanHelper {

    var SERVICE_UUID = "0000ffff-0000-1000-8000-00805F9B34FB"

    /**
    @description 回調(diào)
     */
    var callback: ScanCallback? = null

    /**
    @description 掃描控制類
     */
    var leScanner: BluetoothLeScanner? = null

    /**
    @description 是否正在掃描中
    */
    private var isScanning = false

    private val scanFilterList = mutableListOf<ScanFilter>().apply {
        add(0, getDefFilter())
    }

    private fun getDefFilter(): ScanFilter {
        return ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(SERVICE_UUID)).build()
    }

    private val scanSettings: ScanSettings = getScanSettings()

    /**
    @description 獲取掃描配置
     */
    private fun getScanSettings(): ScanSettings {
        val builder = ScanSettings.Builder()
        if (Build.VERSION.SDK_INT >= 23) {
            builder.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
                .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
        } else {
            builder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
        }
        return builder.build()
    }

    /**
    @description 開啟藍(lán)牙掃描
    @return 是否開啟成功
     */
    fun startScan(
        permissionHelper:
        ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>?,
    ) {
        permissionHelper?.run {
            val permissionGrant = BlePermissionHelp.isPermissionGrant()
            logD { "startScan() permissionGrant = $permissionGrant" }
            if (permissionGrant) {
                executeScan()
            } else {
                BlePermissionHelp.safeRun(this) {
                    executeScan()
                }
            }
        }
    }

    /**
    @description 執(zhí)行掃描操作
     */
    private fun executeScan() {
        logD { "executeScan()" }
        if (!isScanning){
            isScanning = true
            leScanner?.startScan(scanFilterList, scanSettings, callback)
        }
    }

    /**
    @description 取消掃描
     */
    fun stopScan(scanner: BluetoothLeScanner? = null) {
        logD { "stopScan()" }
        isScanning = false
        if (scanner != null) leScanner = scanner
        try {
            leScanner?.stopScan(callback)
        } catch (e:Exception) {
            e.printStackTrace()
        }
    }
}

ScanTimeoutCallback(藍(lán)牙超時回調(diào))

用于統(tǒng)一處理藍(lán)牙掃描超時的而定義出的回調(diào)

interface ScanTimeoutCallback {
    /**
    @description ble掃描列表回調(diào)
    */
    fun onScanList(list: List<BleDeviceInfo>)

    /**
    @description 掃描超時回調(diào)
     */
    fun onTimeout()
}

BleScanManager(藍(lán)牙掃描管理類)

思路整理:

  1. 由于該類是管理類,為了確保線程安全,使用了單例方式進(jìn)行校驗申請

  2. 藍(lán)牙掃描需要有超時時間配置和權(quán)限launch進(jìn)行權(quán)限申請,因此定義了initConfig()方法

  3. 在執(zhí)行掃描前,需要判斷藍(lán)牙是否打開,因此定義了isBleEnable(),利用BluetoothAdapter進(jìn)行判斷

  4. 開始掃描startScan(callback: ScanTimeoutCallback),主要通過ScanTimeoutCallback的封裝,對超時情況和列表掃描結(jié)果進(jìn)行回調(diào)

  5. stopScan()為了防止內(nèi)存泄露,對此進(jìn)行超時的取消和對回調(diào)的清除,同事為防止出現(xiàn)權(quán)限問題,也進(jìn)行了權(quán)限校驗

使用總結(jié):

initConfig() -> isBleEnable() -> startScan(callback: ScanTimeoutCallback) -> stopScan()

class BleScanManager {
    companion object {
        @Volatile
        var instance: BleScanManager? = null

        fun isInited() = instance != null

        fun newInstance(): BleScanManager {
            if (instance == null) {
                synchronized(BleScanManager::class.java) {
                    if (instance == null) {
                        instance = BleScanManager()
                    }
                }
            }
            return instance!!
        }
    }

    @SuppressLint("ServiceCast")
    val bluetoothManager: BluetoothManager =
        BaseGlobalConst.app.applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager

    val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter

    /**
    @description 權(quán)限輔助類
     */
    private var permissionHelper: ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>? =
        null

    /**
    @description 藍(lán)牙列表結(jié)果
     */
    private val scanCallback = BleScanDefCallback {
        // 子線程回調(diào),需要切換線程
        BaseGlobalConst.mainScope.launchOnUi {
            scanTimeoutCallback?.onScanList(it)
            // 列表不為空,移除超時
            if (it.isNotEmpty()){
                BaseGlobalConst.mainHandler.removeCallbacks(scanCancelRunnable)
            }
        }
    }

    /**
    @description 二次封裝回調(diào),用于處理掃描超時回調(diào)
    */
    private var scanTimeoutCallback: ScanTimeoutCallback?= null
    private var scanTimeoutMillis = 40_000L
    private val scanCancelRunnable = Runnable {
        scanTimeoutCallback?.onTimeout()
        stopScan()
    }

    /**
    @description 初始化配置
     */
    fun initConfig(
        scanTimeoutMillis: Long = 40_000L,
        helper: ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>
    ) {
        this.scanTimeoutMillis = scanTimeoutMillis
        permissionHelper = helper
    }

    /**
    @description 打開藍(lán)牙
     */
    fun enableBle(activityForResult: ActivityResultHelper<Intent, ActivityResult>): Boolean {
        if (!BlePermissionHelp.isPermissionGrant()) throw IllegalStateException("not grant ble permission list")
        var isSuc = false
        if (bluetoothAdapter?.isEnabled == false) {
            activityForResult.launch(Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)) {
                isSuc = it.resultCode == RESULT_OK
            }
        }
        return isSuc
    }

    /**
    @description 當(dāng)前藍(lán)牙是否打開
     */
    fun isBleEnable(): Boolean = bluetoothAdapter?.isEnabled ?: false

    /**
    @description 開啟藍(lán)牙掃描,傳遞一個回調(diào)類
     */
    fun startScan(callback: ScanTimeoutCallback) {
        if (!isBleEnable()) return
        // 設(shè)置回調(diào)
        scanTimeoutCallback = callback
        BleScanHelper.callback = scanCallback
        BleScanHelper.leScanner = bluetoothAdapter?.bluetoothLeScanner
        executeScan()
    }

    private fun executeScan(){
        BaseGlobalConst.mainHandler.removeCallbacks(scanCancelRunnable)
        BleScanHelper.startScan(permissionHelper)
        BaseGlobalConst.mainHandler.postDelayed(scanCancelRunnable, scanTimeoutMillis)
    }

    fun stopScan() {
        BaseGlobalConst.mainHandler.removeCallbacks(scanCancelRunnable)
        scanTimeoutCallback = null
        if (!BlePermissionHelp.isPermissionGrant()) throw IllegalStateException("not grant ble permission list")
        BleScanHelper.stopScan(bluetoothAdapter?.bluetoothLeScanner)
    }
}

Blufi設(shè)備配網(wǎng)

官方參考:https://github.com/EspressifApp/EspBlufiForAndroid

Blufi是用于解決設(shè)備配網(wǎng)的一個三方工具,由于硬件使用此套第三方交互,因此使用此庫,但為了防止以后需要定制化開發(fā),在此也提供了另外的解決方案,參考博客:https://cloud.tencent.com/developer/article/1875770

總結(jié)思考:

  1. 設(shè)備配網(wǎng)本質(zhì)是一種藍(lán)牙數(shù)據(jù)通信,所以需要用到官方的BluetoothGattCallback,使用BluetoothDevice.connectGatt()進(jìn)行g(shù)att連接通信

  2. 需要對BluetoothGattCallback的回調(diào)順序有所理解,參考博客中清晰地備注了此方法

  3. blufi主要封裝了建立完Gatt通道后,對信息的加密傳輸,對設(shè)備能訪問到的wifi列表信息穿肚、對設(shè)備配網(wǎng)信息的結(jié)果回調(diào)處理,因此有了BlufiCallback

GattCallback(低功耗藍(lán)牙回調(diào))

思路整理:

  1. BluetoothDevice.connectGatt()進(jìn)行低功耗藍(lán)牙同道連接,連接成功會執(zhí)行onConnectionStateChange,通常在連接成功時,需要調(diào)用gatt.discoverServices(),進(jìn)行服務(wù)發(fā)現(xiàn)

  2. mtu指的是單包藍(lán)牙傳輸數(shù)據(jù)大小,gatt.requestMtu(512),默認(rèn)是512字節(jié),如果請求失敗再設(shè)置為20字節(jié),這是由于低版本ble有的只支持20字節(jié)大小的傳輸,因此通??梢栽趏nConnectionStateChange進(jìn)行配置

  3. 當(dāng)執(zhí)行完畢gatt.discoverServices(),就會回調(diào)onServicesDiscovered,如果發(fā)現(xiàn)不了服務(wù),則需要調(diào)用gatt.disconnect()進(jìn)行斷開連接;如果發(fā)現(xiàn)服務(wù),則需要進(jìn)行服務(wù)的獲取gatt.getService(UUID_SERVICE),其中UUID_SERVICE 就是之前過濾用的" 0000ffff-0000-1000-8000-00805F9B34FB "。在獲取完service之后,需要對進(jìn)行讀、寫通道的特征碼進(jìn)行校驗service.getCharacteristic(uuid),校驗完畢就可以執(zhí)行寫入描述字符gatt.writeDescriptor(notifyDesc)

  4. 由于writeDescriptor()后,會回調(diào)onDescriptorWrite,會在此進(jìn)行讀uuid的校驗和寫描述符的校驗,來確保設(shè)備的合法性來源,校驗完畢后可以對寫入狀態(tài)結(jié)果進(jìn)行回調(diào)判斷

  5. 最終再通過gatt.writeCharacteristic(characteristic)進(jìn)行特征碼寫入,會回調(diào)onCharacteristicWrite

 @SuppressLint("MissingPermission")
    private class GattCallback : BluetoothGattCallback() {
        /**
        @description 1.檢測gatt低功耗藍(lán)牙的連接狀態(tài),連接成功后,BlufiClientImpl 執(zhí)行了gatt.discoverServices()
        @param status 操作開關(guān)狀態(tài),為了區(qū)別于連接狀態(tài),用于處理斷開連接后不回調(diào)
        @param newState 連接狀態(tài) BluetoothProfile.STATE_CONNECTED 代表連接上
         */
        override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
            val address = gatt.device.address
            logD { "onMtuChanged address=$address, status=$status, newState=$newState" }
            // 當(dāng)前藍(lán)牙狀態(tài)連接成功,
            isConnecting = false
            if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
                isConnected = true
                flowCallbackList.forEach { it.onGattConnect(true) }
            } else {
                gatt.close()
                isConnected = false
                flowCallbackList.forEach {
                    it.onGattConnect(false)
                    it.onGattError(BlufiFlow.ERROR_GATT_CONNECT)
                }
            }
        }

        /**
        @description 2.mtu是指單包藍(lán)牙輸數(shù)據(jù)大小,當(dāng)mtu發(fā)生變化時回調(diào)
         */
        override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
            logD { "onMtuChanged status=$status, mtu=$mtu" }
            // 如果藍(lán)牙包大小請求失敗,則恢復(fù)默認(rèn)20字節(jié)
            if (status != BluetoothGatt.GATT_SUCCESS) {
                mBlufiClient?.setPostPackageLengthLimit(20)
            }
            // 代表通信同道已完成
            flowCallbackList.forEach { it.onBlufiPrepared() }
        }

        /**
        @description 3.由于onConnectionStateChange執(zhí)行了 gatt.discoverServices()方法,
         * BlufiClientImpl會進(jìn)行讀、寫藍(lán)牙特征碼校驗,并開啟特征碼監(jiān)聽回調(diào)如 onCharacteristicChanged(),最后會執(zhí)行此方法
         */
        override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
            logD { "onServicesDiscovered status=$status" }
            // 操作狀態(tài)已經(jīng)斷開,則進(jìn)行斷連
            if (status != BluetoothGatt.GATT_SUCCESS) {
                gatt.disconnect()
                flowCallbackList.forEach { it.onGattError(BlufiFlow.ERROR_GATT_DISCOVERED) }
            }
        }

        /**
        @description 4.在執(zhí)行寫入操作前,需要確保描述通道的連接性,之后才會執(zhí)行onCharacteristicWrite()
         */
        override fun onDescriptorWrite(
            gatt: BluetoothGatt,
            descriptor: BluetoothGattDescriptor,
            status: Int
        ) {
            logD { "onDescriptorWrite status=$status" }
            // 在執(zhí)行BlufiClientImpl的onServicesDiscovered,會創(chuàng)建讀通道uuid和通道描述uuid
            if (descriptor.uuid == BlufiParameter.UUID_NOTIFICATION_DESCRIPTOR &&
                descriptor.characteristic.uuid == BlufiParameter.UUID_NOTIFICATION_CHARACTERISTIC
            ) {
                logD { "Set notification enable ${status == BluetoothGatt.GATT_SUCCESS}" }
                // 進(jìn)行消息通知寫的成功或失敗
//                onWriteResult(msg)
            }
        }

        /**
        @description 5.進(jìn)行操作特征碼的寫入
         */
        override fun onCharacteristicWrite(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            status: Int
        ) {
            if (status != BluetoothGatt.GATT_SUCCESS) {
                gatt.disconnect()
                logD { "WriteChar error status=$status" }
            }
        }
    }

BlufiCallbackMain(blufi的回調(diào))

思路總結(jié):

  1. Gatt的連接成功后,在特征碼通道確認(rèn)后,會回調(diào)onGattPrepared

  2. 調(diào)用mBlufiClient?.negotiateSecurity()進(jìn)行通道加密處理

  3. 獲取設(shè)備能獲取到的wifi列表,mBlufiClient?.requestDeviceWifiScan(),成功會回調(diào)onDeviceScanResult

  4. 配網(wǎng)信息傳輸mBlufiClient?.configure(params),將配網(wǎng)信息傳輸給設(shè)備,會依次執(zhí)行onPostConfigureParams(代表信息提交成功) -> onDeviceStatusResponse(配網(wǎng)的成功回調(diào))-> onReceiveCustomData(接收到設(shè)備傳輸?shù)膉son結(jié)果)

@SuppressLint("MissingPermission")
private class BlufiCallbackMain : BlufiCallback() {

    /**
    @description 1.描述通道onDescriptorWrite/服務(wù)發(fā)現(xiàn)onServicesDiscovered時調(diào)用此方法,用于申明gatt已經(jīng)可以通信
     */
    override fun onGattPrepared(
        client: BlufiClient,
        gatt: BluetoothGatt,
        service: BluetoothGattService?,
        writeChar: BluetoothGattCharacteristic?,
        notifyChar: BluetoothGattCharacteristic?
    ) {
        if (service == null) {
            logD { "Discover service failed" }
            gatt.disconnect()
            return
        }
        if (writeChar == null) {
            logD { "Get write characteristic failed" }
            gatt.disconnect()
            return
        }
        if (notifyChar == null) {
            logD { "Get notification characteristic failed" }
            gatt.disconnect()
            return
        }
        // 通知可以進(jìn)行通信
        logD { "Discover service and characteristics success" }
        val mtu: Int = DEFAULT_MTU_LENGTH
        val requestMtu = gatt.requestMtu(mtu)
        if (!requestMtu) {
            logD { "Request mtu failed" }
            flowCallbackList.forEach { it.onBlufiPrepared() }
        }
    }

    /**
    @description 2.negotiateSecurity執(zhí)行加密的狀態(tài)回調(diào)
     */
    override fun onNegotiateSecurityResult(client: BlufiClient, status: Int) {
        // 加密結(jié)果回調(diào)
        flowCallbackList.forEach { it.onBlufiSecurity(status == STATUS_SUCCESS) }
    }

    /**
    @description 3.wifi列表掃描結(jié)果回調(diào)
     */
    override fun onDeviceScanResult(
        client: BlufiClient,
        status: Int,
        results: List<BlufiScanResult>
    ) {
        if (status == STATUS_SUCCESS) {
            val msg = StringBuilder()
            msg.append("Receive device scan result:\n")
            for (scanResult in results) {
                msg.append(scanResult.toString()).append("\n")
            }
            logD { msg.toString() }
            flowCallbackList.forEach { it.onBlufiScanWifiList(results) }
        } else {
            flowCallbackList.forEach { it.onBlufiError(BlufiFlow.ERROR_BLUFI_WIFI_LIST_SCAN) }
        }
    }

    /**
    @description 4.配網(wǎng)信息傳遞是否成功
     */
    override fun onPostConfigureParams(client: BlufiClient, status: Int) {
        logD { "Post configure params status = $status" }
        flowCallbackList.forEach { it.onBlufiConfigWifi(status == STATUS_SUCCESS) }
    }

    /**
    @description 5.配網(wǎng)成功的回調(diào)
     */
    override fun onDeviceStatusResponse(
        client: BlufiClient?,
        status: Int,
        response: BlufiStatusResponse
    ) {
        if (status == STATUS_SUCCESS) {
            logD { "generateValidInfo : ${response.generateValidInfo()}" }
        } else {
            logD { "Device status response error code=$status" }
        }
        flowCallbackList.forEach { it.onBlufiStatusResponse(status == STATUS_SUCCESS) }
    }

    /**
    @description 6.接收自定義消息
     */
    override fun onReceiveCustomData(client: BlufiClient, status: Int, data: ByteArray) {
        if (status == STATUS_SUCCESS) {
            val customStr = String(data)
            flowCallbackList.forEach { it.onBlufiReceive(customStr) }
            logD { "Receive custom data: $customStr" }
        } else {
            logD { "Receive custom data error, code=$status" }
            flowCallbackList.forEach { it.onBlufiError(BlufiFlow.ERROR_BLUFI_RECEIVE) }
        }
    }

    /**
    @description 7.接收錯誤回調(diào)
     */
    override fun onError(client: BlufiClient, errCode: Int) {
        logD { "Receive error code $errCode" }
        // gatt連接超時
        when (errCode) {
            CODE_GATT_WRITE_TIMEOUT -> {
                logD { "Gatt write timeout" }
                client.close()
                flowCallbackList.forEach { it.onGattConnect(false) }
            }
            else -> {
                flowCallbackList.forEach { it.onBlufiError(BlufiFlow.ERROR_BLUFI_COMMON) }
            }
        }
    }
}

BlufiManager(Blufi管理類)

思路整理:

  1. 通過addCallback(callback: BlufiFlow.Callback)和removeCallback(callback: BlufiFlow.Callback)進(jìn)行整體的回調(diào)暴露, BlufiFlow.Callback代表期望暴露的回調(diào)

  2. 執(zhí)行順序是,startConnect(開始連接)-> blufiSecurity(通道加密) -> blufiScanWifiList(Wifi掃描) -> blufiConfigWifi(配網(wǎng)傳輸) -> BlufiFlow.Callback回調(diào)各環(huán)節(jié)結(jié)果

object BlufiManager : BlufiFlow {
    private var mBlufiClient: BlufiClient? = null

    /**
    @description 藍(lán)牙寫超時
     */
    const val GATT_WRITE_TIMEOUT = 5000L

    /**
    @description 默認(rèn)mtu,即每包藍(lán)牙最大長度
     */
    const val DEFAULT_MTU_LENGTH = 512

    /**
    @description 要進(jìn)行連接的藍(lán)牙設(shè)備信息
     */
    var mDevice: BluetoothDevice? = null
    var flowCallbackList: MutableList<BlufiFlow.Callback> = mutableListOf()
    fun addCallback(callback: BlufiFlow.Callback) {
        flowCallbackList.add(callback)
    }

    fun removeCallback(callback: BlufiFlow.Callback){
        flowCallbackList.remove(callback)
    }

    /**
    @description 是否正在連接中
     */
    private var isConnecting = false

    /**
    @description 是否已連接上
     */
    var isConnected = false

    /**
    @description 獲取服務(wù)
     */
    private fun getBlufiClient(): BlufiClient? {
        if (mBlufiClient == null) {
            mBlufiClient = BlufiClient(BaseGlobalConst.app, mDevice).apply {
                setGattCallback(GattCallback())
                setBlufiCallback(BlufiCallbackMain())
                setGattWriteTimeout(GATT_WRITE_TIMEOUT)
            }
        }
        return mBlufiClient
    }

    override fun startConnect() {
        if (!isConnecting) {
            if (mBlufiClient != null) {
                mBlufiClient?.close()
                mBlufiClient = null
            }
            getBlufiClient()?.run {
                connect()
            }
        }
    }

    override fun disConnect() {
        isConnected = false
        flowCallbackList.clear()
        mBlufiClient?.close()
    }

    override fun blufiSecurity() {
        mBlufiClient?.negotiateSecurity()
    }

    override fun blufiScanWifiList() {
        mBlufiClient?.requestDeviceWifiScan()
    }

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

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

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