
蓝牙这东西做系统层和做应用层的感受完全是两码事。做应用层你调个接口就能搜到设备、连上音箱感觉岁月静好做系统层或者做系统能力封装你面对的是协议栈状态机、权限管控、设备白名单、回调时序这些细节任何一个环节没理清楚轻则连接失败重则直接把系统服务搞崩。鸿蒙的短距通信服务也就是我们常说的蓝牙模块把上层应用和底层协议栈做了隔离这套设计的思路、踩坑的点、以及对开发者的影响值得认真拆一拆。这篇东西不会给你贴一堆源码然后让你自己悟。我会从设计思路讲起把蓝牙从打开到连接成功的完整流程走一遍再把最容易出问题的细节单独拎出来说最后分享一些我在实际调试中积累的排查经验。无论你是刚开始接触鸿蒙开发还是已经在做蓝牙应用适配这篇内容应该都能让你少走几个弯路。1. 鸿蒙短距通信服务的整体设计与思路拆解1.1 为什么鸿蒙要把蓝牙单独做成一个服务很多人第一次接触鸿蒙的蓝牙API时会有一个疑问这不就是系统蓝牙接口吗跟安卓的BluetoothAdapter、iOS的CoreBluetooth有什么本质区别区别是有的而且集中在服务这两个字上。鸿蒙对短距通信的定位不是单纯的提供几个API让你调而是把蓝牙作为一个系统级的服务来运营和管理。这意味着它不只是处理连接本身还要做设备管理、连接策略、权限校验、状态感知、多设备协同这些事。蓝牙服务的底层是协议栈上层对接的是应用层框架中间通过软总线做能力调度。这套架构下蓝牙不仅仅是能用而是好管。具体来说鸿蒙的蓝牙服务有以下几个核心设计目标第一是跨设备一致性。鸿蒙生态天然是多设备的手机、平板、手表、音箱都要跑同一套短距通信逻辑。如果每个设备各写一套维护成本是灾难。所以鸿蒙把蓝牙能力抽成统一服务不同设备形态只是配置不同接口保持一致。第二是连接管理的系统化。蓝牙连接不是一个纯粹的链路问题还涉及权限、安全、资源竞争。鸿蒙把连接建立的过程纳入了系统级调度比如多个应用同时申请蓝牙连接时系统会统一仲裁而不是每个应用各自为战。第三是能力的服务化开放。鸿蒙的蓝牙接口设计带上了一定的服务化味道很多操作是异步的、回调式的你需要监听状态变化而不是像传统API那样一调就返回结果。这是为了适应分布式场景下设备状态的不确定性。1.2 蓝牙服务在鸿蒙系统架构中的位置理解鸿蒙蓝牙流程第一步不是看API而是明确蓝牙服务在系统架构里的位置。鸿蒙的层次大致是应用层 - 应用框架层 - 系统服务层 - 内核和驱动层。蓝牙短距通信服务主要落在系统服务层对应的是蓝牙服务进程。从功能模块上看蓝牙服务进程内部大致可以拆成几个模块服务入口模块负责接收应用层绑定请求和指令下发状态管理模块负责蓝牙开关状态、适配器状态、连接状态的维护设备管理模块负责设备发现、配对绑定、设备信息的持久化连接管理模块负责GATT连接、Socket连接、HID连接等不同类型连接的建立与断开协议栈封装层对接到底层的开源蓝牙协议栈完成具体协议的交互平时我们写应用时直接面对的是应用框架层的接口。这些接口通过IPC跨进程通信机制调用到系统服务层系统服务层再往下走。这个链路看着长但它保证了上层应用的稳定性——即使某一个应用崩溃了也不会直接把蓝牙协议栈拖死。这里有个概念容易混淆就是BLE与经典蓝牙在鸿蒙接口层的差异。BLE低功耗蓝牙用的是GATT协议接口围绕Service、Characteristic、Descriptor三个核心概念展开经典蓝牙走的则是socket或者HFP/A2DP等配置文件。鸿蒙的接口设计将两者都包含在短距通信服务下但API风格差异很大做开发时要分清。1.3 鸿蒙蓝牙方案里你必须理解的几层抽象做鸿蒙蓝牙开发脑子里必须建立几层抽象第一层是设备抽象。蓝牙设备在系统层面被建模成一个设备对象里面包含地址、名称、设备类型、配对状态、连接状态等信息。不管是扫描发现的、还是已配对的、还是正在连接的最终都收敛到统一的设备信息结构上。第二层是连接抽象。连接不仅仅意味着物理链路的建立还包括服务发现、MTU协商、鉴权等过程。鸿蒙把连接抽象成一种系统资源连接成功与否、连接后状态如何变化都以回调方式通知给应用层。第三层是服务抽象。BLE设备的Profile能力被封装成Service每个Service下有多个Characteristic每个Characteristic有属性读、写、通知、指示等。这些抽象直接映射到GATT协议的概念理解了这三个概念就等于理解了BLE的八成。这三层抽象建立起来后蓝牙开发中的很多问题就能结构化思考了。比如设备连不上你可以按层去排查是设备发现层没找到设备还是连接建立层失败抑或是服务发现层拿到了空的Service列表。这样效率比乱试要高得多。2. 核心流程详解从打开蓝牙到连接成功2.1 蓝牙权限声明与基础检查很多新手犯的第一个错误就是权限没搞清楚就开调API。鸿蒙的蓝牙权限体系比安卓更细不同的操作需要不同的权限而且有些权限是系统级system_basic级别普通应用根本拿不到。常用的几个蓝牙相关权限如下ohos.permission.USE_BLUETOOTH基础使用权限搜索、连接都需要ohos.permission.DISCOVER_BLUETOOTH扫描发现设备所需ohos.permission.MANAGE_BLUETOOTH系统级权限通常用于系统应用或预置应用ohos.permission.ACCESS_BLUETOOTH访问蓝牙状态信息在开发调试时你需要在module.json5文件中声明权限。如果权限缺失调用接口时不会直接崩溃但会静默失败或者返回权限错误码这个排查起来非常消耗时间。权限声明完成后还要做蓝牙能力的可行性检查。鸿蒙提供了蓝牙状态查询的API你可以查询蓝牙是否可用、是否开启、设备是否支持蓝牙。一个规范的流程应该是检查当前设备是否支持蓝牙检查蓝牙是否已开启如果没有则引导用户开启获取本地蓝牙适配器信息开始扫描或进入连接流程这套流程看起来简单但每一环都有对应的失败场景。比如支持的设备不等于蓝牙可用蓝牙可用不等于已经开启开启不等于你拿到了权限。每一步失败都要有对应的用户提示这才是健壮的应用。2.2 蓝牙扫描流程的完整链路蓝牙扫描是连接的第一步也是坑最多的一步。你可能遇到扫描不到设备、扫描结果不稳定、扫描一会儿就自动停了等各种诡异现象。鸿蒙的BLE扫描流程大致如下第一步是创建扫描配置。需要指定扫描策略比如是低功耗模式还是标准模式是否需要过滤设备名称是否只扫描支持特定服务的设备。扫描配置直接影响扫描结果的质量和耗电。第二步是注册扫描回调。这是一个异步过程当你调用启动扫描接口后系统会通过回调持续上报发现的设备。注意这里的持续是有条件的——BLE扫描不是无限期的系统会有超时机制回调频率和过滤策略也会影响结果。第三步是接收并处理扫描结果。需要从回调中提取设备信息在这里要做过滤剔除信号很弱的设备、重复的设备等。我在实际中的应用中发现扫描不到设备大部分原因不在代码而在扫描参数。比如扫描超时时间太短、过滤条件设置得太苛刻、或者扫描模式太省电。需要根据实际场景做平衡——如果你做的是设备配对功能扫描时间至少5秒起如果只是设备列表展示3秒就够了时间再长用户等不了。还有一个值得单独说明的点鸿蒙的扫描结果里拿到的是设备的MAC地址但出于隐私保护部分设备会广播随机地址导致同一个设备每次扫描地址都不一样。这是BLE的核心特性之一做配对和连接逻辑时必须考虑到。2.3 配对过程的细节与状态流转扫描到设备之后下一步就是配对。需要明确一点BLE的配对与连接是两个概念。有些设备只需要连接不需要配对有些设备连接后必须配对通过才能进行通信。配对过程涉及安全认证本质上是在建立信任关系。鸿蒙的配对流程如下应用发起配对请求传入设备地址系统服务层创建配对通道与对端设备开始配对协商根据设备的安全等级要求可能需要用户确认或输入PIN码配对完成后系统会保存配对信息到持久化存储中应用通过回调获知配对结果这里最复杂的是配对模式的差异。有些设备是Just Works模式不需要任何交互就能配对有些需要数字比对比如两个设备屏幕上各显示一个六位数字用户确认一致后完成配对还有一些需要PIN码输入。在鸿蒙上做配对你无法直接控制对端设备的安全模式但你可以监听配对状态变化在不同阶段给用户不同的界面反馈。比如监听配对请求事件时弹出确认对话框监听配对完成事件时关闭对话框。如果一直等着配对成功才更新UI用户会在等待期产生困惑。我遇到过一种情况设备明明已经出现在已配对列表里但应用里的连接状态还是没有更新。原因是配对完成回调和应用的状态同步不在一个时间点需要主动查询设备状态或者等待连接回调来最终确认。2.4 连接流程的核心GATT连接与服务发现配对完成后真正的数据通路是建立在GATT连接之上的。GATT连接是BLE通信的基石所有的数据交换都发生在GATT连接建立之后。鸿蒙的GATT连接流程包括以下几个关键步骤第一步是作为中心设备连接外围设备。调用连接接口传入设备地址。这一步需要特别注意如果设备已经在系统级连接管理中处于连接状态重复连接可能会失败。所以连接前最好先查询设备状态。第二步是连接建立。系统会与对端设备握手协商连接参数选择物理信道。连接建立的快慢取决于信号质量和对端设备的响应速度。如果对端设备已经休眠可能要先通过广播或者预设的方式唤醒它。第三步是服务发现。连接建立后并不意味着你可以立即开始收发数据。系统需要与对端设备交互获取完整的GATT服务表包括Service、Characteristic、Descriptor三层信息。只有在服务发现完成后你才能找到目标特征值执行读写或订阅通知。服务发现这个步骤很多开发者在代码上容易忽略对状态的处理。因为服务发现是异步的回调时你才能拿到服务列表。如果回调迟迟不来你要判断是对端设备异常还是连接已经断了。我的做法是设置一个定时器比如超过5秒没有任何回调就触发超时处理逻辑。还有一个常见问题是MTU协商。MTU是BLE单次数据传输的最大长度默认值很小大概20字节左右。要做大数据传输需要主动发起MTU协商。协商会增大单次传输的数据量从而提升吞吐。这个在高吞吐率场景下是个关键调优点。2.5 数据交互读写操作与通知订阅到了这一步蓝牙链路已经通了你可以真正和设备进行数据交换。BLE数据交换有三条路径第一是读操作。你主动从对端设备的某个特征值读取数据。读操作一次能拿到的数据长度受限所以不适合大块数据传输。第二是写操作。你向对端设备的某个特征值写入数据。写操作又分为带响应的写和不带响应的写。带响应的写会等待对端确认可靠性高但速度慢不带响应的写不用等确认速度快但无法保证数据到达。具体选哪种要看设备的数据协议要求。第三是通知和指示。这是BLE设备主动上行数据的方式。设备端数据变化时会主动通过Notify或者Indicate方式推送给中心设备。这两种的区别在于Indicate会要求中心设备回复确认可靠性更高而Notify则没有这一层确认。在鸿蒙的API设计里订阅通知是一个关键操作。你需要先找到对应的Characteristic然后使能它的CCCD客户端特征配置描述符之后才能收到设备的上行数据。顺序错了或者漏了这一步你就只看到设备在广播数据但应用完全接收不到。我做蓝牙设备调试时习惯先用系统自带的调试工具确认设备的数据交互方式再用代码去复现。这样可以分清是设备端问题还是应用端问题调试效率会提高很多。2.6 断开连接与异常处理连接断开不是一个简单的事它分为正常断开和异常断开两种情况。正常断开是你主动调用断开接口系统会走完整的断开流程包括解除GATT连接、清理回调、更新状态。这个流程相对可控。异常断开的情况就多了。比如设备超出信号范围、设备端主动断开、对端设备关机或重启、本地蓝牙服务被杀掉等等。异常断开时应用的感知是回调里出现了一个错误状态码如果不妥善处理应用可能一直停留在已连接的假象中。处理断开逻辑的关键点是收到断开回调后要清理所有与连接相关的资源包括回调注册、数据缓存、UI状态。然后根据业务需求决定是否自动重连。重连策略也是值得设计的。不能一味地立即重连也不能完全不重连。我的建议是采用指数退避策略第一次断线等1秒重连第二次等2秒第三次等4秒最多等30秒超过阈值就放弃通知用户手动重连。这种策略既能解决短暂信号波动导致的断线又能避免在设备完全不可用时浪费电量。3. 实操记录一个模拟BLE设备的完整接入过程3.1 开发环境准备为了把这套流程讲得更具体我用一个模拟项目X来做演示一个BLE温湿度传感器通过Notify方式周期性上报温度数据。这个项目覆盖了扫描、配对、连接、服务发现、数据订阅五个核心环节是非常典型的BLE开发路径。环境准备其实没什么特别的主要就是装好开发工具、创建工程。但有一个点值得提醒鸿蒙的蓝牙API对真机和模拟器的支持程度不同。很多蓝牙接口在模拟器上只是空实现返回固定结果真机才是完整的协议栈行为。所以做蓝牙开发一定要准备好真机设备最好不要依赖模拟器调试。工程的配置文件要注意目标设备的API版本。蓝牙接口在不同API版本上有差异如果你使用的接口在当前设备上不存在调用会直接失败甚至闪退。解决方法是在调用前做版本判断或者查阅API文档确认最低支持版本。3.2 基础API封装思路写蓝牙应用前我的习惯是先做一层API封装不直接在上层业务里堆系统API。这一层封装的目的是把重复逻辑抽出来同时把系统API的异步特性转换成更容易管理的调用方式。一个典型的封装包括设备扫描类封装扫描启动、停止、回调管理连接管理类封装连接、断开、重连逻辑维护连接状态数据分析类把蓝牙传输的原始字节流解析成业务数据这样做的原因很简单蓝牙流程里充斥着异步回调如果所有逻辑都堆在Activity或Page里代码会迅速失控。而且蓝牙回调的生命周期管理是一个大坑——页面销毁了回调还在发不去管理就可能导致内存泄漏。我经历过一次很深刻的教训一个页面退出后忘记注销回调导致设备断开后系统仍然在回调UI线程操作一个已经不存在的页面实例直接抛异常。从那以后我的封装里都强制要求回调注册时绑定生命周期页面销毁时必须主动注销不能依赖系统自动清理。3.3 从扫描到连接的代码实现要点下面我把这个模拟项目的关键流程走一遍重点讲代码要点和为什么这么写。扫描部分的核心代码如下所示省略了具体引用但逻辑是通用的// 创建扫描配置 let scanOptions { interval: 500, // 扫描间隔 dutyMode: 100, // 扫描占空比 matchMode: 1, // 匹配模式 }; // 启动扫描 let scanCallback { onDiscovery: (device) { // device 包含地址、名称、RSSI、广播数据等信息 if (device.deviceName Sensor_Temp) { // 找到目标设备停止扫描 stopBLEScan(); // 进入连接流程 connectToDevice(device.deviceId); } } }; startBLEScan(scanCallback);这段代码里有几个关键细节值得展开第一是扫描回调里要做过滤。如果你不做设备名过滤扫描回调会不断上报周围几十个设备的信息处理起来很费性能。过滤条件可以根据业务定制可以是设备名、可以是服务UUID、可以是广播数据里的特定字段。第二是找到目标设备后立即停止扫描。保持扫描会持续耗电而且可能让已经连接的设备连接质量变差因为扫描会占用无线资源。这是BLE的一个物理特性同一时间只能在一个信道上做一种事。连接部分的代码逻辑async function connectToDevice(deviceId) { // 建立GATT连接 let connection await createGattConnection(deviceId); // 监听连接状态变化 connection.onStatusChange((status) { if (status connected) { // 开始服务发现 discoverServices(connection); } }); }连接过程的要点是状态管理。连接状态包括连接中、已连接、连接断开、连接失败。你在业务代码里至少要针对这几个状态做UI反馈。实际开发中用户经常会快速点击连接、取消、再连接状态反复横跳这时候如果没有状态仲裁逻辑底层接口会报错。服务发现和数据订阅部分的处理async function discoverServices(connection) { let services await connection.discoverServices(); // 遍历查找目标服务 for (let service of services) { if (service.uuid SERVICE_UUID_TEMP) { let characteristics await service.getCharacteristics(); for (let char of characteristics) { if (char.uuid CHAR_UUID_TEMP) { // 使能通知 await char.enableNotify(); // 注册数据回调 char.onCharacteristicChange((data) { // 解析温度数据 let temperature parseTemp(data); updateUI(temperature); }); } } } } }这里有个顺序问题必须先使能通知再注册数据回调。因为使能CCCD的操作会与设备端做一次交互如果回调注册在使能之前可能会丢失使能完成后设备端立刻发来的第一批数据。我当时调试时遇到过一个特别隐蔽的问题设备明明在推送数据但UI完全没有更新。排查了半天发现是回调注册得太晚等回调挂上时设备已经进入了等待状态不再主动推送了。这个问题属于典型的时序问题说明在BLE开发中你的代码必须严格遵循协议时序。3.4 设备端收包字节解析经验BLE设备上报的数据是原始字节你要根据设备厂商的协议手册做解析。不同的设备数据格式差异很大但通用的原则是一样的先查包头、再判断长度、按偏移量提取字段、最后做校验。以温湿度传感器为例常见的数据帧可能长这样假设包头(1B 0xAA) 长度(1B) 类型(1B) 数据区(NB) 校验(1B)在解析时有几个细节你要注意第一是字节序问题。有些设备是用大端有些是小端解析多字节字段时顺序反了数据就会变成负数或者大得离谱。遇到这种情况先检查字节序。第二是数据缩放因子。很多传感器把浮点数转成整数传输接收端要根据协议约定做除法或乘法还原真实值。温度30.5度可能传输的原始值是305需要在应用层除以10。第三是分包与粘包。BLE单帧数据长度有上限当业务的协议数据超过这个上限时设备会分多帧发送。此时你的应用端要做数据缓存和拼接等完整的一帧凑齐后再做解析。粘包则是多帧数据挤在一帧里需要按长度字段切割。这一块确实是入门开发者最头疼的。我的建议是先使用日志把收到的原始字节流全都打出来配合设备的协议手册手动解析确认格式无误后再写自动解析代码。跳过这个验证步骤直接开写解析器基本都会出现偏差。4. 常见问题与排查技巧实录4.1 蓝牙扫描不到设备的排查路径扫描不到设备这是出现频率非常高的问题。根据我的经验排查路径应该按照权限 - 系统状态 - 扫描参数 - 环境干扰的顺序来。权限问题的特征是其他功能正常但扫描没有任何回调。这种一般是权限缺失或者权限声明格式不对。你可以检查一下module.json5中的权限字符串是否和官方文档一致。系统状态问题的特征是蓝牙开关打不开或者频繁开关。这种要去系统设置里手动操作一次蓝牙确认系统蓝牙本身是否正常。扫描参数问题的特征是扫描能启动但什么结果都没有或者结果很少。这种优先排查扫描模式是不是太省电间隔设置是不是太长。我实测过使用低功耗扫描模式时部分设备的广播包解析率会明显下降。环境干扰问题的特征是周围设备少的时候能扫到设备多时就扫不到。这个是射频环境的问题只能在代码里多做几轮扫描提高概率或者提示用户靠近设备。4.2 连接失败的高频原因与对策连接失败一般分为两种情况完全连不上和连接后马上断开。完全连不上首先要排除设备是否还在广播。很多BLE设备为了省电广播一段时间后会自动关闭广播进入休眠。如果设备没在广播你怎么连都是失败的。处理方式是确保设备处于可连接状态必要时通过物理按键唤醒设备。其次要检查设备是否已经被连接。BLE设备通常只允许一个中心设备连接。如果设备已经被你的手机或者其他设备连接新的连接请求会被拒。解决方法是先断开已有连接释放资源再发起新连接。连接后马上断开大概率是对端设备端的问题——比如设备的连接参数不支持当前环境、设备固件Bug、或者设备在连接过程中出现异常。这种问题只能通过抓取协议栈日志去定位应用层能做的事情不多。我把常见连接失败原因整理成一个速查表方便快速定位现象可能原因处理建议搜索不到设备权限未声明或设备未广播检查权限配置、确认设备处于广播状态能发现但连接失败设备已被其他中心连接断开其他连接后再试连接后立刻断开信号弱或设备端异常靠近设备、检查设备供电与状态连接成功但无数据服务发现未完成或未订阅通知确认已使能CCCD、检查数据回调注册时机数据是乱码协议解析错误或字节序反了打印原始字节流对照协议手册逐字节核对4.3 回调不触发的常见陷阱回调不触发可能是蓝牙开发中让人最抓狂的问题。因为你不知道是底层没发回调还是回调被消费了或是你的监听器根本没注册上。第一个常见陷阱是回调对象生命周期管理出错。鸿蒙的部分API在注册回调时如果你传入的是一个临时对象可能因为在应用层的引用被系统保存但你的业务代码里已经把对象置空了导致回调到达时逻辑根本没有处理入口。所以回调对象要么作为单例持有要么存到成员变量不要用局部变量注册后就不管了。第二个陷阱是线程问题。蓝牙回调的线程模型在不同接口上不一样。有些回调默认在主线程执行有些在Binder线程如果你在回调里做了UI操作而回调不在主线程就会报错。处理方式是在回调里做线程切换或者在注册时指定回调执行的runner。第三个陷阱是回调被重复注册或覆盖。如果你在代码里两次调用了注册接口后一次可能会覆盖前一次的实例。如果你期望两个回调同时生效就要确认你用的API是否支持多注册。我在调试中遇到过一个非常典型的场景设备能连接、能收发数据但界面上的连接状态始终没有更新。后来发现是回调会被系统发出的另一个实例覆盖导致我的业务回调永远收不到状态变更。最后的解决方法是只保留一个全局的回调实例在里面做分发处理而不是到处注册。4.4 性能与功耗问题的平衡策略蓝牙开发的最后一关是性能与功耗。尤其是做BLE应用功耗是绕不开的话题。扫描功耗是一个大头。持续扫描模式下功耗会明显上升。在应用里要注意扫描结束后立即停止扫描不要等到超时才停。另外扫描间隔和扫描窗口的配置直接影响功耗间隔越长、窗口越短功耗越低但发现设备的概率也会下降。业务上可以根据应用场景动态调整比如在配对模式下用激进参数快速找设备在正常监控模式下用保守参数降低功耗。连接状态的功耗主要取决于连接间隔。连接间隔是设备与中心设备之间的通信频度间隔越短数据实时性越高功耗也越高间隔越长实时性下降但更省电。这个参数一般是由中心设备发起的但也可以在连接后协商调整。如果你做的是长时间监控类应用在业务允许的条件下适当拉长连接间隔是有效的省电手段。还有一个容易被忽略的点是回调风暴。一些设备在监听过程中高频推送数据比如心率传感器每秒发送一次数据如果你的应用每次收到数据就做一轮完整的逻辑处理耗电和CPU占用都会明显上升。处理这类问题时我习惯在应用层加一个简单的数据节流机制如果数据变化幅度不大可以选择性丢弃或者在短时间内只做一次UI更新把中间值直接跳过。4.5 调试工具的使用心得最后聊一下调试工具。做鸿蒙蓝牙开发时我常用的调试手段有以下几种第一种是系统日志抓取。通过hilog抓取蓝牙相关的日志可以定位到系统服务层面的报错。蓝牙相关日志会有固定的模块标签抓到日志后再配合文档查询错误码大部分问题都能定位。第二种是协议栈抓包。蓝牙协议栈支持HCI日志抓取可以看到控制器与主机之间的真实交互数据这个工具主要用来排查连接参数协商、配对流程、加密过程这类底层问题。抓到数据包后你可以清楚地看到设备到底卡在哪一步是链路层失败还是GATT层异常。第三种是协议分析仪器。这是硬件级别的调试设备可以捕获空中的BLE广播包和连接包。我一般在排查射频问题时使用——比如设备实际在广播但手机就是扫不到。用协议分析仪一看广播包里的信道配置和广播间隔全暴露了出来问题往往一目了然。调试工具的使用原则很简单能用日志解决的问题不必上仪器能用仪器解决的问题不要靠猜。很多开发者遇到问题先怀疑代码但实际大量连接问题出在环境、设备状态和射频层面。合理的做法是先确认底层有没有问题再回头审视自己的代码。我个人在实际操作中的体会是蓝牙流程的开发考验的从来不只是写代码的能力更多是对状态机和时序的把握。鸿蒙的短距通信服务设计得比较规范但规范不等于不会踩坑。多记录状态变化、多打印日志、多测试边界场景蓝牙开发也可以像普通业务开发一样稳定可控。