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")这种生命周期事件。 - 执行句柄可以直接调用
pause()、resume()、togglePause()、isPaused()、stop()、forceStop()和isRunning();不必先等待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.pause()
作用
暂停这次执行当前对应的子脚本引擎。
返回值
boolean
| 返回值 | 含义 |
|---|---|
true | 子引擎已经存在、仍然活着,并且暂停标记已经设置成功 |
false | 子引擎尚未启动、已经结束,或当前没有可暂停的子引擎 |
示例
const handle = engines.execScriptFile("/sdcard/scripts/worker.js");
handle.on("start", function () {
const paused = handle.pause();
log("暂停结果:" + paused);
log("当前是否暂停:" + handle.isPaused());
});
config.delay 还没有结束时,子引擎尚未创建,此时直接调用 handle.pause() 会返回 false。需要在 start 事件中暂停,或者先通过 handle.engine() 等待引擎启动。
使用 loopTimes 循环执行时,两轮脚本之间的 interval 等待期也可能暂时没有活的子引擎。此时 handle.isRunning() 仍会返回 true,但 handle.pause() 会返回 false;下一轮创建新运行时后,句柄才会重新指向新的子引擎。
handle.resume()
作用
让当前子引擎从暂停状态继续执行。
返回值
boolean
| 返回值 | 含义 |
|---|---|
true | 当前存在仍然活着的子引擎;恢复请求已经处理 |
false | 子引擎尚未启动或已经结束 |
子引擎本来就处于运行状态时,只要它仍然活着,调用 resume() 也会返回 true。因此需要判断暂停状态时,应读取 handle.isPaused(),不要把 resume() 的返回值理解成“调用前一定处于暂停状态”。
示例
if (handle.isPaused()) {
handle.resume();
log("子脚本已继续运行");
}
handle.togglePause()
作用
切换当前子引擎的暂停状态:运行中就暂停,暂停中就继续。
返回值
返回切换后的暂停状态:
true:现在处于暂停状态。false:现在未暂停,或者当前没有可控制的活引擎。
示例
const nowPaused = handle.togglePause();
log(nowPaused ? "已经暂停" : "已经继续");
如果业务必须区分“成功恢复”和“根本没有子引擎”,应先用 handle.isRunning() 或 handle.engineOrNull 判断,不能只看这里的 false。
handle.isPaused()
作用
查询当前子引擎是否已经设置暂停标记。
返回值
boolean
- 当前子引擎已暂停时返回
true。 - 尚未启动、已经结束或当前正在运行时返回
false。
这个方法只读取状态,不会改变引擎。
handle.stop()
作用
停止这次执行,并中断负责启动、延迟或循环执行子脚本的工作线程。
返回值
当前固定返回 true,表示停止请求已经发出。它不是“子引擎此前一定还活着”的证明。
当前实现把主动停止造成的脚本中断视为正常收尾:句柄最终会触发 success,而不是 exception。因此不要用 success 事件区分“脚本自然执行完毕”和“父脚本主动停止”;需要区分时,应由父脚本在调用 stop() 前自行记录状态。
为什么句柄停止更适合父脚本
handle.stop() 不只会尝试停止已经创建的子引擎,还会中断句柄自己的工作线程。因此即使仍处于 config.delay,或者正在等待下一轮 config.interval,也可以通过句柄终止后续执行。
示例
const handle = engines.execScriptFile("/sdcard/scripts/poll.js", {
interval: 5000,
loopTimes: 0
});
setTimeout(function () {
handle.stop();
}, 30000);
handle.forceStop()
在执行句柄上,forceStop() 和 stop() 当前走同一套停止逻辑,返回值也固定为 true。
handle.isRunning()
作用
判断这次执行是否处于已启动但尚未最终完成的阶段。
返回值
boolean
| 阶段 | 返回值 |
|---|---|
子引擎尚未第一次启动,例如仍处于 delay | false |
| 子引擎正在执行 | true |
多次循环之间正在等待 interval | true |
| 整次执行已经成功结束、异常结束或被停止 | false |
示例
const handle = engines.execScript("status-demo", "sleep(3000); log('done');");
handle.on("start", function () {
log("started=" + handle.isRunning()); // true
});
handle.on("success", function () {
log("finished=" + handle.isRunning()); // false
});
暂停到底会暂停什么
这里的暂停是 ScriptX 运行时的协作式暂停,不是已废弃且不安全的 Java 线程强制挂起:
- Rhino 执行 JavaScript 时会按指令检查暂停状态,因此普通计算循环也会在检查点停住。
- ScriptX 的异步回调和多数耗时模块会在进入回调、继续循环或调用关键操作前检查暂停状态。
- 已经进入的单次 Java、Android 或 native 调用不会在任意一条机器指令处被强行截断;通常会在该调用返回并到达下一个运行时检查点后停住。
- 暂停不会销毁局部变量、事件监听器、定时任务或执行配置;
resume()后会从等待点继续。 - 停止和暂停不同:
stop()/forceStop()会结束执行,之后不能再靠resume()恢复。
父脚本控制子脚本时,推荐优先使用句柄:
const handle = engines.execScript(
"counter",
`
let count = 0;
setInterval(function () {
count += 1;
log("count=" + count);
}, 500);
`
);
handle.on("start", function () {
setTimeout(function () {
handle.pause();
}, 2000);
setTimeout(function () {
handle.resume();
}, 5000);
setTimeout(function () {
handle.stop();
}, 8000);
});
不要让子脚本只靠自己执行 engines.myEngine().pause() 后,又指望该脚本后面的普通代码立即调用 resume()。它到达暂停检查点后会等待外部恢复;更合适的做法是由父脚本、脚本管理界面或另一个引擎负责继续它。
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.stop()
作用
停止这个引擎。它会请求运行时结束,并中断对应的工作线程。
返回值
boolean
- 引擎仍然活着并已发出停止请求时返回
true。 - 引擎已经结束时返回
false。
与 engine.forceStop() 的区别主要在返回值:forceStop() 不检查并报告原来的存活状态,脚本侧固定得到 null;stop() 会用布尔值告诉你这次是否针对活引擎发出了停止请求。
engine.pause()
作用
暂停这个引擎对应的 ScriptX 运行时。
返回值
boolean
- 成功设置暂停状态时返回
true。 - 引擎已经结束时返回
false。
示例:按 id 暂停指定引擎
const targetId = 2;
const list = engines.all();
for (let i = 0; i < list.length; i++) {
const engine = list[i];
if (engine.id == targetId) {
log("pause=" + engine.pause());
break;
}
}
这里用 == 是为了兼容 Rhino 包装后的 Java 数字值;如果你已经确认两边都是原生 JavaScript number,也可以使用 ===。
engine.resume()
作用
继续执行一个暂停中的引擎。
返回值
boolean
- 引擎仍然活着时返回
true。 - 引擎已经结束时返回
false。
即使调用前没有暂停,只要引擎仍然活着也会返回 true。要读取真实暂停状态,请配合 engine.isPaused()。
engine.togglePause()
作用
在暂停和继续之间切换。
返回值
true:调用后处于暂停状态。false:调用后处于运行状态,或引擎已经结束。
示例
const engine = handle.engine();
if (engine) {
const paused = engine.togglePause();
log(paused ? "子引擎已暂停" : "子引擎已继续");
}
engine.isPaused()
作用
只读取这个引擎当前是否暂停,不修改状态。
返回值
boolean。只有运行时仍然有效且暂停标记为 true 时才返回 true;已经结束的引擎返回 false。
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");
当前常见可识别项
idsourcesourceFileworkingDirectorycwdexecutionConfiggetConfigthreadtaggetTagsetTagemiteventexecArgvengineArgsgetEngineArggetEngineArgssetExecArgvcontextscriptableconsoleforceStop
当前的 hasFeature 识别表还没有登记 stop、pause、resume、togglePause 和 isPaused。这些引擎对象方法虽然可以正常调用,但下面的检测在当前版本会返回 false:
const engine = engines.myEngine();
log(engine.hasFeature("pause")); // 当前为 false
log(typeof engine.pause == "function"); // true
因此,检测暂停控制能力时应直接判断对应属性是否为函数,不要用 hasFeature("pause") 代替。
引擎对象上的事件方法
每个引擎对象本身也带 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();
}
