Android扫码异常排查小白教程

14 阅读27分钟

扫码没反应,却请求了另一个接口?从零学会用 ADB 找到真正接收条码的应用

明明站在新版应用的项目列表页,扫了一下条码,页面没有跳转。抓包工具却显示:设备发出了一个“查询容器”的请求,而且接口成功返回了数据。

第一反应很容易是:跳转代码写错了,或者接口返回字段不对。

在这个教学案例中,设备上的旧版应用仍在后台接收条码。最终,关闭旧版应用的无障碍服务后,当前页面恢复了正常扫码跳转。

这篇文章从连接 Android 设备开始,解释如何读日志、找到日志所属的进程、确认前台应用,并通过一次有记录的复测缩小问题范围。即使你以前没有用过 ADB,也可以按步骤完成。

本文将输入冲突的排查方法改编为虚构教学案例。应用包名、接口路径、业务字段、设备信息、页面与模块名称、条码、进程编号和日志时间均为示例,不对应任何实际项目;没有包含现场原始日志。命令中的示例值必须替换成自己的设备信息。

1. 先认识教学场景:页面、请求、接收者可能不是同一个应用

这台设备同时安装了两套应用:

应用技术包名当时的状态
新版作业终端React Native,简称 RNcom.example.scanner正在前台显示项目列表
旧版作业终端UniAppcom.example.legacy页面已退到后台,相关服务仍有活动

新版项目列表的扫码设计是:

扫描条码
  → 列表页收到扫码事件
  → 调用 resolveScanTarget
  → 返回目标 ID
  → 打开项目详情

但实际看到的请求是:

/api/demo/container-info

它返回的结构类似:

{
  "containerId": "example-container-id",
  "code": "DEMO_SCAN_001",
  "itemCount": 1
}

这些字段表示容器信息。接口成功,只能说明这个接口完成了自己的工作,不能说明当前列表页已经收到扫码,也不能说明跳转条件已经满足。

排查的第一个问题因此变成了:到底是谁收到了这次扫描?

2. 只需要先懂这几个词

名称通俗解释排查时有什么用
ADB电脑与 Android 设备通信的工具查设备、读日志、看进程和系统状态
LogcatAndroid 的日志工具看应用和系统记录了什么
包名应用的身份标识,例如 com.example.app区分名字、图标相似的新旧应用
进程应用正在运行的一份执行环境确认代码实际在哪个运行环境执行
PID进程编号,例如 4101将日志对应到一个具体进程
TID线程编号判断日志来自进程里的哪条线程,初学时可先忽略
Tag日志标签,例如 ExampleScanner大致识别是哪块模块打印的日志
无障碍服务Android 提供的一类系统服务能力某些应用会用它观察或处理输入事件

两个细节很重要:

  • 一个应用可能有多个进程,比如 com.example.legacy 和 com.example.legacy:worker。
  • PID 会随应用重启变化,也可能被系统重新使用。必须把 PID 和故障发生的时间一起看。

包名相同的覆盖安装通常仍是同一个应用身份;这次两个包名不同,所以新旧应用能够同时安装、同时运行。

3. 准备工具:让电脑能看到 Android 设备

3.1 安装 ADB

ADB 包含在 Android SDK Platform-Tools 中。可以使用 Android Studio 已安装的版本,也可以从 Google 官方 Platform-Tools 页面 下载对应系统的压缩包。

如果电脑已经能执行下面的命令,就不需要重复安装:

adb version

如果提示 command not found 或“不是内部或外部命令”,说明终端没有找到 ADB。

最容易理解的解决方法是:打开下载并解压后的 platform-tools 目录,在该目录打开终端。

macOS / Linux:

./adb version

Windows PowerShell:

.\adb.exe version

之后可以把这个目录加入系统 PATH,让任意目录都能使用 adb。如果暂时不配置 PATH,本文后面的命令在这个目录执行,并将开头的 adb 换成 ./adb 或 .\adb.exe 即可。

