摘要
在 macOS 环境下进行 Android 无线调试时,开发者常遇到连接超时(No route to host)、协议故障(Protocol fault)或服务无法杀死的现象。本文详细记录了通过 nc 网络探测、ps 进程分析及 fs_usage 系统调用监控的完整排查过程。分析证实,该问题是由 VS Code Flutter 插件 自动拉起机制、多版本 ADB 冲突 以及 网络路由表滞留 共同引发的死锁,并提供了相应的解决与规避方案。
一、 故障现象与初步排查
在网络环境切换(如开启/关闭代理软件的 TUN 模式)后,ADB 无线调试出现异常,主要表现为网络层面的矛盾与进程层面的冲突。
1. 网络层的"虚假连接"
终端尝试连接设备时报错:
arduino
$ adb connect 192.168.50.10:37899
failed to connect to '192.168.50.10:37899': No route to host
然而,使用 nc (Netcat) 工具绕过 ADB 协议进行 TCP 握手测试,连通性却正常:
Bash
css
$ nc -v -z -w 3 192.168.50.10 37899
Connection to 192.168.50.10 port 37899 [tcp/*] succeeded!
分析: 这表明物理网络可达。adb 报错的原因在于后台常驻的 Server 进程保留了旧的网络上下文(Process Context),未能更新路由表。
2. 进程架构与版本冲突 (关键发现)
在排查中发现,即使执行连接命令失败,后台依然存在 ADB 进程。通过 ps -ef | grep adb 进一步观察,发现系统内同时存在两个 ADB 相关的进程实例:
bash
$ ps -ef | grep adb
# 进程 A:终端发起的操作(Client)
501 21871 ... 0:00.01 /opt/homebrew/bin/adb connect 192.168.50.10:37899
# 进程 B:后台常驻服务(Server/Fork-server)
501 21390 ... 0:00.13 /Users/xxx/Library/Android/sdk/platform-tools/adb -L tcp:5037 fork-server server
技术原理分析:
ADB 采用 Client-Server 架构。
- 进程 A 是我们在终端敲击命令时启动的客户端,它使用的是 Homebrew 安装的版本。
- 进程 B 是实际负责设备通信的后台守护进程,日志显示它运行的是 Android SDK 自带的版本。
结论:
- 路径不一致:终端使用 Homebrew 版 ADB 作为 Client,而后台运行的是由 IDE(如 VS Code)拉起的 SDK 版 ADB Server。
- 版本冲突 :不同版本的 Client 和 Server 通信时,极易触发
Protocol fault,导致连接中断或无法握手。
二、 根因定位:谁在"无限复活"进程?
尝试执行 adb kill-server 统一环境时,发现 Server 进程无法被彻底终止。每当旧进程被杀,一个新的 SDK 版 ADB 进程会在毫秒级内自动启动。
为了揪出幕后黑手,使用 macOS 内置的系统调用监控工具 fs_usage 对 ADB 可执行文件进行追踪。
排查步骤:
-
开启监控,过滤涉及
platform-tools/adb的文件系统事件: Bashperlsudo fs_usage -w -f filesys | grep "platform-tools/adb" -
在另一终端执行
pkill adb触发重启。
监控日志截取:
bash
21:34:40 execve /Users/xxx/Library/Android/sdk/platform-tools/adb dart.149044
21:34:44 stat64 /Users/xxx/Library/Android/sdk/platform-tools/adb dart.149044
分析结论:
日志明确指出,PID 为 149044 的 dart 进程正在频繁调用 execve 执行 ADB。经确认,该进程属于 VS Code 的 Flutter/Dart 插件。该插件为了实时刷新设备列表,会运行守护任务不断执行 adb devices。
死锁机制:
手动杀进程 -> VS Code 插件检测到服务丢失 -> 立即拉起新进程(此时可能继承了错误的网络路由或使用了 SDK 版本) -> 终端连接失败 -> 循环往复。
三、 解决方案
针对"版本冲突"和"自动拉起"两个痛点,提供以下解决方案。
方案一:服务启动时序调整(推荐)
核心逻辑是:在 VS Code 介入前,手动建立一个版本正确、网络环境健康的 ADB Server。
操作步骤:
-
彻底退出 VS Code:切断自动拉起 ADB 的触发源。
-
清理残留进程: 执行 pkill -9 adb。由于触发源已关闭,ADB 进程不会再自动复活。
-
统一版本启动: 为了避免路径冲突,建议在终端显式使用 SDK 路径启动(或者先卸载 Homebrew 版):
bash# 显式使用 SDK 路径启动服务 /Users/xxx/Library/Android/sdk/platform-tools/adb start-server -
建立连接 : Bash
bash/Users/xxx/Library/Android/sdk/platform-tools/adb connect 192.168.50.10:<端口> -
启动 VS Code: 连接成功后,重新打开 VS Code。Flutter 插件会检测到 5037 端口已有服务运行,便会复用该服务,从而避免了版本冲突和路由错误。
方案二:环境变量端口隔离(进阶)
通过环境变量修改 ADB 的监听端口,使其与系统默认行为隔离,彻底解决端口抢占问题。
操作步骤:
-
配置 Shell 环境变量: 编辑 ~/.zshrc 或 ~/.bash_profile: Bash
ruby# 1. 统一路径:将 SDK 路径加入 PATH,优先于 Homebrew export PATH=$PATH:~/Library/Android/sdk/platform-tools # 2. 端口隔离:指定 ADB Server 监听 5039 端口,避开默认的 5037 export ADB_SERVER_SOCKET=tcp:5039 -
应用配置: 执行 source ~/.zshrc。
-
重启工具: 必须完全重启 VS Code,使其加载新的环境变量。
-
验证: 此后,终端与 VS Code 将统一通过 5039 端口通信,且均使用 SDK 版本,彻底根治环境污染与版本不一致问题。
四、 总结
macOS 下 ADB 无线调试的故障排查,不能仅停留在网络连通性上。
- 关注进程架构 :使用
ps -ef确认是否存在 Client/Server 路径不一致导致的"版本打架"。 - 关注自动化干扰:开发工具(IDE)的后台守护进程往往是 ADB "杀不死"的根源。
- 解决策略:通过调整启动时序(先连后开 IDE)或端口隔离,可以有效规避此类环境冲突。