本博客参照官方文档:https://docs.droidrun.ai/v3/quickstart

一、前提需要

系统要求:

Android 设备要求:

  • 开启开发者选项
  • 开启 USB 调试
  • 通过 USB 连接,或在同一网络下(用于无线调试)

二、安装

pip install 'droidrun[google,anthropic,openai,deepseek,ollama,dev]'
droidrun setup

在这里插入图片描述

droidrun ping

在这里插入图片描述

Q:安装在安卓10及以下的怎么办?

https://docs.droidrun.ai/guides/device-setup#wireless-debugging-android-10-and-below
在这里插入图片描述
经试验,安装v0.2.2版本的是ok的:https://github.com/droidrun/droidrun-portal/releases?page=3

# 1. 清理设备上残留的旧Portal安装包(避免冲突)
adb shell rm -f /data/local/tmp/droidrun-portal-*.apk

# 2. 推送v0.3.7 APK到设备临时目录
adb push /Users/Downloads/droidrun-portal-v0.2.2.apk /data/local/tmp/

# 3. 安装(-r 覆盖安装,-t 允许测试版,-d 允许降级,解决SDK兼容问题)
adb shell pm install -r -t -d /data/local/tmp/droidrun-portal-v0.2.2.apk

如果一定要用v0.5.3(最新):
阶段1:反编译APK并修改minSdkVersion

步骤 命令 作用
1. 反编译APK apktool d /Users/Downloads/droidrun-portal-v0.5.3.apk -o droidrun-portal-mod 将APK解压为可编辑的文件结构
2. 编辑配置 打开droidrun-portal-mod/AndroidManifest.xml,把<uses-sdk android:minSdkVersion="30"/>改为android:minSdkVersion="29" 降低APK要求的最低系统版本
3. 重新编译 apktool b droidrun-portal-mod -o droidrun-portal-unsigned.apk -f 生成未签名的新APK(-f强制重新编译)

阶段2:APK对齐(解决原生库提取失败)

