ADB (Android Debug Bridge) 详解

无论你是做 App 爬虫自动化、逆向分析,还是 Android 原生 / 跨端开发,ADB(Android Debug Bridge)都是一把“移动端瑞士军刀”。它让你能像操控本地命令行一样操控手机:装应用、传文件、抓日志、截屏、端口转发……一条命令搞定。

这篇文章从零开始,用最直白的语言把 设备连接、文件传输、应用管理、Python 自动化 这几个核心场景讲透。让你不必零散查命令,看完就能上手。


一、设备管理篇:连接是第一步

新手最容易卡在 “unauthorized”(未授权)、“offline”(离线) 或者 设备列表一片空白。我们先把 USB 调试、WiFi 无线连接、常见问题排查一次性说清楚。

1.1 开启 USB 调试(避坑要点)

无论用 USB 还是 WiFi,手机都必须先弹出 “允许 USB 调试” 的授权弹框并确认。配置过程只需要做一次:

  1. 数据线:必须用“充电 + 数据传输二合一”的数据线,纯充电线无法识别。
  2. 开发者选项:进入「设置 → 关于手机」,连续点击 版本号 7 次,直到提示“已开启开发者选项”。
  3. 打开调试开关:返回「设置 → 系统(或更多设置)→ 开发者选项」,找到并开启以下两个选项:
    • USB 调试
    • USB 调试(安全设置)(部分系统叫“USB 安装”或“取消授权空白”等)
  4. 弹框确认:用数据线连接电脑后,手机会弹出“允许 USB 调试吗?”对话框,务必勾选“一律允许使用这台计算机进行调试”,再点击“确定”。

如果弹框死活不出来,或者显示 unauthorized,用下面这条“万能重启大法”:

# 停止 ADB 服务(能解决绝大多数授权和离线问题)
adb kill-server
# 重新启动 ADB 服务
adb start-server

之后再重新插拔数据线或重新连接 WiFi,弹框通常就会出现。

1.2 基础连接检查

连接上设备后,先用 adb devices 确认状态:

# 查看所有已连接设备的 ID 和状态
# device   → 已授权,可以操作
# unauthorized → 未授权,需要手机上确认
# offline  → 离线,一般是服务挂了或调试开关关闭
adb devices

# 只提取正常设备的 ID(写脚本时常用)
adb devices -l | awk 'NR>1 && /device/ && !/offline/ {print $1}'

1.3 WiFi 无线调试(解放数据线)

手机和电脑连在 同一个局域网(同一个 WiFi 或路由器下)就可以开启无线调试,以后不用再插线。

操作步骤:

# 1. 先通过 USB 连上设备,确认 adb devices 显示 device
# 2. 让手机监听 TCP/IP 端口(默认 5555)
adb tcpip 5555

# 3. 查看手机的局域网 IP 地址(任选一种命令)
# 方法 A:最通用,绝大多数机型可用
adb shell getprop dhcp.wlan0.ipaddress
# 方法 B:查看网卡信息,试试 wlan0 / eth0 / rmnet_data0
adb shell ifconfig wlan0
# 方法 C:Android 10 及以上更精准
adb shell cmd connectivity wifi status | grep "IpAddress"

# 4. 用 IP + 端口连接(替换成你手机的 IP)
adb connect 192.168.1.100:5555

# 5. 拔掉 USB 线,再次运行 adb devices 确认设备状态为 device

连接成功后,以后只要手机和电脑在同一个局域网,每次直接 adb connect <手机IP>:5555 就能连上。

提示:如果 WiFi 连接不上,可以先 adb kill-server && adb start-server 再试。另外有些系统重启后 5555 端口会关闭,需要再次用 USB 线激活一次。


二、文件传输篇:高速互传,不压画质

还在用微信 / QQ 传文件?不仅画质被压缩,速度还慢。ADB 的文件传输走数据线或局域网,速度取决带宽,非常适合传 APK、日志、测试数据。

常用命令:

# 将本地文件推送到手机
# /sdcard/ 开头是用户公共目录,不需要 root
adb push ./test_apk.apk /sdcard/

