Auto.js 简介与避坑指南

📅 2026/8/23 10:28:06
Auto.js 简介与避坑指南
Auto.js 是在 Android 平台上运行 JavaScript 脚本的工具在自动化测试等方面非常有优势。本文探讨该项目的发展现状为开发者应选择何种开源实现提供参考探讨如何配置代码补全提高 Auto.js 脚本编写的效率本文也讨论 Auto.js 的局限性结合笔者开发过程中遇到的问题分析其原因给出可行的解决方案。Auto.js 的发展现状Auto.js 由 hyb1996 于 2017/01/27 初次发布, 于 2020/03/13 因故停止维护, 最终版本名称为4.1.1 Alpha2, 构建版本号为461。原作者停止维护后Auto.js 分为付费版和免费开源版两种路线继续发展付费版原作者删除了开源的所有 Auto.js 代码宣布继续开发闭源的收费版本但目前所有版本都已经终止开发不再维护。免费开源版开源社区出现了多个 fork 版本形成各自维护的局面例如 TonyJiangWJ、kkevsekk1、 SuperMonster003、wilinz、aiselp 等作者均参与了其 fork 版本的维护。基于种种考量本文均使用 aiselp 作者开发的 AutoX v7.2.1。配置代码补全改善编程体验Auto.js 的工具链不完善。很多开发者编写 Auto.js 时只是依靠 VSCode 上的一个插件实现代码补全、项目管理、连接 ADB 设备等功能。然而这个插件的自动补全功能有一些 bug 严重影响使用ADB 连接也时常不稳定出现断线问题。可以用更好的方式实现代码补全。AutoX 的 GitHub 仓库中以 npm 包的形式提供了开发模板每开启一个新项目时用npm install安装依赖npm 会自动下载autox-v6-api包包内含有相关的.d.ts类型文件以提供自动补全。Auto.js 的代码补全在 AI 编程时代更加重要因为 Auto.js 是小众平台并没有大量的语料去训练 AI中国大陆以外的地区甚至搜不到相关讨论。这导致 AI 在一些细节方面表现得不尽如人意。有了代码补全人工改错的成本也会小一些。至于“推送项目至手机”等功能笔者目前自行编写批处理脚本实现暂未找到更好的方法。Auto.js 的局限性与避坑指南Auto.js 使用 JS 作为脚本的语言方便了无数开发者编写简单的脚本。但 Auto.js v6 选用 Rhino 作为在 Android 系统上执行 JS 的中间层这引入了一些问题例如 Rhino 并不完全支持 ECMA 规范以及多线程支持的问题。有限的const支持以下代码在 Auto.js 的实际运行结果为控制台打印了 10 次数字0for(leti0;i10;i){constindexi;log(index);}把const改为let问题解决控制台打印了 0~9 的数字for(leti0;i10;i){letindexi;log(index);}在 JS 规范中每次循环都会创建一个新的块级作用域const定义的常量只是在块级作用域中不能重新赋值而不会影响下一次循环。但是 Auto.js 的运行结果显然不符合规范。问题的来源Auto.js 使用 Rhino 运行 JS 脚本Rhino 的 JS 实现并不完全符合规范Rhino 中const具有函数作用域而不是标准规定的块级作用域block-scope。2017 年就有开发者在 Rhino 的 GitHub 仓库中提交了关于这个问题的 issue但 Rhino 至今没有修复完成。问题的普遍性如果一个 JS 变量在作用域内不会被重新赋值很多现代 IDE 和编辑器默认推荐使用const来定义这样的变量不论在语义上它是否是“常量”。例如ESLint 默认启用prefer-const规则VSCode 中 for 循环的自动补全也默认使用 const如果开发者使用了这些工具就很容易中招。问题的隐蔽性该问题不会产生报错。假设开发者想用 for 循环遍历一个ListView中的所有控件出现问题时实际却是每次循环都引用第一个控件但没有任何报错或异常信息可以参考非常难以排查。如果需要在 Auto.js v6 环境下编写 for 循环就需要注意避免使用const定义类似的变量。进一步地在任何需要利用块级作用域特性的地方都应该谨慎使用const定义变量。多线程处理中的问题JavaScript 采用事件循环机制运行如果使用单一线程运行脚本当脚本执行耗时操作时整个线程将阻塞而无法处理其他事件而 Auto.js v6 采用的 Rhino 引擎也不支持现代 JS 的异步函数。所以需要使用 Auto.js 提供的“多线程”能力实际上是一种模拟实现JS 不支持多线程将耗时操作放在单独的线程执行。Auto.js 的文档写道通过threads.start()启动的所有线程会在脚本被强制停止时自动停止。然而对于有些多线程脚本在 Auto.js 应用或打包的应用中点击“停止脚本运行”后仍然会残留一些线程持续执行。下面是一个能够复现该问题的最小示例代码运行这段代码一段时间后尝试停止脚本程序仍然无限循环打印着“正在执行带 try 的线程轮询”functionloopWithWhileTrue():void{threads.start(function(){while(true){try{log(正在执行带 try 的线程轮询);sleep(3000);}catch(e){log(捕获到异常${e});}}});}functionmain():void{loopWithWhileTrue();}main();去掉 try…catch 块把脚本改写如下该问题不再出现functionloopWithWhileTrue():void{threads.start(function(){while(true){log(正在执行线程轮询);sleep(3000);}});}functionmain():void{loopWithWhileTrue();}main();查看 Auto.js 开源代码库笔者了解到在脚本强制停止时每个线程会利用多种方式确保其已被停止其中一种方式就是在线程内部抛出中止异常InterruptedException这样的异常经过序列化得到JavaException: com.stardust.autojs.runtime.exception.ScriptInterruptedException: null。上述出现问题的代码中使用了 try…catch 结构中止异常被 catch 块捕获到没有继续向上抛出这可能使得全局的异常处理器无法感知这个 Exception也无法控制线程停止运行。按照这个猜想修改脚本对于所有未知的异常全部继续向外层抛出functionloopWithWhileTrue():void{threads.start(function(){while(true){try{log(正在执行带 try 的线程轮询);sleep(3000);}catch(e){log(捕获到异常${e});throwe;// 向外层抛出未知异常}}});}functionmain():void{loopWithWhileTrue();}main();经测试强制终止脚本时所有线程都可以正常停止。这个问题带来的经验是开发者要更加规范地使用catch机制。对于已知的异常如 HTTP 错误等可以在内层直接处理而不必向外抛出对于未知异常最好向外继续抛出直至变为 uncaught exception。这既能在早期发现问题也避免了中断异常在线程内被catch处理而不能发挥作用的情况。如果因为种种原因必须要把大部分异常都 catch 到而不向外抛出那么可以利用中断异常序列化后包含ScriptInterruptedException字符串的特性识别它当识别到这种特殊的异常时向外抛出或者主动终止线程functionloopWithWhileTrue():void{threads.start(function(){while(true){try{log(正在执行带 try 的线程轮询);sleep(3000);}catch(e){log(catch 到异常${e});log(异常类型是:${Object.prototype.toString.call(e)});if((easError).toString().includes(ScriptInterruptedException)){log(识别到脚本中断异常向上 throw);throwe;// 或者不抛出直接调用 threads.currentThread().interrupt()}}}});}用序列化的值来判断异常这显然违背了一些公认的软件工程原则但在 Auto.js v6 环境中这也是一种不得已而为之的变通方法。