当前位置:首页>python>Gemini 3.5 Flash:用 Python 控制 Android 模拟器

Gemini 3.5 Flash:用 Python 控制 Android 模拟器

  • 2026-09-10 05:35:03
Gemini 3.5 Flash:用 Python 控制 Android 模拟器

摘要:Gemini 3.5 Flash 具备内置的计算机使用能力。模型可以查看屏幕截图,判断下一步要执行的操作,并返回对应的函数调用指令,例如 click(y=300, x=500)。开发者通过 ADB 在设备上执行这些指令,再把新的屏幕截图反馈给模型,循环往复,直到任务完成。


本文将介绍如何利用移动设备和 Python SDK 控制 Android 模拟器,构建一个基础的移动端计算机使用代理。

GitHub 仓库地址:

https://github.com/google-gemini/gemini-android-computer-use-quickstart

一、什么是计算机使用?

在 Gemini 3.5 Flash 中,计算机使用是一种内置能力。

用户只需要向模型提供屏幕截图和具体操作指令,例如:


打开设置,并切换到深色模式。



模型会返回一系列具体操作:

  • 点击
  • 输入文本
  • 滑动屏幕
  • 启动应用程序

开发者的代码负责在目标设备上执行这些操作。

它的运行方式类似函数调用:模型提出操作建议,程序执行操作,然后将执行结果和新的屏幕截图反馈给模型。模型通过不断获取屏幕截图,持续判断下一步动作。

Gemini 支持三种使用环境:

  • 浏览器模式:用于桌面端网页自动化
  • 移动端模式:用于移动设备或模拟器
  • 桌面端模式:用于操作系统级控制

本文以移动端模式为例。

二、基本代理循环

下面是一个简化版的代理循环示例:

python
from google import genai

client = genai.Client()
bridge = ADBBridge()

# Take initial screenshot and send first request
screenshot = bridge.screenshot()

interaction = client.interactions.create(
    model="gemini-3.5-flash",
    input=[
        {"type": "text", "text": "Open Settings and enable dark mode"},
        {"type": "image", "data": b64(screenshot), "mime_type": "image/png"},
    ],
    tools=[{"type": "computer_use", "environment": "mobile"}],
)

# Agent loop: execute actions, send results back
while interaction has function_calls:
    for call in interaction.function_calls:
        bridge.execute(call.name, call.args)  # click, type, open_app...

    screenshot = bridge.screenshot()
    interaction = client.interactions.create(
        model="gemini-3.5-flash",
        previous_interaction_id=interaction.id,
        input=[function_results + screenshot],
        tools=[{"type": "computer_use", "environment": "mobile"}],
    )

print(interaction.output_text)

这个循环的核心是:

  1. 获取设备截图。
  2. 把截图和任务发送给 Gemini。
  3. Gemini 返回函数调用指令。
  4. 程序通过 ADB 执行动作。
  5. 再次截图,并把结果反馈给 Gemini。
  6. 重复上述流程,直到任务完成。


三、环境配置

不需要使用 Android Studio 的图形界面。只需在 Mac 上运行安装脚本,即可安装 Android SDK 和模拟器,并通过终端创建虚拟设备。

1. 运行安装脚本

安装脚本:

https://github.com/google-gemini/gemini-android-computer-use-quickstart/blob/main/setup_emulator.sh

bash
chmod +x setup_emulator.sh
./setup_emulator.sh

2. 安装 Python 依赖


bash
pip install google-genai

Python 代理脚本会自动处理 SDK 路径定位和模拟器后台启动,因此无需手动设置环境变量或进行额外终端操作。

四、完整代理脚本

下面是完整的 agent.py 示例。

它会自动设置环境变量,并在后台启动模拟器。如果模拟器尚未运行,脚本会等待模拟器启动完成,然后开始执行计算机使用相关操作。

python
import base64
import json
import os
import re
import subprocess
import sys
import time

from google import genai

BASE_DIR = os.path.dirname(os.path.abspath(__file__))


def setup_android_env():
    paths_to_check = [
        os.environ.get("ANDROID_HOME"),
        "/opt/homebrew/share/android-commandlinetools",
        "/usr/local/share/android-commandlinetools",
    ]

    android_home = None
    for p in paths_to_check:
        if p and os.path.exists(p):
            android_home = p
            break

    if not android_home:
        print("Error: ANDROID_HOME not found. Run setup_emulator.sh first.", file=sys.stderr)
        sys.exit(1)

    os.environ["ANDROID_HOME"] = android_home
    sdk_paths = [
        os.path.join(android_home, "cmdline-tools", "latest", "bin"),
        os.path.join(android_home, "emulator"),
        os.path.join(android_home, "platform-tools"),
    ]

    current_path = os.environ.get("PATH", "")
    for p in sdk_paths:
        if p not in current_path:
            current_path = p + os.pathsep + current_path

    os.environ["PATH"] = current_path
    return android_home


