You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Kotlin Multiplatform或原生Android实现可靠BLE连接?

跨平台BLE连接实现方案(基于KMP)

一、核心设计思路

  • 抽象BLE核心操作接口,彻底屏蔽各平台SDK的底层差异
  • 利用KMP的expect/actual机制实现平台专属逻辑,保证上层调用一致性
  • 封装扫描、连接、数据交互、通知等通用流程,降低业务层的开发复杂度

二、具体实现步骤

1. 定义通用BLE接口(Common模块)

// common/src/commonMain/kotlin/com/example/ble/BleManager.kt
expect class BleManager {
    fun startScan(callback: (BleDevice) -> Unit)
    fun stopScan()
    suspend fun connect(deviceId: String): Result<Unit>
    suspend fun writeCharacteristic(serviceUuid: String, charUuid: String, data: ByteArray): Result<Unit>
    suspend fun readCharacteristic(serviceUuid: String, charUuid: String): Result<ByteArray>
    fun setCharacteristicNotification(serviceUuid: String, charUuid: String, enabled: Boolean, callback: (ByteArray) -> Unit)
    fun disconnect()
}

data class BleDevice(val id: String, val name: String?)

2. Android平台实现(Android模块)

// android/src/androidMain/kotlin/com/example/ble/BleManager.kt
actual class BleManager actual constructor() {
    private val bluetoothManager = ContextCompat.getSystemService(
        ApplicationProvider.getApplicationContext(),
        BluetoothManager::class.java
    )!!
    private val bluetoothAdapter = bluetoothManager.adapter
    private var scanCallback: ScanCallback? = null
    private var gatt: BluetoothGatt? = null
    private var notificationCallback: ((ByteArray) -> Unit)? = null

    actual fun startScan(callback: (BleDevice) -> Unit) {
        if (!bluetoothAdapter.isEnabled) return
        scanCallback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
                super.onScanResult(callbackType, result)
                val device = BleDevice(result.device.address, result.device.name)
                callback(device)
            }
        }
        bluetoothAdapter.bluetoothLeScanner.startScan(scanCallback)
    }

    actual fun stopScan() {
        scanCallback?.let { bluetoothAdapter.bluetoothLeScanner.stopScan(it) }
        scanCallback = null
    }

    actual suspend fun connect(deviceId: String): Result<Unit> = suspendCancellableCoroutine { cont ->
        val device = bluetoothAdapter.getRemoteDevice(deviceId)
        gatt = device.connectGatt(
            ApplicationProvider.getApplicationContext(),
            false,
            object : BluetoothGattCallback() {
                override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
                    super.onConnectionStateChange(gatt, status, newState)
                    if (newState == BluetoothProfile.STATE_CONNECTED) {
                        gatt.discoverServices()
                    } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                        cont.resume(Result.failure(IllegalStateException("Connection failed")))
                    }
                }

                override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
                    super.onServicesDiscovered(gatt, status)
                    if (status == BluetoothGatt.GATT_SUCCESS) {
                        cont.resume(Result.success(Unit))
                    } else {
                        cont.resume(Result.failure(IllegalStateException("Discover services failed")))
                    }
                }
            }
        )
    }

    // 读写特征值、通知注册等方法可参照上述逻辑,基于BluetoothGatt实现
}

3. 其他平台适配(如iOS)

在KMP的iOS模块中,基于CoreBluetooth框架实现对应的actual方法,保持接口调用逻辑与Android端完全对齐,无需修改业务代码即可跨平台运行。

三、关键优化点

  • 连接稳定性:实现自动重连机制,处理蓝牙断开、信号弱、系统资源回收等异常场景
  • 扫描优化:对扫描结果去重,添加扫描时长限制,避免无效设备重复回调
  • 线程安全:将所有BLE操作切换至专用线程(Android用HandlerThread,iOS用DispatchQueue),避免主线程阻塞
  • 资源管理:在断开连接或销毁实例时,及时释放BluetoothGatt、ScanCallback等资源,防止内存泄漏

四、业务层使用示例

// 业务代码中直接调用通用接口,无需关心平台差异
val bleManager = BleManager()
val scope = CoroutineScope(Dispatchers.IO)

// 扫描BLE设备
bleManager.startScan { device ->
    println("Found device: ${device.name ?: "Unnamed"} (${device.id})")
}

// 连接指定设备
scope.launch {
    val connectResult = bleManager.connect("AA:BB:CC:DD:EE:FF")
    if (connectResult.isSuccess) {
        // 读取特征值
        val readResult = bleManager.readCharacteristic("SERVICE_UUID", "CHAR_UUID")
        readResult.onSuccess { data -> 
            println("Read data: ${data.decodeToString()}") 
        }

        // 注册特征值通知
        bleManager.setCharacteristicNotification("SERVICE_UUID", "CHAR_UUID", true) { data ->
            println("Received notification: ${data.decodeToString()}")
        }
    }
}

内容的提问来源于stack exchange,提问作者mxkmn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 04:57:09