KMP 结构适配鸿蒙:蓝牙与硬件通信理论知识
本文介绍了在Kotlin Multiplatform (KMP)框架下实现跨平台蓝牙通信的方案。文章分为两部分:第一部分抽象了经典蓝牙和BLE的核心接口,包括设备管理、扫描连接、数据传输等功能;第二部分展示了Android平台的具体实现。该方案支持可穿戴设备、智能家居、专业外设等多种应用场景,通过统一的接口屏蔽平台差异,为开发者提供简洁高效的蓝牙通信能力。
·

项目概述
蓝牙与硬件通信是现代应用与外部设备交互的重要方式。在 KMP 框架下,我们需要实现跨平台的蓝牙连接、数据传输和硬件控制功能。本文详细介绍了如何在 Kotlin Multiplatform 中实现高效的蓝牙系统,包括蓝牙扫描、配对、连接和鸿蒙系统特定的硬件通信方式。
典型的蓝牙/硬件通信场景包括:
- 可穿戴设备:手环、手表同步步数、心率、通知等数据;
- 物联网与智能家居:灯具、门锁、传感器等设备的控制与数据上报;
- 专业外设:条码枪、医疗设备、工业传感器等。
本篇的目标是:
- 在
commonMain中抽象出通用的BluetoothManager、BLEManager接口; - 封装经典蓝牙与 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用来在扫描阶段就过滤出感兴趣的设备,减少功耗和无效连接;BluetoothGattService、BluetoothGattCharacteristic等结构,则对应 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 端蓝牙实现的要点:
- 经典蓝牙部分使用
BluetoothAdapter、BluetoothSocket等类完成设备发现与 RFCOMM 连接,适合串口类设备; - BLE 部分则利用
BluetoothLeScanner和BluetoothGatt来实现低功耗蓝牙扫描和 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 + 鸿蒙 上打通扫描 → 连接 → 简单收发数据的流程;
- 再引入分片传输、设备配对管理等能力,模拟真实业务场景;
- 最终将整套蓝牙模块沉淀为可复用的库,在多个项目和终端形态中复用。
更多推荐




所有评论(0)