☰
Auto.js安卓自动化脚本实战:从环境搭建到打包全流程指南
2026/10/2 9:38:55 网站建设 项目流程

最近在整理移动端自动化方案,手头这个基于auto.js的脚本项目我前前后后折腾了两个多月,中间换过好几版实现,踩了无数坑才把整套流程跑顺。这篇文章就是要把这个项目完整地拆开来讲,从为什么选auto.js开始,到环境怎么搭、脚本怎么写、打包怎么出,再到线上跑的时候出过的各种幺蛾子,全部给你捋一遍。

先交代一下背景。我手上有一批安卓设备,日常要做重复性的操作:批量打卡、自动化回归测试、定时清理数据、多App间的数据搬运。手动点来点去不仅浪费时间,还容易出错。后来我盯上了auto.js,它直接用JavaScript就能调用安卓系统的无障碍服务来模拟点击、滑动、输入,不需要root,对普通开发者非常友好。如果你也想给安卓手机写自动化脚本,或者你正准备做App自动化测试,这篇文章应该能帮你省下不少试错成本。

需要说明的是,我做的这些都是正向的效率工具场景,尽量避免去做任何违反平台规则、影响他人权益的事情,脚本的能量用在正经地方还挺好用的。

1. 内容整体设计与思路拆解

1.1 为什么偏偏选auto.js做自动化

安卓端做自动化方案其实不少,我当时列过一个对比清单,把几个主流路子的优劣势都过了一遍。

Appium走的是WebDriver协议,功能很全,跨平台、支持多语言,但它需要电脑连着手机跑,而且环境配置很重。对一批设备做轻量级操作,Appium显得大材小用,光是启动server、初始化session就得半天。

Tasker、MacroDroid这类工具强在“触发器+动作”的规则组合,但它追求的是零代码,逻辑稍微复杂一点就写不下去,你想做循环、异常重试、动态解析文本,基本没门。

auto.js的优势恰恰就在这里:它把无障碍服务的接口封装成了JS函数,你在手机上直接写脚本、直接跑,不需要电脑也能干活。而且它保留了完整的编程能力——循环、条件、正则、多线程、HTTP请求、文件读写,都能用。对我来说,它更像一个“带着触手”的Node.js环境,只不过触手长在了安卓系统上。

这个项目在设计之初定了几条原则:能用控件定位就坚决不用坐标,因为分辨率不同、屏幕不同,坐标一换设备就废;脚本必须带日志和异常捕获,不能静默死掉;所有涉及网络请求的部分要有超时和重试机制,移动网络不稳定是常态。这套思路后来帮我少踩了很多坑。

1.2 项目整体模块划分

整个项目我按功能分成四块:核心调度模块、交互模块、任务模块和工具模块。

核心调度模块负责脚本的启动入口、退出条件、主循环逻辑。交互模块封装了点击、滑动、输入、等待控件等基础操作,对外提供统一API。任务模块是具体业务逻辑的集合,比如“打开AppA完成打卡”“进入AppB抓取数据”“清理应用缓存”等等。工具模块处理一些杂活,比如日志写入、时间格式化、正则匹配、HTTP请求封装。

分层的好处是,一个任务挂了不影响其他任务,而且新增一个自动化任务只需要新写一个任务函数,不用动底层框架。项目到后期,脚本数量从最初3个增加到20多个,如果没有这套分层,维护成本会高到我不想再打开编辑器。

2. 环境搭建与工程配置细节

2.1 版本选择:Auto.js V4还是AutoX.js

这个我得单独拎出来说,因为版本选错真的会让你怀疑人生。

老牌的Auto.js在V4版本停更了,很多设备上会出现兼容性问题。后来社区里有人维护了AutoX.js这个分支,修复了大量机型兼容bug,API基本兼容老版本,目前我用下来是最稳的。如果你的项目是新建的,直接用它就好。

V4和AutoX.js在无障碍服务、悬浮窗、打包APK这些核心功能上都能用,但AutoX.js对Android 10以上系统适配更好,尤其是对分区存储、前台服务限制这些新特性做了处理。真要说用户量的话,AutoX.js相关的社群活跃度明显更高,遇到问题搜一下基本都能找到答案。

