Auto.js 入门指南(十七)性能优化建议

前言

大家好,我是鲫小鱼。是一名不写前端代码的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!

第十七章:性能优化建议


一、理论讲解:Auto.js 性能优化的重要性与常见瓶颈

1.1 为什么需要性能优化?

在 Auto.js 脚本开发中,性能优化不仅仅是为了让脚本运行得更快,更是为了:

  • 提升用户体验:流畅的脚本运行能减少卡顿、等待,使用户感知更好。
  • 节省设备资源:减少 CPU、内存、电量消耗,延长设备续航。
  • 提高脚本稳定性:低效的脚本可能导致 ANR (Application Not Responding) 错误,或被系统后台杀死。
  • 减少执行时间:对于需要快速响应或频繁执行的自动化任务至关重要。
  • 应对复杂场景:在处理大量数据、复杂 UI 交互时,性能优化能确保脚本正常运行。

1.2 Auto.js 脚本的常见性能瓶颈

理解瓶颈所在是优化的第一步:

  • UI 查找与操作text(), id(), findOne(), find() 等操作会遍历屏幕控件树,频繁或低效的查找是主要瓶颈。
  • 循环与递归:大量循环或深度递归可能导致 CPU 占用过高,甚至栈溢出。
  • 文件 I/O:频繁的读写文件,尤其是大文件,会造成性能下降。
  • 内存管理:大量创建对象、未及时释放资源可能导致内存溢出 (OOM)。
  • 网络请求:同步网络请求、网络超时、频繁短连接、大数据量传输。
  • 图像识别与图色分析images.findImage(), colors.findColor() 等操作计算量大,是典型的 CPU 密集型任务。
  • 多线程管理:线程创建销毁开销大,线程间通信不当可能导致死锁或资源竞争。
  • 日志输出:在生产环境大量输出日志会增加 I/O 负担,影响性能。
  • 不必要的等待sleep() 时间过长或无谓的等待会拖慢脚本。

1.2.1 UI 自动化操作的深度优化

UI 自动化是 Auto.js 的核心,但也是最常见的性能瓶颈。优化不仅仅是减少查找次数,更要理解查找机制和替代方案。

  • 查找策略的选择
    • id() 优先 :如果目标控件有唯一 ID,始终优先使用 id(),它的查找速度最快。
    • text() / desc() :次选。它们需要遍历所有可见文本或描述进行匹配,效率低于 id()
    • className() / bounds() :作为补充。className() 查找可能返回大量结果,需要结合其他条件筛选;bounds() 适合做区域点击,但不适合查找复杂控件。
  • 链式查找与缓存 :避免重复查找。如果需要对同一控件进行多次操作,应先 findOne() 并保存引用,后续直接使用。
javascript 复制代码
// 不推荐:重复查找
// text("确定").click();
// text("确定").longClick();

// 推荐:缓存控件对象
let okButton = text("确定").findOne();
if (okButton) {
    okButton.click();
    sleep(500);
    okButton.longClick();
}
  • 局部查找 :当目标控件在某个特定父控件下时,利用 uiObject.find()uiObject.findOne() 在局部范围内查找,减少全局遍历。
javascript 复制代码
// 假设要在一个卡片内部查找一个按钮
let card = id("card_container").findOne();
if (card) {
    let buttonInCard = card.text("点击进入").findOne();
    if (buttonInCard) {
        buttonInCard.click();
    }
}
  • waitFor 系列函数 :使用 waitForExists(), waitForActivities() 等函数代替简单的 sleep(),等待特定条件满足,避免死等或操作过早。
javascript 复制代码
// 优化前:简单sleep,可能过长或过短
// sleep(3000); // 等待页面加载
// text("按钮").click();

// 优化后:等待按钮出现,提高效率和健壮性
if (text("按钮").waitForExists(5000)) {
    text("按钮").click();
} else {
    log("按钮未在5秒内出现");
}
  • 控件层级优化:避免不必要的深层遍历,如果知道控件大致位置,可以先定位其父级或兄弟控件,再进行相对查找。

