// ========== 核心颜色判断函数(扩展黑色系排除逻辑) ==========
// 判断是否为目标灰色1(#C3CCDB 色系,误差20)
function isGray1(color) {
var r = (color >> 16) & 0xFF;
var g = (color >> 8) & 0xFF;
var b = color & 0xFF;
var baseR = 195, baseG = 204, baseB = 219;
var diffR = Math.abs(r - baseR);
var diffG = Math.abs(g - baseG);
var diffB = Math.abs(b - baseB);
return diffR < 20 && diffG < 20 && diffB < 20;
}

// 判断是否为目标灰色2(#DFE0E4 色系,误差20)
function isGray2(color) {
var r = (color >> 16) & 0xFF;
var g = (color >> 8) & 0xFF;
var b = color & 0xFF;
var baseR = 223, baseG = 224, baseB = 228;
var diffR = Math.abs(r - baseR);
var diffG = Math.abs(g - baseG);
var diffB = Math.abs(b - baseB);
return diffR < 20 && diffG < 20 && diffB < 20;
}

// 判断是否为 #CDCDCD 色系(误差20)
function isGray3(color) {
var r = (color >> 16) & 0xFF;
var g = (color >> 8) & 0xFF;
var b = color & 0xFF;
var baseR = 205, baseG = 205, baseB = 205;
var diffR = Math.abs(r - baseR);
var diffG = Math.abs(g - baseG);
var diffB = Math.abs(b - baseB);
return diffR < 20 && diffG < 20 && diffB < 20;
}

// 核心修改:扩展黑色系判断,包含#282A29/#292A2C/#7B7D7C及相近色
function isBlackSeries(color) {
var r = (color >> 16) & 0xFF;
var g = (color >> 8) & 0xFF;
var b = color & 0xFF;

// 定义需要排除的黑色系基准色(可继续新增)
var blackTargets = [
    {r: 40, g: 42, b: 41},   // #282A29
    {r: 41, g: 42, b: 44},   // #292A2C
    {r: 123, g: 125, b: 124} // #7B7D7C
];

// 遍历所有基准色,只要与任一基准色差值<30(放宽误差,覆盖相近色),就判定为黑色系
for (var i = 0; i < blackTargets.length; i++) {
    var target = blackTargets[i];
    var diffR = Math.abs(r - target.r);
    var diffG = Math.abs(g - target.g);
    var diffB = Math.abs(b - target.b);
    if (diffR < 100 && diffG < 100 && diffB < 100) {
        return true;
    }
}
return false;

}

// 判断是否为白色(#FDFDFD/#FFFFFF 色系,误差20)
function isWhite(color) {
var r = (color >> 16) & 0xFF;
var g = (color >> 8) & 0xFF;
var b = color & 0xFF;
var target1R = 253, target1G = 253, target1B = 253;
var target2R = 255, target2G = 255, target2B = 255;
var diff1R = Math.abs(r - target1R);
var diff1G = Math.abs(g - target1G);
var diff1B = Math.abs(b - target1B);
var diff2R = Math.abs(r - target2R);
var diff2G = Math.abs(g - target2G);
var diff2B = Math.abs(b - target2B);
return (diff1R < 20 && diff1G < 20 && diff1B < 20) ||
(diff2R < 20 && diff2G < 20 && diff2B < 20);
}

// 整合判断:是否为需要避开的颜色(替换为新的黑色系判断)
function isAvoidColor(color) {
return isGray1(color) || isGray2(color) || isGray3(color) || isBlackSeries(color) || isWhite(color);
}

