在这里插入图片描述

项目概述

蓝牙与硬件通信是现代应用与外部设备交互的重要方式。在 KMP 框架下,我们需要实现跨平台的蓝牙连接、数据传输和硬件控制功能。本文详细介绍了如何在 Kotlin Multiplatform 中实现高效的蓝牙系统,包括蓝牙扫描、配对、连接和鸿蒙系统特定的硬件通信方式。

典型的蓝牙/硬件通信场景包括:

  • 可穿戴设备:手环、手表同步步数、心率、通知等数据;
  • 物联网与智能家居:灯具、门锁、传感器等设备的控制与数据上报;
  • 专业外设:条码枪、医疗设备、工业传感器等。

本篇的目标是:

  • commonMain 中抽象出通用的 BluetoothManagerBLEManager 接口;
  • 封装经典蓝牙与 BLE 的常见操作(扫描、连接、读写特征、通知)及监听机制;
  • 在 Android、iOS、鸿蒙、Web 等平台下,以 actual 方式对接各自的蓝牙 API。

第一部分:蓝牙通信核心概念

蓝牙管理架构

// commonMain/kotlin/com/example/bluetooth/BluetoothManager.kt
expect class BluetoothManager {
    suspend fun startScan(): Boolean
    suspend fun stopScan(): Boolean
    suspend fun connectDevice(deviceAddress: String): Boolean
    suspend fun disconnectDevice(): Boolean
    suspend fun sendData(data: ByteArray): Boolean
    suspend fun readData(): ByteArray?
    suspend fun getConnectedDevices(): List<BluetoothDevice>
    fun addListener(listener: BluetoothListener)
}

data class BluetoothDevice(
    val address: String,
    val name: String,
    val rssi: Int,
    val bondState: BondState,
    val deviceClass: Int,
    val uuids: List<String>
)

enum class BondState {
    NONE, BONDING, BONDED
}

interface BluetoothListener {
    fun onDeviceDiscovered(device: BluetoothDevice)
    fun onScanStarted()
    fun onScanStopped()
    fun onConnected(device: BluetoothDevice)
    fun onDisconnected()
    fun onDataReceived(data: ByteArray)
    fun onError(error: String)
}

这一部分抽象的是经典蓝牙(BR/EDR)或基础蓝牙连接能力:

  • BluetoothManager 提供扫描、连接、断开、收发数据等基础操作,并支持获取已连接设备列表;
  • BluetoothDevice 数据类统一描述设备的地址、名称、信号强度、配对状态、设备类型、服务 UUID 等信息;
  • BluetoothListener 将扫描结果、连接/断开事件、数据接收、错误信息等统一通过回调暴露给业务层。

通过这层抽象,业务代码只需要关心“有哪些设备”“当前连着哪个设备”“如何发送命令/接收数据”,而无需直接操作各平台的原生蓝牙 API。

BLE 架构

// commonMain/kotlin/com/example/bluetooth/BLEManager.kt
expect class BLEManager {
    suspend fun startScan(filters: List<ScanFilter>): Boolean
    suspend fun stopScan(): Boolean
    suspend fun connectGatt(deviceAddress: String): Boolean
    suspend fun discoverServices(): Boolean
    suspend fun readCharacteristic(uuid: String): ByteArray?
    suspend fun writeCharacteristic(uuid: String, data: ByteArray): Boolean
    suspend fun setNotification(uuid: String, enabled: Boolean): Boolean
    fun addListener(listener: BLEListener)
}

data class ScanFilter(
    val deviceName: String? = null,
    val deviceAddress: String? = null,
    val serviceUuid: String? = null,
    val manufacturerData: ByteArray? = null
)

data class BluetoothGattService(
    val uuid: String,
    val isPrimary: Boolean,
    val characteristics: List<BluetoothGattCharacteristic>
)

data class BluetoothGattCharacteristic(
    val uuid: String,
    val properties: Int,
    val permissions: Int,
    val descriptors: List<BluetoothGattDescriptor>
)

interface BLEListener {
    fun onServiceDiscovered(services: List<BluetoothGattService>)
    fun onCharacteristicRead(uuid: String, data: ByteArray)
    fun onCharacteristicWrite(uuid: String, success: Boolean)
    fun onCharacteristicNotification(uuid: String, data: ByteArray)
    fun onError(error: String)
}

BLE(低功耗蓝牙)相较经典蓝牙更加适合小数据量、低功耗、持续连接的场景,比如心率带、体脂秤、温湿度传感器等。

  • BLEManager 专注于 GATT 模型:扫描、连接 GATT、发现服务、读写特征、设置通知等;
  • ScanFilter 用来在扫描阶段就过滤出感兴趣的设备,减少功耗和无效连接;
  • BluetoothGattServiceBluetoothGattCharacteristic 等结构,则对应 BLE 的服务/特征/描述符等核心概念。

