用三个方式跑同一个 IME 问题后,我看到的 AI Agent 在 Android 上的真实缺口
这篇文章选用 Compose IME 问题进行测试,用三条路径各跑了一遍,分别是 Codex,使用 Android CLI 的 Codex 和使用 Test Android Apps 的 Codex,模型选用的是 gpt-5.4。
Android CLI 是 Google 新出的工具,解决的是 Android 开发里那些特别容易散掉的 glue。
Test Android Apps 是 OpenAI 官方的插件,一套面向 emulator QA 和性能取证的工作流 skill。
这篇文章不回答"谁能修掉这个 bug"。现代 AI Agent 修这类小问题不难,三条路径最后都能搞定。文章想问的是:面对同一个 Android 问题,每条路径削减了哪类判断成本,哪条是在把已有的 capacity 收得更整齐。
环境设置
2026 年 4 月 16 日,Google 在 Android Developers Blog 发布了新的 Android CLI。


三条路径都绑在同一个问题上:Compose 页面底部输入框和发送按钮,在键盘弹出后会不会被 IME 挤压,以及 agent 要怎么把这个问题查明、修掉、验证掉。
共享修复动作只有一个:
Modifier.navigationBarsPadding().imePadding()路径一:不用 Android CLI,问题一样能做成
没有 Android CLI,AI Agent 也完全可以把这个 IME 问题查出来、修掉、验证掉。但代价是 agent 需要自己把很多 Android 专有胶水拼起来。
查官方做法
baseline 这边没有走泛搜索,也没有找二手博客。直接在 Google Chrome 里打开官方 Android Developers 页面,确认 Compose 下 IME 和 system bar inset 的推荐写法。

这一步对 Android 开发者很熟悉。但对 agent 来说,仍然意味着额外判断:哪个页面才是官方当前建议,哪些结果已经过时,这份写法能不能直接落到当前项目里。
手工拼工程链路
链路是:Gradle 构建、adb install -r 安装、adb shell am start 启动、uiautomator dump 拉 UI 树。放回 macOS Terminal 里重跑,截了窗口图。

这张图对应的一个问题:工具是散的。
在这条路径里,agent 得自己处理 APK 路径、launcher activity、adb shell input 的时机,以及 uiautomator dump 拉回来的 XML 到底该怎么看。对 Android 开发者来说,这些操作本身不陌生;对 agent 来说,它们构成了一层额外的 Android 语义胶水。
坏状态怎么被证实
baseline 的坏状态原始截图。键盘弹出后,底部的 Send from bottom row 已经看不到了。

同时拉了一份 uiautomator XML。它能证明状态,但形状很典型:这次坏状态 XML 有 12,175 字节,节点 bounds 需要自己读、自己比、自己推断下一步。
这就是手工路径的核心特征:它没有缺能力,缺的是收束。
修复后也能被完整验证
修复后用同一条手工链路去跑。键盘依然打开,但按钮已经被抬到键盘上方,没有再被吃掉。

没有 Android CLI 能不能把这个问题做成?能。
但 Android 开发里 agent 在什么地方最容易浪费判断成本?手工路径也给出了很清楚的答案:官方知识检索是开放的,APK 和 Activity 语义要自己补,UI 状态证明依赖原始 XML,视觉结果到下一步动作之间没有现成的桥。传统工具链对 agent 来说太原始。
路径二:Android CLI 收缩的是 Android 专有胶水
Android CLI 处理的还是同一个坏页面、同一个修复动作。不同的是 agent 拿到知识、项目语义和验证反馈的方式。
docs fetch:把平台知识直接送回当前工作流
android docs search "imePadding compose textfield keyboard overlap"
android docs fetch kb://android/develop/ui/compose/system/keyboard-animations返回的是修复真正需要的 Compose 片段:.imePadding() 和 .navigationBarsPadding()。

对应的真实修复代码在 Android Studio 里核对过,底部输入区容器确实用了 navigationBarsPadding() 和 imePadding():

