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

所有测试完成!

相关推荐
熬夜的咕噜猫3 小时前
MySQL 核心数据库操作
adb
Yang-Never5 小时前
ADB ->adb shell perfetto 抓取 trace 指令
android·开发语言·adb·android studio
轩情吖10 小时前
MySQL之事务管理
android·后端·mysql·adb·事务·隔离性·原子性
赶路人儿1 天前
常见的mcp配置
android·adb
ego.iblacat1 天前
MySQL 数据库操作
数据库·mysql·adb
路溪非溪1 天前
adb的安装和基本使用总结
adb
XDHCOM1 天前
MySQL报错LDAP认证初始化连接池失败,远程修复思路和故障排查分享
数据库·mysql·adb
闻哥1 天前
深入理解 MySQL InnoDB Buffer Pool 的 LRU 冷热数据机制
android·java·jvm·spring boot·mysql·adb·面试
炸炸鱼.2 天前
MySQL 数据库核心操作手册
数据库·adb·oracle
总要冲动一次2 天前
MySQL 5.7 全量 + 增量备份方案(本地执行 + 远程存储)
数据库·mysql·adb