启用线程执行获取屏幕所有文本,遇见制定内容点击

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

如何快速点击,指定文本右侧的图片按钮

第 2 条补充
·
1 个月前

在后台执行脚本中,脚本运行长时间后不执行了,如何用代码避免此类情况

回答

Hamibot AI
1
· 1 个月前

要实现在子线程中监控屏幕并点击特定文本的功能,可以使用以下脚本:

// 文档地址 https://docs.hamibot.com/
threads.start(function () {
    const check = () => {
        // 查找所有文本控件
        let textViews = className("TextView").find();
        
        // 遍历文本控件
        for (let i = 0; i < textViews.length; i++) {
            let textView = textViews[i];
            let textContent = textView.text();
            
            // 判断是否匹配目标文本(可修改为你的指定内容)
            if (textContent === "指定内容") {
                // 尝试点击目标文本
                if (textView.click()) {
                    toastLog("成功点击目标文本: " + textContent);
                    return; // 点击成功后退出循环
                }
            }
        }
        
        // 每隔500ms检查一次
        setTimeout(check, 500);
    };
    
    check();
});

// 主线程继续执行其他操作
toastLog("已启动后台文本监控线程");

关键点说明:

  1. 使用 className("TextView").find() 获取屏幕上的所有文本控件
  2. 通过 textView.text() 获取控件的文本内容
  3. 通过 textView.click() 对匹配的文本执行点击操作
  4. 线程每隔500ms重新检查屏幕内容
  5. 使用 toastLog() 显示操作日志

适配建议:

  • 若需要模糊匹配,可将 textContent === "指定内容" 改为:
    textContent.includes("目标关键词") // 包含关键词匹配
    // 或
    textContent.match(/正则表达式/) // 正则表达式匹配
    
  • 如需点击特定位置,可使用 click(x, y) 指定坐标
  • 可通过 textView.bounds() 获取控件坐标范围

注意事项:

  1. 需开启无障碍服务和弹窗权限
  2. 脚本在主线程继续执行其他逻辑时,后台线程会持续监控
  3. 修改目标文本时请确保字符串准确性
  4. 复杂界面建议配合 desc() 等属性联合判断

如果需要处理动态加载内容,建议在检查逻辑中增加:

if (!textView.isSameLayout()) { 
    // 布局变化时重新扫描
}

补充回答 1

实现点击指定文本右侧图片按钮的方案如下,通过控件层级关系和坐标偏移两种方式实现:

// 方法一:基于控件层级关系(推荐)
function clickRightImageByText(text) {
    // 查找文本控件
    var textControl = text(text).findOne(2000);
    if (!textControl) {
        toastLog("未找到文本控件");
        return false;
    }
    
    // 获取父容器
    var parent = textControl.parent();
    if (!parent) {
        toastLog("未找到父容器");
        return false;
    }
    
    // 遍历父容器子控件(假设图片是文本控件之后的兄弟节点)
    for (var i = 0; i < parent.childCount(); i++) {
        var child = parent.child(i);
        if (child.className() === "ImageView" && child.bounds().left > textControl.bounds().right) {
            if (child.click()) {
                toastLog("成功点击右侧图片按钮");
                return true;
            }
        }
    }
    return false;
}

// 示例调用
// clickRightImageByText("设置");

// 方法二:基于坐标偏移(备用方案)
function clickRightImageByText2(text) {
    var target = text(text).findOne(2000);
    if (!target) {
        toastLog("未找到目标文本");
        return false;
    }
    
    var bounds = target.bounds();
    // 在文本右侧1/4高度处点击
    var x = bounds.right + 5;
    var y = bounds.top + (bounds.height() * 0.75);
    
    // 执行点击
    if (click(x, y)) {
        toastLog("已执行坐标点击");
        return true;
    }
    return false;
}

实现原理:

  1. 层级关系法

    • 通过 textControl.parent() 获取文本控件的父容器
    • 遍历父容器的子控件,寻找满足两个条件的 ImageView:
      • 类型为 ImageView(图片控件)
      • 水平位置在文本控件右侧
  2. 坐标偏移法

    • 获取文本控件的边界 bounds()
    • 在其右侧 5px 偏移量位置,垂直方向 75% 高度处计算点击坐标
    • 使用 click(x, y) 执行点击