手工路径里,agent 要自己离开当前上下文去搜官方页面;Android CLI 里,官方平台知识可以直接回到同一个终端工作流。docs fetch 把"查对官方建议"收成更窄的输入面。
describe / run:把项目语义从猜测变成接口
实际拿到的输出很短,但已经够说明问题:
$ android describe --project_dir=artifacts/android-cli-flow/agent-cli-demo-visual
...
APK: /Users/baakarshan/Developer/labs/android-cli-demo/artifacts/android-cli-flow/agent-cli-demo-visual/app/build/outputs/apk/debug/app-debug.apk (Exists)
$ android run --device=emulator-5554 --apks=artifacts/android-cli-flow/agent-cli-demo-visual/app/build/outputs/apk/debug/app-debug.apk
App loaded: com.example.androidclivisualdemo
Multiple candidates found, picking the only launcher activity: com.example.androidclivisualdemo.MainActivity
Selected component: com.example.androidclivisualdemo.MainActivity
Installation completed successfully
Activation completed successfully手工路径里,agent 要自己知道 APK 在 app/build/outputs/... 的哪个位置、launcher activity 应该怎么拼、当前设备上要怎么启动。Android CLI 的区别在于,它把这几个动作前面的 Android 项目语义先讲清楚,再去执行设备动作。它没有替代 Gradle,但它补上了 Gradle 前后那层 agent 最容易猜错的 Android 语义面。
只要项目进入多模块、多变体,describe 这种"先问项目,再执行动作"的方式就比手工猜路径稳得多。
layout --diff:把原始 UI dump 压成 agent 真能吃的变化摘要
如果只比较"能不能抓 UI 树",baseline 早就能用 uiautomator dump。真正让我觉得 Android CLI 开始像 agent 接口的,是 layout --diff --pretty。
先让页面进入输入态,再让 Android CLI 比较变化前后的布局。它给的不是整坨 XML,而是更接近"下一步该怎么判断"的结果:哪些文本跑出屏幕了,哪些控件中心点变了,底部输入区移到了哪里。

手工路径里,拿到两份 XML,还要自己判断什么变化重要;Android CLI 里,先拿到一份面向变化的摘要,再决定要不要继续深挖。它没有替代原始信息源,但把原始信息塑形成了更适合下一轮推理的接口。
screen resolve:把视觉结果变成可执行动作
screen resolve 是这次最像"agent 工具"的部分。
实际跑到的结果:
android screen resolve --screenshot=.../05-cli-fixed-before-ime-annotated.png --string='tap #133 then inspect #139'返回:
tap 539 2031 then inspect 539 2242对应的真实窗口截图:

这一步的价值很硬,补上了手工路径里最别扭的一环:手工路径里,截图是视觉证据,坐标还得靠 XML bounds 再翻一层;Android CLI 里,截图上的视觉编号可以直接回到动作坐标。它把"我看见了一个控件"推进成了"我下一步知道该怎么点它"。
skills:提前堵掉错误分支
Android CLI 还有第二层价值,来自 skills。
选的是 edge-to-edge,因为它和前面的 IME 问题正好连得上,而且 android skills list 里确实能看到它。
没有再做"键盘有没有遮挡"的老问题,而是挑了一个更像真实 Android 开发里会踩到的坑:
- 页面已经用了
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) - 然后 agent 又直觉性地往内容容器上叠
navigationBarsPadding()和imePadding()
这个分支危险的地方在于,它非常像一种"部分成功":键盘不再遮住控件了,构建过了,运行也正常,但 UI 质量其实还不对。
先看两种实现的源码对照。手工直觉修法在 Scaffold(WindowInsets.safeDrawing) 内又叠了一层 .imePadding():

按 skill 调整后的版本则保留 padding(innerPadding),并显式消费这层 inset:

手工直觉修法虽然让输入区留在了键盘上方,但留下了一大块没必要的空白区:

按 skill 的建议改完后,这块多余空白被收掉了:

没有 skill,agent 很容易停在一个"看起来已经能用"的次优解上;有了 skill,这条错误分支会更早暴露出来。这种收益不体现在"能不能编译通过",而体现在"会不会为了同一块 UI 逻辑重新开第二轮探索"。
路径三:Test Android Apps 是 QA 和取证
还有一个 OpenAI 官方插件:Test Android Apps。
它是一组把 Android emulator QA 和性能取证流程打包好的官方 skill。
实际验证到的有两层:android-emulator-qa 和 android-performance。它们覆盖的是 Android CLI 主轴之外的另一块工作面。
android-emulator-qa:把 QA 链路标准化
复用同一个 IME 页面,严格按 QA skill 的顺序去跑:
./gradlew :app:installDebug
adb shell cmd package resolve-activity --brief com.example.androidclivisualdemo
adb exec-out uiautomator dump /dev/tty > 01-before-tap.xml
python3 ui_tree_summarize.py 01-before-tap.xml 01-before-tap-summary.txt
python3 ui_pick.py 01-before-tap.xml "Type a message"
adb shell input tap 245 2031
adb shell input text test_android_apps
adb exec-out screencap -p > 02-ime-open.png
adb logcat -d > logcat-after-flow.txt最值得看的不是"它也会 tap""它也会截图",因为这些能力 adb 本来就有。两步值得记:
ui_tree_summarize.py先把原始 XML 压成可读提纲ui_pick.py再把目标节点翻成具体坐标
这次它算出来的输入框坐标是 245 2031,后面直接把这个坐标喂给 adb shell input tap。

android-emulator-qa 把一条常见的 Android QA 链路标准化了:安装、resolve activity、启动、dump UI tree、总结 UI tree、从 UI tree 算坐标、截图、保存 logcat。它减少了测试取证里的临场 improvisation。
android-performance:证据采集
性能这层没有做成大而散的 profile,挑了一个很小的 flow:
- 启动 app
- reset
gfxinfo - 点输入框,打开 IME
- 输入一段文本
- 导出
gfxinfo - 如果 preflight 允许,再跑一次短时
simpleperf
这次 preflight 给了一个很关键的前提:安装包是 DEBUGGABLE。所以 simpleperf record --app ... 跑通了。
前提本身能回指到原始输出:
flags=[ DEBUGGABLE HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ]
pkgFlags=[ DEBUGGABLE HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ]最后拿到的证据包括:
gfxinfo.txtgfxinfo-framestats.txtperf.datasimpleperf-self.txtsimpleperf-children.txtsimpleperf.csv
对应的运行态截图:

simpleperf 的代表行:前排主要是 RenderThread、ART interpreter 和 libc。
Samples: 2695
14.92% RenderThread ... /apex/com.android.runtime/lib64/bionic/libc.so read
13.28% com.example.androidclivisualdemo ... /apex/com.android.art/lib64/libart.so void art::interpreter::ExecuteSwitchImplCpp<false>(...)
3.30% com.example.androidclivisualdemo ... /apex/com.android.art/lib64/libart.so bool art::interpreter::DoCall<false>(...)第一方 app 自己的 MainScreen 符号能看到,但占比很小:
2.34% 0.00% com.example.androidclivisualdemo ... com.example.androidclivisualdemo.ui.main.MainScreenKt.MainScreen
2.34% 0.00% com.example.androidclivisualdemo ... com.example.androidclivisualdemo.ui.main.MainScreenKt.MainScreen$lambda$7虽然 simpleperf 成功了,最热的 inclusive rows 仍然主要是 framework、ART interpreter 和 render thread;应用自有的行占很小一部分,比如 MainScreenKt.MainScreen。
android-performance 这次证明的不是"插件帮我找到了一个重大热点"。它把 Android 性能证据采集写成了一套可重复的步骤;在 debug/profileable 条件成立时,它能真的把 simpleperf 跑起来。但采到证据,不等于自动得到一个强性能结论。好的取证工作流,和好的性能分析结论,不是同一件事。
结论
如果目标是做一个很小的 Android 任务,而且项目结构简单、你自己也很熟工具链,那么传统工具完全够用。代价主要是 agent 需要自己多补几层 Android 语义。
Android CLI 把 agent 在 Android 上最容易散掉的几段路径收成更像接口的东西:平台知识检索、APK 和组件语义、UI 变化判断、截图到动作的过桥。
Test Android Apps 补的则是另一块:如何把 emulator QA 和性能取证组织成更少即兴发挥的流程。
如果目标开始变成"让 agent 更稳定地参与真实 Android 工程",我会优先上 Android CLI。它改善的是最核心的四段链路:查官方建议、理解项目产物、判断 UI 变化、把视觉结果变成下一步动作。
如果你已经进入"我需要反复在 emulator 上做验证、留证据、采一轮性能数据"的阶段,那再叠加 Test Android Apps 才有意义。它把 QA 和取证工作流做的更标准。