阅读须知
阅读须知
注意
本文档为Pro 9新增的基于Node.js的第二代API的文档(第一代API仍然保留可用)。如果你想查看的是旧的第一代API的文档,请在菜单栏切换。
本文档完善中;加入内测请加QQ群569938976。内测版本有Bug为正常情况,请积极反馈和包容。内测群禁止讨论敏感、违法话题,否则一律永久拉黑。
Auto.js Pro 9是Auto.js Pro的全新版本,除了编辑器、打包等新功能外,最重要的是带来了基于Node.js的引擎和全新的第二代API(第一代API仍然保留可用),伴随着庞大的npm生态(接近200万个npm包),并仍然支持和Android/Java交互(也即可在Node.js中使用Android/Java API)。
快速开始
用Node.js引擎运行代码
为了向前兼容,Pro 9中的代码仍然默认为旧的Rhino引擎运行,要使用新的Node.js引擎可以用以下任一方式:
- 文件头带上
"nodejs";
,例如:
"nodejs";
// 打印nodejs的版本
console.log(`Node.js版本: ${process.version}`);
- 文件以
.node.js
或.mjs
结尾。.mjs
结尾则会同时启用ES Module功能,参见https://nodejs.org/api/esm.html
使用Node.js内置模块
在Node.js中,你可以使用其自带的几十个模块,比如:
fs
: 文件系统,用于读写文件(类似Pro 8中的files模块)http
,https
: http(s)请求与服务,用于发送http(s)请求或者搭建http服务器worker_threads
: 工作线程,用于并行执行任务(类似于Pro 8中的threads模块)- ...
全部的Node.js内置模块及其文档参见Node.js 16.x文档,
以下是一个使用内置的fs模块读取文本文件的例子:
"nodejs";
const fs = require("fs");
// 使用readFile读取文件,参见https://nodejs.org/dist/latest-v16.x/docs/api/fs.html#fsreadfilepath-options-callback
fs.readFile('/sdcard/脚本/test.txt', {'encoding': 'utf-8'}, (err, data) => {
if (err) {
console.error("读取文件失败:", err);
} else {
console.log("读取文件成功:", data);
}
});
使用Pro 9内置模块
作为对Node.js内置模块的补充,Pro 9将部分Pro 8的模块迁移到了Pro 9的API中,比如:
app
: 用于启动其他应用、获取其他应用信息,发送广播、邮件等ui
: 用于显示自定义界面、Web界面accessibility
: 用于使用无障碍API完成自动化任务- ...
在Pro 9中,所有模块均需要使用
require()
来导入才能使用,而不能像Pro 8一样直接使用全局变量。比如不能直接用app
或$app
变量,而需要用const app = require('app')
来导入模块。
所有模块的列表可在本文档右侧或右上角菜单中查看。
每个模块的API可能与Pro 8有所不同,大部分API被设计为异步而非同步阻塞,一些全局函数、变量被设计为模块内函数。比如,requestScreenCapture
函数在Pro 8版本中是一个同步函数,请求截图权限,在用户操作前会一直阻塞,因此无法在UI线程中执行,在Pro 9中则是一个异步函数,返回一个Promise,需要用await
或then
来得到结果;setClip()
, getClip()
在Pro 8中是全局函数,在Pro 9中则属于模块]clip_manager的函数。
以下是一个使用media_projection模块请求截图权限,使用image模块寻找图片的例子:
// 使用解构语法导入模块的部分函数和变量
// 当然也可以用const image = require("image")再使用image.readImage(),但相对繁琐
const {readImage, findImage} = require("image");
const {requestScreenCapture} = require('media_projection');
// 主函数,用async标记以便使用await来等待结果
async function main() {
// 请求截图权限
const capturer = await requestScreenCapture();
// 读取要寻找的图片
const template = await readImage("./template.png");
// 获取下一帧截图图片
const capture = await capturer.nextImage();
// 在截图中模板匹配template
const result = await findImage(capture, template);
// 打印结果
console.log('findImage: ', result);
// 停止截图
capturer.stop();
// 回收图片
template.recycle();
}
// 执行主函数
main();
使用npm安装第三方模块
在npm上有大量的第三方模块,这些模块绝大部分都能在Pro 9中使用。在使用它们之前需要用npm命令来安装模块。
- npm包需要项目才能安装。在Pro 9文件管理中点击右下角菜单,选择项目,在模板中选择Node.js项目。
- 在新建项目页面,填好应用名称、包名(包名需要包含英文".",比如com.example),点击确定
- 在项目文件夹中,点击工具栏的项目图标,点击终端
- 输入"npm i --no-bin-links 模块名称"来安装npm包,安装后就可以在项目的代码中使用该模块
以用于生成UUID的uuid模块为例,整个过程如下:
之前就可以参考uuid模块的文档在main.js中使用该模块:
"nodejs";
const uuid = require("uuid");
console.log("uuid:", uuid.v4());
后续需要安装其他模块,也都在终端中,通过cd
命令进入相应的项目目录。
要搜索模块,请在npm官网中搜索。
之所以要用
--no-bin-links
选项,是因为很多npm模块会在安装时链接一些可执行脚本到node_modules/.bin
目录,但是Android的内部存储分区(sdcard)的文件系统不支持符号链接,因此我们需要使用该选项来禁用它。但是与此同时,我们常常会在npm脚本中用到这些可执行文件,例如安装webpack后运行webpack命令,安装react后运行react-scripts命令,此时只能执行具体路径的js文件来代替,比如用node node_modules/webpack/bin/webpack.js
来代替。另外一个方案是将默认脚本文件夹迁移到app私有目录,这里的文件系统是支持符合链接的,你可以在设置中修改默认脚本文件夹为"~",但是需要注意,私有目录会在卸载APP后被删除,因此,
安装npm全局模块
Pro 9内置的npm也可安装全局模块,比如typescript编译ts文件,webpack-cli打包js文件等。
在终端中执行npm i -g typescript
即可安装typescript模块,之后就可以在终端中执行tsc
命令来编译ts文件。
注意!请勿升级内置的npm版本,否则可能遇到意料之外的问题;另外安装全局模块时不能用
--no-bin-links
参数,否则将无法找到相应的命令。
调用Java/Android API
Pro 9提供了全局对象$autojs,提供一些特殊的API,比如调用Java API。
例如:
"nodejs";
// 获取$java对象,用于和Java交互
const $java = $autojs.java;
// 加载Java/Android类
const StringBuilder = $java.findClass('java.lang.StringBuilder');
// 创建这个类的对象
const sb = new StringBuilder();
// 调用这个类的方法
sb.append("Hello");
sb.append(2);
console.log(sb.toString());
除了findClass外,java对象的文档](https://pro.autojs.org/docs/v9/interfaces/globals.Java.md)。
除了这种比较原始的方式外,Pro 9提供了rhino
模块,用于提供类似Pro 8中rhino引擎类似的和Java交互的方式:
"nodejs";
// 调用install后,可以直接java.*, android.*等来访问Java类
require('rhino').install();
const StringBuilder = java.lang.StringBuilder;
const sb = new StringBuilder();
sb.append(android.util.Base64.decode("YXV0b2pz", 0));
console.log(sb.toString());
暂时还不支持importClass/importPackage等函数;也不支持JavaAdapter。
有关Java/Android交互的更多信息,请等待后续单独章节展开。
线程与线程模型
单线程与多线程
Node.js使用遵循带有事件循环的单线程模型,在Pro 9中也是如此,因此你不能像Pro 8那样使用threads模块启动新线程。
大多数情况你也不必使用线程,一些耗时操作,比如findImage、click等都被封装为异步操作,完全可以并行执行;调用一些Java API时,若这些API是异步操作,也可以指定Java函数执行的线程,比如:
"nodejs";
require('rhino').install();
const path = require('path');
const BitmapFactory = android.graphics.BitmapFactory;
// 当前目录下的test.png文件
const file = path.join(__dirname, "./test.png");
async function main() {
// 调用 BitmapFactory.decodeFile(file)来解码图片文件为Bitmap
// 这是一个耗时操作,我们指定在io线程执行
const bitmap = await BitmapFactory.decodeFile.invoke(null, [file], 'io');
console.log(bitmap);
bitmap.recycle();
}
main();
如果以上均不能满足你的需求,你需要纯JavaScript计算逻辑运行于单独的线程,那么需要使用Node.js的worker_threads
模块,参见Node.js文档和网上相关资料。worker_thread不像Pro 8中的threads子线程那样可以和主线程共享所有公共、全局变量等,需要额外的通信,这里就不展开了。
****目前,worker_threads中的子线程无法访问autojs相关的API和模块,比如$autojs, 只能访问Node.js内置的模块和对象。
UI线程
默认情况下,Node.js引擎运行在非UI线程,但是这样无法操作界面相关的内容;因此Pro 9提供了UI线程的选项,通过在文件头用字符串"ui-thread"或"ui"来标识,例如:
"nodejs ui";
const {isUiThread} = require("ui");
console.log(isUiThread());
"ui"和"ui-thread"是有区别的:
ui
: 用于显示界面(Activity)的情况,比如启动后展示一个Web页面用于用户操作,参见UI模块的文档。ui-thread
: 不在启动时显示一个新页面,但是代码运行在UI线程,一般用于无界面的代码中显示和控制悬浮窗,参见悬浮窗模块的文档。
另外,若在非UI线程中,偶尔需要操作UI元素,比如显示和控制対话框,则可以用前述的调用Java API时切换线程的方式来实现。比如view.setText.invoke(view, ["hello"], "ui")
。
阅读模块文档的指引
模块的文档是通过代码生成的,阅读文档需要一些技巧,否则可能觉得文档晦涩难懂。
以app模块为例,打开app模块文档后,会看到一个列表:
接口
: 接口,第一次看文档,直接跳过这部分内容。变量
: 本模块的变量。我们在这个列表中看到packageName
,代表app模块有一个叫packageName
的变量。可以用以下方式使用它:
"nodejs";
const app = require("app");
console.log(app.packageName);
点击该变量的文档,可以看到前面有常量
标记,表示这是一个常量,不能修改他的值,也即app.packageName = "xxx"
会报错;类型是string,字符串。结合变量名可以知道,这是当前app的包名。当然后面会有变量的中文注释,只是目前还没写,得通过变量名去猜测。
函数
: 本模块的函数。我们在这个列表中看到很多函数,比如editFile
,startActivity
。这些都是app模块的函数,以startActivity
为例,点击其文档可以看到:
startActivity(target: string | IntentOptionsWithRoot): Promise<void>
它有一个参数target,类似是string或IntentOptionsWithRoot。string我们都知道是字符串,也即类似Pro 8中的app.startActivity("console")
的用法。那么IntentOptionsWithRoot呢?
点击IntentOptionsWithRoot可以看到IntentOptionsWithRoot的文档。先看Properties这一栏,是描述该接口的属性,有root
这个boolean属性,前面有Optional标签,表示是可选属性;再看前面的继承关系,表示IntentOptionsWithRoot继承于IntentOptions,跳转到IntentOptions,可以看到它有很多属性,比如string类型的action。其实也可以不跳转过去,勾选右上角的Inherited,可以看到所有继承过来的属性。
综合起来,我们可以知道IntentOptionsWithRoot是要我们传入一个对象,这个对象可以有root、action等可选属性,因此我们可以这样写:
"nodejs";
const app = require("app");
app.startActivity({
"root": false,
"action": "android.intent.action.VIEW",
"data": "https://pro.autojs.org",
});
路线规划
关于V9版本的路线规划,请参考https://github.com/hyb1996/Auto.js/issues/526 。