教程
Python 控制安卓模拟器:ADB 自动化教程
面向 Macro Automation Studio 的 Python 控制安卓模拟器教程:在 BlueStacks 上点击、滑动、输入、按键和截图,adb 由应用替你管理。
- Windows
- Mac
- 模拟器
- 云手机
- 手机
- Python SDK
本页目录
大多数 Python 控制安卓模拟器的教程最后都变成一个包着 adb shell input tap 的 subprocess 封装。这篇面向 Macro Automation Studio(MAS)的 Python 控制安卓模拟器教程走另一条路:应用掌管 adb,你的脚本通过 mas 包和应用对话。指南从一个新项目走到一个打开应用、搜索关键词、滚动并保存截图的脚本。适合想驱动 BlueStacks、LDPlayer、MuMu Player 或 MEmu 又不想写 adb 调用的 Python 开发者。
开始之前
- 已安装 MAS 并用试用或套餐登录。参见安装 MAS。
- 一个模拟器正在运行、已开启 ADB,并已在设备组中添加为设备。参见设备。
- 你懂基础 Python。其他什么都不用你装:MAS 自带 Python 3.13 环境,里面有
mas。
各部分如何配合
MAS 以 python -u -m src.app 启动你的脚本,工作目录是项目文件夹。它通过环境变量(MAS_RPC_PORT、MAS_RPC_HOST、MAS_DEVICE_ID、MAS_SESSION_TOKEN)传入连接信息。每个 mas.* 调用都变成一个发给应用的 JSON-RPC 2.0 请求。应用执行 adb 命令、模板搜索或 OCR,然后把结果送回来。设备在你的第一行代码运行之前就已绑定,所以没有 connect() 调用,也没有序列号要管理。参见核心概念。
创建项目
- 打开宏,点击新建项目。
- 选择代码型,把目标设备设为移动端,给项目命名,点击创建项目。
- 打开项目。Code Editor 会显示
src/app.py。 - 在设备选择器中选择模拟器,想试某段代码时点击运行(F5)。停止是 Shift+F5。
屏幕尺寸与坐标
你传给 SDK 的每个坐标都是相对设备屏幕左上角的像素偏移,以设备自己的分辨率计。模拟器窗口在你显示器上的大小无关紧要。
import mas
size = mas.get_screen_size()
print(f"Screen: {size.width}x{size.height}")
center = (size.width // 2, size.height // 2)
mas.click(*center)get_screen_size() 返回一个带 width 和 height 的 ScreenSize。get_device_info() 返回同样的信息,外加设备的 name、type 和 connected 标志。当脚本必须适应分辨率变化时,像结尾的示例那样按尺寸的比例计算位置。模板图片和 OCR 区域适应不了分辨率变化;参见图像识别指南。
用 Python 通过 adb 截图
import base64
import mas
shot = mas.take_screenshot()
print(shot.width, shot.height, shot.timestamp)
with open("screen.png", "wb") as f:
f.write(base64.b64decode(shot.base64))take_screenshot() 返回一个 Screenshot:base64 存放 PNG,width 和 height 是它的像素尺寸,timestamp 是 ISO 8601 字符串。上面的文件会落在项目文件夹里,因为那是工作目录。把同一个对象用 screenshot=shot 传给多个 find_object 调用,它们就会搜索同一帧,而不是再次截图。
点击
mas.click(270, 800) # tap, then wait 1000 ms
mas.click(270, 800, delay_ms=300) # shorter pause for tight loopsclick(x, y, delay_ms=1000) 点击一次,然后等待 delay_ms 让应用响应。点击没有按住时长;长按请用带 duration_ms 的 key_press。
滑动
mas.swipe((270, 750), (270, 300), duration_ms=400) # scroll a list down: drag from lower to upper
mas.swipe((100, 500), (400, 500), duration_ms=1000) # drag and drop: slow and deliberateswipe(from_coords, to_coords, duration_ms=1000) 接受两个 (x, y) 元组。时长短是轻扫,时长长是拖拽。捏合手势是单独的调用:zoom_in() 和 zoom_out() 默认以屏幕中心为准,percent=50。
输入文字
mas.click(270, 120, delay_ms=500) # focus the field first
mas.input_text("hello world")
mas.input_text("new value", clear=True) # replace what is thereinput_text(text, delay_ms=0, clear=False) 向获得焦点的输入框输入,所以先点一下输入框。clear=True 会把光标移到末尾并删除已有字符,然后再输入。
按键
from mas import KeyCode
mas.key_press(KeyCode.BACK)
mas.key_press(KeyCode.HOME)
mas.key_press(KeyCode.ENTER)
mas.key_press(KeyCode.DELETE, repeat=10) # ten presses in one command
mas.key_press(KeyCode.POWER, duration_ms=3000) # long presskey_press(key_code, modifiers=None, duration_ms=100, repeat=1) 发送一个安卓按键事件。duration_ms 达到 500 或以上就成为长按;超出的具体毫秒数不会被精确遵守。repeat 的范围是 1 到 100,比 Python 循环快得多。KeyCode 还有方向键、音量键、媒体键和数字键。
打开并检查应用
mas.open_app("com.android.settings", timeout_ms=5000)
print(mas.get_current_app()) # package name in front
print(mas.is_app_focused("com.android.settings")) # True or False
if mas.get_app_state("com.android.chrome") == mas.NOT_RUNNING:
mas.open_app("com.android.chrome")
mas.close_app("com.android.settings")open_app(package_name, timeout_ms=2000) 按包名启动并等待 timeout_ms。要知道包名,手动打开应用然后打印 mas.get_current_app()。get_app_state 返回 NOT_INSTALLED、NOT_RUNNING、RUNNING_IN_BACKGROUND_SUSPENDED、RUNNING_IN_BACKGROUND 或 RUNNING_IN_FOREGROUND。close_app 强制停止该包。
一个完整的脚本
这个脚本打开安卓设置、搜索一个关键词、滚动结果、保存截图,并用 OCR 确认关键词在屏幕上。搜索框是你在 Asset Lab 中裁剪的模板;把 ID 换成你的。滑动使用屏幕尺寸的比例,所以在任何分辨率下都能工作。
import base64
import sys
import time
import mas
from mas import KeyCode
PACKAGE = "com.android.settings"
TERM = "Display"
images = mas.images({"search_box": 301})
def main():
size = mas.get_screen_size()
mas.log(f"Screen {size.width}x{size.height}")
mas.open_app(PACKAGE, timeout_ms=5000)
if not mas.is_app_focused(PACKAGE):
mas.log(f"{PACKAGE} did not come to the front", level="error")
sys.exit(2)
box = mas.find_object_retry(images.search_box, total_tries=3, time_sleep=2.0)
if box is None:
mas.log("Search box not found; crop it in Asset Lab", level="error")
sys.exit(2)
mas.click(box.x, box.y, delay_ms=800)
mas.input_text(TERM, clear=True)
mas.key_press(KeyCode.ENTER)
time.sleep(2)
x = size.width // 2
mas.swipe((x, int(size.height * 0.75)), (x, int(size.height * 0.35)), duration_ms=600)
time.sleep(1)
shot = mas.take_screenshot()
with open("search_results.png", "wb") as f:
f.write(base64.b64decode(shot.base64))
mas.log(f"Saved search_results.png ({shot.width}x{shot.height})")
text = mas.read_text(screenshot=shot, psm=11)
mas.log(f"Term on screen: {TERM.lower() in text.text.lower()}")
mas.key_press(KeyCode.HOME)
if __name__ == "__main__":
main()psm=11 的 read_text 读取整帧上的零散文字,适合结果列表。OCR 指南介绍读取单个计数器用的区域和模式。如果你更愿意描述任务而不是写代码,MAS Agent 可以替你写这类脚本。
adb 在这篇 Python 教程里的位置
你从不调用 adb,但底层干活的正是它。
- 可执行文件。 安装包自带 adb。在 Windows 上位于
C:\ProgramData\MacroAutomationStudio\3rdparty,还有一份内置副本作为后备。在 Mac 上位于应用包内,Homebrew 的 adb 作为后备。不会往你的 PATH 里放任何东西。安装页有细节。 - 连接。 模拟器在一个本地 TCP 端口上暴露 adb。你点击启动或运行时,MAS 对设备卡片上的端口运行
adb connect 127.0.0.1:<port>,并在运行期间保持这个会话。 - 输入。
click变成adb shell input tap,swipe变成adb shell input touchscreen swipe,input_text变成adb shell input text,key_press变成adb shell input keyevent。捏合手势直接写入模拟器的触摸输入设备。 - 屏幕。
take_screenshot和每个find_object都用adb exec-out screencap -p截图,这是二进制安全的形式,在 Windows 上不会损坏 PNG。get_screen_size读取wm size;get_current_app读取窗口管理器。 - 应用。
open_app通过monkey触发启动器 intent,失败时回退到am start;close_app运行am force-stop。 - 服务器。 MAS 运行自己的 adb 服务器。如果你 PATH 上的第二个 adb 版本启动了自己的服务器,两者会互相替换,你的设备会掉回连接中。ADB 故障排查页按端口列出了修复方法。
因为这一切都由应用完成,在 BlueStacks 上写的脚本无需修改就能在 LDPlayer、云设备或通过本地端口连接的你自己的手机上运行。
可能出什么问题
Could not discover RPC port
脚本是从终端启动的,而不是从 MAS,所以环境变量缺失。用 Code Editor 里的运行或从设备卡片运行它。
点击落在错误的位置
你用的是窗口像素而不是设备像素,或者模拟器的分辨率变了。打印 mas.get_screen_size(),和你传入的坐标对比。Asset Lab 给出的坐标已经是设备像素。
input_text 什么都没输入
没有输入框获得焦点。用 click 点一下输入框,并给它 delay_ms=500 再输入。有些应用会弹出盖住输入框的键盘;输入后用 key_press(KeyCode.BACK) 关掉它。
找不到设备或设备离线
模拟器中 ADB 是关闭的、设备卡片上的端口错了,或者另一个 adb 服务器接管了。按照 ADB 故障排查处理。
如果你驱动的应用是游戏,请像对待任何其他自动化账号一样谨慎。没有任何自动化工具是 100% 无风险的,请负责任地使用自动化,并自行斟酌决定。
下一步
相关页面
谢谢。如果有哪里不对,请在 Discord 告诉我们。
有疑问? 在 Discord 提问