1.2.2 循环与算法效率

  • 减少不必要的循环次数:在数据处理前进行筛选,避免在循环中执行重复或不必要的计算。
  • 优化数据结构和算法:选择适合场景的数据结构(如 Map/Set 替代数组查找),优化算法复杂度 (O(n) -> O(log n))。
  • 批量操作 :如果需要对多个相似控件进行操作,尽量一次性 find() 所有目标,再进行遍历,而不是每次循环都 findOne()
javascript 复制代码
// 不推荐:循环内重复查找和点击
// for (let i = 0; i < 5; i++) {
//     let item = text("项目" + i).findOne();
//     if (item) item.click();
// }

// 推荐:一次性查找所有,再遍历点击
let items = ("textStartsWith", "项目").find();
for (let i = 0; i < items.length; i++) {
    items[i].click();
    sleep(200); // 适当延时,模拟用户操作,避免过快
}

1.2.3 内存管理与资源释放

  • 避免内存泄漏
    • 对于 threads.start() 创建的子线程,在不需要时调用 thread.interrupt() 中断并释放。
    • 对于 setInterval, setTimeout 创建的定时器,在不再需要时调用 clearInterval, clearTimeout 清除。
    • 对于 ui 相关的监听器,在 Activity 销毁时解除注册。
  • 及时释放大对象 :当处理完图片、大量数据等大对象后,将其设置为 null,以便垃圾回收器回收。
javascript 复制代码
// 图片处理示例
let img = images.read("/sdcard/screenshot.png");
// ... 对 img 进行操作 ...
img.recycle(); // 显式释放图片占用的内存
img = null; // 帮助垃圾回收
  • 数据压缩:对于需要存储或传输的大量数据,考虑进行压缩(如 JSON.stringify 后再进行 Base64 编码或 Gzip 压缩),减少内存占用和 I/O。

1.2.4 文件 I/O 优化

  • 减少读写频率:避免在循环中频繁读写文件。可以先将数据累计到内存中,再批量写入。
  • 选择合适的存储方式
    • files 模块:适合读写文本、JSON、日志等文件。
    • storage 模块:适合轻量级键值对存储,内部已优化。
    • 数据库 (SQLite):适合结构化、大量数据存储和复杂查询,比文件读写更高效。
  • 缓存文件内容:对于经常读取的配置文件、数据文件,可以读取一次后缓存到内存中,减少磁盘 I/O。
javascript 复制代码
// 不推荐:频繁读取配置文件
// function getConfig(key) {
//     let config = JSON.parse(files.read("/sdcard/config.json"));
//     return config[key];
// }
// log(getConfig("user"));
// log(getConfig("password"));

// 推荐:一次性加载到内存并缓存
let cachedConfig = null;
function getCachedConfig() {
    if (!cachedConfig) {
        try {
            cachedConfig = JSON.parse(files.read("/sdcard/config.json"));
        } catch (e) {
            log("读取配置文件失败: " + e, "ERROR");
            cachedConfig = {};
        }
    }
    return cachedConfig;
}
log(getCachedConfig().user);
log(getCachedConfig().password);

1.2.5 网络请求优化

  • 异步请求 :总是使用 http.get(), http.post() 等的异步版本(带回调函数),避免阻塞主线程。
  • 请求合并:将多个小请求合并成一个大请求,减少网络开销。
  • 数据压缩:请求和响应数据量大时,可以协商使用 Gzip 等压缩。
  • 错误重试与超时:合理设置超时时间,对网络错误进行重试,但要注意重试策略,避免无限重试。
  • 缓存网络数据:对于不经常变化的数据,可以缓存到本地,减少重复请求。

1.2.6 多线程与异步操作优化

  • 合理使用线程池:避免频繁创建和销毁线程,对于需要执行大量并发任务的场景,使用线程池可以复用线程,减少开销。
  • 线程安全 :对共享数据进行加锁保护 (threads.lock()), 避免数据竞争和不一致。
  • 避免 UI 阻塞 :所有耗时操作都应在子线程中执行,UI 更新必须通过 ui.run() 调度到主线程。
  • 线程间通信 :使用 events 模块或 Handler 进行线程间通信,避免轮询或不当的等待。

