一、问题背景
用户反馈:WriteBytes 发送字符串硬件能收到,Writestrings 发送却收不到。
同样的字符串数据,通过 WriteBytes 方法发送时硬件正常接收,但通过 Writestrings 方法发送时硬件收不到或收到截断数据。
源码仓库: mit-cml/appinventor-extensions (extension/bluetoothle 分支)
关键文件:
- BluetoothLE.java(2975行)-- 公开API层
- BluetoothLEint.java(3165行)-- 内部实现层
二、核心发现:23字节硬编码限制
位于 BLEWriteOperation.write() 方法第678-690行:
if (mClass == String.class) { byte[] str = ((String) data.get(0)).getBytes(); final int len = Math.min(23, str.length + (nullTerminateStrings ? 1 : 0)); byte[] buffer = new byte[len]; System.arraycopy(str, 0, buffer, 0, len - (nullTerminateStrings ? 1 : 0)); if (nullTerminateStrings) { buffer[len - 1] = 0; } characteristic.setValue(buffer); }
问题核心:Math.min(23, ...) 直接将发送数据截断为最多23字节,没有任何警告或异常。
WriteBytes 走 Integer 路径,无硬编码长度限制。缓冲区大小等于实际数据大小。
三、23字节的来源
| 项目 | 值 | 说明 |
| BLE 4.0 ATT MTU 默认值 | 23 字节 | 规范规定的最小值 |
| ATT 头部开销 | 3 字节 | 操作码 + 句柄 |
| 实际有效载荷 | 20 字节 | 23 - 3 = 20 |
| NullTerminateStrings 开销 | 1 字节 | 默认追加 |
| WriteStrings 实际可用 | 22 字节 | 23 - 1(null终止符) |
为什么23字节不应硬编码:
- MTU 是可协商的:BLE 4.2+ 支持协商更大的 MTU(最大512字节)
- Android BLE 栈自动处理分片
- 硬编码过时了:这个值是 BLE 4.0 的最小 MTU
- 正确做法:应使用协商后的 MTU 值
四、WriteStrings 的问题清单
- 23字节截断 -- 超过23字节的字符串被静默截断,无警告(严重)
- Null终止符默认开启 -- nullTerminateStrings = true,占用1字节,实际可用仅22字节(中等)
- 只取 data.get(0) -- 只写第一个字符串,忽略列表中的其他字符串(中等)
- 无 MTU 协商感知 -- 不查询当前连接的 MTU 大小(中等)
五、解决方案
方案1(推荐):用 WriteBytes 发送字符串
将字符串转为字节列表后使用 WriteBytes 发送。WriteBytes 走 Integer 路径无23字节限制。
方案2:修改 NullTerminateStrings
在 Designer 中将 NullTerminateStrings 设为 false,确保每次发送的字符串不超过 23 字节。
方案3:修改源码(根本修复)
使用协商后的 MTU 值替代硬编码的 23。
六、总结
| 对比项 | WriteStrings | WriteBytes |
| 长度限制 | 23字节硬编码 | 无硬编码限制 |
| Null终止符 | 默认追加 | 不追加 |
| 截断行为 | 静默截断 | 不截断 |
| 推荐度 | 短字符串可用 | 推荐 |
结论:WriteStrings 的 23 字节硬编码是 BLE 4.0 最小 MTU 的过时简化,不应硬编码。推荐使用 WriteBytes 发送字符串作为 workaround。
参考资料
文档版本:2026.05 | 作者:App Inventor 2 中文网