App Inventor 2 蓝牙BLE扩展源码分析 - WriteBytes vs WriteStrings 23字节硬编码问题

« 返回首页

一、问题背景

用户反馈: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字节不应硬编码

  1. MTU 是可协商的:BLE 4.2+ 支持协商更大的 MTU(最大512字节)
  2. Android BLE 栈自动处理分片gatt.writeCharacteristic() 会根据协商后的 MTU 自动分片
  3. 硬编码过时了:这个值是 BLE 4.0 的最小 MTU,现代设备普遍支持更大的 MTU
  4. 正确做法:应使用 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:

  1. 在 Designer 中将 NullTerminateStrings 设为 false
  2. 确保每次发送的字符串不超过 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 开源代码分析整理,源码采用 Apache 2.0 授权。 本文档由 ai2claw 🐝 分析整理,仅供学习参考。

文档反馈