用 adb 无线调试其他设备
在 AiCode 的 Linux 容器里安装好 adb,即可通过无线调试(Android 11 及以上)连接另一台 Android 手机,或连接 AiCode 所在的这台手机本身——全程不需要电脑。
适用场景
- 调试另一台手机:安装应用、抓日志、截屏、模拟输入、上传下载文件。
- 调试本机:在 AiCode 里对正在使用的这台手机执行同样的操作。
- 需要 root 权限的操作不适用:adb 以
shell(uid 2000)身份运行。
环境基线
| 项 | 值 |
|---|---|
| 系统 | Debian GNU/Linux 12 (bookworm) aarch64(PRoot 容器) |
| adb | 34.0.5(Debian backports;仓库默认版本过旧,缺少 pair 命令) |
| 目标设备 | Android 11 及以上(本文实测 Android 15 与 Android 16) |
| 网络 | 调试另一台手机时两台设备需在同一局域网;调试本机走 127.0.0.1,不依赖局域网 |
| 权限 | shell(uid 2000),非 root |
1. 安装 adb
容器仓库自带的 adb 版本过旧,缺少无线调试所需的 pair 命令,需要从 backports 安装:
apt update
apt install -y -t bookworm-backports adb
adb version
# 应输出 Version 34.x注意:adb 安装在容器系统目录,重装容器后需要重新安装,配对记录也会一并丢失。
2. 配对(只需一次)
先在目标手机上操作:
- 设置 → 关于手机 → 连点「版本号」7 次,开启开发者选项;
- 开发者选项 → 打开「无线调试」;
- 点「使用配对码配对设备」,屏幕会显示 IP 地址和端口 与 6 位配对码。
建议把「无线调试」设置页与 AiCode 分屏显示,边看边操作——配对对话框一旦被切走或关闭,端口和配对码就立即失效了。配对端口也可以用文末的脚本自动发现,只有 6 位配对码必须读屏幕。
回到 AiCode 终端,把端口和配对码换成屏幕上的值:
adb pair 192.168.1.23:37123 123456
# 成功会输出 Successfully paired to ...注意:配对端口和配对码存活时间很短,对话框一关闭就立即失效,需要重新打开。配对成功后设备会记住这台主机,之后不必再配对。
3. 连接
adb connect 192.168.1.23:40001
adb devices -l192.168.1.23:40001 是无线调试主页显示的「IP 地址和端口」,注意它与配对用的端口不是同一个。
连接端口每次重新打开无线调试都会变化,可以重新查看手机屏幕,或用文末脚本自动发现。
4. 调试本机
调试 AiCode 所在的这台手机时,地址用 127.0.0.1(容器与手机共享网络,不依赖局域网):
adb pair 127.0.0.1:37123 123456
adb connect 127.0.0.1:40001
adb devices注意:用手机的局域网 IP 也能连,但同一台设备会同时出现两条记录(一条 127.0.0.1、一条局域网 IP),用 adb -s <序列号> 指定设备,或 adb disconnect 去掉多余的那条。
调试本机的价值在于:Android 限制普通应用直接调用系统命令(screencap、pm、dumpsys 等会被拒绝),而以 shell 身份通过 adb 执行则不受此限制。
常用操作
adb shell getprop ro.product.model # 设备型号
adb shell pm list packages # 已安装应用
adb shell logcat -d # 导出当前日志
adb exec-out screencap -p > screen.png # 截屏
adb push local.txt /sdcard/ # 上传文件
adb pull /sdcard/photo.jpg ./ # 下载文件
adb shell input keyevent KEYCODE_HOME # 模拟按键安装应用用 adb install app.apk,部分设备还需要在开发者选项里允许「通过 USB 安装应用」。
这些命令都以 shell 用户身份运行,可以读写 /sdcard,但无法访问 /data/data 等受限目录。
常见问题
报错 protocol fault (couldn't read status message)
最常见的原因是配对端口已失效(对话框关闭或重新打开过),重新打开对话框、用新的端口和配对码再配一次即可。如果端口确认是最新的仍报此错,直接重试同一条命令——部分 adb 版本存在该已知问题,第二次往往就能成功。
报错 failed to connect to ...: Connection refused
连接端口过期,或设备尚未配对。重新查看无线调试主页的端口,必要时重新配对。
每个新终端都要重新连接
adb 服务随终端会话结束而退出,新会话里需要重新执行 adb connect。把 adb start-server、adb connect 和后续命令放在同一个终端里执行即可。
adb mdns services 列表为空
容器里 adb 自带的 mDNS 发现无法工作——它需要的 5353 端口已被手机系统的 mDNS 服务占用。这不影响手动连接,自动发现请用下面的脚本。
附:自动发现端口
无线调试开启后,设备会在局域网广播 _adb-tls-pairing._tcp(配对服务,仅在配对对话框打开时)和 _adb-tls-connect._tcp(连接服务)。把下面的脚本保存为 discover_adb.py,运行后将分别列出两者的「IP:端口」:
#!/usr/bin/env python3
"""扫描局域网中的 adb 无线调试端点"""
import socket
import struct
import time
SERVICES = {
'配对端口': '_adb-tls-pairing._tcp.local',
'连接端口': '_adb-tls-connect._tcp.local',
}
def encode(name):
out = b''
for part in name.split('.'):
out += bytes([len(part)]) + part.encode()
return out + b'\x00'
def read_name(data, off):
labels, jumped, start = [], False, off
while True:
length = data[off]
if length == 0:
off += 1
break
if length & 0xC0 == 0xC0:
ptr = struct.unpack('!H', data[off:off + 2])[0] & 0x3FFF
if not jumped:
start = off + 2
off, jumped = ptr, True
continue
labels.append(data[off + 1:off + 1 + length].decode())
off += 1 + length
return '.'.join(labels), (start if jumped else off)
def scan(service, seconds=6):
# class 高位置 1 表示请求单播响应:配对服务对普通查询只回组播,
# 不绑定 5353 就收不到;连接服务则回单播
query = (struct.pack('!HHHHHH', 0, 0, 1, 0, 0, 0)
+ encode(service)
+ struct.pack('!HH', 12, 0x8001))
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
struct.pack('4s4s', socket.inet_aton('224.0.0.251'),
socket.inet_aton('0.0.0.0')))
sock.bind(('', 0))
sock.settimeout(0.3)
endpoints = set()
deadline = time.time() + seconds
while time.time() < deadline:
sock.sendto(query, ('224.0.0.251', 5353))
try:
data, addr = sock.recvfrom(9000)
except socket.timeout:
continue
qd, an, ns, ar = struct.unpack('!HHHH', data[4:12])
off = 12
for _ in range(qd):
_, off = read_name(data, off)
off += 4
for _ in range(an + ns + ar):
name, off = read_name(data, off)
rtype, _, _, rdlen = struct.unpack('!HHIH', data[off:off + 10])
off += 10
rdata = data[off:off + rdlen]
off += rdlen
if rtype == 33 and service.split('.')[0] in name:
port = struct.unpack('!HHH', rdata[:6])[2]
endpoints.add(f'{addr[0]}:{port}')
sock.close()
return endpoints
for label, service in SERVICES.items():
for endpoint in sorted(scan(service)):
print(f'{label} {endpoint}')用法:
python3 discover_adb.py
# 配对端口 192.168.1.23:37123
# 连接端口 192.168.1.23:40001
adb pair 192.168.1.23:37123 123456
adb connect 192.168.1.23:40001注意:配对服务只在「使用配对码配对设备」对话框打开时广播,因此要在对话框开启时运行脚本;6 位配对码始终需要从手机屏幕读取。列表里可能出现多个结果(含设备重启前的过期端口),逐个 adb connect 试即可。