3.2 开启 USB 调试并连接设备

在 Android 设备上打开开发者选项,再启用 USB 调试。普通设备通常通过连续点击“版本号”开启开发者选项;作业终端可能有厂商专用的设置入口。

使用支持数据传输的 USB 线连接电脑。设备弹出“允许 USB 调试”时,在确认是自己的电脑后允许连接。

adb devices -l

正常示例:

List of devices attached
DEVICE_SERIAL_001  device  product:demo_device model:DemoDevice device:demo_device
结果意义下一步
deviceADB 已连接可以继续读日志
unauthorized设备尚未授权这台电脑解锁设备,查看 USB 调试授权弹窗
offline连接存在,但暂时不可用检查连接,重新插拔数据线后再查
只有标题,没有设备没发现设备检查线、USB 口、调试开关;Windows 还可能需要设备驱动

设备能充电,不代表数据线支持调试通信。完整连接说明可参考 ADB 官方文档。

3.3 多台设备时,先固定目标

如果电脑同时连接了手机、模拟器和作业终端,命令可能报:

error: more than one device/emulator

在命令中加上 -s 和设备序列号:

adb -s DEVICE_SERIAL_001 shell ps

本文后续示例默认只连接一台设备。如果有多台,每条命令都使用同一个 -s DEVICE_SERIAL_001,避免查错设备。

4. 动手之前,先建立一份最小故障记录

不要一上来就重启应用、拔插扫码枪或清空日志。先记录现场,后面的证据才有参照。

设备:某台 Android 作业终端
当前应用:新版作业终端
当前页面:项目列表
操作:扫描一次 DEMO_SCAN_001
预期:请求快捷跳转接口,进入详情页
实际:没有跳转,抓到了容器查询接口
发生时间:设备显示时间约 10:20

选择一个容易搜索、没有敏感信息的测试条码。后面始终用它定位同一次操作。

条码、订单号、任务号、请求 ID,通常比“错误”“失败”更适合追踪一条业务链。 很多异常没有打印错误,却会留下“哪个模块收到了哪个值”的记录。

电脑和设备时间可能不同。可以查看设备时间:

adb shell date

将它和你记下的操作时间对应。不要直接把两台机器显示的相同钟点当成同一时刻。

5. 第一步:先保存日志,再搜索条码

5.1 保存已有日志

adb logcat -d -v threadtime > scan-before.log

这条命令的意思是:

部分含义
adb logcat读取 Android 日志
-d输出当前缓冲区里的日志,然后退出
-v threadtime使用带时间、进程和线程信息的格式
>把输出保存到电脑上的文件
scan-before.log文件名,保存在当前终端目录

> 会覆盖同名文件。多次排查时换一个文件名,比如 scan-before-01.log。

Logcat 使用有限容量的缓冲区,旧日志可能被新日志覆盖。因此,出现问题后尽早保存比较可靠;输出格式和过滤规则可查 Logcat 官方文档。

5.2 在文件里搜索条码

有 rg(ripgrep)时:

rg -n -C 4 'DEMO_SCAN_001' scan-before.log

-n 显示文件行号,-C 4 同时显示匹配位置前后各四行,便于理解上下文。

没有 rg 也能完成排查。直接用 VS Code 或其他文本编辑器打开日志文件,搜索条码即可。也可以使用系统自带工具。

macOS / Linux:

grep -n -C 4 'DEMO_SCAN_001' scan-before.log

Windows PowerShell:

Select-String -Path .\scan-before.log -Pattern 'DEMO_SCAN_001' -Context 4,4

先保存再搜索,比一开始就只抓某一个应用的日志更适合这个案例:如果扫描实际上被别的应用收到,过早过滤就会漏掉关键证据。

5.3 没搜到时,现场再复现一次

开一个终端持续记录:

adb logcat -v threadtime > scan-live.log

然后扫描一次,等待几秒,按 Ctrl+C 停止记录,再搜索文件。

这里没有 -d,所以命令一直运行是正常现象。停止日志采集不会停止设备上的业务应用。

