硬件类型澄清(用户2026-09-09确认):本项目使用扫码器扫描二维码。
rfidData/rfidLen是厂商SDK字段名,不代表此设备采用射频识别;原字节码证据和变量名保留。
审计日期:2026-09-09。方法:AAR ZIP/SHA256 核对、JDK javap 对 Java 字节码逐方法审计、对原始 classes.jar 的纯解析方法做最小 Java 运行验证。没有修改业务仓库,没有初始化串口,没有调用或加载厂商 JNI;没有把会议中的推测当作代码事实。
结论
附件正是本机 ZhctWeightingTableYoukate/app/libs/DeviceSDK-80.1.10.12.20260129.aar 使用的同一包。首页秤体+盘码应审计 PC681ScaleManager,而不是包内名称更像扫码的 SerialScanCodeImpl。PC681 Java 层证实了合帧上送、盘码长度和内容解析,但没有证据证明会议所说“扫码头每 300ms 更新、100ms 合帧沿用旧码”由该 Java SDK 实现。这需要扫码头/秤板固件协议及隔离验证原始帧说明。
PC681 存在可复现的缓存、组帧和异常语义问题,会破坏“每收到一帧就代表新鲜的 100ms 采样”这个前提。它们是代码层已证实缺陷/风险;仍不能据此认定已经找到现场中断根因。
1. 包与范围固定
| 项目 | 结果 |
|---|---|
| 用户附件 AAR 与业务项目 AAR | 完全相同 |
| 文件长度 | 90,728 bytes |
| 两份 AAR SHA256 | adabb1f0f4750081abe131a3baa6f76057ff931e05d7264c56bf6fba5d99aeec |
| PC681ScaleManager.version() | 80.1.10.12.20260129 |
| PC681ScaleManager.class SHA256 | 74b0a5ab481a2eb643e427d0a9f4e26dc827cdb0e53e932ae737f04716525d70 |
| PC681ScaleManager$ReceiveThread.class SHA256 | f23d4bcd00e2bb381111b98ef0c2bd38e455f0d1ce2d538c4d68860da0492fe4 |
| AAR 内容 | classes.jar、manifest/resources,arm64-v8a/armeabi-v7a 下 mserial_port / humansensor / yfgpio JNI 库 |
| 当前有效证据层 | SDK Java 实现、纯解析特征测试 |
| 未覆盖 | 厂商固件/电气层、JNI 原生实现、现场 APK 和日志、真实扫码头的温升/采样周期 |
SerialScanCodeImpl.version() 另返回 85.1.10.12.20260122,该类还带 3000ms 扫码间隔限制;这是同一 AAR 内另一个扫描通道的实现,不能把其版本或 3000ms 逻辑用于解释本次 PC681 路径。两份 AAR 元数据与关键类 SHA 已记录于本节。完整 ZIP 清单及厂商字节码转储仅在本机临时审计目录,不作为仓库交付。
2. 真实数据链和计时含义
org.winplus.serial.utils.SerialPort → FileInputStream → PC681ScaleManager.ReceiveThread → addDataToCache() → parseCacheData() → isFrameValid() → parseFrameData() → Handler.post(mainLooper) → 应用 ScaleDataCallback。
- 接收线程使用
available()>0后read(byte[1024]),无数据时 sleep 10ms。10ms 是空闲轮询,不是秤体或扫码头采样周期。证据:ReceiveThread.javap.txt 第 91 行,run 字节码 56–110。 - 业务回调通过主线程 Handler 异步分发,并不是硬件采样时刻的回调保证。证据:PC681ScaleManager.javap.txt 第 1318 行,postScaleData 字节码 8–22;构造函数第 111 行,字节码 73–83。
ScaleData无采集时间戳、源设备帧序号或码值新鲜度。当前帧的重复码值无法仅凭对象字段区分“同一盘仍在”和“上游旧值沿用”。IntervalPreset.MS_100对应FASTSET:{FRQ=10},其他档位是 50/150/200/250。证据:IntervalPreset.javap.txt 静态初始化 15–27;MS_150实际命令 FRQ=6,不能直接假定精确 150ms。fastSet()只做十六进制转字节、write、flush,没有等待设备 ACK 或读取实际频率。日志“发送成功”不能证明设备已切换到精确 100ms。证据:PC681ScaleManager.javap.txt 第 2572 行,fastSet 字节码 29–58。- PC681 Java 层没有 300ms 扫码采样定时器,也没有把一个码按 100ms 主动重复上送三次的逻辑;它会解析缓存中的旧帧,见 F1。
3. 与中断最相关的发现
F1 — 多个完整帧落入一次 read 时,每次只解析一帧,尾帧可无限等下一次 read
代码证据:PC681ScaleManager.javap.txt 第 677 行 parseCacheData()。字节码 128–176 只解析一个候选帧,177–205 移走已处理帧并直接 return,没有继续消耗完整余帧的循环。ReceiveThread.javap.txt 第 54 行 processData() 仅调用一次 addDataToCache 和 parseCacheData;第 78 行 checkPackageTimeout() 字节码是 0: return。
实际 SDK 验证:一次送入两个完整合法 35 字节帧,第一次只回调 123456,还剩 35 字节;调用 idle timeout 后仍只回调一次;新帧 999999 到达后回调旧尾帧 654321,新帧再留在缓存。
影响:帧龄增加,回调和真实采样时间脱节,空码和有码都可能迟到。若持续出现多帧同读,积压会增大,1024 字节缓存溢出后已有缓存被清空。这一行为本身不凭空创造空码,也不能单独证明“连续空码误离盘”已经发生。
修复方向:按协议持续消费缓存中所有完整帧,保留最后半帧;明确陈旧帧丢弃/上送策略;每帧携带接收时间、序号或可核对的原始数据。需要厂商修改 SDK 或提供可审计解析层替换方案。
F2 — 帧 checksum 正确但 SDK码字段长度不匹配时,SDK 输出业务空码,且不报告解析错误
代码证据:PC681ScaleManager.javap.txt 第 901 行 parseFrameData(),字节码 430–449 从帧偏移 25–26 两个 ASCII 字符解析 rfidLen;456–499 仅在 rfidLen > 0 && 27 + rfidLen <= frameLength - 2 时提取字符串;否则 502–506 设置 rfidData="",509–510 仍返回 ScaleData。
实际 SDK 验证:构造实际只有 6 字节盘码、长度字段声明 7 的 35 字节帧,重新计算正确 checksum;SDK 自己的 isFrameValid(fullFrame) 返回 true,回调一次空字符串,onScaleError 次数为 0。这里的“有效”仅指 SDK 的帧头/帧尾/校验和通过,SDK码字段语义已不一致,不能称为业务有效空盘帧。
影响:若现场发生这种长度异常,应用会收到看似正常的空码。如果应用把空码计作离盘,就把协议异常与真实未识别混为一种业务事件。是否发生该输入必须由现场原始串口 HEX 证明。
补充:SDK 对提取字符串会 trim,合法 rfidLen=5 会返回 12345,并不限定 6 字节。业务层的“必须 6 字符”是应用规则,不能称作 SDK 固定契约。
修复方向:区分 EMPTY_CODE / VALID_CODE / INVALID_FRAME / STALE_FRAME / TRANSPORT_TIMEOUT;SDK码字段长度不一致进入协议错误,不能直接产生可计数的空盘证据。
F3 — 校验字节刚好为 0x3C 时,被组帧逻辑误当作 '<' 帧尾
代码证据:parseCacheData() 字节码 89–122 从索引 28 开始寻找首个 0x3C,直接作为帧尾;没有依据 rfidLen 定位尾部,也没有区分 checksum 与尾标记。isFrameValid() 只校验已经截出来的候选帧。
实际 SDK 验证:构造 6 字节盘码且 checksum=0x3C 的完整帧,完整帧直接 isFrameValid() 为 true;经过 addDataToCache/parseCacheData 后,回调数 0,产生“接收到非法帧,校验失败”,遗留 1 字节实际帧尾。
验证帧(人工协议边界样本,不是现场采集):3e0a0d303030303130303030303031303030303030303100c730363132333435363c3c。尾部 3c3c 依次是 checksum 和真实 '<'。样本温度字段是 0x00C7,是否属于具体硬件的正常温度编码范围尚无厂商协议可验证。
影响:存在格式和校验自洽的输入被误丢弃。它导致缺帧,不自动生成空码;对离盘流程的影响取决于上层对缺帧的处理。
修复方向:依据明确帧长/字段长度完成组帧,并用实际协议边界数据覆盖帧尾字符与 checksum 碰撞。
F4 — 主线程回调和 ScaleData 缺少新鲜度信息,不能将回调次数直接等同时间
代码证据见第 2 节。即使硬件严格每 100ms 发一帧,串口合包、F1 缓存滞留和 Android 主线程排队都能改变应用收到回调的时间。SDK 没有给对象附原始采样时间,因而计数 3/7/10 次只能是“连续收到这些判定样本”,不能不经测量就写成可靠的 300/700/1000ms。
修复方向:应用记录原始读时间、帧解析完成时间、业务回调时间,给判定输入附质量状态;离盘目标应规定最大容忍空档和响应时延,计数阈值需结合真实帧频和计数起点验证。
4. 次要恢复/控制路径发现
- SDK 包内存在 7 字节
>ERR...<命令响应解析分支(parseFrameData 字节码 0–132),但主组帧函数与 isFrameValid 都先要求长度 >=29。这条响应分支在正常接收路径不可达。人工 7 字节响应会保留在缓存,与随后一条有效数据帧合成一个非法候选并被整段丢弃。已用实际 SDK 验证;现场设备是否发送这种响应仍需厂商协议/日志确认。 ReceiveThread.isSerialPortConnected()只检查 SerialPort/InputStream/OutputStream 非空。没有数据却不抛 IOException 的“静默”状态不会因读超时自动重连,因为 checkPackageTimeout 为空。- IOException 或对象缺失触发重连,重连在主 Handler 延迟 2000ms 后执行,失败一次到达上限后 isListening=false。证据:PC681ScaleManager.javap.txt 第 555 / 585 / 1674 行。它是非常有限的恢复策略,不能据此推断现场中断就是串口物理断开。
- stopListening 中断读取线程并关闭流,但无 join;开始监听仅检查 isListening 和 mIsInited。应用应以其实际生命周期封装为准,不把重复 start 当作可靠恢复。此项仅静态风险,没有硬件线程复現,不列为现场根因。
5. 会议说法逐项裁定
| 会议口径 | 本次 SDK 证据裁定 |
|---|---|
| 秤体重量和扫码头盘码在一起返回 | 支持:一个 ScaleData 来自一个帧,含重量、状态、rfidLen、rfidData |
| 秤体设为100ms | 有 FRQ=10 发送能力;实际设备生效和精确时序未验证 |
| 扫码头300ms更新 | PC681 Java 层没有该实现,属待核实固件/硬件事实 |
| 两个100ms中间帧沿用旧码 | 未发现Java主动复制上个码;上游缓存需原始帧验证;SDK自身确有尾帧积压机制 |
| 100ms会发热、不可靠 | 静态软件分析不能证实,需厂商规格/温升隔离验证 |
| 连续3次无数据可认为拿走 | SDK只给数据,没有离盘规则;必须由应用审计和现场验证定义无数据/空码/异常区别 |
| 3改7或9–12次就可解决 | SDK分析不能证明;计数的来源和时间、新鲜度及错误语义仍未闭合 |
| 问题只能是理解错误或代码没按理解实现 | 还必须核实设备固件实际输入、版本一致性、协议异常、SDK缓存与主线程时序这些可观测前提 |
6. 运行验证与交付证据
sdk-verification/harness/ParserAudit.java 直接反射调用原 classes.jar 的 addDataToCache / parseCacheData / isFrameValid 等方法,未重写这些算法。Android Looper/Handler/Log/Context 和 SerialPort 是测试替身;Handler.post 立即执行仅为观测解析结果,不模拟 Android 主线程排队/硬件时序。SerialPort 构造函数会抛 AssertionError,禁止意外进入真实串口路径。不得将该 harness 当作设备稳定性测试。
共 11 项特征检查通过。PASS 表示观测结果匹配断言,其中多项断言专门证明缺陷能够复现,不是 SDK 质量合格。三个基本控制样本验证:合法单帧、两段到达的单帧、trim 后返回码。全部结果见 sdk-verification/parser-audit-results.txt。
从本报告目录复跑(只写本机 /tmp/scan-interruption-audit-20260909/sdk/):
bash sdk-verification/run-parser-audit.sh /absolute/path/DeviceSDK-80.1.10.12.20260129.aar
- 完整只读字节码证据(本机
/tmp/scan-interruption-audit-20260909/sdk/,未入库):com.jocat.device.utils.PC681ScaleManager.javap.txt、com.jocat.device.utils.PC681ScaleManager$ReceiveThread.javap.txt。 - 可复用最小验证源:
sdk-verification/harness/**/*.java、sdk-verification/run-parser-audit.sh。 - 验证结果:
sdk-verification/parser-audit-results.txt;每次脚本复跑会输出自身的临时结果路径。 - 不建议把 AAR/JAR/完整厂商字节码转储放入控制项目 Git;仅保留报告、必要短摘录和自行编写的最小验证源。
7. 建议列入任务目标
- 固定现场 APK、AAR SHA、秤板/扫码头固件版本及持久化参数,与本机版本匹配。
- 获取100ms/300ms双周期的厂商协议定义和带时间戳原始帧;区分真实空码、长度异常、校验失败、无帧和旧帧。
- 厂商修正或说明 PC681 单帧消费、checksum尾字符碰撞、短响应路径问题,以本报告的最小样本验证新包。
- 应用禁止用协议错误、格式拒绝、陈旧帧充当离盘证据;阈值设计同时满足误断率和真实离盘响应时间。
- 做端到端设备回放:持续放盘、1/2/3次空档、超过阈值空档、真实离盘、快速换盘、合包/半包/异常包、断流和恢复;同时验证订单状态和最终金额,按事件序列定位第一处分歧。