说实话,我第一次接触 adb 的时候,就是被一句“adb shell input tap 540 960”带进门的。那时候手机上一个 App 需要重复点击几百次,点得手指发麻,后来发现一行命令就能搞定,那一刻才意识到:adb 不是某个开发者专属的玩具,而是所有需要跟安卓设备打交道的人都该掌握的效率工具。这篇文章我准备把 adb 自动化脚本这件事一次性讲透,从环境配置到脚本编写,再到多设备并发和常见问题排查,全程用我实际踩过的坑和验证过的方案,帮你少走弯路。
adb 全称 Android Debug Bridge,翻译过来就是安卓调试桥。它的本质是一个客户端-服务端-守护进程结构的三层工具,负责把你的电脑和安卓设备连起来,让电脑可以向设备发送各种调试指令。自动化脚本之所以绕不开 adb,是因为它不依赖设备上是否安装了特定 App,也不要求设备 root,只要开启了开发者模式中的 USB 调试,几乎所有安卓机型都能用。所以无论是做 App 自动化测试、批量安装应用、清理缓存、截屏录屏,还是操作智能电视、电视盒子、模拟器,adb 都是最通用的底座。
这篇文章适合谁?如果你是测试工程师,想写一套无人值守的自动化用例;如果你是个人开发者,想快速验证 App 在不同设备上的表现;或者你只是单纯想用电脑控制手机干一些重复的事情,这篇文章的内容都适用。我会从最基础的命令讲起,最后给到一个可直接改来用的 Python 并发脚本框架,保证你读完能上手。
1. 先把思路理清楚:为什么用 adb 做自动化脚本,而不是其他方案
1.1 adb 的架构决定了它的稳定性和通用性
很多新人会把 adb 想象成一根“数据线命令”,其实它的内部结构比一根线复杂得多。adb 由三个部分组成:电脑上运行的 client(客户端),电脑后台运行的 server(服务端),以及设备上运行的 adbd(守护进程)。client 负责接收你输入的命令,server 负责管理设备和文件传输,adbd 则在设备端真正执行指令。这套架构最大的好处是:server 会一直监听本机端口,即使 client 命令执行完退出了,server 依然可以维护与设备的连接,自动化脚本不需要每次都重新握手,效率会高很多。
另外要明白一点,adb 的指令最终都是在设备的 shell 环境里执行的。它本质上就是你拿到了一个远程终端,后面跟着的 input、screencap、dumpsys 这些命令,都是安卓系统自带的可执行文件。这意味着脚本里可以直接组合使用系统命令,不需要在手机上预装任何额外的服务端软件,对纯命令行爱好者来说非常友好。
1.2 和 App 内自动化工具相比,adb 的差异化优势
现在市面上也有不少手机 App 内置了“自动点击”功能,比如无障碍点击脚本或者录制回放工具,但它们通常需要额外开启无障碍服务,有些还需要 root,而且不同机型兼容性差。adb 方案不需要在手机上安装脚本运行环境,脚本全部在电脑端执行,设备只是被动接收指令,所以不会受到 App 后台限制、省电策略干扰等因素影响。
还有一个容易被忽略的点:adb 的输入事件是通过内核级 input 子系统注入的,和真实手触摸在系统层面几乎一致。相比于某些 Hack 方案会被 App 检测到触摸代理或 Root 环境,adb 的注入方式在绝大多数场景下更“干净”,也因此被广泛用于自动化测试。我见过很多 App 的 UI 测试框架,底层实际上也是封装了一层 adb 命令来触达设备。
1.3 哪些场景真正值得用 adb 脚本,哪些不值得
不是所有事情都需要自动化,我自己总结了一个判断标准:凡是“需要连续重复操作 3 次以上,且操作路径固定不变”的,才值得写成脚本。比如批量把电脑里的 APK 装到 20 台测试机上,手动一台台点至少半小时,脚本 3 分钟跑完;又比如每天开机后要打开某 App、登录、进入某个页面,这种事情写成 input keyevent 加上 am start 组合,能省掉大量重复时间。
反过来,如果某次操作只是临时做一次,或者操作路径经常变化、需要依赖视觉判断,那用 adb 硬写反而浪费时间。比如你只是想截图看一眼,那直接用 adb exec-out screencap -p > screen.png 就够了,不需要封装。理解这个权衡,比记住再多命令都重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备:别让第一步绊倒你
2.1 安装 adb 工具:Windows、macOS、Linux 三条路径
Windows 用户最简单的方式是去官方渠道下载 Platform Tools 压缩包,解压后把里面的 adb.exe、AdbWinApi.dll 等文件放到一个固定目录,然后把该目录添加到系统环境变量 Path 里。这一步能让你在任意目录直接敲 adb 命令,而不是每次都要 cd 到工具目录。我把常用命令贴在这里,方便你直接抄:
bash复制adb version
adb devices -l
macOS 用户如果装了 Homebrew,直接 brew install android-platform-tools 就能完成安装,比手动下载方便很多。Linux 用户一般用 apt install android-tools-adb 或发行版对应的包管理器。注意安装完成后要重新打开终端窗口,否则环境变量不会生效。我遇到过很多人在 Windows 上配完 Path 不重开终端,然后跑来问“为什么找不到 adb”,其实只是环境变量没刷新。
安装完成后,在命令行敲 adb devices,会输出当前连接的设备列表。如果看到 List of devices attached 下面是空的,说明还没有连接任何设备。这一步是整个自动化链路里最容易出问题的一环,我后面在常见问题章节里会详细展开。
2.2 在手机上开启开发者模式和 USB 调试
手机端设置比较统一:进入“设置” -> “关于手机” -> “版本号”,连续点击版本号 7 次,就会提示“您已进入开发者模式”。注意不同品牌的入口可能藏得比较深,比如有些国产手机在“更多设置”里才能找到“开发者选项”,但“连点版本号”这个套路基本不变。进入开发者选项后,打开“USB 调试”,同时建议把“USB 调试(安全设置)”也打开,否则部分自动化命令(比如模拟点击)会被系统拦截。
这里有个我没见别人强调过的细节:部分手机在开发者选项里还有一项“仅充电模式下允许 ADB 调试”,如果这项没开启,即使插了数据线也只能充电,adb devices 依然看不到设备。另外小米手机默认会在插入 USB 后弹出“是否允许 USB 调试”的授权框,必须点“允许”,否则设备状态会一直显示 unauthorized。建议直接勾选“一律允许使用这台计算机进行调试”,这样后续脚本运行过程中不会中断。
2.3 有线连接和无线连接,哪个更适合自动化
自动化脚本往往需要长时间运行,一直挂着数据线既碍事又容易松动。我建议有条件就优先用无线 adb,也就是启用设备上的 TCP/IP 调试端口。前提是手机和电脑处于同一个局域网,手机先通过 USB 连接,然后执行下面两步:
bash复制# 让设备在 5555 端口监听 TCP/IP 连接
adb tcpip 5555
# 查看设备的局域网 IP,一般在“设置 -> WLAN -> 当前网络”里能看到
# 假设设备 IP 是 192.168.1.100
adb connect 192.168.1.100:5555
连接成功后,adb devices 会多出一个 192.168.1.100:5555 的设备,此时可以拔掉数据线。以后设备重启会丢失 USB 调试授权,需要重新插线授权一次,这点要注意。除了 Wi-Fi 无线连接,还有更冷门的“以太网 adb”,主要是针对电视盒子或开发板这类有线网口设备,原理一样,只是把 Wi-Fi 换成有线网络,需要用 adb connect 连到有线网卡的 IP,方法完全相同。
2.4 模拟器、电视和盒子怎么接入 adb
如果你用的不是真机而是夜神、MuMu、雷电这类安卓模拟器,adb 连接方式略有不同。一般模拟器会自带 adb 端口,比如夜神模拟器默认端口是 62001,雷电是 5554 或 5555(不同版本有差异)。打开模拟器后,直接用 adb connect 127.0.0.1:端口号 就能连上,前提是模拟器设置里开启了“允许 adb root 连接”之类的开关。
智能电视和电视盒子画风又不一样。比如创维、酷开这些品牌,需要在设置里进入“关于”,连点“版本号”开启开发者模式,再打开“ADB 调试”,然后你才能在电脑上用 adb connect 电视的IP:5555 连接。TCL、VIDAA 这些品牌也把 ADB 开关藏在开发者选项里。具体入口每个版本不同,但思路是一样的:开发者模式 -> ADB 开关 -> 局域网连接。这里特别提醒一下,智能电视安装第三方 App 时,要用 adb install --bypass-low-target-sdk-block 绕过低版本 Socket 限制,这是电视上常见的坑,我后面细讲。
3. 核心命令速查:自动化脚本的地基
3.1 文件操作、安装卸载和基本系统命令
自动化脚本里最常用的三类命令就是文件操作、App 管理和系统信息查询。文件操作主要用于把测试包、配置文件从电脑推送到手机,或者把手机上的日志拉回电脑,常用的是 adb push 本地路径 设备路径 和 adb pull 设备路径 本地路径。App 管理最常碰到的坑是安装失败,比如 INSTALL_FAILED_UPDATE_INCOMPATIBLE 表示安装了不同签名的同包名应用,需要先卸载原应用;还有 Android 14 上安装旧版本 SDK 的 App 时,会报 INSTALL_FAILED_REQUESTED_INSTALLER_PACKAGE_NOT_ALLOWED 之类的错误,此时可以用 Google 官方提供的 adb install --bypass-low-target-sdk-block 参数强制绕过目标 SDK 版本限制。
系统信息查询在定位问题时非常有用。adb shell getprop 可以查看设备的系统属性,比如 ro.product.model 是机型,ro.build.version.release 是系统版本。adb shell wm size 可以获取屏幕分辨率,这在计算点击坐标时至关重要,因为不同手机分辨率不一样,脚本里需要根据分辨率动态计算坐标点,而不是写死。adb shell dumpsys activity top 能查看当前前台 Activity,用来确认脚本是否成功打开了目标页面。
3.2 模拟输入:点击、滑动、文本输入和按键
adb 自动化脚本的核心之一就是模拟人的操作。最常用的是 adb shell input 命令。模拟点击的语法是 adb shell input tap x y,x 和 y 是屏幕坐标,单位是像素。模拟滑动是 adb shell input swipe x1 y1 x2 y2 duration,duration 是滑动持续毫秒数,可以用来实现下拉刷新、滑动列表、还是长按等操作。模拟文本输入是 adb shell input text "hello",注意文本中不能直接包含空格,需要用 %s 代替,比如 adb shell input text "hello%s world"。
模拟按键用 keyevent,常用的键值有:KEYCODE_HOME 对应 3,KEYCODE_BACK 对应 4,KEYCODE_APP_SWITCH 对应 187,KEYCODE_ENTER 对应 66,KEYCODE_MENU 对应 82。如果你想唤醒屏幕,可以用 adb shell input keyevent KEYCODE_WAKEUP,锁屏则用 adb shell input keyevent KEYCODE_SLEEP。这些键值在系统的 KeyEvent 类里都有定义,直接记常用几个就够用。
有个非常实用的进阶命令是 adb shell uiautomator dump。它能将当前界面上的所有控件层级和属性导出为 XML 文件,你可以在电脑上解析这个 XML,找到某个按钮的 bounds 属性,从而自动计算点击坐标。这样可以避免纯靠肉眼找坐标的笨办法,也是很多 App 自动化测试框架背后的原理。不过需要注意,uiautomator dump 对某些使用特殊视图渲染框架的 App 可能拿不到控件信息,这时候只能用坐标硬点。
3.3 系统监控和日志:给脚本加上“眼睛”
脚本不能只会点,还得会看结果。adb shell dumpsys 是一个非常强大的命令族,几乎可以查询系统所有服务状态。自动化测试中最常看的是 dumpsys activity、dumpsys window、dumpsys battery。比如你想知道某个页面是否处于前台,可以用 dumpsys activity activities | grep mResumedActivity;想检查当前屏幕是亮着还是息屏,可以用 dumpsys power | grep mWakefulness。这些信息都可以在脚本里做条件判断,实现“等 App 真的启动完再执行点击”这种智能等待。
日志方面,adb logcat 是 Android 系统的实时日志输出命令,脚本里经常配合 grep 过滤关键字。比如想知道某个崩溃是否发生,可以用 adb logcat -d | grep "FATAL EXCEPTION",然后判断命令返回值。但是 logcat 输出量大,长时间运行会占盘,通常建议用 adb logcat -c 在脚本启动时先清空日志,然后在特定节点备份日志,这样排查问题更高效。
另外,热词里有 dumpsys batterystats --enable full-wake-history,这是用来分析电池和唤醒源的高级命令。自动化测试中如果涉及耗电对比,比如验证某个 App 是否有异常唤醒,可以先用 dumpsys batterystats --enable full-wake-history 开启记录,运行一段时间后再用 dumpsys batterystats 导出报告。配合脚本可以在跑完一个完整流程后,自动抓取耗电数据,形成测试报告。这个功能对做设备功耗测试的团队会很有用。
3.4 脚本编写基础:从单条命令到可复用脚本
单条 adb 命令只能做一件事,真正的自动化是把多条命令组织成一个可执行文件。最简单的落地方式是用 Windows 批处理,比如写一个 test.bat:
bat复制@echo off
adb shell input keyevent KEYCODE_WAKEUP
adb shell wm size
adb shell am start -n com.example.app/.MainActivity
ping -n 3 127.0.0.1 > nul
adb shell input tap 540 1200
这里我用了 ping -n 3 127.0.0.1 > nul 来实现延时 2 秒,也可以直接用 timeout /t 2,但 timeout 在某些精简版 Windows 上可能没有,所以用 ping 的兼容性其实更好。在 Bash 或者 PowerShell 里也可以用类似方式。不过当脚本逻辑变得复杂,比如需要对命令结果做判断、需要遍历设备列表、需要处理异常重试,批处理就力不从心了,这时候建议用 Python。
Python 里执行 adb 命令的核心方式是 subprocess.run,它可以同步等待命令执行并返回输出。一个标准的封装函数长这样:
python复制import subprocess
def run_adb(command, device=None):
if device:
command = ['adb', '-s', device] + command
else:
command = ['adb'] + command
result = subprocess.run(command, capture_output=True, text=True, timeout=30)
return result.stdout.strip()
有了这个函数,你可以很自然地写 run_adb(['shell', 'input', 'tap', str(x), str(y)])。注意 -s device 参数,多设备时必须通过它指定目标设备,否则 adb 会在多设备时报错。我在下文的多设备章节还会详细说线程池并发,这里先记住 adb -s 串号 shell xxx 这种语法。
4. 实操:写一个真正的自动化脚本
4.1 场景拆解:批量清理应用缓存并重启 App
我拿一个最常见的需求来做例子:批量清理测试机上的应用缓存,然后重新启动某个 App。这个场景在 App 测试中很常见,因为每次开始用例前都需要一个干净的环境。步骤拆解如下:
- 获取手机当前连接的设备列表。
- 对每个设备执行:清理目标 App 缓存。
- 等待 App 冷启动完成。
- 截图到电脑端,确认启动是否成功。
这里涉及到的 adb 命令有 adb shell pm clear 包名(清缓存和数据)和 adb shell am start -n 包名/Activity名(启动 App)。注意 pm clear 会清除 App 的所有用户数据,相当于卸载重装后的状态,对测试有用,但不要对用户正在用的手机随便执行。另外如果你只是清缓存不清数据,可以用 adb shell pm trim-caches 来限制缓存大小,不过那跟本文关系不大。
4.2 写一个 Python 版本的脚本
为了真正可用,我把脚本写得稍微完善一点,加入了日志输出和错误处理。完整的代码如下:
python复制import subprocess
import sys
import time
def run_adb(device, command):
cmd = ['adb', '-s', device] + command
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode != 0:
raise RuntimeError(f"adb command failed: {cmd}\nstderr: {result.stderr}")
return result.stdout.strip()
def clear_app_cache(device, package):
print(f"[{device}] clearing {package}...")
out = run_adb(device, ['shell', 'pm', 'clear', package])
if 'Success' not in out:
print(f"[{device}] warning: pm clear output = {out}")
def start_app(device, package, activity):
target = f"{package}/{activity}"
print(f"[{device}] starting {target}...")
run_adb(device, ['shell', 'am', 'start', '-W', '-n', target])
def main():
if len(sys.argv) < 2:
print("usage: python script.py com.example.app/.MainActivity")
sys.exit(1)
target_activity = sys.argv[1]
package = target_activity.split('/')[0]
devices_raw = run_adb(None, ['devices']).splitlines()[1:]
devices = []
for line in devices_raw:
if line.strip() == '':
continue
device = line.split('\t')[0]
status = line.split('\t')[1]
if status == 'device':
devices.append(device)
else:
print(f"skip device {device}, status={status}")
if not devices:
print("no device found")
sys.exit(2)
for device in devices:
try:
clear_app_cache(device, package)
start_app(device, target_activity.split('/')[1])
time.sleep(3)
except Exception as e:
print(f"[{device}] error: {e}")
if __name__ == '__main__':
main()
这里有个非常关键的点:am start -W 参数表示等待 Activity 启动完成后再返回。为什么不用 -n 而无脑 sleep?因为不同设备的冷启动时间差异很大,固定等待太短会截到启动页,太长又浪费时间。-W 会等待系统完成启动该 Activity 的动作,脚本能拿到准确时间。不过它也有限制,比如某些 App 冷启动过程中有闪屏广告,-W 返回的只是第一个界面,不一定包含后续跳转,所以我还额外保留了一个 3 秒的缓冲,实际用的时候可以按需调整。
4.3 加入坐标点击和滑动,模拟真人操作
清缓存和启动只是第一步,自动化往往还需要不停点击、滑动。比如我要在某个 App 里执行“点击搜索框 -> 输入关键词 -> 点击搜索按钮”这样的流程。用到的命令如下:
bash复制adb shell input tap 450 300
adb shell input text "关键词"
adb shell input keyevent 66
这里的坐标需要提前通过 uiautomator dump 查看。你可以在电脑上这样操作:
bash复制adb shell uiautomator dump /sdcard/ui.xml
adb pull /sdcard/ui.xml ./ui.xml
然后打开 ui.xml,搜索包含“搜索框”的 text 属性,找到它的 bounds="[450,280][900,400]",中心点大约是 (675, 340)。这就是我们要点击的坐标。注意不同分辨率下坐标会变化,所以更好的做法是脚本里先获取屏幕分辨率,然后按比例计算坐标点,比如 x = width * 0.5、y = height * 0.7。这样在 1080x2400 和 720x1600 的机器上都能用。
滑动操作也同理。比如要从页面顶部下滑刷新,用 adb shell input swipe 540 800 540 1600 300,起点在屏幕上半部分,终点在下方,中间耗时 300 毫秒。如果你需要在 WebView 或者长列表中滚动,可以用 adb shell input swipe 模拟快速翻页。比较进阶的是用 screencap 截屏后,通过图片处理库(比如 OpenCV)判断元素位置,然后反推坐标,不过这个复杂度已经超出入门篇的范畴了。
4.4 处理低版本 SDK 安装限制和忽略某些安装错误
前面提到过,Android 14 开始对低版本 target SDK 的 App 安装加了限制。热词里也有 adb install --bypass-low-target-sdk-block 和“怎么允许低版本sdk的adb安装”。这个命令确实是最直接的解决方式:
bash复制adb install --bypass-low-target-sdk-block /path/to/old_app.apk
但要注意,这个参数只适用于部分机型上系统本身就允许绕过的情况。如果仍然报 INSTALL_FAILED_OLDER_SDK,说明 App 的 minSdkVersion 比系统版本高,--bypass-low-target-sdk-block 也没用,得换低版本的安卓设备或者让开发调整 minSdk。很多人在电视上安装老版本软件时遇到过这类问题,我的建议是先看完整报错,再决定是加参数还是换包,不要盲目加参数。为了减少安装失败导致脚本中断,可以在 Python 脚本里捕获 run_adb 的异常后,记录错误并继续后续设备,而不是直接退出。我在 4.2 节已经用了 try-except,就是这个目的。
4.5 用 dumpsys batterystats 统计脚本运行过程中的耗电
如果你想观察自动化脚本跑完一轮后设备的耗电情况,可以把电池统计也加到流程里。做法是:
先清空旧统计,开全量记录:
bash复制adb shell dumpsys batterystats --enable full-wake-history
adb shell dumpsys batterystats --reset
在脚本末尾输出统计:
bash复制adb shell dumpsys batterystats > batterystats.txt
然后你在本机把这个文本文件过滤出耗电排名、WakeLock 信息等。如果你用的是 Windows,可以用 findstr;如果你用 Python,可以直接读文件找关键词。这个方法在测试团队里非常实用,它不仅能看 App 耗电,还能看到脚本执行过程中系统是否有异常唤醒,帮助你判断操作是否卡在某个环节导致屏幕常亮。
4.6 参数计算心得:等待时间、坐标、重试次数
写了很多脚本后,我总结出一套参数选择经验:
- 等待时间不要用固定值,优先用轮询。比如每 0.5 秒检查一次当前页面是否出现目标 Activity,直到超时。
- 坐标尽量用比例,别写死像素值。因为测试机可能有多台不同分辨率的设备。
- 执行操作前加一个“前置校验”,比如判断屏幕是否亮着,如果息屏就先唤醒。
- 每组操作之间加 200 到 500 毫秒的小延时,别连发,否则系统会丢弃部分 input 事件。
具体来说,判断屏幕状态的命令是 adb shell dumpsys power | grep "mWakefulness=",如果输出 mWakefulness=Asleep 就需要先 keyevent KEYCODE_WAKEUP。如果你把脚本写成“带重试的点击”,容错率会提升很多:
python复制def tap_with_retry(device, x, y, retries=3):
for i in range(retries):
run_adb(device, ['shell', 'input', 'tap', str(x), str(y)])
time.sleep(0.3)
if run_adb(device, ['shell', 'dumpsys', 'window']).find('mCurrentFocus') != -1:
break
这个重试逻辑的核心在于:点击后检查焦点窗口是否发生了变化,如果没有变化就再点一次。用这种思路,几乎能解决 80% 的偶发点击失效问题。
5. 多设备并发和进阶玩法
5.1 用 adb -s 指定设备,别输错串号
当电脑连接了多台手机、模拟器、电视盒子时,直接运行 adb devices 会看到多行设备列表。如果不指定设备,adb 会报错 more than one device/emulator,所以脚本里必须用 adb -s 设备串号 shell xxx 来指定。设备串号可以直接用 adb devices -l 查看,通常在 device 状态前的那一串字符就是。比如 adb -s emulator-5554 shell input tap 100 100。
在多设备环境下,强烈建议把设备和业务标签做个映射,比如用 Python 字典来维护:
python复制devices_map = {
"device_a": "emulator-5554",
"device_b": "192.168.1.101:5555",
}
脚本跑起来后会清晰很多,也方便后续扩展设备参数配置。
5.2 用线程池并发执行设备任务
串行跑多台设备虽然稳定,但很浪费时间。比如给 20 台设备装同一个 APK,如果一台台跑,每台 20 秒,总耗时 400 秒;如果 5 台并发,总耗时可以压缩到 80 秒左右。Python 里最常见的并发手段是 ThreadPoolExecutor。需要注意,adb 命令本身是 I/O 密集型操作,用线程并发就够了,不需要上升到多进程。
示例代码如下:
python复制from concurrent.futures import ThreadPoolExecutor, as_completed
def run_task_on_device(device, package, activity):
# 省略具体执行的逻辑
...
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(run_task_on_device, dev, pkg, act) for dev in devices]
for future in as_completed(futures):
print(future.result())
这里有几个并发脚本的常见坑:一是 adb server 本身并不是完全线程安全的,所以当并发量过高时,偶尔会出现 adb server failed to start 或者设备掉线。我建议并发数控制在 5 到 10 以内,不要盲目调到 50。二是每个线程内最好自己做重连逻辑,比如检测到设备状态不对劲就重新 adb connect 一次。三是不要在多线程里共用同一个 subprocess.Popen,一定要每个线程独立执行命令。还有一个非常隐蔽的坑:Python 的 capture_output=True 会捕获大量日志,如果命令输出特别大,subprocess 管道缓冲区会被填满从而阻塞,所以可以把不需要输出的命令改成 stdout=subprocess.DEVNULL,或者定期清理日志。
5.3 对模拟器、电视和盒子做自动化时的注意事项
模拟器连接方式前面提过。这里补充一点:夜神、MuMu、雷电都有自己的多开实例,每开一个实例就多一个 adb 端口,需要手动 connect 多次。比如夜神多开后端口依次是 62001、62025、62050 等,你可以用脚本扫描这段端口。如果连不上,可以用模拟器自带的 adb connect 127.0.0.1:端口 试几个端口。
电视盒子这类设备因为硬件性能弱,响应速度比手机慢很多,自动化脚本里的等待时间要拉长。另外大部分电视盒子没有实体返回键事件,需要用 adb shell input keyevent 4 模拟返回,但个别盒子这个键值不一定生效,还需要搭配 input swipe 或者其他方式。还有一点很重要:电视上很多应用是通过遥控器焦点导航的,不一定支持触屏坐标点击,更适合用 dpad 键值来操作,比如 KEYCODE_DPAD_UP(19)、KEYCODE_DPAD_DOWN(20)、KEYCODE_DPAD_CENTER(23)。如果你的目标是给电视写自动化,建议优先研究遥控器按键事件,而不是 tap 坐标。
5.4 定时任务和无人工值守
脚本写好后,怎么定时触发也是个话题。Windows 上可以用“任务计划程序”,在触发器里设置每天几点执行一条 Python 命令。macOS 用 crontab 或者 launchd。我这里给一个 crontab 示例:
bash复制30 2 * * * cd /path/to/script && /usr/bin/python3 auto_task.py > run.log 2>&1
意思是每天凌晨 2 点 30 分执行一次。定时执行要注意设备可能断电、断网,所以脚本开头最好检查设备是否在线,如果不在线就发送提醒或者干脆退出。你可以用 adb wait-for-device 命令等设备上线,但这个命令有超时风险,最好用 adb get-state 循环检测,设置超时时间。以下是一个简单的 wait 函数:
bash复制adb wait-for-device
如果设备一直没接上,这条命令会一直卡住。因此更稳妥的方法是:
python复制import time
def wait_for_device(device, timeout=30):
start = time.time()
while time.time() - start < timeout:
state = run_adb(device, ['get-state'])
if state == 'device':
return True
time.sleep(1)
return False
另外,定时任务的日志特别重要。建议每次运行把输出保存到带时间戳的文件里,比如 run_20250101_023000.log,这样出问题后回来翻日志,能快速定位是设备没连上、命令报错还是网络抖动。
6. 常见问题排查与实战心得
6.1 unauthorized 和 device offline,优先检查授权和驱动
unauthorized 是最常见的连接问题。它的意思是电脑已经识别到了设备,但设备没有授权这台电脑的调试请求。解决办法是看手机屏幕上有没有弹窗,有就点“允许”;如果没有弹窗,可以试着在电脑上执行:
bash复制adb kill-server
adb start-server
adb devices
然后重新插拔一下 USB 线,或者到开发者选项里撤销所有 USB 调试授权,再重新授权一次。值得注意的是,换过 USB 接口、换了电脑、或者升级过手机系统,都可能让旧授权失效,需要重新点弹窗。
device offline 则更麻烦。它通常意味着 adb server 连上了设备但设备没有正确响应。如果重启 adb 无效,建议换一根数据线,尤其是那种只有充电功能没有数据传输功能的数据线。此外,部分手机在连接电脑时默认使用 MIDI 或 PTP 模式,需要在通知栏把 USB 模式切换成“文件传输/MTP”,否则 adb 会不稳定。
还有一个容易出现在 Windows 上的经典报错:
text复制error: adb: could not initialize winsock: a system call has failed. (10107)
这个多半是 Winsock 目录或协议栈损坏,或者杀毒软件拦截了 adb 的网络通信。我用过的解决方案是:以管理员身份运行命令提示符,执行 netsh winsock reset 后重启电脑。如果还不行,检查防火墙是否放行了 adb.exe 的网络访问。
6.2 安装 APK 失败的几种错误和解决思路
安装失败的类型五花八门。我整理了一个速查表:
| 报错信息 | 常见原因 | 解决方法 |
|---|---|---|
INSTALL_FAILED_UPDATE_INCOMPATIBLE |
已安装的 App 与当前安装包签名不一致 | 先卸载原应用,或使用 adb uninstall 包名 |
INSTALL_FAILED_OLDER_SDK |
App 要求的最低系统版本高于当前系统 | 更换低版本 App 或使用更高版本设备 |
INSTALL_FAILED_ALREADY_EXISTS |
已安装同版本 App | 加 -r 参数覆盖安装:adb install -r xxx.apk |
INSTALL_FAILED_INSUFFICIENT_STORAGE |
设备存储不足 | 清理设备空间 |
INSTALL_FAILED_USER_RESTRICTED |
用户限制安装或开启了“仅充电”模式 | 检查开发者选项,或到设置里允许安装未知来源 |
遇到 --bypass-low-target-sdk-block 无法解决的问题时,你还可以试试用 ad install -t 绕过 targetSdk 检查,也就是允许安装 testOnly 包。不过要记住,-t 并不是为了让你绕过所有限制,而是给测试包用。
6.3 点击、滑动无效?大概率是坐标和权限问题
很多新手在自动化脚本里点不到目标,第一个想到的是坐标写错了。但真正原因往往是两个:一是坐标用错了参考系,adb 的坐标系是以设备物理像素为单位的,不是 dp 单位,所以不同屏幕上同样位置的坐标值不一样;二是部分 App 设置了 FLAG_SECURE,禁止截屏,也禁止某些输入模拟,这时候 uiautomator dump 会拿到空数据,点击也无效。
如果排除了这些,还有一种可能是设备上有“USB 调试(安全设置)”权限未打开。打开这个权限后,adb 才能执行 input 命令。某些国产系统默认关闭这项权限,需要手动在开发者选项里打开。另外,如果你发现在不同 App 间切换后点击失效,很可能是因为 App 用到了自定义手势或加密校验,这些只能通过修改坐标和等待时机来规避。
6.4 多设备并发时掉线,如何自动恢复
并发脚本跑了一个小时后,偶尔会有一两台设备 unauthorized 或者 offline,导致任务失败。我的处理方法是:在每个设备的任务循环里加上“设备健康检查”和“自动重连”逻辑。检查命令:
bash复制adb devices
如果状态不是 device,就执行 adb reconnect offline 或者重新 adb connect ip:port。对无线连接,我习惯先用 adb disconnect 再重新 connect;对 USB 连接,只能尽量从硬件层面保证连接稳定,并在脚本里对失败设备做标记,最后汇总报告。
6.5 关于安全边界的实践心得
最后说点关于使用边界的心里话。adb 是一件非常强大的工具,但“强大”意味着需要自律。我只建议在你自己拥有的设备、或者你拥有明确测试授权的设备上使用。不要使用 adb 技术绕过他人设备的锁屏密码、家长控制或者付费验证;也不要拿脚本去进行任何形式的自动化攻击或违规操作。像“忘记密码后用 adb 解开”这类需求,在较新的安卓系统上基本不可能实现,因为安全机制已经堵住了这个口子,正确做法是走官方找回流程。对儿童手表、电视盒子这类设备,同样要尊重厂商的安全设计和用户授权机制。
我实际工作中也踩过不少边界上的坑,比如试用某个自动化框架时,差点为了方便给测试设备关掉所有安全校验,结果导致测试结果失真。后来我意识到,自动化脚本的最终目标是提高效率,而不是破坏设备或绕过规则。写了这么多年 adb 脚本,我最深刻的体会是:真正有用的自动化不是堆砌几百行代码,而是把反复出现的操作流程抽成几个可靠的小工具,然后耐心打磨等待时间和异常处理。每次跑脚本前,我一定会先跑一遍“设备检查”再动手。这个习惯帮我避免了很多莫名其妙的失败。如果你也想在团队里落地 adb 自动化,建议从“每天重复三遍以上”的小事开始,比如批量装包、清缓存、拉日志,把这些做成脚本后,你就会慢慢找到自己的节奏。