“没搜到条码”暂时只能说明这份日志里没有对应记录。还要排除时间不对、日志被覆盖、版本没有打日志、条码被截断或脱敏等情况,不能立即判定扫码枪坏了。

6. 第二步:读懂一行日志,找到 PID

假设采集到了下面这一行示例日志:

01-15 10:20:30.123  4101  4103 E ExampleScanner: 回调完成onScanReceived: DEMO_SCAN_001

按 threadtime 格式拆开看:

字段示例含义
日期01-15设备记录的日期
时间10:20:30.123记录时间,包含毫秒
PID4101打印日志的进程
TID4103打印日志的线程
级别E日志调用者选择的级别
TagExampleScanner日志标签
内容回调完成…DEMO_SCAN_001这条日志记录的事件

现在我们得到一条具体证据:进程 4101 的扫码模块记录了这个条码,并记录了回调完成。

但此时还不知道进程 4101 属于哪个应用,也不知道“回调完成”之后业务是否成功。

别被 E 误导

常见日志级别有 V、D、I、W、E,分别用于详细、调试、信息、警告和错误日志。

不过级别是代码作者选择的。案例里的“回调完成”也被打印成了 E,所以不能只看红色或 E 就判定失败,还要读消息内容及其前后文。

同样,ExampleScanner 是标签,不是应用包名。不同应用完全可能使用相同标签。辨认应用要继续看 PID。

7. 第三步:把 PID 对应到应用

查看设备上的进程:

adb shell ps

筛选相关进程:

adb shell ps | rg '4101|com\.example\.scanner|com\.example\.legacy'

假设同时查到下面的进程映射,省略了不影响判断的列:

PID     进程名
4101    com.example.legacy
4102   com.example.legacy:worker
5201   com.example.scanner

扫描日志的 PID 是 4101,对应旧版应用;新版应用的 PID 是 5201。

这一步把“可能有别的应用处理扫码”变成了更明确的判断:这次扫描确实到达了旧版应用的进程。

Android 版本和厂商不同,ps 的列可能不同;部分设备可以用下面的命令显示更多进程:

adb shell ps -A

根据设备输出中的 PID 和 NAME 等列标题阅读,不要死记第几列,也不要把刚才案例中的 4101 当成你自己设备的固定编号。

如果原进程已经退出,当前的 ps 可能查不到它。此时应重新现场复现,同时采集日志和进程信息,避免将旧 PID 错配到新进程。

8. 第四步:确认前台应用,并检查旧应用是否仍有服务

8.1 确认前台页面属于哪个包

adb shell dumpsys activity activities | rg 'mResumedActivity|topResumedActivity|mFocusedActivity'

教学示例输出如下:

mResumedActivity: ActivityRecord{... com.example.scanner/.MainActivity ...}

这证明当时前台显示的是新版应用。

dumpsys 用于查看系统服务的诊断信息,参见 官方说明。具体字段会因系统版本变化。如果筛选没有结果,先保存完整输出,搜索两个包名:

adb shell dumpsys activity activities > activities.log

现在两份证据可以放到一起了:

屏幕前台:新版应用
收到条码的日志:旧版应用 进程

应用退到后台后,不能仅凭页面消失就认定它的所有服务都退出了。系统管理的服务和应用页面有各自的生命周期。

8.2 查看已启用的无障碍服务

adb shell settings get secure enabled_accessibility_services

输出可能包含类似的组件名:

com.example.legacy/某个服务类名:另一个包名/另一个服务类名

这是只读查询。实际类名以设备输出为准,不要照着示例猜服务名。查询结果表示配置中已启用的服务,不能单独证明它正在连接或一定截获了这次扫描。

再保存无障碍服务状态:

adb shell dumpsys accessibility > accessibility.log

在文件里搜索旧应用包名,查看它是否出现在已启用、已绑定等相关条目附近。字段名称因版本而异,必须结合上下文阅读;只在输出中出现一次包名,不足以断定服务正在拦截。

