- 一、问题背景
- 二、核心发现:23字节硬编码限制
- 三、23字节的来源
- 四、WriteStrings 的问题清单
- 五、WriteBytes 为什么能工作
- 六、NullTerminateStrings 属性
- 七、解决方案
- 八、相关源码位置索引
- 九、总结
- 参考资料
- 参考资料与版权声明
一、问题背景
用户反馈:WriteBytes 发送字符串硬件能收到,WriteStrings 发送却收不到。
同样的字符串数据,通过 WriteBytes 方法发送时硬件正常接收,但通过 WriteStrings 方法发送时硬件收不到或收到截断数据。
源码仓库: mit-cml/appinventor-extensions (extension/bluetoothle 分支)
关键文件:
BluetoothLE.java(2975行)— 公开API层BluetoothLEint.java(3165行)— 内部实现层
二、核心发现:23字节硬编码限制
2.1 WriteStrings 的写入路径
位于 BLEWriteOperation.write() 方法第678-690行:
if (mClass == String.class) {
byte[] str = ((String) data.get(0)).getBytes();
// ↓ 23字节硬限制!
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; // ← 默认追加 null 终止符
}
characteristic.setValue(buffer);
}
问题核心:Math.min(23, ...) 直接将发送数据截断为最多23字节,没有任何警告或异常。
2.2 WriteBytes 的写入路径
位于同一方法第703-712行:
} else {
byte[] contents = new byte[size * data.size()]; // ← 没有长度限制!
long value;
int i = 0;
for (Number n : (List<? extends Number>) data) {
value = n.longValue();
for (int j = 0; j < size; j++) {
contents[i++] = (byte)(value & 0xFF);
value >>= 8;
}
}
characteristic.setValue(contents);
}
关键差异:Integer/Number 路径没有硬编码长度限制,分配的缓冲区大小等于实际数据大小。
三、23字节的来源
| 项目 | 值 | 说明 |
|---|---|---|
| BLE 4.0 ATT MTU 默认值 | 23 字节 | 规范规定的最小值 |
| ATT 头部开销 | 3 字节 | 操作码 + 句柄 |
| 实际有效载荷 | 20 字节 | 23 - 3 = 20 |
| NullTerminateStrings 开销 | 1 字节 | 默认追加 \0 |
| WriteStrings 实际可用 | 22 字节 | 23 - 1(null终止符) |
为什么23字节不应硬编码
- MTU 是可协商的:BLE 4.2+ 支持协商更大的 MTU(最大512字节)
- Android BLE 栈自动处理分片:
gatt.writeCharacteristic()会根据协商后的 MTU 自动分片 - 硬编码过时了:这个值是 BLE 4.0 的最小 MTU,现代设备普遍支持更大的 MTU
- 正确做法:应使用
characteristic.getWriteType()和协商后的 MTU 值
四、WriteStrings 的问题清单
| # | 问题 | 影响 | 严重程度 |
|---|---|---|---|
| 1 | 23字节截断 | 超过23字节的字符串被静默截断,无警告 | 🔴 严重 |
| 2 | Null终止符默认开启 | nullTerminateStrings = true,占用1字节,实际可用仅22字节 |
🟡 中等 |
| 3 | 只取 data.get(0) | 只写第一个字符串,忽略列表中的其他字符串 | 🟡 中等 |
| 4 | 无 MTU 协商感知 | 不查询当前连接的 MTU 大小 | 🟡 中等 |
五、WriteBytes 为什么能工作
5.1 字符串到字节的转换
toIntList() 方法将字符串的每个字节转为 Integer:
字符串 "Hello" → 字节 [72, 101, 108, 108, 111]
→ Integer列表 (72, 101, 108, 108, 111)
5.2 Integer 路径无限制
Integer/Number 路径在写入时:
- 缓冲区大小 =
size × data.size()(实际数据大小) - 没有硬编码长度限制
- Android BLE 栈自动处理 MTU 和分片
5.3 所以同样的字符串通过 WriteBytes 能完整发送
这就是为什么用户发现 WriteBytes 能工作而 WriteStrings 不行的根本原因。
六、NullTerminateStrings 属性
| 属性 | 值 |
|---|---|
| 属性名 | NullTerminateStrings |
| 默认值 | true(在 BluetoothLEint.java 第1243行) |
| 效果 | 在字符串末尾追加 \0 字节 |
| 占用 | 1字节 |
| Designer 可设置 | 是(可改为 false) |
当 NullTerminateStrings = true 时:
- 实际有效载荷 = 23 - 1 = 22字节
- 一个23字节的字符串会被截断为22字节 +
\0
七、解决方案
方案1:继续用 WriteBytes 发送字符串(推荐 workaround)
将字符串转为字节列表后使用 WriteBytes 发送:
// 将字符串 "Hello" 转为字节列表
定义 字符串转字节列表(文本)
初始化局部变量 结果 = 创建空列表
对于 初始化局部变量 i = 1 到 文本长度(文本)
追加列表项(结果, 取字符的Unicode码(选择文本(文本, i)))
结束对于
返回 结果
// 发送时
当 按钮_发送.被点击 时
初始化局部变量 字节列表 = 字符串转字节列表(文本输入框_命令.文本)
调用 蓝牙LE1.WriteBytes(字节列表)
优点:无需修改源码,立即可用 缺点:需要额外的转换步骤
方案2:修改 NullTerminateStrings + 限制长度
如果坚持使用 WriteStrings:
- 在 Designer 中将
NullTerminateStrings设为false - 确保每次发送的字符串不超过 23 字节
优点:直接使用 WriteStrings 缺点:仍有23字节限制
方案3:修改源码(根本修复)
将 BluetoothLEint.java 中的硬编码修复:
// 修复前
final int len = Math.min(23, str.length + (nullTerminateStrings ? 1 : 0));
// 修复后 - 使用协商后的 MTU
final int mtu = gatt.getMtu(); // 或保持一个合理的默认值如 512
final int headerSize = 3; // ATT header
final int maxPayload = mtu - headerSize;
final int len = Math.min(maxPayload, str.length + (nullTerminateStrings ? 1 : 0));
优点:根本解决问题 缺点:需要修改源码并重新编译扩展
八、相关源码位置索引
| 内容 | 文件 | 行号 |
|---|---|---|
| WriteStrings 入口 | BluetoothLE.java | ~678 |
| 23字节硬编码 | BluetoothLEint.java | ~680 |
| WriteBytes 入口 | BluetoothLE.java | ~703 |
| NullTerminateStrings 默认值 | BluetoothLEint.java | ~1243 |
| BLEWriteOperation.write() | BluetoothLEint.java | ~670-720 |
| toIntList() 转换 | BluetoothLEint.java | - |
九、总结
| 对比项 | WriteStrings | WriteBytes |
|---|---|---|
| 长度限制 | 23字节硬编码(含null终止符仅22字节) | 无硬编码限制 |
| Null终止符 | 默认追加 \0 |
不追加 |
| 截断行为 | 静默截断,无警告 | 不截断 |
| 多数据支持 | 只取第一个 | 支持列表 |
| MTU感知 | 无 | 无(但Android栈自动处理) |
| 推荐度 | ⚠️ 短字符串可用 | ✅ 推荐 |
一句话结论:WriteStrings 的 23 字节硬编码是 BLE 4.0 最小 MTU 的过时简化,不应硬编码。WriteBytes 走 Integer 路径无此限制,推荐使用 WriteBytes 发送字符串作为 workaround。
参考资料
| *文档版本:2026.05 | 分析日期:2026-05-17 | 作者:App Inventor 2 中文网 www.fun123.cn* |
参考资料与版权声明
原文来源
- MIT App Inventor BLE Extension - MIT CML
- MIT App Inventor GitHub - MIT CML
版权声明
本文档基于 MIT App Inventor 开源代码分析整理,源码采用 Apache 2.0 授权。 本文档由 ai2claw 🐝 分析整理,仅供学习参考。
扫码添加客服咨询