步骤 命令 作用
1. 执行对齐 /Users/Library/Android/sdk/build-tools/36.1.0/zipalign -v 4 droidrun-portal-unsigned.apk droidrun-portal-aligned.apk 4字节对齐APK,修复INSTALL_FAILED_INVALID_APK错误
2. 验证对齐 /Users/Library/Android/sdk/build-tools/36.1.0/zipalign -c -v 4 droidrun-portal-aligned.apk 确认对齐成功(输出Verification successful

阶段3:用apksigner签名(安卓官方标准)

步骤 命令 作用
1. 生成密钥(若未生成) keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000 创建签名用的密钥库(设置密码并记住)
2. 执行签名 /Users/Library/Android/sdk/build-tools/36.1.0/apksigner sign --ks my-release-key.keystore --ks-key-alias alias_name droidrun-portal-aligned.apk 用安全算法签名对齐后的APK(输入密钥密码)
3. 验证签名 /Users/Library/Android/sdk/build-tools/36.1.0/apksigner verify -v droidrun-portal-aligned.apk 确认签名有效(输出Verified successfully

阶段4:权限配置+推送安装

步骤 命令 作用
1. 修改设备目录权限 adb shell chmod 777 /data/local/tmp 消除原生库提取的权限障碍
2. 推送APK到设备 adb push droidrun-portal-aligned.apk /data/local/tmp/ 把修复后的APK传到设备临时目录
3. 安装APK adb shell pm install -r -t -d /data/local/tmp/droidrun-portal-aligned.apk 执行安装(-r覆盖旧版本,-t允许测试包,-d允许版本降级)

关键补充命令(排障用)

  1. 查找zipalign/apksigner路径:find ~/Library/Android/sdk -name zipalign / find ~/Library/Android/sdk -name apksigner
  2. 检查设备连接:adb devices
  3. 临时添加工具到PATH(避免输完整路径):export PATH=$PATH:/Users/Library/Android/sdk/build-tools/36.1.0

都不行的话:用Android Studio~
如何用虚拟机可以见这个视频:macOS上如何使用安卓系统?最官方的安卓模拟器,用起来也很方便!Android Studio安装和使用教程

三、项目结构

1. 项目树状结构

droidrun/
├── __init__.py           # 主入口,导出核心类
├── __main__.py           # Python 模块入口
├── portal.py             # Portal 设备交互相关
│
├── cli/                  # 命令行界面
│   ├── main.py          # CLI 主命令处理
│   └── logs.py          # 日志处理
│
├── config_manager/       # 配置管理
│   ├── config_manager.py
│   ├── path_resolver.py
│   ├── prompt_loader.py
│   └── safe_execution.py
│
├── tools/                # 设备交互工具
│   ├── adb.py           # Android ADB 工具
│   ├── ios.py           # iOS 工具
│   ├── stealth_adb.py   # 隐身 ADB 工具
│   ├── portal_client.py # Portal 客户端
│   ├── cloud.py         # 云服务集成
│   ├── geometry.py      # 几何计算
│   ├── element_search.py # 元素搜索
│   ├── filters/         # 树过滤器
│   │   ├── base.py
│   │   ├── concise_filter.py
│   │   └── detailed_filter.py
│   └── formatters/      # 格式化器
│       ├── base.py
│       └── indexed_formatter.py
│
├── agent/                # AI 代理核心
│   ├── common/          # 公共事件和工具
│   ├── droid/           # Droid 代理 (主代理)
│   ├── codeact/         # CodeAct 代理 (代码执行)
│   ├── manager/         # Manager 代理 (规划器)
│   ├── executor/        # Executor 代理 (执行器)
│   ├── scripter/        # Scripter 代理 (脚本生成)
│   ├── oneflows/        # OneFlow 工作流
│   ├── trajectory/      # 轨迹记录
│   └── utils/           # 工具函数
│
├── config/               # 配置文件
│   ├── prompts/         # LLM 提示词
│   │   ├── codeact/     # CodeAct 提示词
│   │   ├── manager/     # Manager 提示词
│   │   ├── scripter/    # Scripter 提示词
│   │   └── executor/    # Executor 提示词
│   └── app_cards/       # 应用卡片
│
├── macro/                # 宏命令功能
│   └── cli.py           # 宏命令 CLI
│
└── static/               # 静态资源

2. DroidRun 的几种模式

根据代码分析,DroidRun 有以下几种运行模式:

a) 推理模式 (Reasoning Mode)

  • 位置: droidrun/cli/main.py:423-425
  • CLI 参数: --reasoning/--no-reasoning
  • 说明: 启用规划功能的推理模式
  • 特点:
    • 使用 Manager 代理进行多步骤规划
    • 适合复杂任务
    • 会生成任务分解和执行计划
# 代码位置: droidrun/cli/main.py:214-219
mode = (
    "planning with reasoning"
    if config.agent.reasoning
    else "direct execution"
)

b) 直接执行模式 (Direct Execution Mode)

  • 说明: 默认模式,不启用推理
  • 特点:
    • 直接执行命令,无需复杂规划
    • 适合简单任务
    • 响应速度快

c) 视觉模式 (Vision Mode)

  • 位置: droidrun/cli/main.py:419-422
  • CLI 参数: --vision/--no-vision
  • 说明: 启用截图功能,使用视觉理解
  • 可为不同代理单独设置:
    • --manager-vision: Manager 代理视觉
    • --executor-vision: Executor 代理视觉
    • --codeact-vision: CodeAct 代理视觉
# 代码位置: droidrun/cli/main.py:165-178
if vision is not None:
    config.agent.manager.vision = vision
    config.agent.executor.vision = vision
    config.agent.codeact.vision = vision

d) TCP 通信模式 (TCP Communication Mode)

  • 位置: droidrun/cli/main.py:436-439
  • CLI 参数: --tcp/--no-tcp
  • 说明: 使用 TCP 进行设备控制通信
  • 特点:
    • 比 Content Provider 更快
    • 需要 Portal App 支持

e) 代理类型模式 (Agent Types)

代理类型 说明 目录位置
DroidAgent 主代理,整合所有功能 droidrun/agent/droid/
CodeActAgent 代码执行代理 droidrun/agent/codeact/
ManagerAgent 管理器/规划代理 droidrun/agent/manager/
ExecutorAgent 执行器代理 droidrun/agent/executor/
ScripterAgent 脚本生成代理 droidrun/agent/scripter/

f) 轨迹保存模式 (Trajectory Saving Mode)

  • 位置: droidrun/cli/main.py:441-445
  • CLI 参数: --save-trajectory [none|step|action]
  • 级别:
    • none: 不保存轨迹
    • step: 按步骤保存
    • action: 按动作保存

