扫码枪响了,应用却没收到:一次 Android USB 线材问题排查

外接 USB 扫码枪偶尔"滴滴"两声,应用没有任何反应。刚插入时容易出现,休眠后再次使用时也容易出现。

这种现象很容易让开发者怀疑监听没启动、首条数据被丢弃,或者休眠恢复后需要重新初始化 SDK。这次我们沿着日志往下查,换过工位机,又更换了连接线,最终发现:故障高度指向原来的线材或接头连接问题。换线后,密集掉线停止,休眠激活后的第一次扫码也完整到达了业务页面。

这篇文章记录排查过程。结论来自现场对照和日志,没有对旧线做电气测量,也没有把它重新接回去进行反向复现,因此不把"旧线内部具体哪里坏了"写成已经确认的事实。

1. 先把"扫不到"拆成几段

现场使用 React Native Android 应用,同时支持外接键盘模拟扫码枪和厂商 POS HID 扫码设备。本次异常的外接设备在系统中显示为:

text 复制代码
Manufacturer: LWTEK
Product: Barcode Scanner
VID:PID: 0581:0115
Input device: LWTEK Barcode Scanner

这把枪实际走的是 USB HID Keyboard 链路:

text 复制代码
扫码枪识别条码
  → 通过 USB 发送键盘输入
  → Android 输入系统产生 KeyEvent
  → MainActivity.dispatchKeyEvent()
  → ScanKeyEventHelper 拼出完整条码
  → UidBroadcastScanModule 发出 RN 事件
  → React Native 业务页面处理

扫码枪发声,只能说明设备发出了提示。没有对应型号说明书时,不能仅凭"两声"确定它表示识别成功、唤醒,还是传输失败,更不能据此认定应用一定收到了数据。

排查的第一个问题应该是:这条链路在哪一段停止了?

2. 第一台工位机:USB 还没建立好,应用就无从接收

第一台工位机是 RK3576 平台。日志中最关键的内容来自 USB 内核层:

text 复制代码
Device not responding to setup address.
device not accepting address 55, error -71
device descriptor read/all, error -32
attempt power cycle
unable to enumerate USB device

系统在尝试读取设备信息、分配地址时就失败了,并反复尝试重新枚举设备。此时还没有稳定的键盘输入设备,继续调整 JavaScript 监听或者条码缓存,无法补回设备没有成功传上来的数据。

后来直接读取工位机内核日志,还看到了:

text 复制代码
usb 1-1-port7: Cannot enable. Maybe the USB cable is bad?

这条提示让线材成为重点怀疑对象,但它本身不是检测报告。线材、接头、设备固件、供电以及主机侧问题,仍需要对照验证。

Linux 官方文档指出,EPROTO 一类错误可能与设备、固件或线缆有关;EPIPE 在传输完成状态中可以表示端点停滞。应结合错误发生的操作和上下文分析,不能把 -71 或 -32 直接翻译成"线坏了"。参考:USB Error codes。

同一份日志也出现过完整条码接收和业务处理记录。这说明接收链路至少在连接稳定时可以工作,问题具有间歇性;它不能证明应用的所有场景都没有问题。

3. 换了一台工位机,问题仍然出现

接下来把扫码枪接到另一台工位机:RK3288,Android 7.1.2。

如果问题只来自第一台机器的某个 USB 口或者特定系统实现,换机可能会改变表现。但新机器依旧出现密集的设备移除、重新接入,并且内核仍报 error -32。

在 16:59:23~17:00:49 的日志窗口里,Android 输入系统记录到这把 LWTEK 扫码枪:

事件 次数
Device removed 16
Device added 16

这是窗口内的事件计数,不代表每次移除和接入都能一一配对,也不等于发生了 16 次人工插拔。现场操作人员明确表示没有反复插拔。

其中一段时序尤其直观。以下为日志节选,条码统一替换为 [BARCODE]:

text 复制代码
17:00:24.535 InputReader: Device added: LWTEK Barcode Scanner
17:00:24.705 ScanKeyEventHelper: scan success [BARCODE]
17:00:24.705 UidBroadcastScanModule: emit [BARCODE]
17:00:24.839 ReactNativeJS: [QualityTaskList] 扫码: [BARCODE]
17:00:25.220 InputReader: Device removed: LWTEK Barcode Scanner

设备刚连接,应用收到了完整条码,随后系统又把设备移除了。

