找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 29|回复: 1

定时器驱动的采集数据报表闭环

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
定时器驱动的采集数据报表闭环

QQ截图20261029204817.png


QQ截图20261029204803.png


QQ截图20261029063339.png


  1. /**
  2. * 案例26:定时器驱动的采集数据报表闭环
  3. *
  4. * 三部曲(24→25→26)第三篇:数据流水线**自己转**。
  5. * 案例24/25 的"统计+导出"靠主循环 while+sleep 推动,脚本一停就断;
  6. * 本案例换成 runTime.setInterval:注册两个定时器后主循环纯待机——
  7. *   ⏱ 每 15 秒:四库统计任务(总条数/命中数,一行日志)
  8. *   ⏱ 每 45 秒:CSV 导出任务(控件明细带 BOM+转义落盘 /sdcard/报表/)
  9. *   两个任务各干各的,第 3 次统计时导出刚好到期——"流水线节拍"演示。
  10. *
  11. * 教学对照(事件驱动三部曲收口):
  12. *   案例20~25 主循环 = 脚本**自己踩点**(while+sleep,停了就断);
  13. *   案例21 = **系统事件**驱动(通知来了才动);
  14. *   案例26 = **自建定时器**驱动(setInterval 注册后主循环彻底放手)——
  15. *   挂机脚本的标准形态:初始化一次,之后全在回调里,主线程只负责心跳和退出。
  16. *
  17. * 新知识点:
  18. * 1. runTime.setInterval(函数, 毫秒) 返回定时器 id;runTime.clearInterval(id) 停表
  19. *    (生产脚本 mgkj.js 心跳/日志刷新的同款用法);
  20. * 2. **回调里必须整体 try/catch**:定时器回调抛异常没兜住,表可能直接停摆,
  21. *    主循环却在傻等——挂机脚本半夜断流的经典成因;
  22. * 3. 全局计数器(统计次数/导出次数)在回调里++、在主循环心跳里读——
  23. *    回调和主循环共享同一套全局变量,这就是"模块间通信最省事的办法";
  24. * 4. 停止顺序:置标志 → clearInterval 全部定时器 → stopAllRuning 放最后
  25. *    (不 clear 的话脚本停了回调还可能被触发一次,清理逻辑跑在已关的库上)。
  26. *
  27. * 前置:先跑案例20~23 攒数据(没数据也能跑,统计全 0,导出跳过——容错演示)。
  28. */

  29. // ========== 配置区(改这里) ==========
  30. var 统计周期毫秒 = 15000;   // 每 15 秒一轮四库统计
  31. var 导出周期毫秒 = 45000;   // 每 45 秒一次 CSV 导出(统计的 3 倍节拍)
  32. var CSV最大行数 = 200;
  33. var 报表目录 = "/sdcard/报表";
  34. var 明细库路径 = "/sdcard/控件采集.db";
  35. var 明细表名 = "ui_records";
  36. var 采集库列表 = [
  37.     { 名称: "OCR",     路径: "/sdcard/屏幕文字.db",   表名: "ocr_records" },
  38.     { 名称: "通知",    路径: "/sdcard/通知消息.db",   表名: "notify_records" },
  39.     { 名称: "剪贴板", 路径: "/sdcard/剪贴板内容.db", 表名: "clipboard_records" },
  40.     { 名称: "控件",    路径: "/sdcard/控件采集.db",   表名: "ui_records" }
  41. ];

  42. // ========== 全局变量顶部声明 ==========
  43. var gStop = false;
  44. var 统计定时器id = null;
  45. var 导出定时器id = null;
  46. var 累计统计次数 = 0;      // 回调里++、心跳里读——全局变量就是回调与主循环的通信总线
  47. var 累计导出次数 = 0;
  48. var 最近一轮统计 = "暂无";  // 缓存最近统计结果,心跳日志里回放

  49. // ========== importClass 只能放最外层 ==========
  50. importClass(android.database.sqlite.SQLiteDatabase);
  51. importClass(java.io.File);
  52. importClass(java.io.FileWriter);
  53. importClass(java.text.SimpleDateFormat);
  54. // 踩坑:java.util.Date 不能 importClass(和 JS 内置 Date 撞名,Rhino 拒绝导入),用全限定名

  55. // ========== 工具函数(先定义后调用) ==========

  56. // CSV 字段转义(案例25 同款:含逗号/引号/换行必须引号包裹+引号翻倍)
  57. function CSV转义(值) {
  58.     var s = "" + 值;
  59.     if (s.indexOf(",") >= 0 || s.indexOf(""") >= 0 || s.indexOf("\n") >= 0 || s.indexOf("\r") >= 0) {
  60.         return """ + s.replace(/"/g, """") + """;
  61.     }
  62.     return s;
  63. }

  64. // 统计任务(定时器1 的回调):四库 COUNT 一行汇总——整体 try/catch,踩坑点2
  65. // 实测大坑:停止/重跑脚本后,旧实例的 setInterval 可能没被清掉继续跑(日志里会看到
  66. // 别的脚本运行期间冒出"统计任务#N"、计数跳号)——回调第一行必须查 gStop 闸门自杀
  67. function 统计任务() {
  68.     if (gStop) {
  69.         try { if (统计定时器id != null) runTime.clearInterval(统计定时器id); } catch (eStop) {}
  70.         return;
  71.     }
  72.     try {
  73.         累计统计次数++;
  74.         var 片段 = [];
  75.         var 总条数 = 0;
  76.         for (var i = 0; i < 采集库列表.length; i++) {
  77.             var 库 = 采集库列表[i];
  78.             var f = new File(库.路径);
  79.             if (!f.exists()) { 片段.push(库.名称 + "×无库"); continue; }
  80.             var db = null;
  81.             try {
  82.                 db = SQLiteDatabase.openDatabase(库.路径, null, SQLiteDatabase.OPEN_READONLY);
  83.                 var c = db.rawQuery("SELECT COUNT(*) FROM " + 库.表名, null);
  84.                 var n = 0;
  85.                 if (c.moveToNext()) n = c.getInt(0);
  86.                 c.close();
  87.                 总条数 = 总条数 + n;
  88.                 片段.push(库.名称 + "×" + n);
  89.             } catch (e库) {
  90.                 片段.push(库.名称 + "×异常");
  91.             } finally {
  92.                 if (db != null) { try { db.close(); } catch (e2) {} }
  93.             }
  94.         }
  95.         最近一轮统计 = 片段.join(" | ") + " | 合计 " + 总条数 + " 条";
  96.         printl("📈 [统计任务#" + 累计统计次数 + "] " + 最近一轮统计);
  97.     } catch (e) {
  98.         // 踩坑点2:回调抛异常不兜住,定时器可能停摆,主循环却还在傻等
  99.         printl("⚠ 统计任务异常(已兜住,定时器继续跑): " + e);
  100.     }
  101. }

  102. // 导出任务(定时器2 的回调):控件明细 CSV(BOM 头,时间戳文件名)——同样 gStop 闸门+try/catch
  103. function 导出任务() {
  104.     if (gStop) {
  105.         try { if (导出定时器id != null) runTime.clearInterval(导出定时器id); } catch (eStop) {}
  106.         return;
  107.     }
  108.     try {
  109.         var f = new File(明细库路径);
  110.         if (!f.exists()) {
  111.             printl("ℹ [导出任务] 明细库不存在(案例23 没跑过),本轮跳过");
  112.             return;
  113.         }
  114.         累计导出次数++;
  115.         var db = null;
  116.         try {
  117.             db = SQLiteDatabase.openDatabase(明细库路径, null, SQLiteDatabase.OPEN_READONLY);
  118.             var c = db.rawQuery("SELECT page, text, class_name, cx, cy, collected_at FROM " +
  119.                 明细表名 + " ORDER BY id DESC LIMIT " + CSV最大行数, null);
  120.             var 行们 = [];
  121.             行们.push("页面,控件文字,类名,中心X,中心Y,采集时间");
  122.             var fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  123.             var 行数 = 0;
  124.             while (c.moveToNext()) {
  125.                 行数++;
  126.                 行们.push(CSV转义(c.getString(0)) + "," + CSV转义(c.getString(1)) + "," +
  127.                     CSV转义(c.getString(2)) + "," + CSV转义(c.getString(3)) + "," +
  128.                     CSV转义(c.getString(4)) + "," + CSV转义(fmt.format(new java.util.Date(c.getLong(5)))));
  129.             }
  130.             c.close();
  131.             var 路径 = 报表目录 + "/定时明细_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new java.util.Date()) + ".csv";
  132.             var ff = new File(路径);
  133.             if (ff.getParentFile() != null && !ff.getParentFile().exists()) ff.getParentFile().mkdirs();
  134.             var writer = new FileWriter(ff);
  135.             try {
  136.                 writer.write("\uFEFF" + 行们.join("\r\n") + "\r\n");   // BOM 第一个写,Excel 中文不乱码
  137.                 writer.flush();
  138.             } finally {
  139.                 writer.close();
  140.             }
  141.             printl("💾 [导出任务#" + 累计导出次数 + "] " + 行数 + " 行 → " + 路径 + " (" + ff.length() + " 字节)");
  142.         } finally {
  143.             if (db != null) { try { db.close(); } catch (e3) {} }
  144.         }
  145.     } catch (e) {
  146.         printl("⚠ 导出任务异常(已兜住,定时器继续跑): " + e);
  147.     }
  148. }

  149. // ========== 主流程 ==========
  150. printl("===== 案例26 定时器驱动报表闭环 启动 =====");
  151. printl("⚙ 配置:统计每 " + (统计周期毫秒 / 1000) + " 秒 · 导出每 " + (导出周期毫秒 / 1000) + " 秒 · 库: " + 采集库列表.length + " 个 · 报表: " + 报表目录);
  152. printl("ℹ 三部曲第三篇:流水线自己转——注册完 setInterval 主循环彻底放手,任务全在回调里跑");
  153. printl("ℹ 对照:案例20~25 主循环自己踩点(while+sleep) | 案例21 系统事件驱动 | 本案例自建定时器驱动(挂机脚本标准形态)");

  154. // 注册定时器(返回 id,停止时 clearInterval 用)
  155. 统计定时器id = runTime.setInterval(统计任务, 统计周期毫秒);
  156. 导出定时器id = runTime.setInterval(导出任务, 导出周期毫秒);
  157. printl("⏱ 两个定时器已注册:统计 id=" + 统计定时器id + " | 导出 id=" + 导出定时器id);
  158. printl("ℹ 注册完就没人管了——下面主循环只做心跳,每 10 秒回放最近统计(挂机脚本形态)");

  159. // 停止按钮清理(顺序:置标志 → 停全部定时器 → stopAllRuning 放最后;
  160. // 不先 clearInterval,脚本停了回调还可能被触发,清理逻辑跑在已关的库上)
  161. event.onStopBtnEvent(function () {
  162.     gStop = true;
  163.     try { if (统计定时器id != null) runTime.clearInterval(统计定时器id); } catch (e1) {}
  164.     try { if (导出定时器id != null) runTime.clearInterval(导出定时器id); } catch (e2) {}
  165.     runTime.stopAllRuning();
  166. });

  167. // 心跳主循环:只读全局变量打进度,真正的活在两个回调里干
  168. var 心跳计数 = 0;
  169. while (!gStop) {
  170.     心跳计数++;
  171.     printl("💤 心跳#" + 心跳计数 + " | 统计跑了 " + 累计统计次数 + " 轮 | CSV 导了 " + 累计导出次数 + " 版 | 最近统计: " + 最近一轮统计);
  172.     sleep.second(秒 = 10);
  173. }

  174. printl("===== 流程结束:统计 " + 累计统计次数 + " 轮,导出 " + 累计导出次数 + " 版,定时器已清理 =====");
复制代码


0

主题

2

回帖

4

积分

软件网站新手

积分
4
发表于 半小时前 | 显示全部楼层

定时器驱动的采集数据报表闭环

看完了,这个定时器驱动的采集数据报表闭环挺有意思的。你那张图展示的统计和导出任务是怎么设置的呢?15秒和45秒的间隔是怎么来的?还有,具体用的是什么编程语言实现的?
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|

GMT+8, 2026-10-30 07:43

安卓脚本软件 · 键鼠盒子 · HID · 机械臂

快速回复 返回顶部 返回列表

随机推荐 · 热帖 / 新帖 / 随便看看

社区会员(随机展示)