mcpField
A
android-bridge

android-bridge

github.com/briqt生产力037 次安装1 周前更新
代码仓库

关于 android-bridge

android-bridge 是一项通过命令行操作已连接 Android 设备的技能,面向需要检查界面并执行设备操作的 Agent。它采用感知、定位、操作、验证的循环,并提供适用于原生及 Flutter、Compose 界面的界面树读取方式。

主要功能

  1. 01读取界面文本与交互元素
  2. 02支持原生及 Flutter、Compose 界面检查
  3. 03执行点击、滑动、拖动和按键
  4. 04输入 ASCII 与 CJK 文本
  5. 05连接设备并推送或拉取文件

适用场景

  1. 01自动操作 Android 应用界面
  2. 02检查 Flutter 或 Compose 应用中的界面节点
  3. 03验证点击或输入后的界面变化
  4. 04在设备与本地之间传输文件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/briqt/android-bridge --skill android-bridge

安装后在 Claude Code 中输入/android-bridge确认已加载。

详细介绍

Android 设备控制

通过 android-bridge CLI 操作已连接的 Android 设备。每次交互都遵循 感知 → 定位 → 操作 → 验证 → 升级处理 循环。CLI 是唯一事实来源;本文档是面向 Agent 的手册。

每条命令都会在 stderr 的第一行打印 [profile: <name>](无论成功还是失败;未激活配置时默认为 [profile: default])。这是有意设计的:用它确认当前激活的设备。stdout 用于输出结果;stderr 用于输出诊断信息。

版本与功能检查

本手册要求 android-bridge 0.3.0+ 和 Python 3.11+(Pillow 13)。技能安装与 CLI 安装彼此独立。使用新近记录的命令前,请先检查二进制文件:

android-bridge --version
android-bridge doctor --json   # offline: no device connection or screen changes
# Upgrade CLI and skill:
pipx install --force git+https://github.com/briqt/android-bridge.git
npx skills add briqt/android-bridge -g -y

doctor 会报告实际的模块、Python 和 ADB 路径、配置来源、配置文件、root 模式及支持的功能。缺少 ADB 时,必须单独解决。

快速开始

pipx install git+https://github.com/briqt/android-bridge.git   # install CLI
android-bridge connect 192.168.1.100:5555                       # connect (WiFi ADB; remembered)
android-bridge snapshot                                          # read screen (native apps)
android-bridge dump                                              # read screen (Flutter/Compose apps)
android-bridge tap 540 1200                                      # act

命令速查

命令用法说明
snapshot[--json-out] [--out PATH]精选 UI 树(文本 + 交互元素)。--out 用于保存屏幕截图。
dump[--json-out] [--clickable] [--all] [--max N] [--out PATH]完整节点转储——适用于 Flutter/Compose 稀疏树。
screenshot[--out PATH]屏幕截图文件(--out)或输出到 stdout 的 base64 编码(仅限视觉模型)。
tapx y点击坐标。
long-tapx y长按。
swipex1 y1 x2 y2滑动 / 滚动。
dragx1 y1 x2 y2拖放。
type`[TEXT] [--stdin--file PATH]`
type-cjk`[TEXT] [--stdin--file PATH] [--at x y]`
clear-field[--at x y]清空当前获得焦点的字段。
select-all[--at x y]全选当前获得焦点的字段内容。
pressbuttonback|home|enter|power|volume_up|volume_down|tab|delete|recent(或任意 KEYCODE_*)。
shell"cmd" [--root] [--script f]在设备上运行;支持通过 stdin 管道传入内容。
pushlocal remote将文件/目录推送到设备。
pullremote [local]从设备拉取(默认:当前工作目录)。
devices—列出已连接的设备。
connect[serial]连接并记住设备。

全局选项:-p/--profile <name> 用于选择设备配置文件。

核心循环:感知 → 定位 → 操作 → 验证 → 升级处理

感知 — android-bridge snapshot(原生应用)或 android-bridge dump(Flutter/Compose)。

  • 输出为空、包含 ERROR 或显示 "null root node" → 转到故障决策树(屏幕关闭 / 离线)。
  • 输出正常 → 继续定位。

