410 型优卡特称重台 · 2026.09.09

扫码中断应用代码独立审计

审计日期:2026-09-09。只读审计,未修改业务仓库,未连接设备、生产或网络业务接口。

1. 版本与适用链

真实首页链:DeviceSDK.PC681ScaleManager → YoukateHardwareManager.scaleDataCallback → cleanScanCode(scaleData.rfidData) → HomeActivity.onWeightData → 先 handleWeightData 后 handleYoukateScanCode → handleEmptyScanCode / handleScanResult → Fragment 和取餐记录。

关键入口:hardware/YoukateHardwareManager.java:35,58,108,268,288,316;activity/HomeActivity.java:982,996,1013,1473。下文路径均相对 app/src/main/java/com/cpt/zhct/weighting/。

ScanCodeUtils 不在首页业务链。 它调用 SerialScanCodeImpl(hardware/ScanCodeUtils.java:13,22,26),使用者是 ScanCodeActivity 与 DebugScannerPop。不能把其单独扫码测试结果直接解释为首页 PC681 数据正常/异常。字段名 rfidData 也不能单凭命名认定现场使用 RFID 硬件。

2. 与会议原话逐项对照

会议说法 源码事实 判断
连续 3 次空码认为拿走 HomeActivity:1473-1492 每次空码回调递增,达到 N 调 scanCodeResult(2);当前已有配置 基础计数思路已实现,N 不一定为 3
中间有普通有效码就不算连续空码 HomeActivity:1037 在相同/不同普通有效码分支之前清零 已实现;不是累计若干非连续空码
设置范围 3-10,默认 7 Constants:92-97;SetActivity:815-860;HomeActivity:276,1875-1883 范围与回首页即时生效已实现
没有设置时默认 7;显式设置 3 才保持 3 缺配置时按安装历史分流:升级设备是 3,新装才是 7;读取安装状态失败也按升级处理 会议摘要遗漏关键迁移分支,现场升级后仍可能是 3
每 100ms 做一次判断 每次成功进入业务的 PC681 回调计一次,未按时钟判断;配置支持 50/100/150/200/250ms 仅在实际回调严格 100ms 时才能近似换算
厂商建议 900-1200ms 代码只有 3-10 帧阈值,没有独立毫秒超时;100ms 下默认 7 帧并不等于 900-1200ms,10 帧也不覆盖整个 1200ms 上限 未实现厂商最新建议的时间契约;建议本身尚待证据确认
一次空就是硬件没读到/盘拿走 应用将任何长度非 6 的 SDK 非空码直接变为空;SDK 代理亦确认有协议长度异常变空路径 空码语义混合,不能据此证明物理离盘
程序按称体稳定帧计数 HomeActivity:996-998 无条件执行扫码;handleWeightData:1953 任意状态即置初始数据已就绪 离盘未受秤稳定标志过滤;进取餐另有重量窗口检查
测试覆盖九十几 仓库现有测试包含阈值配置/辅助策略,没有实际 HomeActivity 连续帧链路测试 未发现可回读的覆盖率分母、场景集或现场证据,不能认可覆盖百分比

会议文件是待审查证据;纪要中的“已排除外部干扰/硬件故障”不是本次审计已证实结论。

3. 主要发现(按与本次问题的相关性排序)

APP-01:异常码和真实无盘合并为同一种离盘输入(P1,机制已确认,现场触发待证)

YoukateHardwareManager:316-323 先要求原始 code.length()==6,否则返回空字符串;不记录原始码长度/原因。该空字符串在 HomeActivity:1032-1034,1473-1492 与真实无盘完全相同。

确定性实际方法测试:当前盘为 123456,阈值 3,连续收到三个非空 12345,应用在第三次执行离盘。SDK 独立审计也已验证合法校验、rfidLen=5 的帧确实可返回 12345。所以“有数据但码异常”可以沿真实接入链触发“扫码结束”。这证明可发生路径,不证明现场已经出现该串口帧。

还有两个边界:六位码追加 CRLF 会先因长度 8 被丢弃,不过 PC681 当前 parser 已先 trim 首尾,正常首页路径通常不会遇到这个具体例子;不能把 CRLF 例子冒充现场根因。六字符内嵌控制字符清理后不足六位则进入 HomeActivity 的非法长度告警分支(:770-777)。

建议业务输入区分 有效码 / 明确无盘 / 非法码或协议错误 / 无新数据,记录异常原因,不由一个空字符串包办。

APP-02:升级安装缺配置仍取 3,默认 7 的理解不成立(P1,代码及实际类测试确认)