安装方式不复杂:手机浏览器直接搜AutoX.js,进到GitHub发布页下载对应APK装好就行。安装后先不要急着写脚本,把“无障碍服务”“悬浮窗”两个权限先给到,否则脚本跑不起来。

2.2 手机端预算环境和权限配置

拿到App之后,第一件事是把权限体系捋清楚。

无障碍服务是整个自动化的基础,脚本里的click、scroll、text这些操作全靠它。App引导页通常有一键配置,点击后会跳转到系统无障碍设置界面,找到对应的服务名,打开开关。这里有个常见的坑:部分国产ROM会在后台自动回收无障碍服务,比如MIUI、ColorOS,需要在电池优化里把AutoX.js设为“无限制”,否则脚本跑一半就失灵。

悬浮窗权限也很重要。脚本运行状态、日志预览、中止按钮都是通过悬浮窗展示的,没有悬浮窗权限,你连脚本什么时候卡死了都不知道。在系统设置里搜索“悬浮窗”,把AutoX.js允许开关打开即可。

如果你要执行input操作,还需要在系统设置里打开“USB调试(安全设置)”。这个选项藏得比较深,一般在开发者选项里,允许模拟点击开关。注意,部分手机默认隐藏了这个选项,得连续点击版本号进入开发者模式之后才能看到。

2.3 电脑端开发调试配置

很多人不知道,auto.js不只是手机端App,它还有一个桌面端配套工具,叫Visual Studio Code插件。插件安装好之后,手机和电脑连同一个Wi-Fi,用USB数据线连接或者扫描二维码就能互相通信。

我日常的流程是:电脑端写好脚本并保存,右键“发送到手机”,手机端AutoX.js自动接收,然后在电脑端开着日志面板,手机端跑一遍脚本,日志实时同步到电脑,调试效率比在手机上改代码高非常多。

工程目录我习惯这样组织:

project/ ├── core/ # 核心模块:入口、调度、配置 │ ├── main.js │ └── config.js ├── modules/ # 交互模块封装 │ ├── uiautomator.js │ ├── gesture.js │ └── network.js ├── tasks/ # 具体业务任务 │ ├── checkin.js │ ├── batch_clear.js │ └── ... ├── utils/ # 工具函数 │ ├── logger.js │ └── auto_retry.js └── project.json # 项目配置

project.json文件记录了包名、版本号和入口文件,打包APK的时候会用到。这个结构不是硬性要求,但强烈建议按功能分文件,不然一个脚本写到两三千行,后面找bug能把眼睛看花。

2.4 无障碍服务的判断与自启逻辑

脚本编写过程中,有一个基础问题是新手一定会碰到的:无障碍服务没开,脚本跑起来后什么反应都没有。所以在每个脚本开头,我都要做一次服务可用性判断。

auto.js里提供了一个方法:auto()。不带参数调用它,如果无障碍服务未开启,会直接跳转到设置页提醒用户开启;服务已开启就直接继续执行。另外还有auto.waitFor(),它不会跳设置页,而是等在那里,直到服务可用再往下走。

我通常会在每个任务入口加一个检查函数:

function ensureService() { if (!auto.service) { toast("请先开启无障碍服务"); auto.waitFor(10000); if (!auto.service) { exit(); } } }

这里有个细节值得注意:auto.service这个属性在无障碍服务异常断开后会变成null,所以不仅仅是首次启动要校验,每次任务开始前都应该校验一遍。我在批处理任务中就是这么干的,十个任务跑下来,即使中途有个任务把服务弄挂了,下一个任务开头也能自动检测出来并引导修复,不会整个批处理直接报废。

3. 核心脚本逻辑与API实战

3.1 坐标系、控件选择器与package id

写auto.js脚本,最先要搞明白的就是怎么“告诉手机点哪里”。坦白说,Auto.js的选择器系统是我见过所有安卓自动化框架里最顺手的一层封装。

基于控件定位的写法长这样:

// 等一个文本为“签到”的按钮出现,然后点击 text("签到").waitFor(); text("签到").click();

如果界面上有多个相同文本的控件,你可以用desc、className、packageName、id等条件组合精确锁定:

// 通过控件id定位 id("btn_confirm").findOne().click(); // 通过文本+类名组合定位 text("确认").className("android.widget.Button").findOne().click();