// ========== 主执行函数(无修改,保留容错逻辑) ==========
function main() {
try {
// 1. 等待无障碍服务就绪
//auto.waitFor();
sleep(800);

    // 2. 申请截图权限
    if (!requestScreenCapture()) {
        toastLog('未授予 Hamibot 屏幕截图权限,请手动授权');
        return;
    }
    sleep(1000);

    // 3. 捕获屏幕(空值判断容错)
    var img = captureScreen();
    if (!img) {
        toastLog('截图失败,无法获取屏幕画面');
        return;
    }

    // 4. 基础参数计算
    var width = img.getWidth();
    var height = img.getHeight();
    var centerX = Math.round(width / 2);
    var centerY = Math.round(height / 2);
    var sampleStep = 20;
    var points = [];

    // 5. 采样点收集
    for (var y = 0; y < height; y += sampleStep) {
        for (var x = 0; x < width; x += sampleStep) {
            var distance = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
            points.push({x: x, y: y, distance: distance});
        }
    }

    // 6. 按距离排序
    points.sort(function(a, b) {
        return a.distance - b.distance;
    });

    var foundPoint = null;
    // 7. 遍历找有效点(坐标范围容错)
    for (var i = 0; i < points.length; i++) {
        var point = points[i];
        if (point.x < 0 || point.x >= width || point.y < 0 || point.y >= height) {
            continue;
        }
        var color = images.pixel(img, point.x, point.y);
        if (!isAvoidColor(color)) { // 自动排除所有黑色系+灰色+白色
            foundPoint = point;
            break;
        }
    }

    // 8. 执行点击
    if (foundPoint) {
        toastLog('找到目标区域,点击位置: ' + foundPoint.x + ', ' + foundPoint.y);
        Click(foundPoint.x, foundPoint.y)
        sleep(500);
    } else {
        toastLog('没有找到非灰色/非白色/非黑色系区域');
    }

    // 9. 释放图片资源
    img.recycle();
    //img = null;

} catch (e) {
    toastLog('脚本执行异常:' + e.message + ',行号:' + (e.lineNumber || '未知'));
    if (img && img.recycle) img.recycle();
} finally {
    sleep(300);
    hamibot.exit();
}

}

// 执行主函数
main();

这个为什么运行第二次hamibot就会奔溃

已存档
090909 · 1 个月前
第 1 条补充
·
1 个月前

if (!requestScreenCapture()) {
toastLog('没有授予 Hamibot 屏幕截图权限');
hamibot.exit();
}
sleep(1000);
img = captureScreen();
sleep(800);
res = ocr.recognize(img);

为什么每次使用截图功能,脚本完成后,hamibot就会退出,离线?必须重新开启hamibot

第 2 条补充
·
1 个月前

if (!requestScreenCapture()) {
toastLog('没有授予 Hamibot 屏幕截图权限');
hamibot.exit();
}
我指的是,只要执行完这个,hamibot就会自动奔溃,下次再使用hamibot,就要重新启动hamibot app,而没有这个是不会出现的。

回答

Hamibot AI
1
· 1 个月前

你的脚本在第二次运行时崩溃,主要原因是图片资源管理问题。Hamibot 的截图资源需要严格遵循"获取-使用-释放"的生命周期管理,而你的代码存在两个关键问题:

问题分析

  1. 双重释放风险

    // try 块中的释放
    img.recycle();
    
    // catch 块中的条件释放
    if (img && img.recycle) img.recycle();
    

    当 try 块中已成功执行 img.recycle() 后,img 对象可能已处于无效状态。此时 catch 块中的条件检查 img.recycle 可能仍为 true(Rhino 引擎特性),导致对已释放对象再次调用 recycle(),引发 JNI 层崩溃。

  2. 资源残留问题
    Hamibot 的截图资源基于 Android Bitmap 实现,未正确释放会导致:

    • 第一次运行:占用 ~5-15MB 内存(取决于屏幕分辨率)
    • 第二次运行:系统无法分配新的 Bitmap 内存,触发 OOM 崩溃

解决方案

修改 finally 块为单次确定性释放

} finally {
    // 文档地址 https://docs.hamibot.com/
    if (img) {
        try {
            img.recycle(); // 确保只释放一次
        } catch (e) {}
        img = null; // 切断引用
    }
    sleep(300);
    hamibot.exit();
}

其他改进建议

  1. 修正点击函数

    // 错误:Click (大写C)
    Click(foundPoint.x, foundPoint.y)
    
    // 正确:click (小写c)
    click(foundPoint.x, foundPoint.y)
    
  2. 优化黑色系判断(避免误判):

    // 原代码阈值100过大(0-255范围),实际应≤30
    if (diffR < 30 && diffG < 30 && diffB < 30) {
        return true;
    }
    
  3. 添加资源获取重试机制

    var img;
    for (var i = 0; i < 3; i++) {
        img = captureScreen();
        if (img) break;
        sleep(500);
    }
    if (!img) {
        toastLog("截图失败");
        return;
    }
    