定位 — 在输出中找到目标。

  • 元素包含坐标 → 使用其中心点。
  • 文本出现但没有坐标(Flutter 稀疏树:文本节点未标记为 clickable)→ 运行 android-bridge dump,通过 text/content-desc 找到节点,并使用其 bounds 的中心点。
  • 目标不在屏幕上 → 滚动(swipe),或先关闭覆盖层(见“注意事项”)。
  • 多个元素的文本相同 → 根据 resource_id(使用 --json-out)或 bounds 的 y 轴顺序进行区分。

操作 — 根据操作类型分派。

  • 点击 → tap / long-tap。
  • 输入 → 先点击字段;ASCII 使用 type,CJK 使用 type-cjk --at x y。
  • 滚动/拖动 → swipe / drag。
  • 系统按键 → press。

验证 — 运行 android-bridge snapshot(或 dump),并与操作前的状态进行比较。

  • 明确说明预期变化:“按钮 X 现在显示 Y”“标题为 Z 的页面已出现”“字段中包含 V”。
  • 关于 Toast/Snackbar 等短暂反馈,请参阅“注意事项”——除非你是具备视觉能力的模型,否则不要依赖读取屏幕截图。

升级处理 — 验证失败时。

  • 元素可能在渲染后发生位移 → 重新感知 → 定位新坐标 → 重新操作。最多进行 2 轮。
  • 仍然失败 → 停止并报告:“点击 (x,y) 未产生变化;最近一次快照:”。不要无限重试。

故障决策树

Operation failed?
│
├─ snapshot/dump returns ERROR / "null root node"
│   ├─ Screen off/locked → android-bridge press KEYCODE_WAKEUP → retry
│   └─ Still null → android-bridge devices
│       ├─ No device → android-bridge connect <ip:5555> (WiFi) or plug USB
│       └─ "offline" → reconnect the configured endpoint; device input commands cannot repair an offline transport
│
├─ snapshot/dump reports "could not get idle state" / dump fails
│   └─ Flutter animation never idle → retry ≤3 times → still failing: android-bridge shell "uiautomator dump --compressed /sdcard/d.xml && cat /sdcard/d.xml" → still failing: blind-operate by geometry + verify post-action
│
├─ snapshot normal but Interactive Elements empty / target has no coordinates
│   └─ Flutter/Compose sparse tree → android-bridge dump → find node by text/content-desc → tap bounds center
│
├─ tap produces no state change (verify failed)
│   ├─ Element shifted → re-snapshot, re-tap with new coords (≤2 rounds)
│   ├─ Overlay/coach-mark intercepting → snapshot, find "我知道了/跳过/Got it", tap it, retry target
│   ├─ Non-clickable Flutter node → use dump bounds (above) or press back, re-enter
│   └─ 2 rounds still no change → stop, report diagnostics
│
├─ Screenshot all-black
│   ├─ FLAG_SECURE app → use snapshot/dump (a11y tree is unaffected), drop screenshot path
│   └─ Text-only model → never use --vision/screenshot as visual input; use text + dump + logcat
│
├─ Transient toast not in snapshot
│   └─ Most app feedback is Snackbar (in view tree) → re-snapshot promptly; else verify post-action state; see Pitfalls
│
├─ "unauthorized" / auth failed
│   └─ Do NOT bypass via ro.adb.secure/resetprop/adb_keys (unreliable on HyperOS/MIUI) → user taps "Allow" + "Always allow" on device → reconnect
│
├─ "ADB not found"
│   └─ Set "adb_path" in ~/.config/agent-skills/android-bridge/config.json, or sudo apt install adb
│
└─ Connection timeout / WiFi ADB dropped
    └─ android-bridge connect <ip:5555> → retry; "failed to connect" → on device: adb tcpip 5555 (via USB first)

视觉能力与感知策略

此技能同时适用于具备视觉能力的多模态模型和纯文本模型。选择感知路径前,请先判断自己属于哪一种。