3. 与手机交互的实现方式

Android 设备交互

核心组件: droidrun/tools/adb.py

3.1 ADB 连接
# 使用 async_adbutils 库
from async_adbutils import adb

# 连接设备
self.device = await adb.device(serial=self._serial)

# 检查设备状态
state = await self.device.get_state()
  • 支持方式: USB 或 TCP/IP
  • 代码位置: droidrun/tools/adb.py:101-123
3.2 Portal App

在 Android 设备上安装的辅助应用,提供:

  • Content Provider: 数据查询接口
  • TCP Server: 更快的通信方式
  • Accessibility Service: 获取界面元素树
  • 截图功能: 捕获屏幕内容

代码位置: droidrun/tools/portal_client.py

3.3 交互方式详解
触摸操作
# 通过索引点击元素
async def tap_by_index(self, index: int) -> str:
    """
    点击 UI 元素

    流程:
    1. 从缓存中查找元素
    2. 计算元素中心坐标
    3. 执行点击
    4. 记录轨迹事件
    """
    x, y = self._extract_element_coordinates_by_index(index)
    await self.device.click(x, y)
    # 位置: droidrun/tools/adb.py:220-320

# 滑动手势
async def swipe(self, start_x, start_y, end_x, end_y, duration_ms=1000):
    """执行滑动操作"""
    await self.device.swipe(start_x, start_y, end_x, end_y, duration_ms/1000)
    # 位置: droidrun/tools/adb.py:447-492

# 拖拽手势
async def drag(self, start_x, start_y, end_x, end_y, duration=3):
    """执行拖拽操作"""
    await self.device.drag(start_x, start_y, end_x, end_y, duration)
    # 位置: droidrun/tools/adb.py:494-536
文本输入
async def input_text(self, text: str, index: int = -1, clear: bool = False):
    """
    输入文本

    Args:
        text: 要输入的文本
        index: 目标元素索引
        clear: 是否先清除现有文本

    使用 PortalClient 进行文本输入
    """
    if index != -1:
        await self.tap_by_index(index)
    success = await self.portal.input_text(text, clear)
    # 位置: droidrun/tools/adb.py:538-577
系统操作
# 返回键
async def back(self) -> str:
    """按下 Android 返回键"""
    await self.device.keyevent(4)  # KEYCODE_BACK
    # 位置: droidrun/tools/adb.py:579-601

# 按键操作
async def press_key(self, keycode: int) -> str:
    """
    按下指定按键

    常用按键:
    - 3: HOME
    - 4: BACK
    - 66: ENTER
    - 67: DELETE
    """
    await self.device.keyevent(keycode)
    # 位置: droidrun/tools/adb.py:603-640

# 启动应用
async def start_app(self, package: str, activity: str = None):
    """启动指定应用"""
    await self.device.app_start(package, activity)
    # 位置: droidrun/tools/adb.py:642-675
状态获取
async def get_state(self):
    """
    获取设备状态

    流程:
    1. 从 Portal 获取原始数据
    2. TreeFilter 过滤无关元素
    3. TreeFormatter 格式化输出
    4. 返回格式化文本、焦点文本、元素树、设备状态
    """
    # 获取原始数据
    combined_data = await self.portal.get_state()

    # 过滤
    self.filtered_tree_cache = self.tree_filter.filter(
        self.raw_tree_cache, combined_data["device_context"]
    )

    # 格式化
    formatted_text, focused_text, a11y_tree, phone_state = (
        self.tree_formatter.format(
            self.filtered_tree_cache, combined_data["phone_state"]
        )
    )

    return (formatted_text, focused_text, a11y_tree, phone_state)
    # 位置: droidrun/tools/adb.py:818-874

# 截图
async def take_screenshot(self, hide_overlay: bool = True):
    """截取设备屏幕"""
    image_bytes = await self.portal.take_screenshot(hide_overlay)
    return "PNG", image_bytes
    # 位置: droidrun/tools/adb.py:707-729

# 列出已安装应用
async def list_packages(self, include_system_apps: bool = False):
    """获取设备上已安装的应用包名列表"""
    filter_list = [] if include_system_apps else ["-3"]
    packages = await self.device.list_packages(filter_list)
    return packages
    # 位置: droidrun/tools/adb.py:730-748
3.4 状态获取流程图
Android 设备
    ↓
Portal App (com.droidrun.portal)
    ↓
