1. 从“连一连”说起:为什么辅助工具绕不开ADB
“最强连一连”这类微信小游戏,玩法看着简单——把相同图案的方块用不超过三段的折线连起来,全部消完就过关。但真上手玩到后面几十关,棋盘越来越大、图案越来越密,肉眼找路径的速度根本跟不上,于是很多人就动了写辅助工具的念头。我最初也是这么想的:用OpenCV做图像识别,把棋盘上的图案位置和类型识别出来,再用算法算出连接路径,最后让程序自动点击屏幕完成操作。整个链路里,图像识别和路径算法是“大脑”,而真正把大脑的决策落到手机屏幕上的那只“手”,就是ADB。
ADB全称Android Debug Bridge,是安卓系统自带的一套调试桥接工具。它最核心的能力有两个:一是把手机屏幕的画面实时抓取到电脑上,二是把电脑发出的点击、滑动指令注入到手机里。对于做小游戏辅助来说,这两件事恰好就是全部需求——看屏幕、点屏幕。没有ADB,你的OpenCV代码再精准、路径算法再优雅,也只能在电脑上自娱自乐,碰不到手机里那个真实的游戏。
这篇文章面向的是已经有一点Python和OpenCV基础、想动手做小游戏辅助的开发者。我会把ADB相关的操作从头到尾讲透:怎么装、怎么连、怎么截图、怎么点击、怎么处理多设备、怎么优化速度,以及那些只有真正跑过一遍才会踩到的坑。整套流程我在多个模拟器和真机上都实测过,下面把能直接抄作业的方案和参数都摊开讲。
2. 环境搭建:ADB的安装与设备连接
2.1 ADB工具包的获取与配置
ADB本身是绿色工具,不需要安装程序,解压即用。官方把它打包在Platform Tools里,下载下来是一个压缩包,解压后能看到adb.exe、fastboot.exe以及一堆dll文件。我的习惯是把它放到一个固定目录,比如D:\tools\platform-tools,然后把这个目录加到系统环境变量Path里。这样在任何终端窗口里直接敲adb就能用,不用每次都cd到目录下。
配置完环境变量后,打开一个新的命令行窗口,输入adb version。如果能看到版本号输出,说明配置成功。这里有个小细节:如果你之前装过某些手机助手类软件,它们可能自带了一份老版本ADB并且也写进了环境变量,导致你敲adb时调用的是旧版本。判断方法是看adb version输出的路径,确认是不是你刚配置的那个目录。版本不一致会引发一些莫名其妙的连接问题,我建议统一用较新的版本。
提示:环境变量修改后必须重开终端窗口才生效,已经开着的窗口读的还是旧的环境变量。
2.2 真机连接:开发者模式与USB调试
用真机做辅助,第一步是在手机上打开开发者选项。进入“设置-关于手机”,连续点击“版本号”七次,系统会提示你已进入开发者模式。然后回到设置里找到“开发者选项”,打开“USB调试”。这两个步骤缺一不可,很多人只开了USB调试但没进开发者模式,结果电脑上adb devices一直显示空列表。
用数据线把手机连上电脑后,手机会弹出一个“是否允许USB调试”的授权对话框,勾选“始终允许”再确认。这时候在电脑上执行adb devices,应该能看到一行设备信息,格式是“设备序列号 device”。如果显示的是“unauthorized”,说明授权没通过,拔掉数据线重插一次,重新确认授权弹窗即可。如果显示“offline”,通常是ADB版本和手机系统不匹配,换个新版本ADB一般能解决。
2.3 模拟器连接:端口与连接方式
模拟器是更常用的选择,因为可以多开、可以随时重置、不占用真机。主流模拟器都支持ADB连接,但连接方式略有差异。大部分模拟器在启动后,ADB会自动识别到,直接adb devices就能看到。但有些模拟器需要手动连接,用的是adb connect 127.0.0.1:端口号的形式。
不同模拟器的默认端口不一样,常见的有5555、62001、7555等。如果你不确定端口,可以在模拟器设置里找“ADB调试”或“网络桥接”相关的选项,通常会显示端口号。手动连接成功后,adb devices列表里会出现一个形如127.0.0.1:5555的设备。这里要注意,模拟器重启后端口可能变化,所以连接脚本里最好把端口做成可配置的参数,而不是写死。
| 连接方式 | 命令示例 | 适用场景 | 注意事项 |
|---|---|---|---|
| 真机USB | adb devices | 单设备调试 | 需授权,线材质量影响稳定性 |
| 模拟器自动 | adb devices | 主流模拟器 | 部分模拟器需先启动再执行 |
| 模拟器手动 | adb connect 127.0.0.1:5555 | 自动识别失败时 | 端口因模拟器而异,重启可能变 |
| 无线连接 | adb connect IP:端口 | 真机无线调试 | 需同一局域网,首次需USB配对 |
2.4 多设备场景下的设备指定
当你同时连了真机和模拟器,或者开了多个模拟器实例,adb devices会列出多行。这时候执行任何ADB命令都必须用-s 设备序列号来指定目标设备,否则ADB会报错“more than one device”。比如adb -s 127.0.0.1:5555 shell input tap 100 200。在辅助脚本里,我建议把设备序列号作为全局配置项,所有ADB调用都带上-s参数,这样脚本在多设备环境下也能稳定运行,不会误操作到别的设备上。
3. 核心操作:截图与点击的实现细节
3.1 屏幕截图:三种方式的速度对比
截图是辅助工具的“眼睛”,每次决策前都要先看一眼棋盘。ADB截图最直接的方式是adb shell screencap,把画面存到手机里,再用adb pull拉到电脑。但这种方式要写文件、传文件,一次下来好几百毫秒,对于需要快速连续操作的场景太慢了。
更快的做法是让screencap把PNG数据直接输出到标准输出,电脑端接收字节流后直接交给OpenCV解码,省去了文件读写和传输的中间环节。命令是adb exec-out screencap -p,注意这里用exec-out而不是shell,因为shell会对二进制数据做换行符转换,导致PNG数据损坏。这个坑我踩过,当时截图出来全是花屏,排查了半天才发现是shell把\n转成了\r\n。
还有一种方式是adb shell screencap -p /dev/tcp/...走网络流,但配置复杂、兼容性差,不推荐。实测下来,exec-out screencap -p在模拟器上单次截图大约80到150毫秒,真机稍慢一些,基本能满足辅助的需求。
import subprocess import cv2 import numpy as np def screenshot(device_serial): cmd = ['adb', '-s', device_serial, 'exec-out', 'screencap', '-p'] result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if result.returncode != 0: raise RuntimeError(f"截图失败: {result.stderr.decode('utf-8', errors='ignore')}") img_array = np.frombuffer(result.stdout, dtype=np.uint8) img = cv2.imdecode(img_array, cv2.IMREAD_COLOR) if img is None: raise RuntimeError("图像解码失败,可能是PNG数据损坏") return img这段代码里有个关键点:subprocess.run必须用stdout=subprocess.PIPE来接收二进制数据,不能用text=True,否则Python会尝试用文本方式解码,直接报错。另外np.frombuffer拿到的是只读数组,如果后续要对图像做原地修改,记得先.copy()一份。
3.2 点击操作:input tap与坐标换算
点击用adb shell input tap x y,x和y是屏幕像素坐标,原点在左上角。看起来很简单,但这里有个容易忽略的问题:截图的分辨率和屏幕的实际分辨率可能不一致。比如模拟器窗口被缩放了,截图出来是720p,但实际屏幕是1080p,这时候你按截图坐标去点击就会偏。解决办法是获取屏幕真实分辨率,然后按比例换算坐标。
获取分辨率用adb shell wm size,输出形如Physical size: 1080x1920。把这个值和截图的宽高做对比,算出缩放比例,点击前把坐标乘上比例。我在脚本里封装了一个坐标转换函数,所有点击坐标都先经过转换再发给ADB,这样无论模拟器窗口怎么缩放都不会点偏。
def get_screen_size(device_serial): cmd = ['adb', '-s', device_serial, 'shell', 'wm', 'size'] result = subprocess.run(cmd, stdout=subprocess.PIPE, text=True) output = result.stdout.strip() # 输出格式: Physical size: 1080x1920 size_str = output.split(':')[-1].strip() width, height = map(int, size_str.split('x')) return width, height def tap(device_serial, x, y, screen_size, img_size): real_w, real_h = screen_size img_w, img_h = img_size real_x = int(x * real_w / img_w) real_y = int(y * real_h / img_h) cmd = ['adb', '-s', device_serial, 'shell', 'input', 'tap', str(real_x), str(real_y)] subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)3.3 滑动操作:input swipe的参数与用途
虽然“连一连”主要是点击,但有些场景需要滑动,比如游戏界面滚动、或者某些需要拖拽的操作。滑动用adb shell input swipe x1 y1 x2 y2 duration,duration是滑动持续时间,单位毫秒。这个参数很关键:duration太短,系统可能识别成快速点击而不是滑动;duration太长,操作又太慢。一般设200到500毫秒比较稳妥。
滑动的坐标同样需要做分辨率换算。另外要注意,input swipe的起点和终点如果相同,就变成了长按。有些游戏的长按操作可以用这个特性实现,但“连一连”里用不到。如果你发现滑动没生效,先检查duration是不是设得太小,再检查坐标有没有超出屏幕范围。
3.4 操作速度优化:批量命令与延迟控制
单个ADB命令的往返开销不小,如果每一步操作都单独发一条命令,累积起来会很慢。一个优化思路是把多条命令合并成一条shell命令,用分号或&&连接,减少进程启动次数。比如连续点击多个位置,可以写成adb shell "input tap 100 200; input tap 300 400"。但要注意,input命令本身有执行时间,合并后总耗时不一定减少太多,主要省的是ADB进程的启动开销。
另一个优化点是延迟控制。点击之后游戏需要时间响应和动画播放,如果立刻截下一帧,可能截到的是动画中间状态,导致识别错误。我的做法是在点击后加一个固定延迟,比如300毫秒,然后再截图。这个延迟值需要根据游戏实际动画时长调整,太短会截到过渡帧,太长会拖慢整体速度。实测“连一连”的消除动画大约200到400毫秒,设350毫秒比较合适。
注意:不要用
time.sleep做精确的毫秒级控制,Python的sleep精度受系统调度影响,实际延迟可能偏差几十毫秒。如果需要更精确的控制,可以用time.perf_counter做忙等待,但会占满CPU,一般没必要。
4. 图像识别与ADB的协同工作流
4.1 从截图到棋盘识别的完整链路
整个辅助的工作流是一个循环:截图、识别棋盘、计算路径、执行点击、等待、再截图。ADB负责第一步和第四步,OpenCV负责第二步,路径算法负责第三步。这个循环的节奏很关键,太快了游戏跟不上,太慢了效率低。我实测下来,一个完整的循环大约需要500到800毫秒,其中截图占100到150毫秒,识别占100到200毫秒,点击和等待占300到400毫秒。
识别棋盘的核心是把截图里的每个方块位置和图案类型提取出来。通常的做法是先做颜色分割或模板匹配,定位棋盘的网格区域,然后把每个格子切出来,用模板匹配或特征比对判断图案类型。这一步的精度直接决定了后续路径计算的对错,所以截图的质量很重要。如果截图有压缩伪影或者分辨率太低,识别率会明显下降。
4.2 截图质量对识别率的影响
exec-out screencap -p输出的是PNG格式,无损压缩,质量有保证。但有些模拟器在截图时会做额外的缩放或压缩,导致图像模糊。如果你发现识别率不稳定,可以先保存几张截图下来肉眼看看,确认图像是否清晰、颜色是否准确。我遇到过某个模拟器截图出来颜色偏暗,导致模板匹配阈值需要重新调整的情况。
另一个影响识别率的因素是截图时机。如果截到的是动画中间帧,方块可能处于缩放、淡入淡出或者位移状态,和模板对不上。解决办法是在游戏界面稳定后再截图,比如点击后等待动画结束。判断界面是否稳定的简单方法是连续截两帧做差分,如果差异很小就认为稳定了。这个方法会增加一次截图的开销,但能显著提升识别准确率。
4.3 坐标映射:从图像坐标到屏幕坐标
前面提到过截图分辨率和屏幕分辨率可能不一致,这里再展开说一下坐标映射的细节。假设截图是720x1280,屏幕是1080x1920,缩放比例是1.5。图像上识别到某个方块中心在(360, 640),那么实际屏幕坐标就是(540, 960)。这个换算看起来简单,但要注意截图可能有黑边或者状态栏偏移。
有些模拟器截图会包含状态栏,而游戏画面从状态栏下方开始,这时候图像坐标和屏幕坐标之间还有一个固定的偏移量。处理方法是先识别出游戏画面的实际区域,把偏移量算出来,再做映射。我在脚本里用一个校准步骤:先截一张图,手动或自动标定游戏区域的左上角在截图中的位置,后续所有坐标都基于这个标定做换算。
4.4 多分辨率适配的通用方案
不同设备的分辨率千差万别,如果脚本写死了坐标,换台设备就跑不了。通用方案是把所有坐标都表示成相对比例,比如“屏幕宽度的30%、高度的50%”,用的时候再乘以实际分辨率。这样无论设备分辨率是多少,坐标都能自适应。
对于棋盘识别,也可以用相对比例来定位棋盘区域。比如棋盘通常占据屏幕中间的一块矩形区域,可以用比例描述这个区域的位置和大小,然后在这个区域内做网格划分。网格的行列数可以从游戏规则里获取,或者通过识别结果动态推断。这种比例化的设计让脚本在不同设备上都能直接跑,不需要针对每个分辨率单独调参。
5. 常见问题与排查技巧实录
5.1 设备连接类问题速查
ADB相关的问题里,连接问题占了一大半。下面这张表整理了我遇到过的典型连接问题和解决方法,基本覆盖了九成以上的场景。
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| adb devices列表为空 | USB调试未开或驱动缺失 | 检查开发者选项,重装驱动 |
| 显示unauthorized | 授权弹窗未确认 | 拔插数据线,重新确认授权 |
| 显示offline | ADB版本不匹配 | 更新ADB到较新版本 |
| 模拟器连不上 | 端口不对或未开启调试 | 查模拟器设置里的ADB端口 |
| 多设备报错 | 未指定设备序列号 | 所有命令加-s参数 |
| 连接频繁断开 | 线材或USB口问题 | 换线换口,避免用扩展坞 |
5.2 截图失败与图像异常的排查
截图失败最常见的原因是exec-out被误写成了shell,导致PNG数据被换行符转换破坏。如果你拿到的图像解码失败或者花屏,先检查命令里用的是不是exec-out。另一个原因是设备端screencap权限不足,这种情况在部分定制系统上会出现,解决方法是换用模拟器或者用其他截图方式。
图像异常还包括截图尺寸和预期不符、颜色通道错乱等。尺寸不符通常是模拟器缩放导致的,检查wm size的输出和截图实际尺寸是否一致。颜色通道错乱表现为图像偏色,可能是PNG解码时用了错误的色彩空间,OpenCV的imdecode默认输出BGR格式,如果你后续用RGB处理就会偏色,记得做转换。
5.3 点击无效或点击偏移的处理
点击无效首先要确认坐标有没有超出屏幕范围。如果坐标是负数或者大于屏幕宽高,ADB会静默忽略这条命令,不报错但也不生效。其次是坐标换算有没有做,如果截图和屏幕分辨率不一致而你没换算,点击就会偏。偏移量通常和缩放比例成正比,偏得越多说明缩放比例越大。
还有一种情况是点击被游戏拦截了。有些游戏会检测模拟器或者异常点击频率,如果点击间隔太规律、太快,可能被判定为脚本而失效。解决办法是在点击间隔里加随机抖动,比如在300毫秒基础上随机加减50毫秒,让点击节奏看起来更自然。这个技巧在多个游戏上都验证过,能有效降低被拦截的概率。
5.4 性能瓶颈定位与优化
辅助跑得慢,瓶颈可能在截图、识别、点击任何一个环节。定位方法是给每个环节加计时,打印出各步骤耗时,看哪一步最慢。截图慢通常是设备性能或ADB传输问题,可以尝试降低截图分辨率或者换用更快的连接方式。识别慢一般是模板匹配的搜索范围太大,可以通过缩小搜索区域或者降低模板数量来优化。点击慢主要是ADB命令往返开销,合并命令能有所改善。
我实测过一个优化案例:原本每轮循环要900毫秒,分析后发现识别环节占了400毫秒,原因是模板匹配在全图范围内搜索。后来改成先定位棋盘区域,只在棋盘区域内匹配,识别时间降到120毫秒,整体循环缩短到600毫秒左右,效率提升明显。这个思路的核心是“缩小搜索空间”,在图像处理里是非常通用的优化手段。
5.5 稳定性与长时间运行的注意事项
辅助工具往往要连续跑很长时间,稳定性很重要。我踩过的坑包括:ADB连接在长时间运行后断开、内存泄漏导致脚本越来越慢、截图偶尔返回空数据导致崩溃。针对这些问题,我的做法是加异常捕获和重试机制,任何一步失败都重试几次,连续失败就重连ADB。内存方面,确保每轮循环里的图像对象都被及时释放,不要累积在列表里。
另外建议加一个日志系统,记录每轮的操作和结果。出问题的时候翻日志比盯着屏幕猜要高效得多。日志不用太复杂,把时间戳、截图尺寸、识别到的方块数量、点击坐标这些关键信息记下来就够了。我现在的脚本跑一晚上都不会崩,靠的就是这套重试加日志的机制。
6. 实操心得:那些文档里不会写的经验
做这个辅助的过程中,我最大的体会是:ADB操作本身不难,难的是处理各种边界情况和设备差异。官方文档只会告诉你命令怎么用,但不会告诉你某个模拟器的截图会偏色、某个真机的input命令有延迟、某个分辨率下坐标换算会累积误差。这些东西只能靠实际跑、实际调,踩过坑才知道。
关于截图,我的建议是永远不要假设截图尺寸等于屏幕尺寸,每次启动脚本都先做一次校准,把缩放比例和偏移量算出来。关于点击,永远不要用固定坐标,全部走比例换算,这样换设备不用改代码。关于速度,不要一味追求快,游戏动画需要时间,点太快反而容易出错,找到那个“刚好够”的节奏比盲目加速更重要。
最后分享一个调试技巧:把每轮截图保存下来,用OpenCV把识别结果画在图上,比如在识别到的方块位置画圈、在点击位置画十字,然后人工检查这些标注对不对。这个方法能快速定位是识别错了还是点击偏了,比看日志直观得多。我最初调识别参数的时候,就是靠这个方法一点点把准确率从七成提到九成五以上的。