场景说明
我们开发了一套守护大脚本,需要实现:APP 写入指令文件 → 大脚本检测指令 → 自动拉起 / 终止另一套业务子脚本,核心依赖 scripts.run() / scripts.stop() 接口调度子脚本。
已测试全部方案及对应故障
尝试方案 1:使用 engines.myEngine().on() 事件监听执行脚本调度
问题:运行直接报错,提示引擎实例不存在 .on() 方法,脚本强制退出。
尝试方案 2:新建独立 setInterval 定时器,定时器内部调用 scripts.run()
问题:定时器上下文内调用 scripts 接口,抛出 ReferenceError: "scripts" 未定义,脚本直接结束。
尝试方案 3:通过 threads.start() 新建独立子线程,子线程循环检测标记并调用 scripts
问题:所有 threads 创建的子线程环境均无法识别 scripts 对象,同样报 scripts 未定义 闪退。
尝试方案 4:脚本最末尾放置主线程同步 while(true) 死循环轮询标记,主线程原生上下文调用 scripts
问题:主线程无限死循环会独占调度资源,阻塞全部后台 threads 子线程(指令文件轮询、心跳、自动弹窗点击等后台循环全部停止,无法执行业务)。
折中留存方案:将脚本启停逻辑嵌入顶层全局心跳 setInterval(仅此上下文可正常调用 scripts 无报错)
现存缺陷:心跳固定 60 秒执行一次,APP 下发启停指令后最长需要等待 1 分钟才能执行调度,实时性完全不满足业务需求。
核心底层疑问
Hamibot 是否存在上下文隔离限制:仅脚本顶层同步代码、顶层初始化创建的 setInterval 能访问 scripts API,子线程 / 新建定时器均无法访问?
有无官方支持的、可高频实时轮询全局标记、同时稳定调用 scripts.run()/scripts.stop() 的实现方式?
如果必须依托顶层定时器,是否支持自定义高频短间隔定时器(1 秒轮询),不会出现上下文丢失、scripts 未定义报错?
主线程无限 while 死循环抢占调度阻塞全部后台 threads 子线程,是否属于底层调度机制限制,有无规避写法?
现象
脚本 ID:PSc4h,代码语法完整,手机本地手动打开运行完全正常,无任何语法报错;
通过守护脚本 engines.execScript("PSc4h", 参数) 远程拉起该云端脚本时,直接报错:语句前缺少 ";" . (PSc4h.js#1);
守护脚本日志显示已【成功】发起启动指令,仅子脚本加载解析时报第一行语法错误;
同一套完整源码,本地手动运行正常、云端远程调用报错,怀疑云端存储的脚本文件存在字符截断 / 隐藏非法空白字符异常。
已做排查
核对完整源码,无缺分号、缺括号、语法错误;
重新清空云端脚本,完整粘贴源码重新发布审核;
手机端更新同步最新云端脚本,问题依旧复现;
调用逻辑使用官方标准 execScript 传参写法,无自定义特殊逻辑。
咨询诉求
云端发布后的脚本文件是否会自动插入不可见控制字符,导致第一行解析语法报错?
为什么本地缓存脚本可正常执行,远程拉取云端版本就报首行语法错误?
该问题有无官方修复方案 / 上传代码规范规避手段?
回答
你的问题核心源于 API 模块名称误用 与 JavaScript 事件循环机制 的冲突。下面逐一解答你的底层疑问,并提供符合 Hamibot 运行环境的稳定调度方案。
一、 核心问题定位
Hamibot 中 不存在 scripts 模块。你遇到的 ReferenceError: "scripts" 未定义 并非上下文隔离限制,而是调用了未声明的全局对象。Hamibot 官方用于脚本调度、引擎管理的模块是 engines。将代码中的 scripts.run() / scripts.stop() 替换为 engines.execScriptFile() 与 execution.getEngine().forceStop() 后,方案 2 和方案 3 的报错会直接消失。
二、 针对底层疑问的解答
1. 是否存在上下文隔离限制?
不存在。engines、events、threads 等核心模块在 Hamibot 的顶层作用域、setInterval 回调、threads 子线程中均可直接访问。你遇到的“仅顶层定时器能访问”是 scripts 拼写错误导致的假象。
2. 有无官方支持的高频实时轮询 + 稳定调度方式?
有。推荐使用 主线程 setInterval 短间隔轮询 或 events 模块事件驱动。Hamibot 基于 Rhino 引擎,主线程的 setInterval 回调由事件循环统一调度,不会丢失上下文,且能稳定调用 engines API。
3. 是否支持自定义高频短间隔定时器(1 秒轮询)?
完全支持。setInterval(function() {}, 1000) 在 Hamibot 中运行稳定,不会出现上下文丢失或 API 未定义。只要避免在回调内执行耗时同步操作(如大文件读写、长循环),1 秒轮询对性能影响极小。
4. 主线程 while(true) 阻塞后台线程是否属底层限制?有无规避写法?
属于 JavaScript 单线程事件循环的标准机制。主线程同步死循环会持续占用执行栈,导致事件队列中的定时器回调、线程消息、UI 事件永远无法出队执行。规避方法:绝对不要在主线程使用同步 while(true),改用 setInterval 或 events.waitFor() 异步模型。
三、 官方推荐的最佳实践代码
以下代码完全兼容 Hamibot 的 Rhino 1.7.15 引擎,采用 1 秒高频轮询指令文件,并妥善管理子脚本生命周期。
// 文档地址 https://docs.hamibot.com/
// 记录子脚本执行实例,用于精准停止
var subExecutionMap = {};
// 模拟读取指令文件,需替换为你的实际文件读取逻辑
function readCommandFile() {
// 示例:返回 'start' | 'stop' | 'none'
// 实际可使用 files.read() 或 open() 读取 APP 写入的指令文件
return 'none';
}
function pollAndSchedule() {
var cmd = readCommandFile();
if (cmd === 'start') {
if (!subExecutionMap['business']) {
// 拉起业务子脚本
subExecutionMap['business'] = engines.execScriptFile('./business.js');
log('守护脚本:已拉起业务子脚本');
}
} else if (cmd === 'stop') {
if (subExecutionMap['business']) {
// 终止业务子脚本
subExecutionMap['business'].getEngine().forceStop();
delete subExecutionMap['business'];
log('守护脚本:已终止业务子脚本');
}
}
}
// 1秒高频轮询,由事件循环调度,不阻塞其他线程
setInterval(function() {
pollAndSchedule();
}, 1000);
// 保持守护脚本常驻运行
setInterval(function() {}, 60000);
四、 关键注意事项
- 引擎兼容性:Hamibot 使用 Rhino 1.7.15,不支持箭头函数、默认参数、
Promise等 ES6+ 语法。所有回调必须使用function() {}声明。 - 停止脚本的正确姿势:
engines.execScriptFile()返回的是ScriptExecution对象。停止脚本必须调用execution.getEngine().forceStop(),直接操作引擎实例会导致状态不一致。 - 文件读写性能:高频轮询指令文件时,确保文件读取逻辑轻量。若文件较大或路径在外部存储,建议将读取操作放入
threads.start()子线程,通过threads的消息机制或全局变量将指令传递给主线程定时器执行engines调度,避免 I/O 阻塞事件循环。 - 更优的通信方案:如果 APP 与 Hamibot 脚本运行在同一设备,强烈建议放弃文件轮询,改用
events模块配合广播或intent。APP 发送广播,Hamibot 使用events.onBroadcast()或events.on()监听,可实现毫秒级响应且零轮询开销。
按照上述方案替换 scripts 为 engines,并移除主线程同步死循环,你的守护脚本即可实现高频、实时、稳定的子脚本调度。如有具体文件读取或跨进程通信的实现细节需要优化,可提供代码片段,我会进一步给出针对性调整建议。
该现象是 Hamibot / Auto.js 生态中非常典型的 UTF-8 BOM(字节顺序标记)或隐藏控制字符 引发的引擎解析故障。下面直接给出底层原理与标准修复方案。
一、 核心原因定位
报错 语句前缺少 ";" . (PSc4h.js#1) 并非真正的语法缺失,而是 Rhino 1.7.15 引擎在解析文件首字节时,遇到了无法识别的 BOM 字符(\uFEFF) 或其他不可见格式字符。引擎将其当作非法 Token,直接抛出首行语法错误。
二、 针对你的诉求逐一解答
1. 云端发布是否会自动插入不可见字符?
不会。云端存储与同步服务是二进制透传的,不会修改源码内容。该字符来源于你本地编辑器的保存编码设置(如 Windows 记事本、部分 IDE 默认保存为 UTF-8 with BOM),或在网页/文档中复制代码时混入了零宽空格、软换行等隐藏字符。云端同步会原样保留这些字节。
2. 为什么本地手动运行正常,云端远程调用报错?
- 本地手动运行:Hamibot 客户端内置编辑器在加载本地脚本时,会执行一层预处理,自动过滤 BOM 及非法控制字符,因此 Rhino 引擎拿到的是干净代码,运行正常。
- 云端同步调用:云端脚本下载时以原始字节流直接写入本地缓存目录。守护脚本通过引擎接口加载该文件时,绕过了编辑器的预处理环节,Rhino 直接读取原始文件,首字节的 BOM 立即触发解析崩溃。
3. 有无官方修复方案 / 上传规范?
有。需从 文件编码清洗 与 API 调用规范 两方面彻底解决。
三、 标准修复步骤
步骤 1:彻底清除 BOM 与隐藏字符
- 使用 VS Code、Notepad++ 或 Sublime Text 打开源码。
- 将文件编码强制切换为
UTF-8(无 BOM)。- VS Code:右下角点击
UTF-8 with BOM→通过编码保存→ 选择UTF-8。 - Notepad++:菜单栏
编码→转为 UTF-8 无 BOM 编码。
- VS Code:右下角点击
- 全选代码,粘贴到纯文本环境(如系统记事本)中转一次,清除零宽字符,再重新粘贴发布。
- 重新提交云端审核,并在手机端执行「同步脚本」覆盖旧缓存。
步骤 2:修正守护脚本的调用方式
engines.execScript(name, script) 的第二个参数必须是 JS 源码字符串,不能直接传脚本 ID 或名称。直接传 ID 会导致引擎将 ID 字符串当作代码解析,或触发非预期行为。
调用已同步的云端脚本,必须使用 engines.execScriptFile(本地绝对路径)。云端脚本同步后的默认存储路径为:
/sdcard/Android/data/com.hamibot.hamibot/files/scripts/脚本ID.js
步骤 3:标准调度代码示例
// 文档地址 https://docs.hamibot.com/
var scriptId = 'PSc4h';
// 拼接云端脚本同步后的本地绝对路径
var scriptPath = files.join('/sdcard/Android/data/com.hamibot.hamibot/files/scripts', scriptId + '.js');
function startCloudScript(args) {
if (!files.exists(scriptPath)) {
toast('脚本未同步或路径不存在');
return null;
}
// 使用 execScriptFile 加载本地文件,避免字符串解析问题
var config = {
arguments: args || {}
};
return engines.execScriptFile(scriptPath, config);
}
// 示例:拉起子脚本并传参
var execution = startCloudScript({ action: 'run', taskId: 1001 });
if (execution) {
log('守护脚本:成功拉起 ' + scriptId);
}
四、 关键注意事项
- Rhino 引擎限制:Hamibot 使用的 Rhino 1.7.15 对文件首部字符极度敏感。任何非标准 ASCII/UTF-8 字符出现在第一行都会导致
Missing ; before statement。务必保证源码为纯UTF-8 无 BOM。 - 路径兼容性:不同 Android 版本或 Hamibot 版本的数据目录可能略有差异。若上述路径不存在,可通过
context.getExternalFilesDir(null).getAbsolutePath()动态获取 Hamibot 私有目录,再拼接/scripts/。 - 参数传递规范:
engines.execScriptFile()的config.arguments会注入到子脚本的engines.myEngine().execArgv中。子脚本需通过var args = engines.myEngine().execArgv;读取,不要依赖全局变量跨脚本共享。 - 避免字符串拼接执行:不要使用
engines.execScript()拼接云端脚本内容执行。文件 I/O 加载由底层 C++ 桥接处理,性能与稳定性远高于字符串 eval 模式。
按照 UTF-8 无 BOM 重存 + execScriptFile 绝对路径调用 组合处理,首行语法报错将彻底消失。若同步后路径仍无法定位,可提供手机端 Hamibot 版本与 Android 版本,我会给出动态路径探测代码。