纯文本模型(无法读取 PNG/图像字节)

你的唯一感知渠道是无障碍树文本。请使用此路径:

  1. android-bridge snapshot —— 默认方式。返回可见文本和带中心坐标的交互元素。这就是你的眼睛。
  2. 如果 snapshot 缺少元素(树内容稀疏、Flutter 应用,或你需要已被它丢弃的原始 content-desc/bounds),请使用 android-bridge dump —— 它会保留所有带标签的交互节点及其坐标。
  3. 最后的手段是原始 XML(即使 dump 也无法奏效时):
    android-bridge shell "uiautomator dump /sdcard/d.xml && cat /sdcard/d.xml"
    
    根据 bounds="[x1,y1][x2,y2]" 计算点击中心 → ((x1+x2)/2, (y1+y2)/2)。
  4. screenshot/snapshot --out 会生成你无法读取的图像文件。请勿调用它们进行感知。请参阅下文“截图仍有帮助的情况”。

默认路径(纯文本): 通过 snapshot 感知(必要时回退到 dump)→ 定位元素 → tap → 通过 snapshot 验证。绝不要依赖读取像素。

具备视觉能力的模型

snapshot --out shot.png 会同时提供无障碍树文本和可读取的截图。使用图像来消除文本树无法解决的歧义(没有 content-desc 的图标、画布渲染的内容、布局合理性检查)。坐标仍应以文本树为主要来源。

截图仍有帮助的情况(适用于任何模型)
  • 将文件交给人类进行视觉确认(screenshot --out x.png)。
  • 在多 Agent 设置中,将文件交给具备视觉能力的模型。
  • 存档故障状态的调试材料。

纯文本模型仍可为这些用途拍摄截图,只是无法自行分析截图。

读取屏幕

snapshot —— 精简视图(原生应用的默认方式)
=== Visible Text ===
  下午3:53
  晴 28℃

=== Interactive Elements ===
  [0] 时钟 (540,399) [click]
  [1] 设置 (921,806) [click]

每个元素的格式为:[index] name (cx, cy) [capabilities]。可直接使用中心坐标调用 tap。

snapshot --json-out 会为每个元素额外提供 class_name、resource_id 和 bounds,可用于区分文本相同的元素,或计算偏离中心的点击位置。

dump —— 完整节点转储(用于 Flutter/Compose/Canvas 应用)

如果节点带有标签但未标记为 clickable,snapshot 会将其丢弃(Flutter 会将文本放在 content-desc 中,而且经常跳过可点击标记)。dump 则会保留这些节点:

=== UI Dump ===
screen: 1080x2400  nodes: 44  shown: 44

  [0] 社区小医生,全城专家求我会诊 (540,395) bounds=[0,160][1080,275] class=TextView
  [1] 底部tab-我的 (940,1850) [click] bounds=[800,1800][1080,1900] class=ImageView

每个节点的格式为:[index] label (cx,cy) [flags] bounds=[x1,y1][x2,y2] class=X rid=Y。

[flags] 同时包含能力(click、long-click、checkable、scroll)和状态(checked = 开启的开关/复选框,focused = 输入框已获得焦点,selected = 当前标签页/项目,disabled = 灰显,blocked = 可点击的包装节点与相同范围内的禁用叶节点重叠)。这些状态标记可以让你在不使用截图的情况下判断开关的开/关状态,或哪个 EditText 处于活动状态:

[blocked] 至关重要:带有 [blocked] 的 [click] 节点表示 clickable=true 的包装节点与相同范围内 clickable=false 的节点重叠——即使显示了 [click],实际控件也无法点击。请勿尝试点击 [click,blocked] 节点;该按钮处于禁用状态(例如,“下一步”在必填字段填写完成前会一直呈灰色)。

  [3] 定时发布 (908,1505) [click,checkable,checked] bounds=[831,1454][985,1556] class=Switch
  [4] — (540,1479) [click,focused] bounds=[0,599][1080,2359] class=EditText rid=body
  • 触发规则:如果 snapshot 返回的交互元素少于约 3 个,或目标显示为没有标签的 ImageView/View,请切换到 dump。对于 Flutter/Compose/Canvas 应用(例如 com.bytedance.writer_assistant_flutter),默认使用 dump。
  • dump --clickable —— 仅显示交互节点(树较大时,输出更精简)。
  • dump --json-out —— 提供完整字段,包括未截断的 content_desc 和所有状态标记,便于程序处理。
  • dump --max N —— 限制显示的节点数量(默认值为 200;发生截断时,stderr 会发出警告)。

文本输入(ASCII 和 CJK)

type 仅支持 ASCII。输入 CJK/Unicode 字符请使用 type-cjk(通过 ADBKeyBoard):

android-bridge type-cjk "中文内容"              # field must already have focus
android-bridge type-cjk "中文内容" --at 638 779 # tap field first, then type

对于多行输入、以 - 开头的文本或敏感值,请避免使用位置参数,以免内容进入 shell 历史记录:

android-bridge type-cjk --file input.txt --at 638 779
cat input.txt | android-bridge type-cjk --stdin --at 638 779
android-bridge type-cjk -- "-leading-dash text"

输入命令会报告字符数,并在出错时隐藏输入内容。即使输入失败,也会恢复之前的输入法。文件/标准输入中的内容会原样保留,包括换行符;但输入到字段中的内容仍需通过 snapshot/dump 验证。

相关内容:

android-bridge clear-field                      # clear focused field
android-bridge clear-field --at 638 779         # tap first, then clear
android-bridge select-all                       # Ctrl+A in focused field
android-bridge select-all --at 638 779          # tap first, then select all

--at x y 会点击字段,将输入法切换为 ADBKeyBoard,再次点击以绑定 InputConnection,然后执行操作。有关再次点击为何重要,请参阅“常见问题”。

手动发送 ADBKeyBoard 广播(少见):

android-bridge shell "am broadcast -a ADB_INPUT_B64 --es msg '$(echo -n '文本' | base64)'"
android-bridge shell "am broadcast -a ADB_CLEAR_TEXT"
android-bridge shell "am broadcast -a ADB_INPUT_TEXT --es mcode '4096,29'"  # Ctrl+A
android-bridge shell "am broadcast -a ADB_INPUT_CODE --ei code 67"          # KEYCODE_DEL

滚动

android-bridge swipe 540 1500 540 500    # scroll down
android-bridge swipe 540 500 540 1500    # scroll up
android-bridge swipe 800 1200 200 1200   # scroll left
android-bridge swipe 200 1200 800 1200   # scroll right

常见问题

Flutter/Compose 无障碍树稀疏

症状:snapshot 显示很少或没有可交互元素,或目标文本出现但没有坐标;底部标签栏消失。 原因:Flutter 在 Dart 中处理手势,且不会在无障碍节点上设置 clickable;snapshot 只会将 clickable/scrollable 节点列为可交互元素。 解决方法:使用 android-bridge dump——保留所有带标签/可交互节点及其 bounds 和中心点。对于 Flutter/Compose/Canvas 应用,默认使用 dump。

动画界面上 uiautomator dump 失败

症状:snapshot/dump 返回 ERROR、“could not get idle state”或空结果。 原因:Flutter 持续渲染(动画/加载指示器)会使界面一直处于非空闲状态;uiautomator 无法获取稳定画面。 解决方法:重试 ≤3 次;然后运行 android-bridge shell "uiautomator dump --compressed /sdcard/d.xml && cat /sdcard/d.xml";若仍失败 → 根据已知几何位置盲操作,并通过下一次 snapshot 验证。

教程标记/引导浮层

症状:点击目标坐标无效;snapshot/dump 显示一个变暗的浮层,其上方只有一个“我知道了/知道了/跳过/Got it”按钮,真实界面在浮层下方。 原因:首次访问时的教程标记会拦截触摸操作。 解决方法:先关闭浮层(点击其按钮),重新感知以确认真实界面已显示,然后再对原目标执行操作。