Accessibility Tree + Phone State
    ↓
PortalClient (获取数据)
    ↓
TreeFilter (ConciseFilter/DetailedFilter)
    ↓
TreeFormatter (IndexedFormatter)
    ↓
返回给 LLM 分析

iOS 设备交互

核心组件: droidrun/tools/ios.py

3.5 HTTP API 连接
class IOSTools(Tools):
    def __init__(self, url: str, bundle_identifiers: List[str] = None):
        """
        初始化 iOS 工具

        Args:
            url: iOS 设备 URL (例如: http://192.168.1.100:8080)
            bundle_identifiers: 应用包名列表
        """
        self.url = url
        # 位置: droidrun/tools/ios.py:42-62
3.6 iOS 交互方式
# 点击元素
def tap_by_index(self, index: int) -> str:
    """通过索引点击 iOS 元素"""
    # 格式化 rect: {{x,y},{width,height}}
    ios_rect = f"{{{{{x},{y}}},{{{width},{height}}}}}"

    # 发送点击请求
    tap_url = f"{self.url}/gestures/tap"
    payload = {"rect": ios_rect, "count": 1, "longPress": False}
    response = requests.post(tap_url, json=payload)
    # 位置: droidrun/tools/ios.py:209-301

# 滑动
def swipe(self, start_x, start_y, end_x, end_y, duration_ms=300):
    """执行滑动操作"""
    swipe_url = f"{self.url}/gestures/swipe"
    payload = {"x": float(start_x), "y": float(start_y), "dir": direction}
    response = requests.post(swipe_url, json=payload)
    # 位置: droidrun/tools/ios.py:335-377

# 文本输入
def input_text(self, text: str):
    """输入文本到 iOS 设备"""
    type_url = f"{self.url}/inputs/type"
    payload = {"rect": rect, "text": text}
    response = requests.post(type_url, json=payload)
    # 位置: droidrun/tools/ios.py:401-427

# 按键
def press_key(self, keycode: int):
    """
    按键操作

    iOS 按键码:
    - 0: HOME
    - 4: ACTION
    - 5: CAMERA
    """
    key_url = f"{self.url}/inputs/key"
    payload = {"key": keycode}
    response = requests.post(key_url, json=payload)
    # 位置: droidrun/tools/ios.py:431-459

# 启动应用
def start_app(self, package: str, activity: str = ""):
    """启动 iOS 应用"""
    launch_url = f"{self.url}/inputs/launch"
    payload = {"bundleIdentifier": package}
    response = requests.post(launch_url, json=payload)
    # 位置: droidrun/tools/ios.py:460-481
3.7 iOS Accessibility Tree 解析
def _parse_ios_accessibility_tree(self, a11y_data: str):
    """
    解析 iOS 辅助功能树

    格式示例:
    Button, {{100, 200}, {50, 30}}, label:'Click me', identifier:'btn1'

    解析流程:
    1. 按行分割
    2. 使用正则表达式提取坐标
    3. 提取元素属性
    4. 过滤可交互元素类型
    5. 构建标准化的元素字典
    """
    # 位置: droidrun/tools/ios.py:102-207
3.8 iOS API 端点
端点 方法 功能
/vision/a11y GET 获取辅助功能树
/vision/screenshot GET 截取屏幕
/vision/state GET 获取设备状态
/gestures/tap POST 点击操作
/gestures/swipe POST 滑动操作
/inputs/type POST 输入文本
/inputs/key POST 按键操作
/inputs/launch POST 启动应用

4. CLI 命令结构

4.1 主要命令

droidrun [OPTIONS] COMMAND [ARGS]...

4.2 可用命令

命令 说明 示例
run 在设备上执行命令 (默认) droidrun run "打开微信"
devices 列出已连接设备 droidrun devices
connect 通过 TCP/IP 连接设备 droidrun connect 192.168.1.100:5555
disconnect 断开设备连接 droidrun disconnect 192.168.1.100:5555
setup 安装并启用 DroidRun Portal droidrun setup
ping 检查设备是否可用 droidrun ping
macro 宏命令子组 droidrun macro play test_macro

4.3 常用选项

