清泛IT社区App Inventor 2 中文社区

搜索

扫码访问移动社区 移动社区,您的掌上技术专家

关注我,精彩不错过! 关注我,精彩不错过!

扫码安装最新版AI伴侣 最新版AI伴侣v2.76

Aia StoreApp上架指南 .aia 源码一站式解决方案 发布日志AI2连接测试ai2Starter模拟器

AppInventor2离线版中文教育版接入DeepSeek VIP会员 享专有教程,免费赠送基础版*技术支持服务! AI2入门必读中文文档AI2拓展IoT专题

查看: 14|回复: 0
打印 上一主题 下一主题

[经验分享] App Inventor 2 BLE扩展源码分析 - WriteBytes vs WriteStrings 23字节硬编码问题

  • TA的每日心情
    开心
    8 小时前
  • 签到天数: 812 天

    [LV.10]以坛为家III

    1145

    主题

    1921

    帖子

    5万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    54504

    AI2中文网VIP弹球达人接水果达人撸猫达人

    跳转到指定楼层
    楼主
    发表于 6 小时前 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    一、问题背景

    用户反馈: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 的问题清单

    #问题影响严重程度
    123字节截断超过23字节的字符串被静默截断,无警告严重
    2Null终止符默认开启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 x data.size()(实际数据大小)
    - 没有硬编码长度限制
    - Android BLE 栈自动处理 MTU 和分片

    这就是为什么用户发现 WriteBytes 能工作而 WriteStrings 不行的根本原因。

    六、NullTerminateStrings 属性

    属性
    属性名NullTerminateStrings
    默认值true(在 BluetoothLEint.java 第1243行)
    效果在字符串末尾追加 \0 字节
    占用1字节
    Designer 可设置是(可改为 false)

    当 NullTerminateStrings = true 时:实际有效载荷 = 23 - 1 = 22字节

    七、解决方案

    方案1:继续用 WriteBytes 发送字符串(推荐)

    将字符串转为字节列表后使用 WriteBytes 发送:

    // 将字符串转为字节列表
    定义 字符串转字节列表(文本)
      初始化局部变量 结果 = 创建空列表
      对于 初始化局部变量 i = 1 到 文本长度(文本)
        追加列表项(结果, 取字符的Unicode码(选择文本(文本, i)))
      结束对于
      返回 结果

    // 发送时
    当 按钮_发送.被点击 时
      初始化局部变量 字节列表 = 字符串转字节列表(文本输入框_命令.文本)
      调用 蓝牙LE1.WriteBytes(字节列表)

    优点:无需修改源码,立即可用
    缺点:需要额外的转换步骤

    方案2:修改 NullTerminateStrings + 限制长度

    如果坚持使用 WriteStrings:
    1. 在 Designer 中将 NullTerminateStrings 设为 false
    2. 确保每次发送的字符串不超过 23 字节

    优点:直接使用 WriteStrings
    缺点:仍有23字节限制

    方案3:修改源码(根本修复)

    // 修复前
    final int len = Math.min(23, str.length + (nullTerminateStrings ? 1 : 0));

    // 修复后 - 使用协商后的 MTU
    final int mtu = gatt.getMtu();
    final int headerSize = 3;  // ATT header
    final int maxPayload = mtu - headerSize;
    final int len = Math.min(maxPayload, str.length + (nullTerminateStrings ? 1 : 0));

    优点:根本解决问题
    缺点:需要修改源码并重新编译扩展

    八、总结

    对比项WriteStringsWriteBytes
    长度限制23字节硬编码(含null终止符仅22字节)无硬编码限制
    Null终止符默认追加 \0不追加
    截断行为静默截断,无警告不截断
    多数据支持只取第一个支持列表
    MTU感知无(但Android栈自动处理)
    推荐度短字符串可用推荐

    一句话结论:WriteStrings 的 23 字节硬编码是 BLE 4.0 最小 MTU 的过时简化,不应硬编码。WriteBytes 走 Integer 路径无此限制,推荐使用 WriteBytes 发送字符串作为 workaround。

    参考资料

    - MIT App Inventor BLE 扩展源码
    - BLE 4.0 规范 - ATT MTU
    - Android BluetoothGatt 文档
    - App Inventor 中文网


    文档版本:2026.05 | 分析日期:2026-05-17 | 作者:App Inventor 2 中文网 www.fun123.cn
    源码采用 Apache 2.0 授权。本文档由 ai2claw 分析整理,仅供学习参考。
    App Inventor 2 中文网 - MIT同步更新的中文本土化平台!v2.76 支持Android 15 & iOS 更新日志
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    © 2026 tsingfun.com, Inc.  沪ICP备2020034476号-1  沪公网安备31011702000040号

    GMT+8, 2026-05-19 15:09 , Processed in 0.020743 second(s), 31 queries .