智谱AI-OpenAutoGLM-开源的手机智能体
针对智谱AI-AutoGLM-开源的手机智能体,整理代码拆解步骤,【手机手势】+【手机截屏】

1-思路整理
- 1)先把手机和电脑的连接软件安装上【ADB(电脑安装)】+【ADBKeyboard(手机安装)】
- 2)然后手机打开调试模式->这个电脑的应用就可以直接操作手机
- 3)配置智谱AI-AutoGLM-开源的手机智能体的模型(模型地址/模型)
- 4)运行智谱AI-AutoGLM-开源的手机智能体代码->直接操作手机
2-参考网址
- 智谱AI-AutoGLM-开源的手机智能体代码:https://gitee.com/enzoism/Open-AutoGLM
- 智谱AI-AutoGLM-开源的手机智能体模型-Phone-9B:https://ai.gitcode.com/zai-org/AutoGLM-Phone-9B
- AutoGLM: Autonomous Foundation Agents for GUIs论文:https://arxiv.org/pdf/2411.00820
- Android手机桥接软件ADB(电脑安装-已验证):https://dl.google.com/android/repository/platform-tools-latest-darwin.zip
- Android手机桥接软件ADBKeyboard(手机安装-已验证):https://gitee.com/enzoism/ADBKeyBoard
- Google 提供官方的adb/fastboot独立工具包[Win/macOS/Linux]:https://blog.csdn.net/OpenStack_/article/details/87368289
- adb下载安装及使用教程:https://blog.csdn.net/mowang_hongci/article/details/136358631
3-动手实操
1-ADB操作-手机手势
ADB进行手机的手势操作->这一块的代码很独立,可以理解为独立的工具类