选项 简写 说明 示例
--config -c 配置文件路径 -c config.yaml
--device -d 设备序列号 -d emulator-5554
--provider -p LLM 提供商 -p anthropic
--model -m LLM 模型 -m claude-sonnet-4-5-20250929
--temperature LLM 温度参数 --temperature 0
--steps 最大步骤数 --steps 20
--vision 启用视觉 --vision
--no-vision 禁用视觉 --no-vision
--reasoning 启用推理 --reasoning
--no-reasoning 禁用推理 --no-reasoning
--tcp 启用 TCP 通信 --tcp
--stream 流式输出 --stream
--tracing 启用追踪 --tracing
--debug 调试模式 --debug
--save-trajectory 轨迹保存级别 --save-trajectory action
--ios iOS 设备 --ios

4.4 使用示例

# 基本使用
droidrun "打开设置并搜索电池"

# 指定设备和模型
droidrun -d emulator-5554 -p anthropic -m claude-sonnet-4-5-20250929 "打开 YouTube"

# 启用推理模式
droidrun --reasoning "帮我预订一个酒店"

# 使用 TCP 通信
droidrun --tcp "发送一条消息"

# iOS 设备
droidrun --ios "打开 Safari 浏览器"

# 调试模式
droidrun --debug "测试命令"

# 保存轨迹
droidrun --save-trajectory action "复杂任务"

5. 数据流向

┌─────────────────────────────────────────────────────────────┐
│                        用户输入                              │
│                  (自然语言命令)                              │
└────────────────────────┬────────────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────────────┐
│                      CLI 解析                                │
│  - 解析命令和选项                                            │
│  - 加载配置                                                  │
│  - 初始化日志                                                │
└────────────────────────┬────────────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────────────┐
│                  DroidAgent 初始化                           │
│  - 根据配置选择代理类型 (Manager/Executor/CodeAct)          │
│  - 加载 LLM                                                  │
│  - 初始化 Tools (ADB/IOSTools)                             │
└────────────────────────┬────────────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────────────┐
│                    LLM 处理循环                              │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  1. 获取设备状态                                       │  │
│  │     - get_state() → Accessibility Tree               │  │
│  │     - take_screenshot() → 屏幕图像                    │  │
│  └──────────────────────────────────────────────────────┘  │
│                         ↓                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  2. 构建提示词                                         │  │
│  │     - 设备状态                                         │  │
│  │     - 截图 (如果启用视觉)                              │  │
│  │     - 用户目标                                         │  │
│  │     - 可用工具列表                                     │  │
│  └──────────────────────────────────────────────────────┘  │
│                         ↓                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  3. LLM 推理                                          │  │
│  │     - 选择工具                                        │  │
│  │     - 确定参数                                        │  │
│  └──────────────────────────────────────────────────────┘  │
│                         ↓                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  4. 执行工具                                          │  │
│  │     - tap() / swipe() / input_text()                 │  │
│  │     - start_app() / back()                           │  │
│  └──────────────────────────────────────────────────────┘  │
│                         ↓                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  5. 记录事件                                          │  │
│  │     - 动作事件                                        │  │
│  │     - 轨迹记录 (如果启用)                             │  │
│  └──────────────────────────────────────────────────────┘  │
│                         ↓                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  6. 检查完成条件                                      │  │
│  │     - success = True → 结束                          │  │
│  │     - success = False → 继续循环                     │  │
│  │     - 超过最大步数 → 结束                             │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────────┬────────────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────────────┐
│                       返回结果                               │
│  - success: True/False                                      │
│  - reason: 成功/失败原因                                     │
└─────────────────────────────────────────────────────────────┘

6. 核心类关系

DroidAgent (主代理)
    ├── ManagerAgent (规划器)
    │   └── 生成任务计划
    │
    ├── ExecutorAgent (执行器)
    │   ├── Tools (设备交互工具)
    │   │   ├── AdbTools (Android)
    │   │   ├── IOSTools (iOS)
    │   │   └── StealthAdbTools
    │   │
    │   └── PortalClient (Portal 通信)
    │       ├── Content Provider
    │       └── TCP Client
    │
    └── CodeActAgent (代码执行)
        └── 执行 Python 代码

7. 配置系统

7.1 配置文件结构

# config/droidrun.yaml

agent:
  max_steps: 15
  reasoning: false
  streaming: false

  manager:
    vision: true

  executor:
    vision: true

  codeact:
    vision: true

device:
  serial: null
  platform: "android"  # or "ios"
  use_tcp: false

logging:
  debug: false
  save_trajectory: "none"  # "none", "step", "action"
  rich_text: true

