AutoGLMPhone05-源码-ADB模块

智谱AI-OpenAutoGLM-开源的手机智能体

针对智谱AI-AutoGLM-开源的手机智能体,整理代码拆解步骤


1-思路整理

  • 1)先把手机和电脑的连接软件安装上【ADB(电脑安装)】+【ADBKeyboard(手机安装)】
  • 2)然后手机打开调试模式->这个电脑的应用就可以直接操作手机
  • 3)配置智谱AI-AutoGLM-开源的手机智能体的模型(模型地址/模型)
  • 4)运行智谱AI-AutoGLM-开源的手机智能体代码->直接操作手机

2-参考网址


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 就自动把它当成该参数处理;如果同时有多个候选就会报错。

  1. 定义阶段
python 复制代码
parser.add_argument("--list-devices", action="store_true", ...)
  1. 用户输入
bash 复制代码
python a02_test_parse.py --list-device   # 少了最后的 "s"
python a02_test_parse.py --list          # 更短的缩写
  1. argparse 解析

    • 先扫描所有长选项名:--list-devices
    • 用用户给出的字符串 --list-device(或 --list)去做前缀匹配
      • --list-device--list-devices 的唯一前缀 → 合法
      • --list 也是 --list-devices 的唯一前缀 → 合法
    • 如果再加入一个 --list-something,再敲 --list 就会因歧义而报错。
  2. 关闭方法(如想禁用)

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: 就能检测到。"

下面把细节拆开给你看:

  1. 定义阶段
python 复制代码
parser.add_argument(
    "--list-devices",      # 命令行上敲的名字
    action="store_true",   # 只要出现就把值设为 True
    help="列出可用设备并退出"
)
# 没显式写 dest,argparse 默认 dest = "list_devices"
  1. 用户输入
bash 复制代码
python a02_test_parse.py --list-devices
  1. 解析阶段
python 复制代码
args = parser.parse_args()
# 内部等价于:
# args.list_devices = True
  1. 功能分发
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

时创建,里面主要放两类东西:

  1. 项目级元数据

    • PKG-INFO:名称、版本、作者、描述、依赖等,跟 PyPI 页面展示的信息同源。
    • entry_points.txt(如果有):控制台脚本、插件入口点的注册表。
    • top_level.txt:列出该包在 import 时的顶层命名空间。
  2. 安装过程辅助文件

    • dependency_links.txt、requires.txt:依赖列表,供 pip/setuptools 解析。
    • SOURCES.txt:本次打包包含的所有文件清单,后续做 sdist 或 uninstall 时用来对照。

一句话:它不是"可执行代码",而是告诉工具"我是谁、我依赖谁、我提供了哪些入口"。

对开发者来说,可以:

  • 直接删掉,重新 setup.py egg_infopip 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

所有测试完成!

相关推荐
菜择贰8 小时前
ubantu下载mysql
数据库·mysql·adb
坐吃山猪9 小时前
AutoGLMPhone03-adb模块
adb·llm·glm
今晚务必早点睡9 小时前
MySQL 新手避坑指南:安装、区分、检查一步到位
数据库·mysql·adb
我的offer在哪里19 小时前
mysql修改密码
adb
编程小Y20 小时前
MySQL 与 MCP 集成全解析(核心原理 + 实战步骤 + 应用场景)
数据库·mysql·adb
嘻哈baby1 天前
MySQL主从复制与读写分离实战指南
数据库·mysql·adb
Neolnfra1 天前
系统敏感安全文件路径
linux·windows·安全·web安全·网络安全·adb·系统安全
何妨呀~2 天前
mysql 8服务器实验
android·mysql·adb
元气满满-樱2 天前
MySql部署多实例
数据库·mysql·adb