使用建议:

  1. 优先使用层级关系法,稳定性更高
  2. 对于复杂布局建议:
    • 在代码中添加 toastLog("控件结构: " + textControl.parent().toString()) 调试布局
    • 使用布局分析工具确认控件层级关系
  3. 对于图标按钮可结合 desc() 属性判断:
    if (child.desc().includes("设置")) { /* 特定描述匹配 */ }
    

注意事项:

  • 两种方法都建议添加超时检测(示例中使用 2000ms 超时)
  • 坐标法需要根据实际布局调整偏移量(示例中的 5px)
  • 确保脚本已获得无障碍服务权限
  • 复杂界面建议组合使用 className, desc, id 等属性进行精确匹配

补充回答 2

为避免后台脚本长时间运行后停止执行,可采用以下结构化解决方案:

// 文档地址 https://docs.hamibot.com/
// 主线程监控机制
(function main() {
    // 设置最大运行时间(单位:毫秒)
    const MAX_RUNTIME = 60 * 60 * 1000; // 1小时
    let startTime = new Date().getTime();
    
    // 创建子线程执行任务
    let workerThread = threads.start(function() {
        try {
            while (true) {
                // 1. 核心任务逻辑
                executeTask();
                
                // 2. 运行时间检查
                if (new Date().getTime() - startTime > MAX_RUNTIME) {
                    toastLog("达到最大运行时间,重启任务");
                    startTime = new Date().getTime();
                }
                
                // 3. 定期休眠释放资源
                sleep(5000);
            }
        } catch (e) {
            toastLog("子线程异常: " + e.message);
            threads.shutDownAll(); // 异常时清理线程
        }
    });
    
    // 定期心跳检测机制
    setInterval(() => {
        if (!workerThread.isAlive()) {
            toastLog("检测到线程终止,重新启动");
            workerThread = threads.start(function() {
                // 重启任务逻辑
                while(true) {
                    executeTask();
                    sleep(5000);
                }
            });
        }
    }, 10 * 60 * 1000); // 每10分钟检测一次
})();

// 独立任务模块
function executeTask() {
    // 示例任务:查找特定文本并点击
    try {
        // 1. 设置执行超时
        let timeout = 3000; // 任务阶段超时时间
        let taskStart = Date.now();
        
        // 2. 实际操作逻辑
        if (text("目标文本").exists()) {
            click("目标文本");
        }
        
        // 3. 阶段超时检测
        if (Date.now() - taskStart > timeout) {
            toastLog("任务阶段超时,进入下一轮");
        }
    } catch (e) {
        toastLog("任务阶段异常: " + e.message);
    }
}

关键防护机制解析:

  1. 时间分区机制(MAX_RUNTIME)

    • 设置最大运行时间阈值(推荐1小时)
    • 到达阈值后自动重置计时器
    • 避免单次运行过长导致系统资源回收
  2. 线程健康监控

    setInterval(() => {
        if (!workerThread.isAlive()) {
            // 线程异常恢复逻辑
        }
    }, 10 * 60 * 1000);
    
    • 每10分钟检测线程存活状态
    • 异常终止时自动重启线程
    • 确保后台任务持续可用
  3. 任务阶段超时控制

    let timeout = 3000;
    let taskStart = Date.now();
    // ...执行操作...
    if (Date.now() - taskStart > timeout) {
        // 强制进入下一轮循环
    }
    
    • 防止单个任务阶段无限执行
    • 建议设置为实际操作所需时间的1.5-2倍
  4. 异常处理体系

    try {
        // 核心逻辑
    } catch (e) {
        // 统一异常捕获
        threads.shutDownAll(); // 清理所有线程
    } finally {
        // 资源释放逻辑
    }
    
    • 全局异常捕获防止线程崩溃
    • 确保异常时正确清理资源

高级优化建议:

  1. 添加内存监控
setInterval(() => {
    // 获取内存使用信息(单位MB)
    let memory = parseFloat(java.lang.Runtime.getRuntime().totalMemory() / 1024 / 1024).toFixed(2);
    if (memory > 50) { // 设置内存阈值
        toastLog("内存使用过高(" + memory + "MB),重启脚本");
        engines.execScriptFile(engine.cwd()); // 重启当前脚本
    }
}, 5 * 60 * 1000);
  1. 实现优雅的线程退出:
// 主线程注册退出钩子
events.on("exit", () => {
    // 执行清理操作
    workerThread.int
ℹ️ 本回答由 AI 生成,内容仅供参考