1. 为什么移动端自动化突然又火了
移动端自动化这件事,其实不是新话题。从早期的按键精灵、Appium,到后来的无障碍服务脚本,再到近两年冒出来的各种 AI Agent 方案,本质上都在解决同一个问题:怎么让机器替人去点手机。但过去这些方案有个共同的尴尬——写脚本的人得先把界面元素摸清楚,id 是什么、坐标在哪、什么时候弹窗、什么时候加载慢,全得靠人工预判。一旦 App 改版,脚本集体报废。
ARTEMIS 是谷歌开源的一个移动端 AI 自动化框架,它想做的事情跟传统方案不太一样:让 AI 像人一样"看着屏幕"去操作手机。你不需要告诉它按钮的 id 是btn_submit,你只需要说"帮我把购物车里最便宜的那件删掉",它自己截图、自己理解界面、自己决定点哪里。这背后靠的是视觉语言模型加上一套任务规划与执行循环,再通过 MCP(Model Context Protocol)把模型能力和设备控制打通。
我第一次看到这个项目的时候,第一反应是"这不就是把 Playwright 那套思路搬到手机上,再塞个大模型进去吗"。用下来发现没那么简单,移动端的坑比 Web 多得多:分辨率碎片化、系统弹窗乱入、输入法遮挡、后台被杀、网络抖动导致的白屏。ARTEMIS 的价值不在于它发明了什么新算法,而在于它把这些脏活累活封装成了一套相对可复用的框架,让开发者不用从零造轮子。
这篇文章适合三类人看:一是想给自己的 App 做自动化测试但被元素定位折磨过的测试工程师;二是想折腾 AI Agent 落地到真实设备的开发者;三是纯粹好奇"AI 操作手机"到底靠不靠谱的技术爱好者。我会从整体设计思路讲起,然后拆核心细节、给可复现的实操流程,最后把我踩过的坑和排查经验整理出来。全程按我自己的理解讲,不照搬官方文档。
2. ARTEMIS 的整体设计与思路拆解
2.1 它到底解决了传统自动化的哪个死结
传统移动端自动化最大的死结是脆弱性。Appium 靠 accessibility id 或 xpath 定位元素,UI Automator 靠控件树,这些方案的前提是"界面结构稳定且可被程序化描述"。但现实是,很多 App 的控件树一团糟,WebView 里的内容根本拿不到,游戏和 Flutter 自绘界面更是完全没辙。你写 100 行定位代码,可能只为了点一个"确认"按钮,改版一次全白干。
ARTEMIS 换了个思路:不依赖控件树,直接看像素。它把屏幕截图喂给视觉模型,模型输出"下一步该点哪个坐标、该输入什么文字、该滑动还是等待"。这就绕开了控件树这个不稳定层。代价是每次决策都要跑一次模型推理,慢,而且贵。所以框架里必然要做缓存、要做动作复用、要做失败重试,这些才是工程上的真功夫。
提示:视觉方案不是银弹。纯视觉在文字密集、控件密集的界面上容易点错,实际项目里通常是"视觉为主、控件树为辅"的混合策略,ARTEMIS 也留了接入原生定位的扩展点。
2.2 MCP 在这里扮演什么角色
MCP 是 Anthropic 提出的一套协议,用来让模型和外部工具之间用统一的方式对话。你可以把它理解成"AI 世界的 USB 接口"——不管后面接的是文件系统、数据库还是手机,只要实现了 MCP,模型就能用同一套语法去调用。
ARTEMIS 把设备操作抽象成一组 MCP 工具:screenshot(截图)、tap(点击)、swipe(滑动)、input_text(输入)、launch_app(启动应用)等等。模型不需要知道 ADB 命令怎么写,它只需要说"我要点 (540, 1200)",框架负责翻译成底层指令。这个分层的好处是可替换:今天用 A 模型,明天换 B 模型,只要它支持 MCP 工具调用,框架代码几乎不用动。
我实测下来,MCP 这套设计最大的价值是让"模型决策"和"设备执行"彻底解耦。调试的时候我可以单独 mock 掉设备层,只测模型的规划逻辑;也可以单独录一套动作序列,不跑模型直接回放,用来验证执行层稳不稳。
2.3 任务规划与执行循环是怎么转起来的
ARTEMIS 的核心是一个"感知—规划—执行—验证"的闭环,跟人操作手机的过程几乎一样:
- 感知:截当前屏幕,必要时抓一份控件树做补充。
- 规划:把任务目标、历史动作、当前屏幕一起丢给模型,让它输出下一步动作。
- 执行:把动作翻译成设备指令发下去。
- 验证:再截一次屏,判断上一步是否生效,没生效就重试或换策略。
这个循环听起来简单,但每一步都有讲究。比如"验证"这一步,如果只是简单对比截图是否变化,遇到加载动画就会误判;如果等固定时长,又浪费时间。ARTEMIS 里用的是"关键区域变化检测 + 超时兜底"的组合,具体阈值需要根据 App 的响应速度调。
2.4 为什么选它而不是自己撸一套
自己撸一套不是不行,但你会重复造这些轮子:设备连接管理、截图压缩与传输、动作去重、失败重试、多步任务的上下文管理、模型输出的结构化解析。这些每一块单独看都不难,合在一起就是几千行胶水代码,而且极容易出边界 bug。ARTEMIS 把这些沉淀下来了,你拿到手就能跑通一个最小闭环,把精力放在业务逻辑上。
当然它也不是没有代价。框架抽象层多了,出问题时排查链路变长;模型推理有延迟,对实时性要求高的场景不友好;还有就是成本,跑一次复杂任务可能要几十次模型调用,token 消耗得算清楚。
3. 核心细节解析与实操要点
3.1 环境准备:别一上来就装最新版
环境这块我踩过坑,先说结论:Python 版本别用最新的,ADB 版本要跟设备系统匹配。ARTEMIS 依赖的一些库对 Python 3.12 支持还不完善,我建议用 3.10 或 3.11。ADB 的话,如果你测的是 Android 13 以上的设备,用 platform-tools 34 以上的版本,否则偶尔会出现截图返回空的问题。
安装步骤大致是这样:
# 建议用虚拟环境,别污染全局 python -m venv artemis-env source artemis-env/bin/activate # Windows 用 artemis-env\Scripts\activate # 装框架本体 pip install artemis-agent # 验证 ADB 能连上设备 adb devicesadb devices这一步必须能看到你的设备,状态是device而不是unauthorized。如果是unauthorized,去手机上确认 USB 调试授权弹窗,勾选"始终允许"。
注意:模拟器和真机行为差异很大。模拟器截图快、分辨率固定,但缺少真实的重力感应、来电打断、低电量弹窗等场景。做正式测试一定要上真机,模拟器只适合开发阶段快速验证逻辑。
3.2 模型接入:本地还是云端,这是个成本问题
ARTEMIS 本身不绑定具体模型,它通过 MCP 对接任意支持工具调用的视觉模型。这里有个关键决策:用云端大模型还是本地小模型。
云端模型的优势是理解能力强,复杂界面、模糊指令都能处理,缺点是每次调用都要联网、有延迟、按 token 计费。本地模型(比如量化后的 7B 视觉模型)响应快、免费、数据不出本地,但理解能力弱,遇到复杂界面容易点错。
我的建议是分场景:开发调试阶段用云端模型,因为你需要快速迭代 prompt 和任务逻辑,理解能力强的模型能帮你省很多调试时间;批量回归测试用本地模型,因为这时候任务路径已经固定,模型只需要做简单的界面识别,本地模型够用且成本可控。
配置模型的时候,API Key 千万别硬编码在代码里,用环境变量:
export ARTEMIS_MODEL_API_KEY="your_key_here" export ARTEMIS_MODEL_ENDPOINT="https://your-endpoint/v1"3.3 截图与坐标:分辨率适配是第一个大坑
移动端自动化最烦的就是分辨率。你的模型可能是在 1080x1920 的截图上训练的,但实际设备是 1440x3200,模型输出的坐标直接就对不上。ARTEMIS 的处理方式是统一缩放到一个基准分辨率再喂给模型,执行时再映射回真实坐标。
这个映射逻辑看起来简单,但有两个细节容易翻车:
- 缩放比例要按短边算,不是长边。因为不同设备的宽高比不一样,按长边缩放会导致短边溢出。
- 状态栏和导航栏的高度要单独处理。有些设备截图包含状态栏,有些不包含,坐标映射时如果不减掉这部分偏移,点击位置会整体下移。
我一般会在配置里显式声明设备的实际分辨率和截图分辨率,让框架自己算映射,而不是依赖自动检测。自动检测在刘海屏、挖孔屏上经常出错。
3.4 动作执行:点击不是发个坐标就完事
很多人以为点击就是adb shell input tap x y,实际上真机上这么干经常点不中。原因有几个:一是坐标映射有偏差,二是点击事件被系统拦截,三是目标控件有防误触逻辑需要按下和抬起之间有间隔。
ARTEMIS 在动作层做了几件事:点击前先确认目标区域,点击后等待一小段时间再截图验证,如果没生效就微调坐标重试。这个"微调重试"的逻辑很关键,我见过太多脚本因为差几个像素点不中按钮而卡死。
滑动也是类似。adb shell input swipe的默认滑动速度很快,很多列表会把它识别成 fling(快速滑动),直接滑过头。ARTEMIS 允许你指定滑动时长,我一般设 300 到 500 毫秒,模拟人的正常滑动速度。
3.5 任务描述怎么写才不容易翻车
这是我觉得最容易被低估的一环。任务描述(prompt)写得好不好,直接决定成功率。新手常犯的错是写得太抽象,比如"帮我处理一下订单",模型根本不知道你要处理什么。
好的任务描述应该包含:目标、约束、成功判据。举个例子:
- 差:"把购物车清空"
- 好:"打开购物车页面,删除所有商品。如果弹出确认框,点确认。全部删除后,页面应该显示'购物车是空的'。如果某件商品删除失败,跳过它继续删下一件。"
后面这种写法给了模型明确的边界和终止条件,成功率能高一大截。我实测下来,任务描述里加上"如果...就..."这类分支说明,能显著减少模型在异常情况下的乱点。
4. 实操过程与核心环节实现
4.1 从零跑通第一个任务
假设我们要做一个最简单的任务:打开设置,进入"关于手机",读出系统版本号。这个任务足够简单,适合验证环境是否正常。
第一步,确认设备连接和截图能力:
from artemis import Device, Agent device = Device.connect() # 自动连接第一个可用设备 shot = device.screenshot() print(shot.size) # 打印截图分辨率,确认不是空图如果这里报错,八成是 ADB 没连上或者权限问题,先回去检查adb devices。
第二步,初始化 Agent 并绑定设备:
agent = Agent( device=device, model="your-vision-model", max_steps=15, # 最多执行 15 步,防止死循环 step_timeout=10, # 单步超时 10 秒 )max_steps这个参数一定要设。我见过没设上限的脚本,模型陷入"点一下、没反应、再点一下"的循环,跑了一晚上把电量耗光。
第三步,下发任务:
result = agent.run("打开设置应用,找到'关于手机'并进入,读出系统版本号") print(result.output)跑通之后你会看到框架打印每一步的动作和截图,这个日志对调试非常有用。
4.2 参数计算:超时和重试怎么定
超时和重试这两个参数没有标准答案,得根据你的 App 响应速度来定。我的经验公式是:
- 单步超时 = 页面平均加载时间 × 3 + 2 秒。比如你的 App 平均 1.5 秒加载完,那单步超时设 6.5 秒左右。乘 3 是留足网络抖动和模型推理的时间。
- 重试次数 = 3。第一次失败可能是偶发,第二次失败可能是坐标偏,第三次还失败基本就是逻辑问题,再重试也是浪费。
这里有个反直觉的点:重试不是越多越好。重试次数多了,遇到真正的逻辑错误时,脚本会卡在那里反复试,反而拖慢整体进度。我一般设 3 次,超过就跳过当前步骤,记录到失败日志里,最后统一分析。
4.3 实操现场:一次真实的调试记录
我拿一个电商 App 做测试,任务是"搜索'蓝牙耳机',把价格从低到高排序,把第一个商品加入购物车"。第一次跑,卡在排序那一步。
看日志发现,模型点了"排序"按钮,但弹出的排序选项里,它点的是"价格从高到低"而不是"从低到高"。原因是这两个选项的文字太像,截图缩放到基准分辨率后,模型看不太清。
解决办法有两个:一是提高截图分辨率,不缩放那么多;二是在任务描述里明确说"选择'价格从低到高'这个选项,注意不要选成'从高到低'"。我两个都做了,第二次跑就过了。
这个案例说明,视觉方案的准确率跟截图质量强相关。如果你的任务涉及大量文字识别,别为了省 token 把截图压得太狠。
4.4 批量执行与结果收集
单个任务跑通之后,下一步是批量跑。ARTEMIS 支持把任务定义成列表,循环执行:
tasks = [ "搜索'蓝牙耳机'并加入购物车第一个商品", "搜索'手机壳'并加入购物车第一个商品", "搜索'充电器'并加入购物车第一个商品", ] results = [] for task in tasks: try: r = agent.run(task) results.append({"task": task, "status": "success", "output": r.output}) except Exception as e: results.append({"task": task, "status": "failed", "error": str(e)}) finally: agent.reset() # 每个任务之间重置状态,避免上下文污染agent.reset()这一步很重要。如果不重置,上一个任务的截图和动作历史会带到下一个任务里,模型可能被误导。我一开始没加这个,结果第二个任务老是重复第一个任务的动作,排查了半天才发现是上下文没清。
5. 常见问题与排查技巧实录
5.1 截图黑屏或返回空图
这是最高频的问题。原因通常有三个:设备锁屏了、App 有防截屏保护、ADB 截图权限不足。
排查顺序:先看设备是不是亮屏解锁状态;然后换一个普通 App 试试截图,如果普通 App 能截、目标 App 不能,那就是防截屏保护,这种情况视觉方案基本无解,只能退回控件树方案;如果所有 App 都截不了,检查 ADB 版本和 USB 连接模式。
提示:部分金融类、视频类 App 会主动屏蔽截屏。做这类 App 的自动化,视觉方案走不通,得换思路。
5.2 模型一直点同一个地方
这是典型的"死循环"。模型点了按钮,但界面没变化(可能按钮是禁用的,或者点击没生效),模型看截图没变,以为没点到,就再点一次,无限循环。
解决办法是在框架层加"动作去重":如果连续 N 步的动作和截图都高度相似,就强制中断,抛出异常。ARTEMIS 里可以配置loop_detection参数。另外,任务描述里加上"如果点击后界面没有变化,尝试其他方式或报告失败",也能缓解。
5.3 输入文字乱码或输入不进去
中文输入是老大难。adb shell input text对中文支持很差,经常乱码。ARTEMIS 的处理方式是优先调用设备上的输入法接口,实在不行才退回 ADB。
如果遇到输入不进去,先确认输入框是不是已经获得焦点。有些 App 的搜索框需要先点一下才能输入,直接发文字会被忽略。另外,输入完成后记得收起键盘,否则键盘会遮挡后续要点击的按钮。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决思路 |
|---|---|---|---|
| 截图黑屏 | 锁屏/防截屏/权限 | 换 App 测试 | 解锁或换方案 |
| 点击无反应 | 坐标偏移/控件禁用 | 对比截图坐标 | 校准映射或等待 |
| 模型死循环 | 界面无变化 | 看动作日志 | 开循环检测 |
| 中文输入乱码 | ADB 限制 | 换输入方式 | 用输入法接口 |
| 任务中途失败 | 弹窗打断 | 看失败时截图 | 加弹窗处理逻辑 |
| 执行速度慢 | 模型推理延迟 | 看耗时分布 | 换本地模型或缓存 |
5.5 几个我踩过的坑
坑一:以为模型越强越好。我一开始用最大的模型,结果每次调用要好几秒,一个任务跑下来几分钟。后来换成中等模型,准确率只降了一点点,速度翻倍。模型选型要平衡准确率和延迟,不是越大越好。
坑二:忽略系统弹窗。Android 系统会时不时弹权限请求、更新提示、低电量警告。这些弹窗会打断任务流。我的做法是在每步执行前先检测有没有系统弹窗,有就先关掉。ARTEMIS 里可以注册一个"弹窗处理器"。
坑三:任务描述写太长。我一度以为描述越详细越好,结果写了一大段,模型反而抓不住重点。后来发现,任务描述控制在 3 到 5 句话最合适,把目标、关键约束、成功判据说清楚就行,细节交给模型自己判断。
坑四:没做失败截图留存。早期调试时任务失败了,我只看到报错信息,不知道当时屏幕长什么样,排查全靠猜。后来强制要求每次失败都存一张截图,排查效率提升明显。
6. 这套框架适合用在哪些场景
6.1 自动化测试:回归测试的利器
最直接的应用就是 App 回归测试。传统 UI 自动化测试写起来累、维护成本高,ARTEMIS 这种视觉方案的优势在于对界面改版不敏感。按钮从左边挪到右边,只要文字没变,模型还是能找到。这对迭代快的团队很友好。
但要注意,自动化测试追求的是稳定和可重复,而视觉模型有随机性。同一个任务跑十次,可能有九次成功一次失败。所以用 ARTEMIS 做测试,一定要配合重试机制和结果校验,不能只看单次结果。
6.2 数据采集:批量操作的场景
比如批量给商品点赞、批量关注账号、批量导出数据这类重复性操作,ARTEMIS 能省不少人力。但这类场景要特别注意频率控制,操作太快容易被风控。我一般会在动作之间加随机延迟,模拟人的操作节奏。
6.3 无障碍辅助:帮特殊人群操作手机
这个场景我觉得挺有意义。视障用户操作手机本来就困难,如果有一个 AI 能理解他们的语音指令并代为操作,体验会好很多。ARTEMIS 的视觉方案天然适合这种场景,因为它不依赖用户看懂界面。
6.4 不适合的场景
说句实在话,ARTEMIS 不是万能的。对实时性要求高的场景(比如抢购、秒杀)不适合,模型推理的延迟摆在那里。涉及敏感操作的场景(比如支付、转账)要慎用,模型点错一下可能就是真金白银的损失。界面变化极快的场景(比如游戏)也不适合,截图还没分析完,界面已经变了。
7. 我对这套框架的真实看法
用了一段时间,我的整体判断是:ARTEMIS 代表了一个正确的方向,但离"开箱即用"还有距离。它把移动端 AI 自动化的骨架搭好了,但血肉还得你自己填。任务描述怎么写、超时怎么设、弹窗怎么处理,这些都得根据具体 App 调。
它最大的价值不是让你少写代码,而是让你换一种思路做自动化——从"精确定位每个元素"变成"描述目标让 AI 自己想办法"。这个思路转变本身,比框架里的任何一行代码都重要。
如果你打算上手,我的建议是:先拿一个简单的 App 跑通最小闭环,别一上来就挑战复杂业务;把日志和失败截图做扎实,这是排查问题的命根子;模型选型先云端后本地,开发阶段别在成本上抠门,效率优先。
最后分享一个小技巧:把常用的任务片段做成模板库。比如"关闭所有弹窗"、"滚动到列表底部"、"等待页面加载完成"这些高频操作,写成可复用的函数,新任务直接拼装。这样能省下大量重复调试的时间,也是我从一次次踩坑里总结出来的最实用的一招。