ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

React Native鸿蒙版蓝牙扫描:桥接层设计与踩坑实战

React Native鸿蒙版蓝牙扫描:桥接层设计与踩坑实战 最近帮团队把一个 IoT 调试工具从 Android 迁到鸿蒙上遇到一个特别典型的需求扫描周围的蓝牙设备。这个功能在 Android 上用原生 API 半小时就能跑通但切到 React Native 鸿蒙版RNOH环境里要处理的细节比想象中多得多。网上关于鸿蒙蓝牙的资料本来就少RN 桥接层的资料更少我几乎是一路踩坑过来的。把整个项目从设计到落地的过程整理成这篇博文给正在做 RNOH 适配、或者接智能硬件类需求的开发者一点参考。先交代一下背景我们团队的技术栈是 React Native产品主要面向低功耗蓝牙设备调试场景需要实时扫描附近设备、展示设备名和信号强度。鸿蒙生态发展到现在纯 ArkTS 开发当然是主线但对存量 RN 团队来说全量重写成本太高所以选了 React Native for OpenHarmony 这条路。项目本身的输出物很简单就是一个隐藏在 RN 页面里的蓝牙扫描器但这个看似简单的功能背后牵扯到权限模型、原生桥接、事件回调、线程切换、扫描策略一堆问题。这篇文章会从方案选型开始把桥接层的设计思路、原生代码怎么写、JS 侧怎么封装、以及我在实际调试中遇到的白屏、扫描超时、回调丢失等问题全部摊开来讲。代码部分是基于我项目里的精简示意不同 SDK 版本下导入路径和参数名可能略有出入跑之前一定以官方文档为准。1. 项目拆解RN鸿蒙版蓝牙扫描到底要做什么1.1 需求本质不是写一个页面而是打通三层技术栈这个项目表面上看起来是“一个 RN 页面 一个扫描按钮”但剥开来看它其实横跨了三层技术栈RN 的 JS 层、RNOH 的模块通信层、鸿蒙原生蓝牙 API 层。任何一层掉链子功能都跑不起来。先说为什么不能绕开原生层。React Native 本身没有内置蓝牙模块Web Bluetooth API 在鸿蒙 WebView 里也不支持所以唯一的路径就是通过 NativeModule 把 JS 调用透传到鸿蒙原生代码。也就是说扫描蓝牙设备的核心逻辑其实是在 ArkTS 代码里完成的RN 只负责 UI 交互和状态展示。我在接到这个需求时先列了一张依赖清单鸿蒙系统蓝牙状态检查是否支持 BLE、是否开启蓝牙权限动态申请涉及个人隐私和位置权限BLE 扫描启动与停止扫描结果实时回调名称、设备 ID、RSSI、广播数据扫描状态变化监听蓝牙开关、系统级中断这张清单基本决定了项目的边界。如果只是“能扫到设备”就算完成那可以实现得很粗糙但要做成一个可上线的功能上面每一项都需要在不同系统版本上验证过。1.2 为什么最终选择“RN 原生桥接”而不是三种方案中的其他两个当时团队内部讨论过三条路线我把它们的优劣拉了个表方案开发成本双端复用率蓝牙功能覆盖度维护成本适用场景纯 ArkTS 重写高低Android 要重做高完整原生能力高两套代码鸿蒙是主要平台RN 纯 JS 蓝牙库低高极低鸿蒙 WebView 不支持低仅在 Android/iOS 运行RN 鸿蒙原生桥接中高高取决于桥能力中存量 RN 团队迁鸿蒙我直接说结论如果你的项目还同时维护 Android/iOS 版本那纯 ArkTS 重写意味着三端逻辑彻底分家后面每改一个需求都要同步改三遍。纯 JS 库在鸿蒙上又完全走不通剩下就只有桥接方案。桥接方案的核心代价是“中间层”复杂但这个代价可以通过封装来摊销。我把蓝牙相关的原生逻辑收敛成一个BTScanner模块JS 侧只面对几个简单的 Promise 方法和事件订阅团队其他成员不需要懂 ArkTS 也能正常开发 UI。这个封装思路后面详细讲。2. 核心细节蓝牙扫描桥接层与权限处理要点2.1 鸿蒙BLE扫描的底层逻辑从 ScanFilter 到回调鸿蒙的蓝牙 API 和 Android 在思路上是很接近的都分经典蓝牙和低功耗蓝牙BLE两套体系。这个项目只需要扫描 BLE 设备所以核心 API 都集中在kit.ConnectivityKit的ble模块下旧版本可能叫ohos.bluetooth.ble用的时候先确认你 SDK 对应的是哪一套。扫描的基本流程可以归纳成四步获取蓝牙状态确认系统支持 BLE 且蓝牙已打开构造ScanFilter过滤条件可以按设备名、服务 UUID 过滤调用ble.startBLEScan(filters, options)启动扫描注册BLEDeviceFind监听在回调里接收扫描结果听起来很简单但有几个关键参数必须理解透。ScanOptions里的interval和dutyMode直接影响扫描的功耗和结果返回速度。官方提供低功耗、均衡、高性能等几种模式我实际测试下来SCAN_MODE_LOW_POWER扫描间隔长省电但低功耗设备广播周期通常在 100ms 到 500ms 之间间隔太长会漏设备SCAN_MODE_BALANCED比较均衡大部分场景够用SCAN_MODE_HIGH_PERFORMANCE返回最快但发热和耗电明显不适合长时间运行我在正式上线的版本里给用户提供了“普通扫描”和“快速扫描”两个按钮分别对应均衡和高性能模式。很多设备首次进入配对模式后只广播 30 秒这时候用高性能模式提高抓到设备的概率体验会好很多。还有一个很多人容易忽略的点BLEDeviceFind回调返回的是一组ScanResult数组不是单台设备。回调触发频率由系统扫描策略决定同一台设备可能被上报多次。所以 JS 侧拿到数据后必须做去重和按 RSSI 排序否则同一个设备会在列表里出现好几行。2.2 桥接层设计用模块封装替代散落的原生调用桥接层是这个项目里最值得花心思的地方。如果只是简单地把startBLEScan暴露给 JS那 JS 代码里会到处散落着对系统状态的判断、权限检查、错误处理整个项目会变得很难维护。我设计模块时遵循两个原则第一个原则是“原生层做重活JS 层做轻展示”。权限申请、蓝牙状态检查、扫描启停全部收敛到原生侧。JS 侧调用方只想知道两件事扫描有没有成功、扫到了哪些设备。至于中间这些前置条件怎么满足应该由原生模块去处理。第二个原则是“事件驱动代替轮询”。扫描蓝牙设备是高频回调场景如果用 Promise 包一层那用户感知到的就是“点了开始等几秒一次性拿到一堆设备”。但如果想体验“列表实时往出蹦设备”必须用事件订阅的方式把每一条发现记录实时推到 JS。具体到 RNOH 的模块注册上我用的是 TurboModule 模式。模块类继承TurboModule然后在构造函数里初始化上下文。JS 侧通过NativeModules.BTScanner拿到这个模块的引用。这里有个实测下来的经验原生模块导出方法时建议统一用 Promise 包装所有同步操作不要在原生侧抛出异常让 JS 去捕捉因为 RN 的异常栈有时候会丢失原生错误信息排查起来非常痛苦。事件回调我用的是NativeEventEmitter方案。原生侧调sendEvent推送事件JS 侧在useEffect里订阅。这样扫描到新设备时原生层每推送一次JS 层 UI 就刷新一次整个过程是实时的。2.3 权限处理鸿蒙比 Android 更严格的地方蓝牙扫描涉及用户隐私鸿蒙在权限这块管得比一般开发者想象中严。除了蓝牙开关权限扫描 BLE 设备还涉及到位置信息的获取因为你有可能通过广播数据推测出设备大致距离。所以实际上需要动态申请以下权限ohos.permission.ACCESS_BLUETOOTH蓝牙连接权限访问蓝牙相关能力的基石ohos.permission.DISCOVER_BLUETOOTH扫描广播蓝牙设备权限ohos.permission.APPROXIMATELY_LOCATION模糊定位权限部分版本扫描时需要这里最容易踩的坑是权限申请的时机。不要把权限申请放在原生模块的内部方法里因为requestPermissionsFromUser需要当前 UIAbility 的上下文模块内部拿不到会导致权限弹窗不提示或者直接崩溃。正确做法是在页面装载后、用户点击“开始扫描”前由 JS 侧显式调用一次原生暴露的requestPermissions方法然后再开始扫描流程。我还在页面上加了一个“权限状态检测”的入口。每次进入扫描页时先调用checkPermissions()如果发现权限被用户拒绝过就弹一个自定义提示框引导用户去系统设置里手动打开。这个体验比反复弹系统权限框好很多也符合主流 App 的交互习惯。3. 实操实现从原生模块到JS页面的完整代码3.1 环境准备DevEco Studio 和 RNOH 工程初始化这个项目开始前先确认你的开发环境满足这几个条件DevEco Studio 版本建议 5.0 以上鸿蒙 SDK API 12 以上Node.js 16 以上RNOH 对 Node 版本有一定要求安装了 React Native for OpenHarmony 的脚手架工具如果你的团队还没有 RNOH 工程可以用react-native-ohos-community/cli初始化或者参照官方文档在现有 RN 工程上添加鸿蒙目标目录。我建议直接新建一个带鸿蒙 target 的工程因为双端的原生配置差异不小硬塞进同一个目录容易互相污染。工程初始化好之后需要在entry/src/main/module.json5里声明蓝牙和定位权限。这个文件相当于 Android 里的AndroidManifest.xml权限不在这里声明运行时申请权限会直接失败。{ module: { name: entry, requestPermissions: [ { name: ohos.permission.ACCESS_BLUETOOTH, reason: $string:bluetooth_reason, usedScene: { abilities: [EntryAbility], when: inuse } }, { name: ohos.permission.DISCOVER_BLUETOOTH, reason: $string:bluetooth_reason, usedScene: { abilities: [EntryAbility], when: inuse } }, { name: ohos.permission.APPROXIMATELY_LOCATION, reason: $string:bluetooth_reason, usedScene: { abilities: [EntryAbility], when: inuse } } ] } }注意reason字段对应的是本地化字符串必须在string.json里配好否则编译会报错。这也是鸿蒙和 Android 不太一样的地方权限申请理由强制要求本地化处理。3.2 原生侧实现BTScanner 模块代码逐段拆解原生侧核心模块我用 ArkTS 实现主要分三块权限申请、扫描控制、事件回调。代码是基于我项目的精简示意运行前请核对当前 SDK 版本的 API 差异。import { ble } from kit.ConnectivityKit; import { abilityAccessCtrl, Permissions } from kit.AbilityKit; import { BusinessError } from kit.BasicServicesKit; import { TurboModule, TurboModuleContext } from rnoh/react-native-openharmony;权限申请这块我封装了一个公共方法接收页面传过来的上下文这个上下文在 JS 侧发起调用时比较难拿所以我选择在原生 EntryAbility 初始化阶段把它保存到一个全局单例里权限方法直接复用。export class BTScannerModule extends TurboModule { private scanResults: Mapstring, ble.ScanResult new Map(); constructor(ctx: TurboModuleContext) { super(ctx); } checkPermissions(): Promiseboolean { const atManager abilityAccessCtrl.createAtManager(); const permissions: Permissions[] [ ohos.permission.ACCESS_BLUETOOTH, ohos.permission.DISCOVER_BLUETOOTH, ohos.permission.APPROXIMATELY_LOCATION ]; try { return atManager.requestPermissionsFromUser(this.ctx.uiAbilityContext, permissions) .then((result) { return result.authResults.every((res) res 0); }) .catch((err: BusinessError) { return false; }); } catch (e) { return Promise.resolve(false); } }扫描方法我设计成接受一个filterDeviceName参数这样 JS 侧可以根据用户输入框的内容动态调整过滤条件。ScanFilter里可以传设备名也可以传服务 UUID但项目里设备名过滤更常见。startScan(filterDeviceName: string): Promiseboolean { if (!ble.isBleEnabled()) { return Promise.resolve(false); } this.scanResults.clear(); const filters: ble.ScanFilter[] []; if (filterDeviceName filterDeviceName.length 0) { filters.push({ name: filterDeviceName, }); } const options: ble.ScanOptions { interval: 1000, dutyMode: ble.ScanDuty.SCAN_MODE_BALANCED, }; try { ble.startBLEScan(filters, options); // 注册扫描结果监听 ble.on(BLEDeviceFind, this.onDeviceFind); return Promise.resolve(true); } catch (err) { return Promise.resolve(false); } }扫描结果回调里我先把所有设备暂存在一个Map里以设备 ID 为 key实现天然去重。然后按 RSSI 排序后把前 50 个设备一次性推送到 JS 侧。这里没有做“每发现一台就推送一次”的粒度的原因是为了减少 JS 侧渲染频率。有些蓝牙模块会同时上报十几台设备逐个推送会造成列表频繁重排体验反而不好。private onDeviceFind (data: Arrayble.ScanResult) { data.forEach((device) { this.scanResults.set(device.deviceId, device); }); const sorted Array.from(this.scanResults.values()) .sort((a, b) (b.rssi ?? -100) - (a.rssi ?? -100)) .slice(0, 50); this.emit(onScanResults, sorted.map((device) ({ deviceId: device.deviceId, deviceName: device.deviceName ?? 未知设备, rssi: device.rssi ?? -100, }))); } stopScan(): Promiseboolean { try { ble.stopBLEScan(); ble.off(BLEDeviceFind, this.onDeviceFind); return Promise.resolve(true); } catch (err) { return Promise.resolve(false); } } }这里我用了this.emit往 JS 侧发事件RNOH 的 TurboModule 内置了事件发送能力。在实际项目里你会发现这个方法的名字和参数格式可能在各个版本间有变化我强烈建议封装一层把原生事件先映射成项目内部统一的数据格式这样后续升级 SDK 时只改映射层业务代码不用动。3.3 JS 侧封装自定义 Hook 和扫描页面JS 侧如果直接调NativeModules.BTScanner代码会非常啰嗦。所以我写了一个自定义 HookuseBluetoothScanner把权限检查、开始扫描、停止扫描、结果状态都封装进去页面组件只需要关心 UI 状态。import { useEffect, useRef, useState, useCallback } from react; import { NativeModules, NativeEventEmitter, Platform, } from react-native; const { BTScanner } NativeModules; const scannerEmitter new NativeEventEmitter(BTScanner); export interface BleDevice { deviceId: string; deviceName: string; rssi: number; } type ScannerState idle | scanning | error; export function useBluetoothScanner() { const [devices, setDevices] useStateBleDevice[]([]); const [scannerState, setScannerState] useStateScannerState(idle); const [hasPermission, setHasPermission] useStateboolean(false); const isMountedRef useRef(true); useEffect(() { isMountedRef.current true; const subscription scannerEmitter.addListener(onScanResults, (list: BleDevice[]) { if (isMountedRef.current) { setDevices(list); } }); return () { isMountedRef.current false; subscription.remove(); }; }, []); const requestPermission useCallback(async () { const granted await BTScanner.checkPermissions(); setHasPermission(granted); return granted; }, []); const startScan useCallback(async (keyword?: string) { const granted hasPermission ? true : await requestPermission(); if (!granted) { setScannerState(error); return; } const started await BTScanner.startScan(keyword || ); if (started) { setScannerState(scanning); setDevices([]); } else { setScannerState(error); } }, [hasPermission, requestPermission]); const stopScan useCallback(async () { await BTScanner.stopScan(); setScannerState(idle); }, []); return { devices, scannerState, hasPermission, startScan, stopScan, setScannerState, }; }代码里有几个细节值得说明。第一isMountedRef是为了避免卸载后回调刷新 state 导致的警告。第二hasPermission用 state 缓存避免每次点扫描都弹一次权限框。第三事件订阅在useEffect里注册组件卸载时一定要调用remove()否则会形成内存泄漏。页面组件用FlatList做列表渲染因为扫描结果可能刷新很频繁FlatList自带的渲染优化很重要。字段展示上我按“设备名称 信号强度”两个维度展示信号强度用 RSSI 数值加上一个简单的图标示意。设备去重是在原生层做的所以 JS 侧不需要再处理重复数据。3.4 完整扫描流程串联从点到面的状态驱动整个页面跑的流程我梳理成下面这条链路每一步都是前一步成功后才能进入页面 onMounted检查权限状态更新按钮可用性用户点击“开始扫描”调用startScan(keyword)内部先检查hasPermission没有就请求权限权限通过后原生层检查蓝牙是否开启没开启则返回失败原生层启动 BLE 扫描注册回调扫描结果通过事件通道实时推送到 JS更新列表用户点击“停止扫描”或页面卸载调用stopScan()这里我特意把“蓝牙是否开启”的处理放到原生层返回 false 之后由 JS 侧弹提示。因为判断蓝牙开关状态在原生侧只有一行代码但提示文案和引导跳转逻辑在 JS 侧更好维护。4. 问题排查白屏、扫不到设备、回调丢失的避坑实录4.1 RN鸿蒙版启动白屏最常见的第一个坑我在项目初期遇到最头疼的问题不是蓝牙扫描本身而是 App 启动后直接白屏。而且这个现象不是必现的有时候清掉后台重新进就好有时候怎么重启都是白屏非常影响验证扫描功能。排查下来白屏的根源基本集中在三处。第一处是 Bundle 加载失败。RNOH 开发模式下需要从 Metro Server 拉取 JS Bundle如果电脑防火墙拦截了 Metro 端口或者真机和电脑不在同一网段Bpple 就会一直加载不出来。第二处是原生模块注册异常。当你新增了一个 TurboModule 但没有正确注册到模块列表里整个应用会在 Native 层崩溃表现也是白屏。第三处是 Dev Server 崩溃这个比较容易发现命令行里会直接报错。排查顺序我建议是先看 Metro 终端输出有没有明显报错再确认真机网络能访问电脑的 Metro 端口用浏览器直接访问http://电脑IP:8081/status试试检查原生模块注册文件确认BTScanner模块被正确加载最后看 DevEco Studio 的日志输出定位底层崩溃原因白屏问题往往和用户环境强相关社区里讨论“react native 启动白屏”的频率一直很高。我的建议是在工程里加一个全局的错误边界组件任何 JS 异常都弹一个错误页至少让测试人员能判断是 JS 问题还是原生问题而不是面对一片白屏无从下手。4.2 扫描不到任何设备先别怀疑代码按顺序排查蓝牙扫描跑起来之后第二类高频问题是“点了开始扫描一个设备都扫不到”。我把排查过程整理成一张表你在遇到类似问题时可以按顺序过一遍排查项检查方法常见原因系统权限去系统设置里看 App 的蓝牙和定位权限权限被拒绝尤其是在首次弹窗时误点蓝牙开关正常进设置看蓝牙是否打开部分设备需要重新开关蓝牙才恢复广播过滤条件去掉ScanFilter里的 name 限制扫全量设备广播名和预期不一致被过滤掉了扫描模式切到SCAN_MODE_HIGH_PERFORMANCE低功耗模式漏扫信号弱的设备系统兼容性用另一台已知正常的手机测试个别定制系统对后台扫描限制严格我在项目里还遇到过一个很奇怪的场景用鸿蒙模拟器调试时扫描一直返回空列表。后来查了文档才发现鸿蒙模拟器默认没有模拟蓝牙硬件BLE 扫描功能在模拟器上压根不可用。所以做蓝牙功能一定要用真机调试模拟器只能验证 UI 层。另外有开发者在 Windows 上做调试时打开设备管理器看到“Generic Bluetooth Adapter”带感叹号结果真机上的扫描功能一直异常。这个其实是电脑自带蓝牙适配器驱动异常影响的是电脑和手机之间的通信链路不是手机端扫描本身的问题。遇到这种状况重启蓝牙驱动或者换 USB 连接方式就能排查出来。4.3 扫描结果回调丢失事件订阅时机和线程切换第三种比较隐蔽的问题是“扫描启动了但 ListView 一直不刷新”。这种问题代码逻辑上看起来都对权限也好好的但就是没有设备回流。我定位到的第一个原因是事件订阅时机。我最初把NativeEventEmitter.addListener写在startScan回来之后但原生层可能已经先于 JS 订阅回调触发了emit导致最初的几条扫描结果没人接收。解决方法是把订阅提前到useEffect里页面一加载就注册监听然后再处理扫描逻辑。第二个原因是线程切换。鸿蒙蓝牙扫描结果回调在某些版本上是跑在 IO 线程上的如果你直接在这个回调里调用emit推送事件RNOH 的事件机制可能不会主动切回 JS 线程结果就是丢事件或者延迟。稳妥的做法是在原生回调里用this.ctx.taskExecutor.runTask或者等价的机制切回主线程再执行事件发送。这两个问题都解决之后扫描列表的刷新就稳定多了。我的经验是蓝牙相关的事件回调默认都不信任当前线程所有涉及跨线程传数据的操作显式处理线程切换不要赌框架会帮你做。4.4 功耗和重复扫描的优化不只是“扫完就停”最后一个值得拿出来说的点是功耗优化。蓝牙扫描如果一直开着手机发烫特别明显。而且很多低功耗设备广播是有频次限制的扫描太频繁对设备本身也是负担。我做的优化有三个扫描兜底超时机制默认 15 秒自动停止防止用户忘记关提供“仅扫描一次”模式扫到指定设备后立即停止扫描中降低列表刷新频率原生侧累积一批结果再推送这三点做下来项目的体感和续航表现都好了不少。特别是在调试现场一堆设备同时广播时没有超时机制的话手机很快就变成暖手宝。5. 影响范围与后续扩展一个扫描功能背后的鸿蒙适配思考5.1 扫描只是开始功能扩展的想象空间很大蓝牙扫描功能跑通以后这个桥接层就成了整个 App 连接智能硬件的地基。后面要做设备连接、服务发现、特征值读写、蓝牙串口透传都可以在同一个BTScanner模块里扩展。比如我后面紧接着做的一个需求是连接设备后读取广播数据里的自定义服务数据用于判断设备固件版本。这个功能在原生侧可以直接通过广播数据解析出来桥接层只需要增加一个readDeviceServices(deviceId)方法。如果当时没有做好模块封装这段逻辑就会散落在页面生命周期里扩展起来会非常痛苦。应用场景上这类 RN 鸿蒙版蓝牙方案最适合的领域包括智能家居设备配对与调试工具运动健康类 App 连接手环、体脂秤工业现场的 BLE 传感器数据采集蓝牙外设产线测试工具这些场景有几个共同特点需要跨平台需要快速迭代 UI但又必须访问完整的蓝牙底层能力。RNOH 桥接方案刚好卡在中间既能复用业务代码又不牺牲原生能力。5.2 组件化思路把蓝牙能力沉淀成团队公共库在项目推进过程中我发现蓝牙能力其实是一个很通用的基础设施。与其每个项目都重新写一遍桥接层不如把它拆成一个独立的 npm 包内部封装好权限申请、扫描、连接、通讯这些能力对外只暴露一套 Promise 风格 API。组件化的好处在团队协作中非常明显。UI 组员可以完全不用关心底层是鸿蒙还是 Android他们只需要调scanner.startScan()、监听结果事件。原生开发同学也能在自己擅长的领域深耕把蓝牙适配、线程处理这些脏活累活都收敛在包里。遇到一个特别典型的协作场景产品需求突然要加“扫描时实时显示设备RSSI波形”这时如果桥接层封装得好原生侧只需要多透传一个字段JS 侧画个图表就行整个改动量非常小。但如果封装得差可能要动好几个原生方法还要处理各种线程问题。从更宏观的角度看做鸿蒙适配不仅是把代码跑起来更是审视整个技术栈分层是否合理的机会。React Native 鸿蒙版还在快速演进中API 变动频繁只有把业务代码和系统细节隔离开才能在这个生态里少踩坑、走得稳。5.3 给后来者的三条建议最后分享三个我在这个项目里沉淀下来的经验供大家参考。第一一定要保留一份“最小可运行示例”。我建了一个独立的分支里面只有一个扫描页面和一个原生模块不掺杂任何业务逻辑。每次升级 RNOH 版本或者鸿蒙 SDK先在这个例子里验证蓝牙功能是否正常再合入业务分支。这会帮你把“SDK升级引起的问题”和“业务代码的问题”快速区分开。第二原生日志和 JS 日志给足上下文。蓝牙问题经常需要跨端定位如果日志里没有设备 ID 和操作时间线排查起来会很费劲。我在原生层每次扫描开始、停止、发现设备都打日志并在传给 JS 的数据里带上时间戳这样无论从哪端查问题都能拼出完整的调用链。第三准备好蓝牙调试工具。手机端我一般用串口蓝牙终端类的工具模拟外设端配合测试。电脑端要注意蓝牙适配器状态这在 Windows 上尤其容易出问题驱动异常会直接影响真机调试链路。工具不行的时候换个 USB 口、重启蓝牙服务都是常规操作别一上来就改代码。这次把 React Native 鸿蒙版的蓝牙扫描功能完整做完我最大的感受是鸿蒙生态虽然年轻但技术栈的成熟速度比想象中快。只要找到合适的桥接方式存量 RN 团队的迁移成本并没有传说中那么高。当然前提是有人愿意把原生层这些细碎的坑提前踩平希望这篇文章能帮你省下这部分时间。
返回列表