engines 脚本引擎
engines 脚本引擎
engines 负责“从一个脚本里再启动别的脚本引擎”。它适合做这些事:
- 拉起另一个 JS 文件
- 临时执行一段脚本文本
- 给子脚本传参数
- 广播事件给所有活着的脚本引擎
- 查看当前有哪些脚本引擎还在运行
如果你以前只写单脚本,这页最值得先记住的是:
engines.execScriptFile(...)/engines.execScript(...)返回的是执行句柄engines.myEngine()/engines.all()返回的是引擎对象
这两个不是一回事。
先记住这 12 条
engines.myEngine()代表当前这段脚本自己的引擎对象。engines.all()和engines.getRunningEngines()当前是同一类结果,都会列出活着的引擎对象数组。engines.execScriptFile(...)支持传文件,也支持传目录;传目录时会自动找入口文件。- 目录入口查找顺序当前是:
main.js->index.js->main.node.js-> 目录下按名称排序的第一个.js文件。 engines.execAutoFile(...)当前本质上就是execScriptFile(...)的别名。engines.execScript(name, script, config?)是“直接执行一段脚本文本”。config.delay是首次启动前延迟,config.interval是循环执行之间的间隔。config.loopTimes <= 0时,会被当成“无限循环”。config.arguments会进入子引擎的execArgv。- 执行句柄对象支持
on("start")、on("success")、on("exception")这种生命周期事件。 - 当前执行句柄对象没有直接暴露
requestStop()/requestPause()/requestResume()这组方法,想操作引擎要先拿到handle.engine()返回的引擎对象。 engines.stopAll()会连当前脚本自身也一起停掉。
engines.myEngine()
作用
获取当前脚本自己的引擎对象。
返回值
EngineObject
示例
const engine = engines.myEngine();
log(engine.id);
log(engine.workingDirectory);
engines.all()
作用
获取当前所有还活着的引擎对象列表。
返回值
EngineObject[]
示例
const list = engines.all();
log(`running=${list.length}`);
engines.getRunningEngines()
作用
和 engines.all() 一样,获取当前所有还活着的引擎对象。
返回值
EngineObject[]
说明
当前实现里这两个入口最终都是走同一套逻辑。
engines.execScriptFile(file, config?)
作用
执行一个脚本文件,或者执行一个脚本目录的入口文件。
参数
| 参数 | 类型 | 说明 |
|---|---|---|
file | string | 文件路径或目录路径;不能为空 |
config | object | 启动配置,可选 |
返回值
ExecutionHandle
file 可以怎么传
| 传法 | 是否支持 | 说明 |
|---|---|---|
| 绝对文件路径 | 支持 | 直接执行该文件 |
| 相对文件路径 | 支持 | 相对于 workingDirectory 或默认工作目录解析 |
| 目录路径 | 支持 | 自动查目录入口文件 |
目录入口文件规则
当你传的是目录时,当前会按下面顺序找:
main.jsindex.jsmain.node.js- 目录下按文件名排序后的第一个
.js
config 字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
workingDirectory | string | 当前默认工作目录 | 相对路径会继续按默认工作目录解析 |
path | string[] | [] | 会进入只读执行配置对象 |
arguments | object | null | 会进入子引擎 execArgv |
delay | number | 0 | 首次启动前延迟,毫秒 |
interval | number | 0 | 循环执行间隔,毫秒 |
loopTimes | number | 1 | 执行次数;<= 0 表示无限循环 |
示例:执行单文件
const handle = engines.execScriptFile("/sdcard/scripts/demo.js");
示例:执行目录入口
const handle = engines.execScriptFile("/sdcard/scripts/projectA");
示例:带参数和延迟
const handle = engines.execScriptFile("/sdcard/scripts/task.js", {
arguments: {
uid: 1001,
mode: "debug"
},
delay: 1000,
workingDirectory: "/sdcard/scripts"
});
失败行为
如果 file 是空字符串,会抛:
engines.execScriptFile(file) requires a non-empty path
如果最终找不到入口文件,会抛类似:
Script file not found: ...
engines.execAutoFile(file, config?)
作用
当前等价于 engines.execScriptFile(file, config?)。
返回值
ExecutionHandle
建议
如果你只是写 ScriptX / JSXHook 自己这一套脚本,理解成“另一个名字的同义入口”就够了。
engines.execScript(name, script, config?)
作用
直接执行一段脚本文本。
参数
| 参数 | 类型 | 说明 |
|---|---|---|
name | string | 用来生成虚拟脚本名;可空 |
script | string | 脚本文本;不能为空 |
config | object | 启动配置,可选 |
返回值
ExecutionHandle
真实规则
name为空时,内部会用"<script>"。script不能为空,否则直接抛错。- 如果
name不是.js结尾,内部会自动补成.js风格脚本名。
示例
const handle = engines.execScript(
"child-task",
`
console.log("child running");
console.log("args =", engines.myEngine().getEngineArgs());
`,
{
arguments: {
source: "parent"
}
}
);
失败行为
脚本文本为空时会抛:
engines.execScript(name, script) requires a non-empty script body
engines.broadcast(eventName, ...args)
作用
向所有活着的引擎广播一个事件。
返回值
boolean
说明
- 会遍历当前所有活着的引擎。
- 只要至少有一个引擎成功接收到这个事件,就会返回
true。 - 传入参数会先做一层 JSON 兼容克隆,避免直接把一些复杂运行时对象原样串过去。
示例
父脚本:
engines.broadcast("task:update", {
progress: 60
});
子脚本:
engines.myEngine().on("task:update", function (payload) {
log(`progress=${payload.progress}`);
});
engines.stopAll(options?)
作用
停止当前所有还活着的脚本引擎。
参数
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
toast | boolean | false | 是否弹出“已停止 N 个脚本引擎”提示 |
返回值
boolean
很重要
当前实现会把当前脚本自己也停掉,所以调用它以后,后面代码通常不会继续有意义地跑下去。
示例
engines.stopAll({ toast: true });
engines.stopAllAndToast()
作用
等价于:
engines.stopAll({ toast: true });
返回值
boolean
执行句柄对象 ExecutionHandle
下面这些对象来自:
const handle = engines.execScriptFile(...);
const handle2 = engines.execScript(...);
handle.engineOrNull
作用
如果子引擎已经真正启动,就返回对应引擎对象;还没启动时返回 null。
返回值
EngineObject | null
示例
const handle = engines.execScriptFile("/sdcard/scripts/demo.js");
log(handle.engineOrNull);
handle.executionConfig
作用
返回这次执行的只读配置对象。
里面有哪些字段
| 字段 | 类型 | 说明 |
|---|---|---|
workingDirectory | string | null | 工作目录 |
path | string[] | 附加路径列表 |
projectConfig | any | 当前实现里通常是 null |
delay | number | 首次延迟 |
interval | number | 循环间隔 |
loopTimes | number | 循环次数 |
另外还有:
executionConfig.getPath()
示例
log(util.inspect(handle.executionConfig));
handle.source
作用
这次执行的来源。
可能是什么
| 场景 | 值 |
|---|---|
execScriptFile(...) | File 或文件来源 |
execScript(...) | 你传进去的名字,或生成的虚拟来源 |
handle.sourceFile
作用
这次执行关联的脚本文件路径字符串。
handle.engine()
作用
等待子引擎真正启动,然后返回引擎对象;如果脚本很快结束且还没拿到引擎,也可能返回 null。
返回值
EngineObject | null
说明
这个方法内部会循环等待一段时间片,直到:
- 引擎启动完成
- 或这次执行已经结束
示例
const handle = engines.execScriptFile("/sdcard/scripts/demo.js");
const engine = handle.engine();
if (engine) {
log(engine.id);
}
handle.getEngine()
当前是 handle.engine() 的同义写法。
handle.getConfig()
当前是取执行配置对象的函数式写法,等价于读 handle.executionConfig。
handle.on(eventName, listener) 等生命周期事件
执行句柄对象也挂了完整 emitter 方法,最值得记的事件名有 3 个:
| 事件名 | 触发时机 | 参数 |
|---|---|---|
start | 子引擎第一次真正启动时 | (handle) |
success | 执行正常结束时 | (handle) |
exception | 执行异常结束时 | (handle, error) |
一个最实用的写法
const handle = engines.execScriptFile("/sdcard/scripts/demo.js");
handle.on("start", function () {
log("child started");
});
handle.on("success", function () {
log("child finished");
});
handle.on("exception", function (handle, error) {
log("child failed:", error);
});
事件为什么适合用句柄而不是引擎对象
因为句柄在“子引擎还没真正创建出来之前”就已经有了,所以最适合拿来监听启动过程。
引擎对象 EngineObject
下面这些对象来自:
engines.myEngine()engines.all()engines.getRunningEngines()handle.engine()handle.engineOrNull
只读属性
engine.id
当前引擎 id。
engine.workingDirectory
当前引擎工作目录。
engine.source
当前引擎来源对象,通常是 File 或字符串来源。
engine.sourceFile
当前引擎脚本文件路径。
engine.thread
当前引擎执行线程对象。
engine.executionConfig
当前引擎执行配置对象。
engine.execArgv
当前引擎收到的启动参数对象。
engine.forceStop()
作用
强制停止这个引擎。
返回值
null
engine.getId()
函数式获取 id。
engine.getTag(key) / engine.setTag(key, value)
作用
给引擎对象挂一份进程内标签数据。
示例
const engine = engines.myEngine();
engine.setTag("role", "worker");
log(engine.getTag("role"));
注意
key为空字符串时不会真正写入。value会先做一层可克隆处理。
engine.cwd()
返回当前工作目录字符串;如果为空,返回 null。
engine.getSource()
函数式获取 source。
engine.getConfig()
函数式获取 executionConfig。
engine.getThread()
函数式获取执行线程对象。
engine.getEngineArgs()
返回当前引擎收到的全部启动参数。
最常见用途
子脚本里读取父脚本传进来的 arguments:
const args = engines.myEngine().getEngineArgs();
log(util.inspect(args));
engine.getEngineArg(name, defaultValue?)
作用
只取某一个启动参数。
返回规则
| 场景 | 返回值 |
|---|---|
| 参数存在 | 对应值 |
| 参数不存在 | 第二个参数 defaultValue |
示例
const uid = engines.myEngine().getEngineArg("uid", 0);
log(uid);
engine.setExecArgv(value)
作用
直接覆盖当前引擎的 execArgv。
返回值
引擎对象本身。
注意
这个操作改的是当前引擎对象上的参数快照,不会回头改父脚本原来的配置对象。
engine.getContext()
作用
返回底层 JavaScriptRuntime 对象。
说明
这是偏底层的运行时对象,普通脚本一般不需要用。
engine.getScriptable()
作用
返回这个引擎对应的 Rhino scope。
说明
同样偏底层,主要给高级场景用。
engine.getConsole()
作用
返回该引擎作用域里的 console 对象。
engine.hasFeature(name)
作用
判断当前引擎对象是否支持某个能力名。
特点
内部会先做名字归一化:
- 去首尾空白
- 转小写
- 去掉非字母数字字符
所以这些写法都可能命中:
engine.hasFeature("id");
engine.hasFeature("getId");
engine.hasFeature("working-directory");
engine.hasFeature("exec argv");
当前常见可识别项
idsourcesourceFileworkingDirectorycwdexecutionConfiggetConfigthreadtaggetTagsetTagemiteventexecArgvengineArgsgetEngineArggetEngineArgssetExecArgvcontextscriptableconsole
引擎对象上的事件方法
每个引擎对象本身也带 emitter 方法,并且当前额外开放了 emit()。
这意味着你可以:
const engine = engines.myEngine();
engine.on("custom", function (payload) {
log(payload.msg);
});
engine.emit("custom", { msg: "hello" });
这组能力和 engines.broadcast(...) 的区别是:
engine.emit(...):只发给这个引擎engines.broadcast(...):发给所有活着的引擎
一个完整例子:父脚本拉起子脚本并传参
父脚本:
const handle = engines.execScript(
"child-demo",
`
const engine = engines.myEngine();
log("uid =", engine.getEngineArg("uid", 0));
log("mode =", engine.getEngineArg("mode", "normal"));
`,
{
arguments: {
uid: 1001,
mode: "debug"
}
}
);
handle.on("success", function () {
log("child done");
});
一个完整例子:循环执行同一脚本
engines.execScriptFile("/sdcard/scripts/ping.js", {
delay: 1000,
interval: 5000,
loopTimes: 3
});
这段的真实含义是:
- 先等 1 秒
- 执行第 1 次
- 每隔 5 秒再执行下一次
- 总共执行 3 次
一个完整例子:停掉某个子引擎
const handle = engines.execScriptFile("/sdcard/scripts/loop.js");
sleep(1000);
const engine = handle.engine();
if (engine) {
engine.forceStop();
}