坐标定位就简单粗暴了,直接click(x, y)。但坐标定位的致命弱点是跟分辨率强相关,同一个坐标在不同设备上可能点的是完全不同的位置。我的原则是能控件定位就不坐标定位,控件定位实在找不到,才用坐标轮廓加找色辅助的方式兜底。

控件选择器还有一个极其实用的能力:findOnce、findAndWaitFor、exists等。初期写的时候多读两遍API文档,你会发现很多逻辑可以用一行代码替代原来的十行循环,代码少,bug就少。

3.2 高频交互API:点击、滑动、输入

点击类的API,除了click(),还有一个press(x, y, duration)可以做长按操作。长按在某些场景下非常有用,比如重命名桌面图标、触发App的隐藏菜单。

滑动类的高频API是swipe(x1, y1, x2, y2, duration)。duration是滑动持续时间,单位毫秒。很多人忽略了这个参数的实际意义,快速滑动和慢速滑动产生的效果差别很大。比如在长列表中定位某个条目,慢速滑动更容易被系统识别为“人工操作”,在某些有风控的App里不容易触发验证。

手势方面,gesture()可以模拟多段滑动路径和曲线手势,用来自定义解锁图案、绘制签名什么的。它接收一组表示时长的参数和一组坐标序列:

// 模拟向右滑动解锁 gesture(500, [200, 800], [900, 800]);

输入操作,把焦点放到输入框之后用setText()写入内容。注意,setText()需要目标输入框处于聚焦状态,否则可能无效。有些App自定义了输入框控件,setText()也未必完全兼容,这时候可以用剪贴板方式:setClip(text),然后长按输入框,点“粘贴”。这个方法土但极稳,我在跨App数据搬运场景中一直在用。

3.3 等待机制与空指针防护

写自动化脚本时最忌脚本跑得太快。界面还没加载完,脚本已经执行到点击了,控件找不到,程序直接抛异常。

auto.js里提供了sleep(ms)、waitFor()、findOne(timeout)等机制。我的习惯是,凡是要点击的控件,一律用findOne(timeout)而不是findOne(),因为findOne()是无限等待,一旦界面上永远不出这个控件,脚本就永久卡死了。类似地,waitFor()也一定要带上超时时间。

举一个实战例子:

// 设置超时10秒,超过就放弃本次操作并返回null,方便上层处理 var btn = text("立即领取").findOne(10000); if (btn) { btn.click(); } else { log("未找到领取按钮,跳过"); }

很多初学者写的脚本容易漏掉null判断,结果就是一遍一遍报错。其实加上一个空对象判断并不费事,但对脚本稳定性的提升是质的区别。

3.4 定时、线程与多任务调度

auto.js既然是一门编程语言环境,那就绕不开线程和定时任务。

要做一个每天定时执行的打卡脚本,只需要用setInterval或者写一个循环判断时间即可。如果是“每天执行一次”这种需求,我更推荐在脚本内部做日期判断,而不是依赖系统定时器反复跑:

while (true) { var today = new Date().toDateString(); if (lastRunDate != today && isInTimeWindow(new Date())) { doTask(); lastRunDate = today; } sleep(60 * 1000); // 每分钟检查一次 }

多线程场景主要用在“一个线程跑主流程,一个线程监控异常并重启服务”。Auto.js的threads.start()可以启动新线程,运行时给悬浮窗增加一个停止按钮也常常用线程实现。注意cell:操作UI的函数只能在UI线程调用,纯粹的后台任务可以放心用子线程,但如果子线程里要操作界面控件,需要使用ui.run()包一层。

3.5 图片识别与找色补位

控件定位虽然稳,但偶尔也会遇到控件信息被App隐藏的情况。比如一些App把按钮渲染成一张图片,或者使用Flutter、Unity绘制界面,控件树里拿不到有效节点。这时候就要靠图片识别来补位。

auto.js内置了images模块,支持findColor、findImage等找色找图函数。举个例子,我要在屏幕上找一个固定图案的“开始”按钮,可以先截屏,再用目标小图在截屏大图里匹配:

var big = images.captureScreen(); var small = images.read("/sdcard/start_btn.png"); var res = images.findImage(big, small, { threshold: 0.8, region: [0, 0, 1080, 1920] }); if (res) { click(res.x, res.y); }

这个方案需要提前准备好模板图片,而且不同分辨率的设备需要不同模板。我的做法是在配置里按设备型号保存模板路径,批处理时根据当前设备的型号自动选择对应图片。

4. 一个完整实操案例

4.1 需求描述与流程拆解

光讲理论不落地等于白写,我拿一个实际运行了很久的脚本当案例来讲一遍完整流程,这个任务叫“多App批量签到”。

需求是这样的:每天早上 9 点,我需要在五个App里分别完成签到,每个App的签到按钮位置不同、界面风格不同,有些是文字按钮,有些是图片按钮,还有一个是WebView渲染的按钮。

流程拆解下来长这样:

  1. 按顺序依次打开目标App
  2. 等待App首页加载完成
  3. 进入签到页面(入口可能是首页banner、个人中心、弹窗等)
  4. 找到签到按钮并点击
  5. 判断签到是否成功(根据弹窗文案、按钮状态变化等)
  6. 记录结果,关闭当前App,启动下一个

这六个步骤看起来简单,实际写下来两百多行代码跑不掉,但每一行都有明确的目的。

4.2 脚本运行实现与关键代码

整个脚本的入口比较简洁,核心逻辑集中在了“签到一次”这个函数里:

// 签到主函数,appName表示目标App的唯一标识 function signIn(appConfig) { var result = { app: appConfig.name, success: false, reason: "" }; try { app.launch(appConfig.packageName); // 等待首页关键控件出现 var homeReady = desc(appConfig.homeDesc).findOne(10000); if (!homeReady) { result.reason = "首页加载超时"; return result; } // 进入签到入口 clickByConfig(appConfig.entry); // 执行签到 clickByConfig(appConfig.signBtn); // 判断结果 sleep(1500); if (text(appConfig.successWord).findOne(2000)) { result.success = true; } else if (text(appConfig.failWord).findOne(2000)) { result.reason = appConfig.failWord; } else { result.reason = "未知结果"; } } catch (e) { result.reason = e.message; } return result; }

clickByConfig是我封装的一个函数,入参是一个配置对象,根据对象里的type字段决定走控件定位还是坐标定位。好处是新增一个App的时候,只需要在配置文件里加一段JSON,不需要改主逻辑。

App配置长这样:

[ { "name": "天气App", "packageName": "com.example.weather", "homeDesc": "首页", "entry": { "type": "text", "value": "我的" }, "signBtn": { "type": "desc", "value": "签到" }, "successWord": "已签到", "failWord": "签到失败" }, { "name": "社区App", "packageName": "com.example.community", "homeDesc": "首页", "entry": { "type": "text", "value": "积分中心" }, "signBtn": { "type": "id", "value": "sign_button" }, "successWord": "今日已签", "failWord": "网络异常" } ]

主流程里加了一个指数退避重试机制,连续两次失败就不再重试,避免因为网络问题反复撞同一个App导致被风控。这种细节在单机手动操作时根本不会被注意到,但放到自动化场景里,不加控制就会变成灾难。

4.3 运行结果与日志分析

每次签到任务跑完之后,脚本会把结果写到本地日志文件,同时通过请求的方式上报到我的电脑端,方便我第二天早上快速巡检。日志内容大概长这样:

[09:00:01] 开始批量签到任务 [09:00:03] 天气App 启动成功 [09:00:06] 天气App 签到成功 [09:00:08] 社区App 启动成功 [09:00:15] 社区App 签到失败: 网络异常,等待重试 [09:00:22] 社区App 重试成功 [09:00:25] 全部任务执行完毕,共签到4个,失败0个

这个脚本稳定跑了大半年,成功率基本在98%以上,剩余的2%基本都是网络波动或App改版导致控件路径变化。整体效果我很满意,至少每天给我省出了十五分钟的重复劳动时间。

5. 常见问题与排查技巧实录

5.1 无障碍服务频繁丢失

这个问题在国产ROM上格外明显。表现是脚本跑着跑着,toast弹一下“服务已断开”,然后就再也点不动了。

排查思路是先看电池优化白名单,把AutoX.js加入“无限制”。再看自启动管理,很多ROM会杀掉后台进程,不小心就把它识别成了“长期后台耗电应用”。如果你用的是小米系、华为系、OPPO系手机,建议把“后台弹出界面”“常驻通知”“锁屏后保持运行”这些开关全打开。

如果还不行,写一个守护线程监控auto.service的状态,一旦发现服务断开就尝试重新绑定。注意,重新绑定需要用户在设置界面手动开启,没法在脚本内直接打开。所以我的做法是弹窗提示用户,并且打开系统设置页引导用户操作。

5.2 控件定位找不到

这是初学者最崩溃的问题,辛辛苦苦写的text("签到").click(),运行时日志告诉你“null”。

大概率原因是App界面里确实有这个按钮,但它不是一个文本控件,而是一张图片,或者是画布绘制出来的。这时候可以先打开App自带的“布局分析”工具,查看屏幕的控件层级,确认目标节点的属性。如果节点里确实没有文本信息,再走找色或者模板匹配的路线。

另一个原因可能是页面还没渲染完成。我遇到过很多次,点进去之后控件树已经加载,但视觉上按钮还在渐变出现,这时候加一个sleep(800)或者用waitFor加超时就能解决。

5.3 脚本运行慢与资源占用高

如果脚本长时间循环执行,尤其是每轮都截屏、找色、找图,CPU和内存占用会直线上升,手机会发热。

优化手段主要有三个。第一,降低轮询频率,能500毫秒一查就不要100毫秒一查。第二,截屏之后及时回收图片对象,用recycle()释放内存,不然几百张截屏累积下来,内存直接爆掉。第三,用控件定位替代找图找色,控件定位的消耗远低于图像匹配。

5.4 打包APK后无法运行

脚本调试没问题,打包成独立APK反而出问题,这事情我遇到过好几次,典型的坑有两个。

第一个是打包时勾选了“使用系统签名”但设备没root,导致部分接口权限不足。第二个是打包APK没有声明需要的权限,比如网络权限、悬浮窗权限。在AutoX.js打包界面的权限列表里,把用到的权限全部勾上,再重新打一遍基本能解决。

另外,APK打包后的无障碍服务名称跟开发版不一样,需要重新在系统设置里开启一次。脚本内部可以做一个检测,如果服务没开,自动跳转设置页。

6. 脚本开发中的工程化心得

6.1 写脚本也要讲代码洁癖

很多人觉得脚本是一次性工程,能跑就行,不用管代码质量。我前半年的心态也一样,直到脚本量起来之后,改一个公共函数要打开几十个文件逐个同步,才开始后悔没有早点做抽象。

现在我的做法是:公共逻辑全部收拢到modules目录下,任务文件里只写业务步骤;所有关键节点都预留日志输出;配置文件独立于代码,修改配置不碰代码。这套规矩听起来极其朴素,但对长期维护的帮助是巨大的。哪怕这个脚本三个月没动,回头再看也能在五分钟内定位到需要修改的位置。

6.2 脚本的合规使用与边界意识

这部分我必须专门加一个小节。auto.js的能力很强,但能力越强,越要清楚边界在哪里。我个人的原则是:只用于提升自己的设备使用效率、个人自动化测试、数据整理等合规场景。不要用它去破解、抢购、批量注册、刷量,也不要干扰平台正常秩序。脚本开发是一种效率技能,技能本身没有原罪,但使用方法直接决定了它会带来正面价值还是负面后果。这一点在实际动手之前就想清楚,能帮你少走很多弯路。

6.3 后续可以怎么扩展

这个项目稳定运行后,我还在断断续续往里加东西。目前正在做的方向是接入更复杂的企业微信机器人通知,把脚本执行结果推送到群内;另一个方向是给脚本增加简单的Web管理后台,这样我不用打开手机也能查看所有设备上脚本的运行状态。

如果你也打算深入这块,建议优先把“可控性”和“可观测性”做起来,也就是随时能看日志、随时能停任务、出问题能快速定位。这两个点做扎实了,脚本数量再多也不会失控。

最后分享一个我一直在用的习惯:写脚本时主动给每个关键步骤打点,把日志、执行时间、执行结果写全。刚开始会觉得麻烦,但等脚本真的出了bug,你看着完整日志三分钟就能定位问题的时候,就会感谢当时那个多写了几行log的自己。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询