# 整个文件夹递归推送(-r)
adb push -r ./crawler_temp /sdcard/crawler_backup/

# 从手机拉取文件到本地
adb pull /sdcard/logcat_latest.txt ./
adb pull -r /sdcard/crawler_backup/ ./local_data/

# 增量同步(只传输有变化的文件,比全量 cp 更快)
adb sync          # 同步 /sdcard 等公共分区
adb sync /system  # 同步系统分区(需 root)

三、应用控制篇:App 爬虫的基本功

爬虫的第一步就是控应用:安装、卸载、获取包名、启动、强制停止,这些 ADB 都能完美支持。

3.1 安装与卸载

# 安装 APK
adb install ./test_apk.apk          # 普通安装
adb install -r ./test_apk_v2.apk    # 覆盖安装(保留数据)
adb install -d ./test_apk_v1.apk    # 降级安装(签名要相同)
adb install -s ./test_apk.apk       # 安装到 SD 卡(旧机型)

# 卸载应用
adb uninstall com.example.testapp       # 普通卸载
adb uninstall -k com.example.testapp    # 彻底卸载(删除所有数据)

实际使用中,install -runinstall -k 用得最多,前者用于版本迭代,后者用于重置应用环境。

3.2 查询应用信息(找包名、启动页)

# 列出所有安装的包
adb shell pm list packages        # 全部(系统 + 第三方)
adb shell pm list packages -3     # 只看第三方(爬虫最常用)
adb shell pm list packages -s     # 只看系统应用
adb shell pm list packages -f     # 显示包对应的 APK 路径(方便逆向分析)

# 查看某个应用的详细信息
adb shell dumpsys package com.example.testapp

# 从 dumpsys 中快速提取关键信息
# 版本名
adb shell dumpsys package com.example.testapp | grep versionName
# 版本号
adb shell dumpsys package com.example.testapp | grep versionCode
# 启动 Activity(找带 LAUNCHER 的)
adb shell dumpsys package com.example.testapp | grep -A 5 "android.intent.category.LAUNCHER"

拿到包名和启动 Activity 后,就能精确控制应用启动,比用 monkey 更稳定。

3.3 启动与停止应用

# 强制停止(彻底杀掉进程)
adb shell am force-stop com.example.testapp

# 启动应用
# 方法一:指定组件名(最推荐)
adb shell am start -n com.example.testapp/.MainActivity

# 方法二:用 monkey 模拟点击图标(不知道启动 Activity 时备用)
adb shell monkey -p com.example.testapp -c android.intent.category.LAUNCHER 1

启动完应用后,就可以配合截图、录屏、日志等开始自动化工作。


四、Python 自动化控制:批量操作解放双手

单条命令效率太低,比如你要给 10 台手机安装同一个应用,或者每隔 5 秒自动截屏,手动敲命令太容易出错。用 Python 把 ADB 命令封装成类,不仅能批量处理,还可以灵活集成到爬虫流程里。

下面是一个轻量级的 ADB 控制器,基于 Python 自带的 subprocess 模块,无需安装任何第三方库,拿来即用。

import subprocess
import time
import os
from typing import Optional, List