1.2.7 日志输出优化

  • 日志分级:在开发阶段可以打印详细日志,但在生产环境中应限制日志等级,只输出必要的 ERROR 或 WARN 级别日志,减少 I/O。
  • 异步日志写入:如果日志量非常大,可以将日志写入操作放到一个单独的子线程中,异步进行,避免阻塞主线程。
  • 定期清理日志:防止日志文件过大占用存储空间。

1.2.8 其他通用优化技巧

  • 代码精简与优化:删除不必要的代码、变量、函数。优化算法逻辑。
  • 预加载:对于启动时需要加载的模块或资源,可以考虑预加载,减少运行时延时。
  • 利用 Pro 版特性:Auto.js Pro 提供了更强大的混淆、打包、加密等功能,可以进一步优化脚本性能和安全性。
  • Profile 工具:利用 Auto.js Pro 的性能分析工具,定位脚本的 CPU、内存热点,进行针对性优化。

二、代码示例:Auto.js 性能优化全场景实战

2.1 UI 查找效率对比与优化

javascript 复制代码
// 场景:查找一个特定的文本控件并点击

// 未优化:每次都进行全局查找
function inefficientClick() {
    let startTime = Date.now();
    for (let i = 0; i < 5; i++) {
        let target = text("目标文本").findOne(1000);
        if (target) {
            target.click();
            log(`低效点击:第${i+1}次`);
        }
    }
    log(`未优化耗时:${Date.now() - startTime}ms`);
}

// 优化一:缓存控件对象
function optimizedClickCache() {
    let startTime = Date.now();
    let target = text("目标文本").findOne(1000); // 第一次查找并缓存
    if (target) {
        for (let i = 0; i < 5; i++) {
            target.click();
            log(`缓存优化点击:第${i+1}次`);
        }
    } else {
        log("目标文本未找到");
    }
    log(`缓存优化耗时:${Date.now() - startTime}ms`);
}

// 优化二:局部查找 (假设目标文本在一个 ID 为 'container' 的控件内)
function optimizedClickLocal() {
    let startTime = Date.now();
    let container = id("container").findOne(1000);
    if (container) {
        for (let i = 0; i < 5; i++) {
            let target = container.text("目标文本").findOne(500); // 在局部内查找
            if (target) {
                target.click();
                log(`局部查找优化点击:第${i+1}次`);
            }
        }
    } else {
        log("容器未找到");
    }
    log(`局部查找优化耗时:${Date.now() - startTime}ms`);
}

// 实际运行时需要有相应的UI界面供测试
// console.show();
// toastLog("请确保屏幕上有包含'目标文本'的控件,以及ID为'container'的容器");
// sleep(3000);
// inefficientClick();
// sleep(1000);
// optimizedClickCache();
// sleep(1000);
// optimizedClickLocal();

2.2 大数据量数组处理效率对比

javascript 复制代码
// 场景:从一个大型数组中查找特定元素

let largeArray = [];
for (let i = 0; i < 10000; i++) {
    largeArray.push({ id: i, name: "Item " + i });
}

// 未优化:线性查找
function inefficientSearch(arr, id) {
    let startTime = Date.now();
    for (let i = 0; i < arr.length; i++) {
        if (arr[i].id === id) {
            log(`低效查找耗时:${Date.now() - startTime}ms`);
            return arr[i];
        }
    }
    log(`低效查找耗时:${Date.now() - startTime}ms`);
    return null;
}

// 优化:使用 Map 结构进行快速查找
let dataMap = new Map();
for (let item of largeArray) {
    dataMap.set(item.id, item);
}

function optimizedSearch(map, id) {
    let startTime = Date.now();
    let result = map.get(id);
    log(`Map优化查找耗时:${Date.now() - startTime}ms`);
    return result;
}

// console.show();
// inefficientSearch(largeArray, 9999);
// optimizedSearch(dataMap, 9999);

2.3 内存优化:图片资源释放

javascript 复制代码
// 场景:处理多张图片,避免内存溢出

