Android_BLE开发——读写

本文结合 BLE 开发核心原理与实战经验,深入剖析 Android 蓝牙读写的实现细节,涵盖特征值读写、命令队列实现、通知管理、多线程处理方案,助你打造简洁高效的实现方案。

一、特征读写

注意点:(参见BluetoothGatt.java源码)

  1. 读写操作是异步的。
  2. 一次只能执行一个异步操作。

1.1、特征值读写机制

BLE 的读写操作本质是异步的,需要通过回调接收结果。示例示例:

kotlin 复制代码
// 特征值读取
fun readCharacteristic(characteristic: BluetoothGattCharacteristic) {
    if (!checkReadPermission(characteristic)) return
    
    enqueueCommand(BleCommand.Read(characteristic).apply {
        onExecute = { gatt ->
            if (!gatt.readCharacteristic(characteristic)) {
                Log.e(TAG, "读取特征值失败")
                completeCommand()
            }
        }
    })
}

// 特征值写入(带重试机制)
fun writeCharacteristic(
    characteristic: BluetoothGattCharacteristic,
    data: ByteArray,
    writeType: Int = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
) {
    characteristic.apply {
        value = data
        this.writeType = writeType
    }

    enqueueCommand(BleCommand.Write(characteristic).apply {
        maxRetries = 3
        onExecute = { gatt ->
            if (!gatt.writeCharacteristic(characteristic)) {
                Log.e(TAG, "写入特征值失败")
                completeCommand()
            }
        }
    })
}

二、命令队列实现

使用协程队列管理异步操作(示例代码):

kotlin 复制代码
private val commandQueue = LinkedList<BleCommand>()
private var isExecuting = AtomicBoolean(false)

sealed class BleCommand {
    abstract fun execute(gatt: BluetoothGatt)
    
    data class Read(val characteristic: BluetoothGattCharacteristic) : BleCommand()
    data class Write(val characteristic: BluetoothGattCharacteristic) : BleCommand()
    // 其他命令类型...
}

private fun enqueueCommand(command: BleCommand) {
    synchronized(commandQueue) {
        commandQueue.offer(command)
        if (!isExecuting.get()) processNextCommand()
    }
}

private fun processNextCommand() {
    CoroutineScope(Dispatchers.IO).launch {
        val cmd = synchronized(commandQueue) { commandQueue.poll() }
        cmd?.let {
            isExecuting.set(true)
            it.execute(bluetoothGatt)
        }
    }
}

fun completeCommand() {
    isExecuting.set(false)
    processNextCommand()
}

三、通知管理和数量限制

3.1、通知管理

  • 调用setCharacteristicNotification方法,将 12 作为 16位的 无符号整形,写入特征值的客户特性配置(CCC)描述符。CCC 描述符具有短的 UUID 2902

  • 你需要知道为什么写入 12 ,这是因为底层有 NotificationsIndications 。接收到的 Indications 需要通过蓝牙栈来确认,而 Notifications 不需要确认。通过使用 Indications,设备将有效地知道已经收到数据,并可以从本地存储中删除数据。

  • 1 用于打开 Notifications ,值2用于 Indications。要关闭它们,请写 0

  • 两种(NotificationsIndications)情况并没有什么不同: 在这两种情况下,你将只会收到一个新的字节数组,而栈会在接收到指示时自动处理确认。

示例代码:

kotlin 复制代码
// 启用通知
fun enableNotifications(characteristic: BluetoothGattCharacteristic) {
    val cccdUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
    characteristic.getDescriptor(cccdUuid)?.let { descriptor ->
        bluetoothGatt.setCharacteristicNotification(characteristic, true)
        
        val value = when {
            characteristic.isIndicatable() -> BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
            characteristic.isNotifiable() -> BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
            else -> return
        }
        
        enqueueCommand(BleCommand.WriteDescriptor(descriptor, value))
    }
}

// 处理通知回调
override fun onCharacteristicChanged(
    gatt: BluetoothGatt,
    characteristic: BluetoothGattCharacteristic
) {
    val valueCopy = characteristic.value.copyOf() // 必须复制数据
    CoroutineScope(Dispatchers.Default).launch {
        processNotification(valueCopy)
    }
}