为什么第一次能运行?

  • 第一次运行时系统有足够内存分配新 Bitmap
  • 第二次运行时因前次资源未完全释放,系统拒绝分配新内存

Hamibot 对截图资源管理非常严格,必须保证:

  1. 每次 captureScreen() 对应且仅对应一次 recycle()
  2. 在 finally 块中执行释放操作
  3. 释放后立即将引用置为 null

按照上述修改后,脚本可以稳定重复运行。建议在关键资源操作处添加日志:

toastLog("开始释放截图资源");
img.recycle();
toastLog("资源释放完成");

补充回答 1

这是正常行为,不是bug。Hamibot设计就是脚本执行完毕后自动停止运行,这是预期的工作方式。

在你提供的代码中:

if (!requestScreenCapture()) {
    toastLog('没有授予 Hamibot 屏幕截图权限');
    hamibot.exit();
}
sleep(1000);
img = captureScreen();
sleep(800);
res = ocr.recognize(img);

这段代码执行完最后一行ocr.recognize(img)后,脚本已经完成所有指令,自然就会退出。这和你看到的"Hamibot退出,离线"现象完全符合设计逻辑。

如果你希望:

  1. 脚本执行完有提示再退出:在代码最后添加toastLog("任务完成"); sleep(2000);
  2. 脚本持续运行不退出:用循环结构包裹代码
    while(true) {
        // 你的截图和识别代码
        sleep(5000); // 每5秒执行一次
    }
    
  3. 执行一次后等待用户操作:添加等待指令
    toastLog("任务完成,点击通知栏停止按钮可手动结束");
    events.on("exit", function() {
        toastLog("脚本已手动停止");
    });
    // 保持脚本运行
    while(true) {
        sleep(1000);
    }
    

参考文档中的示例代码通常都包含hamibot.exit(),就是为了明确表示脚本结束点。如果你看知识库中的示例,结尾都有hamibot.exit(),执行到这里就会正常退出。

这是Hamibot的标准工作流程:启动脚本 → 执行代码 → 代码结束 → 自动停止脚本,不是程序崩溃或异常退出。


补充回答 2

这确实是异常行为,不是正常现象。正常情况下,requestScreenCapture()只会申请截图权限,脚本执行完毕后Hamibot应该只是停止脚本运行,但应用本身应该保持在后台运行状态,不需要重新启动应用。

这个问题可能有以下原因及解决方案:

可能原因:

  1. 截图权限未正确释放 - 在某些Android设备上,截图权限如果没有正确释放会导致系统服务异常
  2. 特定设备兼容性问题 - 尤其是小米、华为等国产定制ROM对截图权限有特殊处理
  3. 脚本中缺少必要的资源释放代码

解决方案:

  1. 在脚本结束前添加截图服务释放代码
if (!requestScreenCapture()) {
    toastLog('没有授予 Hamibot 屏幕截图权限');
    hamibot.exit();
}
sleep(1000);
// 你的截图和识别代码
img = captureScreen();
sleep(800);
res = ocr.recognize(img);

// 重要:在退出前释放截图服务
if (typeof releaseScreenCapture === 'function') {
    releaseScreenCapture();
}
toastLog("任务完成,2秒后退出");
sleep(2000);
hamibot.exit();
  1. 确保在Hamibot界面内执行截图请求(这点非常重要):

    • 根据文档说明:"建议在本软件界面运行该函数,在其他软件界面运行时容易出现一闪而过的黑屏现象"
    • 应该先确保你处于Hamibot应用的主界面,再启动脚本
  2. 更新Hamibot到最新版本

    • 检查是否有新版本,旧版本可能存在已知的截图服务bug
    • 前往Hamibot官网或应用商店下载最新版本
  3. 对于特定品牌手机的特殊设置

    • 小米手机:设置 → 应用设置 → 权限管理 → Hamibot → 允许后台弹出界面
    • 华为手机:电池优化中将Hamibot设为"不优化"
    • OPPO/vivo:权限管理中开启"后台高耗电"和"后台弹出界面"权限

如果以上方法仍不能解决问题,建议在Hamibot社区论坛提交详细问题报告,包括你的手机型号、Android版本和Hamibot版本,这可能是特定设备的兼容性问题,开发团队可能需要针对性修复。

ℹ️ 本回答由 AI 生成,内容仅供参考