夜雨聆风学习资料网

ARTICLE · 1118072

安卓 / iOS / 鸿蒙 NEXT:同一套 BLE,三套平台规则

安卓 / iOS / 鸿蒙 NEXT:同一套 BLE,三套平台规则
合集《BLE 蓝牙避坑指南》回顾:
01 BLE 到底是个啥?广播、角色、连接,一次讲明白
02 服务、特征、属性:BLE 的数据存在哪?怎么读写?

03 手机扫不到你的 BLE 设备?九成不是固件的锅

04 连上就断、重连也断:BLE 断连的 7 个真凶

05 BLE notify 收不到、数据还重复两遍?订阅的坑在这


有个做 App 的兄弟找我,说他的代码在安卓机上一切正常,同一台设备,换 iPhone 就扫不到。

他改了三天代码,越改越乱。

其实他一行代码都不用改——问题出在两个系统的规矩不一样:他的扫描里写了服务 UUID 过滤,而设备的广播里没带这个 UUID。安卓放过了,iOS 没放过。

这不是个例。BLE 最坑的地方在于:协议是标准的,但三个系统的实现规矩是各自的。你在安卓上积累的所有"经验",搬到 iOS 或鸿蒙 NEXT 上,可能全部作废。


一、四个维度,一次看清差别

维度
Android
iOS
HarmonyOS NEXT
权限
6.0–11 要定位权限(ACCESS_FINE_LOCATION,不给就搜不到);12+ 改 BLUETOOTH_SCAN / CONNECT / ADVERTISE
NSBluetoothAlwaysUsageDescriptionohos.permission.ACCESS_BLUETOOTH
(需用户授权)
设备地址
本机真实 MAC 从 6.0 起就拿不到了;扫到的设备地址取决于该设备自己用不用随机地址
UUID,不是 MAC
,且会变;拿不到真实 MAC
虚拟 MAC
;配对成功后不变,重启蓝牙 / 取消配对会变
MTUrequestMtu()
 显式调用;14 起系统可能自动按 517 发起
没有主动设置 MTU 的 API
,系统自动协商
需主动协商 + 监听实际值
后台
后台扫描 / 连接均有限制
严格,需声明后台模式,且不保证持续
以华为官方文档为准

二、设备地址:这是最容易翻车的一条

三家的地址规则没有一个是一样的:

  • • 安卓:6.0 起连本机自己的真实 MAC 都拿不到;至于扫到的设备地址稳不稳,取决于那台设备用没用随机地址——设备轮转随机地址时,你看到的 MAC 就会变;
  • • iOS:压根不给 MAC,给的是一个 UUID,而且这个 UUID 会变;
  • • 鸿蒙 NEXT:给的是虚拟 MAC,配对后固定,重启蓝牙或取消配对就会变。

后果是什么?你不可能靠"地址"来稳定识别一台设备。

我见过太多代码把 MAC 当主键写进数据库,结果用户重启一次蓝牙,设备就"变成另一台"了。

✅ 唯一在三端都成立的识别原则:靠设备名称 + 服务 UUID + 广播里的自定义字段(比如序列号)来认设备,别依赖地址。

鸿蒙官方也明确建议依赖名称与服务 UUID,而不是 MAC。iOS 官方同样。这不是我的偏好,是三家的共识。

三、MTU:一个能写死就能出事的数字

MTU 就是"一次能传多少字节",具体概念我放 P6 细讲,这里只说平台差异:

  • • 安卓:你主动调 requestMtu(517),系统去协商,Android 14 起系统甚至会自己按 517 发起,并且忽略你在同一连接上的后续请求——你想改小都改不了;
  • • iOS:没有给你设置 MTU 的 API。系统自己协商,你只能调 maximumWriteValueLength(for:) 去问当前允许写多少(它返回的是最大写入长度,不是 MTU——常见协商结果 MTU 185 时,返回约 182,且这不是固定值);
  • • 鸿蒙 NEXT:需要主动发起协商 + 监听实际结果,没有安卓那种自动顶格的策略,流程没走完整就容易出问题。

⚠️ 这条既是写稿红线,也是写代码红线:185、517、20 这些数字一个都不许写死——它们取决于机型、系统版本、对方设备。正确做法永远是运行时动态获取当前连接允许的最大写入长度。

