1. 为什么移动端 AI 自动化突然成了热门赛道
第一次看到 ARTEMIS 这个项目的时候,我正在折腾一个跨端测试的活儿——手动在十几台设备上重复点击同一个流程,点到手指发麻。当时脑子里就一个念头:能不能让 AI 直接看着屏幕、自己决定点哪儿?ARTEMIS 干的就是这件事,而且它是谷歌开源的,定位很明确——让 AI 助手像真人一样操作手机。
先说清楚它是什么。ARTEMIS 是一个运行在移动端的 AI 自动化框架,核心能力是把「屏幕理解」和「动作执行」这两件事串起来。传统自动化工具(比如基于控件 ID 的脚本)依赖开发者提前知道界面上有什么元素、叫什么名字;而 ARTEMIS 走的是另一条路——它让 AI 模型直接「看」屏幕截图,理解当前界面在表达什么,然后输出下一步该点哪里、输入什么、滑动多少。这就意味着,哪怕界面改版了、按钮换位置了、甚至是个从没见过的 App,它也能靠视觉理解去操作。
这件事解决的核心痛点有三个。第一是脆弱性:写死的脚本一改 UI 就崩,维护成本高得离谱。第二是泛化能力:不同 App、不同机型、不同分辨率,传统方案要分别适配,而视觉方案天然跨端。第三是门槛:不会写代码的人也能通过自然语言描述任务,让 AI 去执行。适合谁来参考?做移动端测试的工程师、想搞 RPA(机器人流程自动化)的开发者、研究 GUI Agent 的算法同学,以及单纯想让自己手机「自动干活」的折腾党。
我把它定位成一个「移动端的 GUI Agent 基础设施」——它不只是一个脚本工具,而是一套让 AI 与手机界面交互的完整链路。下面我会从设计思路、核心细节、实操落地到踩坑排查,把这条链路拆开讲透。
2. ARTEMIS 的整体设计与思路拆解
2.1 从「控件驱动」到「视觉驱动」的范式转变
要理解 ARTEMIS 的价值,得先明白传统移动端自动化的两条老路。第一条是UI Automator / Accessibility 路线,通过系统提供的无障碍服务读取控件树,拿到每个按钮的 ID、文本、坐标,然后精确点击。这条路稳定、快,但死穴是:控件树里没有的东西它就没辙,比如游戏画面、Canvas 绘制的界面、Flutter 自绘控件,读出来一片空白。第二条是坐标录制路线,把人的操作录成坐标序列回放,简单粗暴,但换个分辨率就全废。
ARTEMIS 选的是第三条路——视觉语言模型(VLM)驱动。它的逻辑是:截一张当前屏幕的图,把图喂给一个能理解界面的多模态模型,模型输出结构化的动作指令(比如{"action": "tap", "x": 0.5, "y": 0.8}),框架再把指令翻译成系统级的触摸事件执行。这个设计的精妙之处在于,它把「理解界面」这个最难的活儿外包给了 AI 模型,框架本身只负责「截图—推理—执行」的循环调度。
为什么这么设计?因为界面理解的本质是语义问题,不是坐标问题。人看到「登录」两个字就知道该点它,不需要知道它的控件 ID 是btn_login_001。VLM 恰好擅长这种语义理解,而且天然具备跨 App、跨语言的泛化能力。代价是推理有延迟、有成本,但对于「正确性优先于速度」的场景(比如自动化测试、辅助操作),这个 trade-off 完全划算。
2.2 框架的分层架构与职责边界
ARTEMIS 在工程上做了清晰的分层,我把它归纳成四层,从下往上说。
设备控制层是最底层,负责和 Android 系统打交道——截图、注入触摸事件、输入文本、按键。这一层通常依赖 ADB(Android Debug Bridge)或者设备端的无障碍服务。截图走的是screencap或者 MediaProjection,触摸注入走的是input tap或者更底层的sendevent。这一层的关键指标是延迟和稳定性,截图慢一拍,整个循环就卡。
感知层负责把原始截图处理成模型能吃的输入。这里有个容易被忽略的细节:手机截图分辨率动辄 1080×2400,直接喂给模型既慢又浪费 token。所以感知层通常会做缩放、压缩,甚至把坐标归一化到 0~1 的相对空间。归一化这一步特别重要,它让同一套动作指令能适配不同分辨率的设备——模型说「点屏幕宽度的 50%、高度的 80%」,框架自己换算成具体像素。
决策层是核心,调用 VLM 做推理。输入是当前截图加任务描述(比如「打开设置,把亮度调到最低」),输出是下一步动作。这里涉及一个关键设计:单步决策还是多步规划。ARTEMIS 走的是单步决策为主、配合历史上下文的路子——每一步都重新看屏幕、重新决策,而不是一次性规划十步然后盲执行。这样做的好处是容错性强,某一步点错了,下一步看到新界面能自我纠正;坏处是推理次数多、慢。对于需要长流程的任务,框架会维护一个动作历史,让模型知道「我已经做过什么」,避免原地打转。
编排层在最上面,负责任务的生命周期管理——任务怎么下发、循环怎么终止、异常怎么处理、日志怎么记录。这一层是给使用者直接接触的,也是决定「好不好用」的关键。
2.3 为什么选择「截图 + VLM」而不是「控件树 + LLM」
有人会问:既然要语义理解,为什么不直接读控件树的文本,喂给纯文本 LLM?这样又快又省。我实测过这条路线,问题在于控件树的信息损失太严重。一个界面在控件树里可能是一堆嵌套的 FrameLayout,文本散落在各个节点,层级关系混乱,而且很多视觉上重要的信息(图标、颜色、位置关系)根本不在文本里。模型拿到一堆残缺的文本,经常做出错误判断。
截图方案虽然重,但信息是完整的——所见即所得。模型看到的就是用户看到的,没有信息损失。这也是为什么 ARTEMIS 这类框架普遍选择视觉路线:在界面理解这件事上,视觉信息的保真度远高于控件树。当然,理想方案是两者结合,视觉为主、控件树为辅做坐标校正,这也是后续可以优化的方向。
3. 核心细节解析与实操要点
3.1 环境准备:从零搭起一套可运行的链路
动手之前,先把地基打好。ARTEMIS 跑起来需要三样东西:一台(或模拟器)Android 设备、一个能调用的 VLM 服务、以及框架本身的运行环境。
设备这块,我建议用Android 模拟器起步,原因很实际:真机调试时截图和触摸注入经常遇到权限问题,模拟器(比如 Android Studio 自带的 AVD)默认就开了 root,ADB 权限齐全,省去一堆麻烦。模拟器选 Android 11 以上的镜像,分辨率建议 1080×1920 这种标准比例,别用太奇葩的尺寸,否则坐标换算容易出偏差。如果你要用真机,记得在开发者选项里打开USB 调试,并且确认adb devices能识别到设备。
VLM 服务是另一个关键。ARTEMIS 本身不绑定特定模型,它需要一个能接受图像输入、返回结构化动作的推理后端。你可以用云端的多模态 API,也可以在本地跑一个量化后的小模型。本地跑的好处是数据不出设备、没有网络延迟,坏处是对硬件有要求。我的经验是,如果只是验证流程,先用云端 API 快速跑通;等逻辑稳定了,再考虑本地化降本。
框架运行环境通常是 Python。建议用Python 3.10 以上,建一个独立的虚拟环境,别和系统环境混在一起。依赖装完后,第一件事是跑通「截图—显示」这个最小闭环,确认框架能拿到屏幕画面,再往下走。
提示:模拟器首次启动后,建议先手动把系统语言、时区、输入法都设置好,避免自动化过程中弹出系统引导页打断流程。这个坑我踩过,脚本跑到一半卡在「欢迎使用」界面,排查半天才发现是模拟器没初始化完。
3.2 动作空间设计:AI 到底能对手机做哪些操作
ARTEMIS 的动作空间决定了它的能力边界。一个设计良好的动作集应该覆盖人操作手机的所有基本手势,同时保持足够精简,让模型容易学习和输出。核心动作大致有这么几类:
| 动作类型 | 参数 | 典型场景 | 实现要点 |
|---|---|---|---|
| tap(点击) | x, y 归一化坐标 | 点按钮、点图标 | 坐标用相对值,适配分辨率 |
| swipe(滑动) | 起点、终点、时长 | 翻页、滚动列表 | 时长影响惯性,短滑快、长滑慢 |
| text(输入) | 文本内容 | 填表单、搜索 | 需先聚焦输入框 |
| key(按键) | 键码 | 返回、Home、回车 | 系统级按键走 ADB |
| wait(等待) | 毫秒数 | 等加载、等动画 | 别写死,配合状态检测 |
| done(完成) | 无 | 任务结束信号 | 必须有,否则死循环 |
这里有个设计细节值得说:坐标为什么用归一化值。假设模型输出tap(540, 1600),这是绝对像素,换台 720×1280 的设备就点偏了。而输出tap(0.5, 0.83),框架自己乘以当前屏幕宽高,就能适配任意分辨率。归一化是跨设备泛化的基础,几乎所有视觉自动化框架都这么做。
另一个细节是wait 动作的处理。新手容易犯的错是让模型输出固定等待时间,比如「等 3 秒」。但网络快慢、设备性能都不一样,固定等待要么不够要么浪费。更好的做法是框架层面做状态轮询——截图对比,等界面稳定了再进入下一步。ARTEMIS 这类框架通常会在执行动作后自动加一个「稳定检测」,检测到画面连续两帧变化很小,才认为可以继续。
3.3 提示词工程:怎么让模型「看懂」任务并输出正确动作
这是整个框架里最考验经验的部分。模型再强,提示词写得烂,输出就是一堆废话。ARTEMIS 的提示词设计有几个关键要素,我拆开讲。
第一是角色和任务定义。要明确告诉模型「你是一个手机操作助手,你的任务是根据当前屏幕截图,决定下一步操作」。任务描述要具体,比如「打开微信,找到文件传输助手,发送一条消息说你好」,而不是「帮我用微信」。任务越具体,模型越不容易跑偏。
第二是动作格式约束。必须用严格的 JSON schema 约束输出,比如要求模型只能返回{"action": "...", "params": {...}}这种结构。不加约束的话,模型可能返回一大段自然语言解释,框架解析不了。我一般会在提示词里给两三个 few-shot 示例,展示「看到什么界面、输出什么动作」,模型模仿能力很强,给了例子准确率明显提升。
第三是坐标输出规范。要明确告诉模型坐标是归一化的 0~1 浮点数,并且给出参照,比如「屏幕左上角是 (0,0),右下角是 (1,1)」。有些模型对坐标系理解有偏差,会在提示词里反复强调。
第四是历史上下文。把前面几步的动作和结果拼进提示词,让模型知道进展。但要注意长度控制,历史太长会挤占图像 token 的空间,一般保留最近 3~5 步就够了。
注意:提示词里的动作空间描述要和框架实际支持的动作严格一致。我见过有人提示词里写了「长按」,但框架没实现 long_press,模型输出后直接报错。这种低级错误排查起来很费时间。
3.4 坐标换算与触摸注入的实操细节
模型输出归一化坐标后,框架要做两步转换才能真正点到屏幕上。第一步是乘以屏幕实际尺寸,得到像素坐标。第二步是考虑状态栏和导航栏的偏移。很多框架的截图是包含状态栏的,但触摸注入的坐标系原点可能从内容区开始,这中间差了一个状态栏高度,不处理的话所有点击都会偏上几十像素。
触摸注入的实现方式也有讲究。最简单的是调adb shell input tap x y,但这种方式每次都要起一个 shell 进程,延迟高,高频操作时明显卡顿。进阶方案是用sendevent直接写设备节点,速度快但需要 root 且不同设备的事件节点路径不一样,适配麻烦。ARTEMIS 这类框架通常会封装一个设备控制模块,优先用高效方式,失败时降级到 ADB。
滑动操作还有个坑:时长参数。input swipe x1 y1 x2 y2 duration里的 duration 单位是毫秒,太短会变成快速甩动(触发惯性滚动),太长会变成慢速拖拽(可能触发长按菜单)。翻页场景一般 300~500ms 比较合适,具体要看目标 App 的手势响应阈值。这个参数没有万能值,得针对具体场景调。
4. 实操过程与核心环节实现
4.1 最小可运行 Demo:让 AI 点开设置并进入关于手机
理论讲完,上真家伙。我以一个最经典的任务为例——「打开设置,进入关于手机页面」,把完整流程走一遍。选这个任务是因为它路径清晰、界面标准,适合验证链路是否通畅。
第一步,初始化设备连接。确认 ADB 能连上设备,拿到屏幕分辨率。这一步的代码逻辑大概是:
import subprocess def get_screen_size(): result = subprocess.run( ["adb", "shell", "wm", "size"], capture_output=True, text=True ) # 输出形如 Physical size: 1080x2400 size_str = result.stdout.strip().split(":")[-1].strip() width, height = map(int, size_str.split("x")) return width, height def take_screenshot(path="screen.png"): subprocess.run(["adb", "exec-out", "screencap", "-p"], stdout=open(path, "wb")) return pathwm size拿分辨率,screencap -p截图。注意exec-out比shell更适合传二进制数据,用shell会因为换行符转换把 PNG 搞坏,这是老坑了。
第二步,构造提示词并调用模型。把截图编码成 base64,拼进提示词,发给 VLM。提示词模板我一般这么写:
你是一个 Android 手机操作助手。当前任务:打开设置,进入"关于手机"页面。 请观察截图,输出下一步操作,严格按 JSON 格式返回: {"action": "tap|swipe|text|key|done", "params": {...}, "reason": "简短说明"} 坐标使用归一化值,左上角(0,0),右下角(1,1)。模型返回类似{"action": "tap", "params": {"x": 0.5, "y": 0.95}, "reason": "点击设置图标"}。
第三步,解析并执行动作。把归一化坐标换算成像素,调 ADB 注入:
def execute_action(action, width, height): if action["action"] == "tap": x = int(action["params"]["x"] * width) y = int(action["params"]["y"] * height) subprocess.run(["adb", "shell", "input", "tap", str(x), str(y)]) elif action["action"] == "swipe": # 类似换算,调 input swipe pass elif action["action"] == "done": return False # 结束循环 return True第四步,循环直到完成。每执行一步,等界面稳定,重新截图,再问模型。循环的终止条件是模型返回done,或者达到最大步数上限(防止死循环)。
实测下来,这个任务在标准 Android 模拟器上大概 4~6 步能完成:点设置图标 → 滑到底部 → 点关于手机 → 模型判断已到达 → 返回 done。整个过程十几秒,比人手操作慢,但胜在无人值守。
4.2 参数选择:截图质量、模型温度、循环上限怎么定
这几个参数直接决定框架的稳定性和成本,我一个个说。
截图质量:别用原图。1080×2400 的 PNG 动辄两三兆,base64 编码后更大,传给模型又慢又贵。我的做法是缩放到宽度 720 左右,转成 JPEG 质量 80,体积能压到 100KB 以内,模型识别准确率几乎不受影响。缩放用 Pillow 一行搞定,注意保持宽高比,否则坐标会变形。
模型温度(temperature):这个参数控制输出的随机性。自动化场景要的是稳定复现,温度必须调低,我一般设 0 或者 0.1。温度高了模型会「发挥创意」,同样的界面这次点左边下次点右边,流程没法复现。有些模型还支持 top_p 参数,配合温度一起调,原则就是让输出尽可能确定。
循环上限:必须设。我一般设 20~30 步。正常任务十几步内能完成,超过这个数基本说明模型卡住了(比如在两个界面之间反复横跳)。设上限是为了防止无限循环烧钱。同时要记录每一步的动作和截图,卡住的时候回看日志,一眼就能看出问题在哪。
等待策略:动作执行后不要立刻截图,给界面一点响应时间。我的经验值是先等 500ms,然后连续截两帧对比,如果差异小于阈值就认为稳定了,最多等 3 秒。这样既不会等太久,也不会截到动画中间态。
4.3 一个真实场景的完整拆解:自动填写表单
光点按钮不够过瘾,来个有实际价值的——自动填写一个登录表单。这个场景涉及点击、输入、滑动多种动作的组合,更能体现框架的能力。
任务描述:「在浏览器打开登录页,输入用户名 testuser,密码 123456,点击登录按钮」。
流程是这样的。模型先看到浏览器界面,识别出用户名输入框,输出tap聚焦。框架执行点击后,模型看到光标在输入框里,输出text动作,参数是testuser。这里有个细节:输入文本前必须确保输入框已聚焦,否则文字会丢到别处。框架层面可以在 text 动作前加一个隐式的 tap 确认,或者依赖模型自己判断。
输入完用户名,模型需要点密码框。但密码框可能被键盘遮挡,这时候模型要输出swipe先把页面往上滑,露出密码框再点。这就是视觉方案的优势——模型能看到键盘挡住了什么,主动规避。传统脚本如果没考虑键盘遮挡,直接点坐标就会点到键盘上。
密码输入有个安全考量:敏感信息不要走模型。用户名可以让模型看到,但密码最好由框架层面直接注入,不经过模型推理。ARTEMIS 这类框架一般支持「变量替换」——提示词里写占位符,执行时框架把真实密码填进去,模型全程看不到明文。这个设计在真实业务里很重要。
最后点登录按钮,模型识别按钮位置,输出 tap,任务完成返回 done。整个流程大概 8~10 步,涉及三种动作类型,跑通一次之后,换个表单改改任务描述就能复用,这就是视觉方案的泛化价值。
5. 常见问题与排查技巧实录
5.1 模型「看不懂」界面:识别错误的三大原因
跑自动化最常遇到的问题就是模型点错地方。排查下来,原因基本逃不出这三类。
第一类是截图质量问题。截图糊了、压缩过头了、或者截到了动画中间态,模型自然看不清。排查方法很简单:把传给模型的图存下来,自己看一眼。如果人眼都觉得模糊,模型肯定也不行。解决就是提高截图质量或者加稳定检测。
第二类是提示词歧义。比如任务说「点击确认」,但界面上有「确认」和「确认支付」两个按钮,模型可能选错。这时候要把任务描述写得更具体,或者把界面上的候选元素在提示词里列出来让模型选。我一般会在提示词里加一句「如果存在多个相似元素,选择最符合任务语义的那个」。
第三类是模型能力边界。小模型对复杂界面的理解确实有限,图标密集、文字小的界面容易出错。这种情况要么换更强的模型,要么在框架层面做界面简化——比如把截图放大、或者用 OCR 先把文字提取出来辅助模型。
5.2 动作执行了但没反应:触摸注入的排查链路
有时候模型决策是对的,但点了没反应。这条排查链路我走过很多次,按顺序查基本能定位。
先确认坐标是否算对。把归一化坐标乘以分辨率,看看算出来的像素点是不是在目标元素上。常见错误是忘了处理状态栏偏移,导致所有点击都偏上。可以在截图里把计算出的坐标画个红点,直观确认。
再确认触摸事件是否真的注入了。adb shell input tap有时候会因为设备忙或者权限问题静默失败。可以在执行后立刻截图对比,看界面有没有变化。没变化就是注入失败,检查 ADB 连接和设备状态。
还要确认目标元素是否可点击。有些界面元素看着是按钮,实际被一层透明遮罩盖住了,点击事件被遮罩拦截。这种情况模型看不出来,需要人工分析界面层级。解决方法是先点掉遮罩,或者用更底层的注入方式绕过。
5.3 死循环与卡死:如何让框架「知道该停」
死循环是自动化框架的经典病。表现是模型在两个界面之间反复横跳,或者对着同一个按钮点了又点。根因通常是任务没有明确的完成信号,或者模型陷入了局部最优。
我的解法有三层。第一层是步数硬上限,超过就强制终止并报警,这是兜底。第二层是状态去重,记录最近几步的截图哈希,如果发现重复出现同一画面超过两次,说明卡住了,主动中断。第三层是提示词里强化完成条件,明确告诉模型「当看到 XX 界面时,返回 done」,给它一个清晰的终点。
还有个隐蔽的坑:界面加载慢导致的误判。模型截图时页面还在加载,看到的是空白或者骨架屏,于是做出错误决策。解决就是前面说的稳定检测,等画面连续两帧一致再决策。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查方向 | 解决手段 |
|---|---|---|---|
| 点击位置偏移 | 坐标未归一化/状态栏偏移 | 截图标注坐标点 | 统一归一化,扣除状态栏 |
| 模型输出无法解析 | 提示词约束不足 | 看模型原始返回 | 加 JSON schema 和示例 |
| 输入文字丢失 | 输入框未聚焦 | 检查点击是否生效 | text 前强制 tap 聚焦 |
| 流程卡死 | 无完成信号/死循环 | 看动作历史日志 | 设步数上限+状态去重 |
| 截图模糊 | 压缩过度/动画中 | 人眼看截图 | 提高质量+稳定检测 |
| 触摸无反应 | 注入失败/被遮挡 | 执行后对比截图 | 检查 ADB/绕过遮罩 |
| 推理太慢 | 图太大/模型太重 | 测单步耗时 | 缩图+换轻量模型 |
| 敏感信息泄露 | 密码走模型 | 检查提示词 | 变量替换,模型不碰明文 |
5.5 几个我踩过的坑和独家心得
说几个文档里不会写、但实际会遇到的坑。
坑一:模拟器的输入法。默认输入法可能弹广告或者联想词,干扰输入。我一般会先adb shell ime切换到一个干净的输入法,或者直接用 ADB 的input text绕过输入法。注意input text不支持中文和特殊字符,中文得用其他方式注入。
坑二:屏幕常亮。自动化跑一半屏幕熄了,截图全黑。跑之前记得adb shell svc power stayon true,保持屏幕常亮。
坑三:模型对深色模式的误判。深色模式下界面元素对比度低,模型识别率下降。如果目标 App 支持,尽量在浅色模式下跑。
坑四:多语言界面。模型对英文界面识别最稳,中文次之,小语种容易翻车。如果目标 App 有多语言,切到英文能提升稳定性。
心得方面,我最大的体会是:别指望一次跑通长流程。正确的做法是把长任务拆成短任务,每段单独验证,稳定了再串起来。比如「登录并下单」拆成「登录」「搜索商品」「加购物车」「结算」四段,每段跑通再组合。这样出问题能快速定位是哪一段的锅。
另外,日志要记全。每一步的截图、模型输入、模型输出、执行结果,全部落盘。出问题的时候,回看日志比重新跑一遍高效得多。我一般按时间戳建目录,一次运行一个目录,方便对比。
6. 这套框架还能怎么扩展
跑通基础流程后,我琢磨了几个扩展方向,也分享给想深入的同学。
方向一是多设备并行。ARTEMIS 的单设备链路跑通后,可以横向扩展到多台设备同时跑,做批量测试或者批量操作。关键是把设备控制层抽象成接口,每台设备一个实例,任务调度器统一分发。难点在于资源竞争和结果聚合,但工程上不难。
方向二是结合控件树做坐标校正。纯视觉方案偶尔会点偏,如果能同时拿到控件树,用控件的位置去校正模型的坐标输出,准确率能上一个台阶。这是视觉和传统方案的融合,也是工业级自动化常见的做法。
方向三是本地化模型降本。云端 API 按 token 计费,跑量大了成本可观。把量化后的小模型部署到本地,虽然单步准确率可能略降,但配合重试机制和坐标校正,整体可用性未必差,而且数据不出设备,隐私性更好。
方向四是任务编排与断点续跑。真实业务里任务可能很长,中途失败要能从中断处继续,而不是从头再来。这需要框架记录每一步的状态,支持快照和恢复。做测试的同学应该对这个需求很有共鸣。
我个人在实际操作中的体会是,移动端 AI 自动化现在还在早期,框架能力、模型能力、工程稳定性都有很大提升空间。ARTEMIS 这类开源项目的价值,不在于它现在多完美,而在于它把「AI 操作手机」这条链路跑通了,给了大家一个可参考、可修改、可扩展的起点。真正落地到业务里,还需要根据具体场景做大量适配和优化,但方向是清晰的——让机器替人干重复的活儿,这件事在移动端终于有了靠谱的技术路径。