为什么同样的代码在一个脚本里可以运行到下一个脚本就无法运行了。auto.waitFor();
// 1. 初始化控制台 (移除所有可能报错的 UI 方法)
console.show();
log("🚀 神谕系统:收割者 2.0 启动...");
var w = null;
try {
w = floaty.window(
<frame width="160" height="60" bg="#CC000000" gravity="center">
<vertical>
<text id="text" text="状态: 精准收割中" textSize="11sp" color="#00FF00" gravity="center"/>
<button id="stop" text="停止脚本" w="100" h="30" textSize="10sp" margin="5"/>
</vertical>
</frame>
);
w.setPosition(device.width / 2 - 80, 100);
w.stop.click(() => { exit(); });
} catch (e) { log("⚠️ 悬浮窗跳过"); }
function updateStatus(text) {
if (w) ui.run(() => { if(w.text) w.text.setText(text); });
log(text);
}
// ================= 1. 暴力领奖逻辑 (最高优先级) =================
function handleRewards() {
// 强制清理红包弹窗
if (textContains("获得惊喜红包").exists() || text("去看看").exists()) {
log("🎁 发现红包/去看看,尝试清理...");
let btn = textMatches(/确定|收下|去看看|关闭|知道了/).findOnce();
if (btn) {
press(btn.bounds().centerX(), btn.bounds().centerY(), 200);
} else {
back();
}
sleep(1500);
return true;
}
// 扫描屏幕所有“领取奖励”按钮
let getBtn = textMatches(/领取奖励|立即领取|收下奖励/).findOnce();
if (getBtn) {
log("💰 发现可领奖励!");
let b = getBtn.bounds();
// 检查是否在有效点击区
if (b.top > 0 && b.bottom < device.height) {
press(b.centerX(), b.centerY(), 250);
sleep(1500);
return true;
}
}
return false;
}
// ================= 2. 精准黑名单判定 (解决误跳问题) =================
function isRealGameTask(btnObj) {
const BLACKLIST = /游戏|玩游戏|订单|居民|下单|兑换|购买|买/;
// 策略:不再盲目向上追溯,而是寻找按钮所在行的“标题文字”
// 在淘宝 H5 中,标题通常是按钮的“前一个”或“前两个”兄弟节点的子元素
let parent = btnObj.parent();
if (!parent) return false;
// 搜索当前行容器内所有的文本
let siblingsText = parent.find(className("android.widget.TextView"));
for (let i = 0; i < siblingsText.length; i++) {
let txt = siblingsText[i].text();
if (BLACKLIST.test(txt)) {
log("🚫 命中黑名单关键词 [" + txt + "],已跳过");
return true;
}
}
return false;
}
// ================= 3. 主程序循环 =================
threads.start(function() {
const TASK_TAG = /去完成|去浏览|去观看|去逛逛|去赚次|去搜索/;
app.launch("com.taobao.taobao");
sleep(5000);
while (true) {
// 第一步:清空所有能领的奖励
while (handleRewards()) { sleep(500); }
// 第二步:寻找正常的任务按钮
let buttons = textMatches(TASK_TAG).find().filter(v => {
let b = v.bounds();
// 过滤掉不在屏幕内或被红包遮挡的按钮
return b.top > 300 && b.bottom < device.height - 200 && b.width() > 0;
});
let target = null;
for (let i = 0; i < buttons.length; i++) {
let btn = buttons[i];
// 使用新的精准判定逻辑
if (isRealGameTask(btn)) continue;
target = btn;
break;
}
if (target) {
updateStatus("🎯 任务: " + target.text());
let b = target.bounds();
press(b.centerX(), b.centerY(), 250);
sleep(3000);
let start = Date.now();
let duration = random(25000, 31000);
while (Date.now() - start < duration) {
if (handleRewards()) break;
// 仿真贝塞尔曲线滑动
swipe(device.width/2 + random(-50,50), 1500, device.width/2 + random(-50,50), 700, 800);
// 判定是否已提前完成(回到列表页)
if (Date.now() - start > 5000 && textMatches(TASK_TAG).exists()) {
log("⚡ 判定为即时任务,自动返回");
break;
}
if (textMatches(/.*完成.*|.*已获得.*|.*成功.*|.*已达标.*/).exists()) {
updateStatus("✅ 任务达标");
sleep(2000);
break;
}
sleep(random(1500, 2200));
}
updateStatus("🔙 返回列表");
if (!textMatches(TASK_TAG).exists()) {
back();
sleep(2000);
}
// 返回后检查一下是不是被弹窗挡了
handleRewards();
sleep(2000);
} else {
updateStatus("🔍 扫描全屏无任务,翻页...");
// 翻页幅度调小,防止跳过任务
swipe(device.width/2, 1300, device.width/2, 800, 1000);
sleep(2000);
}
}
});
setInterval(() => {}, 1000);
回答
同样的代码在不同脚本中出现运行不一致的情况,通常由权限配置、JavaScript 引擎兼容性、运行环境状态差异导致。针对你提供的代码,以下是具体原因分析和解决方案。
核心原因分析
-
JavaScript 语法兼容性
代码中大量使用了箭头函数(=>),例如setInterval(() => {}, 1000)和w.stop.click(() => { exit(); })。Hamibot 底层 JavaScript 引擎基于 Rhino 1.7.15,对 ES6 新特性的支持在不同版本或配置下可能存在差异。某些环境可能无法正确解析箭头函数,导致脚本在未执行主逻辑前就报错停止。 -
悬浮窗权限差异
floaty.window需要“显示悬浮窗”权限。如果两个脚本的签名、包名识别不同,或者系统权限被重置,会导致一个脚本有权限而另一个没有。代码中虽然使用了try...catch捕获了创建悬浮窗的错误,但后续逻辑依赖悬浮窗状态,权限缺失可能影响脚本稳定性。 -
无障碍服务状态
auto.waitFor()会阻塞脚本直到无障碍服务可用。如果设备无障碍服务不稳定,或者脚本启动时服务尚未完全就绪,可能导致脚本卡死或超时。不同脚本启动时机不同,可能遇到不同的服务状态。 -
线程与 UI 交互
代码在threads.start创建的非 UI 线程中操作了悬浮窗控件。虽然使用了ui.run包裹,但如果主线程过早结束或资源回收冲突,可能导致脚本意外退出。setInterval(() => {}, 1000)用于保活主线程,但若语法解析失败,保活失效。
解决方案与代码优化
建议对代码进行以下修改,以提高兼容性和稳定性。
1. 替换箭头函数
将所有箭头函数改为标准 function 表达式,确保 Rhino 引擎完全兼容。
2. 增强权限检查
在脚本开头明确检查无障碍和悬浮窗权限。
3. 优化悬浮窗关闭逻辑
使用 window.exitOnClose() 确保悬浮窗关闭时脚本正常退出,避免资源泄露。
优化后的代码片段
auto.waitFor(); // 文档地址:https://docs.hamibot.com/reference/widgetsBasedAutomation
// 1. 初始化控制台
console.show();
log("🚀 神谕系统:收割者 2.0 启动...");
var w = null;
try {
w = floaty.window(
<frame width="160" height="60" bg="#CC000000" gravity="center">
<vertical>
<text id="text" text="状态:精准收割中" textSize="11sp" color="#00FF00" gravity="center"/>
<button id="stop" text="停止脚本" w="100" h="30" textSize="10sp" margin="5"/>
</vertical>
</frame>
);
w.setPosition(device.width / 2 - 80, 100);
// 替换箭头函数为标准函数
w.stop.click(function () {
exit();
});
// 设置悬浮窗关闭时结束脚本
w.exitOnClose();
} catch (e) {
log("⚠️ 悬浮窗跳过:" + e);
}
function updateStatus(text) {
if (w) {
ui.run(function () {
if(w.text) w.text.setText(text);
});
}
log(text);
}
// ================= 1. 暴力领奖逻辑 (最高优先级) =================
function handleRewards() {
if (textContains("获得惊喜红包").exists() || text("去看看").exists()) {
log("🎁 发现红包/去看看,尝试清理...");
let btn = textMatches(/确定 | 收下 | 去看看 | 关闭 | 知道了/).findOnce();
if (btn) {
press(btn.bounds().centerX(), btn.bounds().centerY(), 200);
} else {
back();
}
sleep(1500);
return true;
}
let getBtn = textMatches(/领取奖励 | 立即领取 | 收下奖励/).findOnce();
if (getBtn) {
log("💰 发现可领奖励!");
let b = getBtn.bounds();
if (b.top > 0 && b.bottom < device.height) {
press(b.centerX(), b.centerY(), 250);
sleep(1500);
return true;
}
}
return false;
}
// ================= 2. 精准黑名单判定 =================
function isRealGameTask(btnObj) {
const BLACKLIST = /游戏 | 玩游戏 | 订单 | 居民 | 下单 | 兑换 | 购买 | 买/;
let parent = btnObj.parent();
if (!parent) return false;
let siblingsText = parent.find(className("android.widget.TextView"));
for (let i = 0; i < siblingsText.length; i++) {
let txt = siblingsText[i].text();
if (BLACKLIST.test(txt)) {
log("🚫 命中黑名单关键词 [" + txt + "],已跳过");
return true;
}
}
return false;
}
// ================= 3. 主程序循环 =================
threads.start(function() {
const TASK_TAG = /去完成 | 去浏览 | 去观看 | 去逛逛 | 去赚次 | 去搜索/;
app.launch("com.taobao.taobao");
sleep(5000);
while (true) {
while (handleRewards()) { sleep(500); }
let buttons = textMatches(TASK_TAG).find().filter(function(v) {
let b = v.bounds();
return b.top > 300 && b.bottom < device.height - 200 && b.width() > 0;
});
let target = null;
for (let i = 0; i < buttons.length; i++) {
let btn = buttons[i];
if (isRealGameTask(btn)) continue;
target = btn;
break;
}
if (target) {
updateStatus("🎯 任务:" + target.text());
let b = target.bounds();
press(b.centerX(), b.centerY(), 250);
sleep(3000);
let start = Date.now();
let duration = random(25000, 31000);
while (Date.now() - start < duration) {
if (handleRewards()) break;
swipe(device.width/2 + random(-50,50), 1500, device.width/2 + random(-50,50), 700, 800);
if (Date.now() - start > 5000 && textMatches(TASK_TAG).exists()) {
log("⚡ 判定为即时任务,自动返回");
break;
}
if (textMatches(/.*完成.*|.*已获得.*|.*成功.*|.*已达标.*/).exists()) {
updateStatus("✅ 任务达标");
sleep(2000);
break;
}
sleep(random(1500, 2200));
}
updateStatus("🔙 返回列表");
if (!textMatches(TASK_TAG).exists()) {
back();
sleep(2000);
}
handleRewards();
sleep(2000);
} else {
updateStatus("🔍 扫描全屏无任务,翻页...");
swipe(device.width/2, 1300, device.width/2, 800, 1000);
sleep(2000);
}
}
});
// 替换箭头函数为标准函数,保持主线程存活
setInterval(function () {}, 1000);
排查步骤
- 检查权限:确保设备设置中,Hamibot 及该脚本拥有“无障碍服务”和“显示悬浮窗”权限。
- 查看日志:运行失败时,查看控制台日志。如果看到
SyntaxError相关报错,说明是语法兼容性问题(即箭头函数导致)。 - 清理环境:停止所有正在运行的脚本,重启 Hamibot 服务,确保无障碍服务状态刷新。
- 单独测试:将悬浮窗相关代码单独提取到一个新脚本运行,确认是否是悬浮窗权限导致的问题。
通过上述修改和排查,通常可以解决代码在不同脚本间运行不一致的问题。