四、后台:别指望在三端得到一致行为

  • • 安卓:后台扫描和连接都有限制,不同厂商(小米、华为、OPPO)的省电策略还各不相同;
  • • iOS:最严格,你得声明后台模式,而且系统依然不保证你能一直连着;
  • • 鸿蒙 NEXT:策略以华为官方文档为准,写这个的时候我没查到确定结论,建议你落地前先去官网核一遍,别听二手总结。

共同结论:别把"长时间后台保持连接"当成产品承诺。真要长期在线,老老实实做"断开重连"的逻辑。

五、还有一个 iOS 专属的坑

前面 P1 提过,这里再强调一次:iOS 扫描时如果指定了服务 UUID 过滤,而设备广播里没带这个 UUID,就直接扫不到。安卓宽松得多。

这是"安卓能搜、iPhone 搜不到"的头号原因。解决方法是让固件在广播包里带上服务 UUID——这条在三个平台上都成立,改固件比改 App 划算。


进阶选读:三端差异背后的机制

为什么 iOS 的 identifier 会变?如果设备广播用的是RPA(可解析随机地址,通常 15 分钟级轮转),那它本来就是设计成"认不出同一台"的。只有配对过、手里有 IRK 的一方才解得开这层;没配对时,系统只能按"当前看到的地址"派生 UUID——地址一换,UUID 跟着换。

安卓的 MTU 也有规范依据:一次连接只允许做一次 Exchange MTU 协商。Android 14 只是把这条执行得彻底了——第一次请求直接顶到 517,后续请求按规范就该被拒。

鸿蒙的虚拟 MAC 是同一思路的另一端:系统层做地址隔离,应用看到的地址根本不是空口里那个。

还有一类差异藏得比较深:连接参数三端"能改的程度"不一样。

BLE 里连接参数名义上由主设备(手机)定,外设只能通过 L2CAP 的 connection parameter update request 去"请求"。三端对这件事的支持完全两样:

能不能主动改
改完知道生效没有
安卓
能,但只有一个三档枚举:requestConnectionPriority 的高 / 均衡 / 低功率——不是精确值
要看 onConnectionUpdated 回调(API 26+)里的实际 interval / latency / timeout
iOS没有 API
。只能让外设自己发 update request,系统决定接不接受
无公开接口读当前连接参数,只能靠表现反推
鸿蒙 NEXT
以官方文档为准,接口形态和安卓接近
同上,要监听回调拿实际值

所以"我给固件配了 15 ms,怎么安卓上像生效了、iPhone 上完全没变"——因为 iPhone 压根没打算听你的。iPhone 通常按自己的策略给一个偏保守的间隔,你只能在外设侧把 min / max / latency / timeout 四元组配成一个它能接受的宽松区间(比如 min 30 ms、max 200 ms),给它台阶下。

这也解释了 P2 那条"三端各读一遍实际生效值"为什么重要:你以为你在调一套参数,其实你在跟三个不同的系统谈判。


顺手能用的东西

排查这类"换个手机就不行"的问题,最烦的是你手边不可能备齐三个系统的机器。

搏哥开发了一个三端可用的  BLE蓝牙调试工具箱  小程序,不用安装,安卓/苹果/纯鸿蒙  都可以使用,点击下方:

小程序的好处是同一套逻辑在三端表现一致——微信把平台差异兜住了。所以现场遇到"客户说扫不到、我这边明明能扫"的时候,我会让他用小程序扫一遍:小程序能扫到,说明设备没问题,问题在客户 App 的权限或过滤规则上;小程序也扫不到,那才是设备的事。


收个尾

  1. 1. 别依赖设备地址识别设备——三端规则全不一样,用名称 + 服务 UUID + 广播自定义字段;
  2. 2. MTU 一个数字都别写死,运行时动态取;
  3. 3. 后台保持连接不是承诺,做好断开重连;
  4. 4. 连接参数也不是承诺:安卓只有三档、iOS 连接口都不给,固件要配的是"三端都愿意接受的一个区间",不是某一个精确值。

下一篇讲数据侧最容易混的一笔账:HEX 和 ASCII 到底什么关系、为什么你收到的是乱码(P5)。


这个合集我会接着往下写:数据怎么切包和解析、交付时怎么给设备做二维码、验收该测哪 10 项。

你要是正好在做 BLE 项目,可以关注下「搏哥聊技术」,后面更新了能第一时间看到。

相关学习资料