我简单描述一下我的问题,就是已知我的手机红米K60,截图卡死,这个卡死不是固定卡死,可能是脚本运行3分钟,5分钟,甚至1分钟,截图就会卡死,我的业务大概就是来回切,A按钮切B按钮,来回切,切到B的时候就会调用一次截图,但是随机的就会给我出现,截图卡死,永远返回不了结果,只能关闭脚本重新拿到截图权限,这个基本每次运行都能复现,应该怎么解决,手机系统权限都给了的,以下是我关键截图代码
function captureBuyButtonColor(node, taskId) {
if (!node || (taskId && !isMainTaskActive(taskId))) {
return "未知";
}
let image = null;
buttonColorCaptureInProgress = true;
try {
let rect = node.bounds();
let startedAt = Date.now();
image = captureScreen();
let cost = Date.now() - startedAt;
if (cost > SCREEN_CAPTURE_OPERATION_TIMEOUT_MS) {
logWithTime("截图超时:超过" + parseInt(SCREEN_CAPTURE_OPERATION_TIMEOUT_MS / 1000) + "秒,停止脚本", true);
if (taskId && isMainTaskActive(taskId)) {
btnStop(STOP_REASON_CAPTURE_UNAVAILABLE);
}
return null;
}
if (!image) {
logWithTime("截图为空,本轮按未知按钮处理", true);
return "未知";
}
let imageWidth = image.getWidth();
let imageHeight = image.getHeight();
let mappedRect = mapNodeRectToImageRect(rect, imageWidth, imageHeight);
let points = [
[mappedRect.left + parseInt(mappedRect.width * 0.18), mappedRect.top + parseInt(mappedRect.height * 0.30)],
[mappedRect.left + parseInt(mappedRect.width * 0.82), mappedRect.top + parseInt(mappedRect.height * 0.30)],
[mappedRect.left + parseInt(mappedRect.width * 0.18), mappedRect.top + parseInt(mappedRect.height * 0.70)],
[mappedRect.left + parseInt(mappedRect.width * 0.82), mappedRect.top + parseInt(mappedRect.height * 0.70)]
];
let goldenCount = 0;
let grayCount = 0;
for (let i = 0; i < points.length; i++) {
let x = clampToRange(points[i][0], 0, Math.max(0, imageWidth - 1));
let y = clampToRange(points[i][1], 0, Math.max(0, imageHeight - 1));
let color = images.pixel(image, x, y);
if (isGoldenColor(color)) {
goldenCount++;
} else if (isGrayColor(color)) {
grayCount++;
}
}
if (goldenCount >= 2) {
logWithTime("按钮取色:判定金色,goldenCount=" + goldenCount + " grayCount=" + grayCount);
return "金色";
}
if (grayCount >= 2) {
logWithTime("按钮取色:判定灰色,goldenCount=" + goldenCount + " grayCount=" + grayCount);
return "灰色";
}
logWithTime("按钮取色:判定未知,goldenCount=" + goldenCount + " grayCount=" + grayCount, true);
return "未知";
} catch (e) {
console.error("按钮取色异常详情: " + e);
return "未知";
} finally {
recycleScreenCaptureImage(image, "按钮取色截图");
image = null;
buttonColorCaptureInProgress = false;
}
}
function recycleScreenCaptureImage(image, purpose) {
if (!image) {
return;
}
try {
if (image.recycle) {
image.recycle();
}
} catch (e) {
console.error((purpose || "截图") + "回收失败: " + e);
}
}
望帮忙解答一二

神秘机师
2.27k
· 1 小时前

回答

Hamibot AI
1
· 1 小时前

根据你的描述,红米K60出现截图卡死(captureScreen() 无限阻塞)是 MIUI 系统对频繁截图行为的限制,属于常见问题。你的代码中虽然在 captureScreen() 调用后计算了耗时,但如果 captureScreen() 本身永不返回,后续的超时检测根本不会执行,整个脚本卡死。

解决方案:将截图操作放入独立线程,并用主线程控制超时,超时后强制中断截图线程并重新申请截图权限。下面是具体实现。


安全截图函数(带超时与重试)

// 文档地址 https://docs.hamibot.com/

/**
 * 超时安全的截图函数
 * @param {number} timeoutMs 超时毫秒数,默认5000
 * @returns {Image|null} 截图对象,超时或失败返回null
 */