function processImagesEfficiently(imagePaths) {
    for (let i = 0; i < imagePaths.length; i++) {
        let path = imagePaths[i];
        log(`正在处理图片:${path}`);
        let img = images.read(path);
        if (img) {
            // 模拟对图片进行复杂操作,例如截取、识别
            // let croppedImg = images.clip(img, 0, 0, 100, 100);
            // log(images.detectsText(croppedImg));

            img.recycle(); // 显式释放图片占用的内存
            img = null; // 帮助垃圾回收器回收
            log(`图片 ${path} 处理完成并释放内存。`);
        } else {
            log(`无法读取图片:${path}`, "ERROR");
        }
        sleep(500); // 模拟处理间隔
    }
    log("所有图片处理完毕。");
}

// 假设有这些图片文件 (实际运行需要手动创建或提供路径)
// var paths = [
//     "/sdcard/Pictures/img1.png",
//     "/sdcard/Pictures/img2.png",
//     "/sdcard/Pictures/img3.png"
// ];
// processImagesEfficiently(paths);

2.4 文件 I/O 优化:批量写入日志

javascript 复制代码
// 场景:频繁记录日志,避免频繁写入文件

let logBuffer = [];
let logTimer = null;
const LOG_FILE = "/sdcard/app_log_optimized.txt";
const FLUSH_INTERVAL = 2000; // 每2秒写入一次文件

/**
 * 优化后的日志函数:将日志缓存起来,定时批量写入
 * @param {string} msg 日志消息
 */
function bufferedLog(msg) {
    let line = `[${new Date().toLocaleTimeString()}] ${msg}`;
    logBuffer.push(line);
    console.log(line); // 实时输出到控制台

    if (!logTimer) {
        logTimer = setTimeout(() => {
            flushLogs();
            logTimer = null;
        }, FLUSH_INTERVAL);
    }
}

/**
 * 强制将缓存中的日志写入文件
 */
function flushLogs() {
    if (logBuffer.length > 0) {
        let contentToFlush = logBuffer.join("\n") + "\n";
        files.append(LOG_FILE, contentToFlush);
        logBuffer = []; // 清空缓存
        log("日志已批量写入文件。", "DEBUG");
    }
}

// 确保脚本退出时所有日志都被写入
events.on("exit", function() {
    flushLogs();
    log("脚本退出,所有缓存日志已写入。");
});

// 模拟高频日志生成
// for (let i = 0; i < 20; i++) {
//     bufferedLog(`这是一个测试日志消息 ${i + 1}`);
//     sleep(100); // 模拟短间隔操作
// }
// sleep(FLUSH_INTERVAL + 1000); // 等待最后一次刷新

2.5 多线程优化:异步网络请求与 UI 更新

javascript 复制代码
// 场景:在子线程中进行网络请求,并在主线程更新 UI

"ui";

ui.layout(
    <vertical padding="16">
        <text id="statusText" textSize="16sp" text="点击按钮获取数据"/>
        <button id="fetchBtn" text="获取网络数据"/>
    </vertical>
);

ui.fetchBtn.on("click", () => {
    ui.statusText.setText("正在获取数据...");
    ui.fetchBtn.setEnabled(false); // 禁用按钮防止重复点击

    threads.start(function() {
        let res = null;
        try {
            res = http.get("https://www.baidu.com"); // 模拟耗时网络请求
        } catch (e) {
            log("网络请求失败: " + e, "ERROR");
        }

        ui.run(function() {
            if (res && res.statusCode == 200) {
                ui.statusText.setText(`数据获取成功!状态码: ${res.statusCode},内容长度: ${res.body.bytes().length}`);
            } else {
                ui.statusText.setText("数据获取失败或网络异常。");
            }
            ui.fetchBtn.setEnabled(true); // 重新启用按钮
        });
    });
});

// 启动脚本后,点击"获取网络数据"按钮观察效果。
// 日志中会显示网络请求的成功或失败信息,UI 界面会实时更新状态。

三、实战项目:优化一个效率低下的图片处理脚本

3.1 项目需求

一个自动化脚本需要频繁截屏并在截屏图片上进行颜色查找。目前的实现方式效率低下,导致脚本运行缓慢且容易崩溃。我们需要对其进行性能优化。