在实际设计 KMP 接口时,将经典蓝牙和 BLE 分开抽象,有利于:

  • 在只需要 BLE 的场景精简 API;
  • 在需要兼容老设备/特殊协议时灵活组合使用。

第二部分:平台特定实现

Android 蓝牙实现

// androidMain/kotlin/com/example/bluetooth/BluetoothManager.kt
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager as AndroidBluetoothManager
import android.bluetooth.BluetoothSocket
import android.content.Context
import android.bluetooth.BluetoothDevice as AndroidBluetoothDevice

actual class BluetoothManager(private val context: Context) {
    private val bluetoothManager = context.getSystemService(AndroidBluetoothManager::class.java)
    private val bluetoothAdapter = bluetoothManager?.adapter
    private val listeners = mutableListOf<BluetoothListener>()
    private var bluetoothSocket: BluetoothSocket? = null
    private var connectedDevice: AndroidBluetoothDevice? = null
    
    actual suspend fun startScan(): Boolean {
        return try {
            if (bluetoothAdapter?.isDiscovering == true) {
                bluetoothAdapter.cancelDiscovery()
            }
            listeners.forEach { it.onScanStarted() }
            bluetoothAdapter?.startDiscovery()
            true
        } catch (e: Exception) {
            listeners.forEach { it.onError(e.message ?: "Failed to start scan") }
            false
        }
    }
    
    actual suspend fun stopScan(): Boolean {
        return try {
            bluetoothAdapter?.cancelDiscovery()
            listeners.forEach { it.onScanStopped() }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun connectDevice(deviceAddress: String): Boolean {
        return try {
            val device = bluetoothAdapter?.getRemoteDevice(deviceAddress)
            bluetoothSocket = device?.createRfcommSocketToServiceRecord(
                java.util.UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
            )
            bluetoothSocket?.connect()
            connectedDevice = device
            listeners.forEach { it.onConnected(device?.toBluetoothDevice() ?: return false) }
            true
        } catch (e: Exception) {
            listeners.forEach { it.onError(e.message ?: "Failed to connect") }
            false
        }
    }
    
    actual suspend fun sendData(data: ByteArray): Boolean {
        return try {
            bluetoothSocket?.outputStream?.write(data)
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun readData(): ByteArray? {
        return try {
            val inputStream = bluetoothSocket?.inputStream ?: return null
            val buffer = ByteArray(1024)
            val bytes = inputStream.read(buffer)
            buffer.copyOf(bytes)
        } catch (e: Exception) {
            null
        }
    }
    
    actual fun addListener(listener: BluetoothListener) {
        listeners.add(listener)
    }
}

// Android BLE 实现
actual class BLEManager(private val context: Context) {
    private val bluetoothManager = context.getSystemService(AndroidBluetoothManager::class.java)
    private val bluetoothAdapter = bluetoothManager?.adapter
    private val bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner
    private val listeners = mutableListOf<BLEListener>()
    private var gatt: android.bluetooth.BluetoothGatt? = null
    
    actual suspend fun startScan(filters: List<ScanFilter>): Boolean {
        return try {
            val scanFilters = filters.map { filter ->
                android.bluetooth.le.ScanFilter.Builder().apply {
                    filter.deviceName?.let { setDeviceName(it) }
                    filter.deviceAddress?.let { setDeviceAddress(it) }
                    filter.serviceUuid?.let { 
                        setServiceUuid(android.os.ParcelUuid(java.util.UUID.fromString(it)))
                    }
                }.build()
            }
            
            bluetoothLeScanner?.startScan(
                scanFilters,
                android.bluetooth.le.ScanSettings.Builder()
                    .setScanMode(android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_LATENCY)
                    .build(),
                object : android.bluetooth.le.ScanCallback() {
                    override fun onScanResult(callbackType: Int, result: android.bluetooth.le.ScanResult) {
                        // Handle scan result
                    }
                }
            )
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun connectGatt(deviceAddress: String): Boolean {
        return try {
            val device = bluetoothAdapter?.getRemoteDevice(deviceAddress)
            gatt = device?.connectGatt(
                context,
                false,
                object : android.bluetooth.BluetoothGattCallback() {
                    override fun onServicesDiscovered(gatt: android.bluetooth.BluetoothGatt, status: Int) {
                        if (status == android.bluetooth.BluetoothGatt.GATT_SUCCESS) {
                            val services = gatt.services.map { it.toBluetoothGattService() }
                            listeners.forEach { it.onServiceDiscovered(services) }
                        }
                    }
                    
                    override fun onCharacteristicRead(
                        gatt: android.bluetooth.BluetoothGatt,
                        characteristic: android.bluetooth.BluetoothGattCharacteristic,
                        status: Int
                    ) {
                        if (status == android.bluetooth.BluetoothGatt.GATT_SUCCESS) {
                            listeners.forEach { 
                                it.onCharacteristicRead(characteristic.uuid.toString(), characteristic.value)
                            }
                        }
                    }
                }
            )
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun discoverServices(): Boolean {
        return try {
            gatt?.discoverServices()
            true
        } catch (e: Exception) {
            false
        }
    }
}

Android 端蓝牙实现的要点:

  • 经典蓝牙部分使用 BluetoothAdapterBluetoothSocket 等类完成设备发现与 RFCOMM 连接,适合串口类设备;
  • BLE 部分则利用 BluetoothLeScannerBluetoothGatt 来实现低功耗蓝牙扫描和 GATT 通信;
  • 示例中展示了如何将扫描结果、服务发现、特征读写等事件转换为公共的监听接口。

工程实践中需要特别注意:

  • 不同 Android 版本对蓝牙和扫描权限(包括位置信息权限)的要求差异;
  • 蓝牙开关状态、设备兼容性(部分设备仅支持 BR/EDR 或仅支持 BLE);
  • 与系统蓝牙设置界面之间的协作(提示用户打开蓝牙、配对设备等)。

iOS 蓝牙实现

// iosMain/kotlin/com/example/bluetooth/BluetoothManager.kt
import platform.CoreBluetooth.*
import platform.Foundation.*

actual class BluetoothManager {
    private val centralManager = CBCentralManager()
    private val listeners = mutableListOf<BluetoothListener>()
    
    actual suspend fun startScan(): Boolean {
        return try {
            centralManager.scanForPeripheralsWithServices(null, null)
            listeners.forEach { it.onScanStarted() }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun stopScan(): Boolean {
        return try {
            centralManager.stopScan()
            listeners.forEach { it.onScanStopped() }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun connectDevice(deviceAddress: String): Boolean {
        return try {
            // iOS uses UUID instead of address
            true
        } catch (e: Exception) {
            false
        }
    }
}

// iOS BLE 实现
actual class BLEManager {
    private val centralManager = CBCentralManager()
    private val listeners = mutableListOf<BLEListener>()
    
    actual suspend fun startScan(filters: List<ScanFilter>): Boolean {
        return try {
            val serviceUuids = filters.mapNotNull { filter ->
                filter.serviceUuid?.let { 
                    CBUUID(string = it)
                }
            }
            
            centralManager.scanForPeripheralsWithServices(
                if (serviceUuids.isNotEmpty()) serviceUuids else null,
                null
            )
            true
        } catch (e: Exception) {
            false
        }
    }
}

第三部分:编译后的代码形式

JavaScript 编译版本

// 蓝牙管理器 (JavaScript/Web - Web Bluetooth API)
class BluetoothManager {
    constructor() {
        this.listeners = [];
        this.device = null;
        this.server = null;
    }
    
    async startScan() {
        try {
            this.device = await navigator.bluetooth.requestDevice({
                acceptAllDevices: true
            });
            this.listeners.forEach(l => l.onDeviceDiscovered({
                address: this.device.id,
                name: this.device.name,
                rssi: 0,
                bondState: 'NONE',
                deviceClass: 0,
                uuids: []
            }));
            return true;
        } catch (error) {
            this.listeners.forEach(l => l.onError(error.message));
            return false;
        }
    }
    
    async connectDevice(deviceAddress) {
        try {
            this.server = await this.device.gatt.connect();
            this.listeners.forEach(l => l.onConnected({
                address: this.device.id,
                name: this.device.name
            }));
            return true;
        } catch (error) {
            this.listeners.forEach(l => l.onError(error.message));
            return false;
        }
    }
    
    async sendData(data) {
        try {
            // Send data via BLE characteristic
            return true;
        } catch (error) {
            return false;
        }
    }
    
    addListener(listener) {
        this.listeners.push(listener);
    }
}

// BLE 管理器
class BLEManager {
    async discoverServices() {
        try {
            const services = await this.server.getPrimaryServices();
            const discoveredServices = [];
            
            for (const service of services) {
                const characteristics = await service.getCharacteristics();
                discoveredServices.push({
                    uuid: service.uuid,
                    isPrimary: true,
                    characteristics: characteristics.map(c => ({
                        uuid: c.uuid,
                        properties: 0
                    }))
                });
            }
            
            return discoveredServices;
        } catch (error) {
            return [];
        }
    }
}

第四部分:鸿蒙系统实际应用

鸿蒙蓝牙实现

// ohos/kotlin/com/example/bluetooth/BluetoothManager.kt
import ohos.bluetooth.BluetoothManager as OhosBluetoothManager
import ohos.bluetooth.BluetoothDevice as OhosBluetoothDevice
import ohos.app.Context

actual class BluetoothManager(private val context: Context) {
    private val bluetoothManager = context.getSystemService(OhosBluetoothManager::class.java)
    private val listeners = mutableListOf<BluetoothListener>()
    
    actual suspend fun startScan(): Boolean {
        return try {
            bluetoothManager.startBluetoothDiscovery()
            listeners.forEach { it.onScanStarted() }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun stopScan(): Boolean {
        return try {
            bluetoothManager.cancelBluetoothDiscovery()
            listeners.forEach { it.onScanStopped() }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun connectDevice(deviceAddress: String): Boolean {
        return try {
            val device = bluetoothManager.getRemoteDevice(deviceAddress)
            device?.connect()
            listeners.forEach { it.onConnected(device?.toBluetoothDevice() ?: return false) }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun sendData(data: ByteArray): Boolean {
        return try {
            // Send data via Bluetooth socket
            true
        } catch (e: Exception) {
            false
        }
    }
}

// 鸿蒙 BLE 实现
actual class BLEManager(private val context: Context) {
    private val bluetoothManager = context.getSystemService(OhosBluetoothManager::class.java)
    private val listeners = mutableListOf<BLEListener>()
    
    actual suspend fun startScan(filters: List<ScanFilter>): Boolean {
        return try {
            bluetoothManager.startBluetoothScan()
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun connectGatt(deviceAddress: String): Boolean {
        return try {
            val device = bluetoothManager.getRemoteDevice(deviceAddress)
            device?.connectGatt(context, false, object : ohos.bluetooth.BluetoothGattCallback() {
                override fun onServicesDiscovered(gatt: ohos.bluetooth.BluetoothGatt, status: Int) {
                    if (status == ohos.bluetooth.BluetoothGatt.GATT_SUCCESS) {
                        val services = gatt.services.map { it.toBluetoothGattService() }
                        listeners.forEach { it.onServiceDiscovered(services) }
                    }
                }
            })
            true
        } catch (e: Exception) {
            false
        }
    }
}

第五部分:高级功能

蓝牙配对管理

// commonMain/kotlin/com/example/bluetooth/PairingManager.kt
class PairingManager(private val bluetoothManager: BluetoothManager) {
    private val listeners = mutableListOf<PairingListener>()
    
    suspend fun pairDevice(deviceAddress: String): Boolean {
        return try {
            // Initiate pairing process
            listeners.forEach { it.onPairingStarted(deviceAddress) }
            true
        } catch (e: Exception) {
            listeners.forEach { it.onPairingFailed(deviceAddress, e.message ?: "Unknown error") }
            false
        }
    }
    
    suspend fun unpairDevice(deviceAddress: String): Boolean {
        return try {
            // Remove pairing
            listeners.forEach { it.onUnpaired(deviceAddress) }
            true
        } catch (e: Exception) {
            false
        }
    }
}

interface PairingListener {
    fun onPairingStarted(deviceAddress: String)
    fun onPairingFinished(deviceAddress: String)
    fun onPairingFailed(deviceAddress: String, error: String)
    fun onUnpaired(deviceAddress: String)
}

// 蓝牙数据传输管理
class BluetoothDataTransfer(private val bluetoothManager: BluetoothManager) {
    suspend fun sendLargeData(data: ByteArray, chunkSize: Int = 1024): Boolean {
        return try {
            var offset = 0
            while (offset < data.size) {
                val chunk = data.sliceArray(offset until minOf(offset + chunkSize, data.size))
                bluetoothManager.sendData(chunk)
                offset += chunkSize
            }
            true
        } catch (e: Exception) {
            false
        }
    }
}

在这里插入图片描述

总结

蓝牙与硬件通信是现代应用与外部设备交互的重要方式。通过 KMP 框架,我们可以在共享代码中定义统一的蓝牙接口,同时在各平台上利用原生 API 实现高效的通信。鸿蒙系统提供了完整的蓝牙 API,使得跨平台实现相对平顺。关键是要合理处理蓝牙连接生命周期、数据传输和错误恢复。

在工程实践中,你可以重点思考:

  • 公共层抽象的蓝牙接口是否已经覆盖了目标设备的主要通信模式(经典蓝牙 / BLE / 混合);
  • 各平台蓝牙栈的差异对连接稳定性和功耗的影响,以及如何通过重连、心跳等机制提高可靠性;
  • 如何在确保安全(配对、加密、鉴权)的前提下,简化用户操作流程。

建议的实践路径:

  • 先选取一个常见蓝牙设备(如蓝牙串口模块、BLE 传感器),在 Android + 鸿蒙 上打通扫描 → 连接 → 简单收发数据的流程;
  • 再引入分片传输、设备配对管理等能力,模拟真实业务场景;
  • 最终将整套蓝牙模块沉淀为可复用的库,在多个项目和终端形态中复用。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