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

Ble扫描

Ble权限适配

权限问题点总结:

  1. 如何适配不同sdk版本,以确保获取到蓝牙权限?

  2. 如何过谷歌权限隐私审核要求?即以扫描标准进行权限申请?

清单文件配置

针对不同sdk版本权限,需要定制不同的清单文件,以确保清单文件的权限能被谷歌审核通过,因此需要按照官方要求进行适配。官方链接:https://developer.android.com/guide/topics/connectivity/bluetooth#Permissions

旧版本所需权限如下:

  1. android.permission.BLUETOOTH:用于蓝牙连接

  2. android.permission.BLUETOOTH_ADMIN: 用于蓝牙扫描发现,即找到其他蓝牙设备所需权限

  3. android.permission.ACCESS_COARSE_LOCATION:网络定位权限,只兼容android9 即sdk28以下

  4. android.permission.ACCESS_FINE_LOCATION: Gps精准定位,兼容所有版本

所以需要在清单文件中指令权限最大使用的sdk版本,android11 即sdk30,需要在旧权限上申明此信息

<?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">
    <!--旧版蓝牙权限 BLUETOOTH蓝牙连接,BLUETOOTH_ADMIN蓝牙扫描发现-->
    <uses-permission
        android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />
    <uses-permission
        android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" />

    <!--Android6-11低版本蓝牙权限兼容,ACCESS_FINE_LOCATION精准的GPS定位,ACCESS_COARSE_LOCATION网络定位-->
    <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">    <!--旧版蓝牙权限 BLUETOOTH蓝牙连接,BLUETOOTH_ADMIN蓝牙扫描发现-->    <uses-permission        android:name="android.permission.BLUETOOTH"        android:maxSdkVersion="30" />    <uses-permission        android:name="android.permission.BLUETOOTH_ADMIN"        android:maxSdkVersion="30" />    <!--Android6-11低版本蓝牙权限兼容,ACCESS_FINE_LOCATION精准的GPS定位,ACCESS_COARSE_LOCATION网络定位-->    <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 蓝牙设备通信和检测蓝牙是否打开

  2. android.permission.BLUETOOTH_SCAN: 扫描权限,最好申明不获取位置信息,即neverForLocation

  3. android.permission.BLUETOOTH_ADVERTISE: 当前手机蓝牙能被检测扫描权限

<?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 的蓝牙权限 如果您的应用与已配对的蓝牙设备通信或者获取当前手机蓝牙是否打开-->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <!--Android12 的蓝牙权限 如果您的应用查找蓝牙设备(如蓝牙低功耗 (BLE) 外围设备)-->
    <uses-permission
        android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation"
        tools:targetApi="s" />
    <!--Android12 的蓝牙权限 如果您的应用使当前设备可被其他蓝牙设备检测到-->
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
</manifest>

动态权限申请

总结要点:

  1. 区分android12以上和以下权限android12PermissionList 和 android12LowerPermissionList

  2. 获取当前设备sdk版本,并进行权限判断,分别为isAndroid12()和isPermissionGrant()

  3. 进行权限申请,使用官方的权限申请新方法ActivityResultContract

  4. 用safeRun()确保权限申请获得后,执行扫描代码

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

    /**
    @description 低版本权限
     */
    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 权限安全执行
     */
    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 是权限否授权
     */
    fun isPermissionGrant(): Boolean {
        return if (isAndroid12()) {
            android12PermissionList.hasPermission()
        } else {
            android12LowerPermissionList.hasPermission()
        }
    }

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

Ble扫描代码实现

蓝牙通识:

通常蓝牙扫描会根据蓝牙芯片进行区分,即为普通蓝牙和低功耗蓝牙,在4.0之前蓝牙只有一种模式,在4.0时则区分了普通蓝牙和低功耗蓝牙,4.1/4.2/5.0的蓝牙芯片即包含经典蓝牙(BT)又包含低功耗蓝牙(Ble)

技术说明:

针对google的蓝牙Api,区分了广播注册方式获取和使用蓝牙设配器BluetoothManager.BluetoothAdapter中的BluetoothLeScanner,基于api的新旧,笔者优先考虑了后者BluetoothLeScanner,因此在此不对广播注册方式的蓝牙扫描做过多的展开。如需广播注册方式,请参考官方链接:https://developer.android.com/guide/topics/connectivity/bluetooth#FindDevices

ble扫描所需的api分别为startScan(List <scanfilter>filters, ScanSettings settings, ScanCallback callback) 和 stopScan(ScanCallback callback), 由于需要对扫描的ble进行条件过滤和配置,所以用了一个辅助类去管理此职责</scanfilter>

BleScanHelper (蓝牙扫描辅助类)

