ADB架构
执行 adb shell id 时,终端里只有一条命令,但 ADB 实际上要跨越开发机上的 client、server、设备 上的 adbd、USB 或 TCP transport、多个 socket 和两套协议。把 ADB 简化为“电脑通过 USB 连接手机” 会掩盖最容易出错的地方:命令首先进入哪个进程,设备选择什么时候发生,shell 服务何时创建,数据怎样 在一条物理连接上复用,以及断线后谁负责关闭状态。
本文面向已经读过 模块化编译、 编译错误排查 和 系统镜像结构 的读者。你应当知道如何运行一个 ADB 命令和查看构建产物, 但不需要预先了解 atransport、asocket 或 apacket。本文只建立架构和一条完整调用链;shell 参数、 文件同步、端口转发、无线配对和安装部署分别留给本模块后续专题。
读完后,应该能回答:adb 为什么会自动启动 server,host:tport:any 和 shell:id 分别在哪里被 处理,A_OPEN 的两个 id 如何对应两端 socket,以及设备拔出时为什么不会留下一个假在线的 shell。
1. 进程连接
1.1 client
Android 17 的 ADB 源码仍然把 host client 和 host server 编译在同一个 adb 可执行文件中,但它们 是不同的进程角色:
| 角色 | 运行位置 | 入口 | 主要拥有的状态 | 直接消费者 |
|---|---|---|---|---|
| client | 开发机 | client/main.cpp::main → adb_commandline | 命令参数、目标选择、与 server 的 fd | 终端、脚本、DDMLIB |
| server | 开发机后台进程 | client/main.cpp::adb_server_main | smart socket、设备扫描、transport 列表、多路复用 | client 与设备 transport |
adbd | Android 设备或模拟器 | daemon/main.cpp::main | 设备端 transport、认证、shell/sync/jdwp 服务 | shell 子进程和调试服务 |
client 和 server 的“同一个二进制”不是“同一个进程”。client 通过 server 的 smart socket 发送服务 请求;server 再决定请求是在 host 内部完成,还是沿某个 transport 转到 adbd。
1.2 adb
随 AOSP packages/modules/adb 一起维护的上游 ADB 总览文档 把 server 描述为负责设备发现、状态维护和数据复用的中心循环;上游 ADB 内部实现说明 进一步说明 server 一侧暴露 client 连接,另一侧通过 transport 监视多个 adbd。这些是 AOSP 上游仓库 中的开发文档,不是本站内部页面。因此 server 至少拥有这些全局事实:
- 当前有哪些 USB、emulator 或网络 transport;
- 每个 transport 的 serial、product、model、device、feature 和连接状态;
- 哪个 client socket 已选择哪个 transport;
- 哪些 local/remote
asocket互为 peer; - 哪些 listener、forward/reverse 和 tracker 需要在断线时清理。
adbd 不负责开发机上的设备列表,也不会替 client 决定连接哪个 serial。它只在自己的 transport 上 接受 ADB packet,认证成功后根据 A_OPEN 的地址创建设备端服务。
1.3 transport抽象
transport.h 中的 TransportType 当前至少包括:
源码文件:packages/modules/adb/transport.h
相关函数/类型:TransportType
enum TransportType {
kTransportUsb,
kTransportLocal,
kTransportAny,
kTransportHost,
};kTransportHost 是特殊值,表示服务由 server 自己提供,并不对应真实设备 transport。USB 和 local transport 都被抽象成 atransport,所以上层的 adb shell 不需要知道字节最终通过 USB bulk endpoint 还是 emulator TCP socket。
2. Client入口
2.1 Client命令
client/main.cpp::main 只做追踪初始化并调用 adb_commandline。后者解析 -s serial、-t transport-id、 -d、-e、-H、-P、-L 等参数,随后计算 server socket spec。默认 server 是开发机本地 tcp:localhost:5037;环境变量和显式 -H/-P/-L 可以改变它。
目标选择先留在 client 进程的静态状态中:
源码文件:packages/modules/adb/client/adb_client.cpp
相关函数/类型:adb_set_transport
static TransportType __adb_transport = kTransportAny;
static const char* __adb_serial = nullptr;
static TransportId __adb_transport_id = 0;
void adb_set_transport(TransportType type, const char* serial, TransportId transport_id) {
__adb_transport = type;
__adb_serial = serial;
__adb_transport_id = transport_id;
}这些变量只记录 client 这次请求希望选择的目标,真正的 atransport 对象属于 server。把它们理解 成“client 已经打开了设备”会混淆两个进程的所有权。
2.2 adb_connect
adb_connect 会先进入 adb_check_server_version。它向 server 发送 host:version;server 返回四位 十六进制版本字符串。当前 adb.h 中的 ADB_SERVER_VERSION 为 41。
源码文件:packages/modules/adb/client/adb_client.cpp
相关函数/类型:__adb_check_server_version
unique_fd fd(_adb_connect("host:version", nullptr, error));
if (fd == -2 && local) {
fprintf(stderr, "* daemon not running; starting now at %s\n", __adb_server_socket_spec);
if (launch_server(__adb_server_socket_spec, one_device)) {
*error = "cannot connect to daemon";
return false;
}
} else {
if (version != ADB_SERVER_VERSION) {
adb_kill_server();
goto start_server;
}
}远程 server 不能由本地 client 自动 fork,因此连接失败时不会盲目启动远程进程;本地 server 版本不匹配时才进入 kill/restart 路径。这解释了为什么 ADB_SERVER_SOCKET=tcp:host:port 的失败 恢复能力不同于默认本地 5037。
2.3 launch_server
launch_server fork/exec 同一个 adb 二进制,并传入 fork-server、--reply-fd 等参数。子进程进入 adb_server_main 后先初始化 signal、日志、重连 handler、mDNS 和 USB/emulator 扫描,再安装 disabled 的 smart socket listener,等待设备扫描完成后通过 ack pipe 写入 OK\n,最后在 fdevent loop 上调用 enable_server_sockets。
源码文件:packages/modules/adb/client/main.cpp
相关函数/类型:adb_server_main
while (install_listener(socket_spec, kSmartSocketConnectTo, nullptr,
INSTALL_LISTENER_DISABLED, nullptr, &error) != INSTALL_STATUS_OK) {
if (std::chrono::steady_clock::now() - start > 0.5s) {
LOG(FATAL) << "could not install *smartsocket* listener: " << error;
}
std::this_thread::sleep_for(100ms);
}
std::thread notify_thread([ack_reply_fd]() {
adb_wait_for_device_initialization();
// ...向父进程报告 OK...
fdevent_run_on_looper([] { enable_server_sockets(); });
});disabled listener 与 ack pipe 共同避免 client 看到“server 进程已存在、设备列表却还没初始化”的 中间状态。server 的 owner 是 fdevent loop;扫描工作不能阻塞它处理启动完成回调。
3. socket路由
3.1 长度协议
ADB smart socket 的请求格式是四个 ASCII 十六进制字符加 payload。例如查询 server 版本时是:
000Chost:versionadb_client.cpp::_adb_connect 负责连接 socket、可选地发送 transport 选择请求,再发送目标 service:
源码文件:packages/modules/adb/client/adb_client.cpp
相关函数/类型:_adb_connect
if (!service.starts_with("host") || force_switch) {
std::optional<TransportId> transport_result = switch_socket_transport(fd.get(), error);
if (!transport_result) return -1;
}
if (!SendProtocolString(fd.get(), service)) {
*error = perror_str("write failure during connection");
return -1;
}
if (!adb_status(fd.get(), error)) {
return -1;
}SendProtocolString 写入长度前缀和正文;adb_status 只消费四字节 OKAY 或 FAIL 加错误文本。成功 状态并不必然关闭连接:选择 transport 后,后续请求仍在同一个 client-server socket 上继续发送。
3.2 smart socket
server 的 smart_socket_enqueue 将收到的数据累积在 asocket::smart_socket_data,直到至少有四字节 长度且 payload 完整:
源码文件:packages/modules/adb/sockets.cpp
相关函数/类型:smart_socket_enqueue
if (s->smart_socket_data.size() < 4) {
return 0;
}
uint32_t len = unhex(s->smart_socket_data.data(), 4);
if (len == 0 || len > MAX_PAYLOAD) {
goto fail;
}
if ((len + 4) > s->smart_socket_data.size()) {
return 0;
}
service = std::string_view(s->smart_socket_data).substr(4);return 0 不是成功响应,而是“当前数据还不足以解析请求”。长度为 0、超过 MAX_PAYLOAD 或 解析失败才进入关闭路径。测试中的分片输入正是为了验证不能假设一次 read 就得到完整 service。
3.3 Socket路由
收到完整 service 后,smart socket 先识别 host-serial:、host-transport-id:、host-usb:、 host-local:、host: 前缀。
| 请求形态 | 处理 owner | 结果 |
|---|---|---|
host:version、host:devices、host:features | handle_host_request | server 直接返回数据 |
host:tport:any、host:tport:serial:<id> | handle_host_request | server 选择 atransport,返回 OKAY 和 transport id |
track-devices、wait-for-device | host_service_to_socket | server 创建 tracker 或等待线程 socket |
shell:...、sync:...、jdwp | connect_to_remote | smart socket 把请求转成 transport 上的远端 stream |
transport 选择的当前 Android 17 实现位于 adb.cpp::handle_host_request:
源码文件:packages/modules/adb/adb.cpp
相关函数/类型:handle_host_request
if (service.starts_with("transport") || service.starts_with("tport:")) {
TransportType type = kTransportAny;
bool legacy = true;
if (android::base::ConsumePrefix(&service, "tport:")) {
legacy = false;
if (android::base::ConsumePrefix(&service, "serial:")) {
serial_storage = service;
serial = serial_storage.c_str();
} else if (service == "usb") {
type = kTransportUsb;
} else if (service == "local") {
type = kTransportLocal;
} else if (service == "any") {
type = kTransportAny;
}
}
atransport* t = acquire_one_transport(type, serial, transport_id, nullptr, &error);
if (t != nullptr) {
s->transport = t;
SendOkay(reply_fd);
if (!legacy) WriteFdExactly(reply_fd, &t->id, sizeof(t->id));
return HostRequestResult::SwitchedTransport;
}
}tport 不是设备端命令,而是 server 内部改变 smart socket 后续路由状态的 host service。只有 成功绑定 s->transport 后,后续不带 host: 前缀的 service 才能沿设备 transport 发送。
3.4 生命周期
当 host service 创建了本地 socket,smart socket 会把原 client peer 重新变成普通 local socket,连接 到新服务 socket,发送 OKAY,然后关闭自己。对于远端 service,它则把 peer 设置为等待通知的 local socket,调用 connect_to_remote,再关闭 smart socket。
这就是“smart socket”的准确含义:它只负责第一次的 service 解析与路由选择,不承载整个 shell 数据流。 真正的数据通道由 local/remote asocket peer 负责。
4. Transport状态
4.1 Transport状态
USB 扫描器或 emulator/TCP 连接发现设备后,会创建 atransport 并注册到 server 的 transport 列表。 register_socket_transport 会先检查 pending/online 列表中是否已有同名 transport,再安排 fdevent loop 完成注册。设备未完成握手时状态仍是 offline/connecting,不能因为“发现了 USB”就对 client 报 device。
ConnectionState 包含连接阶段与设备运行模式:
源码文件:packages/modules/adb/transport.h
相关函数/类型:ConnectionState
enum ConnectionState {
kCsConnecting,
kCsAuthorizing,
kCsUnauthorized,
kCsNoPerm,
kCsDetached,
kCsOffline,
kCsBootloader,
kCsDevice,
kCsHost,
kCsRecovery,
kCsSideload,
kCsRescue,
};ConnectionStateIsOnline 只把可承载 service 的状态视为 online。offline 表示对端已被发现但连接尚未 建立或已失去连接;unauthorized 表示认证尚未完成,不能把它当成普通离线设备。
4.2 A_CNXN
建立 USB/TCP 连接后,双方通过 ADB packet 的 A_CNXN 交换协议版本、最大 payload 和 banner。当前 A_VERSION 为 0x01000001,MAX_PAYLOAD 为 1 MiB,旧版本可能使用 4 KiB 上限。
adb.cpp::handle_new_connection 在 host 侧先 handle_offline,再调用 update_version、读取 banner、 parse_banner,最后 handle_online。这样旧的 product/model/features 不会残留到新连接上。
4.3 Transport失败
在非 TLS 路径中,A_AUTH 的 token/signature/public-key 交换由 host client/auth.cpp 与 device daemon/auth.cpp 共同完成。设备端认证成功后才调用 adbd_auth_verified 并再次发送连接信息;失败 会增加 failed_auth_attempts,继续发送认证请求或保持 unauthorized。
这解释了 adb devices 中 unauthorized 的来源:设备 transport 已存在,但认证 owner 尚未把它提升 为 online。只重启 host server 不一定能解决设备侧未确认 RSA key 的问题。
4.4 断线清理
atransport::HandleError 把错误切回 fdevent loop,调用 handle_offline 和 transport_destroy。 handle_offline 会把 state 改成 offline、清除 online 标志、调用 close_all_sockets(t) 和所有 disconnect 回调。
源码文件:packages/modules/adb/adb.cpp
相关函数/类型:handle_offline
t->SetConnectionState(kCsOffline);
t->online = 0;
close_all_sockets(t);
t->RunDisconnects();清理顺序保护了两个不变量:transport 断开后不能再向设备写 packet;所有依赖该 transport 的 local/remote socket 必须收到关闭,避免 client 永久等待一个不会再有数据的 fd。
5. packet
5.1 Packet结构
| 对象 | 所在层 | 作用 |
|---|---|---|
amessage | packet 头 | command、两端 id、payload 长度、checksum、magic |
apacket | transport 数据单元 | amessage + payload,一次读写的协议单位 |
asocket | stream 抽象 | 维护 fd、peer、packet queue、transport 和流关闭状态 |
asocket 是单向的;双向服务由两个互为 peer 的 asocket 表示。host 侧的 local socket 连接 client fd, remote socket 连接 transport;device 侧相反。这样多个 shell、sync、JDWP 流可以在一个 USB/TCP transport 上并行存在,每个流用 local/remote id 对标识。
5.2 Packet服务
当 server 已选择 transport,并要处理 shell:id 时,connect_to_remote 创建 remote socket 并发送:
A_OPEN(arg0 = host_local_id, arg1 = send_window, payload = "shell:id")设备 handle_packet 收到 A_OPEN 后调用 create_local_service_socket(address, t);在 adbd 侧, daemon_service_to_socket 识别 shell 并调用 StartSubprocess,得到连接到 shell 子进程的 service socket。随后 adbd 发送 A_OKAY,携带设备端 remote id。
5.3 A_OKAY 流控
旧协议中 A_OKAY 表示对端已准备好接收下一批数据;当前实现还可在 payload 中携带 delayed-ack 的 已消费字节数。local_socket_flush_incoming 在收到数据写入本地 fd 后,根据 transport 是否支持 delayed ack 决定发送窗口更新。
A_OPEN 的 arg1 不能随便当成设备端 id:在支持 delayed ack 的 transport 上它是初始发送窗口, 设备必须接受与 capability 一致的值;设备返回的 A_OKAY 才提供远端 id。
5.4 A_WRTE/A_CLSE
handle_packet 收到 A_WRTE 后用 p->msg.arg1 查找本地 socket,并用 p->msg.arg0 校验 peer;找到后 把 payload enqueue 到 local socket。A_CLSE 同样按两端 id 找到 socket,再关闭 peer。
源码文件:packages/modules/adb/adb.cpp
相关函数/类型:handle_packet (A_WRTE/A_CLSE)
case A_CLSE:
if (t->online && p->msg.arg1 != 0) {
asocket* s = find_local_socket(p->msg.arg1, p->msg.arg0);
if (s) s->close(s);
}
break;
case A_WRTE:
if (t->online && p->msg.arg0 != 0 && p->msg.arg1 != 0) {
asocket* s = find_local_socket(p->msg.arg1, p->msg.arg0);
if (s) s->enqueue(s, std::move(p->payload));
}
break;设备拔出后,旧 packet 可能晚于关闭事件抵达;t->online 和 peer id 检查共同阻止它把数据注入 已经关闭或属于其他 transport 的 socket。关闭逻辑还防止错误对端用 A_CLSE(0, remote_id) 关闭不属于 自己的 host stream。
6. adbshellid
6.1 shellservice
client/commandline.cpp::adb_shell 先读取 server/device feature,决定是否使用 shell protocol、PTY 或 raw 模式,再由 ShellServiceString 生成:
shell[,pty|raw][,shell protocol]:id交互终端默认倾向 PTY;非交互输入可以使用 raw;设备不支持 shell_v2 时,client 会退回旧 shell 语义。 这些选择属于 shell 专题,但它们最终都被压缩成 ADB service 字符串交给 adb_connect。
6.2 请求阶段
完整顺序是:
这里的 host:tport:any 是 Android 17 client/server 的新 transport 选择请求;旧文档中常见的 host:transport-any 是兼容旧协议的另一种拼法,不能在源码分析中混写。handle_host_request 会根据 tport: 分支返回选中的 TransportId,client 保存它以便后续请求和错误信息使用。
6.3 每一层消费者
| 事件 | 状态拥有者 | 下一个消费者 |
|---|---|---|
| client 解析 shell 参数 | adb_shell | adb_connect |
| server 版本检查 | adb_client | adb_server_main 或已有 server |
| 选择目标 | server atransport 列表 | smart socket 的 s->transport |
| 连接握手 | atransport | parse_banner、认证状态和 device tracker |
| 创建流 | host/device 两端 asocket | daemon_service_to_socket |
| 输出传递 | packet queue、fdevent、peer | client stdout/ShellProtocol |
| 退出或断线 | transport + peer | A_CLSE、fd 清理和 client 返回 |
如果只能说“ADB 把命令发给 adbd”,就还没有说明这些状态的 owner 和生效时机;真正调试时应沿表格 找到最后一个成功消费者。
7. 失败路径边界
7.1 server级失败
| 现象 | 代码边界 | 判断方式 |
|---|---|---|
cannot connect to daemon | _adb_connect / launch_server | server socket spec、端口占用、远程地址和启动日志 |
adb server version ... doesn't match | __adb_check_server_version | host:version 返回值与 ADB_SERVER_VERSION |
| 远程 server 不自动启动 | __adb_check_server_version | is_local_socket_spec 为 false,需先启动远端 server |
| unknown host service | smart_socket_enqueue | handle_host_request 与 host_service_to_socket 都未处理请求 |
adb kill-server 也不是杀掉设备 adbd;它发送 host:kill,由 host server 自己退出,设备 transport 随后因连接关闭而被清理。
7.2 设备失败
adb devices 显示 offline、unauthorized、no permissions 或 bootloader 时,不应直接去看 shell 子进程。先判断:
adb server-status
adb devices -l
adb get-state
adb get-serialno这些命令本身通过 host service 或 tracker 工作。offline 表示 transport 尚未 online;unauthorized 表示认证 owner 尚未完成;bootloader 和 sideload 是另一种对端服务状态,不能当作普通 Android adbd。
7.3 断线失败
即使 transport 显示 device,A_OPEN 仍可能因 shell: 格式、service 不存在、权限或设备资源失败。 handle_packet 在 create_local_service_socket 返回空时发送 A_CLSE,host client 只能看到连接被关闭。 这时应同时检查:
adb -s <serial> shell getprop ro.build.version.release
adb -s <serial> shell id
adb -s <serial> shell 'logcat -b all -d | grep -i -E "adbd|shell|CANNOT LINK|avc: denied"'不要把 adb shell 失败归为 USB 问题;如果 adb devices 已是 device,transport 层可能已经正常, 失败点更可能在 adbd service、子进程、SELinux 或 shell protocol。
7.4 断线和半关闭
设备拔出时可能有三种数据尚未完成:host local socket 的 fd、transport 写队列、对端尚未确认的 apacket。handle_offline 先把 transport 标成 offline,再关闭所有关联 socket;socket 的 deferred close 还会尝试把已经写入的缓冲排空,避免直接 close 导致已成功写出的数据被 TCP RST 丢弃。
因此“终端没有立即返回”不一定是 server 死锁,可能是 socket 正在等待 flush/EOF。打开 host trace 时应同时观察 transport、sockets、packets 和 fdevent,不要只开一个 adb 日志标签。
8. 架构测试
8.1 架构测试
socket_test.cpp::test_parse_host_service 对空 service、普通 serial、带端口 serial、IPv6、tcp:、 udp: 和嵌入 NUL 的输入做断言。它证明 parse_host_service 的职责是从 service 前缀中分离 serial 和 command,而不是验证设备是否真的存在。
这一区分很重要:解析通过只代表路由字符串结构合法,后续 acquire_one_transport 仍可能返回“device not found”或 ambiguous。
8.2 Transport验证
transport_test.cpp 构造 atransport,调用 parse_banner 后断言连接状态为 kCsHost,并检查 product/model/device 与 features。SetFeatures 测试还确认重复 feature 会被去重、替换和清空。
它反向证明 banner 是 transport 状态的输入,而 adb devices -l 输出的 product/model/device 并不是 从 USB 名称临时拼出来的。
8.3 Packet验证
types_test.cpp 的 APacketReader 测试把多个 packet 合并到一个 block,再按 1~255 字节切碎后喂给 reader;reader 仍必须还原相同 packet 序列。另一个测试把 payload 设为 MAX_PAYLOAD + 1,要求返回 ERROR。
它证明 ADB 不能假设底层 read/write 保留 packet 边界,也证明 payload 上限是协议安全边界,而不是 终端命令长度建议。
8.4 Listener验证
adb_listeners_test.cpp 覆盖普通安装、重绑定、禁止重绑定、动态端口、删除、删除不存在 listener 以及 transport disconnect。测试在 teardown 中调用 remove_all_listeners 并检查 fdevent 计数为零。
它证明 listener 状态拥有者是 server,并且 transport 断开会触发清理;不能把 forward/reverse 的 socket 状态留给 client 进程自行猜测。
9. 源码导航
| 问题 | 首个入口 | 继续阅读 |
|---|---|---|
| client 为什么启动/重启 server | client/adb_client.cpp::adb_check_server_version | client/commandline.cpp::launch_server、client/main.cpp::adb_server_main |
| 请求如何选择设备 | client/adb_client.cpp::switch_socket_transport | adb.cpp::handle_host_request、transport.cpp::acquire_one_transport |
| host service 为什么返回 unknown | sockets.cpp::smart_socket_enqueue | adb.cpp::handle_host_request、services.cpp::host_service_to_socket |
| 设备为何 offline/unauthorized | adb.cpp::handle_new_connection | transport.cpp、daemon/auth.cpp、client/auth.cpp |
adb shell 怎样创建进程 | adb.cpp::handle_packet 的 A_OPEN | daemon/services.cpp::daemon_service_to_socket、daemon/shell_service.cpp |
| 输出为何卡住或丢失 | sockets.cpp::local_socket_flush_incoming/outgoing | A_OKAY 窗口、fdevent、A_WRTE/A_CLSE |
| 设备拔出后如何回收 | adb.cpp::handle_offline | close_all_sockets、atransport::HandleError、disconnect callbacks |
一个可靠的源码阅读顺序是:先读上游 ADB 总览文档 建立三组件边界,再读上游 ADB 内部实现说明 建立 smart socket、transport、asocket 和 fdevent 模型,最后沿 adb shell id 进入 adb_client.cpp、sockets.cpp、adb.cpp 与 daemon/services.cpp。
当你再次看到“ADB 不工作”时,不要从命令名猜原因。先问这四个问题:server socket 是否可达,目标 transport 是否 online,A_OPEN 是否创建了远端 service,最后一个 A_WRTE/A_CLSE 到达了哪一端。能把 这四个问题分别映射到当前 Android 17 的 owner、状态和测试,才是对 ADB 架构的可验证理解。
