智谱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-妇女『命令行』之友
拆解AutoGLM的时候发现这个东西,以前我都很好奇的,没想到这么朴素!
1-代码编写
python
import argparse
def list_devices():
"""
列出当前通过 ADB 可见的所有设备及其状态。
仅打印设备序列号与状态,供用户快速确认连接是否正常。
"""
print("[list_devices] 参数:无")
print("help:列出可用设备并退出")
def check_environment():
"""
执行系统环境自检:
- 检查 ADB 可用性
- 检查 Python 依赖
- 检查模型连通性
- 检查屏幕分辨率
自检通过后退出。
"""
print("[check_environment] 参数:无")
print("help:检查系统要求并退出")
def run_task(task, device_id, max_steps, base_url, model, api_key):
"""
根据自然语言任务描述,驱动手机完成自动化操作。
参数:
task: 要执行的自然语言任务描述
device_id: 目标手机设备序列号
max_steps: 单轮任务最大步数上限
base_url: 大模型 API 基础地址
model: 指定使用的模型名称
api_key: 访问 API 所需的认证密钥
"""
print("[run_task] 参数:")
print(f" task = {task!r}")
print(f" device_id = {device_id!r}")
print(f" max_steps = {max_steps}")
print(f" base_url = {base_url!r}")
print(f" model = {model!r}")
print(f" api_key = {'*' * 10 if api_key else None}")
print("help:根据任务描述自动执行手机操作")
def main():
"""
主入口点。
解析命令行参数,并分发到对应的具体实现函数。
"""
parser = argparse.ArgumentParser(
description="Phone Agent Mobile - AI-powered phone automation"
)
parser.add_argument("task", nargs="?", help="要执行的任务描述")
parser.add_argument("--device-id", help="要使用的ADB设备ID")
parser.add_argument("--max-steps", type=int, default=100, help="每个任务的最大步数")
parser.add_argument("--base-url", help="模型API基础地址")
parser.add_argument("--model", help="模型名称")
parser.add_argument("--api-key", help="模型认证的API密钥")
parser.add_argument("--list-devices", action="store_true", help="列出可用设备并退出")
parser.add_argument("--check", action="store_true", help="检查系统要求并退出")
args = parser.parse_args()
# 功能分发
if args.list_devices:
list_devices()
return
if args.check:
check_environment()
return
# 运行任务
run_task(
task=args.task,
device_id=args.device_id,
max_steps=args.max_steps,
base_url=args.base_url,
model=args.model,
api_key=args.api_key,
)
if __name__ == "__main__":
main()
2-使用演示
shell
# 1-验证help命令
(Open-AutoGLM) (base) MacBook-Pro:Open-AutoGLM rong$ python a02_test_parse.py --help
\usage: a02_test_parse.py [-h] [--device-id DEVICE_ID] [--max-steps MAX_STEPS] [--base-url BASE_URL] [--model MODEL] [--api-key API_KEY]
[--list-devices] [--check]
[task]
Phone Agent Mobile - AI-powered phone automation
positional arguments:
task 要执行的任务描述
options:
-h, --help show this help message and exit
--device-id DEVICE_ID
要使用的ADB设备ID
--max-steps MAX_STEPS
每个任务的最大步数
--base-url BASE_URL 模型API基础地址
--model MODEL 模型名称
--api-key API_KEY 模型认证的API密钥
--list-devices 列出可用设备并退出
--check 检查系统要求并退出
# 2-验证--list-devices-补全示例1
(Open-AutoGLM) (base) MacBook-Pro:Open-AutoGLM rong$ python a02_test_parse.py --list-device
[list_devices] 参数:无
help:列出可用设备并退出
# 2-验证--list-devices-补全示例2
(Open-AutoGLM) (base) MacBook-Pro:Open-AutoGLM rong$ python a02_test_parse.py --list
[list_devices] 参数:无
help:列出可用设备并退出
# 3-验证特殊参数
(Open-AutoGLM) (base) MacBook-Pro:Open-AutoGLM rong$ python a02_test_parse.py "测试我的人物是什么?" --model deepseek_v3
[run_task] 参数:
task = '测试我的人物是什么?'
device_id = None
max_steps = 100
base_url = None
model = 'deepseek_v3'
api_key = None
help:根据任务描述自动执行手机操作
3-为什么-前缀匹配
用户输入【python a02_test_parse.py --list-device】和【python a02_test_parse.py --list】都可以命中--list-devices,这个背后是怎么做到的?
argparse 的 "前缀匹配(prefix matching)" 机制在起作用。
只要用户给出的缩写能唯一地对应到某个已定义参数,argparse 就自动把它当成该参数处理;如果同时有多个候选就会报错。
- 定义阶段
python
parser.add_argument("--list-devices", action="store_true", ...)
- 用户输入
bash
python a02_test_parse.py --list-device # 少了最后的 "s"
python a02_test_parse.py --list # 更短的缩写
-
argparse 解析
- 先扫描所有长选项名:
--list-devices - 用用户给出的字符串
--list-device(或--list)去做前缀匹配 :--list-device是--list-devices的唯一前缀 → 合法--list也是--list-devices的唯一前缀 → 合法
- 如果再加入一个
--list-something,再敲--list就会因歧义而报错。
- 先扫描所有长选项名:
-
关闭方法(如想禁用)
python
parser = argparse.ArgumentParser(allow_abbrev=False)
因此,你看到的"两条命令都能命中"并不是代码里单独写了别名,而是 argparse 默认的"前缀匹配"自动帮你补全了。
4-为什么-下划线转化
list_devices和参数描述中的--list-devices又是怎么进行匹配的?
--list-devices 只是一个命令行标志 ,它真正在代码层面的名字是 dest="list_devices"(argparse 自动把长选项的 -- 去掉、- 变 _)。
流程一句话:
"命令行里写了 --list-devices → 解析后变成 args.list_devices == True → 代码里用 if args.list_devices: 就能检测到。"
下面把细节拆开给你看:
- 定义阶段
python
parser.add_argument(
"--list-devices", # 命令行上敲的名字
action="store_true", # 只要出现就把值设为 True
help="列出可用设备并退出"
)
# 没显式写 dest,argparse 默认 dest = "list_devices"
- 用户输入
bash
python a02_test_parse.py --list-devices
- 解析阶段
python
args = parser.parse_args()
# 内部等价于:
# args.list_devices = True
- 功能分发
python
if args.list_devices: # 现在值为 True
list_devices() # 于是进入这个分支
return
所以:
"命令行标志 --list-devices" 与 "代码里的变量 args.list_devices" 是通过 argparse 的 dest 规则 自动映射的;
list_devices() 函数只是被 if args.list_devices: 这个布尔开关调用,和参数名本身没有直接字符串耦合。
2-egg-info文件夹
egg-info 文件夹是 Python 包在"源码安装"阶段自动生成的元数据目录,作用相当于包的"身份证"。它由 setuptools 在运行
python setup.py egg_info
时创建,里面主要放两类东西:
-
项目级元数据
- PKG-INFO:名称、版本、作者、描述、依赖等,跟 PyPI 页面展示的信息同源。
- entry_points.txt(如果有):控制台脚本、插件入口点的注册表。
- top_level.txt:列出该包在 import 时的顶层命名空间。
-
安装过程辅助文件
- dependency_links.txt、requires.txt:依赖列表,供 pip/setuptools 解析。
- SOURCES.txt:本次打包包含的所有文件清单,后续做 sdist 或 uninstall 时用来对照。
一句话:它不是"可执行代码",而是告诉工具"我是谁、我依赖谁、我提供了哪些入口"。
对开发者来说,可以:
- 直接删掉,重新
setup.py egg_info或pip install -e .就会再生; - 不提交到 Git,在
.gitignore里加*.egg-info/即可; - 如果手动改了依赖,记得重新生成,否则安装时可能解析到旧信息。
3-ADB操作-ADBConnection
ADB连接手机->这一块的代码很独立,可以理解为独立的工具类

