热点排行

手势操作与位移实测

436

主题

-245

回帖

2281

积分

管理员

积分
2281
手势操作与位移实测

QQ截图20261102083017.png


QQ截图20261102082958.png


QQ截图20261102082950.png

  1. /**
  2. * 案例29:手势操作与位移实测(独立演示窗版)
  3. *
  4. * 演进史(三轮实测锤死的教训,比代码值钱):
  5. *   v1 打设置页:openApp 之后 H5 日志窗还盖在最前台,手势全打进 WebView,
  6. *      设置页在后台根本没被摸到(快照"0/18 共同文字"抓的就是日志窗自己的节点);
  7. *   v2 打日志终端:H5→脚本反向桥(onH5Gesture/test)实测拿不到——start.js 定义的
  8. *      函数没暴露成 window 全局;改走 web1.evaluateJavascript(小写j!LogBridge 找
  9. *      大写S 一直没发现)带返回值主动读,通道是通了,但**日志终端天生不能当手势靶**:
  10. *      addLog 每条自动滚到底,列表永远贴底,滑了也白滑,scrollDx 恒 0;
  11. *   v3(本版)独立演示窗:new activity() 再开一个窗口,加载 演示页.html——
  12. *      40 条静态列表不会自动滚底,滚到哪停在哪;探针计数直接显示在页面顶上,
  13. *      手势有没有打中**肉眼直接可见**,脚本端 evaluateJavascript 轮询读数只是留档。
  14. *
  15. * 六个手势实验轮转(全部打在演示窗列表上):
  16. *   ① swipPercent_v2 快滑上 ② 快滑下 ③ drag 慢拖上2200ms
  17. *   ④ longClickPercent 长按 ⑤ dispatchGesture 折线 ⑥ clickPercent 单击
  18. * 每轮:resetGestureProbe 清零 → 手势 → 轮询读 getGestureReport(最长3秒)→
  19. * 若 touch=0 用 adb shell input 设备级注入对照 → 验收 → 入库。
  20. *
  21. * 新知识点:
  22. * 1. 一个脚本可以 new 多个 activity() 开多个窗口:演示窗在前台挨打,日志窗后台收 printl;
  23. * 2. WebView 带返回值读:web.evaluateJavascript(code, function(res){...})——
  24. *    回调在 UI 线程落地有延迟,**必须轮询等数据**,固定 sleep 会读空(实测锤死);
  25. * 3. auto.swipPercent_v2(x1%,y1%,x2%,y2%,滑动毫秒,延时抬起毫秒);
  26. * 4. auto.drag(x1,y1,x2,y2,时长毫秒)像素、无惯性;auto.longClickPercent(x%,y%,毫秒);
  27. * 5. auto.clickPercent(x%,y%);auto.dispatchGesture([path])——new path()+setDurTime+addPoint(像素);
  28. * 6. agent.shell("input swipe/tap ...")——设备级注入,auto 打不中时的对照组。
  29. *
  30. * 实测踩坑表(跑完按日志修订):
  31. * - 回调延迟:读探针的 👆 回报经常比脚本解析晚到一轮——"零回报"和"感觉到了"同轮矛盾
  32. *   就是这么来的,本版改轮询等待修复;
  33. * - 键类 API(back/home)会把前台演示窗退掉,别按;
  34. * - auto.* 无障碍手势在 v2 实测页面零感知(touch=0)——本版演示窗是原生前台窗口,
  35. *   若仍零感知而 shell 打中,结论就是"无障碍手势进不了本 App 的 WebView";
  36. * - 日志终端自动滚底,永远别拿它当滚动实验对象。
  37. */

  38. // ========== 配置区(改这里) ==========
  39. var 手势间隔秒 = 10;              // 每 10 秒一轮实验
  40. var 停稳等待毫秒 = 800;           // 手势后等惯性滚完
  41. var 读探针超时毫秒 = 3000;        // 轮询等回调落地的上限(回调有延迟,固定sleep不可靠)
  42. var 滑动幅度 = 0.35;              // 占屏高比例,快滑/慢拖共用方便对比
  43. var 演示页路径 = "/代码/演示页.html";
  44. var SQLite路径 = "/sdcard/手势记录.db";
  45. var 表名 = "gesture_records";     // content_px=页面回报scrollDx, sample=move帧数

  46. // ========== 全局变量顶部声明 ==========
  47. var gStop = false;
  48. var 数据库连接 = null;
  49. var 数据库可用 = false;
  50. var 累计轮数 = 0;
  51. var 累计入库条数 = 0;
  52. var 屏宽 = 0;
  53. var 屏高 = 0;
  54. var gH5报告 = [];                 // 探针回报队列(回调线程往里塞)
  55. var g演示ac = null;               // 演示窗 activity
  56. var g演示web = null;              // 演示窗 WebView

  57. // ========== importClass 只能放最外层 ==========
  58. importClass(android.database.sqlite.SQLiteDatabase);
  59. importClass(java.io.File);

  60. // ========== 探针回报回调(evaluateJavascript 的 ValueCallback 落地处) ==========
  61. function onH5Gesture(msg) {
  62.     var s = "" + msg;
  63.     if (s === "undefined" || s === "null") return;
  64.     s = s.replace(/^"|"$/g, "").replace(/\"/g, '"').replace(/\\n/g, " ");
  65.     gH5报告.push(s);
  66.     printl("👆 演示窗感觉到: " + s);
  67. }

  68. // ========== 演示窗:第二个 activity,静态列表挨打 ==========
  69. function 打开演示窗() {
  70.     try {
  71.         g演示ac = new activity();
  72.         g演示ac.loadXML(
  73.             '<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"' +
  74.             ' android:layout_width="match_parent" android:layout_height="match_parent"' +
  75.             ' android:background="#14060a" android:orientation="vertical">' +
  76.             ' <WebView android:id="@+id/demoweb"' +
  77.             ' android:layout_width="match_parent" android:layout_height="match_parent"/>' +
  78.             '</LinearLayout>'
  79.         );
  80.         g演示web = g演示ac.findWebViewById("demoweb");
  81.         if (!g演示web) { printl("❌ 演示窗 WebView 拿不到(id=demoweb)"); return false; }
  82.         g演示web.url(演示页路径);
  83.         sleep.second(秒 = 3);   // 等页面加载+列表渲染
  84.         printl("🪟 演示窗已打开: " + 演示页路径 + "(静态列表40条,探针计数页面可见)");
  85.         return true;
  86.     } catch (e) {
  87.         printl("❌ 打开演示窗异常: " + e);
  88.         return false;
  89.     }
  90. }

  91. function 关闭演示窗() {
  92.     try { if (g演示ac != null) g演示ac.close(); } catch (e) {}
  93.     g演示ac = null;
  94.     g演示web = null;
  95. }

  96. // ========== 演示窗 JS 通道 ==========
  97. // 单向执行(清零探针用,不等结果)
  98. function 演示JS(code) {
  99.     if (!g演示web) return false;
  100.     try {
  101.         var run = function () {
  102.             try {
  103.                 if (typeof g演示web.evaluateJavascript === "function") {
  104.                     g演示web.evaluateJavascript(code, null);
  105.                 } else { g演示web.loadUrl("javascript:" + code); }
  106.             } catch (e) { printl("⚠ 演示JS 异常: " + e); }
  107.         };
  108.         if (global.ac && typeof global.ac.runOnUiThread === "function") { global.ac.runOnUiThread(run); }
  109.         else { run(); }
  110.         return true;
  111.     } catch (e2) { printl("⚠ 演示JS 外层异常: " + e2); return false; }
  112. }

  113. // 带返回值读:回调塞进 gH5报告,轮询等到数据为止(回调有延迟,固定sleep读空是 v2 实测坑)
  114. function 读演示探针() {
  115.     gH5报告 = [];
  116.     if (!g演示web) return { 滚动: null, move: 0, 触摸: 0, 长按: false, 点击: false, 条数: 0 };
  117.     try {
  118.         var run = function () {
  119.             try { g演示web.evaluateJavascript("getGestureReport()", onH5Gesture); }
  120.             catch (e) { printl("⚠ 读探针异常: " + e); }
  121.         };
  122.         if (global.ac && typeof global.ac.runOnUiThread === "function") { global.ac.runOnUiThread(run); }
  123.         else { run(); }
  124.     } catch (e2) { printl("⚠ 读探针外层异常: " + e2); }
  125.     var 已等 = 0;
  126.     while (gH5报告.length === 0 && 已等 < 读探针超时毫秒 && !gStop) {
  127.         sleep.millisecond(毫秒 = 100);
  128.         已等 = 已等 + 100;
  129.     }
  130.     return 解析回报();
  131. }

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

  133. function 解析回报() {
  134.     var 滚动 = null;
  135.     var move最大 = 0;
  136.     var touch数 = 0;
  137.     var 有长按 = false;
  138.     var 有点击 = false;
  139.     for (var i = 0; i < gH5报告.length; i++) {
  140.         var s = gH5报告[i];
  141.         var m = s.match(/scrollDx=(-?\d+)/);
  142.         if (m) 滚动 = parseInt(m[1], 10);
  143.         var mm = s.match(/move=(\d+)/);
  144.         if (mm && parseInt(mm[1], 10) > move最大) move最大 = parseInt(mm[1], 10);
  145.         var tt = s.match(/touch=(\d+)/);
  146.         if (tt && parseInt(tt[1], 10) > touch数) touch数 = parseInt(tt[1], 10);
  147.         if (s.indexOf("longpress") >= 0) 有长按 = true;
  148.         if (s.indexOf("click") >= 0) 有点击 = true;
  149.     }
  150.     return { 滚动: 滚动, move: move最大, 触摸: touch数, 长按: 有长按, 点击: 有点击, 条数: gH5报告.length };
  151. }

  152. // adb shell input 设备级注入对照(auto 打不中时用,坐标同款)
  153. function shell对照手势(实验名) {
  154.     var cx = Math.round(屏宽 * 0.5);
  155.     var y上 = Math.round(屏高 * 0.40);
  156.     var y下 = Math.round(屏高 * 0.75);
  157.     var y中 = Math.round(屏高 * 0.55);
  158.     var cmd = "";
  159.     if (实验名.indexOf("快滑上") === 0) cmd = "input swipe " + cx + " " + y下 + " " + cx + " " + y上 + " 600";
  160.     else if (实验名.indexOf("快滑下") === 0) cmd = "input swipe " + cx + " " + y上 + " " + cx + " " + y下 + " 600";
  161.     else if (实验名.indexOf("慢拖上") === 0) cmd = "input swipe " + cx + " " + y下 + " " + cx + " " + y上 + " 2200";
  162.     else if (实验名.indexOf("长按") === 0) cmd = "input swipe " + cx + " " + y中 + " " + cx + " " + y中 + " 1500";
  163.     else if (实验名.indexOf("折线") === 0) cmd = "input swipe " + Math.round(屏宽 * 0.30) + " " + y下 + " " +
  164.         Math.round(屏宽 * 0.70) + " " + Math.round(屏高 * (0.75 - 滑动幅度)) + " 800";
  165.     else if (实验名.indexOf("单击") === 0) cmd = "input tap " + cx + " " + y中;
  166.     if (cmd === "") return false;
  167.     try {
  168.         agent.shell(cmd);
  169.         printl("🔧 shell 注入: " + cmd);
  170.         return true;
  171.     } catch (e) {
  172.         printl("⚠ agent.shell 异常: " + e);
  173.         return false;
  174.     }
  175. }

  176. function dbConnect() {
  177.     try {
  178.         数据库连接 = SQLiteDatabase.openOrCreateDatabase(new File(SQLite路径), null);
  179.         数据库可用 = true;
  180.         printl("[DB] ✅ SQLite 已打开: " + SQLite路径);
  181.     } catch (e) {
  182.         数据库可用 = false;
  183.         printl("[DB] ⚠ 打开失败(只打印不入库): " + e);
  184.     }
  185. }

  186. function dbEnsureTable() {
  187.     if (!数据库可用) return;
  188.     try {
  189.         var sql = "CREATE TABLE IF NOT EXISTS " + 表名 + " (" +
  190.             "id INTEGER PRIMARY KEY AUTOINCREMENT," +
  191.             "gesture TEXT, finger_px INTEGER, content_px INTEGER," +
  192.             "sample INTEGER, elapsed_ms INTEGER, collected_at INTEGER)";
  193.         数据库连接.execSQL(sql);
  194.         printl("[DB] ✅ 表已就绪: " + 表名);
  195.     } catch (e) {
  196.         printl("[DB] ⚠ 建表失败: " + e);
  197.         数据库可用 = false;
  198.     }
  199. }

  200. function dbInsertGesture(手势名, 手指位移, 回报滚动, move帧数, 耗时) {
  201.     if (!数据库可用) return false;
  202.     try {
  203.         var stmt = 数据库连接.compileStatement("INSERT INTO " + 表名 +
  204.             " (gesture, finger_px, content_px, sample, elapsed_ms, collected_at) VALUES (?,?,?,?,?,?)");
  205.         stmt.bindString(1, "" + 手势名);
  206.         stmt.bindLong(2, 手指位移);
  207.         stmt.bindLong(3, 回报滚动);
  208.         stmt.bindLong(4, move帧数);
  209.         stmt.bindLong(5, 耗时);
  210.         stmt.bindLong(6, time.nowStamp());
  211.         stmt.executeInsert();
  212.         stmt.close();
  213.         return true;
  214.     } catch (e) {
  215.         printl("[DB] ❌ 入库失败: " + e);
  216.         return false;
  217.     }
  218. }

  219. function dbClose() {
  220.     if (数据库可用 && 数据库连接 != null) {
  221.         try { 数据库连接.close(); } catch (e) {}
  222.         数据库连接 = null;
  223.     }
  224. }

  225. // ========== 六个手势实验(每个返回手指位移像素) ==========

  226. function 实验1_快滑上() {
  227.     auto.swipPercent_v2(0.5, 0.75, 0.5, 0.75 - 滑动幅度, 600, 0);
  228.     return Math.round(屏高 * 滑动幅度);
  229. }
  230. function 实验2_快滑下() {
  231.     auto.swipPercent_v2(0.5, 0.40, 0.5, 0.40 + 滑动幅度, 600, 0);
  232.     return Math.round(屏高 * 滑动幅度);
  233. }
  234. function 实验3_慢拖上() {
  235.     auto.drag(Math.round(屏宽 * 0.5), Math.round(屏高 * 0.75),
  236.         Math.round(屏宽 * 0.5), Math.round(屏高 * (0.75 - 滑动幅度)), 2200);
  237.     return Math.round(屏高 * 滑动幅度);
  238. }
  239. function 实验4_长按() {
  240.     auto.longClickPercent(0.5, 0.55, 1500);
  241.     return 0;
  242. }
  243. function 实验5_折线轨迹() {
  244.     var p = new path();
  245.     p.setDurTime(800);
  246.     p.addPoint(Math.round(屏宽 * 0.30), Math.round(屏高 * 0.75));
  247.     p.addPoint(Math.round(屏宽 * 0.50), Math.round(屏高 * 0.60));
  248.     p.addPoint(Math.round(屏宽 * 0.70), Math.round(屏高 * (0.75 - 滑动幅度)));
  249.     auto.dispatchGesture([p]);
  250.     return Math.round(屏高 * 滑动幅度);
  251. }
  252. function 实验6_单击() {
  253.     auto.clickPercent(0.5, 0.55);
  254.     return 0;
  255. }

  256. var 实验列表 = [
  257.     { 名称: "快滑上(swipPercent_v2)", 执行: 实验1_快滑上, 预期: "scrollDx>0" },
  258.     { 名称: "快滑下(swipPercent_v2)", 执行: 实验2_快滑下, 预期: "scrollDx<0" },
  259.     { 名称: "慢拖上(drag2200ms)",      执行: 实验3_慢拖上, 预期: "scrollDx≈手指位移" },
  260.     { 名称: "长按(longClick)",         执行: 实验4_长按,   预期: "longpress>=1,scrollDx≈0" },
  261.     { 名称: "折线轨迹(dispatchGesture)", 执行: 实验5_折线轨迹, 预期: "move>0,scrollDx>0" },
  262.     { 名称: "单击(clickPercent)",      执行: 实验6_单击,   预期: "click>=1" }
  263. ];

  264. // ========== 主流程 ==========
  265. printl("===== 案例29 手势实测(独立演示窗版)启动 =====");
  266. 屏宽 = screen.getScreenWidth();
  267. 屏高 = screen.getScreenHeight();
  268. printl("📏 屏幕 " + 屏宽 + "x" + 屏高 + " 像素 | 滑动幅度 " + 滑动幅度 + " 屏高 ≈ " +
  269.     Math.round(屏高 * 滑动幅度) + " 像素");
  270. dbConnect();
  271. dbEnsureTable();

  272. if (!打开演示窗()) {
  273.     printl("❌ 演示窗打不开,流程终止");
  274.     dbClose();
  275.     runTime.stopAllRuning();
  276. }

  277. // 探针自检:清零后读一次,应拿到 read 回报
  278. 演示JS("resetGestureProbe()");
  279. sleep.millisecond(毫秒 = 300);
  280. var 自检 = 读演示探针();
  281. if (自检.条数 > 0) {
  282.     printl("✅ 探针读通道打通(轮询模式):" + gH5报告[0]);
  283. } else {
  284.     printl("⚠ " + 读探针超时毫秒 + " 毫秒内没读到探针——evaluateJavascript 回调没落地,本轮验证靠肉眼看演示窗");
  285. }

  286. // 停止按钮清理(顺序:先置标志,stopAllRuning 放最后)
  287. event.onStopBtnEvent(function () {
  288.     gStop = true;
  289.     dbClose();
  290.     关闭演示窗();
  291.     runTime.stopAllRuning();
  292. });

  293. var 实验索引 = 0;
  294. while (!gStop) {
  295.     累计轮数++;
  296.     var 实验 = 实验列表[实验索引 % 实验列表.length];
  297.     实验索引++;

  298.     printl("\n---- 第 " + 累计轮数 + " 轮实验【" + 实验.名称 + "】预期:" + 实验.预期 + " ----");

  299.     // 步骤1:清零探针
  300.     演示JS("resetGestureProbe()");
  301.     sleep.millisecond(毫秒 = 300);

  302.     // 步骤2:打手势(计时)
  303.     var 开始 = time.nowStamp();
  304.     var 手指位移 = 实验.执行();
  305.     var 耗时 = time.nowStamp() - 开始;

  306.     // 步骤3:等惯性滚完,轮询读探针
  307.     sleep.millisecond(毫秒 = 停稳等待毫秒);
  308.     var 结果 = 读演示探针();

  309.     // 步骤4:auto 没打中 → shell 设备级注入对照
  310.     if (结果.触摸 === 0) {
  311.         printl("🔁 auto 手势页面零感知(touch=0)——adb shell input 设备级注入对照");
  312.         var 补打 = shell对照手势(实验.名称);
  313.         if (补打) {
  314.             sleep.millisecond(毫秒 = 停稳等待毫秒);
  315.             var 结果2 = 读演示探针();
  316.             printl("🔬 对照结论:auto 打不中 / shell " + (结果2.触摸 > 0 ? "打中了 ✅" : "也没打中 ❌"));
  317.             if (结果2.条数 > 结果.条数) 结果 = 结果2;
  318.         }
  319.     }

  320.     // 步骤5:验收
  321.     if (结果.条数 === 0) {
  322.         printl("📐 本轮没读到探针数据(通道问题,看上面自检行)");
  323.     } else {
  324.         var 滚动文字 = 结果.滚动 === null ? "无" : (结果.滚动 + " px");
  325.         printl("📐 手指 " + 手指位移 + " px → 页面回报:滚动 " + 滚动文字 +
  326.             " | move帧 " + 结果.move + " | 长按:" + (结果.长按 ? "有" : "无") +
  327.             " | 点击:" + (结果.点击 ? "有" : "无") + "(手势耗时 " + 耗时 + " 毫秒)");
  328.         if (结果.触摸 > 0 && 手指位移 > 0 && 结果.滚动 !== null && Math.abs(结果.滚动) < 50) {
  329.             printl("⚠ 手指动了页面几乎没滚——列表已在顶/底边界,或滚动被探针读数时机错过");
  330.         }
  331.     }
  332.     if (dbInsertGesture(实验.名称, 手指位移, 结果.滚动 === null ? 0 : 结果.滚动, 结果.move, 耗时)) {
  333.         累计入库条数++;
  334.     }
  335.     printl("💾 入库 [" + 累计入库条数 + "] " + 实验.名称);

  336.     // 分片等待,每 5 秒打进度
  337.     var 剩余 = 手势间隔秒;
  338.     while (剩余 > 0 && !gStop) {
  339.         var 步长 = 剩余 >= 5 ? 5 : 剩余;
  340.         printl("⏳ 下一轮换手势...剩余 " + 剩余 + " 秒(累计 " + 累计轮数 + " 轮/入库 " + 累计入库条数 + " 条)");
  341.         sleep.second(秒 = 步长);
  342.         剩余 = 剩余 - 步长;
  343.     }
  344. }

  345. dbClose();
  346. 关闭演示窗();
  347. printl("===== 流程结束:共 " + 累计轮数 + " 轮,入库 " + 累计入库条数 + " 条,库: " + SQLite路径 + " =====");
复制代码


上一篇: 机器的眼睛多点找色 + 比色守卫

下一篇: 没有了

举报 回复

随机推荐
随机推荐每次刷新 · 热帖 + 双随机栏 各 50 篇
热帖浏览量最高
随机换个角度看
随机随便看看
随机会员刷新换一批