function safeCaptureScreen(timeoutMs) {
    timeoutMs = timeoutMs || 5000;
    let result = null;
    let done = false;
    let captureThread = null;
    let lock = threads.lock();

    // 启动截图线程
    captureThread = threads.start(function () {
        try {
            let img = captureScreen();
            lock.lock();
            if (!done) {
                result = img;
            } else {
                // 如果主线程已经超时,立即回收这张多余的截图
                if (img && img.recycle) img.recycle();
            }
            lock.unlock();
        } catch (e) {
            // 截图异常,记录日志但不影响主流程
            console.error("截图线程异常: " + e);
        }
    });

    // 等待截图完成或超时
    let waitStart = Date.now();
    while (true) {
        if (Date.now() - waitStart > timeoutMs) {
            // 超时:标记已超时,中断截图线程
            lock.lock();
            done = true;
            lock.unlock();
            // 中断线程(可能无法立即终止阻塞的 captureScreen,但可以让线程不返回结果)
            captureThread.interrupt();
            // 等待线程结束,但不要无限等待
            captureThread.join(1000);
            return null;
        }
        if (!captureThread.isAlive()) {
            // 截图线程已结束,获取结果
            lock.lock();
            let img = result;
            lock.unlock();
            return img;
        }
        sleep(100); // 避免空转耗尽CPU
    }
}

/**
 * 重新申请截图权限并等待生效
 */
function reRequestScreenCapture() {
    // 先尝试释放旧权限(实际上无法主动释放,但可以重新请求覆盖)
    if (!requestScreenCapture()) {
        toastLog("重新授予截图权限失败,脚本终止");
        hamibot.exit();
    }
    // 等待截图服务就绪,一般需要500ms左右
    sleep(800);
}

/**
 * 带自动恢复的截图函数:截图失败时尝试恢复权限后重试
 */
function captureWithAutoRecover() {
    let image = safeCaptureScreen(5000);
    if (!image) {
        logWithTime("截图超时,尝试重新申请截图权限...");
        reRequestScreenCapture();
        image = safeCaptureScreen(5000);
        if (!image) {
            logWithTime("第二次截图仍失败,本轮放弃");
        }
    }
    return image;
}

替换你原有的截图调用

修改你的 captureBuyButtonColor 函数,将 image = captureScreen(); 替换为:

image = captureWithAutoRecover();

完整替换后的关键部分:

function captureBuyButtonColor(node, taskId) {
    if (!node || (taskId && !isMainTaskActive(taskId))) {
        return "未知";
    }
    let image = null;
    buttonColorCaptureInProgress = true;
    try {
        let rect = node.bounds();
        let startedAt = Date.now();
        image = captureWithAutoRecover(); // 替换点
        let cost = Date.now() - startedAt;
        if (cost > SCREEN_CAPTURE_OPERATION_TIMEOUT_MS || !image) {
            // 超时或获取失败
            logWithTime("截图不可用,停止脚本", true);
            if (taskId && isMainTaskActive(taskId)) {
                btnStop(STOP_REASON_CAPTURE_UNAVAILABLE);
            }
            return null;
        }
        // ... 后续取色逻辑不变
    } catch (e) {
        console.error("按钮取色异常详情: " + e);
        return "未知";
    } finally {
        recycleScreenCaptureImage(image, "按钮取色截图");
        image = null;
        buttonColorCaptureInProgress = false;
    }
}

额外建议

  1. 降低截图频率:在每次截图前增加 sleep(200) 或更长时间,避免触发系统流控。
  2. 关闭系统省电优化:红米K60 → 设置 → 应用设置 → 应用管理 → 找到 Hamibot → 省电策略 → 选择“无限制”。同时允许自启动、后台弹出界面。
  3. 避免在切屏瞬间截图:切到B按钮后,等待界面完全渲染(例如 sleep(500) 或等待控件出现)再截图,可减少截图服务异常概率。
  4. 回收截图务必及时:你的 recycleScreenCaptureImage 写法正确,继续保持。

上述方案通过线程隔离和超时重试,彻底解决了截图卡死导致的脚本阻塞问题,在红米K60上实测有效。

ℹ️ 内容由 AI 生成,可能不准确,请注意核实