# Python 控制安卓模拟器：ADB 自动化教程

> 面向 Macro Automation Studio 的 Python 控制安卓模拟器教程：在 BlueStacks 上点击、滑动、输入、按键和截图，adb 由应用替你管理。

Source: https://automationmacro.com/zh-CN/docs/guides/control-an-emulator-from-python (教程, updated 2026-09-05)

大多数 Python 控制安卓模拟器的教程最后都变成一个包着 `adb shell input tap` 的 `subprocess` 封装。这篇面向 Macro Automation Studio（MAS）的 Python 控制安卓模拟器教程走另一条路：应用掌管 adb，你的脚本通过 `mas` 包和应用对话。指南从一个新项目走到一个打开应用、搜索关键词、滚动并保存截图的脚本。适合想驱动 BlueStacks、LDPlayer、MuMu Player 或 MEmu 又不想写 adb 调用的 Python 开发者。

## 开始之前

- 已安装 MAS 并用试用或套餐登录。参见[安装 MAS](/docs/install)。
- 一个模拟器正在运行、已开启 ADB，并已在**设备组**中添加为设备。参见[设备](/docs/devices)。
- 你懂基础 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()` 调用，也没有序列号要管理。参见[核心概念](/docs/concepts)。

## 创建项目

1. 打开**宏**，点击**新建项目**。
2. 选择**代码型**，把**目标设备**设为移动端，给项目命名，点击**创建项目**。
3. 打开项目。Code Editor 会显示 `src/app.py`。
4. 在设备选择器中选择模拟器，想试某段代码时点击**运行**（<kbd>F5</kbd>）。**停止**是 <kbd>Shift</kbd>+<kbd>F5</kbd>。

## 屏幕尺寸与坐标

你传给 SDK 的每个坐标都是相对设备屏幕左上角的像素偏移，以设备自己的分辨率计。模拟器窗口在你显示器上的大小无关紧要。

```python
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 区域适应不了分辨率变化；参见[图像识别指南](/docs/guides/image-recognition-macros)。

## 用 Python 通过 adb 截图

```python
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` 调用，它们就会搜索同一帧，而不是再次截图。

## 点击

```python
mas.click(270, 800)                 # tap, then wait 1000 ms
mas.click(270, 800, delay_ms=300)   # shorter pause for tight loops
```

`click(x, y, delay_ms=1000)` 点击一次，然后等待 `delay_ms` 让应用响应。点击没有按住时长；长按请用带 `duration_ms` 的 `key_press`。

## 滑动

```python
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 deliberate
```

`swipe(from_coords, to_coords, duration_ms=1000)` 接受两个 `(x, y)` 元组。时长短是轻扫，时长长是拖拽。捏合手势是单独的调用：`zoom_in()` 和 `zoom_out()` 默认以屏幕中心为准，`percent=50`。

## 输入文字

```python
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 there
```

`input_text(text, delay_ms=0, clear=False)` 向获得焦点的输入框输入，所以先点一下输入框。`clear=True` 会把光标移到末尾并删除已有字符，然后再输入。

## 按键

```python
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 press
```

`key_press(key_code, modifiers=None, duration_ms=100, repeat=1)` 发送一个安卓按键事件。`duration_ms` 达到 500 或以上就成为长按；超出的具体毫秒数不会被精确遵守。`repeat` 的范围是 1 到 100，比 Python 循环快得多。`KeyCode` 还有方向键、音量键、媒体键和数字键。

## 打开并检查应用

```python
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 换成你的。滑动使用屏幕尺寸的比例，所以在任何分辨率下都能工作。

```python
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 指南](/docs/guides/ocr-text-reading)介绍读取单个计数器用的区域和模式。如果你更愿意描述任务而不是写代码，[MAS Agent](/agent) 可以替你写这类脚本。

## adb 在这篇 Python 教程里的位置

你从不调用 adb，但底层干活的正是它。

- **可执行文件。** 安装包自带 adb。在 Windows 上位于 `C:\ProgramData\MacroAutomationStudio\3rdparty`，还有一份内置副本作为后备。在 Mac 上位于应用包内，Homebrew 的 adb 作为后备。不会往你的 PATH 里放任何东西。[安装](/docs/install)页有细节。
- **连接。** 模拟器在一个本地 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 故障排查](/docs/adb-troubleshooting)页按端口列出了修复方法。

因为这一切都由应用完成，在 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 故障排查](/docs/adb-troubleshooting)处理。

如果你驱动的应用是游戏，请像对待任何其他自动化账号一样谨慎。没有任何自动化工具是 100% 无风险的，请负责任地使用自动化，并自行斟酌决定。
