上个月接了个需求,要把现有 React Native 应用移植到鸿蒙上。业务功能里大多数页面迁移都很顺利,唯独“Bluetooth 扫描蓝牙设备”这个模块让我折腾了将近一周。不是鸿蒙蓝牙 API 本身多难调,而是“React Native + 鸿蒙”这个组合天然存在信息差:社区里搜不到现成方案,官方文档又偏向纯 ArkTS 工程,RN 桥接层的资料少得可怜。这篇博客就是把这段经历完整复盘一遍,从环境搭建、原生模块封装、桥接调用,到权限适配、踩坑排查,把所有能复现的步骤和能绕开的坑都写清楚。想搞 React Native 鸿蒙版硬件能力开发、或者正在做蓝牙相关适配的同行,可以直接拿来参考。
1. “RN + 鸿蒙 + 蓝牙”这个组合,难点到底在哪
1.1 现状:RN 不是不能跑鸿蒙,而是生态还算不上熟
React Native 官方目前没有直接发布鸿蒙版本,这一点要先有心理预期。目前能用的方案是社区和厂商共同维护的 react-native-harmony 适配库,它通过鸿蒙的 ArkTS 运行时加载 RN 的 JS Bundle,让原本的 React 组件逻辑可以在鸿蒙设备上运行。我建议在动手之前先花半天时间把这套适配机制读透,别急着写代码,因为你后面遇到的很多问题,本质上都不是业务代码的问题,而是适配层的行为和 Android/iOS 有差异。
从我实测的版本来看,react-native-harmony 对 RN 0.72 系列的兼容性相对稳定,鸿蒙侧需要 DevEco Studio 4.0 及以上版本。这里容易踩一个认知误区:有人以为鸿蒙全面兼容 Android 的 RN 库,直接把依赖拖过来就能跑,结果编译期各种报错。实际上,react-native-harmony 有自己独立的 npm 包名和配置方式,不能简单地把它当成 Android 原封不动的移植,第三方原生模块几乎都要做二次适配。
所以对团队来说,是否选择 RN 鸿蒙版,不只是技术验证的问题,还得评估团队有没有能力维护原生侧的兼容代码。如果你只是写纯 UI 页面,RN 鸿蒙版体验还行;一旦涉及蓝牙、Wi-Fi、摄像头这类系统能力,就必须做好自己封装原生模块的心理准备。
1.2 蓝牙功能为什么不能照搬社区库
在 Android 和 iOS 上,RN 社区有很成熟的蓝牙库,比如 react-native-ble-plx、react-native-bluetooth-classic。但在鸿蒙上,这些库基本不可用,原因很简单:它们底层依赖 Android 的 BluetoothAdapter 和 iOS 的 CoreBluetooth,这些 Native 实现并没有鸿蒙版本。鸿蒙系统提供的是自己的蓝牙 API(@ohos.bluetooth),很多概念相似,但接口签名、权限模型、回调机制都不相同,没法直接映射到现有库上。
这意味着你必须自己写一层原生桥接:在鸿蒙侧用 ArkTS 调用系统蓝牙能力,把扫描结果转换成 RN 侧能消费的数据,再通过 TurboModule 或 NativeEventEmitter 传给 JavaScript 层。这层桥接不复杂,但要考虑的细节很多,比如回调时序、内存释放、线程切换、权限结果回传等等,任何一个环节没处理好,出现的现象都是“扫描没反应”或“设备列表空白”。
还有一点容易被忽略:鸿蒙对应用后台使用蓝牙有比较强的限制,尤其当应用切到后台时,扫描可能被系统自动挂起。你在 RN 侧写的扫描逻辑会影响用户的真实体验。这些坑不调试到真机上根本发现不了。
1.3 我的落地策略:先打通最小链路,再补功能
面对这种“官方文档单薄、社区无现成方案”的局面,我的策略是先做垂直切片:不管 UI 和业务状态怎么设计,先把“鸿蒙原生扫描蓝牙设备 -> 数据传给 RN -> RN 渲染出设备列表”这条最小的技术链路跑通。跑通之后,再做权限、过滤、连接、异常处理这些旁支功能。
事实证明这个方法很有效。因为链路短,定位问题快,能快速验证“RN 鸿蒙版是否能满足硬件场景”这个核心问题。如果一开始就想着把完整项目迁移过来,遇到白屏或反射失败时,你很难判断问题出在桥接层还是业务层。对任何涉及到跨语言边界的功能,垂直切片都是最稳妥的启动方式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备:搭一个既跑得动 RN、又能碰硬件的鸿蒙工程
2.1 版本选型:用哪些版本能少折腾
我整理了一份经过实际验证的版本组合,不同时期可能略有出入,但大方向可以参考:
| 组件 | 建议版本 | 说明 |
|---|---|---|
| DevEco Studio | 4.0 Release 及以上 | 鸿蒙应用 IDE,必须官方渠道下载 |
| HarmonyOS SDK | API 10 或更高 | API 10 的蓝牙接口比较稳定 |
| React Native | 0.72.x | react-native-harmony 对这个版本适配最好 |
| react-native-harmony | 跟随 RN 版本选择配套版本 | 具体以 npm 包发布页为准 |
| Node.js | 18 及以上 | 打包 RN Bundle 需要 |
选型时有两点值得注意。第一,RN 版本不是越新越好,react-native-harmony 的适配进度一般会滞后于 RN 官方版本,用太新的 RN 版本可能导致桥接不兼容。第二,DevEco Studio 和 SDK 版本要保持一致,否则编译阶段经常会碰到莫名其妙的符号找不到。
2.2 集成步骤:从空工程到 RN 页面渲染
以我用过的流程为例,核心步骤大致如下:
- 用 DevEco Studio 新建一个空白的鸿蒙工程,包名按你自己的业务域名配置。
- 在工程根目录的
oh-package.json5中声明@react-native-oh/react-native-harmony依赖,并执行ohpm install。 - 把 RN 项目打包出的 JS Bundle 放到
entry/src/main/resources/rawfile目录下。华为官方文档里有专门说明:HarmonyOS 的 RN 运行时会默认从这个位置加载index.jsbundle。 - 在鸿蒙侧创建一个承载 RN 页面的组件(通常是
RNAbility或类似入口),并把刚才配好的 bundle 路径传进去。 - 编译运行,如果能看到 RN 组件渲染出的界面,就说明基础链路通了。
这里容易有个理解偏差:你不需要在鸿蒙工程里写完整的 RN 启动逻辑,而是把鸿蒙工程看作一个“壳”,RN Bundle 是资源文件。JS 侧的 AppRegistry.registerComponent 注册的组件名要与鸿蒙侧指定的组件名一致,否则就是白屏或找不到组件。
关于“可以打包成 hap、hsp、har 的鸿蒙 demo”这个点,我多说一句:如果你想做成可复用的模块,可以把原生桥接代码放在 har 里(静态共享库),把 feature 沙箱模块放在 hsp 里,最终入口应用编译成 hap。RN 桥接模块比较适合作为 har 提供给多个工程复用,因为 har 编译期就打进了主包,不会引入动态加载时序问题。
2.3 启动白屏:RN 鸿蒙版最容易遇到的第一道坎
“React Native 启动白屏”是搜出来的高频词,我在实际接入时也没逃过。白屏的根因通常有三个方向:
第一,Bundle 文件没找到或加载失败。HarmonyOS 侧配置的 bundle 路径和实际存放路径不一致,会导致白屏。这类问题在 hilog 日志里一般能看到明显的 loadBundle 报错,你把日志过滤关键字换成 ReactNative 或 RNInstance 就能看到线索。
第二,JS Bundle 是 Debug 模式还是 Release 模式的问题。调试时如果通过 Metro 服务加载 JS,那么设备必须能和开发机网络互通;Release 包如果打成离线 bundle,需要确保 bundle 里包含所有业务依赖。某种情况下,Metro 连接超时也会白屏,需要检查端口和网络。
第三,组件名不一致。鸿蒙侧加载时指定的组件名,和 JS 侧 AppRegistry.registerComponent('ModuleName', ...) 注册的名称必须完全一致,如果拼写或大小写不同,渲染不出任何东西。
排查白屏时,我建议按“日志优先”的原则来,不要反复杀进程试,把这个时间花在看日志定位上会更高效。
2.4 必须真机调试的蓝牙场景
鸿蒙模拟器对 UI 渲染支持得不错,但蓝牙扫描这种硬件能力在模拟器上基本不可用。我一开始偷懒想用模拟器验证扫描界面,结果发现模拟器压根没有蓝牙适配器,startBLEScan 调用没有任何回调。后来换了真机,一切才走上正轨。
拿真机调试时,建议准备至少两台设备:一台跑扫描,另一台作为可被发现的广播者(或者用现成的 BLE 外设,如心率计、智能灯、蓝牙 Beacon)。没有第二台设备,扫描功能无法验证,连“扫到设备”这个最基本的正反馈都拿不到。
3. 蓝牙扫描核心链路:原生 API、桥接模块与 RN 调用
3.1 鸿蒙侧蓝牙扫描能力速览
鸿蒙系统把所有蓝牙能力都收敛到了 @ohos.bluetooth 这个模块里。做 BLE 扫描,最核心的是下面几个 API:
bluetooth.startBLEScan(filters, options):启动 BLE 扫描,可以传过滤条件和扫描参数。bluetooth.stopBLEScan():停止扫描,不用传参。bluetooth.on('BLEDeviceFind', callback):注册发现设备的监听,每次扫描到设备都会回调,回调参数是一组 ScanResult。
ScanResult 里比较关键的信息是 deviceId、rssi(信号强度)、data(广播数据)和 deviceName(设备名称)。这里的 deviceId 在 BLE 场景下通常是 MAC 地址,但鸿蒙出于隐私保护,在某些版本返回的是系统分配的随机可解析地址,所以你最好不要把 deviceId 直接当永久身份标识来存。
另外,API 版本较新的工程里,官方更推荐用 Kit 方式导入:
typescript复制import { bluetooth } from '@kit.ConnectivityKit';
具体用 @ohos.bluetooth 还是 @kit.ConnectivityKit,取决于工程最低支持的 API 版本。两种方式底层能力相同。
3.2 用原生模块把扫描能力暴露给 RN
在 RN 鸿蒙版中,给 JS 层暴露原生能力通常有两种思路:一种是通过 TurboModule 定义接口,另一种是简单地注册一个 NativeModule。我这边用一个相对通用的方式示例。
鸿蒙侧新建 ScannerModule.ets,负责封装扫描逻辑,并通过事件发射器把设备数据发回 RN:
typescript复制import { TurboModule } from '@ohos.arch';
import { bluetooth } from '@kit.ConnectivityKit';
import { BusinessError } from '@kit.BasicServicesKit';
export class ScannerModule extends TurboModule {
private eventEmitter: any;
private onDeviceFind = (data: Array<bluetooth.ScanResult>) => {
// 通过基类提供的 emit 方法把数据抛回 JS 层
this.emit('onDeviceFound', data);
};
constructor(context: any) {
super(context);
bluetooth.on('BLEDeviceFind', this.onDeviceFind);
}
startScan(): void {
try {
bluetooth.startBLEScan([], { interval: 500, dutyMode: 0 });
} catch (e) {
const err = e as BusinessError;
console.error(`startBLEScan error: ${err.code} ${err.message}`);
}
}
stopScan(): void {
bluetooth.stopBLEScan();
}
getScanState(): boolean {
return bluetooth.getLastScanState();
}
}
实际操作时,你需要把 ScannerModule 注册到 RN 运行时里,这一般是通过实现一个 Package 类,然后在应用入口把 Package 添加进运行时上下文。不同版本的 react-native-harmony 注册方式有差异,我建议优先看你依赖的版本里自带的示例工程,模仿它的注册方式,比自己在网上找资料靠谱得多。
3.3 RN 侧封装:状态管理、回调与事件分发
原生侧把事件抛出来后,RN 侧需要接住。RN 里访问原生模块的标准方式是通过 NativeModules,监听原生事件用 NativeEventEmitter:
javascript复制import { NativeModules, NativeEventEmitter } from 'react-native';
const { ScannerModule } = NativeModules;
const scannerEmitter = new NativeEventEmitter(ScannerModule);
let deviceList = [];
const unsubscribe = scannerEmitter.addListener('onDeviceFound', (results) => {
results.forEach((item) => {
const index = deviceList.findIndex((d) => d.id === item.deviceId);
if (index >= 0) {
deviceList[index].rssi = item.rssi;
deviceList[index].lastSeen = Date.now();
} else {
deviceList.push({
id: item.deviceId,
name: item.deviceName || 'Unknown Device',
rssi: item.rssi,
rawData: item.data,
lastSeen: Date.now(),
});
}
});
// 触发 UI 更新
});
// 开始扫描
ScannerModule.startScan();
我建议把这部分代码独立成一个 useBluetoothScanner Hook,把设备列表、扫描状态、错误信息都收敛在里面。原因很简单:硬件能力和 UI 解耦后,后续如果要加自动重连、扫描超时、设备过滤,都只改 Hook 内部的逻辑,不会污染页面组件。
3.4 一次完整扫描的调用序列
从最终效果倒推,一次完整扫描的调用序列是:
- JS 侧调用
ScannerModule.startScan()。 - 鸿蒙原生侧执行
bluetooth.startBLEScan(),同时监听BLEDeviceFind事件。 - 系统扫描到设备后,通过
BLEDeviceFind回调把 ScanResult 数组抛给ScannerModule。 ScannerModule内部通过基类的emit方法把数据抛到 RN 的NativeEventEmitter。- RN 侧收到
onDeviceFound事件,更新设备列表状态。
这套异步链路最怕的就是时序问题:如果原生侧在发出事件时,RN 侧还没有注册监听器,那这批数据就丢了。因此建议在页面 useEffect 挂载时就注册事件监听,不要在点击“开始扫描”之后才注册。
4. 权限申请与系统适配:几个让扫描失败的隐藏原因
4.1 需要哪几项权限,什么时候申请
鸿蒙的权限模型比 Android 稍微清晰一点,但蓝牙相关权限同样绕不开权限分级和动态申请。
做 BLE 扫描,通常需要这三项:
| 权限 | 作用 | 分级 |
|---|---|---|
ohos.permission.USE_BLUETOOTH |
使用蓝牙功能 | normal(需要声明) |
ohos.permission.DISCOVER_BLUETOOTH |
发现、扫描周围蓝牙设备 | normal(需要动态申请) |
ohos.permission.LOCATION |
解析 BLE 广播中携带的位置相关信息 | user_grant(需要动态申请) |
比较容易被忽略的是定位权限。BLE 广播包中可能携带位置信息,系统在扫描时默认按敏感行为处理,所以即使你的业务完全不关心设备位置,也不代表可以跳过定位权限。在部分 API 版本上,没有定位权限时 BLEDeviceFind 回调会一直不触发,让人误以为是接口没调通。
权限声明的位置在鸿蒙工程的 module.json5 里,类似这样:
json复制{
"requestPermissions": [
{ "name": "ohos.permission.USE_BLUETOOTH" },
{ "name": "ohos.permission.DISCOVER_BLUETOOTH" },
{ "name": "ohos.permission.LOCATION", "reason": "用于扫描周边蓝牙设备", "usedScene": { "abilities": ["EntryAbility"] } }
]
}
动态申请权限则需要调用 abilityAccessCtrl 的 requestPermissionsFromUser,并在回调里拿到授权结果后再执行扫描。很多“扫描没反应”的问题,其实就是权限申请时序错了:先调了扫描,再弹权限框,用户即使点了允许,扫描也已经错过了。
4.2 定位服务与蓝牙开关:两个容易被忽略的前置条件
就算权限都申请了,定位服务本身没开,扫描也可能无疾而终。鸿蒙和 Android 在这一点上很相似:BLE 扫描结果会参考定位服务状态。我遇到过一个情况:真机上蓝牙已打开,权限全部授权,但扫描不到任何设备,最后发现是系统定位开关被关掉了。
所以封装扫描模块时,至少要做三件事:
- 检查蓝牙是否开启。鸿蒙里通过
bluetooth.isBluetoothEnabled()判断,未开启时引导用户打开。 - 检查定位服务是否开启。定位服务开关通常由系统设置控制,App 只能通过
geoLocationManager或相关接口查询状态,不能直接打开。 - 监听应用前后台切换。鸿蒙对后台扫描限制严格,应用退到后台后扫描回调会暂停,回到前台后需要主动恢复扫描。
这几个前置条件看似简单,但如果没有在扫描前统一校验,用户感知就是“怎么点都没反应”,体验很差。
4.3 系统版本与设备差异:哪些设备容易翻车
同样一份代码,在不同鸿蒙设备上表现可能差不少。根据我的测试经验,有两类设备最容易出问题:
一是 API 版本偏低的旧设备。API 9 以下的部分接口行为和 API 10 不一样,比如 ScanResult 里某些字段可能不存在,或者 startBLEScan 的 options 参数支持不到位。如果你要适配旧设备,最好的方式是参考鸿蒙官方 API Diff,然后把扫描模块的降级逻辑写好。
二是部分品牌对蓝牙权限有额外限制。我在某台国产平板上遇到过,动态申请权限弹窗只出现一次,用户点拒绝后,再从设置里打开蓝牙权限,系统居然不允许再次弹窗,这就需要在代码里检测授权状态并引导用户手动去设置里打开权限。
5. 踩坑实录:从“扫不到”到“稳定复现”的排查链路
5.1 模块注册失败:报错只有一行,原因藏在 hilog 里
首次在真机上跑的时候,RN 端调用 ScannerModule.startScan(),直接抛了一个类似 TurboModuleRegistry.getEnforcing(...): 'ScannerModule' could not be found 的错误。第一反应是原生模块没有成功注册。
排查链路是这样的:
- 在 DevEco Studio 中打开 Log 窗口,过滤关键字
ReactNative,先确认RNInstance是否正常初始化。 - 如果 RN 实例正常,继续过滤
ScannerModule,看看有没有 “register” 相关日志。有些版本的 react-native-harmony 会打印出已注册模块的列表。 - 检查你的
Package类有没有真正被系统加载。常见的错误是在两个不同的模块入口里重复注册,导致运行时只加载了其中一份。
最终我的问题出在:我在 har 模块里定义了原生包,但主工程引入时少了依赖配置,导致 ScannerModule 类没有被打进 hap 里。这种错误在编译期完全不报错,只会在运行时暴露。
5.2 权限申请成功却收不到回调:过滤器写成全空也没用
第二个坑是:权限全给了,蓝牙也打开了,startBLEScan 也调用了,但 onDeviceFound 一次都没触发。这个现象非常迷惑。
排查时我发现,问题出在过滤条件上。虽然我传了空数组,但鸿蒙某些版本对 BLEScanFilter 的处理和 Android 不太一样。当过滤条件 serviceUuid 或 deviceName 为空字符串而不是省略字段时,底层扫描逻辑会按严格的匹配规则执行,导致什么都扫不到。
正确的写法是:如果不做过滤,就完全不传 filter 参数,或者传入 undefined。如果要做过滤,也尽量只设置 deviceId 或 serviceUuid 两种常用模式,避免组合条件过多导致兼容性问题。
另外,ScanOptions 里的 interval 和 dutyMode 参数也会影响扫描灵敏度。我测试下来,interval 设置太大会错过一些广播间隔长的设备,dutyMode 选高性能模式(通常是 0)能显著提升发现速度,但更耗电。
5.3 扫描自动停止:超时参数与后台限制
还有一个高频问题:扫描是能扫到的,但扫着扫着就停了,尤其当页面停留在扫描结果列表,用户半天不操作时,设备列表不再更新。
定位后有两个原因。
第一个是 ScanOptions 里的 duration 字段。鸿蒙允许你指定扫描持续时长,默认情况下如果没有显式设置,某些系统版本会有一个隐含的 duration 上限,比如 30 秒。到了时间,扫描自动停止,但上层没有收到任何“停止”回调,UI 自然表现为“卡住”。
解决方法是:在 JS 侧做心跳检查,每隔一段时间对比最后一次收到设备回调的时间,如果超过预设阈值,就自动调用 ScannerModule.stopScan(),再重新 startScan()。或者,在鸿蒙侧调用 startBLEScan 时,显式设置 duration 为一个小于系统上限的值,并在扫描结束后通过事件通知 JS 层。
第二个原因是后台限制。应用进入后台后,系统可能会挂起 BLE 扫描,这是系统级策略,应用无法直接规避。开发阶段要验证这个行为时,可以把工程调成“不锁定屏幕”模式,并打开 keepScreenOn,避免误判为代码问题。
5.4 真机上偶发崩溃与内存问题
扫描功能稳定后,又出现偶发崩溃。日志显示崩溃点不在 JS 侧,而是在 ArkTS 原生侧,错误信息跟内存回收有关。
原因是:BLEDeviceFind 回调在短时间内会被高频触发,每次回调都创建一个包含多个对象的新数组,如果 JS 侧处理不及时,原生侧的事件队列就会堆积,导致内存压力增大。
解决思路有两个方向:
- 在原生侧做节流。例如,统一收集 500 毫秒内的 ScanResult,再一次性抛给 JS 层,减少事件频率。
- 在 RN 侧确保事件监听器处理逻辑足够轻量,比如只更新状态对象,不要在回调里做复杂的 Array 遍历或字符串拼接。
我最终采用了“原生侧节流 + JS 侧浅比较更新”的组合方案,扫描稳定性明显提升。
6. 扫描体验优化与下一步:从扫到设备到连接通信
6.1 扫描参数与去重策略
当设备列表能稳定刷新后,下一个问题就是:怎么让列表既全面又不失真的问题。
BLE 设备的广播周期各不相同,有些设备 20ms 广播一次,有些则要 200ms 以上。如果扫描间隔设置得太短,可能漏掉广播频率低的设备;设置太长,则设备列表里的 RSSI 更新滞后,用户移动设备时会觉得列表信号强度很卡。
我最终采用的参数是:扫描持续 4 秒、间隔 500 毫秒、dutyMode 选低功耗模式。这样既不会漏设备,又不会让手机发烫。去重策略上,deviceId 作为唯一标识,每次收到回调只更新 rssi 和 lastSeen,不重复新增条目。对于超过 10 秒没有更新过的设备,自动从列表里移除,避免显示一些已经走远的僵尸设备。
6.2 设备列表的 UI 与交互细节
RN 侧渲染设备列表时,一个很容易踩的点是:不要用 ScrollView 渲染所有设备。如果扫描到的设备数量较多(比如在办公室,能扫到几十个设备),ScrollView 会把所有子组件一次性渲染出来,明显掉帧。改成 FlatList 之后,长列表滚动性能基本没问题。
另外,建议在设备行上显示 RSSI 信号格数而不是纯数字。纯数字对普通用户没有意义,信号格数是符合直觉的表达方式。RSSI 到信号格的换算可以用一个简单函数:大于等于 -50dBm 显示满格,-50 到 -60 显示三格,-60 到 -70 显示两格,-70 以下显示一格,低于 -90 基本可以过滤掉。
6.3 接入 GATT 连接的前置准备
扫描稳定后,业务下一个需求几乎必然是连接设备。这里我建议在动手写连接代码前,先在原生侧确认你的目标设备服务 UUID。鸿蒙连接 BLE 外设的流程分为四步:
- 通过
bluetooth.createBLEConnection(deviceId)建立连接。 - 连接成功后,通过
bluetooth.getRemoteDevice(deviceId)拿到远端设备对象。 - 调用
device.getServices()或相关接口获取服务列表,查找目标服务 UUID。 - 获取特征值后,再进行读写或通知订阅操作。
RN 侧可以复用你已经封装好的 ScannerModule,在原生侧增加 connectDevice(deviceId)、discoverServices()、readCharacteristic(uuid) 这些方法,把整套 GATT 流程也暴露给 JS。
需要注意,GATT 连接是耗时操作,不要让 RN 侧通过同步调用来等待连接结果。比较好的做法是:原生侧发起连接后,通过回调事件通知 JS 侧“连接成功”或“连接失败”,JS 侧根据状态更新 UI。
6.4 后续可以怎么扩展
有了“RN 鸿蒙版扫描蓝牙设备”这套基础能力,后续扩展方向其实很清晰:
- 支持经典蓝牙(BR/EDR)扫描。鸿蒙的
@ohos.bluetooth也提供经典蓝牙设备发现 API,逻辑类似,只是回调事件不同。如果你的业务需要兼容老式蓝牙设备(比如蓝牙耳机配对),可以再加一层适配。 - 支持后台扫描提醒。鸿蒙为了省电会限制后台扫描,但如果你确实需要在后台持续监听设备出现,可以考虑用系统提供的申请长时任务等方式,只是落地时要认真评估功耗和用户隐私问题。
- 封装成跨平台统一 API。如果团队同时在维护 Android、iOS、鸿蒙三端,可以在 RN 层做一层统一抽象,内部按平台选择不同的实现,这样上层的业务代码就不用关心设备系统了。
根据我个人的经验,最值得先做的其实是“日志可视化”这一步。当扫描模块在真机上出现问题时,如果能通过一个悬浮按钮把原生侧日志(蓝牙开关状态、权限状态、扫描回调频率)实时展示出来,排查效率会高很多。很多看起来诡异的问题,其实就是某个前置条件不满足导致的。
这次做完下来,我最深的感受是:React Native 鸿蒙版做 UI 业务问题不大,但碰硬件能力时一定要做好心理准备,自己去补原生桥接。不要被“React Native 跨端”这个词迷惑,跨端不等于原生能力自动到处都有。先把最小链路打通,权限、过滤、后台限制这些细节逐一验证,整个过程虽然有点折腾,但跑通之后,这个能力就成了团队的资产,后续做类似功能会越来越快。