tracing:
  enabled: false

tools:
  enable_formatters: true

7.2 配置类层次

DroidrunConfig
├── AgentConfig
│   ├── ManagerConfig
│   ├── ExecutorConfig
│   ├── CodeActConfig
│   └── ScripterConfig
├── DeviceConfig
├── LoggingConfig
├── TracingConfig
├── ToolsConfig
├── CredentialsConfig
└── SafeExecutionConfig

8. 事件系统

8.1 事件类型

# droidrun/agent/common/events.py

class Event(BaseModel):
    """基础事件类"""

class StepEvent(Event):
    """步骤事件"""

class ActionEvent(Event):
    """动作事件"""
    ├── TapActionEvent      # 点击
    ├── SwipeActionEvent    # 滑动
    ├── DragActionEvent     # 拖拽
    ├── InputTextActionEvent # 输入文本
    ├── KeyPressActionEvent # 按键
    └── StartAppEvent       # 启动应用

class ResultEvent(Event):
    """结果事件"""
    success: bool
    reason: str

8.2 事件流

Agent 执行
    ↓
发出事件 (write_event_to_stream)
    ↓
LogHandler 处理
    ↓
显示到控制台 / 记录轨迹

9. 扩展性

9.1 添加新的工具

from droidrun.tools import Tools

class CustomTools(Tools):
    @Tools.ui_action
    async def custom_action(self, param: str):
        """自定义操作"""
        # 实现自定义逻辑
        return "结果"

9.2 添加新的代理

from droidrun.agent import Agent

class CustomAgent(Agent):
    async def run(self):
        """自定义代理运行逻辑"""
        # 实现自定义逻辑
        pass

9.3 添加新的过滤器

from droidrun.tools.filters import TreeFilter

class CustomFilter(TreeFilter):
    def filter(self, tree, context):
        """自定义过滤逻辑"""
        # 实现自定义过滤
        return filtered_tree

四、实际运行可能存在的问题

Q:有些时候点击可以跳转,有些时候不行

文件: dynamic_analysis/droidrun/droidrun/tools/adb.py

修改内容

  1. AdbTools的init函数添加点击重试追踪器(第 76-77 行)
# Track tap attempts per element index for retry with different positions
  self._tap_attempt_tracker: Dict[int, int] = {}
  1. 新增_generate_tap_points() 方法(第 360-392 行)
  • 为每个元素生成 5 个候选点击位置:
    • 中心点
    • 左偏移 25%
    • 右偏移 25%
    • 上偏移 25%
    • 下偏移 25%
def _generate_tap_points(self, bounds: tuple) -> list:
        """Generate multiple tap points within element bounds for retry attempts.

        Returns points in order: center, then 4 offset positions (avoiding edges).
        """
        left, top, right, bottom = bounds
        width = right - left
        height = bottom - top

        # Center point
        cx, cy = (left + right) // 2, (top + bottom) // 2

        # Offset points (25% from center towards each direction, but stay within bounds)
        offset_x = max(1, width // 4)
        offset_y = max(1, height // 4)

        points = [
            (cx, cy),  # Center
            (cx - offset_x, cy),  # Left of center
            (cx + offset_x, cy),  # Right of center
            (cx, cy - offset_y),  # Above center
            (cx, cy + offset_y),  # Below center
        ]

        # Filter points to ensure they're within bounds
        valid_points = []
        for px, py in points:
            if left < px < right and top < py < bottom:
                valid_points.append((px, py))

        return valid_points if valid_points else [(cx, cy)]
  1. 修改 tap_on_index() 方法(第 394-465 行)

不建议使用 3 次连续点击(会被某些 App 识别为双击/三击)

  • 自动追踪重试次数:每次点击同一元素时,自动切换到下一个位置
    • 第 1 次点击 → 中心
    • 第 2 次点击 → 左偏移
    • 第 3 次点击 → 右偏移
    • …循环使用 5 个位置
  # 获取当前元素的重试次数并递增
  retry_attempt = self._tap_attempt_tracker.get(index, 0)
  self._tap_attempt_tracker[index] = retry_attempt + 1
  # 根据重试次数选择不同的点击位置
  tap_points = self._generate_tap_points(target_bounds)
  point_idx = retry_attempt % len(tap_points)
  x, y = tap_points[point_idx]
  # 单次点击
  await self.device.click(x, y)
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