AutoJs自动化截图实战:从基础原理到高级应用场景

📅 2026/8/3 20:05:06
AutoJs自动化截图实战:从基础原理到高级应用场景
1. 项目缘起为什么需要AutoJs自动化截图在移动端自动化测试、数据采集、日常任务批处理或者无障碍辅助功能开发中我们经常会遇到一个看似简单却颇为繁琐的需求定时、定点或按条件对手机屏幕进行截图并自动保存到相册。你可能会想到手动操作或者依赖一些录屏软件但这些方法要么效率低下要么无法精准控制。比如你需要监控某个App特定页面的数据变化每小时截取一次或者在运行一个自动化脚本时需要在关键步骤留下证据又或者你只是想做一个自动化的“每日打卡”截图工具。这时AutoJs就成为了一个绝佳的选择。它是一个基于JavaScript的Android平台自动化工具无需Root权限通过模拟点击、滑动、找图、找色等操作可以轻松实现复杂的自动化流程。而“截图并保存”正是其最基础也最核心的功能之一。网络上虽然有很多零散的代码片段但往往缺乏系统性的原理讲解、完整的异常处理以及贴合实际场景的优化建议。今天我就结合自己多次在数据监控和自动化测试项目中的实战经验从头到尾拆解如何用AutoJs实现一个稳定、可靠、功能完备的自动化截图工具并深入探讨那些官方文档不会告诉你的“坑”和技巧。2. AutoJs环境搭建与核心权限剖析在开始写代码之前一个正确且充分授权的环境是成功的一半。很多新手卡在第一步不是因为代码逻辑问题而是权限没给对或者环境没配好。2.1 AutoJs Pro与开源版的选择目前AutoJs主要有两个分支AutoJs Pro闭源收费版和AutoJs 4.x开源免费版。对于学习和个人使用开源版完全足够。但需要注意的是由于一些原因原开源版本在各大应用商店已不易直接下载通常需要从GitHub等开源平台获取APK文件进行安装。安装后请务必从软件内部检查更新确保使用的是较新的稳定版本。注意请务必从官方或可信的渠道获取AutoJs安装包避免使用来历不明的修改版以防安全风险。2.2 必须开启的三大系统权限AutoJs的强大能力建立在系统权限之上截图功能尤其依赖以下三项缺一不可无障碍服务这是AutoJs的“灵魂”。它允许脚本模拟用户操作监听屏幕内容。开启路径系统设置 - 无障碍 - 已下载的服务 - 找到AutoJs并开启。开启后AutoJs的悬浮窗或通知栏通常会有一个“小机器人”图标。悬浮窗权限允许AutoJs在屏幕上层显示控制按钮、日志信息或调试窗口。这对于脚本调试和实时监控非常有用。开启路径系统设置 - 应用管理 - AutoJs - 权限管理 - 开启“悬浮窗”。存储权限这是截图保存到相册的关键。AutoJs需要写入权限才能将图片文件保存到设备的公共存储区域如DCIM、Pictures目录。开启路径系统设置 - 应用管理 - AutoJs - 权限管理 - 开启“存储”或“文件与媒体”权限。一个常见的深坑即使在Android 6.0的动态权限管理中授予了存储权限在某些深度定制的系统如MIUI、EMUI上可能还需要额外开启“允许后台弹出界面”或关闭“电池优化”以防止脚本在后台运行时被系统“杀掉”导致定时截图任务中断。2.3 开发环境与调试技巧建议在电脑上使用VSCode等编辑器编写代码通过Wi-Fi或USB与手机上的AutoJs连接实现实时推送脚本和查看日志。AutoJs自带了一个非常好用的“可视化”开发工具可以录制操作、查看控件布局对于编写找图找色、控件操作的脚本至关重要。初次编写截图脚本时不要急于写完整逻辑。可以先写一个最简单的测试脚本验证核心函数captureScreen()是否能成功执行并打印出返回的图片路径。这能帮你快速定位是权限问题、路径问题还是函数调用问题。3. 核心代码实现从截图到保存的完整链路现在我们进入核心环节。一个健壮的自动化截图脚本绝不仅仅是一行captureScreen()那么简单。它需要包含截图捕获、图片处理、路径生成、文件保存、相册更新等多个环节并且每一环都要考虑异常情况。3.1 基础截图与直接保存AutoJs提供了最基础的截图函数captureScreen()。它返回一个Image对象代表当前屏幕的快照。最简单的保存方式如下// 示例1基础截图并保存到指定路径 auto.waitFor(); // 等待无障碍服务稳定 let img captureScreen(); // 捕获屏幕返回Image对象 let savePath /sdcard/Pictures/Screenshots/auto_capture_ new Date().getTime() .png; img.saveTo(savePath); // 将Image对象保存为文件 toast(截图已保存: savePath);这段代码已经可以实现功能但它存在几个明显问题路径固定所有图片都堆在一个文件夹难以管理。文件名简单仅用时间戳缺乏业务含义。未刷新相册文件虽然保存在存储区但系统的“相册”或“图库”App可能不会立即扫描到新文件导致无法在相册中即时看到。没有错误处理如果存储空间不足或权限异常脚本会直接崩溃。3.2 构建可管理的文件存储体系良好的文件管理是自动化脚本专业性的体现。我推荐按“日期”创建文件夹并按“时间场景”命名文件。// 示例2结构化保存方案 function captureAndSave(description) { auto.waitFor(); try { // 1. 捕获屏幕 let screenImg captureScreen(); if (!screenImg) { throw new Error(截图捕获失败返回了空对象); } // 2. 构建存储路径 let date new Date(); let year date.getFullYear(); let month (date.getMonth() 1).toString().padStart(2, 0); let day date.getDate().toString().padStart(2, 0); let baseDir /sdcard/DCIM/AutoJsCaptures/${year}-${month}-${day}/; // 使用DCIM目录相册识别优先级高 // 确保目录存在 files.ensureDir(baseDir); // 3. 生成有意义的文件名 let hour date.getHours().toString().padStart(2, 0); let minute date.getMinutes().toString().padStart(2, 0); let second date.getSeconds().toString().padStart(2, 0); let timestamp ${hour}${minute}${second}; let descPart description ? _${description} : ; let fileName capture_${timestamp}${descPart}.png; let fullPath baseDir fileName; // 4. 保存图片 let saveResult screenImg.saveTo(fullPath); if (!saveResult) { throw new Error(图片保存到路径失败: ${fullPath}); } log(截图成功保存: ${fullPath}); // 5. 通知媒体库刷新关键步骤 refreshMediaStore(fullPath); return fullPath; } catch (error) { log(截图保存过程发生错误: ${error.message}); console.error(error.stack); toast(截图失败: ${error.message}); return null; } } // 调用示例 let path captureAndSave(主页监控); if (path) { toast(截图成功路径${path}); }这个函数做了几件重要的事使用try...catch包裹核心逻辑防止脚本因单次失败而完全停止将图片按日期归档在文件名中加入可选的描述信息最重要的是保存后调用了一个refreshMediaStore函数来通知系统相册更新。3.3 关键突破让截图立刻出现在系统相册这是很多教程忽略的痛点。在Android系统中单纯向存储设备写入文件媒体扫描器可能不会立即将其加入数据库。我们需要主动“通知”系统。media.scanFile()是AutoJs提供的API但其行为在不同Android版本上可能不一致。一个更稳健的方法是使用Android的MediaScannerConnection广播意图但AutoJs环境执行这个稍微复杂。这里分享一个经过多机型测试的兼容性方法// 示例3刷新媒体库使截图立即出现在相册中 function refreshMediaStore(filePath) { if (!filePath || !files.exists(filePath)) { log(文件不存在无法刷新媒体库: ${filePath}); return false; } try { // 方法1使用AutoJs内置的media.scanFile (API 1.0.2) if (typeof media ! undefined media.scanFile) { let result media.scanFile(filePath); log(使用media.scanFile刷新结果: ${result}); if (result) { return true; } // 如果失败继续尝试方法2 } // 方法2执行shell命令发送广播更底层兼容性更好 // 注意此命令需要shell权限但AutoJs通常具备 let cmd am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://${filePath}; let shellResult shell(cmd, true); if (shellResult.code 0) { log(通过Shell广播刷新媒体库成功); sleep(500); // 给系统一点处理时间 return true; } else { log(Shell命令执行失败: ${shellResult.code}, ${shellResult.error}); } // 方法3备用方案 - 如果上述都失败尝试移动到更“显眼”的目录 log(尝试备用方案将文件移动到DCIM/Camera目录); let cameraDir /sdcard/DCIM/Camera/; files.ensureDir(cameraDir); let newPath cameraDir files.getName(filePath); if (files.copy(filePath, newPath)) { log(文件已复制到Camera目录: ${newPath}); // 对新路径再尝试一次刷新 refreshMediaStore(newPath); return true; } return false; } catch (e) { log(刷新媒体库时发生异常: ${e}); return false; } }这个函数采用了降级策略优先使用AutoJs的高级API失败后尝试更底层的Shell命令最后还有一个移动文件的备用方案。实测中shell命令的方式在绝大多数机型上最为可靠。4. 进阶功能与场景化实战掌握了基础保存后我们可以根据复杂需求对脚本进行增强。自动化截图很少是孤立的它通常是一个更大流程的一部分。4.1 定时循环截图与条件触发假设你需要每30分钟对某个特定App界面截图一次持续一整天。// 示例4定时循环截图 function startScheduledCapture(intervalMinutes, totalDurationMinutes, targetApp) { let intervalMs intervalMinutes * 60 * 1000; let totalCycles Math.floor(totalDurationMinutes / intervalMinutes); let cycleCount 0; log(开始定时截图任务间隔${intervalMinutes}分钟共${totalCycles}次); while (cycleCount totalCycles) { // 1. 确保目标App在前台 if (targetApp) { launchApp(targetApp); sleep(3000); // 等待App完全启动 } // 2. 执行截图 let desc Cycle_${cycleCount 1}_${targetApp || Screen}; let savedPath captureAndSave(desc); if (savedPath) { log(第${cycleCount 1}次截图完成: ${savedPath}); } else { log(第${cycleCount 1}次截图失败); } // 3. 等待下一个周期 cycleCount; if (cycleCount totalCycles) { log(等待${intervalMinutes}分钟进行下一次截图...); sleep(intervalMs); } } log(定时截图任务全部完成); } // 调用示例每30分钟对微信截图一次持续8小时 // startScheduledCapture(30, 8 * 60, 微信);重要提醒长时间运行脚本务必注意功耗和系统休眠。AutoJs自带“保活”机制但为了极致稳定可以在手机设置中为AutoJs开启“自启动”、“后台常驻”并关闭“电池优化”。此外在循环中增加一些随机延迟sleep(random(500, 2000))可以避免被某些App的反自动化机制检测到过于规律的操作。4.2 基于控件状态或图像识别的智能截图单纯的定时截图可能产生大量无效图片。更智能的做法是在特定条件满足时才截图。例如当屏幕上出现“支付成功”弹窗时或者当某个股票App的价格数字超过阈值时。// 示例5条件触发截图 - 以检测特定文本为例 function captureOnCondition(conditionChecker, checkIntervalMs 2000, timeoutMs 60000) { let startTime Date.now(); log(开始条件监控超时时间${timeoutMs/1000}秒); while (Date.now() - startTime timeoutMs) { // 执行条件检查函数 let conditionMet conditionChecker(); if (conditionMet) { log(条件满足触发截图); let path captureAndSave(ConditionTriggered); toast(条件已触发并截图); return path; // 返回截图路径 } // 条件未满足等待一段时间后继续检查 sleep(checkIntervalMs); } log(条件监控超时未触发截图); return null; } // 定义一个检查函数检测屏幕上是否包含“任务完成”文本 function checkForCompletionText() { let found text(任务完成).findOne(100); // 查找文本最多等待100毫秒 return found ! null; } // 启动监控 // captureOnCondition(checkForCompletionText);这里conditionChecker是一个由你定义的函数它可以利用AutoJs强大的选择器text(),id(),className()或图像识别findImage()能力来判断当前屏幕状态。这种模式将截图从“无脑记录”升级为“有意义的快照”。4.3 截图后的自动化处理压缩与上传保存到相册只是第一步。在实际项目中截图往往需要后续处理。例如将截图压缩后通过HTTP API上传到服务器或者调用百度OCR API识别其中的文字。// 示例6截图后压缩并准备上传 function captureCompressAndPrepareUpload(quality 0.7) { // 1. 截图 let originalPath captureAndSave(Original); if (!originalPath) return null; // 2. 压缩图片 let compressedPath originalPath.replace(.png, _compressed.jpg); let img images.read(originalPath); if (img) { // 将PNG转换为JPG并进行质量压缩 images.save(img, compressedPath, jpg, quality * 100); img.recycle(); // 重要释放图像内存 log(图片压缩完成: ${compressedPath}, 质量: ${quality}); // 3. 这里可以添加上传逻辑 // uploadToServer(compressedPath); // 可选删除原始大图以节省空间 // files.remove(originalPath); return compressedPath; } return originalPath; }images.save()方法允许你指定格式和质量。对于网络传输将PNG转为JPG并设置quality0.7左右通常能在清晰度和文件大小间取得良好平衡。务必注意images.read()读取的图片对象在使用后要调用.recycle()释放内存否则在批量处理时可能导致内存不足而脚本崩溃。5. 避坑指南与性能优化在实际部署中我踩过不少坑也总结了一些让脚本更健壮、更高效的经验。5.1 内存泄漏与图片对象回收这是AutoJs脚本尤其是涉及图像处理的脚本最常见的崩溃原因。captureScreen(),images.read(),images.clip()等函数都会创建图像对象它们占用的是原生内存不会像普通JavaScript对象那样被自动垃圾回收。错误示范for (let i 0; i 100; i) { let img captureScreen(); // 每次循环都创建新图像对象 // ... 处理img // 忘记 img.recycle()! } // 循环几十次后很可能内存耗尽脚本被系统终止。正确做法for (let i 0; i 100; i) { let img null; try { img captureScreen(); // ... 处理img } finally { // 确保无论是否出错都尝试回收 if (img ! null) { img.recycle(); } } sleep(100); // 给系统一点喘息时间 }养成“创建即规划回收”的习惯。对于简单的截图保存captureScreen()返回的Image对象在调用saveTo()后其数据已被写入文件可以立即回收。5.2 路径权限与存储空间监控即使授予了存储权限在Android 10及以上版本中由于作用域存储的限制直接写入/sdcard/根目录或某些子目录可能会失败。优先使用AutoJs提供的标准路径或系统媒体目录如/sdcard/DCIM/、/sdcard/Pictures/这些目录的访问限制通常更少。在脚本开始或每次截图前可以增加一个存储空间检查function checkStorageAvailable(minSpaceMB 50) { let sdcardPath files.getSdcardPath(); let availableBytes files.getFreeSpace(sdcardPath); let availableMB availableBytes / (1024 * 1024); if (availableMB minSpaceMB) { log(存储空间不足可用空间仅${availableMB.toFixed(2)}MB要求${minSpaceMB}MB); toast(存储空间不足请清理后重试); return false; } return true; }5.3 脚本稳定性与容错设计自动化脚本需要长时间无人值守运行稳定性至关重要。异常捕获全局化使用try...catch包裹主要功能块并记录详细的错误日志到文件方便事后排查。心跳与自恢复对于长周期任务可以设计一个“看门狗”机制。主脚本定期向一个文件写入时间戳另一个监控脚本检查这个时间戳如果超过预期时间未更新则判断主脚本可能已崩溃并尝试重新启动它。避免过度阻塞在循环中sleep的时间不宜过短避免高频循环耗尽CPU。同时在等待控件出现时使用带超时的findOne(timeout)而不是无限循环的findOne()防止脚本卡死。日志分级输出使用log(),console.verbose(),console.error()等不同级别的日志函数。在调试阶段开启详细日志在生产环境可以关闭部分日志以减少性能开销和日志文件体积。5.4 对抗应用检测与无障碍服务断开一些应用如游戏、金融类App会检测无障碍服务甚至主动断开。这可能导致你的脚本突然失效。策略一伪装在AutoJs Pro中可以使用“伪装无障碍类名”的功能。在开源版中可以尝试修改AutoJs的包名需要一定的技术能力。策略二监控与重连在脚本中定期检查无障碍服务是否开启。function ensureAccessibility() { if (!auto.service) { toast(无障碍服务已断开正在尝试打开设置...); // 尝试跳转到无障碍设置页面 app.startActivity({ action: android.settings.ACCESSIBILITY_SETTINGS }); // 这里无法自动开启需要人工干预。脚本可以在此等待或退出。 sleep(10000); // 再次检查 if (!auto.service) { log(无法恢复无障碍服务脚本停止); exit(); } } } // 在主循环中定期调用 ensureAccessibility();6. 完整项目示例一个监控类App数据变化的自动截图工具让我们将所有知识点整合构建一个模拟真实场景的项目监控某个新闻App的头条新闻标题变化每当检测到标题更新时自动截图并保存同时将新闻标题文本记录到日志文件中。/* * 项目新闻头条变化监控与自动截图工具 * 功能定时打开某新闻App检查头条标题如果发现变化则截图并记录。 */ // 配置区域 const TARGET_APP 网易新闻; // 目标App名称 const CHECK_INTERVAL 5 * 60 * 1000; // 检查间隔5分钟 const HEADLINE_ID com.netease.newsreader.activity:id/bdx; // 头条新闻标题的控件ID需通过AutoJs控件查看器获取 const STORAGE_DIR /sdcard/Documents/NewsMonitor/; // let lastHeadlineText ; // 上一次记录的头条标题 function main() { // 初始化 auto.waitFor(); files.ensureDir(STORAGE_DIR); logFile STORAGE_DIR monitor_log.txt; log(新闻头条监控脚本启动); writeLog( 监控开始于 new Date().toLocaleString() ); while (true) { try { monitorCycle(); } catch (e) { console.error(监控周期发生未捕获错误:, e); writeLog([ERROR] ${new Date().toLocaleString()} - ${e.message}); sleep(30000); // 出错后等待30秒再继续 } sleep(CHECK_INTERVAL); } } function monitorCycle() { log(开始一个新的监控周期); // 1. 启动目标App launchApp(TARGET_APP); sleep(5000); // 等待App加载 // 2. 查找头条标题控件 let headlineWidget id(HEADLINE_ID).findOne(8000); // 等待8秒 if (!headlineWidget) { log(未找到头条标题控件可能页面加载失败或ID已变更); writeLog([WARN] ${new Date().toLocaleString()} - 控件查找失败); return; } let currentText headlineWidget.text(); log(当前头条标题: ${currentText}); // 3. 判断标题是否发生变化 if (lastHeadlineText ) { // 第一次运行只记录不截图 log(首次运行记录初始标题); lastHeadlineText currentText; writeLog([INIT] ${new Date().toLocaleString()} - 初始标题: ${currentText}); } else if (currentText ! lastHeadlineText) { // 标题发生变化 log(检测到头条新闻更新旧标题: ${lastHeadlineText} - 新标题: ${currentText}); // 4. 执行截图 let screenshotPath captureAndSave(NewsUpdate_${formatTime()}); if (screenshotPath) { log(变化已截图: ${screenshotPath}); // 5. 记录到日志文件 let logEntry [UPDATE] ${new Date().toLocaleString()} - 标题更新: ${lastHeadlineText} - ${currentText} | 截图: ${files.getName(screenshotPath)}\n; writeLog(logEntry); } // 更新记录 lastHeadlineText currentText; } else { log(头条标题未发生变化); } // 6. 返回桌面结束本次监控可选 home(); sleep(2000); } // 工具函数写入日志文件 function writeLog(content) { files.append(logFile, content \n); } // 工具函数格式化时间用于文件名 function formatTime() { let d new Date(); return ${d.getFullYear()}${(d.getMonth()1).toString().padStart(2,0)}${d.getDate().toString().padStart(2,0)}_${d.getHours().toString().padStart(2,0)}${d.getMinutes().toString().padStart(2,0)}${d.getSeconds().toString().padStart(2,0)}; } // 启动脚本 main();这个示例展示了如何将截图功能嵌入到一个完整的业务逻辑中状态判断 - 条件触发 - 执行截图 - 记录日志。它包含了错误处理、日志记录、控件查找等实用技巧是一个可以直接修改使用的模板。最后我想强调的是自动化脚本的开发和维护是一个持续的过程。App的UI会更新控件ID会变化系统权限策略也会调整。一个优秀的自动化脚本除了代码本身健壮还需要有完善的文档和配置分离机制。将目标App的控件ID、检查间隔等易变参数放在脚本开头的配置区域而不是硬编码在逻辑中这样当需要适配新版本时你只需要修改几行配置而不是重构整个脚本。这就是从“能运行”的脚本到“好维护”的工具之间的关键一步。