8.3 无障碍服务为什么会影响扫码?

部分扫码枪使用 USB 键盘模式。它们扫描条码后,会向 Android 输入系统发送一串按键,应用再将这些按键组合成条码。

Android 的无障碍 API 允许具备相应能力并请求按键过滤的服务在按键交给应用之前处理它们;服务可以选择消费事件。并不是每个无障碍服务都有这种行为,具体取决于其配置和实现。参见 AccessibilityService.onKeyEvent 官方说明。

因此,可能出现这样的链路:

USB 键盘扫码枪
  → Android 输入系统
  → 旧应用的无障碍服务处理并消费按键
  → 旧应用组合条码并调用业务
  → 当前应用没有收到原本期待的输入

这是一种可能的机制,不能仅凭“机器安装了旧应用”就套用。这个案例有旧进程收到条码的日志,以及关闭旧服务后恢复的现场反馈,才让这个方向成为主要原因。

9. 第五步:核对接口,但别把“看到了请求”当成“找到了调用者”

9.1 先写清楚当前页面应该调用什么

如果你能阅读项目代码,先搜索页面上的扫码函数或接口路径。在这个案例里,需要对比两条业务路径:

路径预期接口关键参数或返回值
项目列表扫码跳转resolveScanTarget条码、业务场景;返回目标 ID
容器信息查询fetchContainerInfo条码、查询上下文;返回容器信息

在项目根目录搜索:

rg -n 'resolveScanTarget|fetchContainerInfo' src

搜索结果要继续读上下文:调用发生在哪个页面,输入参数从哪里来,返回后有没有导航操作。接口名本身只是线索。

没有代码也能先做判断:把当前页面、预期操作、实际接口和返回字段整理出来,交给开发者核对,通常比只说“扫码不跳转”有效得多。

9.2 参数可以帮助辨认调用路径

还可以构造这样一个版本差异:抓到的容器请求带有 contextId,新版应用的对应容器调用没有传这个字段,而旧版辅助查询代码会把包含它的上下文一起传入。

这种差别可以帮助缩小来源:参数形状、字段大小写、默认值,都可能反映不同版本或调用路径。

但它属于推断证据。也可能存在别的调用点、旧安装包或请求重放,不能只凭一个字段就宣布已经证明来源。

9.3 真正要追的是完整链路

同一次扫描的条码
  → 收到条码的进程 PID
  → 处理它的页面或业务函数
  → 发出的接口及参数
  → 接口返回
  → 执行了导航、更新表单,还是直接返回

特别注意抓包工具的范围:它抓的是设备全部流量,某个应用的流量,还是某个调试会话的流量?如果工具没有提供进程归属,就不能从一个请求自动推导出调用它的应用。

在这个示例的证据范围内,可以确认“旧应用进程收到条码”;结合参数特征和现场恢复结果,判断容器请求很可能来自旧应用的辅助查询链路。没有将网络请求逐条关联到进程的独立抓包证据,因此不能把参数推断写成直接观测。

10. 第六步:用有记录的复测验证原因

找到可疑服务以后,下一步是验证,而不是继续凭感觉改代码。

10.1 优先只改变一个条件

在不影响现场业务的前提下,可以这样做:

  1. 保持新版应用、当前页面、扫码枪、条码和场景配置不变。
  2. 保存关闭服务前的日志。
  3. 在系统设置中关闭旧应用对应的无障碍服务。
  4. 回到相同页面,用相同扫码枪再扫同一个条码。
  5. 保存关闭服务后的日志和请求结果。

常见设置入口是“设置 → 无障碍 → 已安装的服务 / 下载的应用 → 对应旧应用”,不同厂商名称会有区别。只处理这次确认相关的旧应用服务,保留现场仍需要的其他无障碍功能。

复测时重点看三件事:

  • 新版进程是否出现了扫码接收日志。
  • 是否发出了预期的快捷跳转请求,以及返回了什么。
  • 页面是否实际进入了目标详情。