antiescape/TrayRemovalConfirmationCountConfig:18-22,41-48 读取安装时间;TrayRemovalConfirmationCountPolicy:22-26 对升级安装返回 3,新装返回 7。已有合法值保留,非法值修复为 7。

实际编译此 Config/Policy 类并以可控 PackageInfo/MMKV 边界验证:升级无键=3、新装无键=7、已保存3=3、非法11=7。此次不是说迁移保留行为必然设计错误,而是现场不能仅凭“默认7”假设所有设备已扩大窗口。应采集实际配置与安装升级时间;是否把升级设备也改为7需先明确业务迁移决定。

APP-03:帧数并非独立扫码次数,更不是固定毫秒窗口(P1,计数行为已确认)

HomeActivity:1473-1492 只有 ++ 与阈值比较。mFirstEmptyScanElapsedMs 仅在 PerformanceTrace 开启时记性能日志,不参与判断(:1477-1487)。实际方法无延时连调三次,数微秒内即完成离盘。该测试证明代码没有最短持续时间,未模拟物理设备。

SDK 代理确认串口解析后通过主线程 Handler 投递,ScaleData 未附设备采集时间/序号;主线程阻塞恢复时排队回调的处理间距可能短于物理间隔。SDK 还存在单次 read 只解析一个包的问题,陈旧缓存会改变数据新鲜度。应用当前不能识别陈旧/重复观测。

名义 T=100ms 时,从第一个空回调到 N 个空回调是 (N-1)×T:N=3/7/10 分别 200/600/900ms;从最后有效回调量约是 300/700/1000ms。物理离盘到最终判定还要叠加扫码采样相位、缓存、串口与主线程排队,不能用 N×100 直接当真实离盘耗时。YoukateHardwareManager:179-229 又允许称体间隔变更,N不随T转换。

APP-04:抬高空帧阈值后,快速换盘会走直接替换旧会话的支路(P1,源码链确认,需业务回归)

触发:A 盘已经进入取餐,A→B 之间空帧不足 N,随后读到合法 B。HomeActivity:1037-1040 直接识别B;:787-800 覆写 mCurrentCode/mLastValidCode;:856-861 调 switchToOngoingFragment;:456-480 没有已在取餐的拒绝/收尾判断,直接重置起始重量并替换 Fragment。正常保存旧单的路径在 scanCodeResult(2)(:611-660),异码替换没有先执行该路径。

因此提高 N 能降低短暂空码误离盘,同时加大两盘快速交接时旧会话尚未收尾的机会。精确抽取的实际路由方法已验证 B 会触发新识别而没有离盘回调;旧重量是否丢失/错归需包含 Fragment/数据库的完整回归。这里并非当前物理中断的已证实根因,但属于同一改阈值方案必须验证的业务边界。

APP-05:现有日志不能闭合“串口→解析→过滤→计数→订单结束”的证据链(P1,审计确认)

建议带有限环形缓冲保存故障前后帧:单调时间、raw frame摘要/长度/校验、SDK解析状态、raw/normalized码、配置N/T、计数前后值、Activity/会话ID、结束原因;餐盘码按项目隐私边界脱敏。没有这些现场证据,不能下“扫码头硬件故障”或“仅改N解决”的结论。

APP-06:普通有效码确实归零,但解除卡是显式例外(P2,实际方法确认)

HomeActivity:1021-1029 对 888880-888899 解除卡在归零前直接 return,UnlockCodePolicy:29-42 明确把它消费掉。输入 空、空、888880、空,N=3时会结束,因此“任何非空帧都会清零”不准确;“普通餐盘有效码清零”准确。这可能是业务有意忽略管理卡,应确认连续性应以“无普通餐盘码”还是“任何无扫码数据”定义。报警支路又有单独规则(HomeActivity:1044-1112),应分别覆盖,不能用普通支路测试代表全部报警模式。

4. 与持续运行、页面切换和资源有关的补充风险

