当前位置: 首页 > news >正文

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 次数字0

for(leti=0;i<10;i++){constindex=i;log(index);}

const改为let,问题解决,控制台打印了 0~9 的数字:

for(leti=0;i<10;i++){letindex=i;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 环境中,这也是一种不得已而为之的变通方法。

http://www.cnnetsun.cn/news/4172414.html

相关文章:

  • 工业AGV多车路径规划:从轻量级算法到仓库落地实践
  • 从文件到屏幕:Python/java 字符编码、解码、文本处理的底层逻辑解析
  • OPTEE 3.15运行在QEMU ARMv8上
  • MiniMax H3前瞻:技术评估、部署准备与效果验证全指南
  • 机器学习十大经典算法实战指南:从原理到数学建模应用
  • 工业机器人Socket通信:3D视觉引导路径传输的完整协议设计与实现
  • Vue.js面经应用开发实战与核心技巧解析
  • react-rangeslider快速上手指南:5分钟搭建你的第一个React滑块组件
  • 用google-ads-python增删改查广告:10个高频广告操作代码示例,简单到直接复制就能用
  • 为什么国旗有4个字符却只有1个字素簇?unicode-segmentation字素簇完整教程
  • meta与EnTT组合实战:打造C++ ECS游戏引擎的运行时内省与属性编辑基础
  • Physical Token经济学:破解机器人规模化瓶颈的能力复用新范式
  • MobileInfo 设备指纹实践:如何基于 Build 与 Serial 生成不碰撞的设备唯一 ID
  • 单机多GPU利用率最大化:Distributed-TensorFlow-Guide中Worker的GPU分配实战教程
  • 阿里云RTC LTR技术解析:硬件解码支持下的弱网视频抗丢包方案
  • 大模型Agent技术面试核心问题与实战解析
  • Vue+Flask求职推荐系统:Apriori算法实战
  • 人工变量法第二次迭代详解:从单纯形表到最优解判定
  • 在SUN 7149A CRT显示器上播放《九龙珠》:高行频驱动与信号配置实践
  • 美赛建模思维实战:从问题分析到模型构建的完整指南
  • 大模型面试8小时速通:知识图谱与高频题型解析
  • 自我认知面试技巧与STAR-L法则应用指南
  • Flutter混合开发中Gradle配置冲突:Cannot change attributes错误深度解析与解决方案
  • invest线性单位投影问题:提示数据集必须以线性单位投影...如何解决?
  • 踩坑记录——eBPF实现实时数据主从同步
  • Revit建筑设计思维课堂:从参数化建模到BIM协同的实战进阶指南
  • PyTorch实战:从零构建八大核心神经网络模型(CNN/RNN/GAN/Transformer等)
  • 北斗导航 | 基于赏金猎人优化算法(Bounty Hunter Optimizer, BHO)的接收机自主完好性监测算法研究,原理,公式,完整matlab代码,参考文献等
  • S-JEPA视觉自监督中GMM软目标映射:非最大概率组件的工程实践与权衡
  • 计算机组成原理核心精讲:从冯诺依曼到Cache与流水线