- 人工智能
- AI Agent
- 大模型
- GUI 自动化
- 移动开发
- 工具调用
【免费下载链接】ApkClaw
ApkClaw 是一款 AI 驱动的 Android 自动化应用:LLM Agent 通过自然语言指令操控你的手机。本文深入拆解它的两大核心机制——用无障碍服务"看"屏幕的节点遍历,和"点"屏幕的手势注入,带你搞懂 AI 是如何做到"所见即所点"的。
一、先认识它的"眼睛和手":无障碍服务
ApkClaw 所有屏幕感知与操作能力的底层,都是一个 Android 系统级组件——无障碍服务(AccessibilityService)。它原本是为视障用户设计的"旁听"通道:系统允许授权的应用读取当前界面的视图树、模拟触摸手势。
ApkClaw 把它变成了 AI 的"眼睛和手":
- 眼睛:读取当前窗口的 UI 节点树,AI 据此理解屏幕内容
- 手:通过系统手势接口注入点击、长按、滑动
安装后在首页点击权限卡片即可开启,应用会自动跳转到系统无障碍设置页(见 HomeActivity.kt)。服务的权限声明在 accessibility_service_config.xml 中,几个关键配置项:
| 配置项 | 作用 |
|---|---|
canPerformGestures="true" | 允许注入手势(点击/滑动)——"手"的开关 |
canRetrieveWindowContent="true" | 允许读取窗口视图树——"眼睛"的开关 |
canTakeScreenshot="true" | 允许截屏(Android 11+) |
flagIncludeNotImportantViews | 包含标记为"不重要"的视图,防止漏读元素 |
flagReportViewIds | 上报控件资源 ID,让 AI 能按 ID 定位元素 |
👀 正因为flagReportViewIds的存在,AI 不仅能认"文字",还能认开发者给控件起的"身份证"(资源 ID)。
二、如何"看"?节点遍历的三级漏斗
当 Agent 调用get_screen_info工具时,数据流是:GetScreenInfoTool.java → ClawAccessibilityService.java → 系统视图树。
1. 一行代码拿到整棵界面树
getRootInActiveWindow()返回当前活跃窗口的根节点。整棵树的每个节点(AccessibilityNodeInfo)都携带:类名、文本、资源 ID、屏幕坐标(bounds)、可点击/可滚动/可编辑等状态。
2. buildNodeTree:给界面树"瘦身"
一棵真实的界面树可能有上百个节点,直接塞给 LLM 既浪费 token 又干扰判断。buildNodeTree 用一套过滤规则做"瘦身":
- 跳过不可见节点:滚动容器中超出屏幕的元素直接略过(但仍递归其子节点,因为父节点不可见 ≠ 子节点不可见)
- 只保留"有意义"的节点:有文本 / 有内容描述 / 可交互(可点、可滚、可编辑、可勾选)/ 滑块 / 进度条
- 超长文本截断到 100 字符,类名简化为最后一段(
android.widget.TextView→TextView) - 每个节点用完即
recycle(),防止内存泄漏
最终输出类似这样一份 AI 友好的"屏幕说明书":
[TextView] text="设置" [clickable] bounds=[120,100][400,180] [Button] text="保存" [clickable] [focused] bounds=[500,200][800,300] [ProgressBar] [loading] bounds=[0,300][720,340]💡 调试时还有 getScreenTreeFull() 完整模式,输出所有节点的全部属性,用于对比排查 AI 行为异常。
3. 精确查找:按文本定位元素
除了整棵树,find_node_info工具(FindNodeInfoTool.java)可直接用可见文本搜索节点:内部调用 findNodesByText 做全树匹配,返回命中元素的坐标与属性,适合交互前快速"验货"。
三、如何"点"?手势注入与三级回退
1. 从坐标到手势:一条 Path 就是一次触摸
Android 的无障碍手势 API 把"一次触摸"抽象为一条路径(Path):
- 点击 = 一个点:
path.moveTo(x, y) - 滑动 = 一条线段:
moveTo(起点)+lineTo(终点) - 路径 + 持续时间打包成
StrokeDescription,再用GestureDescription封装 - 调用系统
dispatchGesture()注入真实触摸事件
performTap(默认 100ms)、performLongPress(默认 1000ms)、performSwipe都遵循同一套路——只是路径形状和持续时长不同。对应的 AI 工具分别是 TapTool.java、LongPressTool.java 和 SwipeTool.java。
2. 同步等待:"做完这一步,才做下一步"
dispatchGesture本质是异步的(通过回调通知完成)。如果 AI 点了"确定"就立刻去读屏幕,页面还没加载,必然读到旧界面。
dispatchGestureSync 用CountDownLatch把异步"掰"成同步:注入手势后阻塞等待回调(上限 5 秒),并严格区分onCompleted与onCancelled两种结局。这保证了 Agent 的"观察 → 行动 → 再观察"节奏不会出现竞态。
3. clickNode 的聪明之处:三级回退策略
拿到一个节点后,怎么点它才稳?clickNode 的答案是层层降级:
- 节点本身可点击→ 直接执行
ACTION_CLICK(模拟语义级点击,最可靠) - 不可点击→ 沿
getParent()向上逐级找,直到找到可点击的父节点(很多 UI 里文字本身不可点,点的是外层容器) - 整条链都不可点→ 兜底方案:取节点 bounds 中心坐标,走第 1 节的
performTap硬点
这个设计让"按语义点击"与"按坐标点击"无缝互补,是节点遍历与手势注入两条路线的汇合点。
四、从"看"到"点":Agent 闭环如何串起来
所有工具统一注册在 ToolRegistry.kt(手机模式注册 tap / long_press / swipe / scroll_to_find,通用模式注册 get_screen_info / find_node_info 等),再由 DefaultAgentService.kt 的循环驱动:构建上下文 → LLM 决策 → 执行工具 → 结果回传 → 循环直到finish(最多 40 轮)。
| 工具 | 职责 | 源码 |
|---|---|---|
get_screen_info | 输出瘦身后的界面树("看") | GetScreenInfoTool.java |
tap/long_press/swipe | 坐标注入("点") | mobile/ |
scroll_to_find | 滚动直到找到目标文本 | ScrollToFindTool.java |
find_node_info | 按文本查元素详情 | FindNodeInfoTool.java |
几个细节值得品味:
scroll_to_find是效率优化:它在屏幕中部安全区(30%~70% 高度)自动循环"滑动 → 搜索",并用前后两次界面树对比检测"已到顶/底",把 AI 从反复swipe + get_screen_info的低效循环中解放出来- 坐标越界拦截:所有坐标类工具先经 BaseTool.validateCoordinates 校验,越界直接报错并提示"先用 get_screen_info 获取有效坐标"
- 死循环检测:Agent 维护 4 轮滑动窗口
(screenHash, toolCall)指纹,若屏幕没变化却重复相同操作,系统会注入提示强制 AI 换思路(如按返回键或重新打开应用) - 系统弹窗兜底:当
getRootInActiveWindow()返回 null(遇到受保护的系统权限弹窗)时,get_screen_info返回特殊标记,Agent 随即截屏发给用户请其手动处理并终止任务(DefaultAgentService.kt)
五、快速上手:三步跑起来
- 安装并授权:安装 APK 后,在首页依次开启无障碍服务、通知、悬浮窗、电池白名单等权限卡片(权限总览见 HomeActivity.kt)
- 配置 LLM 与渠道:设置页填写 API Key 与模型名,并至少接入一个消息渠道(钉钉 / 飞书 / QQ / Discord / Telegram)
- 发条消息:例如"帮我打开设置,把蓝牙关掉"——Agent 就会自主执行
get_screen_info → swipe → tap → …… → finish
完整配置与构建指南见 README_CN.md。
六、已知限制:系统保护窗口
Android 的filterTouchesWhenObscured机制会对系统级保护窗口(如权限请求弹窗)同时屏蔽节点读取与手势注入——此时"眼睛"和"手"会一起失效。ApkClaw 的应对策略是:检测到root == null后不硬闯,而是截屏通知用户手动处理(这是目前 Android 平台层面的硬限制,任何无障碍方案都绕不过去)。
总结
ApkClaw 的设计精髓可以概括为一句话:把 Android 无障碍服务的"读树"与"注手势"两大原生能力,包装成 LLM 能理解的结构化观察与离散动作。
- "看":
getRootInActiveWindow()拿树 → 过滤瘦身 → 输出带坐标的文本化界面,省 token 又保信息量 - "点":
Path → GestureDescription → dispatchGesture注入真实触摸,同步等待确保时序;clickNode三级回退让语义点击与坐标点击互补 - 闭环:Agent 循环叠加坐标校验、死循环检测、系统弹窗兜底,让自动化既"大胆"又"克制"
理解这套机制后,你不仅能更好地使用 ApkClaw,也能把它当作学习"LLM + 设备自动化"架构的一个优秀范本。🚀
- 人工智能
- AI Agent
- 大模型
- GUI 自动化
- 移动开发
- 工具调用
【免费下载链接】ApkClaw
相关推荐
终极指南:如何利用Android无障碍服务自动跳过开屏广告——TouchHelperService原理与实战
终极指南:如何利用Android无障碍服务自动跳过开屏广告——TouchHelperService原理与实战 Android Touch Helper是一款基于
移动开发自动化enzyme 深入解析:`.parents([selector])` 遍历祖先节点
enzyme 深入解析: .parents selector 遍历祖先节点 导读 在 enzyme 测试 React 组件时,经常需要从树中的某个节点向上回溯,
测试前端Error Lens性能优化:解决VSCode卡顿和延迟的7个实用技巧
Error Lens性能优化:解决VSCode卡顿和延迟的7个实用技巧 Error Lens是一款提升VSCode错误和警告显示体验的强大扩展,但随着项目规模增
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考