news 2026/10/27 10:23:23

uniapp BLE 开发踩坑:小米手机数据接收截断问题排查全记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
uniapp BLE 开发踩坑:小米手机数据接收截断问题排查全记录

一、问题背景

项目基于uniapp(APP 端)​ 做跨平台蓝牙通信,设备端是串口透传 BLE 模块,业务层自定义十六进制协议。

正常流程:

  • 手机连接设备 → 开启 notify → 设备主动推数据
  • 前端解析协议帧(帧头AA AA F3 01+ 业务数据)

测试阶段发现一个诡异现象:

机型

表现

vivo

数据完整,版本解析

小米(HyperOS/MIUI)

超时5秒收不到23字节的查询信息

蓝牙设备端用抓包工具确认:数据确实发出去了,不是硬件没发。

二、排查过程

1. 先排除代码层问题

  • 监听只注册一次 ✓
  • notify 订阅在发现服务之后 ✓
  • 没并发调用 write/notify ✓
  • 用 nRF Connect 连同一设备验证

2. nRF Connect 对比验证(关键一步)

不手动改 MTU 时:

  • 小米默认 MTU = 23
  • 单包 notify 有效载荷 = 23 - 3(ATT 头)=20 字节

设备发 23 字节:

  • ATT 层不自动分片
  • 超出的 3 字节被协议栈直接丢弃

vivo:

  • 无修改vivo厂商默认的mtu会更大一点

结论:不是 uniapp bug,是BLE 默认 MTU 机制 + 透传模块不协商 MTU​ 的必然结果。


四、根因总结

1. BLE 协议层限制

  • 默认 ATT_MTU = 23
  • ATT 头占 3 字节(Opcode 1 + Handle 2)
  • 应用层单包最多 20 字节

而我跟主板通讯建立私有协议,23 字节是“应用帧总长”,不是默认的20字节,因为透传模式就是不分割协议只传数据的。

2. 透传模式放大问题

透传模块:

  • 不解析协议
  • 不主动发 MTU 协商
  • MCU 给多少就一次 notify 丢出去

于是:

  • 通道够宽(vivo)→ 正常
  • 通道默认 20(小米)→ 静默截断,但是奇怪的是不影响发送,只影响接受,这里涉及到安卓底层与手机厂商以及uniapp三方的交互协议

3. 小米 /红米问题

  • vivo:部分 ROM 自动协商
  • 小米:默认不协商,setBLEMTU 返回 success 也不一定真生效

五、解决方案(生产可用)

方案核心原则

不信任默认 MTU,不信任协商返回值,永远按“可能只有 20 字节”做兜底。


1. 连接时序严格卡顺序

createBLEConnection → onBLEConnectionStateChange(connected=true) → getBLEDeviceServices → getBLEDeviceCharacteristics → setBLEMTU(延迟 500~800ms 再调) → notifyBLECharacteristicValueChange(等 success) → 再发业务指令

小米上任何一步抢跑都会丢首包。


2. MTU 协商(测试后发现调整mtu值就可以但不能仅凭借uniappapi返回来确定,有时候设置不成功也没有反应,最终测试还是可以的,所以采用这种方法)

setTimeout(() => { uni.setBLEMTU({ deviceId, mtu: 247, success: () => console.log('MTU 协商请求成功'), fail: () => console.log('MTU 协商失败,走分包兜底') }) }, 600)

注意:

  • 不依赖 res 里的返回值
  • 真实可用 MTU 以“实际收到的字节数”为准

3. 发送端:按 MTU-3 串行分包

function writeQueue(buffer, mtu = 23) { const chunkSize = Math.max(20, mtu - 3) let pos = 0 return new Promise((resolve, reject) => { const next = () => { if (pos >= buffer.byteLength) return resolve() const slice = buffer.slice(pos, pos + chunkSize) uni.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId, value: slice, success: () => { pos += slice.byteLength setTimeout(next, 20) // 小米缓冲小,必须节流 }, fail: reject }) } next() }) }

4. 接收端:永远做缓冲区

let cache = new Uint8Array(0) uni.onBLECharacteristicValueChange(res => { const chunk = new Uint8Array(res.value) const merged = new Uint8Array(cache.length + chunk.length) merged.set(cache) merged.set(chunk, cache.length) cache = merged // 按协议帧头 + 长度字段判断是否收齐 const frame = parseFrame(cache) if (frame.complete) { handleBusiness(frame.data) cache = new Uint8Array(0) } })

协议设计建议:

  • 帧头(2~4 字节)
  • 长度字段(1~2 字节)
  • 序号/分包标志
  • CRC 校验

六、避坑清单(小米专属)

  • 不假设系统自动协商 MTU
  • setBLEMTU 不在连接成功瞬间调,延 500ms+
  • notify 订阅必须等 success 再发指令
  • 接收端必须有等待缓冲区,不信任“一次回调=完整帧”
  • 用 nRF Connect 先验证,再写业务代码

七、复盘结论

这次问题表面是“小米收不到数据”,本质是:

透传模式 + 默认 MTU 20 + 不协商 + 长帧直发 = 静默截断

vivo/只是被系统“偷偷兜底”了,不代表协议设计是对的。

真正稳的 BLE 通信,应该:

  • 手机端:协商 MTU + 串行分包 + 接收重组
  • 设备端:感知 MTU + 主动分包 + 帧结构自描述
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 10:21:43

Zig语言的区块链:从0到1构建无隐藏控制流的底层区块链系统

引言:共识节点的确定性,不应该依赖“编译器不会做某件事”的假设 区块链节点有一个与其他软件截然不同的硬约束:同一笔交易,在全网数万个异构节点上,必须产生逐字节完全相同的结果。这个约束的严峻之处不在于“通常情况”,而在于极端情况——内存耗尽时、网络中断时、恶…

作者头像 李华
网站建设 2026/10/27 10:16:37

ros2的学习

--------------------2026.10.151.1今天学了怎么用话题订阅接收小海龟的当前坐标位置并计算与目标位置之间的距离,通过发布者发布速度数据让小海龟走到目标位置。1.1.1遇到问题:编译过程遇到一个问题:cmake里节点文件的路径添加错误&#xff…

作者头像 李华
网站建设 2026/10/27 10:13:56

链表的相关介绍

1.链表常用技巧和操作总结 一、常用技巧:1.画图--->直观形象,便于我们理解。2.引入虚拟头结点--->便于处理边界情况,方便我们对链表操作。3.不要吝啬空间,大胆去定义变量。比如: 来一个变量一定义就不用考虑连接…

作者头像 李华