1-源码展示
python
"""Android自动化设备控制工具。"""
import subprocess
import time
from phone_agent.config.apps import APP_PACKAGES
from phone_agent.config.timing import TIMING_CONFIG
def get_current_app(device_id: str | None = None) -> str:
"""
获取当前聚焦的应用名称。
Args:
device_id: 可选的ADB设备ID,用于多设备环境。
Returns:
如果识别到应用则返回应用名称,否则返回"System Home"。
"""
adb_prefix = _get_adb_prefix(device_id)
result = subprocess.run(
adb_prefix + ["shell", "dumpsys", "window"], capture_output=True, text=True
)
output = result.stdout
# 解析窗口焦点信息
for line in output.split("\n"):
if "mCurrentFocus" in line or "mFocusedApp" in line:
for app_name, package in APP_PACKAGES.items():
if package in line:
return app_name
return "System Home"
def tap(
x: int, y: int, device_id: str | None = None, delay: float | None = None
) -> None:
"""
在指定坐标位置点击。
Args:
x: X坐标。
y: Y坐标。
device_id: 可选的ADB设备ID。
delay: 点击后延迟时间(秒)。如果为None,则使用配置的默认值。
"""
if delay is None:
delay = TIMING_CONFIG.device.default_tap_delay
adb_prefix = _get_adb_prefix(device_id)
subprocess.run(
adb_prefix + ["shell", "input", "tap", str(x), str(y)], capture_output=True
)
time.sleep(delay)
def double_tap(
x: int, y: int, device_id: str | None = None, delay: float | None = None
) -> None:
"""
在指定坐标位置双击。
Args:
x: X坐标。
y: Y坐标。
device_id: 可选的ADB设备ID。
delay: 双击后延迟时间(秒)。如果为None,则使用配置的默认值。
"""
if delay is None:
delay = TIMING_CONFIG.device.default_double_tap_delay
adb_prefix = _get_adb_prefix(device_id)
subprocess.run(
adb_prefix + ["shell", "input", "tap", str(x), str(y)], capture_output=True
)
time.sleep(TIMING_CONFIG.device.double_tap_interval)
subprocess.run(
adb_prefix + ["shell", "input", "tap", str(x), str(y)], capture_output=True
)
time.sleep(delay)
def long_press(
x: int,
y: int,
duration_ms: int = 3000,
device_id: str | None = None,
delay: float | None = None,
) -> None:
"""
长按指定坐标位置。
Args:
x: X坐标。
y: Y坐标。
duration_ms: 按压持续时间(毫秒)。
device_id: 可选的ADB设备ID。
delay: 长按后延迟时间(秒)。如果为None,则使用配置的默认值。
"""
if delay is None:
delay = TIMING_CONFIG.device.default_long_press_delay
adb_prefix = _get_adb_prefix(device_id)
subprocess.run(
adb_prefix
+ ["shell", "input", "swipe", str(x), str(y), str(x), str(y), str(duration_ms)],
capture_output=True,
)
time.sleep(delay)
def swipe(
start_x: int,
start_y: int,
end_x: int,
end_y: int,
duration_ms: int | None = None,
device_id: str | None = None,
delay: float | None = None,
) -> None:
"""
从起始坐标滑动到结束坐标。
Args:
start_x: 起始X坐标。
start_y: 起始Y坐标。
end_x: 结束X坐标。
end_y: 结束Y坐标。
duration_ms: 滑动持续时间(毫秒),如果为None则自动计算。
device_id: 可选的ADB设备ID。
delay: 滑动后延迟时间(秒)。如果为None,则使用配置的默认值。
"""
if delay is None:
delay = TIMING_CONFIG.device.default_swipe_delay
adb_prefix = _get_adb_prefix(device_id)
if duration_ms is None:
# 根据距离计算持续时间
dist_sq = (start_x - end_x) ** 2 + (start_y - end_y) ** 2
duration_ms = int(dist_sq / 1000)
duration_ms = max(1000, min(duration_ms, 2000)) # 限制在1000-2000毫秒之间
subprocess.run(
adb_prefix
+ [
"shell",
"input",
"swipe",
str(start_x),
str(start_y),
str(end_x),
str(end_y),
str(duration_ms),
],
capture_output=True,
)
time.sleep(delay)
def back(device_id: str | None = None, delay: float | None = None) -> None:
"""
按下返回键。
Args:
device_id: 可选的ADB设备ID。
delay: 按下返回键后的延迟时间(秒)。如果为None,则使用配置的默认值。
"""
if delay is None:
delay = TIMING_CONFIG.device.default_back_delay
adb_prefix = _get_adb_prefix(device_id)
subprocess.run(
adb_prefix + ["shell", "input", "keyevent", "4"], capture_output=True
)
time.sleep(delay)
def home(device_id: str | None = None, delay: float | None = None) -> None:
"""
按下主页键。
Args:
device_id: 可选的ADB设备ID。
delay: 按下主页键后的延迟时间(秒)。如果为None,则使用配置的默认值。
"""
if delay is None:
delay = TIMING_CONFIG.device.default_home_delay
adb_prefix = _get_adb_prefix(device_id)
subprocess.run(
adb_prefix + ["shell", "input", "keyevent", "KEYCODE_HOME"], capture_output=True
)
time.sleep(delay)
def launch_app(
app_name: str, device_id: str | None = None, delay: float | None = None
) -> bool:
"""
通过应用名称启动应用。
Args:
app_name: 应用名称(必须存在于APP_PACKAGES中)。
device_id: 可选的ADB设备ID。
delay: 启动后的延迟时间(秒)。如果为None,则使用配置的默认值。
Returns:
如果成功启动应用返回True,否则返回False(应用未找到)。
"""
if delay is None:
delay = TIMING_CONFIG.device.default_launch_delay
if app_name not in APP_PACKAGES:
return False
adb_prefix = _get_adb_prefix(device_id)
package = APP_PACKAGES[app_name]
subprocess.run(
adb_prefix
+ [
"shell",
"monkey",
"-p",
package,
"-c",
"android.intent.category.LAUNCHER",
"1",
],
capture_output=True,
)
time.sleep(delay)
return True
def _get_adb_prefix(device_id: str | None) -> list:
"""获取带有可选设备标识符的ADB命令前缀。"""
if device_id:
return ["adb", "-s", device_id]
return ["adb"]
2-测试代码
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
device_operation模块的真实使用案例测试
测试所有设备操作手势,包括启动QQ音乐应用
"""
import os
import sys
import time
from phone_agent.adb.device_operation_util.device_operation_util import launch_app, get_current_app, tap, double_tap, long_press, swipe, back, home
# 添加当前目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 定义APP_PACKAGES字典的一部分,仅包含需要的部分
APP_PACKAGES = {
"QQ音乐": "com.tencent.qqmusic",
}
def test_device_operations():
"""测试所有设备操作手势"""
print("开始测试设备操作...")
try:
# 1. 测试启动QQ音乐
print("\n1. 测试启动QQ音乐...")
success = launch_app("QQ音乐")
if success:
print("成功启动QQ音乐")
else:
print("启动QQ音乐失败")
# 等待应用启动
time.sleep(3)
# 2. 测试获取当前应用
print("\n2. 测试获取当前应用...")
current_app = get_current_app()
print(f"当前应用: {current_app}")
# 3. 测试点击操作 (假设在QQ音乐主界面某个位置点击)
print("\n3. 测试点击操作...")
# 在屏幕中心附近点击
tap(500, 500)
print("执行点击操作 (500, 500)")
# 等待操作完成
time.sleep(2)
# 4. 测试双击操作
print("\n4. 测试双击操作...")
# 在屏幕中心附近双击
double_tap(500, 500)
print("执行双击操作 (500, 500)")
# 等待操作完成
time.sleep(2)
# 5. 测试长按操作
print("\n5. 测试长按操作...")
# 在屏幕中心附近长按
long_press(500, 500, duration_ms=2000)
print("执行长按操作 (500, 500),持续2秒")
# 等待操作完成
time.sleep(2)
# 6. 测试滑动操作
print("\n6. 测试滑动操作...")
# 从屏幕上方中间向下滑动
swipe(500, 300, 500, 800, duration_ms=1000)
print("执行滑动操作: 从(500, 300)到(500, 800),持续1秒")
# 等待操作完成
time.sleep(2)
# 7. 测试返回键
print("\n7. 测试返回键...")
back()
print("执行返回键操作")
# 等待操作完成
time.sleep(2)
# 8. 测试主页键
print("\n8. 测试主页键...")
home()
print("执行主页键操作")
# 等待操作完成
time.sleep(2)
# 9. 再次测试启动QQ音乐
print("\n9. 再次测试启动QQ音乐...")
success = launch_app("QQ音乐")
if success:
print("成功启动QQ音乐")
else:
print("启动QQ音乐失败")
# 等待应用启动
time.sleep(3)
print("\n所有设备操作测试完成!")
except Exception as e:
print(f"测试过程中出现错误: {e}")
raise
if __name__ == "__main__":
test_device_operations()
- 打印结果
shell
开始测试设备操作...
1. 测试启动QQ音乐...
成功启动QQ音乐
2. 测试获取当前应用...
当前应用: QQ音乐
3. 测试点击操作...
执行点击操作 (500, 500)
4. 测试双击操作...
执行双击操作 (500, 500)
5. 测试长按操作...
执行长按操作 (500, 500),持续2秒
6. 测试滑动操作...
执行滑动操作: 从(500, 300)到(500, 800),持续1秒
7. 测试返回键...
执行返回键操作
8. 测试主页键...
执行主页键操作
9. 再次测试启动QQ音乐...
成功启动QQ音乐
所有设备操作测试完成!
2-ADB操作-手机截屏
1-源码展示
一个独立的工具类方法,直接可以进行屏幕截屏
python
"""Android设备屏幕截图工具。"""
import base64
import os
import subprocess
import tempfile
import uuid
from dataclasses import dataclass
from io import BytesIO
from PIL import Image
@dataclass
class Screenshot:
"""表示已捕获的截图。"""
base64_data: str
width: int
height: int
is_sensitive: bool = False
def get_screenshot(device_id: str | None = None, timeout: int = 10) -> Screenshot:
"""
从连接的Android设备捕获屏幕截图。
参数:
device_id: 可选的ADB设备ID,用于多设备环境。
timeout: 截图操作的超时时间(秒)。
返回:
包含base64数据和尺寸的Screenshot对象。
注意:
如果截图失败(例如在支付页面等敏感屏幕),将返回一张黑色占位图,并设置is_sensitive=True。
"""
temp_path = os.path.join(tempfile.gettempdir(), f"screenshot_{uuid.uuid4()}.png")
adb_prefix = _get_adb_prefix(device_id)
try:
# 执行截图命令
result = subprocess.run(
adb_prefix + ["shell", "screencap", "-p", "/sdcard/tmp.png"],
capture_output=True,
text=True,
timeout=timeout,
)
# 检查截图失败情况(如敏感屏幕)
output = result.stdout + result.stderr
if "Status: -1" in output or "Failed" in output:
return _create_fallback_screenshot(is_sensitive=True)
# 将截图拉取到本地临时路径
subprocess.run(
adb_prefix + ["pull", "/sdcard/tmp.png", temp_path],
capture_output=True,
text=True,
timeout=5,
)
if not os.path.exists(temp_path):
return _create_fallback_screenshot(is_sensitive=False)
# 读取并编码图像
img = Image.open(temp_path)
width, height = img.size
buffered = BytesIO()
img.save(buffered, format="PNG")
base64_data = base64.b64encode(buffered.getvalue()).decode("utf-8")
# 清理临时文件
os.remove(temp_path)
return Screenshot(
base64_data=base64_data, width=width, height=height, is_sensitive=False
)
except Exception as e:
print(f"截图错误: {e}")
return _create_fallback_screenshot(is_sensitive=False)
def _get_adb_prefix(device_id: str | None) -> list:
"""获取带有可选设备标识符的ADB命令前缀。"""
if device_id:
return ["adb", "-s", device_id]
return ["adb"]
def _create_fallback_screenshot(is_sensitive: bool) -> Screenshot:
"""当截图失败时创建一张黑色占位图。"""
default_width, default_height = 1080, 2400
black_img = Image.new("RGB", (default_width, default_height), color="black")
buffered = BytesIO()
black_img.save(buffered, format="PNG")
base64_data = base64.b64encode(buffered.getvalue()).decode("utf-8")
return Screenshot(
base64_data=base64_data,
width=default_width,
height=default_height,
is_sensitive=is_sensitive,
)
2-测试代码
python
from phone_agent.adb.screenshot_util.screenshot_util import get_screenshot
if __name__ == '__main__':
screenshot = get_screenshot()
print(f"截图宽度: {screenshot.width}")
print(f"截图高度: {screenshot.height}")
print(f"截图是否敏感: {screenshot.is_sensitive}")
- 日志打印
shell
截图宽度: 1176
截图高度: 2400
截图是否敏感: False