def start_emulator(avd_name="AI_Agent_Phone"):
    setup_android_env()

    try:
        res = subprocess.run(["adb", "devices"], capture_output=True, text=True)
        if "emulator" in res.stdout:
            return
    except FileNotFoundError:
        pass

    print(f"Starting emulator '{avd_name}'...")
    log_file = open(os.path.join(BASE_DIR, "emulator.log"), "w")

    subprocess.Popen(
        ["emulator", "-avd", avd_name, "-delay-adb"],
        stdout=log_file,
        stderr=log_file,
        start_new_session=True,
    )

    print("Waiting for emulator to boot...")
    for _ in range(60):
        try:
            res = subprocess.run(["adb", "devices"], capture_output=True, text=True)
            if "emulator" in res.stdout:
                boot_res = subprocess.run(
                    ["adb", "shell", "getprop", "sys.boot_completed"],
                    capture_output=True,
                    text=True,
                )
                if boot_res.stdout.strip() == "1":
                    print("Emulator ready.")
                    return
        except Exception:
            pass
        time.sleep(2)

    print("Error: Emulator failed to boot.", file=sys.stderr)
    sys.exit(1)


class ADBBridge:
    def __init__(self, device_id=None):
        self.prefix = ["adb"] + (["-s", device_id] if device_id else [])
        self.width, self.height = self._screen_size()

    def _run(self, args, check=True):
        result = subprocess.run(self.prefix + args, capture_output=True, text=True)
        if check and result.returncode != 0:
            raise RuntimeError(f"ADB error: {result.stderr.strip()}")
        return result.stdout

    def _screen_size(self):
        output = self._run(["shell", "wm", "size"])
        match = re.search(r"Physical size: (\d+)x(\d+)", output)
        return (int(match.group(1)), int(match.group(2))) if match else (1080, 1920)

    def _px(self, x, y):
        return int(x / 1000 * self.width), int(y / 1000 * self.height)

    def click(self, y, x, **_):
        px, py = self._px(x, y)
        self._run(["shell", "input", "tap", str(px), str(py)])

    def type(self, text, press_enter=False, **_):
        self._run(["shell", "input", "text", text.replace(" ", "%s")])
        if press_enter:
            self._run(["shell", "input", "keyevent", "66"])

    def open_app(self, app_name=None, package_name=None, **_):
        pkg = app_name or package_name
        if not pkg:
            raise ValueError("open_app requires app_name or package_name")

        stdout = self._run(
            [
                "shell",
                "monkey",
                "--pct-syskeys",
                "0",
                "-p",
                pkg,
                "-c",
                "android.intent.category.LAUNCHER",
                "1",
            ],
            check=False,
        )
        if "No activities found" in stdout or "monkey aborted" in stdout:
            raise RuntimeError(f"App {pkg} is not installed or has no launcher activity.")

    def scroll(self, y, x, direction, magnitude=800, **_):
        px, py = self._px(x, y)
        dist = int(magnitude / 1000 * self.height)
        dx, dy = {
            "up": (0, -dist),
            "down": (0, dist),
            "left": (-dist, 0),
            "right": (dist, 0),
        }.get(direction, (0, 0))
        self._run(
            [
                "shell",
                "input",
                "swipe",
                str(px),
                str(py),
                str(px + dx),
                str(py + dy),
                "300",
            ]
        )

    def long_press(self, y, x, seconds=2, **_):
        px, py = self._px(x, y)
        self._run(
            [
                "shell",
                "input",
                "swipe",
                str(px),
                str(py),
                str(px),
                str(py),
                str(seconds * 1000),
            ]
        )

    def drag_and_drop(self, start_y, start_x, end_y, end_x, **_):
        sx, sy = self._px(start_x, start_y)
        ex, ey = self._px(end_x, end_y)
        self._run(
            [
                "shell",
                "input",
                "swipe",
                str(sx),
                str(sy),
                str(ex),
                str(ey),
                "300",
            ]
        )

    def press_key(self, key, **_):
        keymap = {
            "home": "3",
            "back": "4",
            "enter": "66",
            "app_switch": "187",
            "menu": "82",
        }
        self._run(["shell", "input", "keyevent", keymap.get(key.lower(), key)])

    def go_back(self, **_):
        self._run(["shell", "input", "keyevent", "4"])

    def wait(self, seconds=1, **_):
        time.sleep(seconds)

    def list_apps(self, **_):
        output = self._run(["shell", "pm", "list", "packages", "-3"])
        apps = [line.split(":")[1] for line in output.splitlines() if line.startswith("package:")]
        if not apps:
            return {"apps": "No third-party apps installed on this device."}
        return {"apps": apps}

    def take_screenshot(self, **_):
        return None

    def screenshot(self) -> bytes:
        result = subprocess.run(
            self.prefix + ["exec-out", "screencap", "-p"],
            capture_output=True,
        )
        return result.stdout


SYSTEM_PROMPT = """You are operating an Android phone.
* Use the provided tools to complete the task.
* Scroll down to inspect the full screen before assuming an element is missing.
* You can open apps by package name from anywhere.
* Type text only using the `type` tool. Do not use the virtual keyboard.
* If the task is already complete, state that directly.
"""