3.2、通知数量限制

  • 通知数量在不同版本中是不一致的,需要兼容。

示例代码:

kotlin 复制代码
// 维护通知集合
private val activeNotifications = Collections.synchronizedSet(mutableSetOf<UUID>())

fun enableLimitedNotifications(characteristic: BluetoothGattCharacteristic) {
    if (activeNotifications.size >= getMaxNotifications()) {
        disableOldestNotification()
    }
    activeNotifications.add(characteristic.uuid)
    enableNotifications(characteristic)
}

// 获取最大通知数量
private fun getMaxNotifications(): Int {
    return when {
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> 15
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 -> 7
        else -> 4
    }
}

四、多线程处理方案

示例代码:

kotlin 复制代码
// 使用协程管理线程
private val bleScope = CoroutineScope(Dispatchers.IO + Job())

// 特征值读取示例(带线程安全)
fun safeReadCharacteristic(characteristic: BluetoothGattCharacteristic) {
    bleScope.launch {
        withContext(Dispatchers.IO) {
            if (!bluetoothGatt.readCharacteristic(characteristic)) {
                Log.e(TAG, "读取操作失败")
                return@withContext
            }
            
            // 使用挂起函数等待回调
            val result = suspendCancellableCoroutine<ByteArray?> { cont ->
                readCallback = { data -> 
                    cont.resume(data)
                }
            }
            
            result?.let { processData(it) }
        }
    }
}

// 使用独立 HandlerThread
private val bleHandler by lazy {
    HandlerThread("BLE-Handler").apply { start() }.looper.let { 
        Handler(it) 
    }
}

fun postToBleThread(action: () -> Unit) {
    bleHandler.post {
        action()
        Unit
    }
}

五、最佳实践建议

  • 数据拷贝原则

    • 在通知回调中立即复制字节数组
  • 线程隔离

    • Binder 线程仅用于事件传递
    • 使用独立线程处理业务逻辑
  • 队列优化

    • 实现优先级队列处理紧急命令
    • 添加超时机制(建议 30s)
  • 资源管理

    • 及时关闭不需要的通知
    • 在 onConnectionStateChange 中清理资源
  • 错误处理

    • 区分 GATT 错误类型(status code)
    • 实现自动重连机制

错误处理示例代码:

kotlin 复制代码
// 错误处理示例
override fun onCharacteristicWrite(
    gatt: BluetoothGatt,
    characteristic: BluetoothGattCharacteristic,
    status: Int
) {
    when (status) {
        BluetoothGatt.GATT_SUCCESS -> {
            Log.d(TAG, "写入成功")
        }
        BluetoothGatt.GATT_WRITE_NOT_PERMITTED -> {
            Log.e(TAG, "无写入权限")
        }
        else -> {
            Log.e(TAG, "写入失败,错误码:$status")
            if (shouldRetry(status)) {
                retryCurrentCommand()
            }
        }
    }
}

建议结合具体业务场景封装基础操作,并通过单元测试验证关键流程。

更多分享

  1. Android_BLE开发------初识BLE
  2. Android_BLE开发------扫描
  3. Android_BLE开发------连接
  4. Android_BLE开发------绑定
  5. Android_BLE开发------优化
相关推荐
双鱼大猫29 分钟前
Android Dalvik虚拟机内存参数优化指南
android
双鱼大猫34 分钟前
深入解析adb install安装全流程
android
顾林海1 小时前
Flutter Dart 运算符全面解析
android·前端
小白马丶1 小时前
Jetpack源码解读(一)——Lifecycle
android·android jetpack
&有梦想的咸鱼&1 小时前
Android Glide 请求构建与管理模块原理深入剖析
android·glide
苏苏码不动了1 小时前
Android MVC、MVP、MVVM三种架构的介绍和使用。
android·架构·mvc
万里鹏程转瞬至2 小时前
开源项目介绍:Native-LLM-for-Android
android·深度学习·开源·大模型
QING6184 小时前
Android_BLE 基于Jetpack Bluetooth实现文件传输指南。
android·kotlin·app
_一条咸鱼_5 小时前
Android Glide 的显示与回调模块原理分析
android
_一条咸鱼_5 小时前
Android Glide 图片解码与转换模块原理分析
android