思路整理:

  1. 扫描过程会扫到许多蓝牙设备,为了确保性能需要对此进行过滤,通常利用ScanFilter类,对service uuid进行过滤,其中蓝牙标准的uuid 就是"0000ffff-0000-1000-8000-00805F9B34FB",当然也可以针对名称等其他条件进行过滤

  2. 对于不同版本sdk,由于api的差异,我们需要配置不同的ScanSettings,对于android6.0以上的蓝牙版本,可以设置平衡模式和全匹配规则,以便更快捷扫描;对于低版本的智能使用低功耗模式用于节电

  3. 正式开始扫描startScan(),为了防止权限问题,对权限进行了二次校验,并且为了防止重复扫描,在executeScan()中打了标记isScanning,确保只会执行以此扫描

  4. 由于蓝牙扫描是耗时行为,所以通常需要定义超时概念,来结果蓝牙扫描,因此需要stopScan()停止此扫描行为

@SuppressLint("MissingPermission")
object BleScanHelper {

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

    /**
    @description 回调
     */
    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 开启蓝牙扫描
    @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 执行扫描操作
     */
    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(蓝牙超时回调)

用于统一处理蓝牙扫描超时的而定义出的回调

interface ScanTimeoutCallback {
    /**
    @description ble扫描列表回调
    */
    fun onScanList(list: List<BleDeviceInfo>)

    /**
    @description 扫描超时回调
     */
    fun onTimeout()
}

BleScanManager(蓝牙扫描管理类)

思路整理:

  1. 由于该类是管理类,为了确保线程安全,使用了单例方式进行校验申请

  2. 蓝牙扫描需要有超时时间配置和权限launch进行权限申请,因此定义了initConfig()方法

  3. 在执行扫描前,需要判断蓝牙是否打开,因此定义了isBleEnable(),利用BluetoothAdapter进行判断

  4. 开始扫描startScan(callback: ScanTimeoutCallback),主要通过ScanTimeoutCallback的封装,对超时情况和列表扫描结果进行回调

  5. stopScan()为了防止内存泄露,对此进行超时的取消和对回调的清除,同事为防止出现权限问题,也进行了权限校验

使用总结:

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 权限辅助类
     */
    private var permissionHelper: ActivityResultHelper<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>? =
        null

    /**
    @description 蓝牙列表结果
     */
    private val scanCallback = BleScanDefCallback {
        // 子线程回调,需要切换线程
        BaseGlobalConst.mainScope.launchOnUi {
            scanTimeoutCallback?.onScanList(it)
            // 列表不为空,移除超时
            if (it.isNotEmpty()){
                BaseGlobalConst.mainHandler.removeCallbacks(scanCancelRunnable)
            }
        }
    }

    /**
    @description 二次封装回调,用于处理扫描超时回调
    */
    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 打开蓝牙
     */
    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 当前蓝牙是否打开
     */
    fun isBleEnable(): Boolean = bluetoothAdapter?.isEnabled ?: false

    /**
    @description 开启蓝牙扫描,传递一个回调类
     */
    fun startScan(callback: ScanTimeoutCallback) {
        if (!isBleEnable()) return
        // 设置回调
        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设备配网

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

Blufi是用于解决设备配网的一个三方工具,由于硬件使用此套第三方交互,因此使用此库,但为了防止以后需要定制化开发,在此也提供了另外的解决方案,参考博客:https://cloud.tencent.com/developer/article/1875770

总结思考:

  1. 设备配网本质是一种蓝牙数据通信,所以需要用到官方的BluetoothGattCallback,使用BluetoothDevice.connectGatt()进行gatt连接通信

  2. 需要对BluetoothGattCallback的回调顺序有所理解,参考博客中清晰地备注了此方法

  3. blufi主要封装了建立完Gatt通道后,对信息的加密传输,对设备能访问到的wifi列表信息穿肚、对设备配网信息的结果回调处理,因此有了BlufiCallback

GattCallback(低功耗蓝牙回调)

思路整理:

  1. BluetoothDevice.connectGatt()进行低功耗蓝牙同道连接,连接成功会执行onConnectionStateChange,通常在连接成功时,需要调用gatt.discoverServices(),进行服务发现

  2. mtu指的是单包蓝牙传输数据大小,gatt.requestMtu(512),默认是512字节,如果请求失败再设置为20字节,这是由于低版本ble有的只支持20字节大小的传输,因此通常可以在onConnectionStateChange进行配置

  3. 当执行完毕gatt.discoverServices(),就会回调onServicesDiscovered,如果发现不了服务,则需要调用gatt.disconnect()进行断开连接;如果发现服务,则需要进行服务的获取gatt.getService(UUID_SERVICE),其中UUID_SERVICE 就是之前过滤用的" 0000ffff-0000-1000-8000-00805F9B34FB "。在获取完service之后,需要对进行读、写通道的特征码进行校验service.getCharacteristic(uuid),校验完毕就可以执行写入描述字符gatt.writeDescriptor(notifyDesc)

  4. 由于writeDescriptor()后,会回调onDescriptorWrite,会在此进行读uuid的校验和写描述符的校验,来确保设备的合法性来源,校验完毕后可以对写入状态结果进行回调判断

  5. 最终再通过gatt.writeCharacteristic(characteristic)进行特征码写入,会回调onCharacteristicWrite

 @SuppressLint("MissingPermission")
    private class GattCallback : BluetoothGattCallback() {
        /**
        @description 1.检测gatt低功耗蓝牙的连接状态,连接成功后,BlufiClientImpl 执行了gatt.discoverServices()
        @param status 操作开关状态,为了区别于连接状态,用于处理断开连接后不回调
        @param newState 连接状态 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" }
            // 当前蓝牙状态连接成功,
            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是指单包蓝牙输数据大小,当mtu发生变化时回调
         */
        override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
            logD { "onMtuChanged status=$status, mtu=$mtu" }
            // 如果蓝牙包大小请求失败,则恢复默认20字节
            if (status != BluetoothGatt.GATT_SUCCESS) {
                mBlufiClient?.setPostPackageLengthLimit(20)
            }
            // 代表通信同道已完成
            flowCallbackList.forEach { it.onBlufiPrepared() }
        }

        /**
        @description 3.由于onConnectionStateChange执行了 gatt.discoverServices()方法,
         * BlufiClientImpl会进行读、写蓝牙特征码校验,并开启特征码监听回调如 onCharacteristicChanged(),最后会执行此方法
         */
        override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
            logD { "onServicesDiscovered status=$status" }
            // 操作状态已经断开,则进行断连
            if (status != BluetoothGatt.GATT_SUCCESS) {
                gatt.disconnect()
                flowCallbackList.forEach { it.onGattError(BlufiFlow.ERROR_GATT_DISCOVERED) }
            }
        }