这些是源码中的独立风险/缺口,不能自动解释本次连续空码中断。

  1. 回首页的状态不一致(P1,静态链):HomeActivity:352-356 每次 onResume 强制切打餐前,但 mCurrentCode 只在 onCreate 或离盘时清空;onStop :572-592 仅解绑回调。如果餐盘未拿走而页面退回,收到同盘码会在 :1038 因相等跳过重新进入取餐,页面/会话可能不一致。暂停前的普通空帧计数也没有无条件清零,配置没变时 :1878-1881 保持原值。需要“放盘中进设置/回来/同盘持续/再移盘”的真实生命周期测试。
  2. 异步查询回包过期(P1,静态链):HomeActivity:902-974 的人员查询结果在验证 result.equals(mCurrentCode) 之前就改全局 mCurrentStaffInfo(:913,941,962),最后无条件发UI事件(:972)。该相等检查仅保护部分告警。快速换盘/误离盘后的迟到回包可能覆盖当前人,需要会话ID/请求代次验证。
  3. 稳定等待的线程和遍历问题(P2,静态风险):HomeActivity:814-849 在工作线程 while 等10条重量数据稳定,未持有可在 onStop/onDestroy 取消的 Disposable/句柄;mCurrentCode 不是 volatile(:126),且生命周期不必修改它。DataProcessor:22,29-35,43-55 使用 synchronizedList,但 Collections.max/min 的复合遍历没有外围同步,主线程 add/remove 与工作线程遍历可能发生并发异常。ThreadUtils :60-65 没有显式 onError 恢复。尚未做Android竞争复现,不声称现场因此崩溃。
  4. 存储低于3GB时日志循环缺退出(P2,静态确定条件):LocalFileRecorder:51-57 一直删最旧日志直到可用空间>=3GB;若没有可删 local_log_ 文件或删除失败,循环没有失败/无进展退出。写日志任务是IO线程异步投递,持续调用可能积累消耗。未访问设备磁盘,不认为这是本次已发生情况。
  5. 运行期无回调恢复未实现(P2,范围缺口):HardwareManager startListening 仅依据内部 listening 布尔(:104-109),没有最后回调时间监测;Home onError仅debug log。初始化有10s兜底,失败后2s退出(HomeActivity:2046-2060),不等于运行中扫码停止会自动恢复。原始串口无回调与有效帧空码是两个不同故障,前者不会递增当前空帧计数。
  6. SDK release 隔离存在但调试链不同:YoukateHardwareManager:146-171 绕过 SDK native close;ScanCodeUtils:37-40 仍直接调用 SerialScanCodeImpl.release。后者只能作为单独调试关闭风险,不能当首页问题根因。

5. 历史“SDK接收缓存竞态修复”是否绕过解析

已检索当前 app/src/main 全部源码:没有 receiveCache、parseCacheData、反射修改 PC681 字段;仅 NewCrashHandler 反射 Build 元数据,且与串口无关。git log -S receiveCache 与 hardware 目录 git log -S setAccessible 没有匹配提交。

266709fecdfe27586d505bc9782a242447db84da(2026-08-01、VWCG-1147)真实修改:hardwareListener 加 volatile;pause 从 isListening=false / mReadThread=null / mCacheLen=0 / listening=false 改为只 hardwareListener=null,现于 YoukateHardwareManager:120-123。这减少切页与SDK接收线程缓存状态竞态,但不更换SDK parser、不排空剩余帧、不验证rfid长度。当前 SDK 解析缺陷仍在首页实际调用路径上,是否现场触发需实帧证据。

6. 本轮确定性验证与局限

产物 /tmp/scan-interruption-audit-20260909/app-logic-tests/:

7. 建议形成的任务目标与问题清单

  1. 冻结现场实际 APK/SHA、SDK哈希、设备型号/固件、称体实际频率、扫码实际频率、N值及安装升级来源,解决“讨论的版本/默认值是否就是现场值”。
  2. 统一输入与离盘语义,明确有效盘、明确无盘、协议异常、未知/无新帧、过期重复帧,解决“空字符串不能等同真实离盘”。
  3. 为SDK解析确认的问题建立串口字节级回放与厂商修复闭环;分别验完整帧、半包、粘包、长度异常、校验/帧尾冲突、idle残包,解决“数据先被SDK损坏/滞留还是应用误判”。
  4. 用真实时间轴验证N=3/7/9/10及50-250ms秤频率,量化误离盘率、离盘P95/P99耗时、恢复耗时,决定帧数/持续时间/新鲜度策略;不能先把增加N当最终修复。
  5. 回归完整取餐会话:同盘短丢码、换盘不足N空帧、异码跳变、报警/解除卡、带盘切页、迟到查人回包、断读恢复;确保无重复单、无漏单、重量与人一致。
  6. 建立默认可用且容量受控的故障证据链;用现场日志、录屏/实物动作时间共同证明至少一个中断事件的责任层,再列修复验收。

持久化复跑入口:python3 app-verification/run_audit.py /absolute/path/to/ZhctWeightingTableYoukate;脚本从目标源码动态提取方法,写入自建临时目录,结束即清理。说明见 应用验证说明。