底部标签栏不在无障碍树中

症状:可见的底部标签栏在 snapshot 中没有任何元素。 解决方法:先尝试 dump(标签文本通常位于 content-desc 中);如果仍未显示,则使用屏幕几何位置——标签位于屏幕底部(1080×2400 时 y≈2284),并按索引均匀分布(5 个标签 → x≈139、340、540、740、940);使用 tap <x> <y_bottom>。或者执行 press back 后重新进入(重新进入后有时会显示节点)。

纯文本模型没有视觉能力

症状:Read shot.png 没有返回有用信息;snapshot --out 截图对你没有提供任何信息。 解决方法:切勿将“查看截图”作为验证步骤。所有“查看屏幕”的操作都必须通过以下方式完成:snapshot → dump → 原始 uiautomator dump XML → android-bridge shell --root "logcat -d -t 50" → android-bridge shell "dumpsys window | grep -E 'mCurrentFocus|mFocusedApp'"。

FLAG_SECURE 导致截图为黑屏

症状:screenshot --out x.png 生成全黑/空白 PNG。 原因:应用设置了 FLAG_SECURE(银行应用、部分编辑器、DRM)。 解决方法:放弃截图路径;使用 snapshot/dump(无障碍树不受 FLAG_SECURE 影响)。

snapshot --out 可独立使用(无需 --vision)

--vision 已弃用。snapshot --out x.png 会自行截取并保存截图。(之前 --save 需要 --vision——此限制现已取消;--save 仍作为别名保留。)

screenshot 未使用 --out 时会将 base64 输出到 stdout

对于纯文本模型,这是一长串难以阅读的内容,会污染上下文。只有当你是能够处理 base64 的视觉模型,或将输出重定向到文件时,才使用不带 --out 的 screenshot。建议使用 screenshot --out x.png(文件会保存到磁盘,不会进入上下文)。

瞬时反馈(Toast / Snackbar)——仅文本检测的注意事项

不要假设 snapshot 无法捕获所有瞬时反馈。两者有所不同:

  • Snackbar(Material;Flutter SnackBar)位于应用视图树 / Flutter 语义中。它会在显示期间(约 4 秒)作为 text/content-desc 节点被 snapshot/dump 捕获。保存 / 提交后,应立即重新感知并查找新消息节点。
  • 原生 Android Toast 会渲染在单独的窗口层中,不会出现在 snapshot/dump 中。没有可靠的文本渠道可读取原生 Toast 文本:adb logcat 不会记录 Toast.show(),dumpsys window 会显示窗口令牌,但不包含其文本。

建议的成功检查方式(仅文本):

  1. 操作后立即执行 snapshot/dump——查找 Snackbar 消息节点和 / 或屏幕状态变化(导航、新元素、字段更新)。
  2. 如果确实没有瞬时文本,应改为验证操作后的状态:android-bridge shell "dumpsys activity top | grep mResumedActivity"(Activity 是否发生变化?)或检查下一个 snapshot 中是否有持久性变化。
  3. 如果必须读取原生 Toast 的确切文本,且没有视觉模型:尽力使用 --windows 转储(不可靠,且对时机敏感):android-bridge shell "uiautomator dump --windows /sdcard/d.xml && cat /sdcard/d.xml" | grep -iE 'toast|TransientNotification'。
  4. 如果 1-3 均失败,仅文本模型无法读取此反馈——请说明这一点,并请用户通过视觉确认,或将屏幕截图交给具备视觉能力的模型处理。不要假装自己看到了。
ADBKeyBoard 需要 InputConnection

只有当 ADBKeyBoard 与焦点字段之间存在活动的 InputConnection 时,ADBKeyBoard 广播才会生效。执行 ime set 后,必须重新点击输入字段以建立连接。type-cjk/clear-field/select-all 的 --at 选项会自动处理这一过程。 如果手动广播无声地失败,原因几乎总是:已切换 IME,但没有重新建立 InputConnection(未再次点击)。 非标准 InputConnection 的应用的后备方案(少见):