如果扫码已到达新版,但接口失败或仍不跳转,说明需要继续排查后面的业务层,不能把所有问题都归到无障碍服务。

10.2 如果还需要停止旧应用

停止应用会影响它的运行状态。先保存旧应用中尚未提交的业务,再考虑下面的操作。

尝试结束可安全终止的后台进程:

adb shell am kill com.example.legacy

执行后检查:

adb shell ps | rg 'com\.example\.legacy'

am kill 并不保证所有相关进程都被终止。官方定义是仅杀掉可安全终止的相关进程;这次执行后,旧进程仍然存在。

如果已确认可以停止整个旧应用,可以使用:

adb shell am force-stop com.example.legacy

它会强制停止与该包关联的应用运行,不会执行卸载或清除应用持久化数据,但尚未保存的内存状态可能丢失。两个命令的区别见 ADB 的 Activity Manager 命令说明。

停止应用和在设置中关闭无障碍服务,不是同一个操作。 如果目标是不再让旧服务参与扫码,仍要检查其启用状态,并在重新打开旧应用后核对。

另外,ps 偶尔会短暂保留状态为 Z 的进程条目。它表示已经退出、尚待回收的进程,不能仅凭名字还在列表里就说它继续处理业务。

10.3 怎样写验证结论才准确

假设在教学场景中,关闭示例旧应用 com.example.legacy 的无障碍服务后,扫码恢复正常,可以记录:“旧进程曾收到条码,关闭旧服务后,新应用恢复扫码。”

如果期间还重启过应用或停止过进程,就应把这些操作一并写入记录,避免当成严格的单变量实验。恢复结果支持旧服务干扰输入这一判断,但不等于已经抓到了该服务消费每一个按键的调用栈。

以后正式复测时,建议使用下面的表格:

项目修改前修改后
当前前台应用新版作业终端新版作业终端
当前页面与扫码枪保持固定保持固定
测试条码DEMO_SCAN_001DEMO_SCAN_001
旧版无障碍服务已启用已关闭
新版扫码日志记录实际结果记录实际结果
请求接口与返回记录实际结果记录实际结果
页面跳转记录实际结果记录实际结果

可以连续测试几次,并补测从详情返回列表、关闭并重新打开新版应用后的行为。是否重新启用旧服务做反向验证,要看测试环境是否允许;生产设备不必为了复现而重新引入已知干扰。

11. 如果不是这个原因,接下来查哪里?

把“扫码没反应”拆成几个阶段,通常比全项目乱改更快。

硬件输出
  → Android 或厂商 SDK 收到
  → 原生模块生成条码
  → JS 收到事件
  → 当前页面处理
  → 请求接口
  → 校验返回
  → 页面更新或导航

下表提供的是下一步方向,不是仅凭一个现象就能确定的根因。

观察到的证据优先排查
扫码枪没有输出迹象,测试软件也无结果供电、连接、设备工作模式、测试条码是否可识别
其他应用记录了条码,当前应用没有相应记录旧服务、全局监听、无障碍按键处理、输入路由
已看到当前 Activity 的按键日志,但没有组合后的条码字符编码、回车结束符、组包超时、按键过滤
原生模块已经发出事件,但 JS 没有记录事件名、桥接状态、订阅是否建立、日志配置
JS 收到条码,却没有进入当前页面处理函数页面焦点、监听优先级、弹窗状态、订阅清理
页面收到条码,但没有发请求空值校验、车间或业务场景条件、去重、请求锁
请求发出了但失败HTTP 状态、响应正文、鉴权、服务器地址、业务规则
请求成功但没有导航返回字段是否匹配、是否提前 return、是否执行导航、路由参数
同一次扫描产生两个请求双驱动重复上报、多个监听器、多个进程、去重范围
不扫码也不停请求自动重试、轮询、Effect 依赖、空列表触底加载

两种扫码通道不要混为一谈

