ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ApkClaw如何“看“与“点“屏幕?无障碍服务手势注入与节点遍历深度解析

ApkClaw如何“看“与“点“屏幕?无障碍服务手势注入与节点遍历深度解析
  • 人工智能
  • AI Agent
  • 大模型
  • GUI 自动化
  • 移动开发
  • 工具调用

【免费下载链接】ApkClaw

项目地址:https://gitcode.com/gh_mirrors/ap/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):

  1. 点击 = 一个点:path.moveTo(x, y)
  2. 滑动 = 一条线段:moveTo(起点)+lineTo(终点)
  3. 路径 + 持续时间打包成StrokeDescription,再用GestureDescription封装
  4. 调用系统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 的答案是层层降级:

  1. 节点本身可点击→ 直接执行ACTION_CLICK(模拟语义级点击,最可靠)
  2. 不可点击→ 沿getParent()向上逐级找,直到找到可点击的父节点(很多 UI 里文字本身不可点,点的是外层容器)
  3. 整条链都不可点→ 兜底方案:取节点 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)

五、快速上手:三步跑起来

  1. 安装并授权:安装 APK 后,在首页依次开启无障碍服务、通知、悬浮窗、电池白名单等权限卡片(权限总览见 HomeActivity.kt)
  2. 配置 LLM 与渠道:设置页填写 API Key 与模型名,并至少接入一个消息渠道(钉钉 / 飞书 / QQ / Discord / Telegram)
  3. 发条消息:例如"帮我打开设置,把蓝牙关掉"——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

项目地址:https://gitcode.com/gh_mirrors/ap/ApkClaw
点击查看免费下载
上一篇:如何5分钟上手Vidage:从零搭建全屏背景视频的快速开始教程
下一篇:PQ2_0 还是 PTQ1_0?Bonsai 2 两种 GGUF 量化格式横评,选型焦虑一次解决

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进