用 Gemini 3.5 Flash 电脑操控 Android 手机
Control an Android Phone with Gemini 3.5 Flash Computer Use
Google 发布 Gemini 3.5 Flash 的 Computer Use 功能,模型通过截图和 function calling 控制 Android 模拟器,支持 click、type、open_app 等操作。指南提供 Python SDK 和 ADBBridge 实现 agent 循环,坐标归一化至 0-999 网格,支持本地模拟器或远程设备。GitHub 仓库包含设置脚本和完整 agent.py 代码。
2026年6月25日 · 9分钟阅读查看代码
Gemini 3.5 Flash 内置了计算机使用功能。该模型查看截图,决定下一步操作,并返回类似 click(y=300, x=500) 的函数调用。你通过 ADB 在设备上执行该操作,截取新截图,然后将其发回。重复此过程,直到任务完成。
本指南将引导你使用 mobile 环境和 Python SDK 控制 Android 模拟器。
GitHub 仓库:https://github.com/google-gemini/gemini-android-computer-use-quickstart
什么是 Computer Use?
Computer Use 是 Gemini 3.5 Flash 中的一个原生工具。你向模型提供一张截图和一个目标(例如“打开设置并开启深色模式”)。模型会返回结构化的操作:点击、文本输入、滑动、启动应用。你的代码在目标设备上执行这些操作。
它的工作方式类似于 function calling。模型提出操作,你执行它们,然后发回结果。模型通过截图保持在循环中。
Gemini 支持三种 Computer Use 环境:browser 用于桌面 Web 自动化,mobile 用于移动设备/模拟器,以及 desktop 用于操作系统级别的控制。本指南使用 mobile。
截图 ──> Gemini 3.5 Flash ──> function_call
▲ │
│ ▼
└──────── 通过 ADB 执行 ◄──────────┘
伪代码
Python
from google import genai
client = genai.Client()
bridge = ADBBridge()
# 截取初始截图并发送第一个请求
screenshot = bridge.screenshot()
interaction = client.interactions.create(
model="gemini-3.5-flash",
input=[
{"type": "text", "text": "打开设置并启用深色模式"},
{"type": "image", "data": b64(screenshot), "mime_type": "image/png"},
],
tools=[{"type": "computer_use", "environment": "mobile"}],
)
# Agent 循环:执行操作,发回结果
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)
支持的操作(mobile 环境)
以下是模型在使用 environment: "mobile" 时可以请求的操作。你的代码必须处理每一个。
| 操作 | 描述 | 参数 |
|---|---|---|
open_app |
按名称打开应用 | app_name: str, intent: str (可选) |
click |
点击屏幕坐标 | y: int (0-999), x: int (0-999) |
type |
输入文本 | text: str, press_enter: bool (默认为 false) |
long_press |
长按坐标 | y: int (0-999), x: int (0-999), seconds: int (默认为 2) |
drag_and_drop |
在两点之间拖动 | start_y, start_x, end_y, end_x |
press_key |
按下按键(home、back、enter...) | key: str |
go_back |
返回 | (无) |
wait |
暂停执行 | seconds: int (默认为 1) |
list_apps |
列出已安装的应用 | (无) |
take_screenshot |
截取屏幕 | (无) |
坐标被归一化到 0-999 的网格。(0, 0) 是左上角,(999, 999) 是右下角。你的 bridge 会根据设备分辨率将这些坐标转换为实际的像素坐标。
设置
无需 Android Studio GUI。在你的 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 agent 脚本会自动处理定位 SDK 路径和在后台启动模拟器,因此无需额外手动导出环境变量或进行终端设置。
Agent 循环
完整的工作脚本 agent.py。它会自动设置环境变量,在后台启动模拟器(如果尚未运行),等待其启动完成,然后开始 Computer Use 循环。
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("错误:未找到 ANDROID_HOME。请先运行 setup_emulator.sh。", 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"正在启动模拟器 '{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("等待模拟器启动...")
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("模拟器已就绪。")
return
except Exception:
pass
time.sleep(2)
print("错误:模拟器启动失败。", 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 错误:{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 需要 app_name 或 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"应用 {pkg} 未安装或没有启动器 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 = [l.split(":")[1] for l in output.splitlines() if l.startswith("package:")]
if not apps:
return {"apps": "此设备上未安装第三方应用。"}
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 = """你正在操作一部 Android 手机。
* 使用提供的工具完成任务。
* 在假设某个元素缺失之前,请向下滚动检查整个屏幕。
* 你可以从任何地方通过包名打开应用。
* 仅使用 `type` 工具输入文本。不要使用虚拟键盘。
* 如果任务已经完成,请直接说明。
"""
def run_agent(task: str, device_id: str = None, max_turns: int = 100):
start_emulator()
client = genai.Client()
bridge = ADBBridge(device_id)
print(f"\n任务:{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"未知操作:{step.name}"}
print(f"[function_result] {result_text}")
if "safety_decision" in step.arguments:
# 演示时自动批准安全决策
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"\n结果:{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 = "查找 Philipp Schmid 的最新博客文章并总结它。"
if len(sys.argv) > 1:
task_desc = " ".join(sys.argv[1:])
run_agent(task_desc)
如何运行
Bash
# 设置你的 API 密钥
export GEMINI_API_KEY="your-key"
# 运行 agent(如果需要,会自动启动模拟器)
python agent.py "打开设置并启用深色模式"
连接到远程设备
你也可以将物理 Android 设备或远程云模拟器作为目标,而不是本地虚拟设备。
- 启用 USB/无线调试:在目标设备上,启用开发者选项并打开 USB 调试或无线调试。
- 通过 ADB 连接:使用
adb connect命令通过 TCP/IP 连接到远程设备:Bash
adb connect <device-ip-address>:5555 有关设置无线调试的详细信息,请参阅官方 Android 开发者文档中关于通过 Wi-Fi 使用 ADB 的部分。
3. 将设备 ID 传递给 Agent:将远程设备的连接字符串作为 device_id 参数传递给 agent 循环,以将其设为目标:Python
# 将远程或云托管的模拟器设为目标
run_agent("查看天气", device_id="35.200.100.10:5555")
后续步骤与开发者提示
希望这能帮助你开始在移动设备上使用 Gemini 3.5 Flash Computer Use。后续步骤可以包括:
- 支持 iOS / iPhone:Gemini API 的
mobile环境是平台无关的。无论设备是 Android 还是 iOS,模型都会在相同的归一化0-999网格上输出操作(点击、滑动、输入)。要将 iPhone 或 iOS 模拟器设为目标,你只需将ADBBridge替换为与 iOS 兼容的工具——例如用于模拟器控制的 ApplesimctlCLI、Appium,或用于物理设备的 go-ios。 - 生产环境鲁棒性:此处提供的 Python bridge 代码是同步的,并针对演示进行了优化。对于生产环境,你应该实现健壮的重试逻辑以应对网络断开,优雅地处理 ADB 断开连接,并异步执行操作。
- 处理安全决策:在现实世界的任务中(尤其是那些修改状态或进行支付的任务),模型可能会用
safety_decision标记操作步骤,请求确认。确保你的生产循环检查step.arguments中的安全标志,并在执行操作前提示用户。详情请参阅 Gemini API Computer Use 安全指南。