ADB网络与转发
adb forward tcp:9000 tcp:8080 和 adb connect 192.0.2.10:5555 都会让开发机与设备之间出现一条“网络路径”,但它们解决的是不同问题:前者保留现有 ADB transport,只在 transport 内建立一个服务转发;后者改变的是 ADB transport 本身,让 host 通过 TCP 或 TLS 连接 adbd。
这一区分很重要。端口转发失败时,通常要检查 listener、socket spec 和目标服务;网络 ADB 失败时,则要检查 transport 是否建立、认证是否完成以及网络发现是否可达。把两者混为“端口没开”会把排查带到错误的进程。
前置阅读是 ADB架构 和 ADB shell机制。前者介绍 adb client、server、transport 和 adbd 的边界,后者介绍一个已建立 transport 上的 service 如何运行;本文从 packages/modules/adb 的真实入口把同一边界扩展到 listener 和网络 transport。需要继续验证 transport 上的应用安装事务时,可阅读 ADB安装与权限管理。
1. 建立四个模型
1.1 forward
adb forward tcp:9000 tcp:8080左侧是开发机上的本地端点,右侧是设备端服务。成功后,开发机进程连接 127.0.0.1:9000,ADB server 接受连接,再通过选中的 transport 请求设备连接 tcp:8080。
forward 不会把 adbd 变成网络监听器,也不会改变设备的 ADB 连接方式。它的 owner 是 host ADB server 中的 alistener;listener 的 target 是设备服务名。
1.2 reverse
adb reverse tcp:8080 tcp:9000这次设备端监听 tcp:8080。设备上的应用连接该端口后,连接经过 ADB transport 回到开发机,再由 host 连接 tcp:9000。命令仍由 host client 发起,但 listener 的实际安装请求通过 adbd 的 reverse_service 进入另一侧。
1.3 传统 TCP ADB
adb tcpip 5555
adb connect 192.0.2.10:5555adb tcpip 是一个发给当前设备的 tcpip: service 请求。设备端 adbd 设置 TCP 端口并重启自身的 TCP service。之后 adb connect 才会让 host server 创建一个以网络地址为 serial 的 TCP transport。这里没有 host listener 把某个应用端口映射到设备;ADB 协议本身直接跑在 TCP 上。
1.4 TLSWi-FiADB
较新的无线调试流程通常是:
adb pair 192.0.2.10:37099 123456
adb connect 192.0.2.10:41567配对端口负责建立信任关系,连接端口负责建立 TLS ADB transport。adb pair 成功后,host 将设备 GUID 和证书材料保存到 adb_known_hosts.pb;mDNS 发现的是可安全连接的服务,最终由 TLS client 与设备侧 TlsServer 建立 socket。配对成功不等于当前 transport 已在线。
四种操作改变的对象不同
本文中的“网络 ADB”包含传统明文 TCP ADB 和 TLS Wi-Fi ADB。两者都改变 transport,但只有后者引入证书配对、known-host 和 mDNS secure-connect 服务。
2. 转发命令
2.1 client协议
Android 17 的 client/commandline.cpp 在 adb_commandline() 中解析 forward 参数。以固定端口为例:
adb forward tcp:9000 tcp:8080
│ │ │
│ │ └─ remote socket spec
│ └────────── local socket spec
└────────────────── host command随后 client 将请求编码为类似下面的 host service:
host:forward:tcp:9000;tcp:8080当使用 --no-rebind 时,编码中会出现 forward:norebind:;--list、--remove 和 --remove-all 则分别进入 list-forward、killforward: 和 killforward-all 形式。client 本身不创建监听 fd,真正的 bind 和 listener 表由 server 侧处理。
adb reverse 的编码不同:
reverse:forward:tcp:8080;tcp:9000这个前缀会沿当前 transport 发到 adbd,因此不能依据命令都在 host 终端中输入,就推断 listener 一定在 host。
2.2 forward request
adb.cpp::handle_forward_request() 接收解析后的 forward:、list-forward、killforward: 和 killforward-all 请求。创建转发时,它依次完成三件事:
- 解析
local;remote两个 socket spec,并检查 local/remote 的合法组合; - 根据 serial 或 transport 选择目标设备;
- 调用
install_listener()在 host listener 表中创建或替换条目。
每个 alistener 至少记录本地监听 socket、local_name、connect_to 和关联的 atransport。因此 adb forward --list 展示的不是“设备打开了哪些端口”,而是 server 当前拥有的 host-side listener 状态。
源码文件:packages/modules/adb/adb.cpp
相关函数/类型:handle_forward_request
if (!strncmp(service, "forward:", 8) || !strncmp(service, "killforward:", 12)) {
std::string error;
atransport* transport = transport_acquirer(&error);
if (!transport) {
SendFail(reply_fd, error);
return true;
}
bool kill_forward = false;
bool no_rebind = false;
if (android::base::StartsWith(service, "killforward:")) {
kill_forward = true;
service += 12;
} else {
service += 8;
if (android::base::StartsWith(service, "norebind:")) {
no_rebind = true;
service += 9;
}
}
std::vector<std::string> pieces = android::base::Split(service, ";");
// ... 校验 killforward 的单字段格式或 forward 的 local;remote 格式
InstallStatus r;
int resolved_tcp_port = 0;
if (kill_forward) {
r = remove_listener(pieces[0].c_str(), transport);
} else {
int flags = no_rebind ? INSTALL_LISTENER_NO_REBIND : 0;
r = install_listener(pieces[0], pieces[1].c_str(), transport, flags,
&resolved_tcp_port, &error);
}
if (r == INSTALL_STATUS_OK) {
SendOkay(reply_fd);
if (resolved_tcp_port != 0) {
SendProtocolString(reply_fd, android::base::StringPrintf("%d", resolved_tcp_port));
}
return true;
}
// ... 根据 InstallStatus 发送具体失败文本
}transport_acquirer 先决定这条映射归属哪个 transport,install_listener 才拥有 listener 表和本地 fd。 因此 OKAY 的生效时机是 listener 安装成功,而不是远端服务已经接受连接;tcp:0 的实际端口也在这一层 通过协议返回给 client。
2.3 动态端口
tcp:0 只适用于让系统为 local endpoint 选择空闲端口:
adb forward tcp:0 tcp:8080install_listener() 完成 bind 后,server 从 socket 得到实际端口,handle_forward_request() 将解析后的端口返回给 client。脚本应读取命令输出,而不是假定端口永远是某个固定值。remote tcp:0 没有同样语义,因为设备服务端无法把“任意空闲端口”解释成一个已知目标。
Android 17 的 adb_utils_test.cpp::test_forward_targets_are_valid 对这一边界做了反向验证:local tcp:0 合法,remote tcp:0 不合法,负数、超范围端口和非数字端口都会被拒绝。
3. 生命周期
3.1 install_listener
install_listener() 成功只说明 host 能够创建本地 listener,并不保证设备上的 tcp:8080 已经有服务。真正的远端连接要等到某个进程连接 local endpoint 后才开始。
连接到 tcp:9000 后,adb_listeners.cpp::listener_event_func() 在 listener fd 上收到可读事件,执行大致如下的路径:
accept(local listening fd)
-> create_local_socket(accepted fd)
-> local socket transport = listener transport
-> connect_to_remote(connect_to)
-> A_OPEN(remote service)host 的 accepted fd 被包装成 asocket,并继承 listener 选择的 atransport。connect_to_remote() 再把目标 socket spec 交给 ADB service 层,设备端最终通过 service_to_fd() 或对应服务工厂得到文件描述符。
3.2 Packet状态
对应用来说,local endpoint 看起来像普通 TCP;对 transport 来说,数据被封装在 ADB 的 A_OPEN、A_WRTE、A_OKAY 和 A_CLSE packet 中。设备服务关闭 fd 时,关闭消息沿 transport 回到 host;host 应用关闭 accepted fd 时,local asocket 发送关闭请求到设备端。
这解释了一个常见现象:设备服务是否监听,不能用 host 上的 ss -lnt 判断;host 只会看到 9000,设备端的 8080 由 adbd 在连接建立后按 service spec 打开。
3.3 三种失败点
| 失败阶段 | 典型表现 | owner | 应检查什么 |
|---|---|---|---|
| bind 阶段 | cannot bind listener | host server | local 地址、端口占用、权限、socket spec |
| transport 阶段 | device offline、连接被关闭 | server/transport | serial 选择、认证、USB/TCP/TLS transport 状态 |
| remote service 阶段 | local 端口可连但立即 EOF | adbd/service | remote spec、目标服务是否存在、设备权限和服务退出码 |
不要把“命令返回 OKAY”当作端到端成功。创建 listener 的成功确认早于第一次 accepted connection,也早于设备服务打开。
4. rebind
4.1 adb forward
当同一个 transport 上已有相同 local endpoint 时,普通 adb forward 允许 rebind。实现会找到旧 listener,关闭其 fd,再安装新的 target。这样可以方便脚本反复启动,但也可能无意中覆盖一个正在使用的映射。
源码文件:packages/modules/adb/adb_listeners.cpp
相关函数/类型:install_listener
for (auto& l : listener_list) {
if (local_name == l->local_name) {
if (flags & INSTALL_LISTENER_NO_REBIND) {
*error = "cannot rebind";
return INSTALL_STATUS_CANNOT_REBIND;
}
l->connect_to = connect_to;
if (l->transport != transport) {
l->transport->RemoveDisconnect(&l->disconnect);
l->transport = transport;
l->transport->AddDisconnect(&l->disconnect);
}
return INSTALL_STATUS_OK;
}
}
auto listener = std::make_unique<alistener>(local_name, connect_to);
listener->fd = socket_spec_listen(listener->local_name, error, &resolved);
if (listener->fd < 0) return INSTALL_STATUS_CANNOT_BIND;这段代码把两类失败分开:已有 endpoint 且禁止替换时返回 CANNOT_REBIND;新 endpoint 无法 bind 时返回 CANNOT_BIND。前者保留旧映射,后者没有可用 listener。源码中的 listener_list_mutex 还保护了查找、替换 和新增的临界区,所以排查时不能只看端口是否空闲,还要看是否已有同名 listener。
4.2 --no-rebind
adb forward --no-rebind tcp:9000 tcp:8080client 发送 forward:norebind:,handle_forward_request() 将 no_rebind 传给 install_listener()。如果 local endpoint 已存在,命令失败而旧映射保持不变。Android 17 的 adb_listeners_test.cpp::test_install_listener_no_rebind 直接验证了这一点;测试的证明边界是 listener manager 的行为,不覆盖 shell 环境中的端口竞争。
4.3 remove
adb forward --remove tcp:9000
adb forward --remove-all
adb forward --listremove 按 local endpoint 删除单个 listener,remove-all 删除当前 transport 的全部 forward。adb reverse --remove 和 adb reverse --remove-all 走对应的 reverse service。删除 listener 只释放转发状态,不会停止远端服务本身;远端服务是否退出取决于它自己的 fd 生命周期。
4.4 清理
listener_disconnect() 遍历 listener 表,移除关联目标 transport 的条目并关闭本地 fd。这个清理动作避免设备拔出后 host 仍显示一个可连接但永远失败的端口。test_transport_disconnect 和 remove-all 测试覆盖了这条生命周期边界。
5. adb reverse
5.1 命令到达adbd
执行:
adb reverse tcp:8080 tcp:9000client 发送 reverse:forward:tcp:8080;tcp:9000。server 先通过当前 transport 把 reverse: 请求交给 adbd。daemon/services.cpp::reverse_service() 为这个请求创建 socketpair,然后把请求委托给通用的 handle_forward_request()。socketpair 让设备侧服务可以复用 ADB 的 socket/transport 连接代码,而不必另造一套数据通道。
设备侧 listener 的 local endpoint 是 tcp:8080,其 target 是 host endpoint tcp:9000。当设备应用连接 8080,adbd 接受连接并沿 transport 发起 host-side open;host 收到后再连接本机 9000。
5.2 reverse 边界
reverse 需要设备上的 adbd 支持对应 service,并且设备端有权限 bind 指定地址。它不能绕过应用沙箱或网络防火墙;它只是让设备侧的 socket listener 把连接交给 ADB transport。adb reverse --list 查询的是 reverse listener 状态,不是设备上所有监听端口。
6. socket spec
ADB 的 forward/reverse endpoint 不是只接受 tcp:<port>。packages/modules/adb/socket_spec.cpp 和 socket_spec_test.cpp 定义并验证多种地址:
| spec | 典型使用 | listener/consumer |
|---|---|---|
tcp:9000 | TCP 端口 | socket_spec_listen 或 connect |
localabstract:name | Android abstract UNIX socket | adbd/设备本地服务 |
localfilesystem:path | 文件系统 UNIX socket | 需要路径权限的本地服务 |
jdwp:<pid> | Java 调试器入口 | adbd 的 JDWP service |
vsock:<cid>:<port> | 虚拟机/宿主通信 | emulator 或受支持设备 |
dev:/path | 设备文件 | 需要对应 fd 权限 |
acceptfd:<fd> | 复用已有 fd | host 侧调用者提供 |
其中 tcp: 的 listen 和 connect 行为由同一套 socket spec 解析器分流。socket_spec_listen_connect_tcp 测试验证了创建 TCP listener、连接它以及传输数据;malformed spec 测试则证明解析器会拒绝缺少参数或格式不完整的地址。
学习源码时应追踪三个问题:谁解析 spec,谁拥有被创建的 fd,谁消费连接后的字节。比如 localabstract: 的 fd 在设备侧由 adbd 打开,而 tcp: local endpoint 的 fd 在 host server 中创建;这两个 endpoint 名字都出现在一条 forward 命令里,但 owner 不同。
7. 传统TCP ADB
7.1 adbtcpipPORT
adb tcpip 5555client 将端口编码为 tcpip:5555,通过当前 USB 或其他已在线 transport 发送给 adbd。daemon/services.cpp 的 tcpip: 分支校验端口后调用 restart_tcp_service()。
daemon/restart_service.cpp::restart_tcp_service() 设置 service.adb.tcp.port,然后触发 adbd 重启,使新的 adbd 在 TCP 端口上接受 ADB protocol。它不是在 host server 里“把 USB 映射到 5555”,而是改变设备端 adbd 的监听方式。
传统 TCP ADB 的网络与认证边界
传统 TCP ADB 依赖目标网络可达,并且 Android 产品可能禁用或限制该入口。端口能够连接只说明 TCP 建立,不代表 ADB authentication、transport online 和 shell service 都成功。
7.2 adbconnect
client/commandline.cpp 将 adb connect 转成 host:connect:<address>。adb.cpp::handle_host_request() 解析地址并查找已有 transport;没有匹配项时创建 TCP transport,已有但失效的 transport 则可能被 kick 后重建。ParseNetAddress() 负责 IPv4、主机名和带端口地址的基本分解。
adb disconnect host:port 走同一地址解析路径,找到匹配 transport 后调用 kick。disconnect 不会修改设备端应用,也不会删除 forward listener;但 transport 断开会触发 listener 清理,所以相关 forward 可能随之消失。
8. TLS Wi-Fi ADB
8.1 配对信任
client/adb_wifi.cpp::adb_wifi_pair_device() 负责 host 侧配对。它创建证书,启动 PairingClient::Start(),向设备配对端口发送 pairing code,并等待对等端响应。成功后 KnownWifiHostsFile::AddKnownHost() 将设备 GUID 等信息写入 adb_known_hosts.pb。
这个文件不是“当前在线设备列表”,而是后续 TLS 连接用的 known-host 数据。错误密码、配对服务停止监听或 feature 不支持都会让配对失败;pairing tests 对这些情况有覆盖。
8.2 TlsServer
在 adbd 侧,daemon/adb_wifi.cpp::TlsServer::Start() 创建 TLS listener,选择动态端口并初始化证书上下文。register_adb_tls_service() 向 mDNS 注册安全调试服务,adbd_send_tls_server_port() 将端口报告给 framework lifecycle,兼容路径下也可通过旧 property 传递。
当 TlsServer::OnFdEvent() 接受新 socket 后,TLS 握手和认证完成,代码调用 register_socket_transport() 把这个 fd 纳入 adbd 的 transport 管理。此后它与 USB transport 共享 ADB packet、认证和 service 分发逻辑;区别只在底层 socket 和 TLS 会话。
8.3 TLS边界
daemon/mdns.cpp 注册和注销 ADB TLS 服务。host 侧 services 层提供 list-mdns-known-hosts、track-mdns-services 等请求,adb_wifi_secure_connect() 根据服务名和 known-host 证书建立安全连接。
因此排查 Wi-Fi ADB 时要分开记录:
- 配对端口是否可达、pairing code 是否正确;
- known-host 文件是否保存成功;
- mDNS 服务是否可见,或是否可以直接使用
HOST:PORT; - TLS 握手是否完成并注册为 online transport。
配对为何不等于连接
配对建立的是“以后允许谁连接”的信任关系;连接建立的是“现在用于 ADB packet 的 transport”。设备可以已经配对但暂时没有可发现的 secure-connect 服务,也可以 mDNS 已发现但 TLS 认证失败。
8.4 TLS失败
| 症状 | 可能停留的阶段 | 代码边界 |
|---|---|---|
| pairing code 错误 | pairing client 等待错误响应 | PairingClient 返回失败,不写入有效 known host |
adb pair 成功但列表无设备 | 配对完成,secure-connect 未发现 | mDNS 注册、网络隔离或服务生命周期 |
adb connect 显示 unauthorized/offline | socket 已建立,认证或 transport 初始化未完成 | host transport 状态机 |
| TLS 握手失败 | socket 可达但证书验证失败 | TlsServer/Wi-Fi pairing connection |
| 连接后很快消失 | transport 断开 | server/adbd 清理 transport 和 listener |
不要用“能 ping 通”证明 ADB 可用。ICMP 可达不能证明配对端口、TLS 端口、mDNS multicast 或 ADB authentication 均可用。
9. 转发测试
9.1 Listener验证
adb_listeners_test.cpp 的测试输入和断言可以整理为:
| 测试 | 输入 | 断言 | 证明边界 |
|---|---|---|---|
test_install_listener | 未占用 local spec | listener 安装成功 | manager 能建立基本状态 |
test_install_listener_rebind | 同一 local spec 二次安装 | 旧 target 被替换 | 默认 rebind 语义 |
test_install_listener_no_rebind | 重复安装并禁止 rebind | 返回错误且旧条目保留 | 不覆盖真实端口竞争 |
test_install_listener_tcp_port_0 | local tcp:0 | 获得非零实际端口 | 动态端口解析 |
test_transport_disconnect | 断开关联 transport | listener 被移除 | 清理关联关系 |
9.2 Socket验证
adb_utils_test.cpp::test_forward_targets_are_valid 适合验证格式与端口范围,不证明防火墙或设备服务状态。socket_spec_test.cpp::socket_spec_listen_connect_tcp 证明 TCP endpoint 的 listen/connect 组合可以工作,但不等于 USB/TLS transport 已建立。
pairing_connection_test.cpp 覆盖成功配对、错误密码、server 停止监听和 feature 支持。它验证的是 pairing library 的状态转换和错误处理;完整设备 Wi-Fi 调试仍需要 framework lifecycle、mDNS 网络和 adbd TLS server 一起参与。
这就是阅读测试时应保留的边界:测试断言越具体,能支持的源码结论越具体;不能把一个 host 单元测试扩大解释成所有产品、所有网络环境都可用。
10. 故障分层
10.1 关系类型
| 目标 | 首先看什么 | 不要先看什么 |
|---|---|---|
| host 访问设备服务 | forward listener、remote service | mDNS |
| 设备访问 host 服务 | reverse listener、host target | adb tcpip |
| host 连接网络 adbd | TCP/TLS transport、认证 | forward 列表 |
| Wi-Fi 自动发现 | pairing、known host、mDNS、TLS | 设备应用端口 |
10.2 forward/reverse
- 用
adb forward --list或adb reverse --list确认 listener 是否存在; - 检查 local/remote spec 是否写反,尤其是
tcp:0只能作为 local 动态端口; - 确认目标 transport 是当前选择的 serial,且状态为 online;
- 让实际消费者连接 local endpoint,观察失败发生在 accept 之前还是 remote service 打开之后;
- transport 断开后重新查询 listener,因为断线清理可能已经移除旧状态。
10.3 tcpip:
- 传统 TCP 模式先确认
tcpip:请求成功,再确认网络地址和端口可达; - TLS Wi-Fi 模式先确认 pairing,再确认 known-host 和 secure-connect 服务;
- mDNS 不可用时,用已知
HOST:PORT区分“发现失败”和“连接/认证失败”; - 看到
offline或unauthorized时,停在 transport/authentication 层,不要继续排查 shell 命令; - transport online 后,才用
adb shell、adb forward等 service 验证上层功能。
故障报告应保留的最小上下文
故障报告最好同时记录命令、serial、local/remote spec、transport 状态和首次失败阶段。只记录“端口不通”无法判断是 host bind、ADB packet、设备 service、TLS 认证还是 mDNS 发现的问题。
11. 连接状态
可以把本文的四种操作压缩成下面这张图:
forward 和 reverse 的核心状态是 listener;tcpip 和 connect 的核心状态是 transport;pair 和 TLS Wi-Fi 的核心状态是 trust material、发现服务与安全 transport。它们最后都可以消费 shell:、sync: 或其他 ADB service,但进入 service 层之前的 owner 和失败边界完全不同。
理解这个分层后,命令的选择就不再依靠记忆:需要把一个设备服务暴露给 host,用 forward;需要让设备应用访问 host 服务,用 reverse;需要让 ADB 自己脱离 USB,用 TCP 或 TLS Wi-Fi;需要建立 Wi-Fi 调试信任,先 pair,再 connect。
12. 入口对应
初学者最容易在多个同名函数之间迷路。下面按一次故障中可能观察到的现象,列出应进入的 Android 17 源码位置:
| 现象或问题 | 首个入口 | 继续追踪的对象 | 你要确认的事实 |
|---|---|---|---|
adb forward 参数被拒绝 | client/commandline.cpp::adb_commandline | adb_utils_test.cpp::test_forward_targets_are_valid | client 是否生成了合法 service,端口是否在允许范围 |
命令成功但 --list 没有预期项 | adb.cpp::handle_forward_request | format_listeners、alistener | 选择的 transport 和 listener owner 是否相同 |
| local 端口能连接但立即关闭 | adb_listeners.cpp::listener_event_func | connect_to_remote、A_OPEN | accepted fd 是否创建,remote spec 是否被设备端消费 |
| reverse 安装失败 | daemon/services.cpp::reverse_service | adbd 侧 handle_forward_request | 请求是否到达 adbd,设备 listener 是否完成 bind |
adb tcpip 返回成功但 connect 失败 | daemon/restart_service.cpp | restart_tcp_service、host handle_host_request | adbd 是否重启到 TCP,host 是否创建了新 transport |
| Wi-Fi 设备不出现在列表 | client/adb_wifi.cpp | KnownWifiHostsFile、mDNS service tracker | 配对材料、服务发现和 secure-connect 是否分别成功 |
| TLS socket 建立后变 offline | daemon/adb_wifi.cpp | TlsServer::OnFdEvent、register_socket_transport | TLS 会话是否真正注册为 ADB transport |
这种定位方式体现了“入口、owner、消费者”的关系:adb_commandline 消费 argv,handle_forward_request 消费 service 字符串,alistener 消费本地连接事件,adbd service 消费 ADB open 请求,最终的应用或调试工具才消费字节流。只阅读命令帮助无法得到这条所有权链。
12.1 观察实验
可以在设备上启动一个明确监听 TCP 的服务,然后把它映射到 host:
adb forward tcp:0 tcp:8080
adb forward --list第一条命令的输出端口是 server 在 bind 后解析出的结果。第二条命令应该显示 local endpoint、serial 和 remote endpoint。随后让 host 客户端连接这个动态端口,才能触发 listener_event_func() 和远端 A_OPEN;只执行第一条命令不会触发设备服务连接。
如果第二条命令没有条目,问题在 listener 安装或查询路径;如果有条目但连接后 EOF,问题已经越过 host bind,应该转向 remote service 和设备端权限。这个实验把“安装成功”和“端到端服务成功”明确分成两个时间点。
12.2 消费者
reverse 的消费者在设备侧,因此验证顺序相反:
adb reverse tcp:8080 tcp:9000
adb reverse --list然后让设备应用或设备 shell 中的客户端连接 127.0.0.1:8080。host 上的 9000 服务只有在设备连接发生后才会看到新连接。若 host 服务没有收到连接,不要先修改 host 服务协议;应先确认设备端 listener 是否存在、应用是否真的连接了同一个地址族和端口。
12.3 状态证据
执行 adb connect 后,adb devices -l 中的 serial 通常是 HOST:PORT 或由 mDNS 解析出的服务名。后续的 adb forward、adb shell 都会选择这个 transport。若同时存在 USB 和 Wi-Fi 设备,必须使用 -s SERIAL 验证,否则你可能把对 USB transport 的观察误认为是 Wi-Fi transport 的结果。
adb devices -l
adb -s 192.0.2.10:5555 shell getprop ro.build.version.release
adb -s 192.0.2.10:5555 forward --list这三个命令分别观察 transport 列表、service 消费者和 listener 状态。它们的共同前提是 transport 已 online;如果状态是 offline 或 unauthorized,应回到认证和连接层,而不是继续改变 forward 目标。
13. 混淆点
第一,forward 不是网络 ADB。 forward 复用已存在的 ADB transport;即使 local endpoint 使用 TCP,ADB server 与设备之间仍然可以是 USB。网络 ADB 则要求 host 能直接建立 TCP/TLS transport。
第二,reverse 不是把 host 端口“推送”到设备文件系统。 reverse 安装的是设备侧 listener,设备应用产生连接后,ADB transport 才把连接事件带回 host。
第三,adb tcpip 不是 adb forward tcp:5555 ... 的别名。 前者请求 adbd 改变自身监听和重启方式,后者只在 host 侧增加一个应用服务映射。
第四,配对不是连接。 pairing 产生可验证的身份材料,connect 才创建当前可用的 ADB transport。mDNS 是发现机制,不是权限授予机制。
第五,listener 表不是设备端口表。 --list 只反映当前 ADB 实例管理的 forward/reverse 条目;它不能替代设备上的 ss、应用服务状态或 framework 的 Wi-Fi 调试状态。
把这五个结论分别放回代码 owner,就能避免大多数“命令看起来成功但业务不通”的误判。
