【Mobile Agent——Droidrun】MacOS+Android配置、使用指南

文章目录

本博客参照官方文档: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

bash 复制代码
# 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 代理进行多步骤规划
    • 适合复杂任务
    • 会生成任务分解和执行计划
python 复制代码
# 代码位置: 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 代理视觉
python 复制代码
# 代码位置: 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 连接
python 复制代码
# 使用 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 交互方式详解
触摸操作
python 复制代码
# 通过索引点击元素
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
文本输入
python 复制代码
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
系统操作
python 复制代码
# 返回键
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
状态获取
python 复制代码
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 连接
python 复制代码
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 交互方式
python 复制代码
# 点击元素
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 解析
python 复制代码
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 主要命令

bash 复制代码
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 使用示例

bash 复制代码
# 基本使用
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 配置文件结构

yaml 复制代码
# 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 事件类型

python 复制代码
# 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 添加新的工具

python 复制代码
from droidrun.tools import Tools

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

9.2 添加新的代理

python 复制代码
from droidrun.agent import Agent

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

9.3 添加新的过滤器

python 复制代码
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 行)
python 复制代码
# 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%
python 复制代码
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 个位置
python 复制代码
  # 获取当前元素的重试次数并递增
  retry_attempt = self._tap_attempt_tracker.get(index, 0)
  self._tap_attempt_tracker[index] = retry_attempt + 1
python 复制代码
  # 根据重试次数选择不同的点击位置
  tap_points = self._generate_tap_points(target_bounds)
  point_idx = retry_attempt % len(tap_points)
  x, y = tap_points[point_idx]
python 复制代码
  # 单次点击
  await self.device.click(x, y)
相关推荐
恋猫de小郭16 小时前
你是不是觉得 R8 很讨厌,但 Android 为什么选择 R8 ?也许你对 R8 还不够了解
android·前端·flutter
城东米粉儿18 小时前
Android Glide 笔记
android
城东米粉儿18 小时前
Android TheRouter 笔记
android
城东米粉儿1 天前
Android AIDL 笔记
android
城东米粉儿1 天前
Android 进程间传递大数据 笔记
android
城东米粉儿1 天前
Android KMP 笔记
android
冬奇Lab1 天前
WMS核心机制:窗口管理与层级控制深度解析
android·源码阅读
松仔log1 天前
JetPack——Paging
android·rxjava
城东米粉儿1 天前
Android Kotlin DSL 笔记
android