USB 键盘模式通常沿着按键事件链路进入应用;某些 HID POS 设备由厂商 SDK 读取数据,再通过回调进入应用。

如果某个项目分别有键盘解析通道和 POS HID SDK 通道。一个通道“已连接”,不能证明另一个通道也能收到条码;设备名称里都带 HID,也不能据此认定它们的数据路径相同。

例如,看到下面的日志:

POS HID USB 已连接

它只能支持“对应驱动报告了连接状态”。还要继续找带有测试条码的数据回调,才能确认这次扫描进入了该通道。

12. 几个最容易浪费时间的误区

12.1 只过滤新版应用的 PID

如果一开始只抓新版进程,就可能永远看不到旧应用接收到条码的证据。先用条码搜索全局日志,找到接收者以后,再按进程缩小范围。

设备支持时,可以进一步按 PID 读日志:

adb logcat -d --pid=5201 -v threadtime

旧系统可能不支持 --pid,先用 adb logcat --help 查看本机支持情况。最通用的替代办法仍是保存完整日志,再按已确认的格式筛选。

12.2 看到成功响应就直接改导航

先确认接口用途。容器查询返回容器字段,本来就不等于任务跳转成功。如果这份请求来自另一应用,在新版加导航代码也解决不了输入没有到达的问题。

12.3 没日志就等于没执行

代码可能没打印、生产包可能移除了日志、缓冲区可能覆盖了记录,搜索条件也可能不对。

更准确的记录方式是:“在这次采集范围内,没有观察到当前页面的接收日志”,然后说明采集时间和范围。

12.4 Tag 相同就认为是同一个应用

ExampleScanner、MainActivity、ReactNativeJS 都可能被多个应用使用。用 PID、时间和包名共同确认归属。

12.5 同时重启、拔插、换模式、改代码

这样可能暂时恢复,但不知道是哪一步起作用。下一次再出现时,又得从头猜。

每次改动前记录预期:如果假设成立,下一次扫描应该新增哪条日志、消失哪个请求、出现什么页面行为。

12.6 一开始就清日志或清应用数据

清空日志会删掉故障现场,清应用数据还会改变登录、设备配置和配置。先保存证据,再决定是否需要重建环境。

12.7 在电脑终端和 Android Shell 里混用命令

本文中的命令默认全部在电脑终端执行。比如:

adb shell ps | rg 'com\.example'

这里 ps 在 Android 上运行,管道后面的 rg 在电脑上运行,因此设备本身不需要安装 rg。

如果已经执行 adb shell 进入 Android Shell,就不应继续原样粘贴以 adb 开头的命令。先输入 exit 回到电脑终端,再按教程执行最省事。

12.8 zsh 报 no matches found: *:S

这是电脑 Shell 把星号当成了通配符,不是 Android 日志工具坏了。给过滤表达式加引号:

adb logcat -d -v threadtime ReactNativeJS:V ExampleScanner:V '*:S'

它只显示指定标签的日志,适合已经知道目标模块后的第二轮排查。定位接收者时,仍建议先保存一份未按标签裁剪的日志。

13. 开发者可以怎样让下一次排查更快?

13.1 在关键边界记录状态

扫码流程至少应能回答这些问题:

  • 哪个驱动收到了数据,什么时候收到的?
  • 哪个页面或弹窗取得了处理权?
  • 哪个校验阻止了请求,原因是什么?
  • 请求是否成功,响应是否包含导航需要的字段?
  • 最终是导航、更新表单,还是忽略了事件?

建议使用同一个扫描事件编号,把多层日志串起来。下面是一组虚构的日志设计示例:

scan.received scanId=scan-001 source=usb-keyboard codeLength=13
scan.routed scanId=scan-001 screen=ItemListScreen consumer=item-list-screen
scan.request scanId=scan-001 endpoint=resolveScanTarget
scan.response scanId=scan-001 hasTargetId=true
scan.navigate scanId=scan-001 target=ItemDetailScreen

如果没通过校验,也记录原因:

scan.ignored scanId=scan-001 reason=missing-context

真实业务条码可能包含敏感信息。开发环境可以用测试条码;生产日志可记录事件编号、长度和必要的脱敏标识,避免保存登录凭证和整份业务响应。

13.2 不要让异常完全消失

这种写法会使现场表现变成“没反应”:

try {
  await doScanBusiness();
} catch (error) {}

可以按项目的日志和错误处理规范,至少保留失败原因与扫描事件编号,并在页面给出可理解的提示。记录服务端响应时只保留诊断必需字段。

13.3 输入监听应有明确的所有者

页面、弹窗和应用退到后台时,应该检查谁仍保留扫码处理权。关闭页面需要清理什么监听,全局服务是否应该继续处理,都要有明确设计。

对于多应用并存的作业终端,还应登记哪套应用提供无障碍服务、谁负责扫描输入。切换新旧版本时一并核对服务状态,避免只切换屏幕上的页面。

14. 一页命令速查

以下命令主要用于读取状态。示例按单设备编写;多设备请统一添加 -s。rg 是电脑端可选的搜索工具,没有它可先导出文件,再用文本编辑器搜索。

想确认什么命令
ADB 是否可用adb version
连接了哪些设备adb devices -l
设备当前时间adb shell date
保存当前日志adb logcat -d -v threadtime > scan-before.log
持续记录本次复现adb logcat -v threadtime > scan-live.log,结束时按 Ctrl+C
搜索测试条码及上下文rg -n -C 4 'DEMO_SCAN_001' scan-live.log
查看进程与包名adb shell ps,必要时试 adb shell ps -A
保存前台页面相关状态adb shell dumpsys activity activities > activities.log
查询无障碍服务启用配置adb shell settings get secure enabled_accessibility_services
保存无障碍服务状态adb shell dumpsys accessibility > accessibility.log
查询某应用的安装包信息adb shell dumpsys package com.example.scanner
保存 USB 设备状态,供进一步检查adb shell dumpsys usb > usb.log
查看本机支持的日志选项adb logcat --help

如果怀疑设备安装了旧包,应用包信息中可重点搜索 versionName、versionCode 和更新时间。版本信息是排查安装状态的线索,不足以单独证明设备实际执行的 JS 内容,尤其是开发包或带更新机制的应用。

这张表不包含强制停止、清数据或修改设置的命令,避免在快速收集证据时顺手改变现场。

15. 可以直接复用的故障记录模板

# 扫码异常排查记录

## 环境

- 日期:
- 设备型号和 Android 版本:
- 应用包名、版本:
- 扫码枪型号和工作模式:
- 当前页面:
- 是否同时安装了旧应用:

## 复现

- 测试条码或脱敏标记:
- 操作时间,以设备时间为准:
- 操作步骤:
- 预期行为:
- 实际行为:

## 已确认的证据

- 采集时间、日志范围、文件名:
- 条码出现在哪条日志:
- 日志 PID、对应包名:
- 当时的前台应用:
- 无障碍服务配置和连接状态:
- 实际请求、关键参数、响应结果:
- 是否确认了请求所属进程,确认方法:

## 当前假设

- 假设:
- 支持它的证据:
- 尚未排除的解释:

## 验证

- 本次只改变了什么:
- 修改前结果:
- 修改后结果:
- 重复测试次数与结果:
- 需要继续检查的环节:

## 处理结果

- 采取的操作:
- 已验证恢复的功能:
- 未验证的场景:
- 防止再次出现的措施:

分享日志前,检查是否包含业务条码、账号、服务器地址、Token 等内容,并按发布范围脱敏。保留一份原始日志供内部核对,文章中使用另存的脱敏副本,避免改写原始证据。

遇到下一次“扫码没反应”,可以先打开终端,保存日志,找一条独特的测试条码。找到它出现的那一行,再沿着 PID、包名、处理函数和请求往下追。每一步都写下“我已经确认了什么”和“下一步要验证什么”,排查就会越来越有方向。