        /**
        @description 4.在执行写入操作前,需要确保描述通道的连接性,之后才会执行onCharacteristicWrite()
         */
        override fun onDescriptorWrite(
            gatt: BluetoothGatt,
            descriptor: BluetoothGattDescriptor,
            status: Int
        ) {
            logD { "onDescriptorWrite status=$status" }
            // 在执行BlufiClientImpl的onServicesDiscovered,会创建读通道uuid和通道描述uuid
            if (descriptor.uuid == BlufiParameter.UUID_NOTIFICATION_DESCRIPTOR &&
                descriptor.characteristic.uuid == BlufiParameter.UUID_NOTIFICATION_CHARACTERISTIC
            ) {
                logD { "Set notification enable ${status == BluetoothGatt.GATT_SUCCESS}" }
                // 进行消息通知写的成功或失败
//                onWriteResult(msg)
            }
        }

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

BlufiCallbackMain(blufi的回调)

思路总结:

  1. Gatt的连接成功后,在特征码通道确认后,会回调onGattPrepared

  2. 调用mBlufiClient?.negotiateSecurity()进行通道加密处理

  3. 获取设备能获取到的wifi列表,mBlufiClient?.requestDeviceWifiScan(),成功会回调onDeviceScanResult

  4. 配网信息传输mBlufiClient?.configure(params),将配网信息传输给设备,会依次执行onPostConfigureParams(代表信息提交成功) -> onDeviceStatusResponse(配网的成功回调)-> onReceiveCustomData(接收到设备传输的json结果)

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

    /**
    @description 1.描述通道onDescriptorWrite/服务发现onServicesDiscovered时调用此方法,用于申明gatt已经可以通信
     */
    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
        }
        // 通知可以进行通信
        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执行加密的状态回调
     */
    override fun onNegotiateSecurityResult(client: BlufiClient, status: Int) {
        // 加密结果回调
        flowCallbackList.forEach { it.onBlufiSecurity(status == STATUS_SUCCESS) }
    }

    /**
    @description 3.wifi列表扫描结果回调
     */
    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.配网信息传递是否成功
     */
    override fun onPostConfigureParams(client: BlufiClient, status: Int) {
        logD { "Post configure params status = $status" }
        flowCallbackList.forEach { it.onBlufiConfigWifi(status == STATUS_SUCCESS) }
    }

    /**
    @description 5.配网成功的回调
     */
    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.接收错误回调
     */
    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)进行整体的回调暴露, BlufiFlow.Callback代表期望暴露的回调

  2. 执行顺序是,startConnect(开始连接)-> blufiSecurity(通道加密) -> blufiScanWifiList(Wifi扫描) -> blufiConfigWifi(配网传输) -> BlufiFlow.Callback回调各环节结果

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

    /**
    @description 蓝牙写超时
     */
    const val GATT_WRITE_TIMEOUT = 5000L

    /**
    @description 默认mtu,即每包蓝牙最大长度
     */
    const val DEFAULT_MTU_LENGTH = 512

    /**
    @description 要进行连接的蓝牙设备信息
     */
    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 获取服务
     */
    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)
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容