3.2 原始低效脚本 (inefficient_image_processor.js)

javascript 复制代码
// inefficient_image_processor.js
// 假设我们需要查找屏幕上是否有红色像素,并记录位置

const TARGET_COLOR = colors.rgb(255, 0, 0); // 纯红色
const CHECK_INTERVAL = 1000; // 每秒检查一次
const DURATION = 10 * 1000; // 持续运行10秒

log("启动低效图片处理脚本...");
let startTime = Date.now();

while (Date.now() - startTime < DURATION) {
    let screenshot = images.captureScreen(); // 频繁截屏
    if (screenshot) {
        let foundPoint = colors.findColor(screenshot, TARGET_COLOR, {threshold: 10, region: [0, 0, device.width, device.height]}); // 全屏查找颜色
        if (foundPoint) {
            log(`在 (${foundPoint.x}, ${foundPoint.y}) 找到红色像素!`);
        } else {
            log("未找到红色像素。");
        }
        screenshot.recycle(); // 释放内存
    } else {
        log("截屏失败!", "ERROR");
    }
    sleep(CHECK_INTERVAL);
}

log(`低效图片处理脚本运行结束,总耗时:${Date.now() - startTime}ms`);
exit();

低效原因分析:

  1. 频繁截屏images.captureScreen() 是一个耗时操作,每秒执行一次会造成严重的性能负担。
  2. 全屏颜色查找colors.findColor() 对整个屏幕进行像素遍历,计算量巨大。
  3. 缺乏目标区域限制:即使目标颜色只出现在屏幕的某个小区域,脚本依然会扫描整个屏幕。

3.3 优化后的脚本 (optimized_image_processor.js)

javascript 复制代码
// optimized_image_processor.js
// 优化后的图片处理脚本

const TARGET_COLOR = colors.rgb(255, 0, 0); // 纯红色
const CHECK_INTERVAL_OPTIMIZED = 2000; // 降低检查频率,每2秒检查一次
const DURATION_OPTIMIZED = 10 * 1000; // 持续运行10秒

// 假设我们知道红色像素通常出现在屏幕右下角的某个固定区域
const SEARCH_REGION = [device.width * 0.7, device.height * 0.7, device.width * 0.3, device.height * 0.3]; // 屏幕右下角30%区域

log("启动优化图片处理脚本...");
let startTimeOptimized = Date.now();

while (Date.now() - startTimeOptimized < DURATION_OPTIMIZED) {
    let screenshot = null;
    try {
        // 优化1:减少截屏频率,并确保每次截屏成功
        screenshot = images.captureScreen();
        if (!screenshot) {
            log("截屏失败,尝试重试...", "WARN");
            sleep(500); // 稍等再重试
            screenshot = images.captureScreen();
            if (!screenshot) {
                log("二次截屏失败,跳过本次检测。", "ERROR");
                sleep(CHECK_INTERVAL_OPTIMIZED);
                continue;
            }
        }

        // 优化2:限制颜色查找区域
        let foundPoint = colors.findColor(screenshot, TARGET_COLOR, {
            threshold: 10,
            region: SEARCH_REGION // 关键优化:只在指定区域查找
        });

        if (foundPoint) {
            log(`在 (${foundPoint.x}, ${foundPoint.y}) 找到红色像素!`);
        } else {
            log("未找到红色像素。");
        }
    } catch (e) {
        log(`图片处理异常: ${e}`, "ERROR");
    } finally {
        if (screenshot) {
            screenshot.recycle(); // 确保每次截屏后释放内存
            screenshot = null; // 帮助垃圾回收
        }
    }
    sleep(CHECK_INTERVAL_OPTIMIZED);
}

log(`优化图片处理脚本运行结束,总耗时:${Date.now() - startTimeOptimized}ms`);
exit();

3.4 运行效果与分析

  • 性能提升:优化后的脚本通过减少截屏频率和限制颜色查找区域,显著降低了 CPU 占用和内存消耗,运行效率更高,响应更及时。
  • 资源管理 :强制 recycle() 图片对象并将其设为 null,确保内存及时回收,避免了内存溢出。
  • 健壮性增强:增加了截屏失败重试机制和异常捕获,提高了脚本的稳定性。
  • 适用性 :通过 SEARCH_REGION 的配置,脚本更适用于目标颜色出现在特定区域的场景。

