如果你正在把 uni-app 应用适配 HarmonyOS Next,并且踩过「蓝牙调用了、不报错、但回调永远不来」这个坑,这篇应该能帮你省掉一整天。文末附我把它做成开源插件的地址(免费 · MIT)。
一、现象:点了按钮,啥也没发生
先还原一下场景。你的 uni-app 里有一段标准的蓝牙逻辑,在 Android / iOS 上跑得好好的:
ts
uni.openBluetoothAdapter({
success: () => {
console.log('适配器已就绪');
uni.startBluetoothDevicesDiscovery({ success: () => {} });
},
fail: (err) => {
console.log('失败', err);
},
});
到了 HarmonyOS Next,诡异的事情发生了:
- 不报
fail:错误回调一次都不触发; - 也不走
success:成功回调同样石沉大海; - 和权限无关:系统蓝牙权限明明给了,甚至手机蓝牙是开着的;
- 表象就是「按钮点了没反应」,没有异常、没有堆栈、没有任何线索。
排查过的人都知道这种「静默失败」有多折磨人------你无法确定是代码错了、时机错了、还是设备没连上,因为它压根不给你反馈。
二、根因:不是 bug,是「官方未实现」
翻到 DCloud 的 uni-app API 兼容性表才会发现真相:整个蓝牙 / BLE API 族(openBluetoothAdapter、createBLEConnection、writeBLECharacteristicValue ......)在 HarmonyOS Next 一栏标的是「不支持」。
「不支持」在这套运行时里的具体表现,就是接口存在、可以调用、但内部既不 resolve 也不 reject------回调被悄悄丢弃。这就解释了为什么它不报错却也没反应。这是一个已知的平台差异(社区 issue #5365)。
要在鸿蒙上把蓝牙真正用起来,只能用华为原生的 @kit.ConnectivityKit。可存量 uni-app 项目的蓝牙代码遍布各页,逐处改写成 ArkTS 原生调用,工程量和回归成本都很高。
三、思路:补齐原生 + 语义对齐,两头都要
我做的 har-ble-compat 就是为了解决这个断层,它干两件事:
- 原生补齐 :用
@kit.ConnectivityKit实现适配器开关、扫描发现、GATT 建连/断连、服务与特征发现、读写、Notify/Indicate 订阅(含 CCCD 写)、连接状态事件、MTU 设置。 - 语义零改写兼容 :把回调语义(
success/fail/complete)、广播数据字段、错误码对齐到与 uni 完全同名同签名,直到可以把兼容对象整体注入第三方纯 JS 蓝牙 SDK 的程度。
关键取舍在第 2 点。市面上已有一些鸿蒙 BLE 的原生 UTS 插件,但它们暴露的是自定义 API 形状 ,业务代码仍要按它的签名重写。har-ble-compat 的目标是你几乎不用动业务代码 ------它长得就和 uni 的蓝牙 API 一模一样。
四、上手:三种接入形态,按需选
形态 A:同名兼容对象(推荐)
ts
// #ifdef APP-HARMONY
import { harmonyBle } from '@/uni_modules/har-ble-compat/ble-api';
// #endif
harmonyBle.openBluetoothAdapter({
success: () => {
harmonyBle.onBluetoothDeviceFound((res) => {
res.devices.forEach((d) => console.log(d.name, d.deviceId, d.RSSI));
});
harmonyBle.startBluetoothDevicesDiscovery({ success: () => {} });
},
fail: (err) => console.log('失败', err.errCode, err.errMsg),
});
对照上面「二、根因」里那段跑不通的代码------除了把 uni. 换成 harmonyBle.,回调结构、字段、错误码原封不动。
形态 B:全局 polyfill(存量项目改造最省事)
如果你不想满世界替换 uni. 前缀,可以在 App 启动早期调一次 installUniBluetoothPolyfill(),让插件接管 uni.* 的蓝牙调用。非鸿蒙端不受影响,鸿蒙端自动走原生桥。老项目基本零业务改动即可让原有蓝牙链路在鸿蒙上复活。
形态 C:扁平挂载
uni.harBleCompatXxx(...) 形式的扁平挂载,适合离线工程 / 不方便静态 import 原生导出的场景。
五、几个让人安心的细节
- 错误码不自造 :严格沿用 uni(微信小程序)蓝牙码表,
10001(未初始化/桥不在)、10006(连接已断开)等语义与 uni 一致,你原有的错误分支逻辑不用改。 - 自带权限声明 :插件目录内置
module.json5,声明USE_BLUETOOTH、DISCOVER_BLUETOOTH、ACCESS_BLUETOOTH三条权限;其中ACCESS_BLUETOOTH是用户授权,会在openBluetoothAdapter时主动拉起系统弹窗------顺带补齐了 uni 在鸿蒙缺失的授权时机。 - 不静默挂死 :非鸿蒙端或桥未就绪时,每个调用同步回可读
fail(errMsg里带现场诊断,比如injected=... / flat=... not found),而不是像原生那样吞掉回调。
六、诚实的边界(用前请看)
它不是「全能力对齐」,以下这些目前不支持或有限制,避免你踩完一个坑又踩另一个:
- iBeacon 相关 API:未实现。
onBluetoothAdapterStateChange:鸿蒙没有对应的「适配器状态变化」事件,需要你用getBluetoothAdapterState轮询替代。- 经典蓝牙 / 其它 profile(HFP、OPP 等):只做了 BLE。
- 平台 :仅 HarmonyOS Next(API 13 / 5.0.1 真机验证),Android / iOS / H5 / 小程序不实现(那些端本就直接用 uni 原生蓝牙就行)。
- 工具链:HBuilderX 4.61+。
filters 数组路径、read、setBLEMTU 等较新增的调用点,README 里标注了「待更多真机核对」,如果你正好用到这些边角能力,欢迎实测后反馈。
七、真机验证范围
扫描起停与设备发现、建连与会话监听、服务/特征发现、Notify 订阅(含 CCCD 写)、写特征值、Array ↔ ArrayBuffer 双向归一化、off 按引用移除------这些核心链路都在真实 BLE 外设(血压计、血糖尿酸仪一类的健康设备)上跑通过完整的「连接 → 读数据 → 断开 → 重连」。插件自带的三条蓝牙权限合并也在产物层 / 设备层 / 运行时三层实测坐实。
一点说明:DCloud 云端打包这条链路我本身没有实跑过,其权限合并走的是同一套 hvigor 机制;如果你走云打包遇到权限相关问题,可对照 README 的排障章节。
八、拿去用 / 一起改
插件已上架 DCloud 插件市场,免费、MIT 开源、含源码与可运行示例工程:
- 插件市场地址:uni 蓝牙 API 鸿蒙兼容层(BLE 桥接 + 语义垫片) - DCloud 插件市场
- 插件 ID:
har-ble-compat
如果这个项目帮你省下了逐页改写蓝牙代码的时间,欢迎点个 star / 试用反馈 / PR。特别是:
- 你手上的 BLE 外设比较冷门、连不上的;
filters/read/setMTU这些边角能力你恰好用到、想帮忙补真机验证的;
都欢迎在插件页留言或提 issue,我把兼容性一点点补齐。
如果这篇帮你避开了「按钮点了没反应」的坑,点个赞让更多人看到,也是对我继续维护的鼓励。