android-bridge shell --root "input keyevent KEYCODE_MOVE_END"
for i in $(seq 1 30); do android-bridge shell --root "input keyevent 67"; done   # KEYCODE_DEL x30
避免批量发送按键事件

不要发送一批互不相关的键码(例如 input keyevent 28 29 30 ...),试图碰运气——这可能导致意外启动应用 / 导航。只发送所需的特定按键事件。

input keycombination 与 ADBKeyBoard mcode

input keycombination 通过 InputManagerService 注入硬件级 KeyEvent,不会经过 IME 通道。对于聚焦的 EditText 上的文本选择(Ctrl+A、Ctrl+C),优先使用 ADBKeyBoard 的 mcode 广播(通过 InputConnection,可靠性更高):

# Prefer (IME channel):
android-bridge shell "am broadcast -a ADB_INPUT_TEXT --es mcode '4096,29'"
# Over (hardware injection — may not reach the editor):
android-bridge shell --root "input keycombination 113 29"

设备与配置文件设置

安装
pipx install git+https://github.com/briqt/android-bridge.git
设备连接
  1. android-bridge devices——检查是否有已连接的设备。
  2. 如果没有,请向用户询问设备 IP(WiFi ADB)或序列号(USB),然后执行 android-bridge connect <ip_or_serial>。
  3. 如果已通过 USB 连接或已记住该设备,执行 android-bridge connect(不带参数)即可自动检测。
  4. 连接后,设备会被记住。
唤醒设备

执行 UI 操作时,设备必须处于唤醒状态。如果 snapshot 失败并显示“null root node”:

android-bridge press KEYCODE_WAKEUP
多设备(配置)

使用 --profile/-p 指定目标设备:

android-bridge -p mi11 snapshot
android-bridge -p tablet tap 500 800

配置文件位于 ~/.config/agent-skills/android-bridge/config.json。选择优先级:--profile 标志 > active 字段。

UI/IME 命令支持在顶层或各配置文件中设置 root_mode: auto(默认)使用普通 shell 权限,仅在明确遭到权限拒绝后才重试 root;required 直接使用 root;never 禁止提权。 对于已知需要 root 才能输入的设备,请在其配置文件中设置 "root_mode": "required"。请勿将一台设备的电源或 root 规则应用于其他设备。

UI_ROOT_UNAVAILABLE、UI_NOT_IDLE、UI_DUMP_TIMEOUT、ADB_OFFLINE 和 ADB_UNAUTHORIZED 会保留最初的失败原因。UI 捕获会使用唯一的远程文件并在之后清理;不要将 XML 捕获失败解读为本地文件缺失。

ADB 不在 PATH 中

在配置中设置 adb_path:

{ "adb_path": "/usr/local/bin/adb" }
远程 ADB 服务(例如从 WSL 连接 Windows 主机)
{ "adb_host": "127.0.0.1", "adb_port": 5038 }

为所有 adb 命令添加 -H <host> -P <port>。

配置参考

运行时数据位于 ~/.config/agent-skills/android-bridge/config.json:

{
  "active": "mi11",
  "adb_path": "",
  "adb_host": "",
  "adb_port": 5037,
  "profiles": {
    "mi11": { "serial": "192.168.31.20:5555", "root_mode": "required" }
  },
  "device": { "serial": "" }
}
  • active — 未指定 --profile 时的默认配置文件。
  • adb_path / adb_host / adb_port — ADB 二进制文件和服务位置。
  • profiles — 已命名设备(serial = IP:port 或 USB 序列号)。
  • device.serial — 未激活任何配置文件时记住的设备(由 connect 写入)。

配置加载优先级:当前工作目录中的 .config.json(开发覆盖文件,已加入 gitignore)优先于标准位置;使用找到的第一个文件,不会合并。

每条命令都会在 stderr 的第一行输出 [profile: <name>](未激活任何配置文件时默认为 [profile: default])。这是有意设计的 — 可用它确认当前设备。

评论

支持 Markdown