class LightADB:
    """轻量级 ADB 控制器,专注 App 爬虫 / 日常自动化"""

    def __init__(self, device_id: Optional[str] = None, timeout: int = 30):
        self.device_id = device_id
        self.timeout = timeout
        self.available_devices = self._fetch_devices()
        self.current_device = self._select_current_device()
        print(f"✅ ADB 初始化完成,当前操作设备: {self.current_device}")

    def _fetch_devices(self) -> List[str]:
        """获取所有已授权在线设备的 ID"""
        try:
            result = subprocess.run(
                ['adb', 'devices'],
                capture_output=True, text=True, timeout=self.timeout
            )
            raw_lines = result.stdout.strip().split('\n')[1:]
            return [
                line.split('\t')[0]
                for line in raw_lines
                if '\tdevice' in line
            ]
        except subprocess.TimeoutExpired:
            print("⚠️ ADB 命令超时,请检查服务是否正常")
            return []
        except Exception as e:
            print(f"❌ 获取设备列表失败: {e}")
            return []

    def _select_current_device(self) -> str:
        """选择当前要操作的设备"""
        if not self.available_devices:
            raise RuntimeError("❌ 未找到可用 ADB 设备,请检查连接和授权。")
        if self.device_id and self.device_id in self.available_devices:
            return self.device_id
        return self.available_devices[0]

    def _exec_cmd(self, cmd_list: List[str]) -> str:
        """执行 adb -s <serial> <cmd> 命令并返回输出"""
        full_cmd = ['adb', '-s', self.current_device] + cmd_list
        try:
            result = subprocess.run(
                full_cmd,
                capture_output=True, text=True, timeout=self.timeout
            )
            if result.returncode != 0:
                print(f"⚠️ 命令执行失败: {' '.join(full_cmd)}\n{result.stderr.strip()}")
                return ""
            return result.stdout.strip()
        except Exception as e:
            print(f"❌ 命令异常: {e}")
            return ""

    # ---------- 常用操作方法 ----------
    def get_third_packages(self) -> List[str]:
        """获取所有第三方应用包名"""
        output = self._exec_cmd(['shell', 'pm', 'list', 'packages', '-3'])
        return [line.split(':')[1] for line in output.split('\n') if line.strip()]

    def capture_screen(self, save_dir: str = "./adb_screenshots") -> Optional[str]:
        """
        手机截屏并自动拉取到本地
        :param save_dir: 本地保存目录,会自动创建
        :return: 成功时返回图片路径,失败返回 None
        """
        os.makedirs(save_dir, exist_ok=True)
        timestamp = int(time.time())
        temp_phone_path = f"/sdcard/adb_temp_{timestamp}.png"
        local_path = os.path.join(save_dir, f"screen_{timestamp}.png")

        # 1. 手机端截图
        self._exec_cmd(['shell', 'screencap', temp_phone_path])
        # 2. 拉取到电脑
        pull_res = self._exec_cmd(['pull', temp_phone_path, local_path])
        # 3. 删除手机临时文件
        self._exec_cmd(['shell', 'rm', temp_phone_path])

        if "pulled" in pull_res.lower():
            print(f"✅ 截屏已保存: {local_path}")
            return local_path
        return None

# ================= 使用示例 =================
if __name__ == "__main__":
    try:
        adb = LightADB()

        # 1. 查看前 3 个第三方应用
        packages = adb.get_third_packages()[:3]
        print(f"📱 前 3 个第三方应用: {packages}")

        # 2. 每隔 2 秒截屏一次,连续截 3 次
        for i in range(3):
            adb.capture_screen()
            if i < 2:
                time.sleep(2)

    except Exception as e:
        print(f"❌ 示例运行失败: {e}")

这个最简控制器覆盖了连接管理、包名获取、截屏等高频操作。你可以根据自己的需求继续扩展:比如添加 tapswipeinput 等方法,实现自动化点击、滑动和文字输入。


五、补充几个实用小技巧

5.1 端口转发

有时候我们需要把手机上的一个本地服务映射到电脑上。比如手机在 5000 端口跑了一个 HTTP API,用端口转发即可在电脑上通过 localhost:6000 直接访问。

# 将电脑 6000 端口转发到手机的 5000 端口
adb forward tcp:6000 tcp:5000

# 查看当前所有转发规则
adb forward --list

# 取消所有转发
adb forward --remove-all

这在调试远程接口、代理中间人抓包时非常有用。

5.2 清空日志缓冲区

爬虫或调试时,旧日志太多会干扰分析。先清空再开始抓日志,结果干净很多:

# 清空所有日志缓冲区
adb logcat -c

之后再用 adb logcat -s YOUR_TAG 抓指定标签的日志,就很清爽了。


ADB 是移动端调试的基石。上面这些命令和 Python 封装足以支撑大部分 App 爬虫和自动化需求。如果碰到更复杂的 UI 交互(比如需要点击按钮、滑动列表、输入文本),可以进一步结合 uiautomator2Appium 这类高级框架,但底层还是离不开 ADB 的稳定连接和基础操作。

希望这篇详解能帮你顺利拿下移动端自动化这一关。