1-源码展示
python
"""ADB连接管理,支持本地和远程设备。"""
import subprocess
import time
from dataclasses import dataclass
from enum import Enum
class ConnectionType(Enum):
"""ADB连接类型。"""
USB = "usb"
WIFI = "wifi"
REMOTE = "remote"
@dataclass
class DeviceInfo:
"""已连接设备的信息。"""
device_id: str
status: str
connection_type: ConnectionType
model: str | None = None
android_version: str | None = None
class ADBConnection:
"""
管理与Android设备的ADB连接。
支持USB、WiFi以及远程TCP/IP连接。
示例:
>>> conn = ADBConnection()
>>> # 连接远程设备
>>> conn.connect("192.168.1.100:5555")
>>> # 列出所有设备
>>> devices = conn.list_devices()
>>> # 断开连接
>>> conn.disconnect("192.168.1.100:5555")
"""
def __init__(self, adb_path: str = "adb"):
"""
初始化ADB连接管理器。
Args:
adb_path: ADB可执行文件路径。
"""
self.adb_path = adb_path
def connect(self, address: str, timeout: int = 10) -> tuple[bool, str]:
"""
通过TCP/IP连接到远程设备。
Args:
address: 设备地址,格式为"主机:端口"(例如:"192.168.1.100:5555")。
timeout: 连接超时时间(秒)。
Returns:
(是否成功, 消息) 元组。
注意:
远程设备必须启用TCP/IP调试功能。
在设备上运行:adb tcpip 5555
"""
# 验证地址格式
if ":" not in address:
address = f"{address}:5555" # 默认ADB端口
try:
result = subprocess.run(
[self.adb_path, "connect", address],
capture_output=True,
text=True,
timeout=timeout,
)
output = result.stdout + result.stderr
if "connected" in output.lower():
return True, f"已连接到 {address}"
elif "already connected" in output.lower():
return True, f"已连接到 {address}"
else:
return False, output.strip()
except subprocess.TimeoutExpired:
return False, f"连接超时,超过 {timeout} 秒"
except Exception as e:
return False, f"连接错误: {e}"
def disconnect(self, address: str | None = None) -> tuple[bool, str]:
"""
断开与远程设备的连接。
Args:
address: 要断开的设备地址。若为None,则断开所有连接。
Returns:
(是否成功, 消息) 元组。
"""
try:
cmd = [self.adb_path, "disconnect"]
if address:
cmd.append(address)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
output = result.stdout + result.stderr
return True, output.strip() or "已断开连接"
except Exception as e:
return False, f"断开连接错误: {e}"
def list_devices(self) -> list[DeviceInfo]:
"""
列出所有已连接的设备。
Returns:
DeviceInfo对象列表。
"""
try:
result = subprocess.run(
[self.adb_path, "devices", "-l"],
capture_output=True,
text=True,
timeout=5,
)
devices = []
for line in result.stdout.strip().split("\n")[1:]: # 跳过标题行
if not line.strip():
continue
parts = line.split()
if len(parts) >= 2:
device_id = parts[0]
status = parts[1]
# 确定连接类型
if ":" in device_id:
conn_type = ConnectionType.REMOTE
elif "emulator" in device_id:
conn_type = ConnectionType.USB # 模拟器通过USB连接
else:
conn_type = ConnectionType.USB
# 解析额外信息
model = None
for part in parts[2:]:
if part.startswith("model:"):
model = part.split(":", 1)[1]
break
devices.append(
DeviceInfo(
device_id=device_id,
status=status,
connection_type=conn_type,
model=model,
)
)
return devices
except Exception as e:
print(f"列出设备时出错: {e}")
return []
def get_device_info(self, device_id: str | None = None) -> DeviceInfo | None:
"""
获取设备的详细信息。
Args:
device_id: 设备ID。若为None,使用第一个可用设备。
Returns:
DeviceInfo对象,未找到则返回None。
"""
devices = self.list_devices()
if not devices:
return None
if device_id is None:
return devices[0]
for device in devices:
if device.device_id == device_id:
return device
return None
def is_connected(self, device_id: str | None = None) -> bool:
"""
检查设备是否已连接。
Args:
device_id: 要检查的设备ID。若为None,检查是否有任何设备连接。
Returns:
已连接返回True,否则返回False。
"""
devices = self.list_devices()
if not devices:
return False
if device_id is None:
return any(d.status == "device" for d in devices)
return any(d.device_id == device_id and d.status == "device" for d in devices)
def enable_tcpip(
self, port: int = 5555, device_id: str | None = None
) -> tuple[bool, str]:
"""
在通过USB连接的设备上启用TCP/IP调试。
此操作允许后续进行无线连接。
Args:
port: ADB使用的TCP端口(默认:5555)。
device_id: 设备ID。若为None,使用第一个可用设备。
Returns:
(是否成功, 消息) 元组。
注意:
设备必须先通过USB连接。
完成此操作后,可断开USB线并改用WiFi连接。
"""
try:
cmd = [self.adb_path]
if device_id:
cmd.extend(["-s", device_id])
cmd.extend(["tcpip", str(port)])
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
output = result.stdout + result.stderr
if "restarting" in output.lower() or result.returncode == 0:
# ADB服务器和连接延迟(单位:秒)->TCP/IP模式后的等待时间
time.sleep(2)
return True, f"已在端口 {port} 启用TCP/IP模式"
else:
return False, output.strip()
except Exception as e:
return False, f"启用TCP/IP时出错: {e}"
def get_device_ip(self, device_id: str | None = None) -> str | None:
"""
获取已连接设备的IP地址。
Args:
device_id: 设备ID。若为None,使用第一个可用设备。
Returns:
IP地址字符串,未找到则返回None。
"""
try:
cmd = [self.adb_path]
if device_id:
cmd.extend(["-s", device_id])
cmd.extend(["shell", "ip", "route"])
result = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
# 从路由输出中解析IP
for line in result.stdout.split("\n"):
if "src" in line:
parts = line.split()
for i, part in enumerate(parts):
if part == "src" and i + 1 < len(parts):
return parts[i + 1]
# 备选方案:尝试wlan0接口
cmd[-1] = "ip addr show wlan0"
result = subprocess.run(
cmd[:-1] + ["shell", "ip", "addr", "show", "wlan0"],
capture_output=True,
text=True,
timeout=5,
)
for line in result.stdout.split("\n"):
if "inet " in line:
parts = line.strip().split()
if len(parts) >= 2:
return parts[1].split("/")[0]
return None
except Exception as e:
print(f"获取设备IP时出错: {e}")
return None
def restart_server(self) -> tuple[bool, str]:
"""
重启ADB服务器。
Returns:
(是否成功, 消息) 元组。
"""
try:
# 停止服务器
subprocess.run(
[self.adb_path, "kill-server"], capture_output=True, timeout=5
)
# 杀死并重新启动ADB服务器之间的等待时间(单位:秒)
time.sleep(1)
# 启动服务器
subprocess.run(
[self.adb_path, "start-server"], capture_output=True, timeout=5
)
return True, "ADB服务器已重启"
except Exception as e:
return False, f"重启服务器时出错: {e}"
def quick_connect(address: str) -> tuple[bool, str]:
"""
快速连接到远程设备的辅助函数。
Args:
address: 设备地址(如"192.168.1.100"或"192.168.1.100:5555")。
Returns:
(是否成功, 消息) 元组。
"""
conn = ADBConnection()
return conn.connect(address)
def list_devices() -> list[DeviceInfo]:
"""
快速列出已连接设备的辅助函数。
Returns:
DeviceInfo对象列表。
"""
conn = ADBConnection()
return conn.list_devices()
2-测试代码
python
from phone_agent.adb.connection_util import list_devices
if __name__ == '__main__':
print("------测试-------")
print(list_devices())
-
打印结果
已连接到 pydev 调试器(内部版本号 231.9225.15)------测试-------
[DeviceInfo(device_id='2KE5T19B23025905', status='device', connection_type=<ConnectionType.USB: 'usb'>, model='LIO_AN00', android_version=None)]
4-支持APP应用列表
1-源码展示
这个代码太简单了,列举了当前LLM支持的应用,根据【应用包名】找到【应用】
python
"""支持的应用程序名称与包名映射表。"""
APP_PACKAGES: dict[str, str] = {
# 社交与通讯
"微信": "com.tencent.mm",
"QQ": "com.tencent.mobileqq",
"微博": "com.sina.weibo",
# 电商
"淘宝": "com.taobao.taobao",
"京东": "com.jingdong.app.mall",
"拼多多": "com.xunmeng.pinduoduo",
"淘宝闪购": "com.taobao.taobao",
"京东秒送": "com.jingdong.app.mall",
# 生活与社交
"小红书": "com.xingin.xhs",
"豆瓣": "com.douban.frodo",
"知乎": "com.zhihu.android",
# 地图与导航
"高德地图": "com.autonavi.minimap",
"百度地图": "com.baidu.BaiduMap",
# 餐饮与服务
"美团": "com.sankuai.meituan",
"大众点评": "com.dianping.v1",
"饿了么": "me.ele",
"肯德基": "com.yek.android.kfc.activitys",
# 出行
"携程": "ctrip.android.view",
"铁路12306": "com.MobileTicket",
"12306": "com.MobileTicket",
"去哪儿": "com.Qunar",
"去哪儿旅行": "com.Qunar",
"滴滴出行": "com.sdu.didi.psnger",
# 视频与娱乐
"bilibili": "tv.danmaku.bili",
"抖音": "com.ss.android.ugc.aweme",
"快手": "com.smile.gifmaker",
"腾讯视频": "com.tencent.qqlive",
"爱奇艺": "com.qiyi.video",
"优酷视频": "com.youku.phone",
"芒果TV": "com.hunantv.imgo.activity",
"红果短剧": "com.phoenix.read",
# 音乐与音频
"网易云音乐": "com.netease.cloudmusic",
"QQ音乐": "com.tencent.qqmusic",
"汽水音乐": "com.luna.music",
"喜马拉雅": "com.ximalaya.ting.android",
# 阅读
"番茄小说": "com.dragon.read",
"番茄免费小说": "com.dragon.read",
"七猫免费小说": "com.kmxs.reader",
# 办公效率
"飞书": "com.ss.android.lark",
"QQ邮箱": "com.tencent.androidqqmail",
# AI与工具
"豆包": "com.larus.nova",
# 健康与健身
"keep": "com.gotokeep.keep",
"美柚": "com.lingan.seeyou",
# 新闻与资讯
"腾讯新闻": "com.tencent.news",
"今日头条": "com.ss.android.article.news",
# 房产
"贝壳找房": "com.lianjia.beike",
"安居客": "com.anjuke.android.app",
# 金融
"同花顺": "com.hexin.plat.android",
# 游戏
"星穹铁道": "com.miHoYo.hkrpg",
"崩坏:星穹铁道": "com.miHoYo.hkrpg",
"恋与深空": "com.papegames.lysk.cn",
"AndroidSystemSettings": "com.android.settings",
"Android System Settings": "com.android.settings",
"Android System Settings": "com.android.settings",
"Android-System-Settings": "com.android.settings",
"Settings": "com.android.settings",
"AudioRecorder": "com.android.soundrecorder",
"audiorecorder": "com.android.soundrecorder",
"Bluecoins": "com.rammigsoftware.bluecoins",
"bluecoins": "com.rammigsoftware.bluecoins",
"Broccoli": "com.flauschcode.broccoli",
"broccoli": "com.flauschcode.broccoli",
"Booking.com": "com.booking",
"Booking": "com.booking",
"booking.com": "com.booking",
"booking": "com.booking",
"BOOKING.COM": "com.booking",
"Chrome": "com.android.chrome",
"chrome": "com.android.chrome",
"Google Chrome": "com.android.chrome",
"Clock": "com.android.deskclock",
"clock": "com.android.deskclock",
"Contacts": "com.android.contacts",
"contacts": "com.android.contacts",
"Duolingo": "com.duolingo",
"duolingo": "com.duolingo",
"Expedia": "com.expedia.bookings",
"expedia": "com.expedia.bookings",
"Files": "com.android.fileexplorer",
"files": "com.android.fileexplorer",
"File Manager": "com.android.fileexplorer",
"file manager": "com.android.fileexplorer",
"gmail": "com.google.android.gm",
"Gmail": "com.google.android.gm",
"GoogleMail": "com.google.android.gm",
"Google Mail": "com.google.android.gm",
"GoogleFiles": "com.google.android.apps.nbu.files",
"googlefiles": "com.google.android.apps.nbu.files",
"FilesbyGoogle": "com.google.android.apps.nbu.files",
"GoogleCalendar": "com.google.android.calendar",
"Google-Calendar": "com.google.android.calendar",
"Google Calendar": "com.google.android.calendar",
"google-calendar": "com.google.android.calendar",
"google calendar": "com.google.android.calendar",
"GoogleChat": "com.google.android.apps.dynamite",
"Google Chat": "com.google.android.apps.dynamite",
"Google-Chat": "com.google.android.apps.dynamite",
"GoogleClock": "com.google.android.deskclock",
"Google Clock": "com.google.android.deskclock",
"Google-Clock": "com.google.android.deskclock",
"GoogleContacts": "com.google.android.contacts",
"Google-Contacts": "com.google.android.contacts",
"Google Contacts": "com.google.android.contacts",
"google-contacts": "com.google.android.contacts",
"google contacts": "com.google.android.contacts",
"GoogleDocs": "com.google.android.apps.docs.editors.docs",
"Google Docs": "com.google.android.apps.docs.editors.docs",
"googledocs": "com.google.android.apps.docs.editors.docs",
"google docs": "com.google.android.apps.docs.editors.docs",
"Google Drive": "com.google.android.apps.docs",
"Google-Drive": "com.google.android.apps.docs",
"google drive": "com.google.android.apps.docs",
"google-drive": "com.google.android.apps.docs",
"GoogleDrive": "com.google.android.apps.docs",
"Googledrive": "com.google.android.apps.docs",
"googledrive": "com.google.android.apps.docs",
"GoogleFit": "com.google.android.apps.fitness",
"googlefit": "com.google.android.apps.fitness",
"GoogleKeep": "com.google.android.keep",
"googlekeep": "com.google.android.keep",
"GoogleMaps": "com.google.android.apps.maps",
"Google Maps": "com.google.android.apps.maps",
"googlemaps": "com.google.android.apps.maps",
"google maps": "com.google.android.apps.maps",
"Google Play Books": "com.google.android.apps.books",
"Google-Play-Books": "com.google.android.apps.books",
"google play books": "com.google.android.apps.books",
"google-play-books": "com.google.android.apps.books",
"GooglePlayBooks": "com.google.android.apps.books",
"googleplaybooks": "com.google.android.apps.books",
"GooglePlayStore": "com.android.vending",
"Google Play Store": "com.android.vending",
"Google-Play-Store": "com.android.vending",
"GoogleSlides": "com.google.android.apps.docs.editors.slides",
"Google Slides": "com.google.android.apps.docs.editors.slides",
"Google-Slides": "com.google.android.apps.docs.editors.slides",
"GoogleTasks": "com.google.android.apps.tasks",
"Google Tasks": "com.google.android.apps.tasks",
"Google-Tasks": "com.google.android.apps.tasks",
"Joplin": "net.cozic.joplin",
"joplin": "net.cozic.joplin",
"McDonald": "com.mcdonalds.app",
"mcdonald": "com.mcdonalds.app",
"Osmand": "net.osmand",
"osmand": "net.osmand",
"PiMusicPlayer": "com.Project100Pi.themusicplayer",
"pimusicplayer": "com.Project100Pi.themusicplayer",
"Quora": "com.quora.android",
"quora": "com.quora.android",
"Reddit": "com.reddit.frontpage",
"reddit": "com.reddit.frontpage",
"RetroMusic": "code.name.monkey.retromusic",
"retromusic": "code.name.monkey.retromusic",
"SimpleCalendarPro": "com.scientificcalculatorplus.simplecalculator.basiccalculator.mathcalc",
"SimpleSMSMessenger": "com.simplemobiletools.smsmessenger",
"Telegram": "org.telegram.messenger",
"temu": "com.einnovation.temu",
"Temu": "com.einnovation.temu",
"Tiktok": "com.zhiliaoapp.musically",
"tiktok": "com.zhiliaoapp.musically",
"Twitter": "com.twitter.android",
"twitter": "com.twitter.android",
"X": "com.twitter.android",
"VLC": "org.videolan.vlc",
"WeChat": "com.tencent.mm",
"wechat": "com.tencent.mm",
"Whatsapp": "com.whatsapp",
"WhatsApp": "com.whatsapp",
}
def get_package_name(app_name: str) -> str | None:
"""
获取应用的包名。
参数:
app_name: 应用的显示名称。
返回:
Android包名,如果未找到则返回None。
"""
return APP_PACKAGES.get(app_name)
def get_app_name(package_name: str) -> str | None:
"""
根据包名获取应用名称。
参数:
package_name: Android包名。
返回:
应用的显示名称,如果未找到则返回None。
"""
for name, package in APP_PACKAGES.items():
if package == package_name:
return name
return None
def list_supported_apps() -> list[str]:
"""
获取所有支持的应用名称列表。
返回:
应用名称列表。
"""
return list(APP_PACKAGES.keys())
2-测试代码
python
from phone_agent.config.apps_util.apps_util import get_app_name, get_package_name, list_supported_apps
if __name__ == '__main__':
# 1-根据【应用名称】找【包路径】
print(get_package_name("微信"))
# 2-根据【包路径】找【应用名称】
print(get_app_name("com.xingin.xhs"))
# 3-列出所有支持的应用名称
print(list_supported_apps())
5-ADB操作-键盘操作
1-源码展示
python
"""Android设备文本输入的输入工具。"""
import base64
import subprocess
def type_text(text: str, device_id: str | None = None) -> None:
"""
使用ADB键盘在当前聚焦的输入框中输入文本。
参数:
text: 要输入的文本。
device_id: 可选的ADB设备ID,用于多设备环境。
注意:
需要在设备上安装ADB Keyboard。
参见:https://github.com/nicnocquee/AdbKeyboard
"""
adb_prefix = _get_adb_prefix(device_id)
encoded_text = base64.b64encode(text.encode("utf-8")).decode("utf-8")
subprocess.run(
adb_prefix
+ [
"shell",
"am",
"broadcast",
"-a",
"ADB_INPUT_B64",
"--es",
"msg",
encoded_text,
],
capture_output=True,
text=True,
)
def clear_text(device_id: str | None = None) -> None:
"""
清除当前聚焦输入框中的文本。
参数:
device_id: 可选的ADB设备ID,用于多设备环境。
"""
adb_prefix = _get_adb_prefix(device_id)
subprocess.run(
adb_prefix + ["shell", "am", "broadcast", "-a", "ADB_CLEAR_TEXT"],
capture_output=True,
text=True,
)
def detect_and_set_adb_keyboard(device_id: str | None = None) -> str:
"""
检测当前输入法,并在需要时切换到ADB键盘。
参数:
device_id: 可选的ADB设备ID,用于多设备环境。
返回:
用于后续恢复的原始输入法标识符。
"""
adb_prefix = _get_adb_prefix(device_id)
# 获取当前输入法
result = subprocess.run(
adb_prefix + ["shell", "settings", "get", "secure", "default_input_method"],
capture_output=True,
text=True,
)
current_ime = (result.stdout + result.stderr).strip()
# 如果未设置为ADB键盘,则切换
if "com.android.adbkeyboard/.AdbIME" not in current_ime:
subprocess.run(
adb_prefix + ["shell", "ime", "set", "com.android.adbkeyboard/.AdbIME"],
capture_output=True,
text=True,
)
# 预热键盘
type_text("", device_id)
return current_ime
def restore_keyboard(ime: str, device_id: str | None = None) -> None:
"""
恢复原始输入法。
参数:
ime: 要恢复的输入法标识符。
device_id: 可选的ADB设备ID,用于多设备环境。
"""
adb_prefix = _get_adb_prefix(device_id)
subprocess.run(
adb_prefix + ["shell", "ime", "set", ime], capture_output=True, text=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 -*-
"""
keyboard_util模块的真实使用案例测试
"""
import logging
import sys
import os
# 添加当前目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# 直接导入模块
from keyboard_util import (
type_text,
clear_text,
detect_and_set_adb_keyboard,
restore_keyboard
)
def setup_logging():
"""设置日志配置"""
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
def test_keyboard_operations():
"""测试键盘操作功能"""
print("开始测试keyboard_util模块...")
# 设置日志
setup_logging()
try:
# 1. 测试检测并设置ADB键盘
print("\n1. 测试检测并设置ADB键盘...")
original_ime = detect_and_set_adb_keyboard()
print(f"原始输入法: {original_ime}")
# 等待键盘设置完成
import time
time.sleep(1)
# 2. 测试输入文本
print("\n2. 测试输入文本...")
test_text = "Hello, 这是一个测试!"
type_text(test_text)
print(f"已输入文本: {test_text}")
# 等待输入完成
time.sleep(1)
# 3. 测试清除文本
print("\n3. 测试清除文本...")
clear_text()
print("文本已清除")
# 等待清除完成
time.sleep(1)
# 4. 测试输入中文文本
print("\n4. 测试输入中文文本...")
chinese_text = "你好世界!123"
type_text(chinese_text)
print(f"已输入中文文本: {chinese_text}")
# 等待输入完成
time.sleep(1)
# 5. 测试恢复原始键盘
print("\n5. 测试恢复原始键盘...")
restore_keyboard(original_ime)
print(f"已恢复输入法: {original_ime}")
# 等待恢复完成
time.sleep(1)
print("\n所有测试完成!")
except Exception as e:
print(f"测试过程中出现错误: {e}")
raise
if __name__ == "__main__":
test_keyboard_operations()
- 测试日志打印
shell
已连接到 pydev 调试器(内部版本号 231.9225.15)开始测试keyboard_util模块...
1. 测试检测并设置ADB键盘...
2025-12-17 19:52:49,531 - keyboard_util - DEBUG - 检测设备 None 上的当前输入法
2025-12-17 19:52:49,609 - keyboard_util - DEBUG - 当前输入法: com.baidu.input/.ImeService
2025-12-17 19:52:49,609 - keyboard_util - DEBUG - 切换到ADB键盘
2025-12-17 19:52:49,685 - keyboard_util - DEBUG - 预热ADB键盘
2025-12-17 19:52:49,686 - keyboard_util - DEBUG - 输入文本: 到设备: None
2025-12-17 19:52:49,686 - keyboard_util - DEBUG - 执行ADB命令: ['adb', 'shell', 'am', 'broadcast', '-a', 'ADB_INPUT_B64', '--es', 'msg', '']
2025-12-17 19:52:49,766 - keyboard_util - DEBUG - 输入文本结果: 255
原始输入法: com.baidu.input/.ImeService
2. 测试输入文本...
2025-12-17 19:52:50,769 - keyboard_util - DEBUG - 输入文本: Hello, 这是一个测试! 到设备: None
2025-12-17 19:52:50,769 - keyboard_util - DEBUG - 执行ADB命令: ['adb', 'shell', 'am', 'broadcast', '-a', 'ADB_INPUT_B64', '--es', 'msg', 'SGVsbG8sIOi/meaYr+S4gOS4qua1i+ivlSE=']
2025-12-17 19:52:50,864 - keyboard_util - DEBUG - 输入文本结果: 0
已输入文本: Hello, 这是一个测试!
3. 测试清除文本...
2025-12-17 19:52:51,867 - keyboard_util - DEBUG - 清除设备 None 上的文本
2025-12-17 19:52:51,867 - keyboard_util - DEBUG - 执行ADB命令: ['adb', 'shell', 'am', 'broadcast', '-a', 'ADB_CLEAR_TEXT']
2025-12-17 19:52:51,938 - keyboard_util - DEBUG - 清除文本结果: 0
文本已清除
4. 测试输入中文文本...
2025-12-17 19:52:52,939 - keyboard_util - DEBUG - 输入文本: 你好世界!123 到设备: None
2025-12-17 19:52:52,939 - keyboard_util - DEBUG - 执行ADB命令: ['adb', 'shell', 'am', 'broadcast', '-a', 'ADB_INPUT_B64', '--es', 'msg', '5L2g5aW95LiW55WM77yBMTIz']
2025-12-17 19:52:53,025 - keyboard_util - DEBUG - 输入文本结果: 0
已输入中文文本: 你好世界!123
5. 测试恢复原始键盘...
2025-12-17 19:52:54,028 - keyboard_util - DEBUG - 恢复输入法 com.baidu.input/.ImeService 到设备 None
2025-12-17 19:52:54,028 - keyboard_util - DEBUG - 执行ADB命令: ['adb', 'shell', 'ime', 'set', 'com.baidu.input/.ImeService']
2025-12-17 19:52:54,093 - keyboard_util - DEBUG - 恢复输入法结果: 0
已恢复输入法: com.baidu.input/.ImeService
所有测试完成!