【Mobile Agent——Droidrun】MacOS+Android配置、使用指南
MacOS+Android的Droidrun配置、使用指南
·
文章目录
本博客参照官方文档:https://docs.droidrun.ai/v3/quickstart
一、前提需要
系统要求:
- Python 3.10 或更高版本
- Android Debug Bridge (adb) 已安装并配置好
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允许版本降级) |
关键补充命令(排障用)
- 查找zipalign/apksigner路径:
find ~/Library/Android/sdk -name zipalign/find ~/Library/Android/sdk -name apksigner - 检查设备连接:
adb devices - 临时添加工具到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
修改内容
- AdbTools的
init函数添加点击重试追踪器(第 76-77 行)
# Track tap attempts per element index for retry with different positions
self._tap_attempt_tracker: Dict[int, int] = {}
- 新增
_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)]
- 修改 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)
更多推荐

所有评论(0)