|
|
手势操作与位移实测
- /**
- * 案例29:手势操作与位移实测(独立演示窗版)
- *
- * 演进史(三轮实测锤死的教训,比代码值钱):
- * v1 打设置页:openApp 之后 H5 日志窗还盖在最前台,手势全打进 WebView,
- * 设置页在后台根本没被摸到(快照"0/18 共同文字"抓的就是日志窗自己的节点);
- * v2 打日志终端:H5→脚本反向桥(onH5Gesture/test)实测拿不到——start.js 定义的
- * 函数没暴露成 window 全局;改走 web1.evaluateJavascript(小写j!LogBridge 找
- * 大写S 一直没发现)带返回值主动读,通道是通了,但**日志终端天生不能当手势靶**:
- * addLog 每条自动滚到底,列表永远贴底,滑了也白滑,scrollDx 恒 0;
- * v3(本版)独立演示窗:new activity() 再开一个窗口,加载 演示页.html——
- * 40 条静态列表不会自动滚底,滚到哪停在哪;探针计数直接显示在页面顶上,
- * 手势有没有打中**肉眼直接可见**,脚本端 evaluateJavascript 轮询读数只是留档。
- *
- * 六个手势实验轮转(全部打在演示窗列表上):
- * ① swipPercent_v2 快滑上 ② 快滑下 ③ drag 慢拖上2200ms
- * ④ longClickPercent 长按 ⑤ dispatchGesture 折线 ⑥ clickPercent 单击
- * 每轮:resetGestureProbe 清零 → 手势 → 轮询读 getGestureReport(最长3秒)→
- * 若 touch=0 用 adb shell input 设备级注入对照 → 验收 → 入库。
- *
- * 新知识点:
- * 1. 一个脚本可以 new 多个 activity() 开多个窗口:演示窗在前台挨打,日志窗后台收 printl;
- * 2. WebView 带返回值读:web.evaluateJavascript(code, function(res){...})——
- * 回调在 UI 线程落地有延迟,**必须轮询等数据**,固定 sleep 会读空(实测锤死);
- * 3. auto.swipPercent_v2(x1%,y1%,x2%,y2%,滑动毫秒,延时抬起毫秒);
- * 4. auto.drag(x1,y1,x2,y2,时长毫秒)像素、无惯性;auto.longClickPercent(x%,y%,毫秒);
- * 5. auto.clickPercent(x%,y%);auto.dispatchGesture([path])——new path()+setDurTime+addPoint(像素);
- * 6. agent.shell("input swipe/tap ...")——设备级注入,auto 打不中时的对照组。
- *
- * 实测踩坑表(跑完按日志修订):
- * - 回调延迟:读探针的 👆 回报经常比脚本解析晚到一轮——"零回报"和"感觉到了"同轮矛盾
- * 就是这么来的,本版改轮询等待修复;
- * - 键类 API(back/home)会把前台演示窗退掉,别按;
- * - auto.* 无障碍手势在 v2 实测页面零感知(touch=0)——本版演示窗是原生前台窗口,
- * 若仍零感知而 shell 打中,结论就是"无障碍手势进不了本 App 的 WebView";
- * - 日志终端自动滚底,永远别拿它当滚动实验对象。
- */
- // ========== 配置区(改这里) ==========
- var 手势间隔秒 = 10; // 每 10 秒一轮实验
- var 停稳等待毫秒 = 800; // 手势后等惯性滚完
- var 读探针超时毫秒 = 3000; // 轮询等回调落地的上限(回调有延迟,固定sleep不可靠)
- var 滑动幅度 = 0.35; // 占屏高比例,快滑/慢拖共用方便对比
- var 演示页路径 = "/代码/演示页.html";
- var SQLite路径 = "/sdcard/手势记录.db";
- var 表名 = "gesture_records"; // content_px=页面回报scrollDx, sample=move帧数
- // ========== 全局变量顶部声明 ==========
- var gStop = false;
- var 数据库连接 = null;
- var 数据库可用 = false;
- var 累计轮数 = 0;
- var 累计入库条数 = 0;
- var 屏宽 = 0;
- var 屏高 = 0;
- var gH5报告 = []; // 探针回报队列(回调线程往里塞)
- var g演示ac = null; // 演示窗 activity
- var g演示web = null; // 演示窗 WebView
- // ========== importClass 只能放最外层 ==========
- importClass(android.database.sqlite.SQLiteDatabase);
- importClass(java.io.File);
- // ========== 探针回报回调(evaluateJavascript 的 ValueCallback 落地处) ==========
- function onH5Gesture(msg) {
- var s = "" + msg;
- if (s === "undefined" || s === "null") return;
- s = s.replace(/^"|"$/g, "").replace(/\"/g, '"').replace(/\\n/g, " ");
- gH5报告.push(s);
- printl("👆 演示窗感觉到: " + s);
- }
- // ========== 演示窗:第二个 activity,静态列表挨打 ==========
- function 打开演示窗() {
- try {
- g演示ac = new activity();
- g演示ac.loadXML(
- '<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"' +
- ' android:layout_width="match_parent" android:layout_height="match_parent"' +
- ' android:background="#14060a" android:orientation="vertical">' +
- ' <WebView android:id="@+id/demoweb"' +
- ' android:layout_width="match_parent" android:layout_height="match_parent"/>' +
- '</LinearLayout>'
- );
- g演示web = g演示ac.findWebViewById("demoweb");
- if (!g演示web) { printl("❌ 演示窗 WebView 拿不到(id=demoweb)"); return false; }
- g演示web.url(演示页路径);
- sleep.second(秒 = 3); // 等页面加载+列表渲染
- printl("🪟 演示窗已打开: " + 演示页路径 + "(静态列表40条,探针计数页面可见)");
- return true;
- } catch (e) {
- printl("❌ 打开演示窗异常: " + e);
- return false;
- }
- }
- function 关闭演示窗() {
- try { if (g演示ac != null) g演示ac.close(); } catch (e) {}
- g演示ac = null;
- g演示web = null;
- }
- // ========== 演示窗 JS 通道 ==========
- // 单向执行(清零探针用,不等结果)
- function 演示JS(code) {
- if (!g演示web) return false;
- try {
- var run = function () {
- try {
- if (typeof g演示web.evaluateJavascript === "function") {
- g演示web.evaluateJavascript(code, null);
- } else { g演示web.loadUrl("javascript:" + code); }
- } catch (e) { printl("⚠ 演示JS 异常: " + e); }
- };
- if (global.ac && typeof global.ac.runOnUiThread === "function") { global.ac.runOnUiThread(run); }
- else { run(); }
- return true;
- } catch (e2) { printl("⚠ 演示JS 外层异常: " + e2); return false; }
- }
- // 带返回值读:回调塞进 gH5报告,轮询等到数据为止(回调有延迟,固定sleep读空是 v2 实测坑)
- function 读演示探针() {
- gH5报告 = [];
- if (!g演示web) return { 滚动: null, move: 0, 触摸: 0, 长按: false, 点击: false, 条数: 0 };
- try {
- var run = function () {
- try { g演示web.evaluateJavascript("getGestureReport()", onH5Gesture); }
- catch (e) { printl("⚠ 读探针异常: " + e); }
- };
- if (global.ac && typeof global.ac.runOnUiThread === "function") { global.ac.runOnUiThread(run); }
- else { run(); }
- } catch (e2) { printl("⚠ 读探针外层异常: " + e2); }
- var 已等 = 0;
- while (gH5报告.length === 0 && 已等 < 读探针超时毫秒 && !gStop) {
- sleep.millisecond(毫秒 = 100);
- 已等 = 已等 + 100;
- }
- return 解析回报();
- }
- // ========== 工具函数(先定义后调用) ==========
- function 解析回报() {
- var 滚动 = null;
- var move最大 = 0;
- var touch数 = 0;
- var 有长按 = false;
- var 有点击 = false;
- for (var i = 0; i < gH5报告.length; i++) {
- var s = gH5报告[i];
- var m = s.match(/scrollDx=(-?\d+)/);
- if (m) 滚动 = parseInt(m[1], 10);
- var mm = s.match(/move=(\d+)/);
- if (mm && parseInt(mm[1], 10) > move最大) move最大 = parseInt(mm[1], 10);
- var tt = s.match(/touch=(\d+)/);
- if (tt && parseInt(tt[1], 10) > touch数) touch数 = parseInt(tt[1], 10);
- if (s.indexOf("longpress") >= 0) 有长按 = true;
- if (s.indexOf("click") >= 0) 有点击 = true;
- }
- return { 滚动: 滚动, move: move最大, 触摸: touch数, 长按: 有长按, 点击: 有点击, 条数: gH5报告.length };
- }
- // adb shell input 设备级注入对照(auto 打不中时用,坐标同款)
- function shell对照手势(实验名) {
- var cx = Math.round(屏宽 * 0.5);
- var y上 = Math.round(屏高 * 0.40);
- var y下 = Math.round(屏高 * 0.75);
- var y中 = Math.round(屏高 * 0.55);
- var cmd = "";
- if (实验名.indexOf("快滑上") === 0) cmd = "input swipe " + cx + " " + y下 + " " + cx + " " + y上 + " 600";
- else if (实验名.indexOf("快滑下") === 0) cmd = "input swipe " + cx + " " + y上 + " " + cx + " " + y下 + " 600";
- else if (实验名.indexOf("慢拖上") === 0) cmd = "input swipe " + cx + " " + y下 + " " + cx + " " + y上 + " 2200";
- else if (实验名.indexOf("长按") === 0) cmd = "input swipe " + cx + " " + y中 + " " + cx + " " + y中 + " 1500";
- else if (实验名.indexOf("折线") === 0) cmd = "input swipe " + Math.round(屏宽 * 0.30) + " " + y下 + " " +
- Math.round(屏宽 * 0.70) + " " + Math.round(屏高 * (0.75 - 滑动幅度)) + " 800";
- else if (实验名.indexOf("单击") === 0) cmd = "input tap " + cx + " " + y中;
- if (cmd === "") return false;
- try {
- agent.shell(cmd);
- printl("🔧 shell 注入: " + cmd);
- return true;
- } catch (e) {
- printl("⚠ agent.shell 异常: " + e);
- return false;
- }
- }
- function dbConnect() {
- try {
- 数据库连接 = SQLiteDatabase.openOrCreateDatabase(new File(SQLite路径), null);
- 数据库可用 = true;
- printl("[DB] ✅ SQLite 已打开: " + SQLite路径);
- } catch (e) {
- 数据库可用 = false;
- printl("[DB] ⚠ 打开失败(只打印不入库): " + e);
- }
- }
- function dbEnsureTable() {
- if (!数据库可用) return;
- try {
- var sql = "CREATE TABLE IF NOT EXISTS " + 表名 + " (" +
- "id INTEGER PRIMARY KEY AUTOINCREMENT," +
- "gesture TEXT, finger_px INTEGER, content_px INTEGER," +
- "sample INTEGER, elapsed_ms INTEGER, collected_at INTEGER)";
- 数据库连接.execSQL(sql);
- printl("[DB] ✅ 表已就绪: " + 表名);
- } catch (e) {
- printl("[DB] ⚠ 建表失败: " + e);
- 数据库可用 = false;
- }
- }
- function dbInsertGesture(手势名, 手指位移, 回报滚动, move帧数, 耗时) {
- if (!数据库可用) return false;
- try {
- var stmt = 数据库连接.compileStatement("INSERT INTO " + 表名 +
- " (gesture, finger_px, content_px, sample, elapsed_ms, collected_at) VALUES (?,?,?,?,?,?)");
- stmt.bindString(1, "" + 手势名);
- stmt.bindLong(2, 手指位移);
- stmt.bindLong(3, 回报滚动);
- stmt.bindLong(4, move帧数);
- stmt.bindLong(5, 耗时);
- stmt.bindLong(6, time.nowStamp());
- stmt.executeInsert();
- stmt.close();
- return true;
- } catch (e) {
- printl("[DB] ❌ 入库失败: " + e);
- return false;
- }
- }
- function dbClose() {
- if (数据库可用 && 数据库连接 != null) {
- try { 数据库连接.close(); } catch (e) {}
- 数据库连接 = null;
- }
- }
- // ========== 六个手势实验(每个返回手指位移像素) ==========
- function 实验1_快滑上() {
- auto.swipPercent_v2(0.5, 0.75, 0.5, 0.75 - 滑动幅度, 600, 0);
- return Math.round(屏高 * 滑动幅度);
- }
- function 实验2_快滑下() {
- auto.swipPercent_v2(0.5, 0.40, 0.5, 0.40 + 滑动幅度, 600, 0);
- return Math.round(屏高 * 滑动幅度);
- }
- function 实验3_慢拖上() {
- auto.drag(Math.round(屏宽 * 0.5), Math.round(屏高 * 0.75),
- Math.round(屏宽 * 0.5), Math.round(屏高 * (0.75 - 滑动幅度)), 2200);
- return Math.round(屏高 * 滑动幅度);
- }
- function 实验4_长按() {
- auto.longClickPercent(0.5, 0.55, 1500);
- return 0;
- }
- function 实验5_折线轨迹() {
- var p = new path();
- p.setDurTime(800);
- p.addPoint(Math.round(屏宽 * 0.30), Math.round(屏高 * 0.75));
- p.addPoint(Math.round(屏宽 * 0.50), Math.round(屏高 * 0.60));
- p.addPoint(Math.round(屏宽 * 0.70), Math.round(屏高 * (0.75 - 滑动幅度)));
- auto.dispatchGesture([p]);
- return Math.round(屏高 * 滑动幅度);
- }
- function 实验6_单击() {
- auto.clickPercent(0.5, 0.55);
- return 0;
- }
- var 实验列表 = [
- { 名称: "快滑上(swipPercent_v2)", 执行: 实验1_快滑上, 预期: "scrollDx>0" },
- { 名称: "快滑下(swipPercent_v2)", 执行: 实验2_快滑下, 预期: "scrollDx<0" },
- { 名称: "慢拖上(drag2200ms)", 执行: 实验3_慢拖上, 预期: "scrollDx≈手指位移" },
- { 名称: "长按(longClick)", 执行: 实验4_长按, 预期: "longpress>=1,scrollDx≈0" },
- { 名称: "折线轨迹(dispatchGesture)", 执行: 实验5_折线轨迹, 预期: "move>0,scrollDx>0" },
- { 名称: "单击(clickPercent)", 执行: 实验6_单击, 预期: "click>=1" }
- ];
- // ========== 主流程 ==========
- printl("===== 案例29 手势实测(独立演示窗版)启动 =====");
- 屏宽 = screen.getScreenWidth();
- 屏高 = screen.getScreenHeight();
- printl("📏 屏幕 " + 屏宽 + "x" + 屏高 + " 像素 | 滑动幅度 " + 滑动幅度 + " 屏高 ≈ " +
- Math.round(屏高 * 滑动幅度) + " 像素");
- dbConnect();
- dbEnsureTable();
- if (!打开演示窗()) {
- printl("❌ 演示窗打不开,流程终止");
- dbClose();
- runTime.stopAllRuning();
- }
- // 探针自检:清零后读一次,应拿到 read 回报
- 演示JS("resetGestureProbe()");
- sleep.millisecond(毫秒 = 300);
- var 自检 = 读演示探针();
- if (自检.条数 > 0) {
- printl("✅ 探针读通道打通(轮询模式):" + gH5报告[0]);
- } else {
- printl("⚠ " + 读探针超时毫秒 + " 毫秒内没读到探针——evaluateJavascript 回调没落地,本轮验证靠肉眼看演示窗");
- }
- // 停止按钮清理(顺序:先置标志,stopAllRuning 放最后)
- event.onStopBtnEvent(function () {
- gStop = true;
- dbClose();
- 关闭演示窗();
- runTime.stopAllRuning();
- });
- var 实验索引 = 0;
- while (!gStop) {
- 累计轮数++;
- var 实验 = 实验列表[实验索引 % 实验列表.length];
- 实验索引++;
- printl("\n---- 第 " + 累计轮数 + " 轮实验【" + 实验.名称 + "】预期:" + 实验.预期 + " ----");
- // 步骤1:清零探针
- 演示JS("resetGestureProbe()");
- sleep.millisecond(毫秒 = 300);
- // 步骤2:打手势(计时)
- var 开始 = time.nowStamp();
- var 手指位移 = 实验.执行();
- var 耗时 = time.nowStamp() - 开始;
- // 步骤3:等惯性滚完,轮询读探针
- sleep.millisecond(毫秒 = 停稳等待毫秒);
- var 结果 = 读演示探针();
- // 步骤4:auto 没打中 → shell 设备级注入对照
- if (结果.触摸 === 0) {
- printl("🔁 auto 手势页面零感知(touch=0)——adb shell input 设备级注入对照");
- var 补打 = shell对照手势(实验.名称);
- if (补打) {
- sleep.millisecond(毫秒 = 停稳等待毫秒);
- var 结果2 = 读演示探针();
- printl("🔬 对照结论:auto 打不中 / shell " + (结果2.触摸 > 0 ? "打中了 ✅" : "也没打中 ❌"));
- if (结果2.条数 > 结果.条数) 结果 = 结果2;
- }
- }
- // 步骤5:验收
- if (结果.条数 === 0) {
- printl("📐 本轮没读到探针数据(通道问题,看上面自检行)");
- } else {
- var 滚动文字 = 结果.滚动 === null ? "无" : (结果.滚动 + " px");
- printl("📐 手指 " + 手指位移 + " px → 页面回报:滚动 " + 滚动文字 +
- " | move帧 " + 结果.move + " | 长按:" + (结果.长按 ? "有" : "无") +
- " | 点击:" + (结果.点击 ? "有" : "无") + "(手势耗时 " + 耗时 + " 毫秒)");
- if (结果.触摸 > 0 && 手指位移 > 0 && 结果.滚动 !== null && Math.abs(结果.滚动) < 50) {
- printl("⚠ 手指动了页面几乎没滚——列表已在顶/底边界,或滚动被探针读数时机错过");
- }
- }
- if (dbInsertGesture(实验.名称, 手指位移, 结果.滚动 === null ? 0 : 结果.滚动, 结果.move, 耗时)) {
- 累计入库条数++;
- }
- printl("💾 入库 [" + 累计入库条数 + "] " + 实验.名称);
- // 分片等待,每 5 秒打进度
- var 剩余 = 手势间隔秒;
- while (剩余 > 0 && !gStop) {
- var 步长 = 剩余 >= 5 ? 5 : 剩余;
- printl("⏳ 下一轮换手势...剩余 " + 剩余 + " 秒(累计 " + 累计轮数 + " 轮/入库 " + 累计入库条数 + " 条)");
- sleep.second(秒 = 步长);
- 剩余 = 剩余 - 步长;
- }
- }
- dbClose();
- 关闭演示窗();
- printl("===== 流程结束:共 " + 累计轮数 + " 轮,入库 " + 累计入库条数 + " 条,库: " + SQLite路径 + " =====");
复制代码
|
|