四、常见问题与解决方案

问题 解决方案
脚本卡顿/ANR 优化 UI 查找、减少循环次数、将耗时操作移至子线程、避免频繁文件 I/O。
内存溢出 (OOM) 及时释放图片等大对象 (recycle());避免创建大量临时对象;优化数据结构。
脚本运行缓慢 定位性能瓶颈 (CPU 密集型或 I/O 密集型);优化算法;减少不必要的等待。
子线程阻塞/死锁 确保子线程不执行 UI 操作;对共享资源加锁保护;合理设计线程间通信。
UI 自动化失败率高 增加等待时间、多条件查找、模拟真实操作路径、图片识别辅助。
日志过多影响性能 生产环境降低日志等级;使用异步日志写入;定期清理日志文件。
网络请求超时/失败 增加超时时间、实现重试机制、优化网络环境、使用 CDN。
电量消耗过快 降低脚本运行频率;减少后台活动;优化算法降低 CPU 占用;减少屏幕亮屏时间。
Auto.js Pro 特性无法利用 了解并充分利用 Pro 版提供的混淆、打包、性能分析工具、Java 扩展等高级功能。
复杂 UI 交互效率低 优先使用 id()text();利用控件层级关系进行局部查找;考虑直接调用 Android API。

五、性能优化建议

  • 性能分析先行:在优化之前,务必使用 Auto.js Pro 的性能分析工具 (如 CPU Profile, Memory Monitor) 定位真正的性能瓶颈,避免盲目优化。
  • 资源利用最大化:充分利用设备的多核 CPU 能力,将独立的耗时任务分配到子线程中并行执行。
  • 精益求精 :每次操作都问自己:"有没有更高效的方式?"例如,一个 click() 背后可能是复杂的控件查找,如果可以换成 坐标点击,则可能更快(但兼容性差)。
  • 防范内存泄漏:养成良好的编程习惯,对于不再使用的对象(尤其是图片、大数组、线程、定时器)及时释放其资源和引用。
  • 网络与文件 I/O 优化:减少网络请求次数,合并请求;文件读写采用批量操作和缓存机制,减少磁盘唤醒。
  • 降低 UI 查找成本 :优先使用 idtext,利用局部查找,缓存已找到的控件,减少不必要的屏幕遍历。
  • 算法与数据结构优化:针对数据处理密集型任务,选择合适的算法和数据结构(如哈希表、平衡树),提高查找、排序、计算效率。
  • 日志管理:在生产环境中,限制日志输出的详细程度和频率,避免日志文件过大和频繁 I/O。
  • 外部工具辅助:结合 ADB、Android Studio 的 Profiler 等工具进行更深层次的性能分析。
  • 代码审查与重构:定期审查和重构代码,消除冗余、提高可读性和可维护性,这本身就是一种性能优化。
  • 模拟真实用户行为:在自动化操作中引入少量随机延时,既能降低被反自动化检测的风险,又能避免过快的操作导致系统或应用响应不及。

最后感谢阅读!欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!!

相关推荐
Uyker几秒前
前端与后端主流框架分类及关键特性
前端·算法·django
GalaxyPokemon5 分钟前
RPC - Response模块
java·前端·javascript
网小鱼的学习笔记20 分钟前
CSS语法中的选择器与属性详解
前端·css
gnip27 分钟前
大屏适配-vm和vh
前端
晴殇i1 小时前
3 分钟掌握图片懒加载核心技术:面试攻略
前端·面试·trae
Running_C1 小时前
一文读懂vite和webpack,秒拿offer
前端
咸鱼青菜好好味1 小时前
node的项目实战相关
前端
hqsgdmn1 小时前
自动导入插件unplugin-auto-import/unplugin-vue-components
前端
雨白1 小时前
Android 音视频播放:MediaPlayer 与 VideoView
android
Harry技术1 小时前
Fragment 和 AppCompatActivity 两个核心组件设计的目的和使用场景对比
android·android studio