Skip to content

用 adb 无线调试其他设备 ​

在 AiCode 的 Linux 容器里安装好 adb,即可通过无线调试(Android 11 及以上)连接另一台 Android 手机,或连接 AiCode 所在的这台手机本身——全程不需要电脑。

适用场景 ​

  • 调试另一台手机:安装应用、抓日志、截屏、模拟输入、上传下载文件。
  • 调试本机:在 AiCode 里对正在使用的这台手机执行同样的操作。
  • 需要 root 权限的操作不适用:adb 以 shell(uid 2000)身份运行。

环境基线 ​

项值
系统Debian GNU/Linux 12 (bookworm) aarch64(PRoot 容器)
adb34.0.5(Debian backports;仓库默认版本过旧,缺少 pair 命令)
目标设备Android 11 及以上(本文实测 Android 15 与 Android 16)
网络调试另一台手机时两台设备需在同一局域网;调试本机走 127.0.0.1,不依赖局域网
权限shell(uid 2000),非 root

1. 安装 adb ​

容器仓库自带的 adb 版本过旧,缺少无线调试所需的 pair 命令,需要从 backports 安装:

bash
apt update
apt install -y -t bookworm-backports adb
adb version
# 应输出 Version 34.x

注意:adb 安装在容器系统目录,重装容器后需要重新安装,配对记录也会一并丢失。

2. 配对(只需一次) ​

先在目标手机上操作:

  1. 设置 → 关于手机 → 连点「版本号」7 次,开启开发者选项;
  2. 开发者选项 → 打开「无线调试」;
  3. 点「使用配对码配对设备」,屏幕会显示 IP 地址和端口 与 6 位配对码。

建议把「无线调试」设置页与 AiCode 分屏显示,边看边操作——配对对话框一旦被切走或关闭,端口和配对码就立即失效了。配对端口也可以用文末的脚本自动发现,只有 6 位配对码必须读屏幕。

回到 AiCode 终端,把端口和配对码换成屏幕上的值:

bash
adb pair 192.168.1.23:37123 123456
# 成功会输出 Successfully paired to ...

注意:配对端口和配对码存活时间很短,对话框一关闭就立即失效,需要重新打开。配对成功后设备会记住这台主机,之后不必再配对。

3. 连接 ​

bash
adb connect 192.168.1.23:40001
adb devices -l

192.168.1.23:40001 是无线调试主页显示的「IP 地址和端口」,注意它与配对用的端口不是同一个。

连接端口每次重新打开无线调试都会变化,可以重新查看手机屏幕,或用文末脚本自动发现。

4. 调试本机 ​

调试 AiCode 所在的这台手机时,地址用 127.0.0.1(容器与手机共享网络,不依赖局域网):

bash
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 执行则不受此限制。

常用操作 ​

bash
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:端口」:

python
#!/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}')

用法:

bash
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 试即可。

基于 GPL-3.0 协议开源