findOne、find、waitFor — 查找方法全解析
方法对比表
| 方法 | 等待超时 | 返回值 | 找不到时 | 适用场景 |
|---|---|---|---|---|
findOne(timeout) | ✅ 默认 10s | UiObject 或 null | 返回 null | 最常用,等控件出现再找 |
findOnce() | ❌ 立即 | UiObject 或 null | 返回 null | 已知控件存在,立即查找 |
find() | ❌ 立即 | UiObject[] | 返回空数组 | 找所有匹配控件 |
exists(timeout) | ✅ 可选 | boolean | 返回 false | 只关心"有没有" |
waitFor(timeout) | ✅ 必选 | void | 超时抛异常 | 阻塞等待控件出现 |
waitUntilGone(timeout) | ✅ 必选 | boolean | 超时返回 false | 等待控件消失 |
findOne — 最常用的方法
// 无参,默认 10 秒超时
let btn = text("登录").findOne();
// 指定 5 秒超时
let btn = text("登录").findOne(5000);
// 必须判空!找不到返回 null,不是抛异常
let btn = text("登录").findOne(2000);
if (btn) {
btn.click();
} else {
console.log("控件未出现");
}
findOne 的超时行为
// 内部流程:
// 0ms → 查找一次 → 没找到 → 等 200ms
// 200ms → 查找一次 → 没找到 → 等 200ms
// ...(循环)
// 9800ms → 查找一次 → 没找到 → 退出,返回 null
findOne 在超时时间内每 200ms 重试一次,直到找到或超时。这比你自己写循环高效得多——每次查找都会刷新无障碍服务的控件树缓存。
findOnce — 立即查找
// 只查找一次,不等待
let btn = text("登录").findOnce();
if (btn) {
btn.click();
} else {
// 控件还没出现或不存在
}
适用场景:
- 已经确认控件在屏幕上,不想浪费等待时间
- 在
waitFor之后立即查找(waitFor已经等待过了)
// 先等出现,再一次性查找
text("登录").waitFor(5000);
let btn = text("登录").findOnce(); // 此时肯定存在
btn.click();
find — 找全部匹配
// 返回数组
let allButtons = className("Button").find();
console.log("共找到 " + allButtons.length + " 个按钮");
// 遍历
allButtons.forEach(btn => {
console.log("按钮文字: " + (btn.text() || "(无)"));
console.log(" 位置: " + btn.bounds());
});
find 的典型用途
// 场景一:批量操作
let checkboxes = className("CheckBox").find();
checkboxes.forEach(cb => {
if (!cb.checked()) {
cb.click(); // 全选
}
});
// 场景二:在列表中找某一项
let items = id("list_item").find();
let target = items.find(item => item.text().includes("目标关键词"));
if (target) {
target.click();
}
exists — 只判断存在性
// exists 返回 boolean,不返回控件对象
if (text("确定").exists()) {
// 存在 → 再查一次获取对象
text("确定").findOne().click();
} else {
// 不存在
}
// exists 也接受超时
if (text("加载中").exists(3000)) {
// 3 秒内出现了
}
注意
exists 和 findOne 都可能要等待。如果你既需要判断存在又需要操作控件,直接用 findOne:
// ❌ 不好:两次等待
if (text("登录").exists()) {
text("登录").findOne().click(); // 这里又等了一次
}
// ✅ 好:一次等待
let btn = text("登录").findOne(3000);
if (btn) {
btn.click();
}
waitFor — 阻塞等待
// 阻塞直到控件出现,默认 10 秒
text("登录").waitFor();
// 自定义超时
text("登录").waitFor(5000);
// 超时未出现会抛异常!需要 catch
try {
text("登录").waitFor(3000);
// 到这里控件肯定存在
} catch (e) {
console.error("控件 3 秒内未出现");
}
waitFor 不会返回控件对象,只是等待。等完需要自己再 findOnce()。
waitUntilGone — 等待消失
// 等待加载圈消失
let gone = id("loading_indicator").waitUntilGone(5000);
if (gone) {
console.log("加载完成");
} else {
console.log("加载超时");
}
层级内查找
所有查找方法都可以在指定控件范围内执行,而不是全屏幕:
// 先找容器
let listContainer = id("goods_list").findOne(5000);
// 在容器内找子项(只在这个容器的子树里查找)
let target = listContainer.findOne(textContains("目标商品"));
// 也可以
let allButtonsInList = listContainer.find(className("Button"));
性能与缓存
查找成本
findOnce → 最低,只查找一次,刷新缓存
findOne → 中等,最多 50 次查找(10s / 200ms)
find → 中高,遍历整棵控件树
控件树刷新
每次查找都会触发无障碍服务刷新控件树缓存。以下情况会自动刷新:
- 窗口内容变化
findOne超时重试- 手动调用
auto.refresh()
// 某些情况下手动刷新
auto.refresh(); // 强制刷新控件树缓存
最佳实践
组合策略
// ✅ 推荐组合
// 1. 先等页面加载好
waitForActivity("com.example/.MainActivity", 5000);
// 2. 找关键控件操作
let btn = id("action_btn").findOne(3000);
if (btn) {
btn.click();
} else {
// 页面可能没加载好,处理异常
}
避免的陷阱
| 陷阱 | 原因 | 解决 |
|---|---|---|
findOne 后不判空 | 返回 null 导致崩溃 | if (btn) 判空 |
find().length === 0 | 空数组不是 null | 直接 .forEach(),空数组自然不执行 |
| 在 while 循环里手动 findOnce | 效率低 | 直接用 findOne(timeout) |
忘了 waitFor 超时抛异常 | 脚本意外中断 | 加 try-catch 或改用 findOne |