 | |  |  | 多线程GET下载轮询等待工具
- /**
- * AIWROK 轮询等待工具 - 演示脚本(录屏版)
- * 故事线:模拟 APK 下载 → 等待文件生成 → 等待安装完成 → 成功启动
- *
- */
- importClass(java.lang.Thread);
- // ======================== 基础 ========================
- function doSleep(ms) { try { Thread.sleep(ms); } catch (e) {} }
- // 带节奏的打印:换行 + 延时
- function step(msg, delayMs) {
- printl(msg);
- doSleep(delayMs || 300);
- }
- function separator() {
- printl("");
- printl("──────────────────────────────────────────────────");
- printl("");
- }
- // ======================== 通用轮询(核心) ========================
- /**
- * 轮询等待某个条件成立
- * @param {Function} condition —— 每次返回 truthy 表示成立
- * @param {int} timeoutMs —— 总超时(默认 10s)
- * @param {int} intervalMs —— 轮询间隔(默认 200ms)
- * @param {String} label —— 日志标签
- * @returns {Object} { success, waitedMs, lastValue, rounds }
- */
- function poll(condition, timeoutMs, intervalMs, label) {
- timeoutMs = timeoutMs || 10000;
- intervalMs = intervalMs || 200;
- label = label || "轮询";
- var start = new Date().getTime();
- var rounds = 0;
- var lastVal = null;
- while (true) {
- lastVal = condition();
- if (lastVal) {
- var waited = new Date().getTime() - start;
- printl(" ✅ [" + label + "] 条件达成!第 " + rounds + " 次轮询 · 耗时 " + waited + " ms");
- return { success: true, waitedMs: waited, lastValue: lastVal, rounds: rounds };
- }
- // 每 5 次打一次进度(录屏看着不密不疏)
- if (rounds % 5 === 0) {
- var elapsed = rounds * intervalMs;
- printl(" ⏳ [" + label + "] 等待中... 已轮询 " + rounds + " 次 · " + elapsed + " / " + timeoutMs + " ms");
- }
- if (new Date().getTime() - start >= timeoutMs) {
- printl(" ❌ [" + label + "] 超时!(" + timeoutMs + " ms 内未达成)");
- return { success: false, waitedMs: timeoutMs, lastValue: lastVal, rounds: rounds };
- }
- doSleep(intervalMs);
- rounds++;
- }
- }
- // ======================== 便捷方法 ========================
- /** 等待文件存在 */
- function waitForFile(path, timeoutMs, intervalMs) {
- return poll(function() {
- try { return new java.io.File(path).exists(); } catch (e) { return false; }
- }, timeoutMs, intervalMs, "文件出现 " + path);
- }
- /** 等待文件大小 >= minBytes(模拟下载中) */
- function waitForFileSize(path, minBytes, timeoutMs, intervalMs) {
- var label = "文件大小 ≥ " + minBytes + " 字节";
- return poll(function() {
- try {
- var f = new java.io.File(path);
- if (!f.exists()) return false;
- var size = f.length();
- // 顺便打印当前进度(录屏好看)
- if (size > 0 && Math.random() < 0.3) {
- printl(" 📥 当前大小: " + size + " / " + minBytes + " 字节 " +
- Math.floor(size / minBytes * 100) + "%");
- }
- return size >= minBytes;
- } catch (e) { return false; }
- }, timeoutMs, intervalMs, label);
- }
- /** 等待 HTTP 200 */
- function waitForHttp(url, timeoutMs, intervalMs) {
- return poll(function() {
- try {
- var res = http.get(url);
- return res && res.code === 200;
- } catch (e) { return false; }
- }, timeoutMs, intervalMs, "接口 " + url);
- }
- /** 等待某条件返回 true,同时打印阶段标题 */
- function stage(title, condition, timeoutMs, intervalMs, label) {
- printl("");
- printl(" ▶ " + title);
- // 造一条下划线(ES5 兼容)
- var line = " ";
- for (var i = 0; i < title.length + 2; i++) line += "─";
- printl(line);
- return poll(condition, timeoutMs, intervalMs, label || title);
- }
- // ======================== 录屏演示 ========================
- function demo() {
- // ══════════════════════════════════════════════════
- // 开场
- // ══════════════════════════════════════════════════
- printl("╔══════════════════════════════════════════════╗");
- printl("║ ║");
- printl("║ 🛠️ AIWROK sleep 轮询等待工具 演示 ║");
- printl("║ ║");
- printl("║ 「模拟完整 APK 下载 → 安装流程」 ║");
- printl("║ ║");
- printl("╚══════════════════════════════════════════════╝");
- doSleep(1200); // 给录屏一个"标题定帧"时间
- printl("");
- printl("📖 场景说明:");
- printl(" 我们将模拟一个完整的自动化流程 ——");
- printl(" 从服务器下载 APK 文件 → 等待写入磁盘 →");
- printl(" 等待文件大小达标 → 最后验证接口就绪。");
- printl("");
- printl(" 用到的核心函数只有一个:poll()");
- printl(" 它负责:每隔 N ms 检查一次条件,");
- printl(" 条件成立立即返回,超时则返回失败。");
- printl("");
- doSleep(1500);
- // ══════════════════════════════════════════════════
- // 第一幕:后台线程模拟下载
- // ══════════════════════════════════════════════════
- separator();
- printl("【第一幕】启动后台下载任务");
- doSleep(800);
- var apkPath = "/sdcard/Download/demo_app_v1.2.apk";
- var targetSize = 4096; // 模拟 4KB 的"APK"(实际写文字进去)
- // 先清理残留
- try { new java.io.File(apkPath).delete(); } catch (e) {}
- step("📱 目标 APK: " + apkPath, 500);
- step("📦 模拟大小: " + targetSize + " 字节", 500);
- step("🚀 启动下载线程...", 800);
- // 后台线程:分 4 次逐步写入文件(模拟渐进下载)
- new thread().runJsCode(function() {
- printl(" [后台] 连接服务器...");
- doSleep(400);
- printl(" [后台] 开始下载...");
- var w = new java.io.FileWriter(apkPath);
- var chunk = 1024;
- var total = 0;
- for (var i = 0; i < 4; i++) {
- // 写 1KB:循环写单字符
- var data = "";
- for (var j = 0; j < chunk; j++) data += String.fromCharCode(65 + i);
- w.write(data);
- w.flush();
- total += chunk;
- printl(" [后台] 已写入 " + total + " / " + targetSize + " 字节");
- doSleep(500); // 故意慢一点,让轮询有东西可看
- }
- w.close();
- printl(" [后台] ✅ 下载完成,文件已落盘");
- }, "APK下载线程");
- doSleep(800); // 让后台线程先跑起来打一行
- // ══════════════════════════════════════════════════
- // 第二幕:轮询等待文件出现
- // ══════════════════════════════════════════════════
- separator();
- printl("【第二幕】主线程轮询等待文件出现");
- printl(" 场景:后台在下载,主线程不能干等,");
- printl(" 需要时刻盯着文件是否已创建。");
- doSleep(1000);
- var r1 = stage("等待 APK 文件出现...", function() {
- try { return new java.io.File(apkPath).exists(); } catch (e) { return false; }
- }, 5000, 200, "waitForFile");
- if (r1.success) {
- step("📂 文件已在磁盘上!耗时 " + r1.waitedMs + " ms", 600);
- } else {
- step("❌ 文件迟迟没出现,下载可能卡住了", 600);
- }
- doSleep(1000);
- // ══════════════════════════════════════════════════
- // 第三幕:轮询等待文件大小达标
- // ══════════════════════════════════════════════════
- separator();
- printl("【第三幕】继续等 —— 文件大小达标才算下载完成");
- printl(" 场景:文件虽然创建了,但可能只写了一半。");
- printl(" 需要等大小 ≥ 期望值,才能开始安装。");
- doSleep(1000);
- var r2 = waitForFileSize(apkPath, targetSize, 8000, 300);
- if (r2.success) {
- step("📊 文件大小达标!已完整写入 " + targetSize + " 字节", 600);
- }
- doSleep(1000);
- // ══════════════════════════════════════════════════
- // 第四幕:演示超时场景(等一个不存在的东西)
- // ══════════════════════════════════════════════════
- separator();
- printl("【第四幕】演示:条件永远不成立 → 触发超时");
- printl(" 场景:实际业务中经常遇到 ——");
- printl(" 网络断了、接口挂了、权限没给...");
- printl(" poll() 不会无限等,到点就返回 false。");
- doSleep(1200);
- var fakePath = "/sdcard/this_file_never_exists.xyz";
- step("🔍 去等一个不存在的文件: " + fakePath, 600);
- step("⏱️ 超时设为 1.5 秒,间隔 300 ms", 600);
- var r3 = waitForFile(fakePath, 1500, 300);
- step("📝 poll() 返回值: { success: " + r3.success + ", waitedMs: " + r3.waitedMs + " }", 800);
- step("💡 业务代码里可以这样写:", 400);
- printl(" var r = waitForFile(xxx, 5000);");
- printl(" if (!r.success) { printl(\"下载超时,重试或报错\"); }");
- doSleep(1200);
- // ══════════════════════════════════════════════════
- // 第五幕:自定义条件 —— 组合使用
- // ══════════════════════════════════════════════════
- separator();
- printl("【第五幕】自定义条件 & 组合使用");
- printl(" poll() 的第一个参数是任意函数 ——");
- printl(" 想等啥就写啥,完全不挑。");
- doSleep(1000);
- // 演示:等文件出现 AND 大小达标(合并条件)
- step("🎯 示例:等「文件存在 AND 大小 ≥ 80%」—— 一条 poll 搞定", 800);
- var r4 = poll(function() {
- try {
- var f = new java.io.File(apkPath);
- if (!f.exists()) return false;
- return f.length() >= targetSize * 0.8;
- } catch (e) { return false; }
- }, 5000, 200, "文件存在 + 大小 ≥ 80%");
- if (r4.success) {
- step("🎊 两条条件同时满足!文件已准备好安装", 800);
- }
- doSleep(1200);
- // ══════════════════════════════════════════════════
- // 收尾
- // ══════════════════════════════════════════════════
- separator();
- printl("╔══════════════════════════════════════════════╗");
- printl("║ ║");
- printl("║ ✨ 演示总结 ║");
- printl("║ ║");
- printl("║ poll(condition, timeout, interval, label) ║");
- printl("║ ║");
- printl("║ · 想等控件? → waitForView() ║");
- printl("║ · 想等文字? → waitForText() ║");
- printl("║ · 想等文件? → waitForFile() ║");
- printl("║ · 想等接口? → waitForHttp() ║");
- printl("║ · 想等啥都行 → poll(function(){ ... }) ║");
- printl("║ ║");
- printl("╚══════════════════════════════════════════════╝");
- doSleep(2000);
- // 清理
- try { new java.io.File(apkPath).delete(); } catch (e) {}
- printl("");
- printl("🎬 演示结束,感谢观看!");
- }
- demo();
复制代码
| |  | |  |
|