这把定位范围进一步缩小了:故障跨两台工位机复现,扫码枪和连接线成为共同的重点排查对象。 两台机器仍使用同一套应用,因此换机也不能单独排除全部软件因素。

4. "休眠"要区分系统自动挂起和设备自身行为

因为问题经常出现在休眠之后,我们检查了扫码枪对应 USB 设备的电源管理状态:

text 复制代码
power/control: on
power/runtime_status: active
power/autosuspend_delay_ms: 2000

这里容易误读的是 2000:它不表示设备一定会在两秒后休眠。

根据 Linux USB 电源管理文档,power/control=on 表示不允许该设备自动挂起;auto 才表示允许自动挂起。延迟值需要结合这个开关解释。参考:Power Management for USB。

所以,采样时的状态不支持"扫码枪被系统 USB 自动挂起"这个判断 。但一次状态读取不能代表整个历史过程,on 也不代表禁止整机休眠,更不能阻止扫码枪固件进入自身的省电状态。

系统 USB 电源管理、扫码枪自身休眠、物理连接异常,是需要分别验证的几个方向。

5. 更换连接线后,密集掉线停止了

保持第二台工位机和应用不变,更换扫码枪连接线后,现场反馈连续扫码正常,休眠后再次使用也正常。

最新日志中,之前短时间内反复移除、接入和枚举报错的现象停止了。不过仍能看到个别断开和重连记录。操作人员确认其中一次没有手动插拔,而是在进行休眠、激活测试。

这时不能简单得出"只要有 USB disconnect,就说明换线没用"的结论。需要继续对齐现场动作,观察重连是否顺利,以及之后的第一条扫码是否完整到达。

最后一次测试的记录是:

时间 观察到的事件
17:23:23.800 输入系统移除扫码枪
17:23:31.327 输入系统重新加入扫码枪
17:23:32.351 原生层拼出完整条码,并发出 RN 事件
17:23:32.354 业务页面收到完整扫码结果

现场确认:扫码枪休眠后,按一下激活,随后第一次扫码成功。

这一次,从原生层发出完整条码,到业务页面打印接收日志,相隔约 3 毫秒。这个数字只描述本次 RN 事件到达业务层的日志间隔,不是扫码枪的识别耗时,也不是完整唤醒耗时。

同样,日志里约 7.5 秒的"设备移除到重新加入"间隔,也不能直接当作唤醒耗时:我们没有记录用户按下激活键的准确时间。

结合现场操作,这些记录支持"休眠测试期间发生断开,激活后顺利重连并完成首扫"的解释;它们不能证明所有扫码枪都应当这样休眠,也不能单独证明断开一定由固件休眠触发。

6. 最终能下什么结论?

把事实和推断分开,排查记录才有复用价值。

结论 证据与边界
异常时存在真实的 USB 掉线和枚举失败 内核及 Android 输入系统都有对应记录
这次故障在两台不同平台工位机上复现 RK3576、RK3288 均有记录
换线后连接表现明显改善 密集掉线停止,连续扫码及休眠激活后首扫成功
旧线或接头连接问题高度可疑 来自换线对照;未测量旧线,也未接回旧线反向验证
不能确定旧线的具体失效方式 没有证据区分内部断续、接头接触、电压降或信号质量问题
本次无需靠修改扫码接收代码解决已观察到的现象 排查期间未修改扫码逻辑,换线后完成了上述验证

更准确的处理记录是:更换连接线后故障未在本轮验证中复现,原线材或接头高度可疑,后续继续观察。

如果还需要进一步确认因果,可以在受控测试环境中把旧线接回去,检查同一故障是否重新出现。这次现场没有执行这个步骤。

7. 下次可以直接复用的排查方法

先保留完整日志,再筛选

下面是本次用到的只读诊断命令。先通过 adb devices -l 确认目标设备,再设置序列号:

bash 复制代码
adb devices -l
SCANNER_STATION_SERIAL='替换为目标工位机序列号'

adb -s "$SCANNER_STATION_SERIAL" shell dmesg > usb-kernel.log
adb -s "$SCANNER_STATION_SERIAL" logcat -d -v threadtime > usb-logcat.log
adb -s "$SCANNER_STATION_SERIAL" shell cat /proc/bus/input/devices

部分 Android 系统会限制读取内核日志,需要使用设备允许的调试方式。以下 rg 命令在电脑上执行:

bash 复制代码
rg -n 'USB disconnect|error -71|error -32|unable to enumerate|Barcode Scanner' usb-kernel.log
rg -n 'LWTEK|dispatchKeyEvent|scan success|UidBroadcastScanModule|扫码:' usb-logcat.log

这次有一个实际教训:一开始只看 dmesg 的末尾几百行,其他系统日志把关键 USB 记录挤出了显示范围。保存完整日志后再筛选,才看到了连续掉线的全貌。

日志应尽量在复现后立即采集;保存文件也无法找回已经从系统环形缓冲区中被覆盖的内容。不要在采集前清空日志。

用 VID/PID 找设备,不要固定 USB 路径

同一把枪在两台工位机上的路径分别是 1-1.7 和 1-1.6。排查脚本不能默认端口路径永远不变。

本次可以按 0581:0115 查找,然后读取电源状态:

bash 复制代码
adb -s "$SCANNER_STATION_SERIAL" shell '
for d in /sys/bus/usb/devices/*; do
  [ -f "$d/idVendor" ] || continue
  [ -f "$d/idProduct" ] || continue
  if [ "$(cat "$d/idVendor")" = "0581" ] &&
     [ "$(cat "$d/idProduct")" = "0115" ]; then
    echo "$d"
    cat "$d/power/control"
    cat "$d/power/runtime_status"
  fi
done
'

这是本次设备的 VID/PID,其他型号需要替换。如果接入多把相同型号的设备,还需要结合序列号、物理端口等信息区分。

按最早失败的那一层继续查

日志表现 优先排查方向
描述符读取失败、无法枚举 线材、接头、供电、设备固件、主机或 Hub
USB 设备稳定,但应用没有 KeyEvent 输入焦点、窗口、输入模式、事件拦截
有 KeyEvent,但没有完整条码 结束符、字符间隔、解析和缓冲策略
原生层已有完整条码,JS 没收到 RN 事件发送、订阅生命周期、线程拥堵
业务层收到条码,但页面没有结果 去重、业务状态、接口和页面处理

这些是调查入口,不是仅凭一行日志就能确定的根因。尤其不要把一次成功扫码,扩大成"所有软件路径都没有问题"。

做对照时一次只改一项

如果同时换线、换口、升级 SDK、增加重试,即使最后恢复,也很难知道哪项起了作用。

这次先换工位机,确认故障仍存在;再换连接线,观察到明显改善;最后单独验证休眠激活后的首扫。这样的过程,比直接给代码增加延迟或重连循环更容易留下可判断的证据。

8. 留下的经验

"应用没收到"是用户看到的结果,故障位置要靠逐层证据确定。 先确认 USB 是否稳定,再看 KeyEvent、条码组包、RN 事件和业务处理,可以避免在错误的层次修改代码。

换线是有效的对照实验,但要记录它改变了什么。 它既换了线材,也重新插合了接头,所以这次能高度怀疑原连接组件,不能凭现象区分内部断线、接触或供电问题。

休眠相关问题要验到第一条业务数据。 "设备重新连接"只是中间状态;用户激活后第一枪能否完整到达业务页面,才是这次真正需要验证的结果。

这次最终没有修改扫码接收逻辑。通过保留日志、换机对照、换线以及休眠首扫验证,我们把一个看起来像软件监听失效的问题,收敛到了原线材或接头这一侧,也避免把一次有限的现场验证写成永久保证。

相关推荐
alexhilton13 小时前
Room Fts4:全文搜索替代 SQL LIKE
android·kotlin·android jetpack
hai_android18 小时前
把 Android App 打包成系统应用(System App)
android·kotlin·android jetpack
JMchen18 小时前
【Jetpack 内核实战 01】开篇:Jetpack 组件全家桶核心逻辑与专栏完整规划
android·kotlin·android jetpack
骑着蜗牛撵大象32720 小时前
Linux 进程间通信之管道:从 pipe() 的内核语义到 FIFO 落地排错
android·java·linux·系统编程·fifo·管道·进程间通信
hunterandroid21 小时前
Retrofit 网络请求的异常处理与重试策略实战
android
sejinan21 小时前
极致轻量与秒级起播:NativeWasmTv 开源硬核技术解析与升级版体验
android·开源·webview·tv
小羊没烦恼!21 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp
mmsx21 小时前
Android 静默失败实录:五个没有日志的线上事故
android·kotlin
hai_android1 天前
从零实现一个支持跨进程(IPC)的自定义 Toast
android·kotlin·android jetpack