def run_agent(task: str, device_id: str = None, max_turns: int = 100):
    start_emulator()

    client = genai.Client()
    bridge = ADBBridge(device_id)

    print(f"\nTask: {task}")
    print("-" * 40)

    screenshot_bytes = bridge.screenshot()
    user_input = [
        {"type": "text", "text": task},
        {
            "type": "image",
            "data": base64.b64encode(screenshot_bytes).decode(),
            "mime_type": "image/png",
        },
    ]

    previous_interaction_id = None
    turn = 0

    while turn < max_turns:
        turn += 1

        interaction = client.interactions.create(
            model="gemini-3.5-flash",
            system_instruction=SYSTEM_PROMPT,
            input=user_input,
            tools=[{"type": "computer_use", "environment": "mobile"}],
            previous_interaction_id=previous_interaction_id,
        )

        function_responses = []
        for step in interaction.steps:
            if step.type == "function_call":
                print(f"[function_call] {step.name}({step.arguments})")
                handler = getattr(bridge, step.name, None)
                result_text = {"status": "ok"}

                if handler:
                    try:
                        res = handler(**step.arguments)
                        if isinstance(res, dict):
                            result_text.update(res)
                    except Exception as e:
                        result_text = {"status": "error", "error": str(e)}
                else:
                    result_text = {"status": "error", "error": f"Unknown action: {step.name}"}

                print(f"[function_result] {result_text}")

                if "safety_decision" in step.arguments:
                    # Auto approve safety decisions for demo.
                    result_text["safety_acknowledgement"] = True

                screenshot_bytes = bridge.screenshot()

                fr = {
                    "type": "function_result",
                    "name": step.name,
                    "call_id": step.id,
                    "result": [
                        {"type": "text", "text": json.dumps(result_text)},
                        {
                            "type": "image",
                            "data": base64.b64encode(screenshot_bytes).decode(),
                            "mime_type": "image/png",
                        },
                    ],
                }
                function_responses.append(fr)
            else:
                print(f"\nResult: {interaction.output_text}")
                break

        user_input = function_responses
        previous_interaction_id = interaction.id

        if not function_responses:
            break

    return interaction


if __name__ == "__main__":
    task_desc = "Find the latest blog post from philipp schmid and summarize it."
    if len(sys.argv) > 1:
        task_desc = " ".join(sys.argv[1:])
    run_agent(task_desc)

五、如何运行

先设置 API Key:

bash
export GEMINI_API_KEY="your-key"

然后运行代理脚本:

bash
python agent.py "Open Settings and enable dark mode"

如果模拟器尚未运行,脚本会自动启动模拟器。

六、连接远程设备

除了本地 Android 模拟器,也可以操作真实 Android 设备或远程云模拟器。

1. 启用调试功能

在目标设备上打开「开发者选项」,然后启用:

  • USB 调试
  • 或无线调试

关于无线调试的配置,可参考 Android 官方开发者文档中「通过 Wi-Fi 使用 ADB」的说明。

2. 连接远程设备

bash
adb connect <device-ip-address>:5555

3. 将设备 ID 传入代理程序

把远程设备的连接字符串作为 device_id 参数传入:

python
# Target a remote or cloud-hosted emulator
run_agent("Check the weather", device_id="35.200.100.10:5555")

这样代理循环就能识别并控制指定设备。

七、后续开发建议

1. 支持 iOS / iPhone

Gemini API 的移动端功能不受平台限制。

无论设备是 Android 还是 iOS,模型都会在相同的 0-999 数值范围内输出操作指令,例如点击、滑动、输入等。

如果想针对 iPhone 或 iOS 模拟器进行测试,只需要把 ADBBridge 替换为 iOS 兼容的工具即可,例如:

  • Apple 的 simctl CLI
  • Appium
  • go-ios

2. 提升生产环境稳定性

示例中的 Python 代码是同步模式,主要为了便于演示。

在实际生产环境中,应补充:

  • 网络中断重试机制
  • ADB 连接断开处理
  • 异步任务执行
  • 更完善的错误恢复逻辑

3. 处理安全相关决策

在真实任务中,尤其是会改变系统状态或涉及付款的操作,模型可能会把某些步骤标记为需要确认的安全决策。

执行任何操作之前,系统都应该检查参数中是否包含安全相关标记,并向用户请求确认。

可进一步参考 Gemini API 的计算机使用安全指南。


结语

Gemini 3.5 Flash 的计算机使用能力,为移动端自动化提供了一种新的实现方式:模型负责观察屏幕并规划动作,开发者负责执行动作并反馈结果。

通过 Android 模拟器、ADB 和 Python SDK,可以快速搭建一个基础的移动端代理循环。后续只需要替换底层设备控制桥接层,就可以扩展到真实 Android 设备、云模拟器,甚至 iOS 设备。


最新文章

随机文章