--- url: 'https://www.wuyunai.com/docs/blog/index.html' description: Auto.js Pro 博客文章 - 了解最新功能、使用技巧和开发指南 --- # 博客 这里收集了 Auto.js Pro 相关的博客文章,包括功能介绍、使用技巧和开发指南。 --- --- url: 'https://www.wuyunai.com/docs/blog/7zip-plugin.html' description: >- Auto.js Pro 7Zip 压缩插件使用教程 - 基于 p7zip 16.02 制作,支持多种格式文件的压缩与解压。学习如何使用 7Zip 插件进行文件压缩、解压操作。 --- ![7zip-plugin](/assets/image/7zip-plugin.png) # 1. 插件信息 > 免责声明:本插件以及本博客内容均由第三方提供,请您仔细甄别后再下载和使用。若插件有任何侵权、病毒或其他违法行为,请联系我方下架。 作者:LZX284 基于 p7zip 16.02 制作,支持多种格式文件的压缩与解压,安装后即可使用。 支持的 API 架构:armeabi-v7a, arm64-v8a, x86, x86\_64 下载地址: > 从 Pro 9.2 开始,打包时插件可被合并到 apk 中,打包后无需再安装插件即可使用。 ### 2. 用法示例 #### 解压 javascript ```javascript let P7zip = $plugins.load("cn.lzx284.p7zip"); let mP7zip = new P7zip(); // 源路径(目录或文件皆可,必须是完整路径) let srcPath = "/storage/emulated/0/脚本/"; // 目标目录(必须是完整路径) let dirPath = "/storage/emulated/0/"; // 压缩类型 // 支持的压缩类型包括: zip 7z bz2 bzip2 tbz2 tbz gz gzip tgz tar wim swm xz txz。 let type = "7z"; // 压缩等级: 0-9。 let level = 0; // 压缩密码(注意,在p7zip中,类似""这样的空字符也算是密码的一种,与没有密码不一样) let password = "password"; // 压缩文件路径(必须是完整路径) let compressedFile = "/storage/emulated/0/test." + type; let rarFile = files.path("./test.rar"); // rar格式仅支持解压缩 let cmdStr = "7z x -y -aos -p" + password + " -o" + dirPath + " " + rarFile; // 支持的解压缩类型包括:zip、7z、bz2、bzip2、tbz2、tbz、gz、gzip、tgz、tar、wim、swm、xz、txz以及rar、chm、iso、msi等众多格式。 // p7zip解压(若文件已存在则跳过) // 无密码解压:mP7zip.extract(compressedFile, dirPath) switch (mP7zip.extract(compressedFile, dirPath, password)) { case 0: toastLog("解压缩成功!请到 " + dirPath + " 目录下查看。"); break; case 1: toastLog("压缩结束,存在非致命错误(例如某些文件正在被使用,没有被压缩)"); break; case 2: toastLog("致命错误"); break; case 7: toastLog("命令行错误"); break; case 8: toastLog("没有足够内存"); break; case 255: toastLog("用户中止操作"); break; default: toastLog("未知错误"); } ``` #### 压缩 javascript ```javascript let P7zip = $plugins.load("cn.lzx284.p7zip"); let mP7zip = new P7zip(); // 源路径(目录或文件皆可,必须是完整路径) let srcPath = "/storage/emulated/0/脚本/"; // 目标目录(必须是完整路径) let dirPath = "/storage/emulated/0/"; // 压缩类型 // 支持的压缩类型包括: zip 7z bz2 bzip2 tbz2 tbz gz gzip tgz tar wim swm xz txz。 let type = "7z"; // 压缩等级: 0-9。 let level = 0; // 压缩密码(注意,在p7zip中,类似""这样的空字符也算是密码的一种,与没有密码不一样) let password = "password"; // 压缩文件路径(必须是完整路径) let compressedFile = "/storage/emulated/0/test." + type; let rarFile = files.path("./test.rar"); // rar格式仅支持解压缩 let cmdStr = "7z x -y -aos -p" + password + " -o" + dirPath + " " + rarFile; // p7zip压缩(若文件已存在则跳过) // 无密码压缩:mP7zip.add(type, level, srcPath, compressedFile) switch (mP7zip.add(type, level, srcPath, compressedFile, password)) { case 0: toastLog("压缩成功!文件已保存为: " + compressedFile); break; case 1: toastLog("压缩结束,存在非致命错误(例如某些文件正在被使用,没有被压缩)"); break; case 2: toastLog("致命错误"); break; case 7: toastLog("命令行错误"); break; case 8: toastLog("没有足够内存"); break; case 255: toastLog("用户中止操作"); break; default: toastLog("未知错误"); } ``` #### 自定义命令 javascript ```javascript let P7zip = $plugins.load("cn.lzx284.p7zip"); let mP7zip = new P7zip(); // 源路径(目录或文件皆可,必须是完整路径) let srcPath = "/storage/emulated/0/脚本/"; // 目标目录(必须是完整路径) let dirPath = "/storage/emulated/0/"; // 压缩类型 // 支持的压缩类型包括: zip 7z bz2 bzip2 tbz2 tbz gz gzip tgz tar wim swm xz txz。 let type = "7z"; // 压缩等级: 0-9。 let level = 0; // 压缩密码(注意,在p7zip中,类似""这样的空字符也算是密码的一种,与没有密码不一样) let password = "password"; // 压缩文件路径(必须是完整路径) let compressedFile = "/storage/emulated/0/test." + type; let rarFile = files.path("./test.rar"); // rar格式仅支持解压缩 let cmdStr = "7z x -y -aos -p" + password + " -o" + dirPath + " " + rarFile; // 自定义命令:解压加密的rar压缩文件 switch (mP7zip.cmdExec(cmdStr)) { case 0: toastLog("解压缩成功!请到 " + dirPath + " 目录下查看。"); break; case 1: toastLog("压缩结束,存在非致命错误(例如某些文件正在被使用,没有被压缩)"); break; case 2: toastLog("致命错误"); break; case 7: toastLog("命令行错误"); break; case 8: toastLog("没有足够内存"); break; case 255: toastLog("用户中止操作"); break; default: toastLog("未知错误"); } ``` ### 3. 附录(p7zip 命令行使用参考) #### 7z|7za 子命令 \[参数开关] 压缩包 \[<文件名称>|<@列表文件…>] 在方括号内的表达式(“\[” 和 “]”之间的字符)是可选的。在书名号内的表达式(“<” 和 “>”之间的字符)是必须替换的表达式(而且要去掉括号)。 7-Zip 支持和 Windows 相类似的通配符: “\_”可以使用星号代替零个或多个字符。 “?”可以用问号代替名称中的单个字符。 如果只用 `_`,7-Zip 会将其视为任何扩展名的全部文件。 ##### 子命令 * a: 添加到压缩文件 * b: 基准测试 * d: 从档案中删除文件 * e: 解压档案文件 (无目录名称) * l: 列出压缩文件内容 * t: 测试压缩文件的完整性 * u: 更新文件到压缩文件中 * x: 完整路径下解压文件 ##### 参数开关 * -ai\[r\[-|0]]{@列表文件|!通配符}: 包括 压缩文件 * -ax\[r\[-|0]]{@列表文件|!通配符}: 排除 压缩文件 * -bd: 禁用百分比显示功能 * -i\[r\[-|0]]{@列表文件|!通配符}: 包括 文件名称 * -m<参数>,关于它有许多参数及指令,这里仅介绍简单且常用的用法。 -m0=<压缩算法> 默认使用 lzma -md=<字典大小> 设置字典大小,例如 -md=32m -mfb=64 number of fast bytes for LZMA = 64 -ms=\ 是否固实压缩 -mx=<1~~9> 压缩级别-m0=压缩算法 默认使用 lzma -mx=数字 1~~9 压缩级别 * -o{目录}: 设置输出目录 * -p{密码}: 设置密码 * -r\[数字]: 递归子目录,使用数字定义递归子目录的深度 * -scs{UTF-8 | WIN | DOS}: 设置列表文件的编码格式 * -sfx\[{名称}]: 创建 SFX 自解压文件 * -si\[{名称}]: 从 stdin(标准输入设备)读取数据 * -slt: 为 l (列表) 命令显示技术信息 * -so: 将数据写入 stdout(标准输出设备) * -ssc\[-]: 设置大小写区分模式 * -ssw: 压缩共享文件 * -t{类型}: 设置压缩文件格式,(7z, zip, gzip, bzip2 or tar. 7z 是默认的格式) * -v{大小}\[b|k|m|g]: 分卷压缩 * -u\[-]\[p#]\[q#]\[r#]\[x#]\[y#]\[z#]\[!新建档案\_名称]: 更新选项 * -w\[{路径}]: 指定工作目录,路径为空时代表临时文件夹目录 * -x\[r\[-|0]]]{@列表文件|!通配符}: 排除 文件名称 * -y: 所有询问选是 ##### 退出代码 * 0 :正常,没有错误 * 1 :警告,没有致命的错误,例如某些文件正在被使用,\* 没有被压缩 * 2 :致命错误 * 7 :命令行错误 * 8 :没有足够的内存 * 255 :用户中止了操作 ### 开源地址 已过期 GitHub:[p7zip\_autojs\_plugin](https://github.com/LZX284/p7zip_autojs_plugin) --- --- url: 'https://www.wuyunai.com/docs/blog/encryption.html' description: >- Auto.js Pro 代码加密完整说明 - 了解 Auto.js Pro 支持的加密方式,包括本地加密、在线加密、Rhino 引擎加密、Node.js 引擎加密等。学习如何保护代码安全。 --- # Auto.js Pro 加密说明 Auto.js Pro 支持在打包时对 js 文件进行加密,加密是通过多种方式将源码编译、加密为非明文文件。 > 注意: Auto.js Pro 的加密并不包括对代码的混淆,建议在使用自带加密前再用其他混淆工具对代码进行一次混淆。可配置编译脚本在打包时自动执行混淆,具体可参考 Pro 9.2.9 以上版本中的示例 -> 项目与打包 -> 代码混淆。 ## 加密方式 由于 Auto.js Pro 目前有两个引擎 ———— 旧引擎 Rhino 和新引擎 Node.js。Pro 8 以前的代码均是使用 Rhino 引擎,在 Pro 9 则可在文件最前面增加”nodejs”来使用 Node.js 引擎,同时启用全新的异步 API(具体参见[Pro 9 文档](/docs/v9/))。 每个引擎的加密实现方式有所不同,破解难度也会有所区别。在打包时可选择不同的加密等级,这里将说明不同加密方式的区别。 ![encryption-levels](/assets/image/encryption-levels.png) **encryption-levels** ### Node.js 加密 > 还原难度:⭐️⭐️⭐️⭐️⭐️ Node.js 引擎目前只有一种加密方式,因此无论选择哪种加密等级结果没有不同。Node.js 加密的还原难度较高,推荐使用新的 Node.js 引擎来编写你的代码,文档参见[第二代 API 文档](/docs/v9/)。 目前 Node.js 加密方式暂不支持 mjs 文件/ES Module,这些文件不会被加密,预计在 9.5 版本支持。 使用 Node.js 加密后运行错误,请先使用不加密方式打包,若加密后报错不加密正常,则可能是以下原因: #### 模块文件未被识别为 nodejs 文件,被使用 Rhino 引擎方式加密,导致无法运行。 比如主模块文件是 main.node.js,require 了另一个模块文件 mod.js,但是 mod.js 文件既没有以.node.js 结尾,也没有用`"nodejs";`文件头,因此在加密时被当成非 nodejs 文件使用快照、dex 等加密方式加密。运行时自然会报错。 有两种方式解决: * 所有 nodejs 文件,都以.node.js 结尾,或使用”nodejs”;\`文件头 * 在 project.json 中,配置`"type": "node"`,则项目下的所有文件默认是 nodejs 引擎(此时 rhino 引擎的文件需要加上`"rhino;"`的文件头) #### node\_modules 下的文件未被识别为 nodejs 文件,被使用 Rhino 引擎方式加密,导致无法运行。 原理同上,推荐在 project.json 中,配置`"type": "node"`来解决这个问题。配置后项目下的所有文件默认是 nodejs 引擎,此时 rhino 引擎的文件需要加上`"rhino;"`的文件头。 #### 在 64 位手机上加密,在 32 位手机上运行 Node.js 加密暂不支持跨 CPU 架构加密,因此只能在 64 位手机上加密只能在 64 位手机上运行;在 32 位手机上加密,CPU 架构选择 armeabi-v7a,则可以在 64 位、32 位手机上运行。 ### Rhino 普通加密 > 还原难度:⭐️ 这种加密仅仅是将文件用某种方式加密,运行时需要解密,比较容易被还原。 ### Rhino Dex 加密 > 还原难度:⭐️⭐️ 这种加密方式将 js 文件编译为 dex 文件,一方面提升运行效率,另一方面提高了破解门槛。但 dex 本身仍然能看出代码逻辑,推荐再使用加固或 dex2c 等方式进一步保护代码。 ### Rhino Snapshot 加密 > 还原难度:⭐️⭐️⭐️ 这种加密方式将 js 文件编译为字节码文件,提升了运行效率和破解门槛。但字节码文件中的字符串等仍然为明文,因此不管使用什么加密,都推荐先使用混淆工具混淆。 ## 加密时或加密后遇到错误 若加密过程遇到编译错误,可能是因为项目中包含 web 但 js 文件,这些文件也被参与加密导致。可以通过`ignore文件`将其排除加密范围,具体参见[项目与配置](/guide/project.html#ignore配置)。 --- --- url: 'https://www.wuyunai.com/docs/blog/ffmpeg-plugin.html' description: >- Auto.js Pro FFMpeg 插件使用指南 - 可调用 FFmpeg 处理音视频文件、音视频流,支持文件格式转换、视频加水印等功能。支持打包时合并到 apk。 --- ![ffmpeg-plugin](/assets/image/ffmpeg-plugin.png) # 插件信息 > 从 Pro 9.2 开始,打包时插件可被合并到 apk 中,打包后无需再安装插件即可使用。 官方 FFMpeg 插件,可调用 FFmpeg 处理音视频文件、音视频流,比如文件格式转换(视频提取音频、提取图片,mp4 转 avi 等),视频加水印等,更多信息参见[FFMpeg 百科](https://baike.baidu.com/item/ffmpeg/2665727)。 本插件提供了调用 ffmpeg 命令的方法。 # 插件下载 64 位: 32 位: # 使用示例 ## Rhino 引擎(Pro 8 API) ```javascript // 加载ffmpeg插件 let ffmpeg = $plugins.load("org.autojs.plugin.ffmpeg"); let mp4Path = "/sdcard/input.mp4"; let mp3Path = "/sdcard/output.mp3"; // 使用ffmpeg提取文件为mp3,参见https://www.baidu.com/s?wd=ffmpeg%20%E8%A7%86%E9%A2%91%E6%8F%90%E5%8F%96mp3 let result = ffmpeg.inProcess.exec(`-i "${mp4Path}" "${mp3Path}"`); console.log(result); ``` ## Node.js 引擎(Pro 8 API) ```javascript "nodejs"; const plugins = require("plugins"); // 加载ffmpeg插件 const ffmpeg = plugins.load("org.autojs.plugin.ffmpeg"); const mp4Path = "/sdcard/input.mp4"; const mp3Path = "/sdcard/output.mp3"; // 使用ffmpeg提取文件为mp3,参见https://www.baidu.com/s?wd=ffmpeg%20%E8%A7%86%E9%A2%91%E6%8F%90%E5%8F%96mp3 const result = ffmpeg.inProcess.exec(`-i "${mp4Path}" "${mp3Path}"`); console.log(result); ``` --- --- url: 'https://www.wuyunai.com/docs/blog/mlkit-ocr-plugin.html' description: >- Auto.js Pro 官方 MLKitOCR 插件使用指南 - 基于谷歌 MLKit,识别速度超过绝大部分 OCR。支持打包时合并到 apk,无需单独安装插件即可使用。 --- ![mlkit-ocr-plugin](/assets/image/mlkit-ocr-plugin.png) # 插件信息 > 从 Pro 9.2 开始,打包时插件可被合并到 apk 中,打包后无需再安装插件即可使用。 官方 MLKitOCR 模块,下载后需要安装才可使用,打包时可合并到安装包中无需再安装插件才能运行,需要 Pro 9.2.11 以上版本。 基于谷歌 MLKit,识别速度超过绝大部分 OCR。 # 插件下载 蓝奏云下载: # 使用示例 ## Rhino 引擎(Pro 8 API) ```javascript // 加载OCR插件,需要先下载官方MLKitOCR插件 let MLKitOCR = $plugins.load("org.autojs.autojspro.plugin.mlkit.ocr"); let ocr = new MLKitOCR(); requestScreenCapture(); for (let i = 0; i < 5; i++) { let capture = captureScreen(); // 检测截图文字并计算检测时间,首次检测的耗时比较长 // 检测时间取决于图片大小、内容、文字数量 let start = Date.now(); let result = ocr.detect(capture); let end = Date.now(); console.log(result); toastLog(`第${i + 1}次检测: ${end - start}ms`); sleep(3000); } ocr.release(); ``` ## Node.js 引擎(Pro 9 API) ```javascript "nodejs"; const plugins = require("plugins"); const { requestScreenCapture } = require("media_projection"); const { showToast } = require("toast"); const { delay } = require("lang"); async function main() { // 加载OCR插件,需要先在Auto.js Pro的插件商店中下载官方MLKitOCR插件 const MLKitOCR = await plugins.load("org.autojs.autojspro.plugin.mlkit.ocr"); // 创建OCR对象 const ocr = new MLKitOCR(); const capturer = await requestScreenCapture(); for (let i = 0; i < 5; i++) { const capture = await capturer.nextImage(); // 检测截图文字并计算检测时间,首次检测的耗时比较长 // 检测时间取决于图片大小、内容、文字数量 const start = Date.now(); const result = await ocr.detect(capture); const end = Date.now(); console.log(result); showToast(`第${i + 1}次检测: ${end - start}ms`); await delay(3000); } } main().catch(console.error); ``` *** --- --- url: 'https://www.wuyunai.com/docs/blog/ui.html' description: Auto.js Pro UI 界面与权限交互教程 - 学习如何创建权限引导界面,枚举常见权限,获取权限状态,提供直观的权限跳转方案,提升用户体验。 --- # UI界面与常见权限互动 ## 拟解决的问题 运行脚本前,我们往往需要用户开启一些必须的权限。由于用户水平不同,造成部分人群使用困难,甚至不知道需要先开启权限,这与无障碍权限“以人为本”的初衷相违背,因此我们需要一个直观的引导界面,以及方便的跳转方案,降低沟通成本,提升用户使用体验。 ## 解决思路 1)枚举常见权限 2)获取权限状态 3)开启或跳转到权限开启界面 4)将权限状态与ui界面关联互动 ## 具体步骤 我们运行脚本,常见的权限主要有无障碍服务、悬浮窗权限、前台服务、无障碍稳定模式、截图权限、查看使用统计权限、后台弹出权限(小米手机的miui系统),下面将对这7个权限的状态和开启办法逐一进行讨论。 ### 无障碍服务 无障碍服务是否开启可以通过[auto.service](/v8/automator/api.html#autoservice)或[auto.rootInActiveWindow](/v8/automator/api.html#auto-rootinactivewindow)获取,开启方式在[上一篇博客](#)中已经进行了详细的介绍,这里不再赘述。 ### 悬浮窗权限 悬浮窗权限的判断autojspro已经有封装好的方法,可以直接用[floaty.checkPermission()](/v8/floaty.html#floaty-checkpermission)的返回值进行判断,此权限的跳转,这里介绍两种方法,一种是autojspro自带的基于context.startActivity封装的[floaty.requestPermission()](/v8/floaty.html#floatyrequestpermission),用起来比较简单。另外介绍一种基于activity.startActivityForResult()封装的方法,可以在activity结束的回调里进行自己的操作,相对复杂,但是一些情形下用起来比较方便,同时也可以初步学习activity.startActivityForResult()的用法,不仅可以用在权限获取,在媒体库选取等方面也同样适用。这里仅展示原理以及相关代码,不做过多细致的讲解。 使用activity.startActivityForResult()有两个核心部分,一个发起带有标记的intent,另一个接收此标记的监听。 ```javascript let mIntent = app.intent({ action: "android.settings.action.MANAGE_OVERLAY_PERMISSION", data: "package:" + context.getPackageName(), }); //这里把数字1作为标记 activity.startActivityForResult(mIntent, 1); activity.getEventEmitter().on("activity_result", (requestCode, resultCode, data) => { if (requestCode == 1) { //requestCode为1说明是跳转到开启悬浮窗权限的activity结束的回调 toast(floaty.checkPermission()); //这里可以进行ui界面的同步 } }); ``` ### 前台服务 前台服务的开启和查询,官方的[Settings](/v8/settings.html)模块都进行了很好地封装,我们可以直接使用。 查询是否开启前台服务:$settings.isEnabled(‘foreground\_service’) 设置开启前台服务:$settings.setEnabled(‘foreground\_service’, true); ### 无障碍稳定模式 无障碍有一个稳定模式,使用场景不多,这里可以看安卓文档[AccessibilityServiceInfo](https://developer.android.google.cn/reference/android/accessibilityservice/AccessibilityServiceInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS)里的相关说明,大致意思就是不访问不重要的视图。 稳定模式的查询可以通过`$settings.isEnabled('stable_mode')`来查询。 设置开启稳定模式:$settings.setEnabled(‘stable\_mode’, true); ### 截图权限 目前新版的autojspro的[images模块](/v8/images.html)也支持了截图权限的查询、申请和注销,使用起来也很方便 查询是否有截图权限:[$images.getScreenCaptureOptions()](/v8/images.html#images-getscreencaptureoptions) 请求截图权限:[$images.requestScreenCapture(options)](/v8/images.html#images-requestscreencapture-landscap) 注销截图权限:[$images.stopScreenCapture()](/v8/images.html#images-stopscreencapture) 这里特别提一下,安卓11以上设备,一般可以不申请截图权限,直接使用无障碍权限可以获取到ajtojspro的image对象 安卓11+使用无障碍权限截图:[$automator.takeScreenshot()](/v8/automator/api.html#automator-takescreenshot) > 作者注:这种方式截图频繁有限制,一般是1秒一次。 ### 查看使用统计权限 这个权限一般使用较少,但个别时候也有大作用,比如为了[currentPackage()](/v8/globals.html#currentpackage)获取最近包名更准确,或者获取某一应用的运行状态。 由于官方没有提供此权限的查询办法,这里我查阅安卓文档,自行封装了一份,可以判断是否有此权限 ```javascript checkSystemService("usage_stats") function checkSystemService(service) { importClass(android.app.AppOpsManager); appOps = context.getSystemService(context.APP_OPS_SERVICE); mode = appOps.checkOpNoThrow("android:get_" + service, android.os.Process.myUid(), context.getPackageName()); return (granted = mode == AppOpsManager.MODE_ALLOWED); } ``` 跳转直接使用intent即可 ```javascript app.startActivity({ action: "android.settings.USAGE_ACCESS_SETTINGS", }); ``` ### 后台弹出权限(miui系统) 这个权限主要是会影响到[app.launchPackage(packageName)](/v8/app#applaunchpackagepackagename)打开未启动的应用,因此必要时我们可以判断是否有该权限,然后对用户进行提示,此权限由于是miui系统自己创造的,且没有文档说明,研究起来用了不少时间,这里我直接贴出来源码 ```javascript checkMiuiPermission(10021); function checkMiuiPermission(flag) { //flag为10021是后台弹出界面,为10016是NFC权限 importClass(android.app.AppOpsManager); let appOps = context.getSystemService(context.APP_OPS_SERVICE); try { let myClass = util.java.array("java.lang.Class", 3); myClass[0] = java.lang.Integer.TYPE; myClass[1] = java.lang.Integer.TYPE; myClass[2] = java.lang.Class.forName("java.lang.String"); let method = appOps.getClass().getMethod("checkOpNoThrow", myClass); let op = new java.lang.Integer(flag); result = method.invoke(appOps, op, new java.lang.Integer(android.os.Process.myUid()), context.getPackageName()); return result == AppOpsManager.MODE_ALLOWED; } catch (err) { console.error(err); } } ``` --- --- url: 'https://www.wuyunai.com/docs/blog/vscode-debug-v9.html' description: >- Auto.js Pro 9.3 版本 VSCode 调试完整教程 - 学习如何使用全新的 VSCode 插件进行设备连接、文件同步、远程调试。包含智能设备扫描、文件管理、增量同步、远程终端等功能。 --- # 新版VSCode调试教程 Auto.js Pro 9.3 版本带来了全新的 VSCode 插件和完美的设备连接、文件同步、调试体验: * 智能扫描设备、连接设备,永久授权后可自动连接 ![设备授权](/assets/image/vsc-debug2-intro-prompt.jpg) * 可在 VSCode 直接浏览、管理、编辑、运行手机设备上的文件 ![文件管理](/assets/image/vsc-debug2-intro-files.jpg) * 高效、增量、极速的文件同步,再也不用担心运行时文件缺失问题 ![文件同步](/assets/image/vsc-debug2-intro-sync.jpg) * 可在 VSCode 运行和手机环境一致的远程终端,无需 adb 命令 ![远程终端](/assets/image/vsc-debug2-intro-terminal.jpg) 除此之外,插件还有很多优化和功能完善,因此新插件的使用方式也有所不同,使用前务必阅读本教程和视频教程。 视频已失效!!! ## 环境准备 ### Auto.js Pro 9.3 以上版本 新插件需要 Pro 9.3 以上版本才能发挥全部作用,目前 9.3 版本已发布,请在[官网下载](/)。 ### VSCode VSCode 是电脑上的代码编辑器,支持 Windows、Mac、Linux 等,官方下载地址: (VSCode 本身是免费的,用百度等搜索到的可能是国内无良商家的“付费”版本,请仔细辨别) 下载后,先解压压缩包,然后打开Vscode,选择扩展,右上角 => ... => 选择 从 VSIX 安装... => 选中刚刚解压的 VSIX 文件,即可成功安装上插件 ### 安装插件 安装并打开 VSCode 后,在侧边菜单中打开拓展面板: ![拓展](/assets/image/vscode-debug-exts.jpg) 搜索 chinese,安装中文语言包: ![中文包](/assets/image/vscode-debug-ch-ext.jpg) 搜索 hyb1996,安装 Auto.js-Pro-Ext,也即 Auto.js Pro 插件。安装后重新启动 VSCode。 ![Auto.js-Pro-Ext](/assets/image/vsc-debug2-ext.png) ## 连接设备 ### 智能连接(推荐) > 本连接方式的前提条件是**手机和电脑在同一局域网中**,比如连接到同一 WiFi,或者手机开启热点给电脑连接等。模拟器需要开启桥接模式。 打开并登录 Auto.js Pro,进入 Auto.js Pro 主界面,打开侧拉菜单,展开开发者调试,**开启“允许远程调试”**。 ![允许远程调试](/assets/image/vsc-debug2-dev-mode.png) 启动 VSCode,使用快捷键`F1`,或快捷键`Ctrl + Shift + P`,或在上方菜单点击`查看` -> `命令面板`。 在命令面板中,输入 Auto.js Pro,选择重新扫描设备。 ![重新扫描设备](/assets/image/vsc-debug2-scan-device.png) 此时手机上会弹出一个授权弹窗,选择“永久允许”或“本次允许”。 ![设备授权](/assets/image/vsc-debug2-intro-prompt.jpg) 授权后会在 VSCode 右下角提示“新设备已连接”,同时在左下角显示当前设备的名称。 首次建立连接后,后续打开 VSCode 和 Auto.js Pro 后,只要重新扫描设备,即可自动连接设备,无需输入 IP 地址。 > 若未能连接,参考文章末尾的常见问题。 ### 手动连接 > 在模拟器或其他场景时,有时我们需要手动输入 IP 地址连接设备。此连接方式也同样需要**手机和电脑在同一局域网中**,或者电脑有公网 IP 地址、绑定域名。 启动 VSCode,使用快捷键`F1`,或快捷键`Ctrl + Shift + P`,或在上方菜单点击`查看` -> `命令面板`。 在命令面板中,输入 Auto.js Pro,选择”连接到新设备“,此时可以看到电脑的 IP 地址,记住这个 IP 地址,关闭窗口。(也可以用`ipconfig`或`ifconfig`命令的高级方式查看电脑的 IP 地址) ![连接到新设备](/assets/image/vsc-debug2-local-ip.png) 打开 Auto.js Pro 的侧拉菜单,展开开发者调试,开启“允许远程调试”,点击”主动连接电脑“。在弹出的弹窗中输入上面的电脑 IP 地址,点击确定。 ![输入IP地址](/assets/image/vsc-debug2-input-ip.png) 连接成功后 VSCode 将弹出“新设备已连接”,同时在左下角显示当前设备的名称。 若连接一段时间(可能很快,也可能长达一分钟)后提示错误,比如`Connection Timeout`,`Connection Refused`等,则为 IP 无法连接,有多种原因,参考文章末尾的常见问题。 ### USB/adb 方式连接 > USB/adb 方式连接适用于电脑和手机不在同一局域网、模拟器的情况,相比起无线方式连接,此种方式的传输效率可能更高。 打开并登录 Auto.js Pro,进入 Auto.js Pro 主界面,打开侧拉菜单,展开开发者调试,**开启“允许远程调试”**。 ![允许远程调试](/assets/image/vsc-debug2-dev-mode.png) 使用 USB 接口将手机连接到电脑(若是模拟器,则开启模拟器的 ADB 调试、开发者调试开关),在手机的系统设置中开启开发者模式和 USB 调试。可参考网络资料或百度经验[安卓手机 USB 调试模式打开的方法](https://jingyan.baidu.com/article/ca41422fce01335faf99ed35.html)。 启动 VSCode,使用快捷键`F1`,或快捷键`Ctrl + Shift + P`,或在上方菜单点击`查看` -> `命令面板`。 在命令面板中,输入 Auto.js Pro,选择”连接到新设备“,点击使用 adb 连接。 ![adb连接](/assets/image/vsc-debug2-adb1.png) 此时会弹出所有使用 adb 连接的设备列表,选择相应设备即可自动连接到该设备。连接成功后 VSCode 将弹出“新设备已连接”,同时在左下角显示当前设备的名称。 ![adb设备](/assets/image/vsc-debug2-adb2.png) 如果此时提示”没有已通过 adb 连接到设备“,检查手机是否弹出“是否允许 USB 调试”的弹框,允许后可以再重试尝试”连接到新设备“->”使用 adb 连接设备”。 ## 创建项目与运行代码 在 VSCode 调试中,我们建议以项目为单位进行,即使是调试一个单文件,也建议放在一个项目中,这样可获得更好的调试、自动补全体验。 打开 VSCode 的命令面板,输入”新建”,此时有两个关于 Auto.js Pro 的命令: * 新建第一代 API 项目(Rhino) * 新建第二代 API 项目(Node.js) 有关这两个引擎和 API 的区别请参考[Auto.js Pro 文档](/guide/#auto-js-pro学习路线综述),根据自己的需要选择项目类型。 之后会打开文件选择器,选择一个**空文件夹**或者新建一个新文件夹后,点击“新建到这里”即可创建项目并自动打开该项目。 默认会创建 project.json、自动补全文件、main.js 等文件,可自行新建文件、编辑代码,编辑代码也会有相应的补全和说明: ![补全和函数说明](/assets/image/vsc-debug2-method-doc-hint.png) 要运行代码,则打开命令面板,选择”Auto.js Pro: 运行”命令即可。或者按快捷键`F5`运行。快捷键可能因为冲突而无效,可以点击命令右边的设置图标重新绑定快捷键。 ![快捷键绑定](/assets/image/vsc-debug2-run-shortcut.png) 每次代码运行,插件会扫描电脑上的文件变化,将文件夹下的文件修改同步到设备脚本文件夹下的`.remote`文件夹,然后再运行文件。此过程一般很快,除非有大量(几千上万个)文件。我们建议仅打开需要编辑的项目文件夹,而不是打开比如 D 盘、包含所有项目的总文件夹。 ## 打开已有项目或文件 这 VSCode 上方菜单中选择文件 -> 打开文件夹,选择之前已有的项目或文件即可。此时我们可以编辑和运行之前已有的项目或文件。 * **请勿打开一个很大的文件夹**,比如打开整个 D 盘,或者用几千上万个脚本文件的文件夹。因为每次运行会将文件夹同步到手机上,我们只需要打开当前需要的项目或文件夹即可。过大的文件夹会导致文件同步失败或同步缓慢。 * 若运行的代码依赖其他模块文件、图片资源等,请不要仅仅打开这个文件,而是打开它所在的文件夹。当你用 VSCode 仅打开一个单文件时,运行时只有这个文件会被同步到手机上,因此打开这个文件所在的文件夹会更加合适。推荐使用项目来管理多个文件、模块和图片资源。 * 打开一个项目时,运行默认是允许当前打开的文件,若要运行项目的主文件,请选择”运行项目“命令。 * 即使打开一个项目或文件夹,”运行单文件“命令则只会同步该文件到手机,该文件夹或项目的其他文件不会被同步到手机上。 ## 查看日志、停止运行 点击 VSCode 上面菜单,点击”查看”->”输出”打开输出面板,切换到设备的日志输出,即可查看设备的运行日志。 ![运行日志](/assets/image/vsc-debug2-console.png) 要停止上一次启动的脚本,在命令面板中选择”Auto.js Pro: 停止当前脚本”即可;也可选择”Auto.js Pro: 停止所有脚本”。 ## 单步调试 单步调试是诊断 Bug、解决 Bug 的利器,它允许你一步一步地运行代码,查看代码运行的中间状态,检查变量、函数执行是否符合预期,从而发现问题,解决问题。 切换到需要单步调试的文件,在 VSCode 菜单中点击”运行” -> “启动调试”,选择”Auto.js Pro Debug”调试器,即可启动单步调试。启动后代码将在第一行停下,等待开发者操作。 ![首行停下](/assets/image/vsc-debug2-stop-on-entry.png) 右上角的调试工具栏上的几个图标分别代表: * ▶️:继续运行 * →:单步运行,即运行当前函数的下一行代码 * ↓:单步进入,即进入到当前行的子函数里面执行 * ↑:单步跳出,执行完当前函数的剩余部分,返回到函数调用处 * 🔁:重新运行,停止当前脚本,重新重头开始运行和调试 我们还可以点击文件的某一行的左侧,给这一行下断点,当代码执行到这里时,将会在断点处停下。 在代码暂停时,左侧的变量区可以看到当前全局变量和局部变量的值;调用堆栈区可以看到当前的调用堆栈。将鼠标移动到某个变量上可以查看该变量的值 在代码暂停时,我们还可以在 VSCode 菜单”查看” -> “调试控制台”中动态执行一些表达式并获得结果: ![调试控制台](/assets/image/vsc-debug2-debug-console.png) > 需要注意,单步调试 UI 脚本时,可能提示“Auto.js Pro 无响应”,这是因为 UI 线程被调试所阻塞,此时点击继续等待即可。部分设备即使点了“继续等待”也会将程序杀死,在原生 Android 开发中也会遇到相同的问题。 ## 切换设备 VSCode 支持同时连接多个设备,要切换不同设备运行代码、浏览设备文件、调试等,请点击 VSCode 左下角的设备名称: ![当前设备](/assets/image/vsc-debug2-current-device.png) 选择设备后,再次运行则在选中的设备上运行。 ![选择设备](/assets/image/vsc-debug2-select-device.png) ## 浏览、管理设备文件 在 VSCode 中可直接浏览、管理、编辑、运行手机上的文件。打开 VSCode 的命令面板,输入”浏览设备文件”,选择”Auto.js Pro: 浏览设备文件”。 此时弹出选择文件夹的窗口,选择某个文件夹或者整个脚本文件夹后,点击“√”图标。 ![选择文件夹](/assets/image/vsc-debug2-select-dir.png) 此时插件会触发重新加载(VSCode 的设计),在提示”新设备已连接“后,我们在左侧的文件管理中找到手机的文件夹,展开。 ![设备文件列表](/assets/image/vsc-debug2-device-workspace.png) 在这里我们可以新建文件、新建项目、删除文件、重命名等管理设备上的文件,也可以将电脑上的文件、文件夹通过拖拽方式传输到手机,或者右键点击手机的文件下载到电脑。 ![文件管理操作](/assets/image/vsc-debug2-file-operations.png) 另外,可以直接编辑、运行、单步调试设备上的文件,省去文件同步、保存的时间。但由于 ts 语言服务对虚拟文件系统的限制,直接编辑设备上的文件将没有自动补全 Auto.js 函数的功能。 ## 远程终端 在 VSCode 菜单选择”查看”->”终端”打开终端界面,点击右上角”+”旁边的下拉箭头,选择”Auto.js Pro 远程终端”。 此终端运行于手机上,环境和 Auto.js Pro 中的环境一致,可执行 node/npm 命令。 ![远程终端](/assets/image/vsc-debug2-intro-terminal.jpg) ## 文件同步设置 默认情况下,在 VSCode 打开文件夹后,运行、调试代码会将该文件夹所有文件同步到设备脚本文件夹下的`.remote`临时文件夹中,保存项目、保存文件则会同步到设备的脚本文件夹下。 同步是增量、高效的,但某些文件夹可能在运行、保存时并不需要同步到手机,比如.git、build 目录,我们可以找`.autojs.sync.ignore`文件中配置同步忽略的文件。 文件的编写规则和.gitnore 是一致的,比如`/.git`规则可以忽略.git 文件下的所有文件。 ## 常见问题 ### 无法连接设备 可能是以下原因: 1. 手机和电脑不在同一个局域网中。查看手机的 WLAN 连接网络详情可以看到手机的 IP 地址,比如 192.168.0.10,在电脑上尝试 ping 该 IP 看能否 ping 通。 2. Windows 电脑连接后设置了公用网络。在 WLAN 设置中切换该网络为专用网络。 3. 端口被防火墙屏蔽,暂时关闭防火墙可测试是否是防火墙导致。或者在防火墙规则中添加 8129, 9317, 21029 端口的放通规则。 4. 路由器、校园网、公司网络等网络设置不允许设备互连。 ### VSCode 代码编辑没有 Auto.js 函数自动补全 插件的补全依赖于.d.ts 文件,默认在新建项目会附带。因此要获得自动补全,使用插件新建一个项目即可。 如果你是自行创建的文件夹或者单文件,可以新建项目后将 node\_modules 文件夹、tsconfig.json 文件复制到你的文件夹中。 如果你会使用 npm 和配置 tsconfig.json,则使用 npm 安装对应的 types 文件: * 第一代 API(rhino): [@autojs/types-pro8](https://www.npmjs.com/package/@autojs/types-pro8) * 第二代 API(nodejs): [@autojs/types-pro9](https://www.npmjs.com/package/@autojs/types-pro9) 并在 tsconfig.json 的 typeRoots 字段增加`"./node_modules/@autojs"`的配置。 ### 调试、运行时提示”open ‘extension-output-hyb1996.auto-js-pro-ext-#1-“错误 调试、运行时若出现以下提示,是因为当前焦点在日志区域或者其他区域导致。重新点击一下要运行的文件的内容,使焦点重新回到文件编辑器,再重新运行、启动调试即可。 ![调试错误](/assets/image/vsc-debug2-err-ext-output.png) ### 锁屏、后台时连接断开或运行无反应 在锁屏、后台情况下,Auto.js Pro 的网络连接可能被暂停或断开,导致 VSCode 无法和手机正常通信。请开启 Auto.js Pro 的前台服务,锁定 Auto.js Pro 后台,关闭电量优化,加入后台白名单;或者保持 Auto.js Pro 始终在前台,并打开”调试时屏幕常亮“的开关。 ### 锁屏、后台时文件管理失效 在锁屏、后台情况下,Auto.js Pro 可能无法访问文件,导致 VSCode 打开的手机文件夹显示加载中或操作失败。请开启 Auto.js Pro 的前台服务,锁定 Auto.js Pro 后台,关闭电量优化,加入后台白名单;或者保持 Auto.js Pro 始终在前台,并打开”调试时屏幕常亮“的开关。 --- --- url: 'https://www.wuyunai.com/docs/custom/index.html' --- 本文档由 老猫 整理,顺带承接脚本定制。我们在整理编辑过程中修正了原文档中的一些错误,以便为大家呈现更准确和易于理解的内容。 # Autojs.Pro 文档整理 ## 感谢开源社区 我们要特别感谢开源社区,其中部分代码是我们原创的,同时也借鉴了开源代码。在这里,我们向所有为 Android 开发做出贡献的开源社区开发者表示衷心的感谢。 ## 版权声明 本文档内容版权归原作者所有。我们只负责整理编辑和搬运,旨在分享和传播有关 Autojs 的知识。如有侵权,请及时联系我们删除相关内容。 ## 文档更新 我们将不定期更新本文档,以提供更多有关 Autojs 的常用技巧与案例。请大家随时关注文档的更新,以获取最新的内容。 ## 常用 Javascript 函数接口示例 本文档还会陆续增加一些常用的 JavaScript 函数接口示例,以便帮助开发者更好地理解和使用这些功能。 感谢大家的支持与关注!让我们一起学习和探索 Autojs 的奥秘吧! --- --- url: 'https://www.wuyunai.com/docs/custom/broadcast-receiver-tutorial.html' --- # Autojs 实现监听电话状态 ### 简介 在 Android 平台上,广播接收器是一种组件,用于监听和响应系统或应用程序发出的广播消息。通过注册广播接收器,您可以在特定事件发生时执行相应的操作。 本教程将引导您使用 Auto.js Pro 和 JavaScript 编程语言编写一个简单的 Android 广播接收器,用于监听电话状态和拨打电话事件。 ### 前提条件 在开始之前,请确保您已满足以下前提条件: 1. 安装 Auto.js Pro:您可以从 [Auto.js Pro 官方网站](https://www.wuyunai.com/) 下载并安装 Auto.js Pro 应用程序。 2. 具备 JavaScript 基础知识:本教程假设您具备基本的 JavaScript 编程知识。 ### 步骤 1:导入必要的类 首先,在代码中导入所需的类。在这个例子中,我们需要使用 `TelephonyManager`、`Intent` 和 `IntentFilter` 类。 ```javascript importClass(android.telephony.TelephonyManager); importClass(android.content.Intent); importClass(android.content.IntentFilter); ``` ### 步骤 2:创建 IntentFilter 并添加要监听的动作 接下来,创建一个 `IntentFilter` 对象,并添加要监听的动作。在这个例子中,我们将监听电话状态变化的动作 `android.intent.action.PHONE_STATE`。 ```javascript let filter = new IntentFilter(); filter.addAction("android.intent.action.PHONE_STATE"); ``` ### 步骤 3:创建广播接收器 然后,我们将创建一个广播接收器。通过继承 `android.content.BroadcastReceiver` 类,并重写 `onReceive` 方法来实现广播接收器。 在 `onReceive` 方法中,我们可以根据接收到的 `Intent` 对象进行相应的逻辑处理。在这个例子中,我们打印出广播的动作和电话状态。 ```javascript var myBroadcastReceiver = new JavaAdapter(android.content.BroadcastReceiver, { onReceive: function(context, intent) { let action = intent.getAction(); if ('android.intent.action.PHONE_STATE' == action) { let state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); switch (state) { case 'IDLE': console.log('挂断电话'); break; case 'RINGING': console.log('电话正在响铃'); break; case 'OFFHOOK': console.log('电话处于通话状态'); break; default: break; } } } }); ``` ### 步骤 4:注册广播接收器 接下来,我们将广播接收器注册到系统中,以便接收相应的广播消息。 ```javascript context.registerReceiver(myBroadcastReceiver, filter); // 注册广播接收器 ``` ### 步骤 5:执行代码 最后,我们可以在一个循环中保持代码的执行,以便持续监听广播消息。在这个例子中,我们使用 `setInterval` 函数来实现每秒执行一次的功能。 ```javascript setInterval(() => { // 保持代码执行 }, 1000); ``` ### 步骤 6:取消注册广播接收器 在代码执行结束时,我们需要取消注册广播接收器,以释放资源,否则会导致内存泄漏。 ```javascript $events.on('exit', () => { context.unregisterReceiver(myBroadcastReceiver); }); ``` ### 完整代码 ```javascript /** * 注册电话状态广播接收器 */ function registerPhoneStateReceiver() { importClass(android.telephony.TelephonyManager); importClass(android.content.Intent); importClass(android.content.IntentFilter); let filter = new IntentFilter(); filter.addAction("android.intent.action.PHONE_STATE"); // 监听电话状态变化 let myBroadcastReceiver = new JavaAdapter(android.content.BroadcastReceiver, { /** * 接收广播回调函数 * @param {android.content.Context} context 广播上下文对象 * @param {Object} intent 广播意图对象 */ onReceive: function(context, intent) { let action = intent.getAction(); if ('android.intent.action.PHONE_STATE' == action) { // 监听来电状态 let state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); switch(state) { case 'IDLE': // 挂断电话 console.log('挂断电话'); break; case 'RINGING': // 电话正在响铃 console.log('电话正在响铃'); break; case 'OFFHOOK': // 电话处于通话状态 console.log('电话处于通话状态'); break; default: break; } } } }); context.registerReceiver(myBroadcastReceiver, filter); // 注册广播接收器 $events.on('exit', () => { // 取消注册广播接收器,否则会导致内存泄露 context.unregisterReceiver(myBroadcastReceiver); }); } registerPhoneStateReceiver(); setInterval(() => { // 用于脚本保活,ui界面不需要 }, 1000); ``` ### 结论 通过以上步骤,您已经成功编写了一个简单的 Android 广播接收器,用于监听电话状态。您可以根据实际需求进一步扩展和修改代码功能。 希望本教程对您有所帮助!如果您有任何问题,请随时提问。 **注意:请确保遵守当地法律法规,并尊重他人的隐私和通信权利。** --- --- url: 'https://www.wuyunai.com/docs/custom/calling_functions.html' --- # Auto.js中实现电话拨打功能的两种方式 ## Auto.js中实现电话拨打功能的两种方式 在Auto.js中,我们可以使用不同的方式来实现电话拨打功能。本文将介绍其中的两种方式,并提供示例代码。 ### 跳转拨号界面 首先,我们可以通过跳转拨号界面的方式实现电话拨打功能。这种方式不需要额外的权限,用户需要手动点击拨打按钮来完成拨号操作。 以下是使用Auto.js实现跳转拨号界面的示例代码: ```javascript var Intent = { action: "DIAL", data: "tel:10000" }; app.startActivity(Intent); ``` 在这段代码中,我们创建了一个名为`Intent`的对象,并设置了两个属性:`action`和`data`。其中,`action`的值为`DIAL`,表示拨号动作;`data`的值为`tel:10000`,表示要拨打的电话号码是10000。通过调用`app.startActivity(Intent)`方法,我们可以在Auto.js应用中启动系统的电话拨号界面,并自动填充电话号码为10000。 ### 直接拨号 另一种实现电话拨打功能的方式是直接拨号。但是,这种方式需要在应用中添加相应的权限声明。 以下是使用Auto.js实现直接拨号的示例代码: ```javascript var Intent = { action: "android.intent.action.CALL", data: "tel:10000" }; app.startActivity(Intent); ``` 与跳转拨号界面不同,这段代码中的`Intent`对象设置了两个属性:`action`和`data`。其中,`action`的值为`android.intent.action.CALL`,表示进行呼叫操作;`data`的值为`tel:10000`,表示要拨打的电话号码是10000。 在中使用直接拨号方式时: * 安卓 * 需要在`AndroidManifest.xml`文件中添加相应的权限声明: ```xml ``` * Autojs * 需要在`project.json`文件中添加相应的权限声明: ```json { "permissionConfig": { "manifestPermissions": [ "android.permission.CALL_PHONE" ] } } ``` ``` - 建议配置启动时自动申请权限: ``` ```json { "permissionConfig": { "manifestPermissions": [ "android.permission.CALL_PHONE" ], "requestListOnStartup": [ "android.permission.CALL_PHONE" ] } } ``` 这样才能确保Auto.js应用在运行时具有拨号权限。 ### 总结 本文介绍了在Auto.js中实现电话拨打功能的两种方式。跳转拨号界面方式无需额外权限,但用户需要手动点击拨打按钮来完成拨号操作。直接拨号方式可以直接进行拨号,但需要在应用中添加相应的权限声明。根据实际需求,选择适合的方式来实现电话拨打功能。 --- --- url: 'https://www.wuyunai.com/docs/custom/control-styling.html' --- # 使用Auto.js创建带边框和圆角的渐变背景对象 ## 介绍 本文将介绍如何使用Auto.js,在Android平台上使用JavaScript代码创建一个带有边框和圆角的渐变背景对象。我们将使用Auto.js提供的类库来实现这个功能。 ## 导入类 首先,我们需要导入一些Android图形类,包括Color、GradientDrawable、Orientation、ShapeDrawable和RoundRectShape。这些类将用于创建和配置我们的渐变背景对象。 ```javascript importClass(android.graphics.Color); importClass(android.graphics.drawable.GradientDrawable); importClass(android.graphics.drawable.GradientDrawable.Orientation); importClass(android.graphics.drawable.ShapeDrawable) importClass(android.graphics.drawable.shapes.RoundRectShape) importClass(android.graphics.drawable.LayerDrawable) ``` ## 创建带边框和圆角的GradientDrawable对象 下面是我们创建带边框和圆角的GradientDrawable对象的函数: ```javascript /** * 创建一个带有边框和圆角的 GradientDrawable 对象。 * * @param {Object} [args] - 函数参数对象。 * @param {string} [args.rgb='#eeeeee'] - 边框颜色,默认为灰色(#eeeeee)。 * @param {number} [args.cornerRadius=10] - 圆角半径,默认为 10 像素。 * @param {number} [args.alpha=255] - 填充颜色透明度,取值范围是 0 到 255,默认为不透明(255)。 * @return {GradientDrawable} 返回一个创建好的 GradientDrawable 对象。 */ function createBorderedDrawable(args) { args = args || {} args.rgb = args.rgb || '#eeeeee'; args.cornerRadius = args.cornerRadius || 10; args.alpha = args.alpha || 255; // 注意:alpha 的取值范围是 0 到 255 let gradientDrawable = new GradientDrawable(); // 创建一个 GradientDrawable 对象 gradientDrawable.setStroke(2, colors.parseColor(args.rgb)); // 设置对象的边框宽度和颜色 gradientDrawable.setOrientation(GradientDrawable$Orientation.LEFT_RIGHT); // 设置对象的方向为左右 gradientDrawable.setCornerRadius(args.cornerRadius); // 设置对象的圆角半径 gradientDrawable.setAlpha(args.alpha); // 设置对象的填充颜色透明度 return gradientDrawable; // 返回创建的 GradientDrawable 对象 } ``` 该函数接受一个可选的参数对象`args`,用于自定义渐变背景的属性。默认情况下,边框颜色为灰色,圆角半径为10像素,填充颜色透明度为不透明。函数首先根据参数对象来设置相应属性的值,然后创建一个GradientDrawable对象,并使用`setStroke`方法设置边框的宽度和颜色,使用`setOrientation`方法设置渐变方向为从左到右,使用`setCornerRadius`方法设置圆角半径,使用`setAlpha`方法设置填充颜色的透明度。最后,返回创建好的GradientDrawable对象。 ## 使用示例 你可以根据自己的需求调用`createBorderedDrawable`函数并传入自定义的参数来创建渐变背景对象。以下是一个使用示例: ```javascript "ui"; importClass(android.graphics.Color); importClass(android.graphics.drawable.GradientDrawable); importClass(android.graphics.drawable.GradientDrawable.Orientation); $ui.layout( ); // ================== run_type = "超级福袋" function pay_bnt_color1() { run_type = "超级福袋" ui.run(function () { }) } function pay_bnt_color2() { run_type = "普通福袋" ui.run(function () { }) } ui.tv_text.setSelected(true);//设置文字滚动状态 读取界面配置(true) //右上角设置 activity.setSupportActionBar(ui.toolbar); //创建选项菜单(右上角) ui.emitter.on("create_options_menu", menu => { menu.add("日志"); }); //监听选项菜单点击 ui.emitter.on("options_item_selected", (e, item) => { switch (item.getTitle()) { case "日志": app.startActivity("console"); break; } e.consumed = true; }); ui.autoService.on("check", function (checked) { // 用户勾选无障碍服务的选项时,跳转到页面让用户去开启 if (checked && auto.service == null) { app.startActivity({ action: "android.settings.ACCESSIBILITY_SETTINGS" }); } if (!checked && auto.service != null) { auto.service.disableSelf(); } }); ui.emitter.on("resume", function () { // 此时根据无障碍服务的开启情况,同步开关的状态 ui.autoService.checked = auto.service != null; }); ui.overlayService.on("check", function (checked) { // 用户勾选无障碍服务的选项时,跳转到页面让用户去开启 if (checked) { try { int = app.startActivity({ packageName: "com.android.settings", className: "com.android.settings.Settings$AppDrawOverlaySettingsActivity", data: "package:" + context.getPackageName().toString() }); } catch (err) { app.openAppSetting(getPackageName("dy_cf")); } toast("请打开悬浮窗开关"); } if (!checked && auto.service != null) { // auto.service.disableSelf(); toast("已关闭悬浮窗权限"); } }); ui.start.on("click", () => { //按钮单击事件 哪个按钮 start 需要修改这个ID 保存界面配置() //先读取配置 读取界面配置(false) if (auto.service == null) { toastLog("请先开启无障碍服务!"); return }; if (悬浮创建 == 0) { events.removeAllKeyDownListeners("volume_up") // 屏蔽音量键调节声音 events.setKeyInterceptionEnabled("volume_up", true); events.observeKey(); //监听音量键按下 events.onKeyDown("volume_up", () => { exit() }); threads.start(function () { 日志显示(0, 屏幕高度 * 0.7, true) }) 悬浮创建 = 1 } // home() toast('脚本转入后台') // app.launchApp("抖音") }); function Main() { let number = 0 // 记时开始 let timeStart = timeStamp(); dlog("当前版本:" + getVerName(app.getPackageName('抖音')) + ' 执行分钟: ' + 0 + ' / ' + myapp.time + ' 参与次数: ' + 0 + ' / ' + myapp.count); jlog('执行功能:抖音福袋') //判断主页 while (true) { if (!desc("更多面板 按钮").visibleToUser().exists()) { jlog('请在直播间运行') } else { break } sleep(1500) } while (true) { jlog("抖音直播间已打开") sleep(3000) if (!descContains('超级福袋').visibleToUser().exists()) { swipe_downUp(true) sleep(3000) jlog("未发现超级福袋") } else { jlog("超级福袋......") sleep(1000) break } } // ========================= while (true) { if (desc('活动已结束').visibleToUser().exists()) { back(); clog('活动结束,返回'); } sleep(300) if (descContains('参与成功').visibleToUser().exists()) { back(); jlog('参与成功,等待开奖'); } sleep(300) if (descContains('我知道了').visibleToUser().exists()) { back(); clog('我知道了,返回'); } sleep(300) if (desc('一键发表评论').visibleToUser().exists()) { clickCoords('desc', '一键发表评论', true); clog('一键发表评论'); ++number } sleep(300) if (descContains('超级福袋').visibleToUser().exists()) { clickCoords('desc', '超级福袋', true); jlog('点击超级福袋'); } var timeUp = timeStamp(); var record = timeUp - timeStart dlog("当前版本:" + getVerName(app.getPackageName('抖音')) + ' 执行分钟: ' + (record / 60).toFixed(1) + ' / ' + myapp.time + ' 参与次数: ' + number + ' / ' + myapp.count); if (number >= parseInt(myapp.count)) { sleep(300) tlog('停止运行') console.hide() alert("运行完毕"); sleep(300) exit() return } } } function 普通福袋() { let number = 0 // 记时开始 let timeStart = timeStamp(); dlog("当前版本:" + getVerName(app.getPackageName('抖音')) + ' 执行分钟: ' + 0 + ' / ' + myapp.time + ' 参与次数: ' + 0 + ' / ' + myapp.count); jlog('执行功能:抖音普通福袋') //判断主页 while (true) { if (!desc("更多面板 按钮").visibleToUser().exists()) { jlog('请在直播间运行') } else { break } sleep(1500) } while (true) { jlog("抖音直播间已打开") sleep(3000) if (!descStartsWith('福袋').visibleToUser().findOnce()) { swipe_downUp(true) sleep(3000) jlog("未发现普通福袋") } else { jlog("福袋......") sleep(1000) break } } // ========================= while (true) { if (desc('活动已结束').visibleToUser().exists()) { back(); clog('活动结束,返回'); } sleep(300) if (descContains('参与成功').visibleToUser().exists()) { back(); jlog('参与成功,等待开奖'); } sleep(300) if (descContains('我知道了').visibleToUser().exists()) { back(); clog('我知道了,返回'); } sleep(300) if (textContains('已参与').visibleToUser().exists()) { back(); clog('已参与,返回'); } sleep(300) if (textContains('去发表评论').visibleToUser().exists()) { clickCoords('text', '去发表评论', true); sleep(2000) clickCoords("desc", '发送', true) clog('去发表评论'); ++number } sleep(300) if (descStartsWith('福袋').visibleToUser().findOnce()) { var w = descStartsWith('福袋').visibleToUser().findOnce().bounds() click(w.centerX(), w.centerY()) sleep(500) jlog('点击福袋'); } var timeUp = timeStamp(); var record = timeUp - timeStart dlog("当前版本:" + getVerName(app.getPackageName('抖音')) + ' 执行分钟: ' + (record / 60).toFixed(1) + ' / ' + myapp.time + ' 参与次数: ' + number + ' / ' + myapp.count); if (number >= parseInt(myapp.count)) { sleep(300) tlog('停止运行') console.hide() alert("运行完毕"); sleep(300) exit() return } } } //下滑 function swipe_downUp(Vertical) { let x = device.width / 2 let y1 = parseInt(device.height * 0.8) let y2 = parseInt(device.height * 0.1) if (Vertical) { gesture(random(500, 800), [x + random(-30, 30), y1], [x + random(-30, 30), y1 - 200], [x + random(-20, 20), y1 - 400], [x + random(-20, 20), y2]) } else { gesture(random(500, 800), [x + random(-30, 30), y2 + 100], [x + random(-30, 30), y2 + 300], [x + random(-20, 20), y2 + 500], [x + random(-20, 20), y1]) } } function timeStamp() { return Date.parse(new Date()) / 1000; } //随机秒数 function getRandom(min, max) { // console.log("停留:" + random(parseInt(min), parseInt(max)) + " 毫秒"); return random(parseInt(min), parseInt(max)) } function clickCoords(type, value, isContain) { if (isContain) { switch (type) { case "id": try { var w = idContains(value).visibleToUser().findOnce().bounds() click(w.centerX(), w.centerY()) sleep(500) return true } catch (e) { console.log(e); return false } case "text": try { var w = textContains(value).visibleToUser().findOnce().bounds() click(w.centerX(), w.centerY()) sleep(500) return true } catch (e) { console.log(e); return false } case "desc": try { var w = descContains(value).visibleToUser().findOnce().bounds() click(w.centerX(), w.centerY()) sleep(500) return true } catch (e) { console.log(e); return false } default: return false } } else { switch (type) { case "id": try { var w = id(value).visibleToUser().findOnce().bounds() click(w.centerX(), w.centerY()) sleep(500) return true } catch (e) { console.log(e); return false } case "text": try { var w = text(value).visibleToUser().findOnce().bounds() click(w.centerX(), w.centerY()) sleep(500) return true } catch (e) { console.log(e); return false } case "desc": try { var w = desc(value).visibleToUser().findOnce().bounds() click(w.centerX(), w.centerY()) sleep(500) return true } catch (e) { console.log(e); return false } default: return false } } } //软件版本 function getVerName(package_name) { let pkgs = context.getPackageManager().getInstalledPackages(0).toArray(); for (let i in pkgs) { if (pkgs[i].packageName.toString() === package_name) return pkgs[i].versionName; } } //=============== function 日志显示(x, y, is) { if (typeof x != 'number') x = 0; if (typeof y != 'number') y = 10; let floatyLogW = floaty.rawWindow( ); ui.callback.click(() => { dialogs.confirm("要弹出输入框吗?", "", function(b) { if (b) { dialogs.rawInput("输入", "", function(str) { alert("您输入的是:" + str); }); } else { ui.finish(); } }); }); ui.promise.click(() => { dialogs.confirm("要弹出输入框吗") .then(function(b) { if (b) { return dialogs.rawInput("输入"); } else { ui.finish(); } }).then(function(str) { alert("您输入的是:" + str); }); }); ui.calc.click(() => { let num1, num2, op; dialogs.rawInput("请输入第一个数字") .then(n => { num1 = parseInt(n); return dialogs.singleChoice("请选择运算", ["加", "减", "乘", "除", "幂"]); }) .then(o => { op = o; return dialogs.rawInput("请输入第二个数字"); }) .then(n => { num2 = parseInt(n); var result; switch (op) { case 0: result = num1 + num2; break; case 1: result = num1 - num2; break; case 2: result = num1 * num2; break; case 3: result = num1 / num2; break; case 4: result = Math.pow(num1, num2); break; } alert("运算结果", result); }); }); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/engines/index.html' description: Auto.js Pro 脚本引擎 相关示例代码集合 --- # 脚本引擎 示例 ## 示例列表 * [停止所有正在运行的脚本](./停止所有正在运行的脚本.html) * [被运行的脚本文件](./被运行的脚本文件.html) * [运行录制文件](./运行录制文件.html) * [运行新的脚本任务](./运行新的脚本任务.html) * [运行脚本文件](./运行脚本文件.html) ## 返回 * [示例代码首页](../) --- --- url: 'https://www.wuyunai.com/docs/examples/engines/停止所有正在运行的脚本.html' description: Auto.js Pro 脚本引擎 示例代码 - 停止所有正在运行的脚本 --- # 停止所有正在运行的脚本 ```javascript engines.stopAllAndToast(); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/engines/被运行的脚本文件.html' description: Auto.js Pro 脚本引擎 示例代码 - 被运行的脚本文件 --- # 被运行的脚本文件 ```javascript toastLog("Hello, engines!"); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/engines/运行录制文件.html' description: Auto.js Pro 脚本引擎 示例代码 - 运行录制文件 --- # 运行录制文件 ```javascript var path = files.path("./test.auto"); engines.execAutoFile(path); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/engines/运行新的脚本任务.html' description: Auto.js Pro 脚本引擎 示例代码 - 运行新的脚本任务 --- # 运行新的脚本任务 ```javascript var script = "toast('Hello, Auto.js');" + "sleep(3000);" + "toast('略略略');"; var execution = engines.execScript("Hello", script); sleep(1000); execution.getEngine().forceStop(); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/engines/运行脚本文件.html' description: Auto.js Pro 脚本引擎 示例代码 - 运行脚本文件 --- # 运行脚本文件 ```javascript var path = $files.path("./被运行的脚本文件.js"); engines.execScriptFile(path); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/events/index.html' description: Auto.js Pro 事件与按键、触摸监听 相关示例代码集合 --- # 事件与按键、触摸监听 示例 ## 示例列表 * [Toast监听](./Toast监听.html) * [按键监听](./按键监听.html) * [触摸监听](./触摸监听.html) * [通知监听](./通知监听.html) * [长按返回退出当前程序](./长按返回退出当前程序.html) * [音量键控制程序](./音量键控制程序.html) ## 返回 * [示例代码首页](../) --- --- url: 'https://www.wuyunai.com/docs/examples/events/按键监听.html' description: Auto.js Pro 事件与按键、触摸监听 示例代码 - 按键监听 --- # 按键监听 ```javascript "auto"; events.observeKey(); var keyNames = { "KEYCODE_VOLUME_UP": "音量上键", "KEYCODE_VOLUME_DOWN": "音量下键", "KEYCODE_HOME": "Home键", "KEYCODE_BACK": "返回键", "KEYCODE_MENU": "菜单键", "KEYCODE_POWER": "电源键", }; events.on("key", function(code, event) { var keyName = getKeyName(code, event); if (event.getAction() == event.ACTION_DOWN) { toast(keyName + "被按下"); } else if (event.getAction() == event.ACTION_UP) { toast(keyName + "弹起"); } }); function getKeyName(code, event) { var keyCodeStr = event.keyCodeToString(code); var keyName = keyNames[keyCodeStr]; if (!keyName) { return keyCodeStr; } return keyName; } ``` --- --- url: 'https://www.wuyunai.com/docs/examples/events/触摸监听.html' description: Auto.js Pro 事件与按键、触摸监听 示例代码 - 触摸监听 --- # 触摸监听 ```javascript events.observeTouch(); events.setTouchEventTimeout(30); toast("请在日志中查看触摸的点的坐标"); events.on("touch", function(point) { log(point); }); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/events/通知监听.html' description: Auto.js Pro 事件与按键、触摸监听 示例代码 - 通知监听 --- # 通知监听 ```javascript auto(); events.observeNotification(); events.onNotification(function(notification) { printNotification(notification); }); toast("监听中,请在日志中查看记录的通知及其内容"); function printNotification(notification) { log("应用包名: " + notification.getPackageName()); log("通知文本: " + notification.getText()); log("通知优先级: " + notification.priority); log("通知目录: " + notification.category); log("通知时间: " + new Date(notification.when)); log("通知数: " + notification.number); log("通知摘要: " + notification.tickerText); } ``` --- --- url: 'https://www.wuyunai.com/docs/examples/events/长按返回退出当前程序.html' description: Auto.js Pro 事件与按键、触摸监听 示例代码 - 长按返回退出当前程序 --- # 长按返回退出当前程序 ```javascript "auto"; var 长按间隔 = 1500; var curPackage = null; var timeoutId = null; events.observeKey(); events.onKeyDown("back", function(event) { curPackage = currentPackage(); timeoutId = setTimeout(function() { backBackBackBack(); }, 长按间隔); }); events.onKeyUp("back", function(event) { clearTimeout(timeoutId); }); function backBackBackBack() { while (curPackage == currentPackage()) { back(); sleep(200); } } ``` --- --- url: 'https://www.wuyunai.com/docs/examples/events/音量键控制程序.html' description: Auto.js Pro 事件与按键、触摸监听 示例代码 - 音量键控制程序 --- # 音量键控制程序 ```javascript "auto"; events.observeKey(); var interval = 5000; var task = task1; events.onKeyDown("volume_up", function(event) { if (task == task1) { task = task2; } else { task = task1; } toast("任务已切换"); }); events.onKeyDown("volume_down", function(event) { toast("程序结束"); exit(); }); task(); function task1() { toast("任务1运行中,音量下键结束,音量上键切换任务"); setTimeout(task, interval); } function task2() { toast("任务2运行中,音量下键结束,音量上键切换任务"); setTimeout(task, interval); } ``` --- --- url: 'https://www.wuyunai.com/docs/examples/events/Toast监听.html' description: Auto.js Pro 事件与按键、触摸监听 示例代码 - Toast监听 --- # Toast监听 ```javascript auto(); events.observeToast(); events.onToast(function(toast) { var pkg = toast.getPackageName(); log("Toast内容: " + toast.getText() + " 来自: " + getAppName(pkg) + " 包名: " + pkg); }); toast("监听中,请在日志中查看记录的Toast及其内容"); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/files/index.html' description: Auto.js Pro 文件读写 相关示例代码集合 --- # 文件读写 示例 ## 示例列表 * [写入文本文件](./写入文本文件.html) * [删除所有空文件夹](./删除所有空文件夹.html) * [文件编码转换](./文件编码转换.html) * [文件编码转换(高级)](./文件编码转换\(高级\).html) * [文件(夹)监听](./文件(夹)监听.html) * [读写文本文件](./读写文本文件.html) * [读取文本文件](./读取文本文件.html) ## 返回 * [示例代码首页](../) --- --- url: 'https://www.wuyunai.com/docs/examples/files/写入文本文件.html' description: Auto.js Pro 文件读写 示例代码 - 写入文本文件 --- # 写入文本文件 ```javascript //文件路径 var path = "/sdcard/1.txt"; //要写入的文件内容 var text = "Hello, AutoJs"; //以写入模式打开文件 var file = open(path, "w"); //写入文件 file.write(text); //关闭文件 file.close(); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/files/删除所有空文件夹.html' description: Auto.js Pro 文件读写 示例代码 - 删除所有空文件夹 --- # 删除所有空文件夹 ```javascript if (confirm("该操作会删除SD卡目录及其子目录下所有空文件夹,是否继续?")) { toast("请点击右上角打开日志"); deleteAllEmptyDirs(files.getSdcardPath()); toast("全部完成!"); } function deleteAllEmptyDirs(dir) { var list = files.listDir(dir); var len = list.length; if (len == 0) { log("删除目录 " + dir + " " + (files.remove(dir) ? "成功" : "失败")); return; } for (let i = 0; i < len; i++) { var child = files.join(dir, list[i]); if (files.isDir(child)) { deleteAllEmptyDirs(child); } } } ``` --- --- url: 'https://www.wuyunai.com/docs/examples/files/文件编码转换.html' description: Auto.js Pro 文件读写 示例代码 - 文件编码转换 --- # 文件编码转换 ```javascript //以UTF-8编码打开SD卡上的1.txt文件 var f = open("/sdcard/1.txt", "r", "utf-8"); //读取文件所有内容 var text = f.read(); //关闭文件 f.close(); //以gbk编码打开SD卡上的2.txt文件 var out = open("/sdcard/2.txt", "w", "gbk"); //写入内容 out.write(text); //关闭文件 out.close(); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/files/文件编码转换(高级).html' description: Auto.js Pro 文件读写 示例代码 - 文件编码转换(高级) --- # 文件编码转换(高级) ```javascript convert("/sdcard/1.txt", "utf-8", "/sdcard/2.txt", "gbk"); /** * fromFile: 源文件路径 * fromEncoding: 源文件编码 * toFile: 输出文件路径 * toEncoding: 输出文件编码 */ function convert(fromFile, fromEncoding, toFile, toEncoding) { fromFile = open(fromFile, "r", fromEncoding); toFile = open(toFile, "w", toEncoding); while (true) { var line = fromFile.readline(); if (!line) break; toFile.writeline(line); } } ``` --- --- url: 'https://www.wuyunai.com/docs/examples/files/文件(夹)监听.html' description: Auto.js Pro 文件读写 示例代码 - 文件(夹)监听 --- # 文件(夹)监听 ```javascript // 监听脚本文件夹(无法监听二级目录),可以监听该文件夹的所有事件, // 包括修改、创建等,无论这些行为是否由Auto.js本身作出 let watcher = $files.observe("/sdcard/脚本"); watcher.on("any", (event, path) => { log("事件: %s 相对路径: %s", event, path); }); // 创建一个文件,看看是否能监听到 $files.create("/sdcard/脚本/_test_for_file_observer.txt"); sleep(1000); // 一秒后删除文件 $files.remove("/sdcard/脚本/_test_for_file_observer.txt"); // 停止监听 watcher.stopWatching(); // 停止监听后 ``` --- --- url: 'https://www.wuyunai.com/docs/examples/files/读写文本文件.html' description: Auto.js Pro 文件读写 示例代码 - 读写文本文件 --- # 读写文本文件 ```javascript //以写入模式打开SD卡根目录文件1.txt var file = open("/sdcard/1.txt", "w") //写入aaaa file.write("aaaa"); //写入bbbbb后换行 file.writeline("bbbbb"); //写入ccc与ddd两行 file.writelines(["ccc", "ddd"]); //关闭文件 file.close(); //以附加模式打开文件 file = open("/sdcard/1.txt", "a"); //附加一行"啦啦啦啦" file.writeline("啦啦啦啦"); //附加一行"哈哈哈哈" file.writeline("哈哈哈哈"); //附加两行ccc, ddd file.writelines(["ccc", "ddd"]); //输出缓冲区 file.flush(); //关闭文件 file.close(); //以读取模式打开文件 file = open("/sdcard/test.txt", "r") //读取一行并打印 print(file.readline()); //读取剩余所有行并打印 for each(line in file.readlines()) { print(line) } file.close() //显示控制台 console.show() ``` --- --- url: 'https://www.wuyunai.com/docs/examples/files/读取文本文件.html' description: Auto.js Pro 文件读写 示例代码 - 读取文本文件 --- # 读取文本文件 ```javascript //文件路径 var path = "/sdcard/1.txt"; //打开文件 var file = open(path); //读取文件的所有内容 var text = file.read(); //打印到控制台 print(text); //关闭文件 file.close(); console.show(); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/floating-window/index.html' description: Auto.js Pro 悬浮窗 相关示例代码集合 --- # 悬浮窗 示例 ## 示例列表 * [test](./test.html) * [动态悬浮文字](./动态悬浮文字.html) * [悬浮文字](./悬浮文字.html) * [悬浮窗输入框](./悬浮窗输入框.html) * [悬浮窗运行脚本按钮简单版](./悬浮窗运行脚本按钮简单版.html) * [悬浮运行脚本按钮](./悬浮运行脚本按钮.html) * [护眼模式](./护眼模式.html) ## 返回 * [示例代码首页](../) --- --- url: 'https://www.wuyunai.com/docs/examples/floating-window/动态悬浮文字.html' description: Auto.js Pro 悬浮窗 示例代码 - 动态悬浮文字 --- # 动态悬浮文字 ```javascript var window = floaty.window( ); window.exitOnClose(); window.text.click(() => { window.setAdjustEnabled(!window.isAdjustEnabled()); }); setInterval(() => { //对控件的操作需要在UI线程中执行 ui.run(function() { window.text.setText(dynamicText()); }); }, 1000); function dynamicText() { var date = new Date(); var str = util.format("时间: %d:%d:%d\n", date.getHours(), date.getMinutes(), date.getSeconds()); str += util.format("内存使用量: %d%%\n", getMemoryUsage()); str += "当前活动: " + currentActivity() + "\n"; str += "当前包名: " + currentPackage(); return str; } //获取内存使用率 function getMemoryUsage() { var usage = (100 * device.getAvailMem() / device.getTotalMem()); //保留一位小数 return Math.round(usage * 10) / 10; } ``` --- --- url: 'https://www.wuyunai.com/docs/examples/floating-window/悬浮文字.html' description: Auto.js Pro 悬浮窗 示例代码 - 悬浮文字 --- # 悬浮文字 ```javascript var window = floaty.window( ); window.exitOnClose(); window.text.click(() => { window.setAdjustEnabled(!window.isAdjustEnabled()); }); setInterval(() => {}, 1000); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/floating-window/悬浮窗输入框.html' description: Auto.js Pro 悬浮窗 示例代码 - 悬浮窗输入框 --- # 悬浮窗输入框 ```javascript var window = floaty.window( ) ui.webview.loadUrl("http://www.autojs.org"); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/ui-complex/界面模板一.html' description: Auto.js Pro 复杂界面 示例代码 - 界面模板一 --- # 界面模板一 ```javascript "ui"; var color = "#009688"; ui.layout( ); //创建选项菜单(右上角) ui.emitter.on("create_options_menu", menu => { menu.add("设置"); menu.add("关于"); }); //监听选项菜单点击 ui.emitter.on("options_item_selected", (e, item) => { switch (item.getTitle()) { case "设置": toast("还没有设置"); break; case "关于": alert("关于", "Auto.js界面模板 v1.0.0"); break; } e.consumed = true; }); activity.setSupportActionBar(ui.toolbar); //设置滑动页面的标题 ui.viewpager.setTitles(["标签一", "标签二", "标签三"]); //让滑动页面和标签栏联动 ui.tabs.setupWithViewPager(ui.viewpager); //让工具栏左上角可以打开侧拉菜单 ui.toolbar.setupWithDrawer(ui.drawer); ui.menu.setDataSource([{ title: "选项一", icon: "@drawable/ic_android_black_48dp" }, { title: "选项二", icon: "@drawable/ic_settings_black_48dp" }, { title: "选项三", icon: "@drawable/ic_favorite_black_48dp" }, { title: "退出", icon: "@drawable/ic_exit_to_app_black_48dp" } ]); ui.menu.on("item_click", item => { switch (item.title) { case "退出": ui.finish(); break; } }) ``` --- --- url: 'https://www.wuyunai.com/docs/examples/ui-complex/登录界面.html' description: Auto.js Pro 复杂界面 示例代码 - 登录界面 --- # 登录界面 ```javascript "ui"; showLoginUI(); ui.statusBarColor("#000000") //显示登录界面 function showLoginUI() { ui.layout( 用户名 密码 ); ui.cancel.on("click", () => showLoginUI()); } ``` --- --- url: 'https://www.wuyunai.com/docs/examples/ui-complex/高性能列表.html' description: Auto.js Pro 复杂界面 示例代码 - 高性能列表 --- # 高性能列表 ```javascript "ui"; $ui.layout( ); ui.ok.on("click", () => { var i = ui.sp1.getSelectedItemPosition(); var j = ui.sp2.getSelectedItemPosition(); toast("您的选择是选项" + (i + 1) + "和选项" + (j + 4)); }); ui.select3.on("click", () => { ui.sp1.setSelection(2); }); ``` --- --- url: 'https://www.wuyunai.com/docs/examples/ui-components/列表控件.html' description: Auto.js Pro 界面控件 示例代码 - 列表控件 --- # 列表控件 ```javascript "ui"; ui.layout( ); var downloadId = null; ui.download.click(() => { if (downloadId != null) { stopDownload(); } else { startDownload(); } }); function stopDownload() { ui.download.text("开始下载"); clearInterval(downloadId); downloadId = null; } function startDownload() { if (ui.progress.getProgress() == 100) { ui.progress.setProgress(0); } ui.download.text("停止下载"); downloadId = setInterval(() => { var p = ui.progress.getProgress(); p++; if (p > 100) { stopDownload(); return; } ui.progress.setProgress(p); ui.progress_value.setText(p.toString()); }, 200); } ``` --- --- url: 'https://www.wuyunai.com/docs/examples/websocket/index.html' description: Auto.js Pro WebSocket 相关示例代码集合 --- # WebSocket 示例 ## 示例列表 * [WebSocket](./WebSocket.html) ## 返回 * [示例代码首页](../) --- --- url: 'https://www.wuyunai.com/docs/examples/websocket/WebSocket.html' description: Auto.js Pro WebSocket 示例代码 - WebSocket --- # WebSocket ```javascript // 新建一个WebSocket // 指定web socket的事件回调在当前线程(好处是没有多线程问题要处理,坏处是不能阻塞当前线程,包括死循环) // 不加后面的参数则回调在IO线程 let ws = web.newWebSocket("wss://echo.websocket.events", { eventThread: 'this' }); console.show(); // 监听他的各种事件 ws.on("open", (res, ws) => { log("WebSocket已连接"); }).on("failure", (err, res, ws) => { log("WebSocket连接失败"); console.error(err); }).on("closing", (code, reason, ws) => { log("WebSocket关闭中"); }).on("text", (text, ws) => { console.info("收到文本消息: ", text); }).on("binary", (bytes, ws) => { console.info("收到二进制消息:"); console.info("hex: ", bytes.hex()); console.info("base64: ", bytes.base64()); console.info("md5: ", bytes.html5()); console.info("size: ", bytes.size()); console.info("bytes: ", bytes.toByteArray()); }).on("closed", (code, reason, ws) => { log("WebSocket已关闭: code = %d, reason = %s", code, reason); }); // 发送文本消息 log("发送消息: Hello, WebSocket!"); ws.send("Hello, WebSocket!"); setTimeout(() => { // 两秒后发送二进制消息 log("发送二进制消息: 5piO5aSp5L2g6IO96ICDMTAw5YiG44CC"); ws.send(web.ByteString.decodeBase64("5piO5aSp5L2g6IO96ICDMTAw5YiG44CC")); }, 2000); setTimeout(() => { // 8秒后断开WebSocket log("断开WebSocket"); // 1000表示正常关闭 ws.close(1000, null); }, 8000); setTimeout(() => { log("退出程序"); }, 12000) ``` --- --- url: 'https://www.wuyunai.com/docs/examples/zip/index.html' description: Auto.js Pro Zip 压缩与解压 相关示例代码集合 --- # Zip 压缩与解压 示例 ## 示例列表 * [Zip文件压缩与解压](./Zip文件压缩与解压.html) ## 返回 * [示例代码首页](../) --- --- url: 'https://www.wuyunai.com/docs/examples/zip/Zip文件压缩与解压.html' description: Auto.js Pro Zip 压缩与解压 示例代码 - Zip文件压缩与解压 --- # Zip文件压缩与解压 ```javascript // 准备工作,创建文件夹与文件,以便后续用于压缩 // 创建两个文件夹与三个文件 $files.create("/sdcard/脚本/zip_test/"); $files.create("/sdcard/脚本/zip_out/"); $files.write("/sdcard/脚本/zip_test/1.txt", "Hello, World"); $files.write("/sdcard/脚本/zip_test/2.txt", "GoodBye, World"); $files.write("/sdcard/脚本/zip_test/3.txt", "Auto.js Pro"); // 1. 压缩文件夹 // 要压缩的文件夹路径 let dir = '/sdcard/脚本/zip_test/'; // 压缩后的文件路径 let zipFile = '/sdcard/脚本/zip_out/未加密.zip'; $files.remove(zipFile); $zip.zipDir(dir, zipFile); // 2.加密压缩文件夹 let encryptedZipFile = '/sdcard/脚本/zip_out/加密.zip'; $files.remove(encryptedZipFile); $zip.zipDir(dir, encryptedZipFile, { password: 'Auto.js Pro' }); // 3. 压缩单个文件 let zipSingleFie = '/sdcard/脚本/zip_out/单文件.zip' $files.remove(zipSingleFie); $zip.zipFile('/sdcard/脚本/zip_test/1.txt', zipSingleFie); // 4. 压缩多个文件 let zipMultiFile = '/sdcard/脚本/zip_out/多文件.zip'; $files.remove(zipMultiFile); let fileList = ['/sdcard/脚本/zip_test/1.txt', '/sdcard/脚本/zip_test/2.txt'] $zip.zipFiles(fileList, zipMultiFile); // 5. 解压文件 $zip.unzip('/sdcard/脚本/zip_out/未加密.zip', '/sdcard/脚本/zip_out/未加密/'); // 6. 解压加密的zip $zip.unzip('/sdcard/脚本/zip_out/加密.zip', '/sdcard/脚本/zip_out/加密/', { password: 'Auto.js Pro' }); // 7. 从压缩包删除文件 let z = $zip.open('/sdcard/脚本/zip_out/多文件.zip'); z.removeFile('1.txt'); // 8. 为压缩包增加文件 z.addFile('/sdcard/脚本/zip_test/3.txt'); ``` --- --- url: 'https://www.wuyunai.com/docs/guide/index.html' description: >- Auto.js Pro 是 Android 上基于 JavaScript 的面向小应用开发、编程学习的代码开发平台。支持自动化脚本、UI开发、OCR识别、Node.js引擎等功能。 --- # 简介 Auto.js Pro 是 Android 上基于 JavaScript 的面向小应用开发、编程学习的代码开发平台。 ## Auto.js Pro 可以做什么 使用 JavaScript 和 Node.js 实现一个小应用。 ![官方界面Demo](/assets/image/lvji.jpg) ### 开发小应用 Auto.js Pro 本身具有开发完整应用的功能,**可由 JavaScript 项目生成独立分发的 apk 包**。你既可以沿用 Web 知识开发界面,也可以使用 Auto.js 提供的控件开发简单的界面,甚至可以使用原生控件开发优美界面。 Auto.js Pro 可安装 npm 包、加载 dex、jar、动态库等,连接 Android/Node.js 的生态,更让 Auto.js 不仅局限于小应用,使用 Auto.js 开发坦克大战游戏、愤怒的小鸟游戏、http 服务器等的例子也不少见。 ![官方界面Demo](/assets/image/m3.jpg) ### 学习 JavaScript 与验证想法 Auto.js Pro 本身带有多 Tab 编辑器、调试器等专业开发工具,也允许你使用 VSCode 来编写和运行代码。对于有兴趣学习编程的学生来说是一个不错的编程学习工具,代码不再仅局限于理论;对于想从事编程行业的人也是不错的低门槛入门工具,你可以从 Auto.js 走向 Node.js 全栈开发和 Android 开发;对于成熟的开发人员来说也是随手验证想法,摸鱼偷懒时的有趣玩具,也是辅助平时开发的好工具。 ## Auto.js Pro 对比其他版本有什么优势 * Node.js 引擎 Pro 9 新增 Node.js 16.x 引擎,性能是原引擎的 100 倍以上,支持 ES2021 * 插件商店与免安装 插件打包时可被合并到 apk,无需再单独安装;插件商店上线,多个插件任你选择 * 加密增强 Node.js 引擎加密目前未被还原代码,即将推出在线优化进一步增强加密 * 完美 VSCode 调试体验 远程单步调试、自动补全,9.3 版本更全面优化了文件同步效率、管理手机文件等功能 * Npm 生态支持 可安装和使用 npm 包,包括 ws、express、koa 等 200 万个 npm 包 * 代码商店 近千个免费在线代码与项目随意下载,也可与其他用户分享你 * 打包自定义 打包时可自定义签名、CPU 架构、启动图、权限,优化应用大小,混淆组件等 * React/Vue/Web 官方支持使用 web 编写界面,甚至可以使用 React/Vue 等框架,并提供了 web 交互 API * 多 Tab 编辑器工作区 多 Tab 文件编辑、树状文件管理,编辑器基于 LSP 智能补全、语法错误提示等 * UI 可视化设计 由浩然实现的可视化 UI 设计,为小白设计 UI 提供了更方便简单的设计界面 * API 增强 新增 WebSocket、数据库、原生界面、任务、设置等多个模块 * Bug 修复与优化 3 年 200 个版本,近 500 个 Bug 修复,近 200 个优化,200 多个新功能。 * 更多新特性 参见更新日志,更多功能持续更新中 ## Auto.js Pro 学习路线综述 Auto.js Pro 使用[JavaScript](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript)作为脚本语言。目前使用两个 JavaScript 引擎: * [Rhino 1.7.14](https://github.com/mozilla/rhino),支持 ES5 与部分 ES6 特性。免费版和旧版 Pro 使用的引擎,对新的 JS 标准的支持不太完善。基于此引擎的 API 称为第一代 API 或 Rhino API(API v1)。 * [Chrome V8](https://v8.dev/)。在 9.0 版本,Auto.js Pro 新增了基于 V8 引擎的 Node.js 16.x 引擎。Node.js 是一个成熟、流行的 JavaScript 运行时。它在 Web 前后端开发,Electron 跨平台应用开发中都有很多的应用。使用 Node.js 可以让获得更高的性能、更好的代码保护。基于 Node.js 引擎的 API 称为第二代 API 或 Node.js API。 因此,在学习 Auto.js Pro 之前的,需要先学习 JavaScript 基础知识。 另外,**Auto.js Pro 内置的示例非常重要**,建议在初读文档时,可以结合具体模块运行每个功能的示例,对于理解 Auto.js Pro 的功能(以及部分暂时还没有文档的功能)非常关键。 ![官方示例](/assets/image/examples.jpg) ## Auto.js Pro 不能做什么 虽然 Auto.js Pro 无所不能,但不能用于游戏外挂、读写内存、黑灰产等违法违规行为。 * Auto.js Pro 官方永久不会提供读写其他程序内存的功能。 * Auto.js Pro 官方永久不会提供抓取和修改其他应用网络数据的功能。 * Auto.js Pro 不能用于根据有关法律法规、有关部门条例文书、有关法院判决判例等相关规定不能使用的其他情形。 另外,Auto.js Pro 移除了自动化测试、图片处理、消息通知等模块,如果你需要实现的是自动化、工作流工具,则不适合 Auto.js Pro。 ## 如何获得 Auto.js Pro 什么?读到这里你居然还没下载 Auto.js Pro!点击[软件下载](../README.html)立即下载 Android App 吧。 接下来就可以开始[编写第一行代码](quickstart.html)了。 --- --- url: 'https://www.wuyunai.com/docs/guide/build-apk.html' description: >- Auto.js Pro APK 打包完整教程 - 学习如何将脚本打包为独立 Android 应用,包括单文件打包、项目打包、应用配置、权限设置、签名配置等详细步骤。 --- # 打包为应用 当我们完成代码后,可以将代码打包为独立 apk。打包可以分为单文件打包、项目打包,其中单文件打包只能打包一个 js 文件,如果这个 js 文件依赖了其他的资源、代码文件,无法被打包进 apk 中,此时需要用项目打包。有关项目的功能请参考下一节“项目与打包”,这里我们重点介绍打包功能。 在文件列表中,点击需要打包的文件右边的更多图标(三个点),选择“打包单文件”。 ![打包单文件](/assets/image/build-apk-menu.cb174a12.jpg) 进入打包界面。打包界面包含多个配置,你可以自定义权限、包名、应用名称等。调整后这些配置后,点击右下角的完成(√)图标,即可进行打包。 这里我们依次介绍关键的打包配置。 ## 应用配置 * 应用名称:打包后应用安装后显示在桌面的名称 * 包名:应用的唯一标识符,相同包名和签名的安装包可以覆盖安装。包名只能包含字母、数字、下划线、英文点等,并且至少包含一个英文点,比如"com.example"。不合法的包名打包后无法安装,可能提示“安装包解析失败”。 * 版本名称:显示给用户的版本名称,比如"1.10.2"。 * 版本号:一个整数,代表内部版本号。每次更新版本时,需要增加这个整数。 * 图标:应用安装后显示在桌面的图标 * 权限:配置应用权限清单中的权限,默认为 126 个权限。你可以按需要配置权限,不同功能需要不同的权限: * 读写文件:需要 READ\_EXTERNAL\_STORAGE,WRITE\_EXTERNAL\_STORAGE 权限 * 前台服务:需要 FOREGROUND\_SERVICE 权限 * 访问网络:需要 INTERNET 权限 * 任务:需要 WAKE\_LOCK 权限 * 开机启动:需要 RECEIVE\_BOOT\_COMPLETED 权限 * 悬浮窗:需要 SYSTEM\_ALERT\_WINDOW 权限 * adb 权限运行 shell 命令:需要 moe.shizuku.manager.permission.API\_V23 权限 如果用到某些功能,但没有配置相应的权限,运行时可能报错。比如使用到前台服务,但是没有配置 FOREGROUND\_SERVICE 权限,使用到相应功能时会崩溃。 权限还可配置为启动时自动申请,比如自动申请 WRITE\_EXTERNAL\_STORAGE 权限,则打包后应用启动后会弹出权限申请框。一些权限不可申请,只能在权限清单中配置。 ## 特性 * 内置图标包:使用到内置图标,比如 ic\_add\_black\_48dp 等,需要勾选此特性。 * 处理外部文件:使用到任务中的 IntentTask(打开、编辑文件等),需要勾选此特性。勾选后,在文件管理器中打开一些文件,会提示使用打包的应用打开。 * Node.js 引擎:是否使用 Node.js 引擎(第二代 API)。Node.js 引擎的体积较大,单架构达到十几 MB。 * 自动:自动根据打包的文件、项目中是否使用第二代 API 而决定 * 禁用:一律禁用 Node.js 引擎 * 启用:一律启用 Node.js 引擎 * 插件:当使用到 MLKit OCR、FFMpeg 等插件时,需要在这里勾选使用到的插件,插件才能被打包到 apk 中。内置 OCR 模块则无需在这里勾选插件。 ## 构建配置 * 加密:选择加密等级。参考[加密与级别说明](https://blog.autojs.org/2022/08/24/encryption/)。 * CPU 架构:默认为当前设备的架构。CPU 架构将影响软件的体积、兼容性、占用内存、允许速度,arm64-v8a 架构速度更快、占用内存更高、且仅适用支持 64 位的机器,通常来说模拟器不支持 arm64-v8a。如果要让软件兼容性更好,可以选择 armeabi-v7a 或者双架构。 * 混淆组件名称:是否将内置的组件(比如广播、Activity、服务)等名称随机化。勾选此选项后,打包时间将变得很长,并且务必打包时保持在前台,以保持较高的调度优先级。 ## 运行配置 * 隐藏日志:打包后应用是否显示日志界面,当脚本有 UI 界面时此选项不生效。一般来说,建议脚本不要没有任何界面,否则一些依赖界面的功能会出现错误,或者是容易被系统杀死。 * 显示启动界面:是否显示启动界面,默认是软件图标的界面。即使设置为关闭,在首次启动时由于需要初始化,仍然会显示一次。 * 启动界面文本:默认为 Powered by Auto.js Pro,可自定义文本内容。 * 启动界面图标:默认为软件图标。此图标不能铺满屏幕,如果你需要自定义,请使用自定义启动图功能。(参见应用内示例 -> 项目与打包 -> 自定义启动图) ## 签名 只有包名相同且签名相同的应用才能覆盖安装和升级。如果你需要持续更新某个应用,建议你使用自定义签名。因为默认签名是每次安装 Auto.js Pro 时随机生成的,一旦你卸载 Auto.js Pro 或者清除数据,默认签名将会丢失并且不能找回。签名丢失后,你更新应用重新打包后需要让用户卸载旧版本才能安装新版本。 要创建自定义签名,在签名管理中创建签名,输入密码、别名和别名密码即可创建。一个签名可以用于多个软件,每个软件用不同的别名和别名密码,但是 Auto.js Pro 自带的签名管理只支持创建一个别名。 签名文件、密码、别名、别名密码需要保存好,一旦丢失或忘记,没有任何途径可以找回。 --- --- url: 'https://www.wuyunai.com/docs/guide/llms-support.html' description: Auto.js Pro 文档站点支持 LLM(大型语言模型)协议,提供对 AI 友好的内容格式,方便 LLM 读取和理解文档内容。 --- # LLM 协议支持 Auto.js Pro 官方文档站点已支持 LLM(大型语言模型)协议,为 AI 助手和 LLM 提供友好的内容格式,方便它们更好地理解和处理文档信息。 ## 什么是 LLM 协议? LLM 协议是一种为大型语言模型(如 ChatGPT、Claude、Gemini 等)提供结构化文档内容的标准化方式。通过提供 `llms.txt` 文件,网站可以让 AI 更高效地访问和理解文档内容。 ## 为什么需要 LLM 协议? 大型语言模型越来越依赖网站信息,但面临一个关键限制:**上下文窗口太小**,无法完整处理大多数网站。将包含导航、广告和 JavaScript 的复杂 HTML 页面转换为适合 LLM 的纯文本既困难又不精确。 虽然网站同时为人类读者和 LLM 服务,但后者受益于在一个可访问的位置收集的更简洁、专家级的信息。这对于开发环境等使用案例尤其重要,因为 LLM 需要快速访问编程文档和 API。 ## 如何使用 ### 对于 AI 助手和 LLM AI 助手可以通过以下方式访问文档: 1. **访问 llms.txt**:获取文档站点的概览和目录 ```text https://www.wuyunai.com/docs/llms.txt ``` 2. **访问 llms-full.txt**:获取完整的文档内容 ```text https://www.wuyunai.com/docs/llms-full.txt ``` 3. **访问特定页面的 .html 文件**:获取单个页面的 Markdown 格式内容 ```text https://www.wuyunai.com/docs/guide/quickstart.html ``` ### 对于开发者 如果你在使用 AI 助手(如 ChatGPT、Claude 等)时,想要让它了解 Auto.js Pro 的文档,可以: 1. **直接提供链接**:将 `llms.txt` 或 `llms-full.txt` 的链接提供给 AI 2. **提供特定页面**:将特定页面的 `.html` 文件链接提供给 AI 3. **在提示词中说明**:告诉 AI 可以通过这些文件获取 Auto.js Pro 的文档信息 **示例提示词:** ```text 请参考以下文档了解 Auto.js Pro: - 概览:https://www.wuyunai.com/docs/llms.txt - 完整文档:https://www.wuyunai.com/docs/llms-full.txt - 快速上手:https://www.wuyunai.com/docs/guide/quickstart.html ``` ## 相关链接 * [VuePress LLM 插件文档](https://ecosystem.vuejs.press/zh/plugins/ai/llms.html) * [llms.txt 规范](https://llmstxt.org/) * [Auto.js Pro 文档首页](/docs/) ## 总结 通过支持 LLM 协议,Auto.js Pro 官方文档站点为 AI 助手提供了更好的文档访问方式,使得: * ✅ AI 可以更快速地理解文档结构 * ✅ AI 可以更准确地回答关于 Auto.js Pro 的问题 * ✅ 开发者可以更方便地让 AI 助手帮助编写代码 * ✅ 文档内容以结构化的方式提供给 LLM 如果你在使用 AI 助手时遇到关于 Auto.js Pro 的问题,可以引导它访问 `llms.txt` 或 `llms-full.txt` 文件来获取准确的文档信息。 --- --- url: 'https://www.wuyunai.com/docs/guide/project.html' description: >- Auto.js Pro 项目配置完整指南 - 了解 project.json 配置文件的所有参数,包括应用名称、包名、权限配置、启动配置、优化设置等详细说明。 --- 在Auto.js Pro中,我们有时不仅需要运行单文件,还有运行项目的需求。所谓项目,就是一个包含配置、代码文件、资源文件(图片等)的文件夹。 在Auto.js Pro中可以在主页新建项目,有多种项目模块可供选择(Pro 8.7以上)。 ## project.json `project.json`文件用于配置项目的相关参数,比如主文件、启动图、包名等信息。 | 参数名称 | 意义 | 类型 | 默认值 | | ---------------- | ------------------------------------------------------------ | -------- | --------------------------------------------------------- | | androidResources | 安卓资源,参见[androidResources](#androidresources) | Object | { "resDir": "res", "manifest": "AndroidManifest.xml" } | | build | 自动生成的构建信息,无需修改,参见[build](#build) | Object | | | assets | 保留字段,暂时没有作用 | string\[] | \[] | | encryptLevel | 加密级别 0-不加密, 1-本地加密, 2-在线加密(仅在8.7以上版本支持) | number | 0 | | icon | 桌面图标 | string | "icons/icon.png" | | ignore | 从VSCode中同步项目时的忽略文件,在Pro9.3以上版本被.autojs.sync.ignore文件代替 | string\[] | \["build"] | | launchConfig | 启动配置,参见[launchConfig](#launchconfig) | Object | | | main | 入口文件 | string | "main.js" | | name | app名字 | string | "" | | optimization | 优化配置,参见[optimization](#optimization) | Object | | | packageName | 包名,必须符合Android包名规范,另外上传商店时包名必须唯一 | string | "" | | permissionConfig | 权限配置,参见[permissionConfig](#permissionconfig) | Object | { "manifestPermissions": \[], "requestListOnStartup": \[] } | | publish | 发布/上传商店配置,参见[publish](#publish) | Object | | | scripts | 构建等时机自动触发运行的脚本配置,参见[scripts](#scripts) | Object | | | useFeatures | 特性 **`continuation`** - 是否使用协程特性,参见示例->协程 | string\[] | \[] | | versionCode | 版本号 | number | 1 | | versionName | 给用户看的版本名称 | string | "1.0.0" | 完整配置实例: ```javascript { "androidResources": { "resDir": "res", "manifest": "AndroidManifest.xml" }, "assets": [], "build": { "build_id": "6F47F367-1", "build_number": 1, "build_time": 1615553004812, "release": true }, "encryptLevel": 0, "useFeatures": [], "icon": "res/icon.png", "ignore": ["build"], "launchConfig": { "displaySplash": true, "hideLogs": false, "splashIcon": "res/splashIcon.png", "splashLayoutXml": "splash.xml", "splashText": "Powered by Auto.js Pro", "stableMode": false }, "main": "main.js", "name": "Shape3.0", "optimization": { "removeOpenCv": true, "unusedResources": true }, "packageName": "com.suzy.rippledrawable", "permissionConfig": { "manifestPermissions": ["android.permission.WRITE_EXTERNAL_STORAGE"], "requestListOnStartup": ["android.permission.WRITE_EXTERNAL_STORAGE"] }, "publish": { "category": "其他", "details": "控件描边、渐变、水波纹、文字渐变", "maxAutoJsVersion": -1, "minAutoJsVersion": -1, "maxProVersion": 8059999, "minProVersion": 8050000, "minSdkVersion": 2, "permissions": [], "summary": "控件描边、渐变、水波纹、文字渐变", "tags": [] }, "scripts": \{\}, "versionCode": 1, "versionName": "1.0.0" } ``` 最小配置实例: ```javascript { "name": "新建项目", "main": "main.js", "ignore": [ "build" ], "packageName": "com.example", "versionName": "1.0.0", "versionCode": 1 } ``` ### androidResources 用于配置Android原生界面的参数,参见示例->复杂界面->Android 原生界面。 | 参数名称 | 意义 | 类型 | 默认值 | | -------- | ----------------------------- | ------ | --------------------- | | resDir | Android资源文件夹 | string | "res" | | manifest | AndroidManifest.xml的文件路径 | string | "AndroidManifest.xml" | ### build 自动生成的构建信息,包含构建时间、构建号等,请勿修改。 打包软件中,将根据这里的信息判断是否需要解压覆盖安装包的文件到数据路径。(每次打包后这里的信心会更新,因此安装后可以自动更新本地数据中的项目文件) | 参数名称 | 意义 | 类型 | 默认值 | | ------------ | ---------------------------------------------- | ------- | -------------- | | build\_id | 自动生成的构建id | string | "" | | build\_number | 构建号,每次构建自增1 | number | 1 | | build\_time | 上次构建时间 | number | 当前13位时间戳 | | release | 是否为打包后项目,为自动生成的字段,不需要修改 | boolean | false | ### launchConfig 打包后的相关启动配置。 | 参数名称 | 意义 | 类型 | 默认值 | | --------------- | ------------------------------------------------------------ | ------- | ------------------------ | | displaySplash | 打包后是否显示启动图(即使设置为false,打包后第一次也仍然会显示启动图) | boolean | true | | hideLogs | 打包后是否隐藏日志界面 | boolean | false | | splashIcon | 打包后启动界面图标 | string | "icons/splashIcon.png" | | splashLayoutXml | 启动图xml,用于打包后自定义启动图,参见示例->项目与打包->自定义启动图 (8.5以上版本) | string | "splash.xml" | | splashText | 打包后启动界面文本 | string | "Powered by Auto.js Pro" | | stableMode | 打包后是否以稳定模式运行 | boolean | false | ### permissionConfig > Pro 8.8.1新增 自定义权限配置,包括应用打包后声明的权限列表和启动时自动申请的权限列表。 也可以在打包界面中使用权限配置修改。 | 参数名称 | 意义 | 类型 | 默认值 | | -------------------- | ------------------------------------------------------------ | -------- | --------------------------------------------- | | manifestPermissions | 打包后应用声明的权限列表。为了兼容旧版本配置,如果该字段为null则默认为122个自带权限。 | string\[] | null | | requestListOnStartup | 应用启动时自动申请的权限列表,权限务必包含在manifestPermissions中,否则会无法申请 | string\[] | \["android.permission.WRITE\_EXTERNAL\_STORAGE"] | 全部权限列表参见Android官方文档:[Manifest.permission](https://developer.android.google.cn/reference/android/Manifest.permission) ### publish 上传商店发布项目的相关配置。 | 参数名称 | 意义 | 类型 | 默认值 | | ---------------- | -------------------------------------------------- | -------- | ------ | | maxAutoJsVersion | 支持的最大autojs版本号 | number | 0 | | maxProVersion | 支持的最大autojspro版本号 | number | 0 | | minAutoJsVersion | 支持的最小autojs版本号 | number | 0 | | minProVersion | 支持的最大autojspro版本号 | number | 0 | | minSdkVersion | 支持的最小安卓版本 | number | 0 | | category | 项目类别,用于发布在商店时作为分类 | string | "其他" | | details | 项目详细描述,用于发布在商店时作为项目详情 | string | "" | | permissions | 权限列表,比如"root",暂时没有作用 | string\[] | \[] | | summary | 脚本功能简介 | string | "" | | tags | 脚本标签,由于商店还没有标签过滤功能,暂时没有作用 | string\[] | \[] | ### optimization 优化配置。目前用于打包时缩小体积。 | 参数名称 | 意义 | 类型 | 默认值 | | --------------- | -------------- | ------- | ------ | | removeOpenCv | 不需要图色模块 | boolean | false | | unusedResources | 不需要内置图标 | boolean | false | ## ignore配置 > Pro 8.7.6新增 ignore配置文件类似于`.gitignore`,用于配置Auto.js Pro处理打包、加密等忽略的文件。 ignore文件的规则和`.gitignore`相同,比如: ```text # / 表示 当前文件所在的目录 # 忽略public下的所有目录及文件 /public/ #不忽略/public/assets,就是特例的意思,assets文件不忽略 !/public/assets # 忽略具体的文件 index.js # 忽略所有的js文件 *.js # 忽略 a.js b.js [ab].js # 匹配规则和linux文件匹配一样 # 以斜杠“/”开头表示目录 # 以星号“*”通配多个字符 # 以问号“?”通配单个字符 # 以方括号“[]”包含单个字符的匹配列表 # 以叹号“!”表示不忽略(跟踪)匹配到的文件或目录 ``` ### .autojs.source.ignore 文件路径:项目文件夹下`.autojs.source.ignore`文件。 该文件配置的规则所匹配的文件,将不视为Auto.js Pro的JavaScript源码文件,不参与加密过程。 例如有些js文件是用于Web中加载的,不希望在打包时将其加密,可以配置该文件来忽略。 ### .autojs.build.ignore 文件路径:项目文件夹下`.autojs.build.ignore`文件。 该文件配置的规则所匹配的文件,将不参与Auto.js Pro的打包apk过程,最终生成的apk中,将不包含指定的文件。 例如有时node\_modules仅在开发时使用,最终打包时已经通过webpack等工具打包为单文件,则可以配置忽略node\_modules文件夹。 ### .autojs.sync.ignore 文件路径:项目文件夹下`.autojs.sync.ignore`文件。 该文件配置的规则所匹配的文件,在VSCode插件运行、同步、保存电脑上的文件时,将被忽略。仅适用于Pro 9.3以上版本。 ## scripts scripts字段用于配置构建等时机自动执行的shell命令。例如: ```json { // ... "scripts": { "build-apk-pre-prepare": "sh build.sh" }, // ... } ``` 以上配置将在打包apk前自动运行build.sh脚本,从而可以在打包前进行自定义的文件替换、混淆等。在这些shell命令中,你可以用`node build.js`来执行js文件(纯Node.js环境);目前暂不支持执行Auto.js环境的js脚本。 目前支持以下时机: ### 构建apk时触发 Auto.js Pro构建apk分为几个阶段: * 准备阶段:拷贝项目文件、apk文件,处理源文件等 * 构建阶段:执行aapt编译,添加内置图标包,修改并写入Manifest文件、处理插件等 * 优化阶段:移除无用资源、模块、混淆组件等 * 打包阶段:签名、压缩、清理工作空间等 可以在不同阶段自定义要执行的sh脚本。在这些脚本中,可以通过以下环境变量获取信息: * `BUILD_APK_WORKSPACE`: 构建的临时工作区,也就是解压apk的临时项目 * `BUILD_APK_WORKSPACE_PROJECT`: 工作区下的项目文件夹,项目将会被复制到这里 * `BUILD_APK_OUTPUT`:apk的目标输出路径 可以通过`pwd`等命令获取当前项目路径。 每个阶段对应的名称有: * `build-apk-pre-prepare`: 准备阶段前触发 * `build-apk-post-prepare`: 准确阶段后触发 * `build-apk-pre-build`: 构建阶段前触发 * `build-apk-post-build`: 构建阶段后触发 * `build-apk-pre-optimize`: 优化阶段前触发 * `build-apk-post-optimize`: 优化阶段后触发 * `build-apk-pre-package`: 打包阶段前触发 * `build-apk-post-package`: 打包阶段后触发 --- --- url: 'https://www.wuyunai.com/docs/guide/using-modules.html' description: >- Auto.js Pro 内置模块和函数使用指南 - 学习如何使用 Auto.js 自带的内置函数和模块,包括 API 文档查找、模块引入、函数调用等基础知识。 --- # 使用内置模块和函数 在学会 JavaScript 基础后,你便可以尝试使用 Auto.js 自带的函数和模块了。类似于之前的`toastLog`函数,Auto.js 内置了很多函数和模块。这些函数和模块都可以在文档中找到。 文档链接:[第一代 API 文档](../v8/) 在文档的左侧菜单(手机需要点击左上角按钮展开)是模块列表,列出每个模块的功能和名字。例如 app 模块用于启动其他应用、获取其他应用信息等。 在第一代 API 中,所有模块都可以直接使用而无需导入。例如,我们需要使用 app 模块的`launchApp`函数来启动其他应用: ```javascript $app.launchApp("日历"); ``` 在这行代码中,`$app`表示 app 模块(前面的$符号表示这是内置模块,避免和你自定义的变量冲突),launchApp 则是 app 模块的函数。 通过阅读[这个函数的文档](../v8/app.html#app-launchapp-appname)我们知道,这个函数的作用是通过应用名称启动一个应用,运行后可启动日历应用。 ## 一个小例子 这里给一个小例子。(未完待续) ## 更多的例子 在 Auto.js Pro 的主页面,切换到示例的 Tab,这里有大量的官方示例。 ![更多的例子](/assets/image/examples.dc88f4c7.jpg) --- --- url: 'https://www.wuyunai.com/docs/guide/using-project.html' description: Auto.js Pro 项目与资源管理指南 - 学习如何创建和管理项目,组织多个文件,使用资源文件(图片、音乐等),以及项目模板的使用方法。 --- # 项目与资源 当我们的代码越来越复杂时,我们可能需要编写多个 js 文件,文件之间互相调用;一些 js 文件也可能需要读取图片、音乐等资源文件。项目就是用来组织多个文件的文件夹,项目的文件夹中包含一个 project.json 文件,描述项目的配置,这个配置文件可以让你在打包时无需每次重新填写打包的信息。 通过 Auto.js Pro 的文件管理界面右下角的菜单,选择项目即可新建项目。创建时可选择不同的项目模板或者自定义模板,首次使用时选择空项目即可。 新建项目后将自动创建一个项目名称的文件夹,在该文件夹下可看到至少两个文件: * `main.js`:项目的主入口文件,运行项目时将执行该文件 * `project.json`:项目的配置文件,配置项目的名称、包名、打包配置等 在项目中,我们所有使用的资源文件、模块文件都要放在项目的文件夹下,并用相对路径引用,才可以在打包时被打包进 apk 并正确运行。 例如我们写了一个模块文件`sum.js`: ```javascript // sum.js function sum(n) { let result = 0; for (let i = 0; i < n; i++) { result += i; } return result; } module.exports = sum; ``` 那么在`main.js`中,需要用相对路径引用: ```javascript // main.js let sum = require("./sum.js"); console.log(sum(100)); ``` 同样地,资源文件也需要使用相对路径引用。比如我们的项目文件夹下有以下文件: ```text main.js sum.js apple.txt project.json ``` 在读取 apple 文本时,需要使用相对路径,比如`let apple = $files.read('./apple.txt')`。 ## 项目管理与打包 打开项目的文件夹,可以看到上方工具栏有一个圆规图标: ![项目管理](/assets/image/project-menu.b422b5e8.jpg) 点击后弹出的菜单可编辑项目、打包项目、发布项目等。项目的打包和上一节的打包基本一样,这里不再阐述;发布项目将会上传项目到商店中,审核通过后,所有人都可以下载该项目。 --- --- url: 'https://www.wuyunai.com/docs/v8/index.html' description: >- Auto.js Pro 第一代 API 文档阅读须知 - 基于 Rhino 引擎的 API 文档,包含 API 稳定性说明、使用指南和注意事项。适合使用默认引擎的开发者。 --- 本文档为第一代API文档,也即Auto.js Pro的默认引擎(Rhino犀牛引擎)的文档。如果你从来没有听说过什么第一代API,犀牛引擎,Node.js引擎,那么说明**你来对文档了**。 > 查看基于Node.js的API(第二代API)的文档请在菜单栏切换第二代API文档。 # API稳定性 由于Auto.js Pro处于活跃的更新和开发状态,API可能随时有变动,我们用Stability来标记模块、函数的稳定性。这些标记包括: ```txt Stability: 0 - Deprecated 弃用的函数、模块或特性,在未来的更新中将很快会被移除或更改。应该在脚本中移除对这些函数的使用,以免后续出现意料之外的问题。 Stability: 1 - Experimental 实验性的函数、模块或特性,在未来的更新中可能会更改或移除。应该谨慎使用这些函数或模块,或者仅用作临时或试验用途。 Stability: 2 - Stable 稳定的函数、模块或特性,在未来的更新中这些模块已有的函数一般不会被更改,会保证后向兼容性。 ``` # 如何阅读本文档 先看一个例子,下面是[基于控件的操作模拟](./coordinatesBasedAutomation.html)的章节中input函数的部分说明。 ## input(\[i, ]text) * `i` {number} 表示要输入的为第i + 1个输入框 * `text` {string} 要输入的文本 input表示函数名,括号内的`[i, ]text`为函数的参数。下面是参数列表,"number"表示参数i的类型为数值,"string"表示参数text的类型为字符串。 例如input(1, "啦啦啦"),执行这个语句会在屏幕上的第2个输入框处输入"啦啦啦"。 方括号\[ ]表示参数为可选参数。也就是说,可以省略i直接调用input。例如input("嘿嘿嘿"),按照文档,这个语句会在屏幕上所有输入框输入"嘿嘿嘿"。 调用有可选参数的函数时请不要写上方括号。 我们再看第二个例子。图片和图色处理中detectsColor函数的部分说明。 ## images.detectsColor(image, color, x, y\[, threshold = 16, algorithm = "diff"]) * `image` {Image} 图片 * `color` {number} | {string} 要检测的颜色 * `x` {number} 要检测的位置横坐标 * `y` {number} 要检测的位置纵坐标 * `threshold` {number} 颜色相似度临界值,默认为16。取值范围为0~255。 * `algorithm` {string} 颜色匹配算法,包括: * "equal": 相等匹配,只有与给定颜色color完全相等时才匹配。 * "diff": 差值匹配。与给定颜色的R、G、B差的绝对值之和小于threshold时匹配。 * "rgb": rgb欧拉距离相似度。与给定颜色color的rgb欧拉距离小于等于threshold时匹配。 * "rgb+": 加权rgb欧拉距离匹配([LAB Delta E](https://en.wikipedia.org/wiki/Color_difference))。 * "hs": hs欧拉距离匹配。hs为HSV空间的色调值。 同样地,`[, threshold = 16, algorithm = "rgb"]`为可选参数,并且,等于号=后面的值为参数的默认值。也就是如果不指定该参数,则该参数将会为这个值。 例如 `images.detectsColor(captureScreen(), "#112233", 100, 200)` 相当于 `images.detectsColor(captureScreen(), "#112233", 100, 200, 16, "rgb")`, 而`images.detectsColor(captureScreen(), "#112233", 100, 200, 64)` 相当于`images.detectsColor(captureScreen(), "#112233", 100, 200, 64, "rgb")`。 调用有可选参数及默认值的函数时请不要写上方括号和等于号。 --- --- url: 'https://www.wuyunai.com/docs/v8/advanced.html' --- # Auto.js 高级版更新日志 ## ✨ Auto.js Pro 高级版 Auto.js 高级版基于 Auto.js Pro 9.3.11 停服后的完美复活版本,修复服务器 API,自搭建优化,功能与原官方 Pro 版本一致,并新增多项增强特性。为脚本开发者提供 免 Root、免 Host 转发、打包稳定、运行高效 的自动化平台。 ## 🔥 Auto.js 高级版 9.3.11 - 全网完美版 深度优化的本地客户端与服务器,带来以下核心特性: * 🚫 无需配置 Host:无缝对接服务器 API,省去复杂网络配置。 * ✅ 打包不闪退:深度定制客户端,彻底解决脚本打包闪退问题。 * 🌍 多设备支持:邀请码通用,灵活切换设备。 * 🚀 一键安装:快速上手,极致直装体验。 * 💻 支持电脑远程调试:通过 VsCode 远程连接,高效调试脚本。 * 📱 微信混淆控件适配:支持最新版微信混淆控件的识别与点击,完美支持打包。 * ⚙️ 自定义无障碍服务类名:支持打包时自定义无障碍服务类名,适配特定应用的检测机制或白名单要求。 * 🔄 开机自启支持:打包后支持开机自启功能,自动化更省心。 * ☁️ 完美热更新:支持网络加载 JS 代码、快照源码、工程 Zip 代码,灵活部署。 ## 🚀 快速上手指南 1. 📱 打开应用,点击 “登录” 按钮。 2. 🔑 输入 邀请码,即可解锁所有功能。 ## 💬 获取邀请码 QQ 群:1065375789 QQ: 68107808 微信: 68107808 ## ✨ 核心功能特性 ### 🔗 无缝对接官方 API * 🛠️ 支持所有 Auto.js 原生 API。 * 🔌 兼容所有第三方扩展,支持自创扩展。 * 📸 内置完整 OCR 插件,满足图像识别需求。 ### 🛡️ 免 Root & 免 Host 转发 * 🔧 源码级优化,实现后台直连。 * 🌐 内建流量转发机制,无需 Root 或 Host。 * 📲 安装即用,无需额外权限配置。 ### 🔓 解锁功能限制 * ✅ 自动解除主流 App 控件点击限制,提升兼容性。 * 🛠️ 适配复杂界面,增强自动化执行成功率。 ### 🔒 代码加密与安全 * 🔐 增强型离线加密:保护脚本逻辑。 * 📷 快照加密:确保核心代码安全。 * 🛡️ 防止脚本逻辑泄露,保障开发者权益。 ### 🔄 邀请码多设备支持 * 📱 同一邀请码支持多设备切换使用。 * ⚠️ 注意:跨 IP 使用可能导致账号挤出,建议合理规划使用。 ### ⚙️ 特色亮点 1. 深度优化 Auto.js Pro 9.3.11: * 🛠️ 功能完整,打包稳定不闪退。 * 🚀 免 Host 配置,即装即用。 2. 适配最新微信混淆控件: * 📲 精准识别与点击,提升自动化成功率。 3. [支持打包脚本开机启动](./settings.html) 💡 提示:需卸载旧版本并安装本高级版后生效。 4. 支持 VsCode 远程调试: * 💻 通过电脑远程连接 VsCode,实时调试脚本,提升开发效率。 * [VSCode调试教程](/blog/vscode-debug-v9.html) 5. 自定义无障碍服务类名: \- ⚙️ 打包时支持自定义无障碍服务类名,适配特定应用检测机制。 6. 完美热更新支持: * ☁️ 支持网络加载 JS、快照、工程 Zip 文件,灵活实现代码热更新。 ## 🌐 [运行网络代码资源](./engines.html#engines-execscriptfile-path) 📜 版权与声明 📚 所有资源整理自互联网,版权归原作者所有。 ⚖️ 如有侵权或问题,请联系维护者及时处理。 --- --- url: 'https://www.wuyunai.com/docs/v8/app.html' description: >- Auto.js Pro v8 app 模块 API 文档 - 提供与其他应用交互的功能,包括启动应用、发送意图、打开文件、发送邮件、广播接收等。支持启动 Activity、发送广播等高级功能。 --- # app - 应用 app 模块提供一系列函数,用于使用其他应用、与其他应用交互。例如发送意图、打开文件、发送邮件等。 同时提供了方便的进阶函数 startActivity 和 sendBroadcast,用他们可完成 app 模块没有内置的和其他应用的交互。 ## app.versionCode * {number} 当前软件版本号,整数值。例如 160, 256 等。 如果在 Auto.js 中运行则为 Auto.js 的版本号;在打包的软件中则为打包软件的版本号。 ```javascript toastLog(app.versionCode); ``` ## app.versionName * {string} 当前软件的版本名称,例如"3.0.0 Beta"。 如果在 Auto.js 中运行则为 Auto.js 的版本名称;在打包的软件中则为打包软件的版本名称。 ```javascript toastLog(app.versionName); ``` ## app.autojs.versionCode * {number} Auto.js 版本号,整数值。例如 160, 256 等。 ## app.autojs.versionName * {string} Auto.js 版本名称,例如"3.0.0 Beta"。 ## app.launchApp(appName) * `appName` {string} 应用名称 通过应用名称启动应用。如果该名称对应的应用不存在,则返回 false; 否则返回 true。如果该名称对应多个应用,则只启动其中某一个。 该函数也可以作为全局函数使用。 ```javascript launchApp("Auto.js"); ``` ## app.launch(packageName) * `packageName` {string} 应用包名 通过应用包名启动应用。如果该包名对应的应用不存在,则返回 false;否则返回 true。 该函数也可以作为全局函数使用。 ```javascript //启动微信 launch("com.tencent.mm"); ``` ## app.launchPackage(packageName) * `packageName` {string} 应用包名 相当于`app.launch(packageName)`。 ## app.getPackageName(appName) * `appName` {string} 应用名称 获取应用名称对应的已安装的应用的包名。如果该找不到该应用,返回 null;如果该名称对应多个应用,则只返回其中某一个的包名。 该函数也可以作为全局函数使用。 ```javascript var name = getPackageName("QQ"); //返回"com.tencent.mobileqq" ``` ## app.getAppName(packageName) * `packageName` {string} 应用包名 获取应用包名对应的已安装的应用的名称。如果该找不到该应用,返回 null。 该函数也可以作为全局函数使用。 ```javascript var name = getAppName("com.tencent.mobileqq"); //返回"QQ" ``` ## app.openAppSetting(packageName) * `packageName` {string} 应用包名 打开应用的详情页(设置页)。如果找不到该应用,返回 false; 否则返回 true。 该函数也可以作为全局函数使用。 ## app.getCurrentActivity() * 返回 {Activity | null} 返回当前记录的 Activity 对象。在无前台 Activity 上下文时可能返回 `null`。 ```javascript log(app.getCurrentActivity()); ``` ## app.getTopActivity() * 返回 {Activity | null} 返回当前栈顶 Activity 对象。在无可用 Activity 时可能返回 `null`。 ```javascript log(app.getTopActivity()); ``` ## app.getFileProviderAuthority() * 返回 {string} 返回当前应用使用的 FileProvider Authority。 ```javascript log(app.getFileProviderAuthority()); ``` ## app.getPathFromUri(uri) * `uri` {Uri} Uri 对象 * 返回 {string | null} 尝试将 Uri 解析为文件路径。对于部分 `content://` Uri,可能返回 `null` 或不可直接访问路径。 ```javascript var uri = app.parseUri("file:///sdcard/1.txt"); log(app.getPathFromUri(uri)); ``` ## app.sendLocalBroadcastSync(intent) * `intent` {Intent} 广播意图对象 * 返回 {void} 同步发送本地广播(Local Broadcast),通常用于应用内部组件之间通信。 ```javascript var i = app.intent({ action: "com.example.LOCAL_TEST", }); app.sendLocalBroadcastSync(i); ``` ## app.viewFile(path) * `path` {string} 文件路径 用其他应用查看文件。文件不存在的情况由查看文件的应用处理。 如果找不出可以查看该文件的应用,则抛出`ActivityNotException`。 ```javascript //查看文本文件 app.viewFile("/sdcard/1.txt"); ``` ## app.editFile(path) * `path` {string} 文件路径 用其他应用编辑文件。文件不存在的情况由编辑文件的应用处理。 如果找不出可以编辑该文件的应用,则抛出`ActivityNotException`。 ```javascript //编辑文本文件 app.editFile("/sdcard/1.txt/"); ``` ## app.uninstall(packageName) * `packageName` {string} 应用包名 卸载应用。执行后会会弹出卸载应用的提示框。如果该包名的应用未安装,由应用卸载程序处理,可能弹出"未找到应用"的提示。 ```javascript //卸载QQ app.uninstall("com.tencent.mobileqq"); ``` ## app.openUrl(url) * `url` {string} 网站的 Url,如果不以"http://"或"https://"开头则默认是"http://"。 用浏览器打开网站 url。 如果没有安装浏览器应用,则抛出`ActivityNotException`。 ## app.sendEmail(options) * `options` {Object} 发送邮件的参数。包括: * `email` {string} | {Array} 收件人的邮件地址。如果有多个收件人,则用字符串数组表示 * `cc` {string} | {Array} 抄送收件人的邮件地址。如果有多个抄送收件人,则用字符串数组表示 * `bcc` {string} | {Array} 密送收件人的邮件地址。如果有多个密送收件人,则用字符串数组表示 * `subject` {string} 邮件主题(标题) * `text` {string} 邮件正文 * `attachment` {string} 附件的路径。 根据选项 options 调用邮箱应用发送邮件。这些选项均是可选的。 如果没有安装邮箱应用,则抛出`ActivityNotException`。 ```javascript //发送邮件给10086@qq.com和10001@qq.com。 app.sendEmail({ email: ["10086@qq.com", "10001@qq.com"], subject: "这是一个邮件标题", text: "这是邮件正文", }); ``` ## app.startActivity(name) * `name` {string} 活动名称,可选的值为: * `console` 日志界面 * `settings` 设置界面 启动 Auto.js 的特定界面。该函数在 Auto.js 内运行则会打开 Auto.js 内的界面,在打包应用中运行则会打开打包应用的相应界面。 ```javascript app.startActivity("console"); ``` ## app.intent(options) * `options` {Object} 选项,包括: * `action` {string} 意图的 Action,指意图要完成的动作,是一个字符串常量,比如"android.intent.action.SEND"。当 action 以"android.intent.action"开头时,可以省略前缀,直接用"SEND"代替。参见[Actions](https://developer.android.com/reference/android/content/Intent.html#standard-activity-actions)。 * `type` {string} 意图的 MimeType,表示和该意图直接相关的数据的类型,表示比如"text/plain"为纯文本类型。 * `data` {string} 意图的 Data,表示和该意图直接相关的数据,是一个 Uri, 可以是文件路径或者 Url 等。例如要打开一个文件, action 为"android.intent.action.VIEW", data 为"file:///sdcard/1.txt"。 * `category` {Array} 意图的类别。比较少用。参见[Categories](https://developer.android.com/reference/android/content/Intent.html#standard-categories)。 * `packageName` {string} 目标包名 * `className` {string} 目标 Activity 或 Service 等组件的名称 * `extras` {Object} 以键值对构成的这个 Intent 的 Extras(额外信息)。提供该意图的其他信息,例如发送邮件时的邮件标题、邮件正文。参见[Extras](https://developer.android.com/reference/android/content/Intent.html#standard-extra-data)。 * `flags` {Array} intent 的标识,字符串数组,例如`["activity_new_task", "grant_read_uri_permission"]`。参见[Flags](https://developer.android.com/reference/android/content/Intent.html#setFlags\(int\))。 **\[v4.1.0 新增]** * `root` {Boolean} 是否以 root 权限启动、发送该 intent。使用该参数后,不能使用`context.startActivity()`等方法,而应该直接使用诸如`app.startActivity({...\})`的方法。 **\[v4.1.0 新增]** 根据选项,构造一个意图 Intent 对象。 例如: ```javascript //打开应用来查看图片文件 var i = app.intent({ action: "VIEW", type: "image/png", data: "file:///sdcard/1.png", }); context.startActivity(i); ``` Intent(意图) 是一个消息传递对象,您可以使用它从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件之间的通信,但其基本用例主要包括以下三个: * 启动活动(Activity): Activity 表示应用中的一个"屏幕"。例如应用主入口都是一个 Activity,应用的功能通常也以 Activity 的形式独立,例如微信的主界面、朋友圈、聊天窗口都是不同的 Activity。通过将 Intent 传递给 startActivity(),您可以启动新的 Activity 实例。Intent 描述了要启动的 Activity,并携带了任何必要的数据。 * 启动服务(Service): Service 是一个不使用用户界面而在后台执行操作的组件。通过将 Intent 传递给 startService(),您可以启动服务执行一次性操作(例如,下载文件)。Intent 描述了要启动的服务,并携带了任何必要的数据。 * 传递广播: 广播是任何应用均可接收的消息。系统将针对系统事件(例如:系统启动或设备开始充电时)传递各种广播。通过将 Intent 传递给 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast(),您可以将广播传递给其他应用。 需要注意的是,除非应用专门暴露 Activity 出来,否则在没有 root 权限的情况下使用 intent 是无法跳转到特定 Activity、应用的特定界面的。例如我们能通过 Intent 跳转到 QQ 的分享界面,是因为 QQ 对外暴露了分享的 Activity;而在没有 root 权限的情况下,我们无法通过 intent 跳转到 QQ 的设置界面,因为 QQ 并没有暴露这个 Activity。 但如果有 root 权限,则在 intent 的参数加上`"root": true`即可。例如使用 root 权限跳转到 Auto.js 的设置界面为: ```javascript app.startActivity({ packageName: "org.autojs.autojs", className: "org.autojs.autojs.ui.settings.SettingsActivity_", root: true, }); ``` 另外,关于 intent 的参数如何获取的问题,一些 intent 是意外发现并且在网络中传播的(例如跳转 QQ 聊天窗口是因为 QQ 给网页提供了跳转到客服 QQ 的方法),如果要自己获取活动的 intent 的参数,可以通过例如"intent 记录","隐式启动"等应用拦截内部 intent 或者查询暴露的 intent。其中拦截内部 intent 需要 XPosed 框架,或者可以通过反编译等手段获取参数。总之,没有简单直接的方法。 更多信息,请百度\[安卓 Intent]\(https://www.baidu.com/s?wd=android Intent)或参考[Android 指南: Intent](https://developer.android.com/guide/components/intents-filters.html#Types)。 ## app.startActivity(options) * `options` {Object} 选项 根据选项构造一个 Intent,并启动该 Activity。 ```javascript app.startActivity({ action: "SEND", type: "text/plain", data: "file:///sdcard/1.txt", }); ``` ## app.sendBroadcast(options) * `options` {Object} 选项 根据选项构造一个 Intent,并发送该广播。 ## app.startService(options) * `options` {Object} 选项 根据选项构造一个 Intent,并启动该服务。 ## app.sendBroadcast(name) **\[v4.1.0 新增]** * `name` {string} 特定的广播名称,包括: * `inspect_layout_hierarchy` 布局层次分析 * `inspect_layout_bounds` 布局范围 发送以上特定名称的广播可以触发 Auto.js 的布局分析,方便脚本调试。这些广播在 Auto.js 发送才有效,在打包的脚本上运行将没有任何效果。 ```javascript app.sendBroadcast("inspect_layout_bounds"); ``` ## app.intentToShell(options) **\[v4.1.0 新增]** * `options` {Object} 选项 根据选项构造一个 Intent,转换为对应的 shell 的 intent 命令的参数。 例如: ```javascript shell( "am start " + app.intentToShell({ packageName: "org.autojs.autojs", className: "org.autojs.autojs.ui.settings.SettingsActivity_", }), true, ); ``` 参见[intent 参数的规范](https://developer.android.com/studio/command-line/adb#IntentSpec)。 ## app.parseUri(uri) **\[v4.1.0 新增]** * `uri` {string} 一个代表 Uri 的字符串,例如"file:///sdcard/1.txt", "https://www.autojs.org" * 返回 {Uri} 一个代表 Uri 的对象,参见[android.net.Uri](https://developer.android.com/reference/android/net/Uri)。 解析 uri 字符串并返回相应的 Uri 对象。即使 Uri 格式错误,该函数也会返回一个 Uri 对象,但之后如果访问该对象的 scheme, path 等值可能因解析失败而返回`null`。 需要注意的是,在高版本 Android 上,由于系统限制直接在 Uri 暴露文件的绝对路径,因此如果 uri 字符串是文件`file://...`,返回的 Uri 会是诸如`content://...`的形式。 ## app.currentActivity * {Activity | null} 当前 Activity 的快捷属性,语义与 `app.getCurrentActivity()` 一致。 ## app.topActivity * {Activity | null} 栈顶 Activity 的快捷属性,语义与 `app.getTopActivity()` 一致。 ## app.fileProviderAuthority * {string} FileProvider Authority 的快捷属性,语义与 `app.getFileProviderAuthority()` 一致。 ## app.packageName * {string | undefined} 当前脚本环境对应的包名快捷属性。语义上可理解为 `app.getPackageName(app.appName)` 的便捷访问。 ## app.appName * {string | undefined} 当前脚本环境对应的应用名称快捷属性。语义上可理解为当前包名对应的应用名。 ## app.pathFromUri * {string | undefined} 最近一次 Uri 路径解析结果的快捷属性。通常更推荐直接使用 `app.getPathFromUri(uri)` 获取明确结果。 ## app.getUriForFile(path) **\[v4.1.0 新增]** * `path` {string} 文件路径,例如"/sdcard/1.txt" * 返回 {Uri} 一个指向该文件的 Uri 的对象,参见[android.net.Uri](https://developer.android.com/reference/android/net/Uri)。 从一个文件路径创建一个 uri 对象。需要注意的是,在高版本 Android 上,由于系统限制直接在 Uri 暴露文件的绝对路径,因此返回的 Uri 会是诸如`content://...`的形式。 ## app.getInstalledApps(\[options]) \*\* \[[Pro 8.0.0 新增](https://pro.autojs.org/)] \*\* * `options` {Object} 选项,包括: * `get`: 指定返回的应用信息中包含的信息 * `"activities"` 应用的 Activity 组件信息 * `"configurations"` 应用的硬件配置 * `"gids"` 应用的 group id * `"instrumentation"` 应用的 Instrumentation 信息 * `"intent_filters"` 应用的意图过滤 * `"meta_data"` 应用的元信息(默认) * `"permissions"` 应用的权限信息 * `"providers"` 应用的 ContentProvider 组件信息 * `"receivers"` 应用的 BroadcastReceiver 组件信息 * `"services"` 应用的 Service 组件信息 * `"shared_library_files"` 应用的动态链接库文件信息 * `"signatures"` 应用的签名信息(已弃用 * `"signing_certificates"` 应用的签名信息 * `"uri_permission_patterns"` * `"disabled_components"` 被卸载的但保留了数据的应用 * `"disabled_until_used_components"` 禁用直到被使用的组件 * `"uninstalled_packages"` 被卸载的但保留了数据的应用 * `match`: 指定要匹配的应用列表 * `"uninstalled_packages"` 被卸载的但保留了数据的应用 * `"disabled_components"` 被禁用的组件 * `"disabled_until_used_components"` 禁用直到被使用的组件 * `"system_only"` 只匹配系统应用 * `"factory_only"` 只匹配预装应用 * `"apex"` APEX 应用 * 返回 {Array\} 返回为当前用户安装的所有应用程序包的列表。如果设置了 match 选项 `uninstalled_packages`,则包括被删除但保留了数据的应用程序。 获取安装的应用列表。 返回值是 ApplicationInfo 对象的数组。 如果没有安装任何应用,则返回一个空数组。 选项 options 的 match 选项用于指定要返回哪些应用程序,get 选项用于指定返回的应用程序携带哪些信息。 ```javascript // 获取系统app let apps = $app.getInstalledApps({ get: ["meta_data"], match: ["system_only"], }); console.log(apps); ``` ## app.getInstalledPackages(\[options]) * `options` {Object} 可选,与 `app.getInstalledApps([options])` 的筛选参数语义一致 * 返回 {Array\} 获取已安装应用的包名列表。\ 当不传 `options` 时,返回当前用户可见应用的包名数组。 ```javascript let packages = app.getInstalledPackages(); log("包名数量: " + packages.length); ``` --- --- url: 'https://www.wuyunai.com/docs/v8/automator/index.html' description: >- Auto.js Pro v8 自动化控件操作入门指南 - 学习基于控件的自动化操作,包括无障碍服务使用、控件选择、控件操作等基础知识。适合自动化脚本开发新手。 --- # 入门介绍 基于控件的操作指的是选择屏幕上的控件,获取其信息或对其进行操作。对于一般软件而言,基于控件的操作对不同机型有很好的兼容性;但是对于游戏而言,由于游戏界面并不是由控件构成,无法采用本章节的方法,也无法使用本章节的函数。有关游戏脚本的编写,请参考[自动化 - 坐标操作](../coordinatesBasedAutomation.html)。 基于控件的操作依赖于无障碍服务,因此最好在脚本开头使用`auto()`函数来确保无障碍服务已经启用。如果运行到某个需要权限的语句无障碍服务并没启动,则会抛出异常并跳转到无障碍服务界面。这样的用户体验并不好,因为需要重新运行脚本,后续会加入等待无障碍服务启动并让脚本继续运行的函数。 您也可以在脚本开头使用`"auto";`表示这个脚本需要无障碍服务,但是不推荐这种做法,因为这个标记必须在脚本的最开头(前面不能有注释或其他语句、空格等),我们推荐使用`auto()`函数来确保无障碍服务已启用。 --- --- url: 'https://www.wuyunai.com/docs/v8/automator/api.html' description: >- Auto.js Pro v8 automator 模块 API 文档 - 提供自动化操作相关函数,包括 auto() 函数、无障碍服务管理、自动化操作辅助函数等。完整的自动化 API 参考。 --- ## auto(\[mode]) * `mode` {string} 模式 检查无障碍服务是否已经启用,如果没有启用则抛出异常并跳转到无障碍服务启用界面;同时设置无障碍模式为mode。mode的可选值为: * `fast` 快速模式。该模式下会启用控件缓存,从而选择器获取屏幕控件更快。对于需要快速的控件操作的脚本可以使用该模式,一般脚本则没有必要使用该函数。 * `normal` 正常模式,默认。 如果不加mode参数,则为正常模式。 建议使用`auto.waitFor()`和`auto.setMode()`代替该函数,因为`auto()`函数如果无障碍服务未启动会停止脚本;而`auto.waitFor()`则会在在无障碍服务启动后继续运行。 示例: ```javascript auto("fast"); ``` 示例2: ```javascript auto(); ``` ## auto.waitFor() 检查无障碍服务是否已经启用,如果没有启用则跳转到无障碍服务启用界面,并等待无障碍服务启动;当无障碍服务启动后脚本会继续运行。 因为该函数是阻塞的,因此除非是有协程特性,否则不能在ui模式下运行该函数,建议在ui模式下使用`auto()`函数。 ## auto.setMode(mode) * `mode` {string} 模式 设置无障碍模式为mode。mode的可选值为: * `fast` 快速模式。该模式下会启用控件缓存,从而选择器获取屏幕控件更快。对于需要快速的控件查看和操作的脚本可以使用该模式,一般脚本则没有必要使用该函数。 * `normal` 正常模式,默认。 ## auto.setFlags(flags) **\[v4.1.0新增]** * `flags` {string} | {Array} 一些标志,来启用和禁用某些特性,包括: * `findOnUiThread` 使用该特性后,选择器搜索时会在主进程进行。该特性用于解决线程安全问题导致的次生问题,不过目前貌似已知问题并不是线程安全问题。 * `useUsageStats` 使用该特性后,将会以"使用情况统计"服务的结果来检测当前正在运行的应用包名(需要授予"查看使用情况统计"权限)。如果觉得currentPackage()返回的结果不太准确,可以尝试该特性。 * `useShell` 使用该特性后,将使用shell命令获取当前正在运行的应用的包名、活动名称,但是需要root权限。 启用有关automator的一些特性。例如: ```javascript auto.setFlags(["findOnUiThread", "useShell"]); ``` ## auto.service **\[v4.1.0新增]** * {[AccessibilityService](https://developer.android.com/reference/android/accessibilityservice/AccessibilityService)} 获取无障碍服务。如果无障碍服务没有启动,则返回`null`。 参见[AccessibilityService](https://developer.android.com/reference/android/accessibilityservice/AccessibilityService)。 ## auto.windows **\[v4.1.0新增]** * {Array} 当前所有窗口([AccessibilityWindowInfo](https://developer.android.com/reference/android/view/accessibility/AccessibilityWindowInfo))的数组,可能包括状态栏、输入法、当前应用窗口,弹出窗口、悬浮窗、分屏应用窗口等。可以分别获取每个窗口的布局信息。 该函数需要Android 5.0以上才能运行。 ## auto.root **\[v4.1.0新增]** * {UiObject} 当前窗口的布局根元素。如果无障碍服务未启动或者WindowFilter均返回false,则会返回`null`。 如果不设置windowFilter,则当前窗口即为活跃的窗口(获取到焦点、正在触摸的窗口);如果设置了windowFilter,则获取的是过滤的窗口中的第一个窗口。 如果系统是Android5.0以下,则始终返回当前活跃的窗口的布局根元素。 ## auto.rootInActiveWindow **\[v4.1.0新增]** * {UiObject} 当前活跃的窗口(获取到焦点、正在触摸的窗口)的布局根元素。如果无障碍服务未启动则为`null`。 ## auto.setWindowFilter(filter) **\[v4.1.0新增]** * `filter` {Function} 参数为窗口([AccessibilityWindowInfo](https://developer.android.com/reference/android/view/accessibility/AccessibilityWindowInfo)),返回值为Boolean的函数。 设置窗口过滤器。这个过滤器可以决定哪些窗口是目标窗口,并影响选择器的搜索。例如,如果想要选择器在所有窗口(包括状态栏、输入法等)中搜索,只需要使用以下代码: ```javascript auto.setWindowFilter(function(window){ //不管是如何窗口,都返回true,表示在该窗口中搜索 return true; }); ``` 又例如,当前使用了分屏功能,屏幕上有Auto.js和QQ两个应用,但我们只想选择器对QQ界面进行搜索,则: ```javascript auto.setWindowFilter(function(window){ // 对于应用窗口,他的title属性就是应用的名称,因此可以通过title属性来判断一个应用 return window.title == "QQ"; }); ``` 选择器默认是在当前活跃的窗口中搜索,不会搜索诸如悬浮窗、状态栏之类的,使用WindowFilter则可以控制搜索的窗口。 需要注意的是, 如果WindowFilter返回的结果均为false,则选择器的搜索结果将为空。 另外setWindowFilter函数也会影响`auto.windowRoots`的结果。 该函数需要Android 5.0以上才有效。 ## auto.windowRoots **\[v4.1.0新增]** * {Array} 返回当前被WindowFilter过滤的窗口的布局根元素组成的数组。 如果系统是Android5.0以下,则始终返回当前活跃的窗口的布局根元素的数组。 # automator automator提供了一些模拟简单操作的函数,例如点击文字、模拟按键等。部分函数可以直接作为全局函数使用。 ## click(text\[, i]) * `text` {string} 要点击的文本 * `i` {number} 如果相同的文本在屏幕中出现多次,则i表示要点击第几个文本, i从0开始计算 返回是否点击成功。当屏幕中并未包含该文本,或者该文本所在区域不能点击时返回false,否则返回true。 该函数可以点击大部分包含文字的按钮。例如微信主界面下方的"微信", "联系人", "发现", "我"的按钮。 通常与while同时使用以便点击按钮直至成功。例如: ```javascript while(!click("扫一扫")); ``` 当不指定参数i时则会尝试点击屏幕上出现的所有文字text并返回是否全部点击成功。 i是从0开始计算的, 也就是, `click("啦啦啦", 0)`表示点击屏幕上第一个"啦啦啦", `click("啦啦啦", 1)`表示点击屏幕上第二个"啦啦啦"。 > 文本所在区域指的是,从文本处向其父视图寻找,直至发现一个可点击的部件为止。 ## click(left, top, bottom, right) * `left` {number} 要点击的长方形区域左边与屏幕左边的像素距离 * `top` {number} 要点击的长方形区域上边与屏幕上边的像素距离 * `bottom` {number} 要点击的长方形区域下边与屏幕下边的像素距离 * `right` {number} 要点击的长方形区域右边与屏幕右边的像素距离 **注意,该函数一般只用于录制的脚本中使用,在自己写的代码中使用该函数一般不要使用该函数。** 点击在指定区域的控件。当屏幕中并未包含与该区域严格匹配的区域,或者该区域不能点击时返回false,否则返回true。 有些按钮或者部件是图标而不是文字(例如发送朋友圈的照相机图标以及QQ下方的消息、联系人、动态图标),这时不能通过`click(text, i)`来点击,可以通过描述图标所在的区域来点击。left, bottom, top, right描述的就是点击的区域。 至于要定位点击的区域,可以在悬浮窗使用布局分析工具查看控件的bounds属性。 通过无障碍服务录制脚本会生成该语句。 ## longClick(text\[, i]) * `text` {string} 要长按的文本 * `i` {number} 如果相同的文本在屏幕中出现多次,则i表示要长按第几个文本, i从0开始计算 返回是否点击成功。当屏幕中并未包含该文本,或者该文本所在区域不能点击时返回false,否则返回true。 当不指定参数i时则会尝试点击屏幕上出现的所有文字text并返回是否全部长按成功。 ## scrollUp(\[i]) * `i` {number} 要滑动的控件序号 找到第i+1个可滑动控件上滑或**左滑**。返回是否操作成功。屏幕上没有可滑动的控件时返回false。 另外不加参数时`scrollUp()`会寻找面积最大的可滑动的控件上滑或左滑,例如微信消息列表等。 参数为一个整数i时会找到第i + 1个可滑动控件滑动。例如`scrollUp(0)`为滑动第一个可滑动控件。 ## scrollDown(\[i]) * `i` {number} 要滑动的控件序号 找到第i+1个可滑动控件下滑或**右滑**。返回是否操作成功。屏幕上没有可滑动的控件时返回false。 另外不加参数时`scrollUp()`会寻找面积最大的可滑动的控件下滑或右滑。 参数为一个整数i时会找到第i + 1个可滑动控件滑动。例如`scrollUp(0)`为滑动第一个可滑动控件。 ## setText(\[i, ]text) * `i` {number} 表示要输入的为第i + 1个输入框 * `text` {string} 要输入的文本 返回是否输入成功。当找不到对应的文本框时返回false。 不加参数i则会把所有输入框的文本都置为text。例如`setText("测试")`。 这里的输入文本的意思是,把输入框的文本置为text,而不是在原来的文本上追加。 ## input(\[i, ]text) * `i` {number} 表示要输入的为第i + 1个输入框 * `text` {string} 要输入的文本 返回是否输入成功。当找不到对应的文本框时返回false。 不加参数i则会把所有输入框的文本追加内容text。例如`input("测试")`。 ## automator.takeScreenshot() **\[[Pro 8.8.0新增](//www.wuyunai.com/docs)]** * 返回 {Image} 使用无障碍权限截图,返回一个Image对象。 相比起images模块申请截图权限截图,该函数不需要额外权限,但是有以下限制: * 截图频率限制。系统限制截图最多一秒一次,否则抛出异常 * 需要Android 11及以上版本 ```javascript $auto.waitFor(); let capture = $automator.takeScreenshot(); $images.save(capture, "../capture.png"); ``` ## automator.switchToInputMethod(packageName) **\[[Pro 8.8.0新增]()]** * `packageName` {string} 输入法包名 * 返回 {boolean} 切换到指定输入法,返回是否成功。失败的情况有以下可能: * 指定包名的输入法不存在或未启用 * 系统返回切换输入法失败 此函数需要Android 11及以上。 ```javascript // 切换到搜狗输入法 $automator.switchToInputMethod('com.sohu.inputmethod.sogou') ``` ## automator.headsetHook() **\[[Pro 8.8.0新增]()]** * 返回 {boolean} 模拟耳机键,返回是否执行成功。用于挂断、接听电话,播放、暂停音乐。 ## auto.clearCache() **\[v9.0.14新增]** 清空无障碍缓存。当屏幕上的控件已经出现但是选择器始终无法找到该控件,可能是缓存没有刷新,此时可调用`auto.clearCache()`来清空控件缓存。另外,从9.0版本开始,`findOne()`等函数在长时间找不到时也会自动清空缓存。 ## Rect `UiObject.bounds()`, `UiObject.boundsInParent()`返回的对象。表示一个长方形(范围)。 ### Rect.left * {number} 长方形左边界的x坐标、 ### Rect.right * {number} 长方形右边界的x坐标、 ### Rect.top * {number} 长方形上边界的y坐标、 ### Rect.bottom * {number} 长方形下边界的y坐标、 ### Rect.centerX() * 返回 {number} 长方形中点x坐标。 ### Rect.centerY() * 返回 {number} 长方形中点y坐标。 ### Rect.width() * 返回 {number} 长方形宽度。通常可以作为控件宽度。 ### Rect.height() * 返回 {number} 长方形高度。通常可以作为控件高度。 ### Rect.contains(r) * `r` {[Rect](../automator/api.html#rect)} 返回是否包含另一个长方形r。包含指的是,长方形r在该长方形的里面(包含边界重叠的情况)。 ### Rect.intersect(r) * `r` {[Rect](../automator/api.html#rect)} 返回是否和另一个长方形相交。 --- --- url: 'https://www.wuyunai.com/docs/v8/automator/selector.html' description: >- Auto.js Pro v8 UiSelector 选择器完整指南 - 学习如何使用选择器通过各种条件选取屏幕上的控件,包括文本、描述、类名、ID 等属性选择。包含丰富的选择器示例。 --- # 选择器 UiSelector即选择器,用于通过各种条件选取屏幕上的控件,再对这些控件进行点击、长按等动作。这里需要先简单介绍一下控件和界面的相关知识。 一般软件的界面是由一个个控件构成的,例如图片部分是一个图片控件(ImageView),文字部分是一个文字控件(TextView);同时,通过各种布局来决定各个控件的位置,例如,线性布局(LinearLayout)里面的控件都是按水平或垂直一次叠放的,列表布局(AbsListView)则是以列表的形式显示控件。 控件有各种属性,包括文本(text), 描述(desc), 类名(className), id等等。我们通常用一个控件的属性来找到这个控件,例如,想要点击某聊天应用的聊天窗口的"发送"按钮,我们就可以通过他的文本属性为"发送"来找到这个控件并点击他,具体代码为: ```javascript var sendButton = text("发送").findOne(); sendButton.click(); ``` 在这个例子中, `text("发送")`表示一个条件(文本属性为"发送"),`findOne()`表示基于这个条件找到一个符合条件的控件,从而我们可以得到发送按钮sendButton,再执行`sendButton.click()`即可点击"发送"按钮。 用文本属性来定位按钮控件、文本控件通常十分有效。但是,如果一个控件是图片控件,比如Auto.js主界面右上角的搜索图标,他没有文本属性,这时需要其他属性来定位他。我们如何查看他有什么属性呢?首先打开悬浮窗和无障碍服务,点击蓝色的图标(布局分析), 可以看到以下界面: 之后我们点击搜索图标,可以看到他有以下属性: 我们注意到这个图标的desc(描述)属性为"搜索",那么我们就可以通过desc属性来定位这个控件,得到点击搜索图标的代码为: ```javascript desc("搜索").findOne().click(); ``` 可能心细的你可能注意到了,这个控件还有很多其他的属性,例如checked, className, clickable等等,为什么不用这些属性来定位搜索图标呢?答案是,其他控件也有这些值相同的属性、尝试一下你就可以发现很多其他控件的checked属性和搜索控件一样都是`false`,如果我们用`checked(false)`作为条件,将会找到很多控件,而无法确定哪一个是搜索图标。因此,要找到我们想要的那个控件,**选择器的条件通常需要是可唯一确定控件的**。我们通常用一个独一无二的属性来定位一个控件,例如这个例子中就没有其他控件的desc(描述)属性为"搜索"。 另外,对于这个搜索图标而言,id属性也是唯一的,我们也可以用`id("action_search").findOne().click()`来点击这个控件。如果一个控件有id属性,那么这个属性很可能是唯一的,除了以下几种情况: * 被混淆的控件ID,可能名称都是相同的,或者在版本之间有变化 * 列表中的控件,比如联系人列表等 尽管id属性很方便,但也不总是最方便的,例如对于微信和网易云音乐,每次更新他的控件id都会变化,导致了相同代码对于不同版本的微信、网易云音乐并不兼容。 除了这些属性外,主要还有以下几种属性: * `className` 类名。类名表示一个控件的类型,例如文本控件为"android.widget.TextView", 图片控件为"android.widget.ImageView"等。 * `packageName` 包名。包名表示控件所在的应用包名,例如Auto.js Pro界面的控件的包名为"org.autojs.autojspro"。 * `bounds` 控件在屏幕上的范围。 * `drawingOrder` 控件在父控件的绘制顺序。 * `indexInParent` 控件在父控件的位置。 * `clickable` 控件是否可点击。 * `longClickable` 控件是否可长按。 * `checkable` 控件是否可勾选。 * `checked` 控件是否已勾选。 * `scrollable` 控件是否可滑动。 * `selected` 控件是否已选择。 * `editable` 控件是否可编辑。 * `visibleToUser` 控件是否可见。 * `enabled` 控件是否已启用。 * `depth` 控件的布局深度。 有时候只靠一个属性并不能唯一确定一个控件,这时需要通过属性的组合来完成定位,例如`className("ImageView").depth(10).findOne().click()`,通过链式调用来组合条件。 通常用这些技巧便可以解决大部分问题,即使解决不了问题,也可以通过布局分析的"生成代码"功能来尝试生成一些选择器代码。接下来的问题便是对选取的控件进行操作,包括: * `click()` 点击。点击一个控件,前提是这个控件的clickable属性为true * `longClick()` 长按。长按一个控件,前提是这个控件的longClickable属性为true * `setText()` 设置文本,用于编辑框控件设置文本。 * `scrollForward()`, `scrollBackward()` 滑动。滑动一个控件(列表等), 前提是这个控件的scrollable属性为true * `exists()` 判断控件是否存在 * `waitFor()` 等待控件出现 这些操作包含了绝大部分控件操作。根据这些我们可以很容易写出一个"刷屏"脚本(代码仅为示例,请不要在别人的群里测试,否则容易被踢): ```javascript while(true){ className("EditText").findOne().setText("刷屏..."); text("发送").findOne().click(); } ``` 上面这段代码也可以写成: ```javascript while(true){ className("EditText").setText("刷屏..."); text("发送").click(); } ``` 如果不加`findOne()`而直接进行操作,则选择器会找出**所有**符合条件的控件并操作。 另外一个比较常用的操作的滑动。滑动操作的第一步是找到需要滑动的控件,例如要滑动QQ消息列表则在悬浮窗布局层次分析中找到`AbsListView`,这个控件就是消息列表控件,如下图: 长按可查看控件信息,注意到其scrollable属性为true,并找出其id为"recent\_chat\_list",从而下滑QQ消息列表的代码为: ```javascript id("recent_chat_list").className("AbsListView").findOne().scrollForward(); ``` `scrollForward()`为向前滑,包括下滑和右滑。 选择器的入门教程暂且要这里,更多信息可以查看下面的文档和选择器进阶。 ## selector() * 返回 {UiSelector} 创建一个新的选择器。但一般情况不需要使用该函数,因为可以直接用相应条件的语句创建选择器。 由于历史遗留原因,本不应该这样设计(不应该让`id()`, `text()`等作为全局函数,而是应该用`By.id()`, `By.text()`),但为了后向兼容性只能保留这个设计。 这样的API设计会污染全局变量,后续可能会支持"去掉这些全局函数而使用By.\*\*\*"的选项。 ## UiSelector.algorithm(algorithm) **\[v4.1.0新增]** * `algorithm` {string} 搜索算法,可选的值有: * `DFS` 深度优先算法,选择器的默认算法 * `BFS` 广度优先算法 指定选择器的搜索算法。例如: ```javascript log(selector().text("文本").algorithm("BFS").find()); ``` 广度优先在控件所在层次较低时,或者布局的层次不多时,通常能更快找到控件。 ## UiSelector.text(str) * `str` {string} 控件文本 * 返回 {UiSelector} 返回选择器自身以便链式调用 为当前选择器附加控件"text等于字符串str"的筛选条件。 控件的text(文本)属性是文本控件上的显示的文字,例如微信左上角的"微信"文本。 ## UiSelector.textContains(str) * `str` {string} 要包含的字符串 为当前选择器附加控件"text需要包含字符串str"的筛选条件。 这是一个比较有用的条件,例如QQ动态页和微博发现页上方的"大家都在搜...."的控件可以用`textContains("大家都在搜").findOne()`来获取。 ## UiSelector.textStartsWith(prefix) * `prefix` {string} 前缀 为当前选择器附加控件"text需要以prefix开头"的筛选条件。 这也是一个比较有用的条件,例如要找出Auto.js脚本列表中名称以"QQ"开头的脚本的代码为`textStartsWith("QQ").find()`。 ## UiSelector.textEndsWith(suffix) * `suffix` {string} 后缀 为当前选择器附加控件"text需要以suffix结束"的筛选条件。 ## UiSelector.textMatches(reg) * `reg` {string} | {Regex} 要满足的正则表达式。 为当前选择器附加控件"text需要满足正则表达式reg"的条件。 有关正则表达式,可以查看[正则表达式 - 菜鸟教程](http://www.runoob.com/Stringp/Stringp-example.html)。 需要注意的是,如果正则表达式是字符串,则需要使用`\\`来表达`\`(也即Java正则表达式的形式),例如`textMatches("\\d+")`匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如`textMatches(/\d+/)`。但如果使用字符串的正则表达式则该字符串不能以"/"同时以"/"结束,也即不能写诸如`textMatches("/\\d+/")`的表达式,否则会被开头的"/"和结尾的"/"会被忽略。 ## UiSelector.desc(str) * `str` {string} 控件文本 * 返回 {UiSelector} 返回选择器自身以便链式调用 为当前选择器附加控件"desc等于字符串str"的筛选条件。 控件的desc(描述,全称为Content-Description)属性是对一个控件的描述,例如网易云音乐右上角的放大镜图标的描述为搜索。要查看一个控件的描述,同样地可以借助悬浮窗查看。 desc属性同样是定位控件的利器。 ## UiSelector.descContains(str) * `str` {string} 要包含的字符串 为当前选择器附加控件"desc需要包含字符串str"的筛选条件。 ## UiSelector.descStartsWith(prefix) * `prefix` {string} 前缀 为当前选择器附加控件"desc需要以prefix开头"的筛选条件。 ## UiSelector.descEndsWith(suffix) * `suffix` {string} 后缀 为当前选择器附加控件"desc需要以suffix结束"的筛选条件。 ## UiSelector.descMatches(reg) * `reg` {string} | {Regex} 要满足的正则表达式。 为当前选择器附加控件"desc需要满足正则表达式reg"的条件。 有关正则表达式,可以查看[正则表达式 - 菜鸟教程](http://www.runoob.com/Stringp/Stringp-example.html)。 需要注意的是,如果正则表达式是字符串,则需要使用`\\`来表达`\`(也即Java正则表达式的形式),例如`textMatches("\\d+")`匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如`textMatches(/\d+/)`。但如果使用字符串的正则表达式则该字符串不能以"/"同时以"/"结束,也即不能写诸如`textMatches("/\\d+/")`的表达式,否则会被开头的"/"和结尾的"/"会被忽略。 ## UiSelector.id(resId) * `resId` {string} 控件的id,以"包名:id/"开头,例如"com.tencent.mm:id/send\_btn"。**也可以不指定包名**,这时会以当前正在运行的应用的包名来补全id。例如id("send\_btn"),在界面想当于id("com.tencent.mobileqq:id/send\_btn")。 为当前选择器附加"id等于resId"的筛选条件。 控件的id属性通常是可以用来确定控件的唯一标识,如果一个控件有id,那么使用id来找到他是最好的方法。要查看屏幕上的控件的id,可以开启悬浮窗并使用界面工具,点击相应控件即可查看。若查看到的控件id为null, 表示该控件没有id。另外,在列表中会出现多个控件的id相同的情况。例如微信的联系人列表,每个头像的id都是一样的。此时不能用id来唯一确定控件。 在QQ界面经常会出现多个id为"name"的控件,在微信上则每个版本的id都会变化。对于这些软件而言比较难用id定位控件。 ## UiSelector.idContains(str) * `str` {string} id要包含的字符串 为当前选择器附加控件"id包含字符串str"的筛选条件。比较少用。 ## UiSelector.idStartsWith(prefix) * `prefix` {string} id前缀 为当前选择器附加"id需要以prefix开头"的筛选条件。比较少用。 ## UiSelector.idEndsWith(suffix) * `suffix` {string} id后缀 为当前选择器附加"id需要以suffix结束"的筛选条件。比较少用。 ## UiSelector.idMatches(reg) * `reg` {Regex | string} id要满足的正则表达式 附加id需要满足正则表达式。 需要注意的是,如果正则表达式是字符串,则需要使用`\\`来表达`\`(也即Java正则表达式的形式),例如`textMatches("\\d+")`匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如`textMatches(/\d+/)`。但如果使用字符串的正则表达式则该字符串不能以"/"同时以"/"结束,也即不能写诸如`textMatches("/\\d+/")`的表达式,否则会被开头的"/"和结尾的"/"会被忽略。 ```javascript idMatches("[a-zA-Z]+") ``` ## UiSelector.className(str) * `str` {string} 控件文本 * 返回 {UiSelector} 返回选择器自身以便链式调用 为当前选择器附加控件"className等于字符串str"的筛选条件。 控件的className(类名)表示一个控件的类别,例如文本控件的类名为android.widget.TextView。 如果一个控件的类名以"android.widget."开头,则可以省略这部分,例如文本控件可以直接用`className("TextView")`的选择器。 常见控件的类名如下: * `android.widget.TextView` 文本控件 * `android.widget.ImageView` 图片控件 * `android.widget.Button` 按钮控件 * `android.widget.EditText` 输入框控件 * `android.widget.AbsListView` 列表控件 * `android.widget.LinearLayout` 线性布局 * `android.widget.FrameLayout` 帧布局 * `android.widget.RelativeLayout` 相对布局 * `android.widget.RelativeLayout` 相对布局 * `android.support.v7.widget.RecyclerView` 通常也是列表控件 ## UiSelector.classNameContains(str) * `str` {string} 要包含的字符串 为当前选择器附加控件"className需要包含字符串str"的筛选条件。 ## UiSelector.classNameStartsWith(prefix) * `prefix` {string} 前缀 为当前选择器附加控件"className需要以prefix开头"的筛选条件。 ## UiSelector.classNameEndsWith(suffix) * `suffix` {string} 后缀 为当前选择器附加控件"className需要以suffix结束"的筛选条件。 ## UiSelector.classNameMatches(reg) * `reg` {string} | {Regex} 要满足的正则表达式。 为当前选择器附加控件"className需要满足正则表达式reg"的条件。 有关正则表达式,可以查看[正则表达式 - 菜鸟教程](http://www.runoob.com/Stringp/Stringp-example.html)。 需要注意的是,如果正则表达式是字符串,则需要使用`\\`来表达`\`(也即Java正则表达式的形式),例如`textMatches("\\d+")`匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如`textMatches(/\d+/)`。但如果使用字符串的正则表达式则该字符串不能以"/"同时以"/"结束,也即不能写诸如`textMatches("/\\d+/")`的表达式,否则会被开头的"/"和结尾的"/"会被忽略。 ## UiSelector.packageName(str) * `str` {string} 控件文本 * 返回 {UiSelector} 返回选择器自身以便链式调用 为当前选择器附加控件"packageName等于字符串str"的筛选条件。 控件的packageName表示控件所属界面的应用包名。例如Auto.js Pro的包名为"org.autojs.autojspro", 那么Auto.js Pro界面的控件的packageName为"org.autojs.autojspro"。 要查看一个应用的包名,可以用函数`app.getPackageName()`获取,例如`toast(app.getPackageName("微信"))`。 ## UiSelector.packageNameContains(str) * `str` {string} 要包含的字符串 为当前选择器附加控件"packageName需要包含字符串str"的筛选条件。 ## UiSelector.packageNameStartsWith(prefix) * `prefix` {string} 前缀 为当前选择器附加控件"packageName需要以prefix开头"的筛选条件。 ## UiSelector.packageNameEndsWith(suffix) * `suffix` {string} 后缀 为当前选择器附加控件"packageName需要以suffix结束"的筛选条件。 ## UiSelector.packageNameMatches(reg) * `reg` {string} | {Regex} 要满足的正则表达式。 为当前选择器附加控件"packageName需要满足正则表达式reg"的条件。 有关正则表达式,可以查看[正则表达式 - 菜鸟教程](http://www.runoob.com/Stringp/Stringp-example.html)。 ## UiSelector.bounds(left, top, right, bottom) * `left` {number} 控件左边缘与屏幕左边的距离 * `top` {number} 控件上边缘与屏幕上边的距离 * `right` {number} 控件右边缘与屏幕左边的距离 * `bottom` {number} 控件下边缘与屏幕上边的距离 一个控件的bounds属性为这个控件在屏幕上显示的范围。我们可以用这个范围来定位这个控件。尽管用这个方法定位控件对于静态页面十分准确,却无法兼容不同分辨率的设备;同时对于列表页面等动态页面无法达到效果,因此使用不推荐该选择器。 注意参数的这四个数字不能随意填写,必须精确的填写控件的四个边界才能找到该控件。例如,要点击QQ主界面的右上角加号,我们用布局分析查看该控件的属性,如下图: 可以看到bounds属性为(951, 67, 1080, 196),此时使用代码`bounds(951, 67, 1080, 196).clickable().click()`即可点击该控件。 ## UiSelector.boundsInside(left, top, right, bottom) * `left` {number} 范围左边缘与屏幕左边的距离 * `top` {number} 范围上边缘与屏幕上边的距离 * `right` {number} 范围右边缘与屏幕左边的距离 * `bottom` {number} 范围下边缘与屏幕上边的距离 为当前选择器附加控件"bounds需要在left, top, right, bottom构成的范围里面"的条件。 这个条件用于限制选择器在某一个区域选择控件。例如要在屏幕上半部分寻找文本控件TextView,代码为: ```javascript var w = className("TextView").boundsInside(0, 0, device.width, device.height / 2).findOne(); log(w.text()); ``` 其中我们使用了`device.width`来获取屏幕宽度,`device.height`来获取屏幕高度。 ## UiSelector.boundsContains(left, top, right, bottom) * `left` {number} 范围左边缘与屏幕左边的距离 * `top` {number} 范围上边缘与屏幕上边的距离 * `right` {number} 范围右边缘与屏幕左边的距离 * `bottom` {number} 范围下边缘与屏幕上边的距离 为当前选择器附加控件"bounds需要包含left, top, right, bottom构成的范围"的条件。 这个条件用于限制控件的范围必须包含所给定的范围。例如给定一个点(500, 300), 寻找在这个点上的可点击控件的代码为: ```javascript var w = boundsContains(500, 300, 500, 300).clickable().findOne(); w.click(); ``` ## UiSelector.drawingOrder(order) * `order` {number} 控件在父视图中的绘制顺序 为当前选择器附加控件"drawingOrder等于order"的条件。 drawingOrder为一个控件在父控件中的绘制顺序,通常可以用于区分同一层次的控件。 但该属性在Android 7.0以上才能使用。 ## UiSelector.clickable(\[b = true]) * `b` {Boolean} 表示控件是否可点击 为当前选择器附加控件是否可点击的条件。但并非所有clickable为false的控件都真的不能点击,这取决于控件的实现。对于自定义控件(例如显示类名为android.view.View的控件)很多的clickable属性都为false都却能点击。 需要注意的是,可以省略参数`b`而表示选择那些可以点击的控件,例如`className("ImageView").clickable()`表示可以点击的图片控件的条件,`className("ImageView").clickable(false)`表示不可点击的图片控件的条件。 ## UiSelector.longClickable(\[b = true]) * `b` {Boolean} 表示控件是否可长按 为当前选择器附加控件是否可长按的条件。 ## UiSelector.checkable(\[b = true]) * `b` {Boolean} 表示控件是否可勾选 为当前选择器附加控件是否可勾选的条件。勾选通常是对于勾选框而言的,例如图片多选时左上角通常有一个勾选框。 ## UiSelector.checked(\[b = true]) * `b` {Boolean} 表示控件是否已勾选 为当前选择器附加控件是否已勾选的条件。可用于筛选复选框、开关等已选中状态。 ## UiSelector.selected(\[b = true]) * `b` {Boolean} 表示控件是否被选 为当前选择器附加控件是否已选中的条件。被选中指的是,例如QQ聊天界面点击下方的"表情按钮"时,会出现自己收藏的表情,这时"表情按钮"便处于选中状态,其selected属性为true。 ## UiSelector.enabled(\[b = true]) * `b` {Boolean} 表示控件是否已启用 为当前选择器附加控件是否已启用的条件。大多数控件都是启用的状态(enabled为true),处于“禁用”状态通常是灰色并且不可点击。 ## UiSelector.scrollable(\[b = true]) * `b` {Boolean} 表示控件是否可滑动 为当前选择器附加控件是否可滑动的条件。滑动包括上下滑动和左右滑动。 可以用这个条件来寻找可滑动控件来滑动界面。例如滑动Auto.js的脚本列表的代码为: ```javascript className("android.support.v7.widget.RecyclerView").scrollable().findOne().scrollForward(); //或者classNameEndsWith("RecyclerView").scrollable().findOne().scrollForward(); ``` ## UiSelector.editable(\[b = true]) * `b` {Boolean} 表示控件是否可编辑 为当前选择器附加控件是否可编辑的条件。一般来说可编辑的控件为输入框(EditText),但不是所有的输入框(EditText)都可编辑。 ## UiSelector.multiLine(\[b = true]) * `b` {Boolean} 表示文本或输入框控件是否是多行显示的 为当前选择器附加控件是否文本或输入框控件是否是多行显示的条件。 ## UiSelector.focusable(\[b = true]) * `b` {Boolean} 表示控件是否可获取焦点 为当前选择器附加控件是否可获取焦点的条件。 ## UiSelector.focused(\[b = true]) * `b` {Boolean} 表示控件当前是否处于焦点状态 为当前选择器附加控件是否处于焦点状态的条件。 ## UiSelector.contextClickable(\[b = true]) * `b` {Boolean} 表示控件是否支持上下文点击 为当前选择器附加控件是否支持上下文点击(Context Click)的条件。 ## UiSelector.dismissable(\[b = true]) * `b` {Boolean} 表示控件是否可关闭 为当前选择器附加控件是否可关闭(dismiss)的条件。 ## UiSelector.contentInvalid(\[b = true]) * `b` {Boolean} 表示控件内容是否无效 为当前选择器附加控件内容有效性条件。 ## UiSelector.password(\[b = true]) * `b` {Boolean} 表示控件是否密码输入类型 为当前选择器附加控件是否为密码输入框的条件。 ## UiSelector.visibleToUser(\[b = true]) * `b` {Boolean} 表示控件是否对用户可见 为当前选择器附加控件是否可见的条件。通常用于排除不可见节点。 ## UiSelector.depth(d) * `d` {number} 控件层级深度 为当前选择器附加控件深度条件。可用于在复杂布局中缩小搜索范围。 ## UiSelector.indexInParent(index) * `index` {number} 控件在父控件中的索引 为当前选择器附加控件在父控件中的位置条件。 ## UiSelector.row(row) * `row` {number} 行索引 为当前选择器附加行索引条件。 ## UiSelector.rowCount(count) * `count` {number} 行数量 为当前选择器附加行数量条件。 ## UiSelector.rowSpan(span) * `span` {number} 跨行数量 为当前选择器附加跨行数量条件。 ## UiSelector.column(column) * `column` {number} 列索引 为当前选择器附加列索引条件。 ## UiSelector.columnCount(count) * `count` {number} 列数量 为当前选择器附加列数量条件。 ## UiSelector.columnSpan(span) * `span` {number} 跨列数量 为当前选择器附加跨列数量条件。 ## UiSelector.findOne() * 返回 {[UiObject](uiobject.html#uiobject)} 根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件。如果找不到控件,当屏幕内容发生变化时会重新寻找,直至找到。 需要注意的是,如果屏幕上一直没有出现所描述的控件,则该函数会阻塞,直至所描述的控件出现为止。因此此函数不会返回`null`。 该函数本来应该命名为`untilFindOne()`,但由于历史遗留原因已经无法修改。如果想要只在屏幕上搜索一次而不是一直搜索,请使用`findOnce()`。 另外,如果屏幕上有多个满足条件的控件,`findOne()`采用深度优先搜索(DFS),会返回该搜索算法找到的第一个控件。注意控件找到的顺序有时会起到作用。 ## UiSelector.findOne(timeout) * `timeout` {number} 搜索的超时时间,单位毫秒 * 返回 {[UiObject](uiobject.html#uiobject)} 根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件;如果在timeout毫秒的时间内没有找到符合条件的控件,则终止搜索并返回`null`。 该函数类似于不加参数的`findOne()`,只不过加上了时间限制。 示例: ```javascript //启动Auto.js launchApp("Auto.js"); //在6秒内找出日志图标的控件 var w = id("action_log").findOne(6000); //如果找到控件则点击 if(w != null){ w.click(); }else{ //否则提示没有找到 toast("没有找到日志图标"); } ``` ## UiSelector.findOnce() * 返回 {[UiObject](uiobject.html#uiobject)} 根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,如果找到符合条件的控件则返回该控件;否则返回`null`。 ## UiSelector.findOnce(i) * `i` {number} 索引 根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,并返回第 i + 1 个符合条件的控件;如果没有找到符合条件的控件,或者符合条件的控件个数 < i, 则返回`null`。 注意这里的控件次序,是搜索算法深度优先搜索(DFS)决定的。 ## UiSelector.find() * 返回 {[UiCollection](uiobject.html#uicollection)} 根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,找到所有满足条件的控件集合并返回。这个搜索只进行一次,并不保证一定会找到,因而会出现返回的控件集合为空的情况。 不同于`findOne()`或者`findOnce()`只找到一个控件并返回一个控件,`find()`函数会找出所有满足条件的控件并返回一个控件集合。之后可以对控件集合进行操作。 可以通过empty()函数判断找到的是否为空。例如: ```javascript var c = className("AbsListView").find(); if(!c.empty()){ toast("找到啦"); }else{ toast("没找到╭(╯^╰)╮"); } ``` ## UiSelector.untilFind() * 返回 {[UiCollection](uiobject.html#uicollection)} 根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到找到至少一个满足条件的控件为止,并返回所有满足条件的控件集合。 该函数与`find()`函数的区别在于,该函数永远不会返回空集合;但是,如果屏幕上一直没有出现满足条件的控件,则该函数会保持阻塞。 ## UiSelector.exists() * 返回 {Boolean} 判断屏幕上是否存在控件符合选择器所确定的条件。例如要判断某个文本出现就执行某个动作,可以用: ```javascript if(text("某个文本").exists()){ //要支持的动作 } ``` ## UiSelector.waitFor() 等待屏幕上出现符合条件的控件;在满足该条件的控件出现之前,该函数会一直保持阻塞。 例如要等待包含"哈哈哈"的文本控件出现的代码为: ```javascript textContains("哈哈哈").waitFor(); ``` ## UiSelector.filter(f) * `f` {Function} 过滤函数,参数为 UiObject,返回值为 boolean 为当前选择器附加自定义的过滤条件。 例如,要找出屏幕上所有文本长度为10的文本控件的代码为: ```javascript var uc = className("TextView").filter(function(w){ return w.text().length == 10; }); ``` --- --- url: 'https://www.wuyunai.com/docs/v8/automator/uiobject.html' description: >- Auto.js Pro v8 UiObject 控件操作完整指南 - 学习如何获取控件属性、对控件进行点击、长按等操作。包括 UiCollection 控件集合的使用方法。 --- # 控件和控件集合 ## UiObject UiObject 表示一个控件,可以通过这个对象获取到控件的属性,也可以对控件进行点击、长按等操作。 获取一个 UiObject 通常通过选择器的`findOne()`, `findOnce()`等函数,也可以通过 UiCollection 来获取,或者通过`UiObject.child()`, `UiObject.parent()`等函数来获取一个控件的子控件或父控件。 ### UiObject.click() * 返回 {boolean} 点击该控件,并返回是否点击成功。 如果该函数返回 false,可能是该控件不可点击(clickable 为 false),当前界面无法响应该点击等(这种情况下可以使用`clickCenter()`代替)。 ### UiObject.clickCenter() **\[[Pro 8.8.17 新增](//www.wuyunai.com/docs)]** * 返回 {boolean} 使用坐标点击该控件的中点,相当于`click(uiObj.bounds().centerX(), uiObject.bounds().centerY())`。 返回是否点击成功。 ### UiObject.longClick() * 返回 {boolean} 长按该控件,并返回是否点击成功。 如果该函数返回 false,可能是该控件不可点击(longClickable 为 false),当前界面无法响应该点击等。 ### UiObject.setText(text) * `text` {string} 文本 * 返回 {boolean} 设置输入框控件的文本内容,并返回是否设置成功。 该函数只对可编辑的输入框(editable 为 true)有效。 ### UiObject.copy() * 返回 {boolean} 对输入框文本的选中内容进行复制,并返回是否操作成功。 该函数只能用于输入框控件,并且当前输入框控件有选中的文本。可以通过`setSelection()`函数来设置输入框选中的内容。 ```javascript var et = className("EditText").findOne(); //选中前两个字 et.setSelection(0, 2); //对选中内容进行复制 if (et.copy()) { toast("复制成功"); } else { toast("复制失败"); } ``` ### UiObject.cut() 对输入框文本的选中内容进行剪切,并返回是否操作成功。 该函数只能用于输入框控件,并且当前输入框控件有选中的文本。可以通过`setSelection()`函数来设置输入框选中的内容。 ### UiObject.paste() * 返回 {boolean} 对输入框控件进行粘贴操作,把剪贴板内容粘贴到输入框中,并返回是否操作成功。 ```javascript //设置剪贴板内容为“你好” setClip("你好"); var et = className("EditText").findOne(); et.paste(); ``` ### UiObject.setSelection(start, end) * `start` {number} 选中内容起始位置 * `end` {number} 选中内容结束位置(不包括) * 返回 {boolean} 对输入框控件设置选中的文字内容,并返回是否操作成功。 索引是从 0 开始计算的;并且,选中内容不包含 end 位置的字符。例如,如果一个输入框内容为"123456789",要选中"4567"的文字的代码为`et.setSelection(3, 7)`。 该函数也可以用来设置光标位置,只要参数的 end 等于 start,即可把输入框光标设置在 start 的位置。例如`et.setSelection(1, 1)`会把光标设置在第一个字符的后面。 ### UiObject.scrollForward() * 返回 {boolean} 对控件执行向前滑动的操作,并返回是否操作成功。 向前滑动包括了向右和向下滑动。如果一个控件既可以向右滑动和向下滑动,那么执行`scrollForward()`的行为是未知的(这是因为 Android 文档没有指出这一点,同时也没有充分的测试可供参考)。 ### UiObject.scrollBackward() * 返回 {boolean} 对控件执行向后滑动的操作,并返回是否操作成功。 向后滑动包括了向右和向下滑动。如果一个控件既可以向右滑动和向下滑动,那么执行`scrollForward()`的行为是未知的(这是因为 Android 文档没有指出这一点,同时也没有充分的测试可供参考)。 ### UiObject.select() * 返回 {boolean} 对控件执行"选中"操作,并返回是否操作成功。"选中"和`isSelected()`的属性相关,但该操作十分少用。 ### UiObject.collapse() * 返回 {boolean} 对控件执行折叠操作,并返回是否操作成功。 ### UiObject.expand() * 返回 {boolean} 对控件执行操作,并返回是否操作成功。 ### UiObject.show() 执行显示操作,并返回是否全部操作成功。 ### UiObject.scrollUp() * 返回 {boolean} 执行向上滑的操作,并返回是否全部操作成功。(虽然有些控件看起来可以滑动,但调用`scrollUp`可能无效,可以用`scrollBackward`代替) ### UiObject.scrollDown() * 返回 {boolean} 执行向下滑的操作,并返回是否全部操作成功。(虽然有些控件看起来可以滑动,但调用`scrollDown`可能无效,可以用`scrollForward`代替) ### UiObject.scrollLeft() * 返回 {boolean} 执行向左滑的操作,并返回是否全部操作成功。(虽然有些控件看起来可以滑动,但调用`scrollLeft`可能无效,可以用`scrollBackward`代替) ### UiObject.scrollRight() * 返回 {boolean} 执行向右滑的操作,并返回是否全部操作成功。(虽然有些控件看起来可以滑动,但调用`scrollRight`可能无效,可以用`scrollForward`代替) ### UiObject.scrollTo(row, column) * `row` {number} 目标行索引 * `column` {number} 目标列索引 * 返回 {boolean} 将可滚动控件滚动到指定行列位置。是否生效取决于控件本身是否支持该操作。 ### UiObject.setProgress(progress) * `progress` {number} 进度值 * 返回 {boolean} 设置控件进度值(例如进度条、滑块等)。仅对支持进度语义的控件有效。 ### UiObject.contextClick() * 返回 {boolean} 执行上下文点击操作(Context Click)。常见于触发上下文菜单或特殊点击行为。 ### UiObject.dismiss() * 返回 {boolean} 尝试关闭当前控件(如通知、可关闭弹层等),并返回是否成功。 ### UiObject.clearFocus() * 返回 {boolean} 清除控件焦点,并返回是否成功。 ### UiObject.clearAccessibilityFocus() * 返回 {boolean} 清除控件的无障碍焦点,并返回是否成功。 ### UiObject.children() * 返回 {[UiCollection](#uicollection)} 返回该控件的所有子控件组成的控件集合。可以用于遍历一个控件的子控件,例如: ```javascript className("AbsListView") .findOne() .children() .forEach(function (child) { log(child.className()); }); ``` ### UiObject.childCount() * 返回 {number} 返回子控件数目。 ### UiObject.child(i) * `i` {number} 子控件索引 * 返回 {UiObject} 返回第 i+1 个子控件。如果 i>=控件数目或者小于 0,则抛出异常。 需要注意的是,由于布局捕捉的问题,该函数可能返回`null`,也就是可能获取不到某个子控件。 遍历子控件的示例: ```javascript var list = className("AbsListView").findOne(); for (var i = 0; i < list.childCount(); i++) { var child = list.child(i); log(child.className()); } ``` ### UiObject.parent() * 返回 {UiObject} 返回该控件的父控件。如果该控件没有父控件,返回`null`。 ### UiObject.bounds() * 返回 {[Rect](api.html#rect)} 返回控件在屏幕上的范围,其值是一个[Rect](api.html#rect)对象。 示例: ```javascript var b = text("Auto.js").findOne().bounds(); toast("控件在屏幕上的范围为" + b); ``` 如果一个控件本身无法通过`click()`点击,那么我们可以利用`bounds()`函数获取其坐标,再利用坐标点击。例如: ```javascript var b = desc("打开侧拉菜单").findOne().bounds(); click(b.centerX(), b.centerY()); //如果使用root权限,则用 Tap(b.centerX(), b.centerY()); ``` ### UiObject.boundsInParent() * 返回 {[Rect](api.html#rect)} 返回控件在父控件中的范围,其值是一个 [Rect](api.html#rect) 对象。 ### UiObject.drawingOrder() * 返回 {number} 返回控件在父控件中的绘制次序。该函数在安卓 7.0 及以上才有效,7.0 以下版本调用会返回 0。 ### UiObject.id() * 返回 {string} 获取控件的 id,如果一个控件没有 id,则返回`null`。 ### UiObject.text() * 返回 {string} 获取控件的文本,如果控件没有文本,返回`""`。 ### UiObject.findByText(str) * `str` {string} 文本 * 返回 {[UiCollection](#uicollection)} 根据文本 text 在子控件中递归地寻找并返回文本或描述(desc)**包含**这段文本 str 的控件,返回它们组成的集合。 该函数会在当前控件的子控件,孙控件,曾孙控件...中搜索 text 或 desc 包含 str 的控件,并返回它们组合的集合。 ### UiObject.findOne(selector) * `selector` {[UiSelector](selector.html)} * 返回 {[UiObject](#uiobject)} 根据选择器 selector 在该控件的子控件、孙控件...中搜索符合该选择器条件的控件,并返回找到的第一个控件;如果没有找到符合条件的控件则返回`null`。 例如,对于酷安动态列表,我们可以遍历他的子控件(每个动态列表项),并在每个子控件中依次寻找点赞数量和图标,对于点赞数量小于 10 的点赞: ```javascript //找出动态列表 var list = id("recycler_view").findOne(); //遍历动态 list.children().forEach(function (child) { //找出点赞图标 var like = child.findOne(id("feed_action_view_like")); //找出点赞数量 var likeCount = child.findOne(id("text_view")); //如果这两个控件没有找到就不继续了 if (like == null || likeCount == null) { return; } //判断点赞数量是否小于10 if (parseInt(likeCount.text()) < 10) { //点赞 like.click(); } }); ``` ### UiObject.find(selector) * `selector` {[UiSelector](selector.html)} * 返回 {[UiCollection](#uicollection)} 根据选择器 selector 在该控件的子控件、孙控件...中搜索符合该选择器条件的控件,并返回它们组合的集合。 ## UiCollection UiCollection, 控件集合, 通过选择器的`find()`, `untilFind()`方法返回的对象。 UiCollection"继承"于数组,实际上是一个 UiObject 的数组,因此可以使用数组的函数和属性,例如使用 length 属性获取 UiCollection 的大小,使用 forEach 函数来遍历 UiCollection。 例如,采用 forEach 遍历屏幕上所有的文本控件并打印出文本内容的代码为: ```javascript console.show(); className("TextView") .find() .forEach(function (tv) { if (tv.text() != "") { log(tv.text()); } }); ``` 也可以使用传统的数组遍历方式: ```javascript console.show(); var uc = className("TextView").find(); for (var i = 0; i < uc.length; i++) { var tv = uc[i]; if (tv.text() != "") { log(tv.text()); } } ``` UiCollection 的每一个元素都是 UiObject,我们可以取出他的元素进行操作,例如取出第一个 UiObject 并点击的代码为`ui[0].click()`。如果想要对该集合的所有元素进行操作,可以直接在集合上调用相应的函数,例如`uc.click()`,该代码会对集合上所有 UiObject 执行点击操作并返回是否全部点击成功。 因此,UiCollection 具有所有 UiObject 对控件操作的函数,包括`click()`, `longClick()`, `scrollForward()`等等,不再赘述。 ### UiCollection.size() * 返回 {number} 返回集合中的控件数。 历史遗留函数,相当于属性 length。 ### UiCollection.get(i) * `i` {number} 索引 * 返回 {[UiObject](#uiobject)} 返回集合中第 i+1 个控件(UiObject)。 历史遗留函数,建议直接使用数组下标的方式访问元素。 ### UiCollection.each(func) * `func` {Function} 遍历函数,参数为 UiObject。 遍历集合。 历史遗留函数,相当于`forEach`。参考[forEach](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)。 ### UiCollection.empty() * 返回 {boolean} 返回控件集合是否为空。 ### UiCollection.nonEmpty() * 返回 {boolean} 返回控件集合是否非空。 ### UiCollection.find(selector) * `selector` {[UiSelector](selector.html)} * 返回 {[UiCollection](#uicollection)} 根据 selector 所确定的条件在该控件集合的控件、子控件、孙控件...中找到所有符合条件的控件并返回找到的控件集合。 注意这会递归地遍历控件集合里所有的控件以及他们的子控件。和数组的`filter`函数不同。 例如: ```javascript var names = id("name").find(); //在集合 var clickableNames = names.find(clickable()); ``` ### UiCollection.findOne(selector) * `selector` {[UiSelector](selector.html)} * 返回 {[UiObject](#uiobject)} 根据选择器 selector 在该控件集合的控件的子控件、孙控件...中搜索符合该选择器条件的控件,并返回找到的第一个控件;如果没有找到符合条件的控件则返回`null`。 --- --- url: 'https://www.wuyunai.com/docs/v8/base64.html' --- # base64 提供基本的Base64转换函数。 ## $base64.encode(str, encoding = 'utf-8') \*\* \[[Pro 7.0.4新增](//www.wuyunai.com/docs)] \*\* * `str` {string} 要编码的字符串 * `encoding` {string} 可选,字符编码 将字符串str使用Base64编码并返回编码后的字符串。 ```javascript console.log($base64.encode('test')); // 打印dGVzdA== ``` ## $base64.decode(str, encoding = 'utf-8') \*\* \[[Pro 7.0.4新增](//www.wuyunai.com/docs)] \*\* * `str` {string} 要解码的字符串 * `encoding` {string} 可选,字符编码 将字符串str使用Base64解码并返回解码后的字符串。 ```javascript console.log($base64.decode('dGVzdA==')); // 打印test ``` --- --- url: 'https://www.wuyunai.com/docs/v8/canvas.html' --- # canvas - 画布 canvas提供了使用画布进行2D画图的支持,可用于简单的小游戏开发或者图片编辑。使用canvas可以轻松地在一张图片或一个界面上绘制各种线与图形。 ::: tip Canvas模块本质上是将 [Android Canvas](https://developer.android.google.cn/reference/android/graphics/Canvas) 进行包装后的结果。本模块的部分用法和文档暂时缺失,但可以在 Android 文档中找到。请参阅 [Android Canvas](https://developer.android.google.cn/reference/android/graphics/Canvas)、[Android Paint](https://developer.android.google.cn/reference/android/graphics/Paint) 与 [Android Path](https://developer.android.google.cn/reference/android/graphics/Path) 了解更多细节。 ::: canvas的坐标系为平面直角坐标系,以控件左上角为原点,控件上边沿为x轴正方向,控件左边沿为y轴正方向。例如分辨率为1920\*1080的屏幕上,canvas控件覆盖全屏,画一条从屏幕左上角到屏幕右下角的线段为: ```javascript canvas.drawLine(0, 0, 1080, 1920, paint); ``` canvas的绘制依赖于画笔Paint, 通过设置画笔的粗细、颜色、填充等可以改变绘制出来的图形。例如绘制一个红色实心正方形为: ```javascript let paint = new Paint(); // 设置画笔为填充,则绘制出来的图形都是实心的 paint.setStyle(Paint.Style.FILL); // 设置画笔颜色为红色 paint.setColor(colors.RED); // 绘制一个从坐标(0, 0)到坐标(100, 100)的正方形 canvas.drawRect(0, 0, 100, 100, paint); ``` 如果要绘制正方形的边框,则通过设置画笔的Style来实现: ```javascript let paint = new Paint(); // 设置画笔为描边,则绘制出来的图形都是轮廓 paint.setStyle(Paint.Style.STROKE); // 设置画笔颜色为红色 paint.setColor(colors.RED); // 绘制一个从坐标(0, 0)到坐标(100, 100)的正方形 canvas.drawRect(0, 0, 100, 100, paint); ``` 结合画笔,canvas可以绘制基本图形、图片等。 ## 常用方法索引 绘制颜色: * 根据R、G、B分量绘制颜色:[canvas.drawRGB(r, g, b)](#canvas-drawrgb-r-g-b) * 根据A、R、G、B分量绘制颜色:[canvas.drawARGB(r, g, b)](#canvas-draw-argba-r-g-b) * 根据颜色值绘制颜色:[canvas.drawColor(color)](#canvas-drawcolor-color) * 根据颜色值与混合模式绘制颜色:[canvas.drawColor(color, mode)](#canvas-drawcolor-color-mode) 绘制画笔: * [canvas.drawPaint(paint)](#canvas-drawpaint-paint) 绘制几何图形: * 绘制一个点:[canvas.drawPoint(x, y, paint)](#canvas-drawpoint-x-y-paint) * 绘制多个点:[canvas.drawPoints(pts, paint)](#canvas-drawpoints-pts-paint) * 绘制一条线:[canvas.drawLine(startX, startY, stopX, stopY, paint)](#canvas-drawlinestart-x-starty-stopx-stopy-paint) * 绘制多条线:[canvas.drawLines(pts, paint)](#canvas-drawlines-pts-paint) * 绘制矩形:[canvas.drawRect(r, paint)](#canvas-drawrect-r-paint) * 绘制椭圆:[canvas.drawOval(oval, paint)](#canvas-drawoval-oval-paint) * 绘制圆:[canvas.drawCircle(cx, cy, radius, paint)](#canvas-drawcircle-cx-cy-radius-paint) * 绘制弧:[canvas.drawArc(oval, startAngle, sweepAngle, useCenter, paint)](#canvas-drawarc-oval-startangle-sweepangle-usecenter-paint) * 绘制圆角矩形:[canvas.drawRoundRect(rect, rx, ry, paint)](#canvas-drawroundrect-rect-rx-ry-paint) * 绘制路径:[canvas.drawPath(path, paint)](#canvas-drawpath-path-paint) 绘制文字: * 沿直线绘制文字:[canvas.drawText(text, x, y, paint)](#canvas-drawtext-text-x-y-paint) * 沿路径绘制文字:[canvas.drawTextOnPath(text, path, hOffset, vOffset, paint)](#canvas-drawtextonpath-text-path-hoffset-voffset-paint) 绘制图片: * 绘制位图:[canvas.drawBitmap(bitmap, left, top, paint)](#canvas-drawbitmap-bitmap-left-top-paint) * 绘制图章:[canvas.drawPicture(picture)](#canvas-drawpicture-picture) 获取画布大小: * 获取宽度:[canvas.getWidth()](#canvas-getwidth) * 获取高度:[canvas.getHeight()](#canvas-getheight) 矩阵变换: * 平移:[canvas.translate(dx, dy)](#canvas-translate-dx-dy) * 缩放:[canvas.scale(sx, sy\[, px, py\])](#canvas-scale-sx-sy-px-py) * 旋转:[canvas.rotate(degrees\[, px, py\])](#canvas-rotate-degrees-px-py) * 切变变换:[canvas.skew(sx, sy)](#canvas-skew-sx-sy) ## canvas.getWidth() * 返回 {number} 返回画布当前图层的宽度。 ## canvas.getHeight() * 返回 {number} 返回画布当前图层的高度。 ## canvas.drawRGB(r, g, b) * `r` {number} 红色通道值 * `g` {number} 绿色通道值 * `b` {number} 蓝色通道值 将整个可绘制区域填充为r、g、b指定的颜色。相当于 `canvas.drawColor(colors.rgb(r, g, b))`。 ## canvas.drawARGB(a, r, g, b) * `a` {number} 透明通道值 * `r` {number} 红色通道值 * `g` {number} 绿色通道值 * `b` {number} 蓝色通道值 将整个可绘制区域填充为a、r、g、b指定的颜色。相当于 `canvas.drawColor(colors.argb(a, r, g, b))`。 ## canvas.drawColor(color) * `color` {number} 颜色值 将整个可绘制区域填充为color指定的颜色。 ## canvas.drawColor(color, mode) * `color` {number} 颜色值 * `mode` {PorterDuff.Mode} 混合模式 将整个可绘制区域按mode指定的混合模式填充为color指定的颜色。 ## canvas.drawPaint(paint) * `paint` {Paint} 画笔 将整个可绘制区域用paint指定的画笔填充。相当于绘制一个无限大的矩形,但是更快。通过该方法可以绘制一个指定的着色器的图案。 ## canvas.drawPoint(x, y, paint) * `x` {number} x坐标 * `y` {number} y坐标 * `paint` {Paint} 画笔 在可绘制区域绘制由坐标(x, y)指定的点。 点的形状由画笔的线帽决定(参见paint.setStrokeCap(cap))。 点的大小由画笔的宽度决定(参见paint.setStrokeWidth(width))。 > 如果画笔宽度为0,则也会绘制1个像素(若抗锯齿启用则绘制至多4个像素)。 相当于 `canvas.drawPoints([x, y], paint)`。 ## canvas.drawPoints(pts, paint) * `pts` {number\[]} 点坐标数组 \[x0, y0, x1, y1, x2, y2, ...] * `paint` {Paint} 画笔 在可绘制区域绘制由坐标数组指定的多个点。 ## canvas.drawLine(startX, startY, stopX, stopY, paint) * `startX` {number} 起点x坐标 * `startY` {number} 起点y坐标 * `endX` {number} 终点x坐标 * `endY` {number} 终点y坐标 * `paint` {Paint} 画笔 在可绘制区域绘制由起点坐标(startX, startY)和终点坐标(endX, endY)指定的线。 绘制时会忽略画笔的样式(Style)。也就是说,即使样式设为“仅填充(FILL)”也会绘制。 退化为点的线(长度为0)不会被绘制。 ## canvas.drawLines(pts, paint) * `pts` {number\[]} 点坐标数组 \[x0, y0, x1, y1, x2, y2, ...] * `paint` {Paint} 画笔 在可绘制区域绘制由坐标数组指定的点两两连成的一系列线。 每条线需要点坐标数组中的四个连续的值,因此要绘制一条线,数组必须至少包含四个值。 相当于 `canvas.drawLine(pts[0], pts[1], pts[2], pts[3], paint)` 然后 `canvas.drawLine(pts[4], pts[5], pts[6], pts[7], paint)`,之后以此类推。 绘制时会忽略画笔的样式(Style)。也就是说,即使样式设为“仅填充(FILL)”也会绘制。 ## canvas.drawRect(r, paint) * `r` {Rect|RectF} 矩形边界 * `paint` {Paint} 画笔 在可绘制区域绘制由矩形边界r指定的矩形。 绘制时画笔的样式(Style)决定了是否绘制矩形界线和填充矩形。 相当于 `canvas.drawRect(r.left, r.top, r.right, r.bottom, paint)`。 ## canvas.drawRect(left, top, right, bottom, paint) * `left` {number} 矩形左边界x坐标 * `top` {number} 矩形上边界y坐标 * `right` {number} 矩形右边界x坐标 * `bottom` {number} 矩形下边界y坐标 * `paint` {Paint} 画笔 在可绘制区域绘制由矩形边界(left, top) - (right, bottom)指定的矩形。 绘制时画笔的样式(Style)决定了是否绘制矩形界线和填充矩形。 ## canvas.drawOval(oval, paint) * `oval` {RectF} 椭圆的外接矩形的边界 * `paint` {Paint} 画笔 在可绘制区域绘制由矩形边界oval指定的椭圆。 绘制时画笔的样式(Style)决定了是否绘制椭圆界线和填充椭圆。 相当于 `canvas.drawOval(oval.left, oval.top, oval.right, oval.bottom, paint)`。 ## canvas.drawOval(left, top, right, bottom, paint) * `left` {number} 椭圆外接矩形的左边界x坐标 * `top` {number} 椭圆外接矩形的上边界y坐标 * `right` {number} 椭圆外接矩形的右边界x坐标 * `bottom` {number} 椭圆外接矩形的下边界y坐标 * `paint` {Paint} 画笔 在可绘制区域绘制由矩形边界(left, top) - (right, bottom)指定的椭圆。 绘制时画笔的样式(Style)决定了是否绘制椭圆界线和填充椭圆。 ## canvas.drawCircle(cx, cy, radius, paint) * `cx` {number} 圆心的x坐标 * `cy` {number} 圆心的y坐标 * `radius` {number} 圆的半径 * `paint` {Paint} 画笔 在可绘制区域绘制由圆心(cx, cy)与半径radius指定的圆。如果半径小于等于0则不会绘制。 绘制时画笔的样式(Style)决定了是否绘制圆的界线和填充圆。 ## canvas.drawArc(oval, startAngle, sweepAngle, useCenter, paint) * `oval` {RectF} 圆弧对应的椭圆外接矩形的边界 * `startAngle` {number} 圆弧的起始角(以角度计算) * `sweepAngle` {number} 圆弧所对的圆心角(顺时针方向,以角度计算) * `useCenter` {boolean} 绘制路径时是否连接圆弧对应的椭圆圆心 * `paint` {Paint} 画笔 在可绘制区域绘制指定的圆弧。 这一圆弧是由矩形边界oval指定的椭圆的一部分,起始于startAngle指定的角度,并以顺时针方向扫过sweepAngle指定的角度。 如果起始角startAngle为负数或大于等于360,则绘制时起始角将对360取模。 圆弧是沿顺时针方向绘制的。起始角0°相当于从直角坐标系的0°开始绘制(即从3点钟方向沿顺时针方向绘制)。 如果圆心角sweepAngle大于等于360,则此函数将绘制一个完整的椭圆。 注意,这与path.arcTo方法不同,path.arcTo会将圆心角对360取模。 如果圆心角为负数则会将圆心角对360取模。 如果指定useCenter为true,在描边和填充时将会连接圆弧对应的椭圆圆心,最终将绘制出一个扇形。 相当于 `canvas.drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter, paint)`。 ## canvas.drawArc(left, top, right, bottom, startAngle, sweepAngle, useCenter, paint) * `left` {number} 椭圆外接矩形的左边界x坐标 * `top` {number} 椭圆外接矩形的上边界y坐标 * `right` {number} 椭圆外接矩形的右边界x坐标 * `bottom` {number} 椭圆外接矩形的下边界y坐标 * `startAngle` {number} 圆弧的起始角(以角度计算) * `sweepAngle` {number} 圆弧所对的圆心角(顺时针方向,以角度计算) * `useCenter` {boolean} 绘制路径时是否连接圆弧对应的椭圆圆心 * `paint` {Paint} 画笔 在可绘制区域绘制指定的圆弧。这一圆弧是由矩形边界(left, top) - (right, bottom)指定的椭圆的一部分,其余参数请参见 [canvas.drawArc(oval, startAngle, sweepAngle, useCenter, paint)](#canvas-drawarcoval-startangle-sweepangle-usecenter-paint)。 ## canvas.drawRoundRect(rect, rx, ry, paint) * `rect` {RectF} 矩形边界 * `rx` {number} 圆角在x轴上的半径 * `ry` {number} 圆角在y轴上的半径 * `paint` {Paint} 画笔 在可绘制区域绘制由矩形边界rect、圆角半径rx、ry指定的圆角矩形。 绘制时画笔的样式(Style)决定了是否绘制圆角矩形界线和填充圆角矩形。 相当于 `canvas.drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint)`。 ## canvas.drawRoundRect(left, top, right, bottom, rx, ry, paint) * `left` {number} 圆角矩形左边界x坐标 * `top` {number} 圆角矩形上边界y坐标 * `right` {number} 圆角矩形右边界x坐标 * `bottom` {number} 圆角矩形下边界y坐标 * `rx` {number} 圆角在x轴上的半径 * `ry` {number} 圆角在y轴上的半径 * `paint` {Paint} 画笔 在可绘制区域绘制由矩形边界(left, top) - (right - bottom)、圆角半径rx、ry指定的圆角矩形。 绘制时画笔的样式(Style)决定了是否绘制圆角矩形界线和填充圆角矩形。 ## canvas.drawPath(path, paint) * `path` {Path} 路径 * `paint` {Paint} 画笔 在可绘制区域绘制指定的路径。 绘制时画笔的样式(Style)决定了是否描边路径和填充路径。 ## canvas.drawBitmap(bitmap, left, top, paint) * `bitmap` {Bitmap} 位图 * `left` {number} x坐标 * `top` {number} y坐标 * `paint` {Paint} 画笔 在可绘制区域绘制指定的位图,使它的左上角位于(left, top)指定的坐标,并应用画布的变换矩阵。 ::: tip 如果画笔指定了遮罩过滤器并且该过滤器范围比位图大(超出位图的长/宽)(如 BlurMaskFilter),位图将会像被应用了CLAMP铺展模式的着色器一样绘制。因此超出位图原始范围的颜色将会重复使用边缘的颜色。 ::: 如果位图bitmap与画布canvas的密度不同,在绘制时位图将会被缩放至与画布相同密度再绘制。 ## canvas.drawPicture(picture) * `picture` {Picture} 图章 保存画布的变换矩阵与可绘制区域范围,在可绘制区域绘制指定的图章,随后恢复画布的变换矩阵与可绘制区域范围。 该过程与 `picture.draw(canvas)` 不同,因为后者不会进行保存与恢复的操作。 ::: tip 绘制图章将会强制图章退出录制模式(即调用 `picture.endRecording()`)以准备之后的绘制。 ::: ## canvas.drawText(text, x, y, paint) * `text` {string} 文字 * `x` {number} x坐标 * `y` {number} y坐标 * `paint` {Paint} 画笔 在可绘制区域绘制指定的文字,使文字的原点位于(x, y)。文字的起始位置取决于paint的对齐选项。 文字的样式取决于paint的相关设置。 ## canvas.drawTextOnPath(text, path, hOffset, vOffset, paint) * `text` {string} 文字 * `path` {Path} 路径 * `hOffset` {number} 在与路径平行方向的偏移,取正为沿路径方向平移 * `vOffset` {number} 在与路径垂直方向的偏移,取正为向文字的下方平移 * `paint` {Paint} 画笔 在可绘制区域沿着指定的路径绘制指定的文字。文字的起始位置取决于paint的对齐选项。 文字的样式取决于paint的相关设置。 ## canvas.translate(dx, dy) * `dx` {number} 向x轴正方向平移的距离,负数表示反方向平移 * `dy` {number} 向y轴正方向平移的距离,负数表示反方向平移 将当前的变换矩阵右乘指定的平移变换矩阵。相当于将坐标系平移指定距离。 ## canvas.scale(sx, sy\[, px, py]) * `sx` {number} 在x轴上缩放的倍数,负数表示沿x轴翻转 * `sy` {number} 在y轴上缩放的倍数,负数表示沿y轴翻转 * `px` {number} 缩放中心的x坐标,默认为0 * `py` {number} 缩放中心的y坐标,默认为0 将当前的变换矩阵右乘指定的缩放变换矩阵。相当于以(px, py)为中心将坐标系缩放指定的倍数。 倍数大于 1 表示放大,小于 1 表示缩小。 ## canvas.rotate(degrees\[, px, py]) * `degrees` {number} 旋转的角度(以角度计算) * `px` {number} 旋转中心的x坐标,默认为0 * `py` {number} 旋转中心的y坐标,默认为0 将当前的变换矩阵右乘指定的旋转变换矩阵。相当于以(px, py)为中心将坐标系旋转指定的角度。 ## canvas.skew(sx, sy) * `sx` {number} x轴方向的切变系数 * `sy` {number} y轴方向的切变系数 将当前的变换矩阵右乘指定的切变变换矩阵。 --- --- url: 'https://www.wuyunai.com/docs/v8/colors.html' --- > Stability: 2 - Stable 在Auto.js有两种方式表示一个颜色。 一种是使用一个字符串"#AARRGGBB"或"#RRGGBB",其中 AA 是Alpha通道(透明度)的值,RR 是R通道(红色)的值,GG 是G通道(绿色)的值,BB是B通道(蓝色)的值。例如"#ffffff"表示白色, "#7F000000"表示半透明的黑色。 另一种是使用一个16进制的"32位整数" 0xAARRGGBB 来表示一个颜色,例如 `0xFF112233`表示颜色"#112233", `0x11223344`表示颜色"#11223344"。 可以通过`colors.toString()`把颜色整数转换为字符串,通过`colors.parseColor()`把颜色字符串解析为颜色整数。 ## colors.toString(color) * `color` {number} 整数RGB颜色值 * 返回 {string} 返回颜色值的字符串,格式为 "#AARRGGBB"。 ## colors.red(color) * `color` {number | string} 颜色值 * 返回 {number} 返回颜色color的R通道的值,范围0 ~ 255. ## colors.green(color) * `color` {number | string} 颜色值 * 返回 {number} 返回颜色color的G通道的值,范围0 ~ 255. ## colors.blue(color) * `color` {number | string} 颜色值 * 返回 {number} 返回颜色color的B通道的值,范围0 ~ 255. ## colors.alpha(color) * `color` {number | string} 颜色值 * 返回 {number} 返回颜色color的Alpha通道的值,范围0 ~ 255. ## colors.rgb(red, green, blue) * `red` {number} 颜色的R通道的值 * `blue` {number} 颜色的G通道的值 * `green` {number} 颜色的B通道的值 * 返回 {number} 返回这些颜色通道构成的整数颜色值。Alpha通道将是255(不透明)。 ## colors.argb(alpha, red, green, blue) * `alpha` {number} 颜色的Alpha通道的值 * `red` {number} 颜色的R通道的值 * `green` {number} 颜色的G通道的值 * `blue` {number} 颜色的B通道的值 * 返回 {number} 返回这些颜色通道构成的整数颜色值。 ## colors.parseColor(colorStr) * `colorStr` {string} 表示颜色的字符串,例如"#112233" * 返回 {number} 返回颜色的整数值。 # colors.BLACK 黑色,颜色值 #FF000000 # colors.DKGRAY 深灰色,颜色值 #FF444444 # colors.GRAY 灰色,颜色值 #FF888888 # colors.LTGRAY 亮灰色,颜色值 #FFCCCCCC # colors.WHITE 白色,颜色值 #FFFFFFFF # colors.RED 红色,颜色值 #FFFF0000 # colors.GREEN 绿色,颜色值 #FF00FF00 # colors.BLUE 蓝色,颜色值 #FF0000FF # colors.YELLOW 黄色,颜色值 #FFFFFF00 # colors.CYAN 青色,颜色值 #FF00FFFF # colors.MAGENTA 品红色,颜色值 #FFFF00FF # colors.TRANSPARENT 透明,颜色值 #00000000 --- --- url: 'https://www.wuyunai.com/docs/v8/console.html' description: >- Auto.js Pro v8 console 模块 API 文档 - 提供调试控制台功能,可以输出调试信息、中间结果等。类似 Web 浏览器中的控制台,支持 log、print 等函数。 --- # console - 控制台 > Stability: 2 - Stable 控制台模块提供了一个和Web浏览器中相似的用于调试的控制台。用于输出一些调试信息、中间结果等。 console模块中的一些函数也可以直接作为全局函数使用,例如log, print等。 ## console.show() 显示控制台。这会显示一个控制台的悬浮窗(需要悬浮窗权限)。 ## console.hide() 隐藏控制台悬浮窗。 ## console.clear() 清空控制台。 ## console.log(\[data]\[, ...args])\# * `data` {any} * `...args` {any} 打印到控制台,并带上换行符。 可以传入多个参数,第一个参数作为主要信息,其他参数作为类似于 [printf(3)](http://man7.org/linux/man-pages/man3/printf.3.html) 中的代替值(参数都会传给 util.format())。 ```javascript const count = 5; console.log('count: %d', count); // 打印: count: 5 到 stdout console.log('count:', count); // 打印: count: 5 到 stdout ``` 详见 util.format()。 该函数也可以作为全局函数使用。 ## console.verbose(\[data]\[, ...args]) * `data` {any} * `...args` {any} 与console.log类似,但输出结果以灰色字体显示。输出优先级低于log,用于输出观察性质的信息。 ## console.info(\[data]\[, ...args]) * `data` {any} * `...args` {any} 与console.log类似,但输出结果以绿色字体显示。输出优先级高于log, 用于输出重要信息。 ## console.warn(\[data]\[, ...args]) * `data` {any} * `...args` {any} 与console.log类似,但输出结果以蓝色字体显示。输出优先级高于info, 用于输出警告信息。 ## console.error(\[data]\[, ...args]) * `data` {any} * `...args` {any} 与console.log类似,但输出结果以红色字体显示。输出优先级高于warn, 用于输出错误信息。 ## console.assert(value, message) * `value` {any} 要断言的布尔值 * `message` {string} value为false时要输出的信息 断言。如果value为false则输出错误信息message并停止脚本运行。 ```javascript var a = 1 + 1; console.assert(a == 2, "加法出错啦"); ``` ## console.time(\[label]) **\[v4.1.0新增]** * `label` {String} 计时器标签,可省略 启动一个定时器,用以计算一个操作的持续时间。 定时器由一个唯一的 `label` 标识。 当调用 `console.timeEnd()` 时,可以使用相同的 `label` 来停止定时器,并以毫秒为单位将持续时间输出到控制台。 重复启动同一个标签的定时器会覆盖之前启动同一标签的定时器。 ## console.timeEnd(label) **\[v4.1.0新增]** * `label` {String} 计时器标签 停止之前通过调用 `console.time()` 启动的定时器,并打印结果到控制台。 调用 `console.timeEnd()` 后定时器会被删除。如果不存在标签指定的定时器则会打印 `NaNms`。 ```javascript console.time('求和'); var sum = 0; for(let i = 0; i < 100000; i++){ sum += i; } console.timeEnd('求和'); // 打印 求和: xxx ms ``` ## console.trace(\[data]\[, ...args]) **\[v4.1.0新增]** * `data` {any} * `...args` {any} 与console.log类似,同时会打印出调用这个函数所在的调用栈信息(即当前运行的文件、行数等信息)。 ```javascript console.trace('Show me'); // 打印: (堆栈跟踪会根据被调用的跟踪的位置而变化) // Show me // at :7 ``` ## console.input(data\[, ...args]) * `data` {any} * `...args` {any} 与console.log一样输出信息,并在控制台显示输入框等待输入。按控制台的确认按钮后会将输入的字符串用eval计算后返回。 **部分机型可能会有控制台不显示输入框的情况,属于bug。** 例如: ```javascript var n = console.input("请输入一个数字:"); //输入123之后: toast(n + 1); //显示124 ``` ## console.rawInput(data\[, ...args]) * `data` {any} * `...args` {any} 与console.log一样输出信息,并在控制台显示输入框等待输入。按控制台的确认按钮后会将输入的字符串直接返回。 部分机型可能会有控制台不显示输入框的情况,属于bug。 例如: ```javascript var n = console.rawInput("请输入一个数字:"); //输入123之后: toast(n + 1); //显示1231 ``` ## console.setSize(w, h) * `w` {number} 宽度 * `h` {number} 高度 设置控制台的大小,单位像素。 ```javascript console.show(); //设置控制台大小为屏幕的四分之一 console.setSize(device.width / 2, device.height / 2); ``` ## console.setPosition(x, y) * `x` {number} 横坐标 * `y` {number} 纵坐标 设置控制台的位置,单位像素。 ```javascript console.show(); console.setPosition(100, 100); ``` ## console.setGlobalLogConfig(config) **\[v4.1.0新增]** * `config` {Object} 日志配置,可选的项有: * `file` {string} 日志文件路径,将会把日志写入该文件中 * `maxFileSize` {number} 最大文件大小,单位字节,默认为512 \* 1024 (512KB) * `rootLevel` {string} 写入的日志级别,默认为"ALL"(所有日志),可以为"OFF"(关闭), "DEBUG", "INFO", "WARN", "ERROR", "FATAL"等。 * `maxBackupSize` {number} 日志备份文件最大数量,默认为5 * `filePattern` {string} 日志写入格式,参见[PatternLayout](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html) 设置日志保存的路径和配置。例如把日志保存到"/sdcard/1.txt": ```javascript console.setGlobalLogConfig({ "file": "/sdcard/1.txt" }); ``` 注意该函数会影响所有脚本的日志记录。 ## print(text) * `text` {string} | {Object} 要打印到控制台的信息 相当于`log(text)`。 --- --- url: 'https://www.wuyunai.com/docs/v8/coordinatesBasedAutomation.html' description: >- Auto.js Pro v8 坐标操作自动化指南 - 学习使用坐标进行点击、滑动等操作,适用于游戏脚本开发。需要 Android 7.0 以上或 root 权限。包含坐标点击、滑动、手势等函数。 --- # 自动化 - 坐标操作 > Stability: 2 - Stable 本章节介绍了一些使用坐标进行点击、滑动的函数。这些函数有的需要安卓7.0以上,有的需要root权限。 要获取要点击的位置的坐标,可以在开发者选项中开启"指针位置"。 基于坐标的脚本通常会有分辨率的问题,这时可以通过`setScreenMetrics()`函数来进行自动坐标放缩。这个函数会影响本章节的所有点击、长按、滑动等函数。通过设定脚本设计时的分辨率,使得脚本在其他分辨率下自动放缩坐标。 控件和坐标也可以相互结合。一些控件是无法点击的(clickable为false), 无法通过`.click()`函数来点击,这时如果安卓版本在7.0以上或者有root权限,就可以通过以下方式来点击: ```javascript //获取这个控件 var widget = id("xxx").findOne(); //获取其中心位置并点击 click(widget.bounds().centerX(), widget.bounds().centerY()); //如果用root权限则用Tap ``` ## setScreenMetrics(width, height) * `width` {number} 屏幕宽度,单位像素 * `height` {number} 屏幕高度,单位像素 设置脚本坐标点击所适合的屏幕宽高。如果脚本运行时,屏幕宽度不一致会自动放缩坐标。 例如在1920\*1080的设备中,某个操作的代码为 ```javascript setScreenMetrics(1080, 1920); click(800, 200); longClick(300, 500); ``` 那么在其他设备上AutoJs会自动放缩坐标以便脚本仍然有效。例如在540 \* 960的屏幕中`click(800, 200)`实际上会点击位置(400, 100)。 ## 安卓7.0以上的触摸和手势模拟 > Stability: 2 - Stable **注意以下命令只有Android7.0及以上才有效** ### click(x, y) * `x` {number} 要点击的坐标的x值 * `y` {number} 要点击的坐标的y值 模拟点击坐标(x, y),并返回是否点击成功。只有在点击执行完成后脚本才继续执行。 一般而言,只有点击过程(大约150毫秒)中被其他事件中断(例如用户自行点击)才会点击失败。 使用该函数模拟连续点击时可能有点击速度过慢的问题,这时可以用`press()`函数代替。 ### longClick(x, y) * `x` {number} 要长按的坐标的x值 * `y` {number} 要长按的坐标的y值 模拟长按坐标(x, y), 并返回是否成功。只有在长按执行完成(大约600毫秒)时脚本才会继续执行。 一般而言,只有长按过程中被其他事件中断(例如用户自行点击)才会长按失败。 ### press(x, y, duration) * `x` {number} 要按住的坐标的x值 * `y` {number} 要按住的坐标的y值 * `duration` {number} 按住时长,单位毫秒 模拟按住坐标(x, y), 并返回是否成功。只有按住操作执行完成时脚本才会继续执行。 如果按住时间过短,那么会被系统认为是点击;如果时长超过500毫秒,则认为是长按。 一般而言,只有按住过程中被其他事件中断才会操作失败。 一个连点器的例子如下: ```javascript //循环100次 for(var i = 0; i < 100; i++){ //点击位置(500, 1000), 每次用时1毫秒 press(500, 1000, 1); } ``` ### swipe(x1, y1, x2, y2, duration) * `x1` {number} 滑动的起始坐标的x值 * `y1` {number} 滑动的起始坐标的y值 * `x2` {number} 滑动的结束坐标的x值 * `y2` {number} 滑动的结束坐标的y值 * `duration` {number} 滑动时长,单位毫秒 模拟从坐标(x1, y1)滑动到坐标(x2, y2),并返回是否成功。只有滑动操作执行完成时脚本才会继续执行。 一般而言,只有滑动过程中被其他事件中断才会滑动失败。 ### gesture(duration, \[x1, y1], \[x2, y2], ...) * `duration` {number} 手势的时长 * `[x, y]` {...} 手势滑动路径的一系列坐标 模拟手势操作。例如`gesture(1000, [0, 0], [500, 500], [500, 1000])`为模拟一个从(0, 0)到(500, 500)到(500, 100)的手势操作,时长为2秒。 ### gestures(\[delay1, duration1, \[x1, y1], \[x2, y2], ...], \[delay2, duration2, \[x3, y3], \[x4, y4], ...], ...) 同时模拟多个手势。每个手势的参数为\[delay, duration, 坐标], delay为延迟多久(毫秒)才执行该手势;duration为手势执行时长;坐标为手势经过的点的坐标。其中delay参数可以省略,默认为0。 例如手指捏合: ```javascript gestures([0, 500, [800, 300], [500, 1000]], [0, 500, [300, 1500], [500, 1000]]); ``` ## 使用root权限点击和滑动的简单命令 > Stability: 1 - Experimental 注意:本章节的函数在后续版本很可能有改动!请勿过分依赖本章节函数的副作用。推荐使用`RootAutomator`代替本章节的触摸函数。 以下函数均需要root权限,可以实现任意位置的点击、滑动等。 * 这些函数通常首字母大写以表示其特殊的权限。 * 这些函数均不返回任何值。 * 并且,这些函数的执行是异步的、非阻塞的,在不同机型上所用的时间不同。脚本不会等待动作执行完成才继续执行。因此最好在每个函数之后加上适当的sleep来达到期望的效果。 例如: ```javascript Tap(100, 100); sleep(500); ``` 注意,动作的执行可能无法被停止,例如: ```javascript for(var i = 0; i < 100; i++){ Tap(100, 100); } ``` 这段代码执行后可能会出现在任务管理中停止脚本后点击仍然继续的情况。 因此,强烈建议在每个动作后加上延时: ```javascript for(var i = 0; i < 100; i++){ Tap(100, 100); sleep(500); } ``` ### Tap(x, y) * `x` {number} 要点击的x坐标 * `y` {number} 要点击的y坐标 点击位置(x, y), 您可以通过"开发者选项"开启指针位置来确定点击坐标。 ### Swipe(x1, y1, x2, y2, \[duration]) * `x1` {number} 滑动起点的x坐标 * `y1` {number} 滑动起点的y坐标 * `x2` {number} 滑动终点的x坐标 * `y2` {number} 滑动终点的y坐标 * `duration` {number} 滑动动作所用的时间 滑动。从(x1, y1)位置滑动到(x2, y2)位置。 ## RootAutomator > Stability: 2 - Stable RootAutomator是一个使用root权限来模拟触摸的对象,用它可以完成触摸与多点触摸,并且这些动作的执行没有延迟。 一个脚本中最好只存在一个RootAutomator,并且保证脚本结束退出他。可以在exit事件中退出RootAutomator,例如: ```javascript var ra = new RootAutomator(); events.on('exit', function(){ ra.exit(); }); //执行一些点击操作 ... ``` 注意 RootAutomator需要root权限或adb权限才能执行,要使用adb权限可以用`shell.setDefaultOptions(\{adb: true\})`默认使用adb权限,需要shizuku授权。 另外RootAutomoat兼容性不佳,从9.3版本开始,推荐使用[RootAutomator2](#rootautomator2)代替。 ### new RootAutomator(\[options]) * `options` {object} 可选参数,包括: * `adb` {boolean} 是否使用adb权限,默认为false。若为`true`时,需要用shizuku授权才能使用。 * `inputDevice` {string} 指定RootAutomator操作的设备,比如`/dev/input/event4`。不指定则自动检测。 构造一个RootAutomator。 ### RootAutomator.tap(x, y\[, id]) * `x` {number} 横坐标 * `y` {number} 纵坐标 * `id` {number} 多点触摸id,可选,默认为1,可以通过setDefaultId指定。 点击位置(x, y)。其中id是一个整数值,用于区分多点触摸,不同的id表示不同的"手指",例如: ```javascript var ra = new RootAutomator(); //让"手指1"点击位置(100, 100) ra.tap(100, 100, 1); //让"手指2"点击位置(200, 200); ra.tap(200, 200, 2); ra.exit(); ``` 如果不需要多点触摸,则不需要id这个参数。 多点触摸通常用于手势或游戏操作,例如模拟双指捏合、双指上滑等。 某些情况下可能存在tap点击无反应的情况,这时可以用`RootAutomator.press()`函数代替。 ### RootAutomator.swipe(x1, x2, y1, y2\[, duration, id]) * `x1` {number} 滑动起点横坐标 * `y1` {number} 滑动起点纵坐标 * `x2` {number} 滑动终点横坐标 * `y2` {number} 滑动终点纵坐标 * `duration` {number} 滑动时长,单位毫秒,默认值为300 * `id` {number} 多点触摸id,可选,默认为1 模拟一次从(x1, y1)到(x2, y2)的时间为duration毫秒的滑动。 ### RootAutomator.press(x, y, duration\[, id]) * `x` {number} 横坐标 * `y` {number} 纵坐标 * `duration` {number} 按下时长 * `id` {number} 多点触摸id,可选,默认为1 模拟按下位置(x, y),时长为duration毫秒。 ### RootAutomator.longPress(x, y\[, id]) * `x` {number} 横坐标 * `y` {number} 纵坐标 * `duration` {number} 按下时长 * `id` {number} 多点触摸id,可选,默认为1 模拟长按位置(x, y)。 以上为简单模拟触摸操作的函数。如果要模拟一些复杂的手势,需要更底层的函数。 ### RootAutomator.touchDown(x, y\[, id]) * `x` {number} 横坐标 * `y` {number} 纵坐标 * `id` {number} 多点触摸id,可选,默认为1 模拟手指按下位置(x, y)。 ### RootAutomator.touchMove(x, y\[, id]) * `x` {number} 横坐标 * `y` {number} 纵坐标 * `id` {number} 多点触摸id,可选,默认为1 模拟移动手指到位置(x, y)。 ### RootAutomator.touchUp(\[id]) * `id` {number} 多点触摸id,可选,默认为1 模拟手指弹起。 ## RootAutomator2 RootAutomator2用于基于root或者adb权限,模拟点击、手势、长按等操作。相比起基于无障碍的操作,RootAutomator支持多点触控和动态改变手势;相比RootAutomator,RootAutomator2的兼容性更佳。 ```javascript let screenWidth = $device.width; let screenHeight = $device.height; // 使用root权限执行。也可以指定为\{adb: true\}使用adb权限,需要shizuku授权 const ra = new RootAutomator2(\{ root: true \}); // 点击(200, 200)的位置 ra.tap(200, 200); sleep(1000); // 按住屏幕中点持续500毫秒 ra.press(screenWidth / 2, screenHeight / 2, 500); sleep(1000); // 从(500, 200)滑动到(500, 1000),滑动时长300毫秒 ra.swipe(500, 200, 500, 1000, 300); sleep(1000); // 双指捏合 // 左上角位置 let p0 = { x: screenWidth / 6, y: screenHeight / 6, }; // 右下角位置 let p1 = { x: screenWidth - p0.x, y: screenHeight - p0.y, } // 同时按下左上角和右下角,手指id为0和1 ra.touchDown([ \{ x: p0.x, y: p0.y, id: 0 \}, \{ x: p1.x, y: p1.y, id: 1 \}, ]); // 移动步数 const steps = 20; // 计算每一步移动的偏移量 const stepX = Math.round((p1.x - p0.x) / steps) / 2; const stepY = Math.round((p1.y - p0.y) / steps) / 2; for (let i = 0; i < steps; i++) { // 手指0向右下移动,手指1向左上移动 ra.touchMove([ \{ x: p0.x + stepX * i, y: p0.y + stepY * i, id: 0 \}, \{ x: p1.x - stepX * i, y: p1.y - stepY * i, id: 1 \} ]); } // 弹起所有手指 ra.touchUp(); // 等待前面的操作全部完成 ra.flush(); // 退出RootAutomator,如果没有正确退出,可能导致"手指"残留在屏幕上 ra.exit(); ``` ### new RootAutomator2(\[`options`]) * ``` options ``` 创建RootAutomator2的选项,可选。 * `adb` {boolean} 是否使用adb权限,默认为\`false * `root` {boolean} 是否使用root权限,当不指定adb权限时,默认为`true` 根据选项创建一个新的RootAutomator2实例。RootAutomator2相比RootAutomator有更好的兼容性。 可以指定是否使用root权限、adb权限等,参见ShellOptions。如果不指定root或adb权限,则默认使用root权限。 ### RootAutomator2.tap(`x`, `y`) 点击位置(x, y),时长为5毫秒。此函数会等待操作同步完成,可能函数执行时间大于实际操作时间,若对执行时间有要求,可以用`touchDown`, `touchUp`等异步方法代替。 #### 参数 | 名称 | 类型 | | :--- | :------- | | `x` | `number` | | `y` | `number` | ### RootAutomator2.longPress(`x`, `y`) 长按(x, y)位置。长按的时长为[ViewConfiguration.getLongPressTimeout()](https://developer.android.google.cn/reference/android/view/ViewConfiguration.html#getLongPressTimeout\(\))+100毫秒。此函数会等待操作同步完成,可能函数执行时间大于实际操作时间,若对执行时间有要求,可以用`touchDown`, `touchUp`等异步方法代替。 #### 参数 | 名称 | 类型 | | :--- | :------- | | `x` | `number` | | `y` | `number` | ### RootAutomator2.press(`x`, `y`, `duration`) 按下(x, y)位置持续duration时长,然后抬起手指。此函数会等待操作同步完成,可能函数执行时间大于实际操作时间,若对执行时间有要求,可以用`touchDown`, `touchUp`等异步方法代替。 #### 参数 | 名称 | 类型 | 描述 | | :--------- | :------- | :----------------- | | `x` | `number` | - | | `y` | `number` | - | | `duration` | `number` | 按下时长,单位毫秒 | ### RootAutomator2.swipe(`x1`, `y1`, `x2`, `y2`, `duration`) 在给定的duration时长从(x1, y1)位置滑动到(x2, y2)位置。此函数会等待操作同步完成,可能函数执行时间大于实际操作时间,若对执行时间有要求,可以用`touchDown`, `touchUp`等异步方法代替。 #### 参数 | 名称 | 类型 | 描述 | | :--------- | :------- | :----------------- | | `x1` | `number` | - | | `y1` | `number` | - | | `x2` | `number` | - | | `y2` | `number` | - | | `duration` | `number` | 滑动时长,单位毫秒 | ### RootAutomator2.touchDown(`x`, `y`, \[`id`]) 按下(x, y)位置。若对应id的手指之前已经是按下状态,则会模拟手指移动(touchMove)事件。此操作是异步进行的,若要等待操作完成,可以使用`flush`方法。 #### 参数 | 名称 | 类型 | 描述 | | :--- | :------- | :-------------- | | `x` | `number` | - | | `y` | `number` | - | | `id` | `number` | 手指ID,默认为0 | ### RootAutomator2.touchDown(`pointers`) 模拟一个手指按下事件,使用数组描述触摸的位置和相应的手指id。例如`ra.touchDown([\{x: 100, y: 100, id: 0}, {x: 200, y: 200, id: 1\}])`会使用手指0按下位置(100, 100),使用手指1按下位置(200, 200)。此操作是异步进行的,若要等待操作完成,可以使用`flush`方法。 #### 参数 | 名称 | 类型 | 描述 | | :--------- | :------ | :----------------------------------------------------------- | | `pointers` | `Array` | 描述每个手指位置的数组,数组的每个元素带有`x`, `y`和`id`三个字段 | ### RootAutomator2.touchMove(`x`, `y`, \[`id`]) 将手指移动到(x, y)位置。若对应id的手指之前并非按下状态,则会模拟手指按下(touchDown)事件。此操作是异步进行的,若要等待操作完成,可以使用`flush`方法。 #### 参数 | 名称 | 类型 | 描述 | | :--- | :------- | :-------------- | | `x` | `number` | - | | `y` | `number` | - | | `id` | `number` | 手指ID,默认为0 | ### RootAutomator2.touchMove(`pointers`) 模拟一个手指移动事件,使用数组描述触摸的位置和相应的手指id。例如`ra.touchMove([\{x: 100, y: 100, id: 0}, {x: 200, y: 200, id: 1\}])`会将手指0移动到位置(100, 100),将手指1移动到位置(200, 200)。此操作是异步进行的,若要等待操作完成,可以使用`flush`方法。 #### 参数 | 名称 | 类型 | 描述 | | :--------- | :------ | :----------------------------------------------------------- | | `pointers` | `Array` | 描述每个手指位置的数组,数组的每个元素带有`x`, `y`和`id`三个字段 | ### RootAutomator2.touchUp(\[`id`]) 抬起手指。 #### 参数 | 名称 | 类型 | 描述 | | :--- | :------- | :----------------------------- | | `id` | `number` | 手指ID,若不指定则抬起所有手指 | ### RootAutomator2.touchUp(`pointers`) 模拟一个手指抬起事件,使用数组描述触摸的位置和相应的手指id。例如`ra.touchUp([\{x: 100, y: 100, id: 0}, {x: 200, y: 200, id: 1\}])`会使用手指0和手指1抬起,其中的坐标位置为手指抬起时的位置。此操作是异步进行的,若要等待操作完成,可以使用`flush`方法。 #### 参数 | 名称 | 类型 | 描述 | | :--------- | :------ | :----------------------------------------------------------- | | `pointers` | `Array` | 描述每个手指位置的数组,数组的每个元素带有`x`, `y`和`id`三个字段 | ### RootAutomator2.flush() 等待所有操作完成。例如我们使用`tocuhDown`, `touchMove`, `touchUp`完成了一系列手势,需要等待这些手势完成后继续下一步时,使用`ra.flush()`来等待这些操作完成。 ### RootAutomator2.exit(\[`forced`]) 退出RootAutomator2。 #### 参数 | 名称 | 类型 | 描述 | | :------- | :-------- | :----------------------------------------------------------- | | `forced` | `boolean` | 可选。如果为true,将不等待未完成的操作,而是尽可能快地退出;如果为false,在会所有未完成的操作结束后退出进程。 | --- --- url: 'https://www.wuyunai.com/docs/v8/crypto.html' --- **\[[Pro 8.0.0新增](../../../pro.autojs.html)]** $crypto模块提供了对称加密(例如AES)、非对称加密(例如RSA)、消息摘要(例如MD5, SHA)等支持。 ## $crypto.digest(message, algorithm\[, options]) * `data` {any} 需要进行消息摘要的消息 * `key` {[Key](#key)} 解密密钥 * `algorithm` {string} 消息摘要算法,包括: * `MD5` * `SHA-1` * `SHA-224` * `SHA-256` * `SHA-384` * `SHA-512` 具体可参阅 [MessageDigest](https://developer.android.com/reference/java/security/MessageDigest) * `options` {Object} 可选项,用于指定[输入与输出的类型与格式](#输入与输出的类型与格式) * 返回 {any} 根据`options`指定的输出类型返回不同数据 对数据`data`用算法`algorithm`计算消息摘要,数据`data`可以是文件、二进制、base64、hex、字符串等数据,解密后数据可以返回二进制、base64、hex、字符串或者直接写入到文件中,具体参见[输入与输出的类型与格式](#输入与输出的类型与格式)。 ```javascript // 计算字符串abc的md5 console.log($crypto.digest("abc", "MD5")); // 计算字符串abc的sha-256 console.log($crypto.digest("abc", "SHA-256")); console.log($crypto.digest("Auto.js", "SHA-256", \{ input: "string", output: "hex" \})); // 计算文件/sdcard/1.txt的md5 console.log($crypto.digest("/sdcard/1.txt", "MD5", { input: "file" })); ``` ## $crypto.encrypt(data, key, algorithm\[, options]) * `data` {any} 明文消息,根据`options`指定的输入类型为不同格式的参数 * `key` {[Key](#key)} 加密密钥。对称加密算法使用单个密钥,非对称加密则需要生成密钥对,参见[Key](#key) * `algorithm` {string} 加密算法,包括: * AES * AES/ECB/NoPadding * AES/ECB/PKCS5Padding * AES/CBC/NoPadding * AES/CBC/PKCS5Padding * AES/CFB/NoPadding * AES/CFB/PKCS5Padding * AES/CTR/NoPadding * AES/CTR/PKCS5Padding * AES/OFB/PKCS5Padding * AES/OFB/PKCS5Padding * RSA/ECB/PKCS1Padding * RSA/ECB/NoPadding * ... 具体可参阅 [javax.crypto.Cipher](https://developer.android.com/reference/javax/crypto/Cipher) * `options` {Object} 可选项,用于指定[输入与输出的类型与格式](#输入与输出的类型与格式) * 返回 {any} 根据`options`指定的输出类型返回不同数据 使用密钥`key`对数据`data`用加密算法算法`algorithm`进行加密,数据`data`可以是文件、二进制、base64、hex、字符串等数据,加密后数据可以返回二进制、base64、hex、字符串或者直接写入到文件中,具体参见[输入与输出的类型与格式](#输入与输出的类型与格式)。 ```javascript let message = "Hello Autojs"; // 密钥,由于AES等算法要求是128/192/256 bits,我们这里长度为16, 即128bits let str16 = "a".repeat(16); let key = new $crypto.Key(str16); // AES toastLog($crypto.encrypt(message, key, "AES")); // [-18, 27, -69, 81, 2, -87, -116, 23, -114, -86, -111, 40, 58, -127, -29, -59] // AES输出结果用base64展示 toastLog( $crypto.encrypt(message, key, "AES", { output: "base64", }) ); // 7hu7UQKpjBeOqpEoOoHjxQ== // AES默认明文填充模式PKCS5Padding, 结果同上 toastLog( $crypto.encrypt(message, key, "AES/ECB/PKCS5Padding", { output: "base64", }) ); // 7hu7UQKpjBeOqpEoOoHjxQ== // AES加密 let cipherText = $crypto.encrypt(message, key, "AES"); toastLog(cipherText); // [-18, 27, -69, 81, 2, -87, -116, 23, -114, -86, -111, 40, 58, -127, -29, -59] // RSA256KeyPair let algorithm = "RSA"; let length = "2048"; // 生成RSA密钥对 key = $crypto.generateKeyPair(algorithm, length); let message = "Hello Autojs"; // RSA加密 cipherText = $crypto.encrypt(message, key.publicKey, "RSA/ECB/PKCS1Padding"); toastLog(cipherText); // [114, 99, -93, 6, -88, 8, -12, -53, -68, -15, ...] ``` ## $crypto.decrypt(data, key, algorithm\[, options]) * `data` {any} 密文消息`options`指定的输入类型为不同格式的参数 * `key` {[Key](#key)} 解密密钥。对称加密算法使用单个密钥,非对称加密则需要生成密钥对,参见[Key](#key) * `algorithm` {string} 加密算法,包括: * AES * AES/ECB/NoPadding * AES/ECB/PKCS5Padding * AES/CBC/NoPadding * AES/CBC/PKCS5Padding * AES/CFB/NoPadding * AES/CFB/PKCS5Padding * AES/CTR/NoPadding * AES/CTR/PKCS5Padding * AES/OFB/PKCS5Padding * AES/OFB/PKCS5Padding * RSA/ECB/PKCS1Padding * RSA/ECB/NoPadding * ... 具体可参阅 [javax.crypto.Cipher](https://developer.android.com/reference/javax/crypto/Cipher) * `options` {Object} 可选项,用于指定[输入与输出的类型与格式](#输入与输出的类型与格式) * 返回 {any} 根据`options`指定的输出类型返回不同数据 使用密钥`key`对数据`data`用解密算法算法`algorithm`进行解密,数据`data`可以是文件、二进制、base64、hex、字符串等数据,解密后数据可以返回二进制、base64、hex、字符串或者直接写入到文件中,具体参见[输入与输出的类型与格式](#输入与输出的类型与格式)。 ```javascript // AES加密,加密为base64数据 let key = new $crypto.Key("1234567890123456"); let cipherText = $crypto.encrypt("Hello, Auto.js Pro!", "AES", { input: "string", "output": "base64" }); // AES解密,将base64数据解密为字符串 let plaintext = $crypto.decrypt(cipherText, key, "AES", { "input": "base64", "output": "string" }); toastLog(plaintext); ``` ## $crypto.generateKeyPair(algorithm\[, length]) * `algorithm` {string} 加密算法,包括 * `DH` * `DSA` * `EC` * `RSA` * `length` {number} 密钥长度。和算法相关,例如以位数指定的模数长度。默认为256。 * 返回 {[KeyPair](#keypair)} 生成一对密钥,包括公钥和私钥。例如在RSA加密算法中,我们可以用私钥加密,公钥解密做签名;或者公钥加密,私钥解密做数据加密。 ```javascript let keyPair = $crypto.generateKeyPair("RSA"); console.log("公钥为", keyPair.publicKey); console.log("私钥为", keyPair.privateKey); // 公钥加密、私钥解密 let plainText = "Hello World"; let bytes = $crypto.encrypt(plainText, keyPair.publicKey, "RSA"); let decryptedText = $crypto.decrypt(bytes, keyPair.privateKey, "RSA", { output: "string" }); console.log(decryptedText); // 公钥解密、私钥加密 let base64 = $crypto.encrypt(plainText, keyPair.privateKey, "RSA", { output: "base64" }); decryptedText = $crypto.decrypt(base64, keyPair.publicKey, "RSA", { input: "base64", output: "string" }); console.log(decryptedText); ``` # Key 密钥对象。可以直接通过构造函数构造。比如`new Key('12345678')`。 ## new Key(data\[, options]) * `data` {any} 密钥的内容,根据`options`选项的输入格式而定,默认为字符串格式 * `options` {Object} 可选参数,参见[输入与输出的类型与格式](#输入与输出的类型与格式) 构造函数,构造一个Key对象。 ```javascript let key = new $crypto.Key('1234567890123456'); // 获取Key的二进制数据 let data = key.data; // 转换为base64 let base64 = android.util.Base64.encodeToString(data, android.util.Base64.NO_WRAP); // 从base64重新构造一个Key let copiedKey = new $crypto.Key(base64, \{input: "base64"\}); console.log(copiedKey.toString()); ``` ## Key.data * {byte\[]} Key的二进制数据。 # KeyPair 密钥对对象。可以通过`$crypto.generateKeyPair()`函数生成,也可以通过构造函数构造。 ## new KeyPair(publicKey, privateKey\[, options]) **\[Pro 8.7.2新增]** * `publicKey` {any} 公钥的数据,根据`options`选项的输入格式而定,默认为字符串格式 * `privateKey` {any} 私钥的数据,根据`options`选项的输入格式而定,默认为字符串格式 * `options` {Object} 可选参数,参见[输入与输出的类型与格式](#输入与输出的类型与格式) 构造函数,构造一个KeyPair对象。 ```javascript let keyPair = $crypto.generateKeyPair("RSA"); // 获取公钥私钥的二进制数据,并转为base64 let data = { publicKey: base64Bytes(keyPair.publicKey.data), privateKey: base64Bytes(keyPair.privateKey.data), }; // 从base64重新构造一个Key let copiedKeyPair = new $crypto.KeyPair(data.publicKey, data.privateKey, \{input: "base64"\}); console.log(copiedKeyPair); function base64Bytes(bytes) { return android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP); } ``` ## KeyPair.privateKey * {[Key](#key)} 私钥。 ## KeyPair.publicKey * {[Key](#key)} 公钥。 # 输入与输出的类型与格式 `options` {object} 用于指定加解密、消息摘要时输入和输出的类型与格式。 * `input` {string} 输入类型,用于指定加密、解密、摘要的源数据的类型。如果输入为字符串,则默认为`string`;否则默认为`bytes`。可选的值包括: * `string` 字符串格式的数据 * `base64` base64格式的数据 * `hex` base16格式的数据 * `bytes` Java二进制字节数组 * `file` 文件类型,将从文件读取数据进行加解密 * `output` {string} 输出类型,用于指定加密、解密、摘要后的数据的类型。对于加解密,默认为`bytes`;对于消息摘要,默认为`hex`。可选的值包括: * `string` 字符串格式的数据 * `base64` base64格式的数据 * `hex` base16格式的数据 * `bytes` Java二进制字节数组 * `file` 文件类型,将处理后的数据写入到文件中,必须同时制定`dest`参数 * `dest` {string} 输出文件路径,`output`类型为`file`时,用于指定加密、解密、摘要后输出文件的路径 * `encoding` {string} 编码格式,`input`类型为`string`时,用于指定输入字符串转为二进制数据所使用的字符编码;`output`类型为`string`时,用于指定输出数据转为字符串数据所使用的字符编码。默认为`utf-8` * `iv` {string} | {bytes} 指定AES等加密的初始化向量参数,可选。Pro 9.2.12版本新增。 ```javascript let filepath = files.join("./test.txt", "1.txt"); let message = "Hello Autojs"; $files.write(filepath, message); let str16 = "a".repeat(16); let key = new $crypto.Key(str16); // 创建加密前的base64内容 let base64Content = $base64.encode(key); // 加密前的hex内容 let hexContent = "48656c6c6f204175746f6a73"; // 加密文件,输出格式为二进制 console.log($crypto.encrypt(filepath, key, "AES", \{ input: "file", output: "base64" \})); // 加密文件,输出到另一个文件 console.log($crypto.encrypt(filepath, key, "AES", \{ input: "file", output: "file", dest: "./output.txt" \})); // 加密base64内容,输出格式为base64 console.log($crypto.encrypt("SGVsbG8gQXV0b2pz", key, "AES", \{ input: "base64", output: "base64" \}); // 加密hex内容,输出格式为hex console.log($crypto.encrypt("48656c6c6f204175746f6a73", key, "AES", \{ input: "hex", output: "hex" \})); console.log($crypto.encrypt("Hello Autojs", key, "AES")); // [-18, 27, -69, 81, 2, -87, -116, 23, -114, -86, -111, 40, 58, -127, -29, -59] // 计算文件MD5,输出为hex console.log($crypto.digest(filepath, "MD5", \{ input: "file", output: "hex" \})); ``` --- --- url: 'https://www.wuyunai.com/docs/v8/debug.html' --- # debug - 调试工具 **\[Pro 8.7.0新增]** Debug模块提供了一些调试工具,比如诊断内存泄露,获取一个Error的详细堆栈等。 ## $debug.dumpHprof(file) * `file` {string} dump文件路径 将整个脚本进程的内存dump到文件file中。 当你发现Auto.js Pro占用内存很高时,你可以运行这个函数来dump整个内存并反馈给开发者,开发者可以通过内存dump文件来诊断是否有内存泄露。 dump过程中整个进程将会卡死,此时请不要操作手机,以便造成dump失败或其他问题等;dump一般需要几十秒到几分钟时间,请耐心等待。 ::: tip 如何将文件发送给开发者?您可以附上您的脚本和dump文件,发送给邮箱 hybbbb1996@gmail.com,开发者将尽快排查和回复。另外建议在反馈之前,通过`$debug.setMemoryLeakDetectionEnabled()`函数来开启内存泄露检查,排查脚本中的内存泄露,防止乌龙,减少开发者的工作量。 ::: ```javascript $debug.dumpHprof('./dump.hprof'); ``` ## $debug.dumpAndSendHprof(\[file]) * `file` {string} dump文件路径,可选。默认为当前目录下的`dump.hprof.zip`。 将整个脚本进程的内存dump到文件file中,并自动压缩为zip文件。使用压缩程度最高的压缩等级,因此需要的时间更久,但文件更小。 更多信息参见`$debug.dumpHprof`。 ## $debug.getStackTrace(error) * `error` {Error} 异常/错误 * 返回 {string} 获取一个异常的详细堆栈并返回。 ```javascript try { undefined_var; } catch(e) { console.error($debug.getStackTrace(e)); } ``` ## $debug.setMemoryLeakDetectionEnabled(enabled) * `enabled` {boolean} 是否启用内存泄露检测 启用内存泄露检测后,将会在日志中打印没有手动回收的对象,比如图片对象。 目前检测的对象包括: * 图片图像 例如以下代码将会造成内存泄露,运行后一段时间应该在日志中看到泄露日志。 ```javascript $debug.setMemoryLeakDetectionEnabled(true); requestScreenCapture(); for (let i = 0; i < 10; i++) { // 这个图片本应手动调用recycle回收 let leak = captureScreen().clone(); // 我们故意注释掉回收的代码 // leak.recycle(); } // 触发gc $debug.gc(); ``` 提示 在Auto.js Pro运行时,此功能默认开启;在打包软件中,此功能默认关闭。 ## $debug.gc() 建议JVM进行垃圾回收(并不一定进行垃圾回收)。 --- --- url: 'https://www.wuyunai.com/docs/v8/device.html' description: >- Auto.js Pro v8 device 模块 API 文档 - 提供设备信息获取和操作功能,包括设备宽高、内存使用率、IMEI、电池信息、屏幕亮度、音量控制、振动等设备相关操作。 --- # device - 设备 > Stability: 2 - Stable device模块提供了与设备有关的信息与操作,例如获取设备宽高,内存使用率,IMEI,调整设备亮度、音量等。 此模块的部分函数,例如调整音量,需要"修改系统设置"的权限。如果没有该权限,会抛出`SecurityException`并跳转到权限设置界面。 ## device.width * {number} 设备屏幕分辨率宽度。例如1080。 ## device.height * {number} 设备屏幕分辨率高度。例如1920。 ## device.buildId * {string} Either a change list number, or a label like "M4-rc20". 修订版本号,或者诸如"M4-rc20"的标识。 ## device.broad * {string} The name of the underlying board, like "goldfish". 设备的主板(?)型号。 ## device.brand * {string} The consumer-visible brand with which the product/hardware will be associated, if any. 与产品或硬件相关的厂商品牌,如"Xiaomi", "Huawei"等。 ## device.device * {string} The name of the industrial design. 设备在工业设计中的名称。 ## device.model * {string} The end-user-visible name for the end product. 设备型号。 ## device.product * {string} The name of the overall product. 整个产品的名称。 ## device.bootloader * {string} The system bootloader version number. 设备Bootloader的版本。 ## device.hardware * {string} The name of the hardware (from the kernel command line or /proc). 设备的硬件名称(来自内核命令行或者/proc)。 ## device.fingerprint * {string} A string that uniquely identifies this build. Do not attempt to parse this value. 构建(build)的唯一标识码。 ## device.serial * {string} A hardware serial number, if available. Alphanumeric only, case-insensitive. 硬件序列号。 ## device.sdkInt * {number} The user-visible SDK version of the framework; its possible values are defined in Build.VERSION\_CODES. 安卓系统API版本。例如安卓4.4的sdkInt为19。 ## device.incremental * {string} The internal value used by the underlying source control to represent this build. E.g., a perforce change list number or a git hash. ## device.release * {string} The user-visible version string. E.g., "1.0" or "3.4b5". Android系统版本号。例如"5.0", "7.1.1"。 ## device.baseOS * {string} The base OS build the product is based on. ## device.securityPatch * {string} The user-visible security patch level. 安全补丁程序级别。 ## device.codename * {string} The current development codename, or the string "REL" if this is a release build. 开发代号,例如发行版是"REL"。 ## device.getIMEI() * {string} 返回设备的IMEI. ## device.getAndroidId() * {string} 返回设备的Android ID。 Android ID为一个用16进制字符串表示的64位整数,在设备第一次使用时随机生成,之后不会更改,除非恢复出厂设置。 ## device.getMacAddress() * {string} 返回设备的Mac地址。该函数需要在有WLAN连接的情况下才能获取,否则会返回null。 **可能的后续修改**:未来可能增加有root权限的情况下通过root权限获取,从而在没有WLAN连接的情况下也能返回正确的Mac地址,因此请勿使用此函数判断WLAN连接。 ## device.getBrightness() * {number} 返回当前的(手动)亮度。范围为0~255。 ## device.getBrightnessMode() * {number} 返回当前亮度模式,0为手动亮度,1为自动亮度。 ## device.setBrightness(b) * `b` {number} 亮度,范围0~255 设置当前手动亮度。如果当前是自动亮度模式,该函数不会影响屏幕的亮度。 此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。 ## device.setBrightnessMode(mode) * `mode` {number} 亮度模式,0为手动亮度,1为自动亮度 设置当前亮度模式。 此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。 ## device.getMusicVolume() * {number} 整数值 返回当前媒体音量。 ## device.getNotificationVolume() * {number} 整数值 返回当前通知音量。 ## device.getAlarmVolume() * {number} 整数值 返回当前闹钟音量。 ## device.getMusicMaxVolume() * {number} 整数值 返回媒体音量的最大值。 ## device.getNotificationMaxVolume() * {number} 整数值 返回通知音量的最大值。 ## device.getAlarmMaxVolume() * {number} 整数值 返回闹钟音量的最大值。 ## device.setMusicVolume(volume) * `volume` {number} 音量 设置当前媒体音量。 此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。 ## device.setNotificationVolume(volume) * `volume` {number} 音量 设置当前通知音量。 此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。 ## device.setAlarmVolume(volume) * `volume` {number} 音量 设置当前闹钟音量。 此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。 ## device.getBattery() * {number} 0.0~100.0的浮点数 返回当前电量百分比。 ## device.isCharging() * {boolean} 返回设备是否正在充电。 ## device.getTotalMem() * {number} 返回设备内存总量,单位字节(B)。1MB = 1024 \* 1024B。 ## device.getAvailMem() * {number} 返回设备当前可用的内存,单位字节(B)。 ## device.isScreenOn() * 返回 {boolean} 返回设备屏幕是否是亮着的。如果屏幕亮着,返回`true`; 否则返回`false`。 需要注意的是,类似于vivo xplay系列的息屏时钟不属于"屏幕亮着"的情况,虽然屏幕确实亮着但只能显示时钟而且不可交互,此时`isScreenOn()`也会返回`false`。 ## device.getScreenOrientation() * 返回 {string} 返回当前屏幕方向字符串。 常见返回值包括 `portrait`(竖屏)和 `landscape`(横屏)。 ```javascript log("屏幕方向: " + device.getScreenOrientation()); ``` ## device.getScreenRotation() * 返回 {number} 返回当前屏幕旋转角度,单位为度。 常见返回值包括 `0`、`90`、`180`、`270`。 ```javascript log("屏幕旋转角度: " + device.getScreenRotation()); ``` ## device.wakeUp() 唤醒设备。包括唤醒设备CPU、屏幕等。可以用来点亮屏幕。 ## device.wakeUpIfNeeded() 如果屏幕没有点亮,则唤醒设备。 ## device.keepScreenOn(\[timeout]) * `timeout` {number} 屏幕保持常亮的时间, 单位毫秒。如果不加此参数,则一直保持屏幕常亮。 保持屏幕常亮。 此函数无法阻止用户使用锁屏键等正常关闭屏幕,只能使得设备在无人操作的情况下保持屏幕常亮;同时,如果此函数调用时屏幕没有点亮,则会唤醒屏幕。 在某些设备上,如果不加参数timeout,只能在Auto.js的界面保持屏幕常亮,在其他界面会自动失效,这是因为设备的省电策略造成的。因此,建议使用比较长的时长来代替"一直保持屏幕常亮"的功能,例如`device.keepScreenOn(3600 * 1000)`。 可以使用`device.cancelKeepingAwake()`来取消屏幕常亮。 ```javascript //一直保持屏幕常亮 device.keepScreenOn() ``` ## device.keepScreenDim(\[timeout]) * `timeout` {number} 屏幕保持常亮的时间, 单位毫秒。如果不加此参数,则一直保持屏幕常亮。 保持屏幕常亮,但允许屏幕变暗来节省电量。此函数可以用于定时脚本唤醒屏幕操作,不需要用户观看屏幕,可以让屏幕变暗来节省电量。 此函数无法阻止用户使用锁屏键等正常关闭屏幕,只能使得设备在无人操作的情况下保持屏幕常亮;同时,如果此函数调用时屏幕没有点亮,则会唤醒屏幕。 可以使用`device.cancelKeepingAwake()`来取消屏幕常亮。 ## device.keepAwake(\[mode]\[, timeout]) * `mode` {number} 唤醒模式,可选 * `timeout` {number} 保持唤醒时间,单位毫秒,可选 保持设备唤醒状态。该方法是更底层的保持唤醒接口,通常优先使用 `device.keepScreenOn()` 或 `device.keepScreenDim()`。 ```javascript // 保持唤醒一小时 device.keepAwake(0, 3600 * 1000); ``` ## device.cancelKeepingAwake() 取消设备保持唤醒状态。用于取消`device.keepScreenOn()`, `device.keepScreenDim()`等函数设置的屏幕常亮。 ## device.vibrate(ms) * `ms` {number} 震动时间,单位毫秒 使设备震动一段时间。 ```javascript //震动两秒 device.vibrate(2000); ``` ## device.cancelVibration() 如果设备处于震动状态,则取消震动。 ## 快捷属性(与方法对应) 除函数形式外,`device` 还提供了一组可直接读取的快捷属性。\ 这些属性通常与对应的 `getXxx()` / `isXxx()` 方法语义一致,适合在脚本中快速读取状态。 ## device.battery * {number} 当前电量百分比(0~100),等价于 `device.getBattery()`。 ## device.brightness * {number} 当前亮度值(0~255),等价于 `device.getBrightness()`。 ## device.brightnessMode * {number} 当前亮度模式,`0` 为手动亮度,`1` 为自动亮度。等价于 `device.getBrightnessMode()`。 ## device.charging * {boolean} 设备是否正在充电,等价于 `device.isCharging()`。 ## device.screenOn * {boolean} 屏幕是否点亮,等价于 `device.isScreenOn()`。 ## device.screenOrientation * {string} 屏幕方向,常见值为 `portrait` 或 `landscape`,等价于 `device.getScreenOrientation()`。 ## device.screenRotation * {number} 屏幕旋转角度,常见值为 `0`、`90`、`180`、`270`,等价于 `device.getScreenRotation()`。 ## device.totalMem * {number} 设备内存总量(字节),等价于 `device.getTotalMem()`。 ## device.availMem * {number} 设备当前可用内存(字节),等价于 `device.getAvailMem()`。 ## device.androidId * {string} 设备 Android ID,等价于 `device.getAndroidId()`。 ## device.alarmVolume / device.alarmMaxVolume * {number} 当前闹钟音量与闹钟最大音量,分别等价于 `device.getAlarmVolume()` 与 `device.getAlarmMaxVolume()`。 ## device.musicVolume / device.musicMaxVolume * {number} 当前媒体音量与媒体最大音量,分别等价于 `device.getMusicVolume()` 与 `device.getMusicMaxVolume()`。 ## device.notificationVolume / device.notificationMaxVolume * {number} 当前通知音量与通知最大音量,分别等价于 `device.getNotificationVolume()` 与 `device.getNotificationMaxVolume()`。 --- --- url: 'https://www.wuyunai.com/docs/v8/dialogs.html' description: >- Auto.js Pro v8 dialogs 模块 API 文档 - 提供简单的对话框支持,可以通过对话框和用户进行交互,包括确认框、输入框、选择框等常用对话框类型。 --- > Stability: 2 - Stable dialogs 模块提供了简单的对话框支持,可以通过对话框和用户进行交互。最简单的例子如下: ```javascript alert("您好"); ``` 这段代码会弹出一个消息提示框显示"您好",并在用户点击"确定"后继续运行。稍微复杂一点的例子如下: ```javascript var clear = confirm("要清除所有缓存吗?"); if(clear){ alert("清除成功!"); } ``` `confirm()`会弹出一个对话框并让用户选择"是"或"否",如果选择"是"则返回true。 需要特别注意的是,对话框在ui模式下不能像通常那样使用,应该使用回调函数或者[Promise](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise)的形式。理解这一点可能稍有困难。举个例子: ```javascript "ui"; //回调形式 confirm("要清除所有缓存吗?", function(clear){ if(clear){ alert("清除成功!"); } }); //Promise形式 confirm("要清除所有缓存吗?") .then(clear => { if(clear){ alert("清除成功!"); } }); ``` ## dialogs.alert(title\[, content, callback]) * `title` {string} 对话框的标题。 * `content` {string} 可选,对话框的内容。默认为空。 * `callback` {Function} 回调函数,可选。当用户点击确定时被调用,一般用于ui模式。 显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。 该函数也可以作为全局函数使用。 ```javascript alert("出现错误~", "出现未知错误,请联系脚本作者”); ``` 在ui模式下该函数返回一个`Promise`。例如: ```javascript "ui"; alert("嘿嘿嘿").then(()=>{ //当点击确定后会执行这里 }); ``` ## dialogs.confirm(title\[, content, callback]) * `title` {string} 对话框的标题。 * `content` {string} 可选,对话框的内容。默认为空。 * `callback` {Function} 回调函数,可选。当用户点击确定时被调用,一般用于ui模式。 显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 `true` ,否则返回 `false` 。 该函数也可以作为全局函数使用。 在ui模式下该函数返回一个`Promise`。例如: ```javascript "ui"; confirm("确定吗").then(value=>{ //当点击确定后会执行这里, value为true或false, 表示点击"确定"或"取消" }); ``` ## dialogs.rawInput(title\[, prefill, callback]) * `title` {string} 对话框的标题。 * `prefill` {string} 输入框的初始内容,可选,默认为空。 * `callback` {Function} 回调函数,可选。当用户点击确定时被调用,一般用于ui模式。 显示一个包含输入框的对话框,等待用户输入内容,并在用户点击确定时将输入的字符串返回。如果用户取消了输入,返回null。 该函数也可以作为全局函数使用。 ```javascript var name = rawInput("请输入您的名字", "小明"); alert("您的名字是" + name); ``` 在ui模式下该函数返回一个`Promise`。例如: ```javascript "ui"; rawInput("请输入您的名字", "小明").then(name => { alert("您的名字是" + name); }); ``` 当然也可以使用回调函数,例如: ```javascript rawInput("请输入您的名字", "小明", name => { alert("您的名字是" + name); }); ``` ## dialogs.input(title\[, prefill, callback]) 等效于 `eval(dialogs.rawInput(title, prefill, callback))`, 该函数和rawInput的区别在于,会把输入的字符串用eval计算一遍再返回,返回的可能不是字符串。 可以用该函数输入数字、数组等。例如: ```javascript var age = dialogs.input("请输入您的年龄", "18"); // new Date().getYear() + 1900 可获取当前年份 var year = new Date().getYear() + 1900 - age; alert("您的出生年份是" + year); ``` 在ui模式下该函数返回一个`Promise`。例如: ```javascript "ui"; dialogs.input("请输入您的年龄", "18").then(age => { var year = new Date().getYear() + 1900 - age; alert("您的出生年份是" + year); }); ``` ## dialogs.prompt(title\[, prefill, callback]) 相当于 `dialogs.rawInput()`; ## dialogs.select(title, items, callback) * `title` {string} 对话框的标题。 * `items` {Array} 对话框的选项列表,是一个字符串数组。 * `callback` {Function} 回调函数,可选。当用户点击确定时被调用,一般用于ui模式。 显示一个带有选项列表的对话框,等待用户选择,返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择,返回-1。 ```javascript var options = ["选项A", "选项B", "选项C", "选项D"] var i = dialogs.select("请选择一个选项", options); if(i >= 0){ toast("您选择的是" + options[i]); }else{ toast("您取消了选择"); } ``` 在ui模式下该函数返回一个`Promise`。例如: ```javascript "ui"; dialogs.select("请选择一个选项", ["选项A", "选项B", "选项C", "选项D"]) .then(i => { toast(i); }); ``` ## dialogs.singleChoice(title, items\[, index, callback]) * `title` {string} 对话框的标题。 * `items` {Array} 对话框的选项列表,是一个字符串数组。 * `index` {number} 对话框的初始选项的位置,默认为0。 * `callback` {Function} 回调函数,可选。当用户点击确定时被调用,一般用于ui模式。 显示一个单选列表对话框,等待用户选择,返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择,返回-1。 在ui模式下该函数返回一个`Promise`。 ## dialogs.multiChoice(title, items\[, indices, callback]) * `title` {string} 对话框的标题。 * `items` {Array} 对话框的选项列表,是一个字符串数组。 * `indices` {Array} 选项列表中初始选中的项目索引的数组,默认为空数组。 * `callback` {Function} 回调函数,可选。当用户点击确定时被调用,一般用于ui模式。 显示一个多选列表对话框,等待用户选择,返回用户选择的选项索引的数组。如果用户取消了选择,返回`[]`。 在ui模式下该函数返回一个`Promise`。 ## dialogs.setDefaultDialogType(type) > \*\* \[[Pro 8.8.5新增](https://pro.autojs.org/)] \*\* * `type` {string} 对话框类型 设置默认的对话框类型,可选的值为: * `overlay` {string} 始终使用悬浮窗权限显示对话框,不管应用是否位于前台;没有悬浮窗权限时抛出异常 * `app` {string} 始终为应用内对话框,不管应用是否有界面存在以及界面是否位于前台;没有界面时抛出异常,有界面但位于后台仍然会弹出,但只有用户回到本应用才能看到。 需要注意的是,Auto.js Pro的主界面(文件列表)和脚本进程不属于一个进程,因此不能在Auto.js Pro主界面等弹窗。 * `app-or-overlay` {string} 如果本应用有界面存在,则显示为应用内对话框;位于后台仍然会弹出,但只有用户回到本应用才能看到。没有界面时使用悬浮窗权限弹出。 * `foreground-or-overlay` {string} 本应用位于前台时,使用应用内悬浮窗;在后台时,用悬浮窗权限弹出。从而保证任何情况都能立即被用户看到。 此函数影响所有由dialogs模块弹出的对话框。自定义对话框(`$dialogs.build`)可以通过`type`属性覆盖。 不执行此函数设置对话框类型时,默认为`overlay`类型(保持对之前版本的兼容)。 ```javascript // 设置所有对话框默认为自适应前台对话框,包括alert, $dialogs.build()等 $dialogs.setDefaultDialogType("foreground-or-overlay"); let types = ['overlay', 'app', 'app-or-overlay', 'foreground-or-overlay']; let i = $dialogs.select('请选择对话框类型', ['悬浮窗对话框: overlay', '应用内对话框: app', '自适应对话框: app-or-overlay', '自适应前台对话框: foreground-or-overlay']); if (i < 0) { exit(); } let type = types[i]; alert('选择的类型是' + type + ', 点击确定将在3秒后显示对话框', '您可以在这3秒内执行返回、切换后台等动作测试对话框行为') ``` ## dialogs.build(properties) * `properties` {Object} 对话框属性,用于配置对话框。 * 返回 {Dialog} 创建一个可自定义的对话框,例如: ```javascript dialogs.build({ //对话框标题 title: "发现新版本", //对话框内容 content: "更新日志: 新增了若干了BUG", //确定键内容 positive: "下载", //取消键内容 negative: "取消", //中性键内容 neutral: "到浏览器下载", //勾选框内容 checkBoxPrompt: "不再提示" }).on("positive", ()=>{ //监听确定键 toast("开始下载...."); }).on("neutral", ()=>{ //监听中性键 app.openUrl("https://www.autojs.org"); }).on("check", (checked)=>{ //监听勾选框 log(checked); }).show(); ``` 选项properties可供配置的项目为: * `title` {string} 对话框标题 * `titleColor` {string} | {number} 对话框标题的颜色 * `buttonRippleColor` {string} | {number} 对话框按钮的波纹效果颜色 * `icon` {string} | {Image} 对话框的图标,是一个URL或者图片对象 * `content` {string} 对话框文字内容 * `contentColor`{string} | {number} 对话框文字内容的颜色 * `contentLineSpacing`{number} 对话框文字内容的行高倍数,1.0为一倍行高 * `customView` {android.view.View} 对话框自定义内容View,可以使用ui.inflate从xml创建 * `items` {Array} 对话框列表的选项 * `itemsColor` {string} | {number} 对话框列表的选项的文字颜色 * `itemsSelectMode` {string} 对话框列表的选项选择模式,可以为: * `select` 普通选择模式 * `single` 单选模式 * `multi` 多选模式 * `itemsSelectedIndex` {number} | {Array} 对话框列表中预先选中的项目索引,如果是单选模式为一个索引;多选模式则为数组 * `positive` {string} 对话框确定按钮的文字内容(最右边按钮) * `positiveColor` {string} | {number} 对话框确定按钮的文字颜色(最右边按钮) * `neutral` {string} 对话框中立按钮的文字内容(最左边按钮) * `neutralColor` {string} | {number} 对话框中立按钮的文字颜色(最左边按钮) * `negative` {string} 对话框取消按钮的文字内容(确定按钮左边的按钮) * `negativeColor` {string} | {number} 对话框取消按钮的文字颜色(确定按钮左边的按钮) * `checkBoxPrompt` {string} 勾选框文字内容 * `checkBoxChecked` {boolean} 勾选框是否勾选 * `progress` {Object} 配置对话框进度条的对象: * `max` {number} 进度条的最大值,如果为-1则为无限循环的进度条 * `horizontal` {boolean} 如果为true, 则对话框无限循环的进度条为水平进度条 * `showMinMax` {boolean} 是否显示进度条的最大值和最小值 * `cancelable` {boolean} 对话框是否可取消,如果为false,则对话框只能用代码手动取消 * `canceledOnTouchOutside` {boolean} 对话框是否在点击对话框以外区域时自动取消,默认为true * `inputHint` {string} 对话框的输入框的输入提示 * `inputPrefill` {string} 对话框输入框的默认输入内容 * `type` {string} 对话框类型,参考`$dialogs.setDefaultDialogType()`函数的解释 通过这些选项可以自定义一个对话框,并通过监听返回的Dialog对象的按键、输入事件来实现交互。下面是一些例子。 模拟alert对话框: ```javascript dialogs.build({ title: "你好", content: "今天也要元气满满哦", positive: "好的" }).show(); ``` 模拟confirm对话框: ```javascript dialogs.build({ title: "你好", content: "请问你是笨蛋吗?", positive: "是的", negative: "我是大笨蛋" }).on("positive", ()=>{ alert("哈哈哈笨蛋"); }).on("negative", ()=>{ alert("哈哈哈大笨蛋"); }).show(); ``` 模拟单选框: ```javascript dialogs.build({ title: "单选", items: ["选项1", "选项2", "选项3", "选项4"], itemsSelectMode: "single", itemsSelectedIndex: 3 }).on("single_choice", (index, item)=>{ toast("您选择的是" + item); }).show(); ``` "处理中"对话框: ```javascript var d = dialogs.build({ title: "下载中...", progress: { max: -1 }, cancelable: false }).show(); setTimeout(()=>{ d.dismiss(); }, 3000); ``` 输入对话框: ```javascript dialogs.build({ title: "请输入您的年龄", inputPrefill: "18" }).on("input", (input)=>{ var age = parseInt(input); toastLog(age); }).show(); ``` 使用这个函数来构造对话框,一个明显的不同是需要使用回调函数而不能像dialogs其他函数一样同步地返回结果;但也可以通过threads模块的方法来实现。例如显示一个输入框并获取输入结果为: ```javascript var input = threads.disposable(); dialogs.build({ title: "请输入您的年龄", inputPrefill: "18" }).on("input", text => { input.setAndNotify(text); }).show(); var age = parseInt(input.blockedGet()); toastLog(age); ``` # Dialog `dialogs.build()`返回的对话框对象,内置一些事件用于响应用户的交互,也可以获取对话框的状态和信息。 ## 事件: `show` * `dialog` {Dialog} 对话框 对话框显示时会触发的事件。例如: ```javascript dialogs.build({ title: "标题" }).on("show", (dialog)=>{ toast("对话框显示了"); }).show(); ``` ## 事件: `cancel` * `dialog` {Dialog} 对话框 对话框被取消时会触发的事件。一个对话框可能按取消按钮、返回键取消或者点击对话框以外区域取消。例如: ```javascript dialogs.build({ title: "标题", positive: "确定", negative: "取消" }).on("cancel", (dialog)=>{ toast("对话框取消了"); }).show(); ``` ## 事件: `dismiss` * `dialog` {Dialog} 对话框 对话框消失时会触发的事件。对话框被取消或者手动调用`dialog.dismiss()`函数都会触发该事件。例如: ```javascript var d = dialogs.build({ title: "标题", positive: "确定", negative: "取消" }).on("dismiss", (dialog)=>{ toast("对话框消失了"); }).show(); setTimeout(()=>{ d.dismiss(); }, 5000); ``` ## 事件: `positive` * `dialog` {Dialog} 对话框 确定按钮按下时触发的事件。例如: ```javascript var d = dialogs.build({ title: "标题", positive: "确定", negative: "取消" }).on("positive", (dialog)=>{ toast("你点击了确定"); }).show(); ``` ## 事件: `negative` * `dialog` {Dialog} 对话框 取消按钮按下时触发的事件。例如: ```javascript var d = dialogs.build({ title: "标题", positive: "确定", negative: "取消" }).on("negative", (dialog)=>{ toast("你点击了取消"); }).show(); ``` ## 事件: `neutral` * `dialog` {Dialog} 对话框 中性按钮按下时触发的事件。例如: ```javascript var d = dialogs.build({ title: "标题", positive: "确定", negative: "取消", neutral: "稍后提示" }).on("positive", (dialog)=>{ toast("你点击了稍后提示"); }).show(); ``` ## 事件: `any` * `dialog` {Dialog} 对话框 * ``` action ``` {string} 被点击的按钮,可能的值为: * `positive` 确定按钮 * `negative` 取消按钮 * `neutral` 中性按钮 任意按钮按下时触发的事件。例如: ```javascript var d = dialogs.build({ title: "标题", positive: "确定", negative: "取消", neutral: "稍后提示" }).on("any", (action, dialog)=>{ if(action == "positive"){ toast("你点击了确定"); }else if(action == "negative"){ toast("你点击了取消"); } }).show(); ``` ## 事件: `item_select` * `index` {number} 被选中的项目索引,从0开始 * `item` {Object} 被选中的项目 * `dialog` {Dialog} 对话框 对话框列表(itemsSelectMode为"select")的项目被点击选中时触发的事件。例如: ```javascript var d = dialogs.build({ title: "请选择", positive: "确定", negative: "取消", items: ["A", "B", "C", "D"], itemsSelectMode: "select" }).on("item_select", (index, item, dialog)=>{ toast("您选择的是第" + (index + 1) + "项, 选项为" + item); }).show(); ``` ## 事件: `single_choice` * `index` {number} 被选中的项目索引,从0开始 * `item` {Object} 被选中的项目 * `dialog` {Dialog} 对话框 对话框单选列表(itemsSelectMode为"singleChoice")的项目被选中并点击确定时触发的事件。例如: ```javascript var d = dialogs.build({ title: "请选择", positive: "确定", negative: "取消", items: ["A", "B", "C", "D"], itemsSelectMode: "singleChoice" }).on("item_select", (index, item, dialog)=>{ toast("您选择的是第" + (index + 1) + "项, 选项为" + item); }).show(); ``` ## 事件: `multi_choice` * `indices` {Array} 被选中的项目的索引的数组 * `items` {Array} 被选中的项目的数组 * `dialog` {Dialog} 对话框 对话框多选列表(itemsSelectMode为"multiChoice")的项目被选中并点击确定时触发的事件。例如: ```javascript var d = dialogs.build({ title: "请选择", positive: "确定", negative: "取消", items: ["A", "B", "C", "D"], itemsSelectMode: "multiChoice" }).on("item_select", (indices, items, dialog)=>{ toast(util.format("您选择的项目为%o, 选项为%o", indices, items)); }).show(); ``` ## 事件: `input` * `text` {string} 输入框的内容 * `dialog` {Dialog} 对话框 带有输入框的对话框当点击确定时会触发的事件。例如: ```javascript dialogs.build({ title: "请输入", positive: "确定", negative: "取消", inputPrefill: "" }).on("input", (text, dialog)=>{ toast("你输入的是" + text); }).show(); ``` ## 事件: `input_change` * `text` {string} 输入框的内容 * `dialog` {Dialog} 对话框 对话框的输入框的文本发生变化时会触发的事件。例如: ```javascript dialogs.build({ title: "请输入", positive: "确定", negative: "取消", inputPrefill: "" }).on("input_change", (text, dialog)=>{ toast("你输入的是" + text); }).show(); ``` ## dialog.getProgress() * 返回 {number} 获取当前进度条的进度值,是一个整数 ## dialog.getMaxProgress() * 返回 {number} 获取当前进度条的最大进度值,是一个整数 ## dialog.getActionButton(action) * action `{string}` 动作,包括: * `positive` * `negative` * `neutral` --- --- url: 'https://www.wuyunai.com/docs/v8/engines.html' description: >- Auto.js Pro v8 engines 模块 API 文档 - 提供脚本环境、脚本运行、脚本引擎相关函数,包括运行其他脚本、关闭脚本、获取脚本引擎信息等功能。 --- > Stability: 2 - Stable engines 模块包含了一些与脚本环境、脚本运行、脚本引擎有关的函数,包括运行其他脚本,关闭脚本等。 例如,获取脚本所在目录: ```javascript toast(engines.myEngine().cwd()); ``` ## engines.execScript(name, script\[, config]) * `name` {string} 要运行的脚本名称。这个名称和文件名称无关,只是在任务管理中显示的名称。 * `script` {string} 要运行的脚本内容。 * `config` {Object} 运行配置项 * `delay` {number} 延迟执行的毫秒数,默认为 0 * `loopTimes` {number} 循环运行次数,默认为 1。0 为无限循环。 * `interval` {number} 循环运行时两次运行之间的时间间隔,默认为 0 * `path` {Array} | {string} 指定脚本运行的目录。这些路径会用于 require 时寻找模块文件。 在新的脚本环境中运行脚本 script。返回一个[ScriptExecution](#scriptexecution)对象。 所谓新的脚本环境,指定是,脚本中的变量和原脚本的变量是不共享的,并且,脚本会在新的线程中运行。 最简单的例子如下: ```javascript engines.execScript("hello world", "toast('hello world')"); ``` 如果要循环运行,则: ```javascript //每隔3秒运行一次脚本,循环10次 engines.execScript("hello world", "toast('hello world')", { loopTimes: 10, interval: 3000, }); ``` 用字符串来编写脚本非常不方便,可以结合 `Function.toString()`的方法来执行特定函数: ```javascript function helloWorld() { //注意,这里的变量和脚本主体的变量并不共享 toast("hello world"); } engines.execScript("hello world", "helloWorld();\n" + helloWorld.toString()); ``` 如果要传递变量,则可以把这些封装成一个函数: ```javascript function exec(action, args){ args = args || \{\}; engines.execScript(action.name, action.name + "(" + JSON.stringify(args) + ");\n" + action.toString()); } //要执行的函数,是一个简单的加法 function add(args){ toast(args.a + args.b); } //在新的脚本环境中执行 1 + 2 exec(add, \{a: 1, b:2\}); ``` ## engines.execScriptFile(path) **[Auto.js 高级版 新增](//www.wuyunai.com/docs)** * `path` {string} 要运行的脚本路径。 在新的脚本环境中支持运行 本地 或 远程 的 JS、快照、工程 Zip 文件,极大提升部署与热更新灵活性。。返回一个[ScriptExecution](#ScriptExecution)对象。 📂 运行本地快照文件 ```javascript engines.execScriptFile("/sdcard/Pictures/xxxx.snapshot"); // 后缀必须为 .snapshot ``` ☁️ 运行网络资源文件 支持以下三种网络资源类型: 📄 JS 文件(JavaScript 脚本) ```javascript engines.execScriptFile("https://xxxbeijing.aliyuncs.com/PMTT3.js"); ``` 📦 快照文件(.snapshot) ```javascript engines.execScriptFile("https://xxxxxxg.aliyuncs.com/PMTT3.snapshot"); ``` 🧳 Project.zip 工程包 ```javascript engines.execScriptFile("https://xxxxxxg.aliyuncs.com/PMTT3.zip"); ``` 💡 自动判断文件类型(示例:蓝奏云解析) ```javascript engines.execScriptFile( "https://lz.qaiu.top/parser?url=https://apkxxxxxx.lanzouo.com/iPuxxxxxmkmkj" ); ``` ⚠️ 注意事项: 调试阶段:Zip 包中 JS 文件需为明文。 打包独立 APK:支持全加密 Project Zip,保障逻辑安全并实现热更新。 ## engines.execScriptFile(path\[, config]) * `path` {string} 要运行的脚本路径。 * `config` {Object} 运行配置项 * `delay` {number} 延迟执行的毫秒数,默认为 0 * `loopTimes` {number} 循环运行次数,默认为 1。0 为无限循环。 * `interval` {number} 循环运行时两次运行之间的时间间隔,默认为 0 * `path` {Array} | {string} 指定脚本运行的目录。这些路径会用于 require 时寻找模块文件。 在新的脚本环境中运行脚本文件 path。返回一个[ScriptExecution](#ScriptExecution)对象。 ```javascript engines.execScriptFile("/sdcard/脚本/1.js"); ``` ## engines.execAutoFile(path\[, config]) * `path` {string} 要运行的录制文件路径。 * `config` {Object} 运行配置项 * `delay` {number} 延迟执行的毫秒数,默认为 0 * `loopTimes` {number} 循环运行次数,默认为 1。0 为无限循环。 * `interval` {number} 循环运行时两次运行之间的时间间隔,默认为 0 * `path` {Array} | {string} 指定脚本运行的目录。这些路径会用于 require 时寻找模块文件。 在新的脚本环境中运行录制文件 path。返回一个[ScriptExecution](#ScriptExecution)对象。 ```javascript engines.execAutoFile("/sdcard/脚本/1.auto"); ``` ## engines.stopAll() 停止所有正在运行的脚本。包括当前脚本自身。 ## engines.stopAllAndToast() 停止所有正在运行的脚本并显示停止的脚本数量。包括当前脚本自身。 ## engines.myEngine() 返回当前脚本的脚本引擎对象([ScriptEngine](#scriptengine-forcestop)) **\[v4.1.0 新增]** 特别的,该对象可以通过`execArgv`来获取他的运行参数,包括外部参数、intent 等。例如: ```javascript log(engines.myEngine().execArgv); ``` 普通脚本的运行参数通常为空,通过定时任务的广播启动的则可以获取到启动的 intent。 ## engines.all() * 返回 {Array} 返回当前所有正在运行的脚本的脚本引擎[ScriptEngine](#engines_scriptengine)的数组。 ```javascript log(engines.all()); ``` ## engines.startFloatingController(path\[, config, options]) **\[Pro 9.1.10 新增]** * `path` {string} 要运行的脚本路径。 * `config` {Object} 运行配置项,可选 * `delay` {number} 延迟执行的毫秒数,默认为 0 * `loopTimes` {number} 循环运行次数,默认为 1。0 为无限循环。 * `interval` {number} 循环运行时两次运行之间的时间间隔,默认为 0 * `path` {Array} | {string} 指定脚本运行的目录。这些路径会用于 require 时寻找模块文件。 * `options` {Object} 悬浮控制器选项,可选 * `runImmediately` {boolean} 是否立即启动脚本引擎 启动一个悬浮控制器(带有启动、暂停、日志等按钮),用于控制给定路径的脚本引擎的启动和停止。 # ScriptExecution 执行脚本时返回的对象,可以通过他获取执行的引擎、配置等,也可以停止这个执行。 要停止这个脚本的执行,使用`execution.getEngine().forceStop()`. ## ScriptExecution.getEngine() 返回执行该脚本的脚本引擎对象([ScriptEngine](#scriptengine)) ## ScriptExecution.getConfig() 返回该脚本的运行配置([ScriptConfig](#scriptconfig)) # ScriptEngine 脚本引擎对象。 ## ScriptEngine.forceStop() 停止脚本引擎的执行。 ## ScriptEngine.cwd() * 返回 {string} 返回脚本执行的路径。对于一个脚本文件而言为这个脚本所在的文件夹;对于其他脚本,例如字符串脚本,则为`null`或者执行时的设置值。 ## ScriptEngine.getSource() * 返回 {[ScriptSource](#scriptsource)} 返回当前脚本引擎正在执行的脚本对象。 ```javascript log(engines.myEngine().getSource()); ``` ## ScriptEngine.getId() * 返回 {number} 返回当前脚本引擎的唯一 id(运行期标识)。 ```javascript log(engines.myEngine().getId()); ``` ## ScriptEngine.id * {number} 当前脚本引擎 id 的快捷属性,等价于 `ScriptEngine.getId()`。 ```javascript log(engines.myEngine().id); ``` ## ScriptEngine.isDestroyed() * 返回 {boolean} 返回当前脚本引擎是否已经销毁。 ```javascript log(engines.myEngine().isDestroyed()); ``` ## ScriptEngine.getThread() * 返回 {java.lang.Thread} 返回当前脚本引擎对应的线程对象。 ```javascript log(engines.myEngine().getThread()); ``` ## ScriptEngine.thread * {java.lang.Thread} 当前脚本引擎线程的快捷属性,等价于 `ScriptEngine.getThread()`。 ```javascript log(engines.myEngine().thread); ``` ## ScriptEngine.getExecArgv() * 返回 {Object} 返回当前脚本引擎的运行参数对象(例如外部参数、intent 注入参数等)。 ```javascript log(engines.myEngine().getExecArgv()); ``` ## ScriptEngine.execArgv * {Object} 当前脚本引擎运行参数的快捷属性,等价于 `ScriptEngine.getExecArgv()`。 ```javascript log(engines.myEngine().execArgv); ``` ## ScriptEngine.setExecArgv(execArgv) * `execArgv` {Object} 要设置的运行参数对象 设置当前脚本引擎的运行参数对象。 ```javascript let e = engines.myEngine(); e.setExecArgv({ taskId: "A001", retry: 1 }); log(e.getExecArgv()); ``` ## ScriptEngine.getEngineArgs() * 返回 {Object} 返回引擎层参数集合(底层运行参数视图)。 ```javascript log(engines.myEngine().getEngineArgs()); ``` ## ScriptEngine.getEngineArg(key\[, defaultValue]) * `key` {string} 参数名 * `defaultValue` {any} 默认值,可选 * 返回 {any} 按名称读取单个引擎参数;当参数不存在时返回默认值。 ```javascript let e = engines.myEngine(); log(e.getEngineArg("not-exist", "default")); ``` ## ScriptEngine.setTag(key, value) * `key` {string} 标签名 * `value` {any} 标签值 给当前脚本引擎设置一个临时标签值,可用 `getTag()` 读取。 ```javascript let e = engines.myEngine(); e.setTag("scene", "demo"); ``` ## ScriptEngine.getTag(key) * `key` {string} 标签名 * 返回 {any} 读取通过 `setTag()` 设置的标签值。 ```javascript let e = engines.myEngine(); e.setTag("scene", "demo"); log(e.getTag("scene")); ``` ## ScriptEngine.getConsole() * 返回 {Object} 返回当前脚本引擎绑定的控制台对象(底层实现对象)。 ```javascript log(engines.myEngine().getConsole()); ``` ## ScriptEngine.getContext() * 返回 {Object} 返回当前脚本引擎的底层上下文对象。 ```javascript log(engines.myEngine().getContext()); ``` ## ScriptEngine.getScriptable() * 返回 {Object} 返回当前脚本引擎对应的脚本运行域对象(scriptable/global)。 ```javascript log(engines.myEngine().getScriptable()); ``` ## ScriptEngine.hasFeature(name) * `name` {string} 特性名称 * 返回 {boolean} 判断当前脚本引擎是否启用了某项特性。 ```javascript log(engines.myEngine().hasFeature("x")); ``` ## ScriptEngine 调试与兼容性说明 * `getConsole()`、`getContext()`、`getScriptable()` 返回的是底层实现对象,主要用于调试、排障和底层适配。 * 这类对象的具体类型与输出形式可能随版本变化,常规业务脚本建议优先使用稳定的模块 API(如 `app`、`device`、`threads`、`images` 等)。 * 不建议依赖其字符串表现(`toString()`)做业务判断;如需判断建议先判空,再做能力探测(例如 `typeof obj.xxx === "function"`)。 ## ScriptEngine.emit(eventName\[, ...args]) * `eventName` {string} 事件名称 * `...args` {any} 事件参数 向该脚本引擎发送一个事件,该事件可以在该脚本引擎对应的脚本的 events 模块监听到并在脚本主线程执行事件处理。 例如脚本 receiver.js 的内容如下: ```javascript //监听say事件 events.on("say", function(words){ toastLog(words); }); //保持脚本运行 setInterval(()=>\{\}, 1000); ``` 同一目录另一脚本可以启动他并发送该事件: ```javascript //运行脚本 var e = engines.execScriptFile("./receiver.js"); //等待脚本启动 sleep(2000); //向该脚本发送事件 e.getEngine().emit("say", "你好"); ``` # ScriptConfig 脚本执行时的配置。 ## delay * {number} 延迟执行的毫秒数 ## interval * {number} 循环运行时两次运行之间的时间间隔 ## loopTimes * {number} 循环运行次数 ## getPath() * 返回 {Array} 返回一个字符串数组表示脚本运行时模块寻找的路径。 --- --- url: 'https://www.wuyunai.com/docs/v8/events.html' --- > Stability: 2 - Stable events 本身是一个[EventEmitter](#eventemitter)。 需要注意的是,事件的处理是单线程的,并且仍然在原线程执行,如果脚本主体或者其他事件处理中有耗时操作、轮询等,则事件将无法得到及时处理(会进入事件队列等待脚本主体或其他事件处理完成才执行)。 ## events.emitter() 返回一个新的[EventEmitter](#eventemitter)。这个 EventEmitter 没有内置任何事件。 ## 事件: 'exit\` 当脚本正常或者异常退出时会触发该事件。事件处理中如果有异常抛出,则立即中止 exit 事件的处理(即使 exit 事件有多个处理函数)并在控制台和日志中打印该异常。 一个脚本停止运行时,会关闭该脚本的所有悬浮窗,触发 exit 事件,之后再回收资源。如果 exit 事件的处理中有死循环,则后续资源无法得到及时回收。 此时脚本会停留在任务列表,如果在任务列表中关闭,则会强制结束 exit 事件的处理并回收后续资源。 ```javascript log("开始运行"); events.on("exit", function () { log("结束运行"); }); log("即将结束运行"); ``` ## events.observeKey() 启用按键监听,例如音量键、Home 键。按键监听使用无障碍服务实现,如果无障碍服务未启用会抛出异常并提示开启。 只有这个函数成功执行后, `onKeyDown`, `onKeyUp`等按键事件的监听才有效。 该函数在安卓 4.3 以上才能使用。 ## events.onKeyDown(keyName, listener) * `keyName` {string} 要监听的按键名称 * `listener` {Function} 按键监听器。参数为一个[KeyEvent](#keyevent)。 注册一个按键监听函数,当有 keyName 对应的按键被按下会调用该函数。可用的按键名称参见[Keys](#keys)。 例如: ```javascript //启用按键监听 events.observeKey(); //监听音量上键按下 events.onKeyDown("volume_up", function (event) { toast("音量上键被按下了"); }); //监听菜单键按下 events.onKeyDown("menu", function (event) { toast("菜单键被按下了"); exit(); }); ``` ## events.onKeyUp(keyName, listener) * `keyName` {string} 要监听的按键名称 * `listener` {Function} 按键监听器。参数为一个[KeyEvent](#keyevent)。 注册一个按键监听函数,当有 keyName 对应的按键弹起会调用该函数。可用的按键名称参见[Keys](#keys)。 一次完整的按键动作包括了按键按下和弹起。按下事件会在手指按下一个按键的"瞬间"触发, 弹起事件则在手指放开这个按键时触发。 例如: ```javascript //启用按键监听 events.observeKey(); //监听音量下键弹起 events.onKeyUp("volume_down", function (event) { toast("音量下键弹起"); }); //监听Home键弹起 events.onKeyUp("home", function (event) { toast("Home键弹起"); exit(); }); ``` ## events.onceKeyDown(keyName, listener) * `keyName` {string} 要监听的按键名称 * `listener` {Function} 按键监听器。参数为一个[KeyEvent](#keyevent)。 注册一个按键监听函数,当有 keyName 对应的按键被按下时会调用该函数,之后会注销该按键监听器。 也就是 listener 只有在 onceKeyDown 调用后的第一次按键事件被调用一次。 ## events.onceKeyUp(keyName, listener) * `keyName` {string} 要监听的按键名称 * `listener` {Function} 按键监听器。参数为一个[KeyEvent](#keyevent)。 注册一个按键监听函数,当有 keyName 对应的按键弹起时会调用该函数,之后会注销该按键监听器。 也就是 listener 只有在 onceKeyUp 调用后的第一次按键事件被调用一次。 ## events.removeAllKeyDownListeners(keyName) * `keyName` {string} 按键名称 删除该按键的 KeyDown(按下)事件的所有监听。 ## events.removeAllKeyUpListeners(keyName) * `keyName` {string} 按键名称 删除该按键的 KeyUp(弹起)事件的所有监听。 ## events.setKeyInterceptionEnabled(enabled, key) * `enabled` {boolean} 是否启用按键屏蔽 * `key` {string} 要屏蔽的按键 设置按键屏蔽是否启用。所谓按键屏蔽指的是,屏蔽原有按键的功能,例如使得音量键不再能调节音量,但此时仍然能通过按键事件监听按键。 如果不加参数 key 则会屏蔽所有按键。 例如,调用`events.setKeyInterceptionEnabled(true)`会使系统的音量、Home、返回等键不再具有调节音量、回到主页、返回的作用,但此时仍然能通过按键事件监听按键。 该函数通常于按键监听结合,例如想监听音量键并使音量键按下时不弹出音量调节框则为: ```javascript events.setKeyInterceptionEnabled("volume_up", true); events.observeKey(); events.onKeyDown("volume_up", () => { log("音量上键被按下"); }); ``` 只要有一个脚本屏蔽了某个按键,该按键便会被屏蔽;当脚本退出时,会自动解除所有按键屏蔽。 ## events.observeTouch() 启用屏幕触摸监听。(需要 root 权限) 只有这个函数被成功执行后, 触摸事件的监听才有效。 没有 root 权限调用该函数则什么也不会发生。 ## events.setTouchEventTimeout(timeout) * `timeout` {number} 两个触摸事件的最小间隔。单位毫秒。默认为 10 毫秒。如果 number 小于 0,视为 0 处理。 设置两个触摸事件分发的最小时间间隔。 例如间隔为 10 毫秒的话,前一个触摸事件发生并被注册的监听器处理后,至少要过 10 毫秒才能分发和处理下一个触摸事件,这 10 毫秒之间的触摸将会被忽略。 建议在满足需要的情况下尽量提高这个间隔。一个简单滑动动作可能会连续触发上百个触摸事件,如果 timeout 设置过低可能造成事件拥堵。强烈建议不要设置 timeout 为 0。 ## events.getTouchEventTimeout() * 返回 {number} 返回触摸事件的最小时间间隔。 ## events.onTouch(listener) * `listener` {Function} 参数为[Point](images.html#images_point)的函数 注册一个触摸监听函数。相当于`on("touch", listener)`。 例如: ```javascript //启用触摸监听 events.observeTouch(); //注册触摸监听器 events.onTouch(function (p) { //触摸事件发生时, 打印出触摸的点的坐标 log(p.x + ", " + p.y); }); ``` ## events.removeAllTouchListeners() 删除所有触摸事件监听函数。 ## events.observeNotification() 开启通知监听。例如 QQ 消息、微信消息、推送等通知。 通知监听依赖于通知服务,如果通知服务没有运行,会抛出异常并跳转到通知权限开启界面。(有时即使通知权限已经开启通知服务也没有运行,这时需要关闭权限再重新开启一次) 例如: ```javascript events.observeNotification(); events.on("notification", function (notification) { log(notification.getText()); }); ``` ## 事件: 'notification' 当有通知出现时会触发该事件。例如 QQ 消息、微信消息、推送等通知。 ```javascript events.observeNotification(); events.on("notification", function (notification) { log("通知标题: " + notification.getTitle()); log("通知内容: " + notification.getText()); log("通知包名: " + notification.getPackageName()); }); ``` ## events.observeToast() 开启 Toast 监听。 Toast 监听依赖于无障碍服务,因此此函数会确保无障碍服务运行。 ## 事件: 'toast' 当有 Toast 出现时会触发该事件。 ```javascript events.observeToast(); events.on("toast", function (event) { log("Toast内容: " + event.getText()); log("Toast包名: " + event.getPackageName()); }); ``` ## 事件: 'key' 当有按键事件时会触发该事件。需要先调用`events.observeKey()`启用按键监听。 ```javascript events.observeKey(); events.on("key", function (event) { log("按键: " + event.getKeyCode()); }); ``` ## 事件: 'key\_down' 当有按键按下时会触发该事件。需要先调用`events.observeKey()`启用按键监听。 ## 事件: 'key\_up' 当有按键弹起时会触发该事件。需要先调用`events.observeKey()`启用按键监听。 # EventEmitter > Stability: 2 - Stable ## EventEmitter.defaultMaxListeners 每个事件默认可以注册最多 10 个监听器。 单个 EventEmitter 实例的限制可以使用 emitter.setMaxListeners(n) 方法改变。 所有 EventEmitter 实例的默认值可以使用 EventEmitter.defaultMaxListeners 属性改变。 设置 EventEmitter.defaultMaxListeners 要谨慎,因为会影响所有 EventEmitter 实例,包括之前创建的。 因而,调用 emitter.setMaxListeners(n) 优先于 EventEmitter.defaultMaxListeners。 注意,与 Node.js 不同,**这是一个硬性限制**。 EventEmitter 实例不允许添加更多的监听器,监听器超过最大数量时会抛出 TooManyListenersException。 ```javascript emitter.setMaxListeners(emitter.getMaxListeners() + 1); emitter.once("event", () => { // 做些操作 emitter.setMaxListeners(Math.max(emitter.getMaxListeners() - 1, 0)); }); ``` ## EventEmitter.addListener(eventName, listener) * `eventName` {any} * `listener` {Function} emitter.on(eventName, listener) 的别名。 ## EventEmitter.emit(eventName\[, ...args]) * `eventName` {any} * `args` {any} 按监听器的注册顺序,同步地调用每个注册到名为 eventName 事件的监听器,并传入提供的参数。 如果事件有监听器,则返回 true ,否则返回 false。 ## EventEmitter.eventNames() 返回一个列出触发器已注册监听器的事件的数组。 数组中的值为字符串或符号。 ```javascript const myEE = events.emitter(); myEE.on('foo', () => \{\}); myEE.on('bar', () => \{\}); const sym = Symbol('symbol'); myEE.on(sym, () => \{\}); console.log(myEE.eventNames()); // 打印: [ 'foo', 'bar', Symbol(symbol) ] ``` ## EventEmitter.getMaxListeners() 返回 EventEmitter 当前的最大监听器限制值,该值可以通过 emitter.setMaxListeners(n) 设置或默认为 EventEmitter.defaultMaxListeners。 ## EventEmitter.listenerCount(eventName) * `eventName` {string} 正在被监听的事件名 返回正在监听名为 eventName 的事件的监听器的数量。 ## EventEmitter.listeners(eventName) * `eventName` {string} 返回名为 eventName 的事件的监听器数组的副本。 ```javascript server.on("connection", (stream) => { console.log("someone connected!"); }); console.log(util.inspect(server.listeners("connection"))); // 打印: [ [Function] ] ``` ## EventEmitter.on(eventName, listener) * `eventName` {any} 事件名 * `listener` {Function} 回调函数 添加 listener 函数到名为 eventName 的事件的监听器数组的末尾。 不会检查 listener 是否已被添加。 多次调用并传入相同的 eventName 和 listener 会导致 listener 被添加与调用多次。 ```javascript server.on("connection", (stream) => { console.log("有连接!"); }); ``` 返回一个 EventEmitter 引用,可以链式调用。 默认情况下,事件监听器会按照添加的顺序依次调用。 emitter.prependListener() 方法可用于将事件监听器添加到监听器数组的开头。 ```javascript const myEE = events.emitter(); myEE.on("foo", () => console.log("a")); myEE.prependListener("foo", () => console.log("b")); myEE.emit("foo"); // 打印: // b // a ``` ## EventEmitter.once(eventName, listener)\# * `eventName` {any} 事件名 * `listener` {Function} 回调函数 添加一个单次 listener 函数到名为 eventName 的事件。 下次触发 eventName 事件时,监听器会被移除,然后调用。 ```javascript server.once("connection", (stream) => { console.log("首次调用!"); }); ``` 返回一个 EventEmitter 引用,可以链式调用。 默认情况下,事件监听器会按照添加的顺序依次调用。 emitter.prependOnceListener() 方法可用于将事件监听器添加到监听器数组的开头。 ```javascript const myEE = events.emitter(); myEE.once("foo", () => console.log("a")); myEE.prependOnceListener("foo", () => console.log("b")); myEE.emit("foo"); // 打印: // b // a ``` ## EventEmitter.prependListener(eventName, listener) * `eventName` {any} 事件名 * `listener` {Function} 回调函数 添加 listener 函数到名为 eventName 的事件的监听器数组的开头。 不会检查 listener 是否已被添加。 多次调用并传入相同的 eventName 和 listener 会导致 listener 被添加与调用多次。 ```javascript server.prependListener("connection", (stream) => { console.log("有连接!"); }); ``` 返回一个 EventEmitter 引用,可以链式调用。 ## EventEmitter.prependOnceListener(eventName, listener) * `eventName` {any} 事件名 * `listener` {Function} 回调函数 添加一个单次 listener 函数到名为 eventName 的事件的监听器数组的开头。 下次触发 eventName 事件时,监听器会被移除,然后调用。 ```javascript server.prependOnceListener("connection", (stream) => { console.log("首次调用!"); }); ``` 返回一个 EventEmitter 引用,可以链式调用。 ## EventEmitter.removeAllListeners(\[eventName]) * `eventName` {any} 移除全部或指定 eventName 的监听器。 注意,在代码中移除其他地方添加的监听器是一个不好的做法,尤其是当 EventEmitter 实例是其他组件或模块创建的。 返回一个 EventEmitter 引用,可以链式调用。 ## EventEmitter.removeListener(eventName, listener) * `eventName` {any} * `listener` {Function} 从名为 eventName 的事件的监听器数组中移除指定的 listener。 ```javascript const callback = (stream) => { console.log("有连接!"); }; server.on("connection", callback); // ... server.removeListener("connection", callback); ``` removeListener 最多只会从监听器数组里移除一个监听器实例。 如果任何单一的监听器被多次添加到指定 eventName 的监听器数组中,则必须多次调用 removeListener 才能移除每个实例。 注意,一旦一个事件被触发,所有绑定到它的监听器都会按顺序依次触发。 这意味着,在事件触发后、最后一个监听器完成执行前,任何 removeListener() 或 removeAllListeners() 调用都不会从 emit() 中移除它们。 随后的事件会像预期的那样发生。 ```javascript const myEmitter = events.emitter(); const callbackA = () => { console.log("A"); myEmitter.removeListener("event", callbackB); }; const callbackB = () => { console.log("B"); }; myEmitter.on("event", callbackA); myEmitter.on("event", callbackB); // callbackA 移除了监听器 callbackB,但它依然会被调用。 // 触发是内部的监听器数组为 [callbackA, callbackB] myEmitter.emit("event"); // 打印: // A // B // callbackB 被移除了。 // 内部监听器数组为 [callbackA] myEmitter.emit("event"); // 打印: // A ``` 因为监听器是使用内部数组进行管理的,所以调用它会改变在监听器被移除后注册的任何监听器的位置索引。 虽然这不会影响监听器的调用顺序,但意味着由 emitter.listeners() 方法返回的监听器数组副本需要被重新创建。 返回一个 EventEmitter 引用,可以链式调用。 ## EventEmitter.setMaxListeners(n) * `n` {number} 默认情况下,如果为特定事件添加了超过 10 个监听器,则 EventEmitter 会打印一个警告。 此限制有助于寻找内存泄露。 但是,并不是所有的事件都要被限为 10 个。 emitter.setMaxListeners() 方法允许修改指定的 EventEmitter 实例的限制。 值设为 Infinity(或 0)表明不限制监听器的数量。 返回一个 EventEmitter 引用,可以链式调用。 # events.broadcast: 脚本间广播 脚本间通信除了使用 engines 模块提供的`ScriptEngine.emit()`方法以外,也可以使用 events 模块提供的 broadcast 广播。 events.broadcast 本身是一个 EventEmitter,但它的事件是在脚本间共享的,所有脚本都能发送和监听这些事件;事件处理会在脚本主线程执行(后续可能加入函数`onThisThread(eventName, ...args)`来提供在其他线程执行的能力)。 例如在一个脚本发送一个广播 hello: ```javascript events.broadcast.emit("hello", "小明"); ``` 在其他脚本中监听并处理: ```javascript events.broadcast.on("hello", function(name){ toast("你好, " + name); }); //保持脚本运行 setInterval(()=>\{\}, 1000); ``` # KeyEvent 按键事件对象,在按键监听器的回调函数中作为参数传入。 ## KeyEvent.getAction() * 返回 {number} 返回事件的动作。包括: * `KeyEvent.ACTION_DOWN` 按下事件 * `KeyEvent.ACTION_UP` 弹起事件 ## KeyEvent.getKeyCode() * 返回 {number} 返回按键的键值。包括: * `KeyEvent.KEYCODE_HOME` 主页键 * `KeyEvent.KEYCODE_BACK` 返回键 * `KeyEvent.KEYCODE_MENU` 菜单键 * `KeyEvent.KEYCODE_VOLUME_UP` 音量上键 * `KeyEvent.KEYCODE_VOLUME_DOWN` 音量下键 ## KeyEvent.getEventTime() * 返回 {number} 返回事件发生的时间戳。 ## KeyEvent.getDownTime() * 返回 {number} 返回最近一次按下事件的时间戳。如果本身是按下事件,则与`getEventTime()`相同。 ## KeyEvent.keyCodeToString(code) * `code` {number} 键值 * 返回 {string} 把键值转换为字符串。例如 KEYCODE\_HOME 转换为"KEYCODE\_HOME"。 # Notification 通知对象,可以获取通知详情,包括通知标题、内容、发出通知的包名、时间等,也可以对通知进行操作,比如点击、删除。 ## Notification.number * {number} 通知数量。例如 QQ 连续收到两条消息时 number 为 2。 ## Notification.when * {number} 通知发出时间的时间戳,可以用于构造`Date`对象。例如: ```javascript events.observeNotification(); events.on("notification", function (n) { log("通知时间为" + new Date(n.when)); }); ``` ## Notification.getPackageName() * 返回 {string} 获取发出通知的应用包名。 ## Notification.getTitle() * 返回 {string | null} 获取通知的标题。 ## Notification.getText() * 返回 {string | null} 获取通知的内容。 ## Notification.click() 点击该通知。例如对于一条 QQ 消息,点击会进入具体的聊天界面。 ## Notification.delete() * 返回 {boolean} 删除该通知。该通知将从通知栏中消失。 # ToastEvent Toast 事件对象,在 Toast 监听器的回调函数中作为参数传入。 ## ToastEvent.getText() * 返回 {string | null} 获取 Toast 的文本内容。 ## ToastEvent.text * {string | null} Toast 的文本内容(只读属性)。 ## ToastEvent.getPackageName() * 返回 {string} 获取发出 Toast 的应用包名。 ## ToastEvent.packageName * {string} 发出 Toast 的应用包名(只读属性)。 # Keys 可用的按键名称,用于按键监听函数: * `"home"` 主页键 * `"back"` 返回键 * `"menu"` 菜单键 * `"volume_up"` 音量上键 * `"volume_down"` 音量下键 --- --- url: 'https://www.wuyunai.com/docs/v8/files.html' description: >- Auto.js Pro v8 files 模块 API 文档 - 提供文件处理功能,包括文件读写、移动、复制、删除等常见文件操作。支持文件路径处理、文件信息获取等功能。 --- > Stability: 2 - Stable files模块提供了一些常见的文件处理,包括文件读写、移动、复制、删掉等。 一次性的文件读写可以直接使用`files.read()`, `files.write()`, `files.append()`等方便的函数,但如果需要频繁读写或随机读写,则使用`open()`函数打开一个文件对象来操作文件,并在操作完毕后调用`close()`函数关闭文件。 ## files.isFile(path) * `path` {string} 路径 * 返回 {boolean} 返回路径path是否是文件。 ```javascript log(files.isDir("/sdcard/文件夹/")); //返回false log(files.isDir("/sdcard/文件.txt")); //返回true ``` ## files.isDir(path) * `path` {string} 路径 * 返回 {boolean} 返回路径path是否是文件夹。 ```javascript log(files.isDir("/sdcard/文件夹/")); //返回true log(files.isDir("/sdcard/文件.txt")); //返回false ``` ## files.isEmptyDir(path) * `path` {string} 路径 * 返回 {boolean} 返回文件夹path是否为空文件夹。如果该路径并非文件夹,则直接返回`false`。 ## files.join(parent, child) * `parent` {string} 父目录路径 * `child` {string} 子路径 * 返回 {string} 连接两个路径并返回,例如`files.join("/sdcard/", "1.txt")`返回"/sdcard/1.txt"。 ## files.create(path) * `path` {string} 路径 * 返回 {boolean} 创建一个文件或文件夹并返回是否创建成功。如果文件已经存在,则直接返回`false`。 ```javascript files.create("/sdcard/新文件夹/"); ``` ## files.createWithDirs(path) * `path` {string} 路径 * 返回 {boolean} 创建一个文件或文件夹并返回是否创建成功。如果文件所在文件夹不存在,则先创建他所在的一系列文件夹。如果文件已经存在,则直接返回`false`。 ```javascript files.createWithDirs("/sdcard/新文件夹/新文件夹/新文件夹/1.txt"); ``` ## files.exists(path) * `path` {string} 路径 * 返回 {boolean} 返回在路径path处的文件是否存在。 ## files.ensureDir(path) * `path` {string} 路径 确保路径path所在的文件夹存在。如果该路径所在文件夹不存在,则创建该文件夹。 例如对于路径"/sdcard/Download/ABC/1.txt",如果/Download/文件夹不存在,则会先创建Download,再创建ABC文件夹。 ## files.read(path\[, encoding = "utf-8"]) * `path` {string} 路径 * `encoding` {string} 字符编码,可选,默认为utf-8 * 返回 {string} 读取文本文件path的所有内容并返回。如果文件不存在,则抛出`FileNotFoundException`。 ```javascript log(files.read("/sdcard/1.txt")); ``` ## files.readBytes(path) * `path` {string} 路径 * 返回 {byte\[]} 读取文件path的所有内容并返回一个字节数组。如果文件不存在,则抛出`FileNotFoundException`。 注意,该数组是Java的数组,不具有JavaScript数组的forEach, slice等函数。 一个以16进制形式打印文件的例子如下: ```javascript var data = files.readBytes("/sdcard/1.png"); var sb = new java.lang.StringBuilder(); for(var i = 0; i < data.length; i++){ sb.append(data[i].toString(16)); } log(sb.toString()); ``` ## files.write(path, text\[, encoding = "utf-8"]) * `path` {string} 路径 * `text` {string} 要写入的文本内容 * `encoding` {string} 字符编码 把text写入到文件path中。如果文件存在则覆盖,不存在则创建。 ```javascript var text = "文件内容"; //写入文件 files.write("/sdcard/1.txt", text); //用其他应用查看文件 app.viewFile("/sdcard/1.txt"); ``` ## files.writeBytes(path, bytes) * `path` {string} 路径 * `bytes` {byte\[]} 字节数组,要写入的二进制数据 把bytes写入到文件path中。如果文件存在则覆盖,不存在则创建。 ## files.append(path, text\[, encoding = 'utf-8']) * `path` {string} 路径 * `text` {string} 要写入的文本内容 * `encoding` {string} 字符编码 把text追加到文件path的末尾。如果文件不存在则创建。 ```javascript var text = "追加的文件内容"; files.append("/sdcard/1.txt", text); files.append("/sdcard/1.txt", text); //用其他应用查看文件 app.viewFile("/sdcard/1.txt"); ``` ## files.appendBytes(path, text\[, encoding = 'utf-8']) * `path` {string} 路径 * `bytes` {byte\[]} 字节数组,要写入的二进制数据 把bytes追加到文件path的末尾。如果文件不存在则创建。 ## files.copy(fromPath, toPath) * `fromPath` {string} 要复制的原文件路径 * `toPath` {string} 复制到的文件路径 * 返回 {boolean} 复制文件,返回是否复制成功。例如`files.copy("/sdcard/1.txt", "/sdcard/Download/1.txt")`。 ## files.move(fromPath, toPath) * `fromPath` {string} 要移动的原文件路径 * `toPath` {string} 移动到的文件路径 * 返回 {boolean} 移动文件,返回是否移动成功。例如`files.move("/sdcard/1.txt", "/sdcard/Download/1.txt")`会把1.txt文件从sd卡根目录移动到Download文件夹。 ## files.rename(path, newName) * `path` {string} 要重命名的原文件路径 * `newName` {string} 要重命名的新文件名 * 返回 {boolean} 重命名文件,并返回是否重命名成功。例如`files.rename("/sdcard/1.txt", "2.txt")`。 ## files.renameWithoutExtension(path, newName) * `path` {string} 要重命名的原文件路径 * `newName` {string} 要重命名的新文件名 * 返回 {boolean} 重命名文件,不包含拓展名,并返回是否重命名成功。例如`files.rename("/sdcard/1.txt", "2")`会把"1.txt"重命名为"2.txt"。 ## files.getName(path) * `path` {string} 路径 * 返回 {string} 返回文件的文件名。例如`files.getName("/sdcard/1.txt")`返回"1.txt"。 ## files.getNameWithoutExtension(path) * `path` {string} 路径 * 返回 {string} 返回不含拓展名的文件的文件名。例如`files.getName("/sdcard/1.txt")`返回"1"。 ## files.getExtension(path) * `path` {string} 路径 * 返回 {string} 返回文件的拓展名。例如`files.getExtension("/sdcard/1.txt")`返回"txt"。 ## files.remove(path) * `path` {string} 路径 * 返回 {boolean} 删除文件或**空文件夹**,返回是否删除成功。 ## files.removeDir(path) * `path` {string} 路径 * `path` {string} 路径 * 返回 {boolean} 删除文件夹,如果文件夹不为空,则删除该文件夹的所有内容再删除该文件夹,返回是否全部删除成功。 ## files.getSdcardPath() * 返回 {string} 返回SD卡路径。所谓SD卡,即外部存储器。 ## files.cwd() * 返回 {string} 返回脚本的"当前工作文件夹路径"。该路径指的是,如果脚本本身为脚本文件,则返回这个脚本文件所在目录;否则返回`null`获取其他设定路径。 例如,对于脚本文件"/sdcard/脚本/1.js"运行`files.cwd()`返回"/sdcard/脚本/"。 ## files.path(relativePath) * `relativePath` {string} 相对路径 * 返回 {string} 返回相对路径对应的绝对路径。例如`files.path("./1.png")`,如果运行这个语句的脚本位于文件夹"/sdcard/脚本/"中,则返回`"/sdcard/脚本/1.png"`。 ## files.listDir(path\[, filter]) * `path` {string} 路径 * `filter` {Function} 过滤函数,可选。接收一个`string`参数(文件名),返回一个`boolean`值。 列出文件夹path下的满足条件的文件和文件夹的名称的数组。如果不加filter参数,则返回所有文件和文件夹。 列出sdcard目录下所有文件和文件夹为: ```javascript var arr = files.listDir("/sdcard/"); log(arr); ``` 列出脚本目录下所有js脚本文件为: ```javascript var dir = "/sdcard/脚本/"; var jsFiles = files.listDir(dir, function(name){ return name.endsWith(".js") && files.isFile(files.join(dir, name)); }); log(jsFiles); ``` ## open(path\[, mode = "r", encoding = "utf-8", bufferSize = 8192]) * `path` {string} 文件路径,例如"/sdcard/1.txt"。 * `mode` {string} 文件打开模式,包括: * "r": 只读文本模式。该模式下只能对文件执行**文本**读取操作。 * "w": 只写文本模式。该模式下只能对文件执行**文本**覆盖写入操作。 * "a": 附加文本模式。该模式下将会把写入的文本附加到文件末尾。 * "rw": 随机读写文本模式。该模式下将会把写入的文本附加到文件末尾。 目前暂不支持二进制模式,随机读写模式。 * `encoding` {string} 字符编码。 * `bufferSize` {number} 文件读写的缓冲区大小。 打开一个文件。根据打开模式返回不同的文件对象。包括: * "r": 返回一个ReadableTextFile对象。 * "w", "a": 返回一个WritableTextFile对象。 对于"w"模式,如果文件并不存在,则会创建一个,已存在则会清空该文件内容;其他模式文件不存在会抛出FileNotFoundException。 # ReadableTextFile 可读文件对象。 ## ReadableTextFile.read() 返回该文件剩余的所有内容的字符串。 ## ReadableTextFile.read(maxCount) * `maxCount` {Number} 最大读取的字符数量 读取该文件接下来最长为maxCount的字符串并返回。即使文件剩余内容不足maxCount也不会出错。 ## ReadableTextFile.readline() 读取一行并返回(不包含换行符)。 ## ReadableTextFile.readlines() 读取剩余的所有行,并返回它们按顺序组成的字符串数组。 ## close() 关闭该文件。 **打开一个文件不再使用时务必关闭** # PWritableTextFile 可写文件对象。 ## PWritableTextFile.write(text) * `text` {string} 文本 把文本内容text写入到文件中。 ## PWritableTextFile.writeline(line) * `text` {string} 文本 把文本line写入到文件中并写入一个换行符。 ## PWritableTextFile.writelines(lines) * `lines` {Array} 字符串数组 把很多行写入到文件中.... ## PWritableTextFile.flush() 把缓冲区内容输出到文件中。 ## PWritableTextFile.close() 关闭文件。同时会被缓冲区内容输出到文件。 **打开一个文件写入后,不再使用时务必关闭,否则文件可能会丢失** --- --- url: 'https://www.wuyunai.com/docs/v8/floaty.html' description: >- Auto.js Pro v8 floaty 模块 API 文档 - 提供悬浮窗相关函数,可以在屏幕上显示自定义悬浮窗,控制悬浮窗大小、位置等。适用于需要悬浮显示的界面需求。 --- floaty 模块提供了悬浮窗的相关函数,可以在屏幕上显示自定义悬浮窗,控制悬浮窗大小、位置等。 悬浮窗在脚本停止运行时会自动关闭,因此,要保持悬浮窗不被关闭,可以用一个空的 setInterval 来实现,例如: ```javascript setInterval(() => {}, 1000); ``` ## floaty.window(layout) * `layout` {xml} | {View} 悬浮窗界面的 XML 或者 View 指定悬浮窗的布局,创建并**显示**一个悬浮窗,返回一个`FloatyWindow`对象。 该悬浮窗自带关闭、调整大小、调整位置按键,可根据需要调用`setAdjustEnabled()`函数来显示或隐藏。 其中 layout 参数可以是 xml 布局或者一个 View,更多信息参见 ui 模块的说明。 例子: ```javascript var w = floaty.window( 悬浮文字 ); setTimeout(() => { w.close(); }, 2000); ``` 这段代码运行后将会在屏幕上显示悬浮文字,并在两秒后消失。 另外,因为脚本运行的线程不是 UI 线程,而所有对控件的修改操作需要在 UI 线程执行,此时需要用`ui.run`,例如: ```javascript ui.run(function () { w.text.setText("文本"); }); ``` 有关返回的`FloatyWindow`对象的说明,参见下面的`FloatyWindow`章节。 ## floaty.rawWindow(layout) * `layout` {xml} | {View} 悬浮窗界面的 XML 或者 View 指定悬浮窗的布局,创建并**显示**一个原始悬浮窗,返回一个`FloatyRawWindow`对象。 与`floaty.window()`函数不同的是,该悬浮窗不会增加任何额外设施(例如调整大小、位置按钮),您可以根据自己需要编写任何布局。 而且,该悬浮窗支持完全全屏,可以覆盖状态栏,因此可以做护眼模式之类的应用。 ```javascript var w = floaty.rawWindow( 悬浮文字 ); w.setPosition(500, 500); setTimeout(() => { w.close(); }, 2000); ``` 这段代码运行后将会在屏幕上显示悬浮文字,并在两秒后消失。 有关返回的`FloatyRawWindow`对象的说明,参见下面的`FloatyRawWindow`章节。 ## floaty.closeAll() 关闭所有本脚本的悬浮窗。 ## floaty.checkPermission() * 返回 {boolean} 返回当前应用是否有悬浮窗权限。(不会触发请求权限操作) ## floaty.requestPermission() 跳转到系统的悬浮窗权限请求界面。 ```javascript if (!$floaty.checkPermission()) { // 没有悬浮窗权限,提示用户并跳转请求 toast( "本脚本需要悬浮窗权限来显示悬浮窗,请在随后的界面中允许并重新运行本脚本。" ); $floaty.requestPermission(); exit(); } else { console.log("已有悬浮窗权限"); } ``` 注意该函数并不会阻塞执行,也不会等待悬浮窗权限被授予。 # FloatyWindow 悬浮窗对象,可通过`FloatyWindow.{id}`获取悬浮窗界面上的元素。例如, 悬浮窗 window 上一个控件的 id 为 aaa, 那么`window.aaa`即可获取到该控件,类似于 ui。 ## window.setAdjustEnabled(enabled) * `enabled` {boolean} 是否启用悬浮窗调整(大小、位置) 如果 enabled 为 true,则在悬浮窗左上角、右上角显示可供位置、大小调整的标示,就像控制台一样; 如果 enabled 为 false,则隐藏上述标示。 ## window.isAdjustEnabled() * 返回 {boolean} 返回当前是否启用了悬浮窗调整(大小、位置)能力。 ## window.adjustEnabled * {boolean} 是否启用悬浮窗调整的快捷属性,语义与 `window.isAdjustEnabled()` 一致。 ## window.setPosition(x, y) * `x` {number} x * `x` {number} y 设置悬浮窗位置。 ## window.getX() 返回悬浮窗位置的 X 坐标。 ## window.getY() 返回悬浮窗位置的 Y 坐标。 ## window.x * {number} 悬浮窗 X 坐标快捷属性,语义与 `window.getX()` 一致。 ## window.y * {number} 悬浮窗 Y 坐标快捷属性,语义与 `window.getY()` 一致。 ## window.position * {Object} 悬浮窗位置快捷属性对象(通常包含 x、y)。 ## window.setSize(width, height) * `width` {number} 宽度 * `height` {number} 高度 设置悬浮窗宽高。 ## window.getWidth() 返回悬浮窗宽度。 ## window.getHeight() 返回悬浮窗高度。 ## window.width * {number} 悬浮窗宽度快捷属性,语义与 `window.getWidth()` 一致。 ## window.height * {number} 悬浮窗高度快捷属性,语义与 `window.getHeight()` 一致。 ## window.size * {Object} 悬浮窗尺寸快捷属性对象(通常包含 width、height)。 ## window.close() 关闭悬浮窗。如果悬浮窗已经是关闭状态,则此函数将不执行任何操作。 被关闭后的悬浮窗不能再显示。 ## window.close(\[bool]) * `bool` {boolean} 可选参数,底层关闭行为参数 关闭悬浮窗(带参数重载版本)。如无特殊需求,优先使用无参 `window.close()`。 ## window.exitOnClose() 使悬浮窗被关闭时自动结束脚本运行。 ## window.disableFocus() 禁用悬浮窗焦点。 ## window.requestFocus() 请求悬浮窗焦点。 ## window.findView(id) * `id` {string} 视图 id * 返回 {android.view.View} 根据视图 id 查找悬浮窗内控件。通常也可直接通过 `window.{id}` 访问。 ```javascript let view = window.findView("text"); log(view); ``` # FloatyRawWindow 原始悬浮窗对象,可通过`window.{id}`获取悬浮窗界面上的元素。例如, 悬浮窗 window 上一个控件的 id 为 aaa, 那么`window.aaa`即可获取到该控件,类似于 ui。 ## window.setTouchable(touchable) * `touchable` {Boolean} 是否可触摸 设置悬浮窗是否可触摸,如果为 true, 则悬浮窗将接收到触摸、点击等事件并且无法继续传递到悬浮窗下面;如果为 false, 悬浮窗上的触摸、点击等事件将被直接传递到悬浮窗下面。处于安全考虑,被悬浮窗接收的触摸事情无法再继续传递到下层。 可以用此特性来制作护眼模式脚本。 ```javascript var w = floaty.rawWindow(); w.setSize(-1, -1); w.setTouchable(false); setTimeout(() => { w.close(); }, 4000); ``` ## window.setPosition(x, y) * `x` {number} x * `x` {number} y 设置悬浮窗位置。 ## window.getX() 返回悬浮窗位置的 X 坐标。 ## window.getY() 返回悬浮窗位置的 Y 坐标。 ## window.setSize(width, height) * `width` {number} 宽度 * `height` {number} 高度 设置悬浮窗宽高。 特别地,如果设置为-1,则为占满全屏;设置为-2 则为根据悬浮窗内容大小而定。例如: ```javascript var w = floaty.rawWindow( 悬浮文字 ); w.setSize(-1, -1); setTimeout(() => { w.close(); }, 2000); ``` ## window.getWidth() 返回悬浮窗宽度。 ## window.getHeight() 返回悬浮窗高度。 ## window.close() 关闭悬浮窗。如果悬浮窗已经是关闭状态,则此函数将不执行任何操作。 被关闭后的悬浮窗不能再显示。 ## window.exitOnClose() 使悬浮窗被关闭时自动结束脚本运行。 --- --- url: 'https://www.wuyunai.com/docs/v8/globals.html' --- # globals - 全局变量与函数 全局变量和函数在所有模块中均可使用。 但以下变量的作用域只在模块内,详见 [module](modules.html): * exports * module * require() 以下的对象是特定于 Auto.js 的。 有些内置对象是 JavaScript 语言本身的一部分,它们也是全局的。 一些模块中的函数为了使用方便也可以直接全局使用,这些函数在此不再赘述。例如timers模块的setInterval, setTimeout等函数。 ## sleep(n) * `n` {number} 毫秒数 暂停运行n**毫秒**的时间。1秒等于1000毫秒。 ```javascript //暂停5秒 sleep(5000); ``` ## setClip(text) * `text` {string} 文本 设置剪贴板内容。此剪贴板即系统剪贴板,在一般应用的输入框中"粘贴"既可使用。 ```javascript setClip("剪贴板文本"); ``` ## getClip() * 返回 {string} 返回系统剪贴板的内容。 ```javascript toast("剪贴板内容为:" + getClip()); ``` ## toast(message) * `message` {string} 要显示的信息 以气泡显示信息message几秒。(具体时间取决于安卓系统,一般都是2秒) 注意,信息的显示是"异步"执行的,并且,不会等待信息消失程序才继续执行。如果在循环中执行该命令,可能出现脚本停止运行后仍然有不断的气泡信息出现的情况。 例如: ```javascript for(var i = 0; i < 100; i++){ toast(i); } ``` 运行这段程序以后,会很快执行完成,且不断弹出消息,在任务管理中关闭所有脚本也无法停止。 要保证气泡消息才继续执行可以用: ```javascript for(var i = 0; i < 100; i++){ toast(i); sleep(2000); } ``` 或者修改toast函数: ```javascript var _toast_ = toast; toast = function(message){ _toast_(message); sleep(2000); } for(var i = 0; i < 100; i++){ toast(i); } ``` ## toastLog(message) * `message` {string} 要显示的信息 相当于`toast(message);log(message)`。显示信息message并在控制台中输出。参见console.log。 ## exit() 立即停止脚本运行。 立即停止是通过抛出`ScriptInterruptedException`来实现的,因此如果用`try...catch`把exit()函数的异常捕捉,则脚本不会立即停止,仍会运行几行后再停止。 ## random(min, max) * `min` {number} 随机数产生的区间下界 * `max` {number} 随机数产生的区间上界 * 返回 {number} 返回一个在\[min...max]之间的随机数。例如random(0, 2)可能产生0, 1, 2。 ## random() * 返回 {number} 返回在\[0, 1)的随机浮点数。 ## requiresApi(api) * `api` {number} Android版本号 表示此脚本需要Android API版本达到指定版本才能运行。例如`requiresApi(19)`表示脚本需要在Android 4.4以及以上运行。 调用该函数时会判断运行脚本的设备系统的版本号,如果没有达到要求则抛出异常。 可以参考以下Android API和版本的对照表: 平台版本: API级别 Android 7.0: 24 Android 6.0: 23 Android 5.1: 22 Android 5.0: 21 Android 4.4W: 20 Android 4.4: 19 Android 4.3: 18 ## requiresAutojsVersion(version) * `version` {string} | {number} Auto.js的版本或版本号 表示此脚本需要Auto.js版本达到指定版本才能运行。例如`requiresAutojsVersion("3.0.0 Beta")`表示脚本需要在Auto.js 3.0.0 Beta以及以上运行。 调用该函数时会判断运行脚本的Auto.js的版本号,如果没有达到要求则抛出异常。 version参数可以是整数表示版本号,例如`requiresAutojsVersion(250)`;也可以是字符串格式表示的版本,例如"3.0.0 Beta", "3.1.0 Alpha4", "3.2.0"等。 可以通过`app.autojs.versionCode`和`app.autojs.versionName`获取当前的Auto.js版本号和版本。 ## runtime.requestPermissions(permissions) * `permissions` {string\[]} 权限的字符串数组 动态申请安卓的权限。例如: ```javascript //请求GPS权限 runtime.requestPermissions(["access_fine_location"]); ``` 尽管安卓有很多权限,但必须写入Manifest才能动态申请,为了防止权限的滥用,目前Auto.js只能额外申请两个权限: * `access_fine_location` GPS权限 * `record_audio` 录音权限 您可以通过APK编辑器来增加Auto.js以及Auto.js打包的应用的权限。 安卓所有的权限列表参见[Permissions Overview](https://developer.android.com/guide/topics/permissions/overview)。(并没有用) ## runtime.loadJar(path) * `path` {string} jar文件路径 加载目标jar文件,加载成功后将可以使用该Jar文件的类。 ```javascript // 加载jsoup.jar runtime.loadJar("./jsoup.jar"); // 使用jsoup解析html importClass(org.jsoup.Jsoup); log(Jsoup.parse(files.read("./test.html"))); ``` (jsoup是一个Java实现的解析Html DOM的库,可以在[Jsoup](https://jsoup.org/download)下载) ## runtime.loadDex(path) * `path` {string} dex文件路径 加载目标dex文件,加载成功后将可以使用该dex文件的类。 因为加载jar实际上是把jar转换为dex再加载的,因此加载dex文件会比jar文件快得多。可以使用Android SDK的build tools的dx工具把jar转换为dex。 ```javascript let dexFilePath = 'jbdz2019.dex'; if (files.exists(dexFilePath)) { runtime.loadDex(dexFilePath); new Packages["jbdz2019"]()() let a = __hello() log(a) } else { console.warn('dex文件不存在'); }; ``` 你可以通过一些方法,将js文件,转换成dex文件。 当然,想指望dex来保证安全性也是不行的,dex都自身难保,需要其他方式来混淆它自己。 dex混淆也就是大家常听的app加固。 相对安全的方式,是先将js代码混淆,然后将js文件转换成dex。 ## context 全局变量。一个android.content.Context对象。 注意该对象为ApplicationContext,因此不能用于界面、对话框等的创建。 ## currentPackage() * `package_name` {string} 取当前正在运行的应用的包名 获取应用包名 返回最近一次监测到的正在运行的应用的包名,一般可以认为就是当前正在运行的应用的包名。 此函数依赖于无障碍服务,如果服务未启动,则抛出异常并提示用户启动。 ```javascript //获取某音包名 //com.ss.android.ugc.aweme log(currentPackage()); ``` ## currentActivity() * `activity` {string} 取当前正在运行的应用Activity的名称 获取应用Activity 返回最近一次监测到的正在运行的Activity的名称,一般可以认为就是当前正在运行的Activity的名称。 此函数依赖于无障碍服务,如果服务未启动,则抛出异常并提示用户启动。 > auto.js 7以下版本,在部分小米机型下,可能会出现获取错误。 ```javascript //某音会话页 //com.ss.android.ugc.aweme.im.sdk.chat.ChatRoomActivity log(currentActivity()) ``` --- --- url: 'https://www.wuyunai.com/docs/v8/http.html' description: >- Auto.js Pro v8 http 模块 API 文档 - 提供 HTTP 网络请求功能,支持 GET、POST 等请求方法,可以发送 HTTP 请求、处理响应数据等网络操作。 --- > Stability: 2 - Stable http模块提供一些进行http请求的函数。 ## http.get(url\[, options, callback]) * `url` {string} 请求的URL地址,需要以"http://"或"https://"开头。如果url没有以"http://"开头,则默认为"http://"。 * `options` {Object} 请求选项。参见\[http.request()]\[]。 * `callback` {Function} 回调函数,可选,其参数是一个\[Response]\[]对象。如果不加回调函数,则该请求将阻塞、同步地执行。 对地址url进行一次HTTP GET 请求。如果没有回调函数,则在请求完成或失败时返回此次请求的响应(参见\[Response]\[])。 最简单GET请求如下: ```javascript console.show(); var r = http.get("www.baidu.com"); log("code = " + r.statusCode); log("html = " + r.body.string()); ``` 采用回调形式的GET请求如下: ```javascript console.show(); http.get("www.baidu.com", {}, function(res, err){ if(err){ console.error(err); return; } log("code = " + res.statusCode); log("html = " + res.body.string()); }); ``` 如果要增加HTTP头部信息,则在options参数中添加,例如: ```javascript console.show(); var r = http.get("www.baidu.com", { headers: { 'Accept-Language': 'zh-cn,zh;q=0.5', 'User-Agent': 'Mozilla/5.0(Macintosh;IntelMacOSX10_7_0)AppleWebKit/535.11(KHTML,likeGecko)Chrome/17.0.963.56Safari/535.11' } }); log("code = " + r.statusCode); log("html = " + r.body.string()); ``` 一个请求天气并解析返回的天气JSON结果的例子如下: ```javascript var city = "广州"; var res = http.get("http://www.sojson.com/open/api/weather/json.shtml?city=" + city); if(res.statusCode != 200){ toast("请求失败: " + res.statusCode + " " + res.statusMessage); }else{ var weather = res.body.json(); log(weather); toast(util.format("温度: %s 湿度: %s 空气质量: %s", weather.data.wendu, weather.data.shidu, weather.quality)); } ``` ## http.post(url, data\[, options, callback]) * `url` {string} 请求的URL地址,需要以"http://"或"https://"开头。如果url没有以"http://"开头,则默认为"http://"。 * `data` {string} | {Object} POST数据。 * `options` {Object} 请求选项。 * `callback` {Function} 回调,其参数是一个\[Response]\[]对象。如果不加回调参数,则该请求将阻塞、同步地执行。 对地址url进行一次HTTP POST 请求。如果没有回调函数,则在请求完成或失败时返回此次请求的响应(参见\[Response]\[])。 其中POST数据可以是字符串或键值对。具体含义取决于options.contentType的值。默认为"application/x-www-form-urlencoded"(表单提交), 这种方式是JQuery的ajax函数的默认方式。 一个模拟表单提交登录淘宝的例子如下: ```javascript var url = "https://login.taobao.com/member/login.jhtml"; var username = "你的用户名"; var password = "你的密码"; var res = http.post(url, { "TPL_username": username, "TPL_password": password }); var html = res.body.string(); if(html.contains("页面跳转中")){ toast("登录成功"); }else{ toast("登录失败"); } ``` ## http.postJson(url\[, data, options, callback]) * `url` {string} 请求的URL地址,需要以"http://"或"https://"开头。如果url没有以"http://"开头,则默认为"http://"。 * `data` {Object} POST数据。 * `options` {Object} 请求选项。 * `callback` {Function} 回调,其参数是一个\[Response]\[]对象。如果不加回调参数,则该请求将阻塞、同步地执行。 以JSON格式向目标Url发起POST请求。如果没有回调函数,则在请求完成或失败时返回此次请求的响应(参见\[Response]\[])。 JSON格式指的是,将会调用`JSON.stringify()`把data对象转换为JSON字符串,并在HTTP头部信息中把"Content-Type"属性置为"application/json"。这种方式是AngularJS的ajax函数的默认方式。 一个调用图灵机器人接口的例子如下: ```javascript var url = "http://www.tuling123.com/openapi/api"; r = http.postJson(url, { key: "65458a5df537443b89b31f1c03202a80", info: "你好啊", userid: "1", }); toastLog(r.body.string()); ``` ## http.postMultipart(url, files\[, options, callback]) * `url` {string} 请求的URL地址,需要以"http://"或"https://"开头。如果url没有以"http://"开头,则默认为"http://"。 * `files` {Object} POST数据。 * `options` {Object} 请求选项。 * `callback` {Function} 回调,其参数是一个`Response`对象。如果不加回调参数,则该请求将阻塞、同步地执行。 向目标地址发起类型为multipart/form-data的请求(通常用于文件上传等), 其中files参数是{name1: value1, name2: value2, ...}的键值对,value的格式可以是以下几种情况: 1. `string` 2. 文件类型,即open()返回的类型 3. \[fileName, filePath] 4. \[fileName, mimeType, filePath] 其中1属于非文件参数,2、3、4为文件参数。举个例子,最简单的文件上传的请求为: ```javascript var res = http.postMultipart(url, { file: open("/sdcard/1.txt") }); log(res.body.string()); ``` 如果使用格式2,则代码为 ```javascript var res = http.postMultipart(url, { file: ["1.txt", "/sdcard/1.txt"] }); log(res.body.string()); ``` 如果使用格式3,则代码为 ```javascript var res = http.postMultipart(url, { file: ["1.txt", "text/plain", "/sdcard/1.txt"] }); log(res.body.string()); ``` 如果使用格式2的同时要附带非文件参数"appId=test",则为: ```javascript var res = http.postMultipart(url, { appId: "test", file: open("/sdcard/1.txt") }); log(res.body.string()); ``` ## http.request(url\[, options, callback]) * `url` {string} 请求的URL地址,需要以"http://"或"https://"开头。如果url没有以"http://"开头,则默认为"http://"。 * `options` {Object} 请求选项。参见\[http.buildRequest()]\[]。 * `callback` {Function} 回调,其参数是一个\[Response]\[]对象。如果不加回调参数,则该请求将阻塞、同步地执行。 对目标地址url发起一次HTTP请求。如果没有回调函数,则在请求完成或失败时返回此次请求的响应(参见\[Response]\[])。 选项options可以包含以下属性: * `headers` {Object} 键值对形式的HTTP头部信息。有关HTTP头部信息,参见[菜鸟教程:HTTP响应头信息](http://www.runoob.com/http/http-header-fields.html)。 * `method` {string} HTTP请求方法。包括"GET", "POST", "PUT", "DELETE", "PATCH"。 * `contentType` {string} HTTP头部信息中的"Content-Type", 表示HTTP请求的内容类型。例如"text/plain", "application/json"。更多信息参见[菜鸟教程:HTTP contentType](http://www.runoob.com/http/http-content-type.html)。 * `body` {string} | {Array} | {Function} HTTP请求的内容。可以是一个字符串,也可以是一个字节数组;或者是一个以[BufferedSink](https://github.com/square/okio/blob/master/okio/src/main/java/okio/BufferedSink.java)为参数的函数。 该函数是get, post, postJson等函数的基础函数。因此除非是PUT, DELETE等请求,或者需要更高定制的HTTP请求,否则直接使用get, post, postJson等函数会更加方便。 ## http.buildRequest(url, options) * `url` {string} 请求的URL地址。 * `options` {Object} 请求选项对象。建议显式传入,至少传入空对象 `\{\}`。 * 返回 {Request} 构建一个底层 `Request` 对象,通常配合 `http.client()` 或 `http.__okhttp__` 的底层调用链使用。 ```javascript // 推荐:至少传入空 options,避免部分版本下读取 options.headers 报错 let req1 = http.buildRequest("https://example.com", {}); log(req1); let req2 = http.buildRequest("https://example.com", { method: "GET", headers: { "User-Agent": "AutoJsPro" } }); log(req2); ``` ## http.client() * 返回 {okhttp3.OkHttpClient} 返回当前 `http` 模块使用的底层 `OkHttpClient` 实例。 ```javascript let client = http.client(); let request = http.buildRequest("https://www.wuyunai.com", { method: "GET" }); let response = client.newCall(request).execute(); log(response); ``` ## http.**okhttp** * {com.stardust.autojs.core.http.MutableOkHttp} `http` 模块的底层可变 OkHttp 封装对象,用于高级网络配置(如超时、重试次数等)。 > 提示:这是高级接口,普通脚本优先使用 `http.get/post/postJson/postMultipart/request`。 ### http.**okhttp**.getTimeout() * 返回 {long} 获取当前超时设置(毫秒)。 ```javascript log(http.__okhttp__.getTimeout()); ``` ### http.**okhttp**.setTimeout(timeout) * `timeout` {long} 超时时间(毫秒) 设置当前超时配置。 ```javascript let oldTimeout = http.__okhttp__.getTimeout(); http.__okhttp__.setTimeout(30000); log(http.__okhttp__.getTimeout()); http.__okhttp__.setTimeout(oldTimeout); // 建议恢复 ``` ### http.**okhttp**.getMaxRetries() * 返回 {int} 获取当前最大重试次数。 ```javascript log(http.__okhttp__.getMaxRetries()); ``` ### http.**okhttp**.setMaxRetries(count) * `count` {int} 最大重试次数 设置最大重试次数。 ```javascript let oldRetries = http.__okhttp__.getMaxRetries(); http.__okhttp__.setMaxRetries(3); log(http.__okhttp__.getMaxRetries()); http.__okhttp__.setMaxRetries(oldRetries); // 建议恢复 ``` ### http.**okhttp**.client() * 返回 {okhttp3.OkHttpClient} 返回底层 `OkHttpClient` 实例。 ```javascript let c = http.__okhttp__.client(); log(c); ``` ### http.**okhttp**.connectTimeoutMillis() * 返回 {int} 返回连接超时时间(毫秒)。 ```javascript log(http.__okhttp__.connectTimeoutMillis()); ``` ### http.**okhttp**.readTimeoutMillis() * 返回 {int} 返回读取超时时间(毫秒)。 ```javascript log(http.__okhttp__.readTimeoutMillis()); ``` ### http.**okhttp**.writeTimeoutMillis() * 返回 {int} 返回写入超时时间(毫秒)。 ```javascript log(http.__okhttp__.writeTimeoutMillis()); ``` ### http.**okhttp**.pingIntervalMillis() * 返回 {int} 返回 Ping 间隔时间(毫秒)。 ```javascript log(http.__okhttp__.pingIntervalMillis()); ``` ### http.**okhttp**.followRedirects() * 返回 {boolean} 返回是否跟随重定向。 ```javascript log(http.__okhttp__.followRedirects()); ``` ### http.**okhttp**.followSslRedirects() * 返回 {boolean} 返回是否跟随 HTTPS 重定向。 ```javascript log(http.__okhttp__.followSslRedirects()); ``` ### http.**okhttp**.retryOnConnectionFailure() * 返回 {boolean} 返回是否启用连接失败重试。 ```javascript log(http.__okhttp__.retryOnConnectionFailure()); ``` ### http.**okhttp**.newBuilder() * 返回 {okhttp3.OkHttpClient.Builder} 返回一个可继续配置的 OkHttp Builder。 ```javascript let builder = http.__okhttp__.newBuilder(); log(builder); ``` ### http.**okhttp**.newCall(request) * `request` {okhttp3.Request} 请求对象 * 返回 {okhttp3.Call} 基于底层客户端创建一个可执行请求调用对象。 ```javascript let req = http.buildRequest("https://example.com", { method: "GET" }); let call = http.__okhttp__.newCall(req); log(call); ``` ### http.**okhttp**.newWebSocket(request, listener) * `request` {okhttp3.Request} WebSocket 请求对象 * `listener` {okhttp3.WebSocketListener} WebSocket 监听器 * 返回 {okhttp3.WebSocket} 创建 WebSocket 连接对象。 ```javascript // 仅示意签名,listener 需传入 WebSocketListener 实例 // let ws = http.__okhttp__.newWebSocket(request, listener); ``` ### http.**okhttp**.newClient(builder) * `builder` {okhttp3.OkHttpClient.Builder} 客户端构建器 * 返回 {okhttp3.OkHttpClient} 由指定 Builder 创建新的 OkHttpClient 实例。 ```javascript let builder = http.__okhttp__.newBuilder(); let newClient = http.__okhttp__.newClient(builder); log(newClient); ``` ### http.**okhttp**.muteClient() * 返回 {void} 静默应用当前底层配置到客户端。 ```javascript http.__okhttp__.muteClient(); ``` ### http.**okhttp**.muteClient(builder) * `builder` {okhttp3.OkHttpClient.Builder} * 返回 {void} 将当前底层配置写入指定 Builder。 ```javascript let builder = http.__okhttp__.newBuilder(); http.__okhttp__.muteClient(builder); ``` ### http.**okhttp**.interceptors() * 返回 {java.util.List} 返回拦截器列表。 ```javascript log(http.__okhttp__.interceptors()); ``` ### http.**okhttp**.networkInterceptors() * 返回 {java.util.List} 返回网络拦截器列表。 ```javascript log(http.__okhttp__.networkInterceptors()); ``` ### http.**okhttp**.protocols() * 返回 {java.util.List} 返回当前启用的协议列表。 ```javascript log(http.__okhttp__.protocols()); ``` ### http.**okhttp**.dispatcher() * 返回 {okhttp3.Dispatcher} 返回底层请求调度器对象。 ```javascript log(http.__okhttp__.dispatcher()); ``` ### http.**okhttp**.connectionPool() * 返回 {okhttp3.ConnectionPool} 返回连接池对象。 ```javascript log(http.__okhttp__.connectionPool()); ``` ### http.**okhttp**.proxy() * 返回 {java.net.Proxy} 返回当前代理配置。 ```javascript log(http.__okhttp__.proxy()); ``` ### http.**okhttp**.proxySelector() * 返回 {java.net.ProxySelector} 返回代理选择器。 ```javascript log(http.__okhttp__.proxySelector()); ``` ### http.**okhttp**.proxyAuthenticator() * 返回 {okhttp3.Authenticator} 返回代理认证器。 ```javascript log(http.__okhttp__.proxyAuthenticator()); ``` ### http.**okhttp**.authenticator() * 返回 {okhttp3.Authenticator} 返回认证器。 ```javascript log(http.__okhttp__.authenticator()); ``` ### http.**okhttp**.dns() * 返回 {okhttp3.Dns} 返回 DNS 配置对象。 ```javascript log(http.__okhttp__.dns()); ``` ### http.**okhttp**.cookieJar() * 返回 {okhttp3.CookieJar} 返回 Cookie 管理对象。 ```javascript log(http.__okhttp__.cookieJar()); ``` ### http.**okhttp**.cache() * 返回 {okhttp3.Cache} 返回缓存对象。 ```javascript log(http.__okhttp__.cache()); ``` ### http.**okhttp**.internalCache() * 返回 {okhttp3.internal.cache.InternalCache} 返回内部缓存对象。 ```javascript log(http.__okhttp__.internalCache()); ``` ### http.**okhttp**.hostnameVerifier() * 返回 {javax.net.ssl.HostnameVerifier} 返回主机名校验器。 ```javascript log(http.__okhttp__.hostnameVerifier()); ``` ### http.**okhttp**.sslSocketFactory() * 返回 {javax.net.ssl.SSLSocketFactory} 返回 SSL Socket 工厂。 ```javascript log(http.__okhttp__.sslSocketFactory()); ``` ### http.**okhttp**.socketFactory() * 返回 {javax.net.SocketFactory} 返回 Socket 工厂。 ```javascript log(http.__okhttp__.socketFactory()); ``` ### http.**okhttp**.certificatePinner() * 返回 {okhttp3.CertificatePinner} 返回证书固定器配置。 ```javascript log(http.__okhttp__.certificatePinner()); ``` ### http.**okhttp**.eventListenerFactory() * 返回 {okhttp3.EventListener.Factory} 返回事件监听器工厂。 ```javascript log(http.__okhttp__.eventListenerFactory()); ``` ### http.**okhttp**.connectionSpecs() * 返回 {java.util.List} 返回连接规格列表。 ```javascript log(http.__okhttp__.connectionSpecs()); ``` ### http.**okhttp** 常用属性 * `http.__okhttp__.timeout` {number} 当前超时毫秒值(快捷属性) * `http.__okhttp__.maxRetries` {number} 当前最大重试次数(快捷属性) * `http.__okhttp__.connectTimeout` {number} 连接超时毫秒值(快捷属性) * `http.__okhttp__.readTimeout` {number} 读取超时毫秒值(快捷属性) * `http.__okhttp__.writeTimeout` {number} 写入超时毫秒值(快捷属性) * `http.__okhttp__.pingInterval` {number} Ping 间隔毫秒值(快捷属性) * `http.__okhttp__.certificateChainCleaner` {Object} 证书链清理器对象 * `http.__okhttp__.class` {Object} 当前 Java 类对象 ```javascript log(http.__okhttp__.timeout, http.__okhttp__.maxRetries); log(http.__okhttp__.certificateChainCleaner, http.__okhttp__.class); ``` # Response HTTP请求的响应。 ## Response.statusCode * {number} 当前响应的HTTP状态码。例如200(OK), 404(Not Found)等。 有关HTTP状态码的信息,参见[菜鸟教程:HTTP状态码](http://www.runoob.com/http/http-status-codes.html)。 ## Response.statusMessage * {string} 当前响应的HTTP状态信息。例如"OK", "Bad Request", "Forbidden"。 有关HTTP状态码的信息,参见[菜鸟教程:HTTP状态码](http://www.runoob.com/http/http-status-codes.html)。 例子: ```javascript var res = http.get("www.baidu.com"); if(res.statusCode >= 200 && res.statusCode < 300){ toast("页面获取成功!"); }else if(res.statusCode == 404){ toast("页面没找到哦..."); }else{ toast("错误: " + res.statusCode + " " + res.statusMessage); } ``` ## Response.headers * {Object} 当前响应的HTTP头部信息。该对象的键是响应头名称,值是各自的响应头值。 所有响应头名称都是小写的(吗)。 有关HTTP头部信息,参见[菜鸟教程:HTTP响应头信息](http://www.runoob.com/http/http-header-fields.html)。 例子: ```javascript console.show(); var res = http.get("www.qq.com"); console.log("HTTP Headers:") for(var headerName in res.headers){ console.log("%s: %s", headerName, res.headers[headerName]); } ``` ## Response.body * {Object} 当前响应的内容。他有以下属性和函数: * bytes() {Array} 以字节数组形式返回响应内容 * string() {string} 以字符串形式返回响应内容 * json() {Object} 把响应内容作为JSON格式的数据并调用JSON.parse,返回解析后的对象 * contentType {string} 当前响应的内容类型 ## Response.request * {Request} 当前响应所对应的请求。参见\[Request]\[]。 ## Response.url * {number} 当前响应所对应的请求URL。 ## Response.method * {string} 当前响应所对应的HTTP请求的方法。例如"GET", "POST", "PUT"等。 --- --- url: 'https://www.wuyunai.com/docs/v8/images.html' description: >- Auto.js Pro v8 images 模块 API 文档 - 提供图片处理功能,包括截图、读写图片、图片剪裁、旋转、二值化、找色找图等。支持图片资源管理和回收。 --- # images - 图片处理 > Stability: 2 - Stable images 模块提供了一些手机设备中常见的图片处理函数,包括截图、读写图片、图片剪裁、旋转、二值化、找色找图等。 该模块分为两个部分,找图找色部分和图片处理部分。 需要注意的是,image 对象创建后尽量在不使用时进行回收,同时避免循环创建大量图片。因为图片是一种占用内存比较大的资源,尽管 Auto.js 通过各种方式(比如图片缓存机制、垃圾回收时回收图片、脚本结束时回收所有图片)尽量降低图片资源的泄漏和内存占用,但是糟糕的代码仍然可以占用大量内存。 Image 对象通过调用`recycle()`函数来回收。例如: ```javascript // 读取图片 var img = images.read("./1.png"); // 对图片进行操作 ... // 回收图片 img.recycle(); ``` 例外的是,`captureScreen()`返回的图片不需要回收。 ## 图片处理 ## images.read(path) * `path` {string} 图片路径 读取在路径 path 的图片文件并返回一个 Image 对象。如果文件不存在或者文件无法解码则返回 null。 ## images.load(url) * `url` {string} 图片 URL 地址 加载在地址 URL 的网络图片并返回一个 Image 对象。如果地址不存在或者图片无法解码则返回 null。 ## images.copy(img) * `img` {Image} 图片 * 返回 {Image} 复制一张图片并返回新的副本。该函数会完全复制 img 对象的数据。 ## images.save(image, path\[, format = "png", quality = 100]) * `image` {Image} 图片 * `path` {string} 路径 * `format` {string} 图片格式,可选的值为: * `png` * `jpeg`/`jpg` * `webp` * `quality` {number} 图片质量,为 0~100 的整数值 把图片 image 以 PNG 格式保存到 path 中。如果文件不存在会被创建;文件存在会被覆盖。 ```javascript // 把图片压缩为原来的一半质量并保存 var img = images.read("/sdcard/1.png"); images.save(img, "/sdcard/1.jpg", "jpg", 50); app.viewFile("/sdcard/1.jpg"); ``` ## images.fromBase64(base64) * `base64` {string} 图片的 Base64 数据 * 返回 {Image} 解码 Base64 数据并返回解码后的图片 Image 对象。如果 base64 无法解码则返回`null`。 ## images.toBase64(img\[, format = "png", quality = 100]) * `image` {image} 图片 * `format` {string} 图片格式,可选的值为: * `png` * `jpeg`/`jpg` * `webp` * `quality` {number} 图片质量,为 0~100 的整数值 * 返回 {string} 把图片编码为 base64 数据并返回。 ## images.fromBytes(bytes) * `bytes` {byte\[]} 字节数组 解码字节数组 bytes 并返回解码后的图片 Image 对象。如果 bytes 无法解码则返回`null`。 ## images.toBytes(img\[, format = "png", quality = 100]) * `image` {image} 图片 * `format` {string} 图片格式,可选的值为: * `png` * `jpeg`/`jpg` * `webp` * `quality` {number} 图片质量,为 0~100 的整数值 * 返回 {byte\[]} 把图片编码为字节数组并返回。 ## images.readPixels(path) * `path` {string} 图片的地址 * 返回 {Object} 包括图片的像素数据和宽高,{data,width,height} 读取图片的像素数据和宽高。 ## images.clip(img, x, y, w, h) * `img` {Image} 图片 * `x` {number} 剪切区域的左上角横坐标 * `y` {number} 剪切区域的左上角纵坐标 * `w` {number} 剪切区域的宽度 * `h` {number} 剪切区域的高度 * 返回 {Image} 从图片 img 的位置(x, y)处剪切大小为 w \* h 的区域,并返回该剪切区域的新图片。 ```javascript var src = images.read("/sdcard/1.png"); var clip = images.clip(src, 100, 100, 400, 400); images.save(clip, "/sdcard/clip.png"); ``` ## images.resize(img, size\[, interpolation]) **\[v4.1.0 新增]** * `img` {Image} 图片 * `size` {Array} 两个元素的数组\[w, h],分别表示宽度和高度;如果只有一个元素,则宽度和高度相等 * `interpolation` {string} 插值方法,可选,默认为"LINEAR"(线性插值),可选的值有: * `NEAREST` 最近邻插值 * `LINEAR` 线性插值(默认) * `AREA` 区域插值 * `CUBIC` 三次样条插值 * `LANCZOS4` Lanczos 插值 参见[InterpolationFlags](https://docs.opencv.org/3.4.4/da/d54/group__imgproc__transform.html#ga5bb5a1fea74ea38e1a5445ca803ff121) * 返回 {Image} 调整图片大小,并返回调整后的图片。例如把图片放缩为 200\*300:`images.resize(img, [200, 300])`。 参见[Imgproc.resize](https://docs.opencv.org/3.4.4/da/d54/group__imgproc__transform.html#ga47a974309e9102f5f08231edc7e7529d)。 ## images.scale(img, fx, fy\[, interpolation]) **\[v4.1.0 新增]** * `img` {Image} 图片 * `fx` {number} 宽度放缩倍数 * `fy` {number} 高度放缩倍数 * `interpolation` {string} 插值方法,可选,默认为"LINEAR"(线性插值),可选的值有: * `NEAREST` 最近邻插值 * `LINEAR` 线性插值(默认) * `AREA` 区域插值 * `CUBIC` 三次样条插值 * `LANCZOS4` Lanczos 插值 参见[InterpolationFlags](https://docs.opencv.org/3.4.4/da/d54/group__imgproc__transform.html#ga5bb5a1fea74ea38e1a5445ca803ff121) * 返回 {Image} 放缩图片,并返回放缩后的图片。例如把图片变成原来的一半:`images.scale(img, 0.5, 0.5)`。 参见[Imgproc.resize](https://docs.opencv.org/3.4.4/da/d54/group__imgproc__transform.html#ga47a974309e9102f5f08231edc7e7529d)。 ## images.rotate(img, degree\[, x, y]) **\[v4.1.0 新增]** * `img` {Image} 图片 * `degree` {number} 旋转角度。 * `x` {number} 旋转中心 x 坐标,默认为图片中点 * `y` {number} 旋转中心 y 坐标,默认为图片中点 * 返回 {Image} 将图片逆时针旋转 degree 度,返回旋转后的图片对象。 例如逆时针旋转 90 度为`images.rotate(img, 90)`。 ## images.concat(img1, image2\[, direction]) **\[v4.1.0 新增]** * `img1` {Image} 图片 1 * `img2` {Image} 图片 2 * `direction` {string} 连接方向,默认为"RIGHT",可选的值有: * `LEFT` 将图片 2 接到图片 1 左边 * `RIGHT` 将图片 2 接到图片 1 右边 * `TOP` 将图片 2 接到图片 1 上边 * `BOTTOM` 将图片 2 接到图片 1 下边 * 返回 {Image} 连接两张图片,并返回连接后的图像。如果两张图片大小不一致,小的那张将适当居中。 ## images.grayscale(img) **\[v4.1.0 新增]** * `img` {Image} 图片 * 返回 {Image} 灰度化图片,并返回灰度化后的图片。 ## images.threshold(img, threshold, maxVal\[, type]) **\[v4.1.0 新增]** * `img` {Image} 图片 * `threshold` {number} 阈值 * `maxVal` {number} 最大值 * `type` {string} 阈值化类型,默认为"BINARY",参见[ThresholdTypes](https://docs.opencv.org/3.4.4/d7/d1b/group__imgproc__misc.html#gaa9e58d2860d4afa658ef70a9b1115576), 可选的值: * `BINARY` * `BINARY_INV` * `TRUNC` * `TOZERO` * `TOZERO_INV` * `OTSU` * `TRIANGLE` * 返回 {Image} 将图片阈值化,并返回处理后的图像。可以用这个函数进行图片二值化。例如:`images.threshold(img, 100, 255, "BINARY")`,这个代码将图片中大于 100 的值全部变成 255,其余变成 0,从而达到二值化的效果。如果 img 是一张灰度化图片,这个代码将会得到一张黑白图片。 可以参考有关博客(比如[threshold 函数的使用](https://blog.csdn.net/u012566751/article/details/77046445))或者 OpenCV 文档[threshold](https://docs.opencv.org/3.4.4/d7/d1b/group__imgproc__misc.html#gae8a4a146d1ca78c626a53577199e9c57)。 ## images.adaptiveThreshold(img, maxValue, adaptiveMethod, thresholdType, blockSize, C) **\[v4.1.0 新增]** * `img` {Image} 图片 * `maxValue` {number} 最大值 * `adaptiveMethod` {string} 在一个邻域内计算阈值所采用的算法,可选的值有: * `MEAN_C` 计算出领域的平均值再减去参数 C 的值 * `GAUSSIAN_C` 计算出领域的高斯均值再减去参数 C 的值 * `thresholdType` {string} 阈值化类型,可选的值有: * `BINARY` * `BINARY_INV` * `blockSize` {number} 邻域块大小 * `C` {number} 偏移值调整量 * 返回 {Image} 对图片进行自适应阈值化处理,并返回处理后的图像。 可以参考有关博客(比如[threshold 与 adaptiveThreshold](https://blog.csdn.net/guduruyu/article/details/68059450))或者 OpenCV 文档[adaptiveThreshold](https://docs.opencv.org/3.4.4/d7/d1b/group__imgproc__misc.html#ga72b913f352e4a1b1b397736707afcde3)。 ## images.cvtColor(img, code\[, dstCn]) **\[v4.1.0 新增]** * `img` {Image} 图片 * `code` {string} 颜色空间转换的类型,可选的值有一共有 205 个(参见[ColorConversionCodes](https://docs.opencv.org/3.4.4/d8/d01/group__imgproc__color__conversions.html#ga4e0972be5de079fed4e3a10e24ef5ef0)),这里只列出几个: * `BGR2GRAY` BGR 转换为灰度 * `BGR2HSV` BGR 转换为 HSV * `dstCn` {number} 目标图像的颜色通道数量,如果不填写则根据其他参数自动决定。 * 返回 {Image} 对图像进行颜色空间转换,并返回转换后的图像。 可以参考有关博客(比如[颜色空间转换](https://blog.csdn.net/u011574296/article/details/70896811?locationNum=14\&fps=1))或者 OpenCV 文档[cvtColor](https://docs.opencv.org/3.4.4/d8/d01/group__imgproc__color__conversions.html#ga397ae87e1288a81d2363b61574eb8cab)。 ## images.inRange(img, lowerBound, upperBound) **\[v4.1.0 新增]** * `img` {Image} 图片 * `lowerBound` {string} | {number} 颜色下界 * `upperBound` {string} | {number} 颜色下界 * 返回 {Image} 将图片二值化,在 lowerBound~upperBound 范围以外的颜色都变成 0,在范围以内的颜色都变成 255。 例如`images.inRange(img, "#000000", "#222222")`。 ## images.interval(img, color, interval) **\[v4.1.0 新增]** * `img` {Image} 图片 * `color` {string} | {number} 颜色值 * `interval` {number} 每个通道的范围间隔 * 返回 {Image} 将图片二值化,在 color-interval ~ color+interval 范围以外的颜色都变成 0,在范围以内的颜色都变成 255。这里对 color 的加减是对每个通道而言的。 例如`images.interval(img, "#888888", 16)`,每个通道的颜色值均为 0x88,加减 16 后的范围是\[0x78, 0x98],因此这个代码将把#787878~#989898 的颜色变成#FFFFFF,而把这个范围以外的变成#000000。 ## images.blur(img, size\[, anchor, type]) **\[v4.1.0 新增]** * `img` {Image} 图片 * `size` {Array} 定义滤波器的大小,如\[3, 3] * `anchor` {Array} 指定锚点位置(被平滑点),默认为图像中心 * `type` {string} 推断边缘像素类型,默认为"DEFAULT",可选的值有: * `CONSTANT` iiiiii|abcdefgh|iiiiiii with some specified i * `REPLICATE` aaaaaa|abcdefgh|hhhhhhh * `REFLECT` fedcba|abcdefgh|hgfedcb * `WRAP` cdefgh|abcdefgh|abcdefg * `REFLECT_101` gfedcb|abcdefgh|gfedcba * `TRANSPARENT` uvwxyz|abcdefgh|ijklmno * `REFLECT101` same as BORDER\_REFLECT\_101 * `DEFAULT` same as BORDER\_REFLECT\_101 * `ISOLATED` do not look outside of ROI * 返回 {Image} 对图像进行模糊(平滑处理),返回处理后的图像。 可以参考有关博客(比如[实现图像平滑处理](https://www.cnblogs.com/denny402/p/3848316.html))或者 OpenCV 文档[blur](https://docs.opencv.org/3.4.4/d4/d86/group__imgproc__filter.html#ga8c45db9afe636703801b0b2e440fce37)。 ## images.medianBlur(img, size) **\[v4.1.0 新增]** * `img` {Image} 图片 * `size` {Array} 定义滤波器的大小,如\[3, 3] * 返回 {Image} 对图像进行中值滤波,返回处理后的图像。 可以参考有关博客(比如[实现图像平滑处理](https://www.cnblogs.com/denny402/p/3848316.html))或者 OpenCV 文档[blur](https://docs.opencv.org/3.4.4/d4/d86/group__imgproc__filter.html#ga564869aa33e58769b4469101aac458f9)。 ## images.gaussianBlur(img, size\[, sigmaX, sigmaY, type]) **\[v4.1.0 新增]** * `img` {Image} 图片 * `size` {Array} 定义滤波器的大小,如\[3, 3] * `sigmaX` {number} x 方向的标准方差,不填写则自动计算 * `sigmaY` {number} y 方向的标准方差,不填写则自动计算 * `type` {string} 推断边缘像素类型,默认为"DEFAULT",参见`images.blur` * 返回 {Image} 对图像进行高斯模糊,返回处理后的图像。 可以参考有关博客(比如[实现图像平滑处理](https://www.cnblogs.com/denny402/p/3848316.html))或者 OpenCV 文档[GaussianBlur](https://docs.opencv.org/3.4.4/d4/d86/group__imgproc__filter.html#gaabe8c836e97159a9193fb0b11ac52cf1)。 ## images.getSimilarity(img1, img2, options) * `img1` {Image} 图片 1 * `img2` {Image} 图片 2 * `options` {Object} 选项包括: * `type` {string} 比较相似度的算法(默认为 MSSIM): * `MSSIM` 平均结构相似性,在影像品质的衡量上更能符合人眼对影像品质的判断。结构相似性 SSIM 的取值范围是 \[ 0 , 1 ] ,当两张图像越相似时,则 SSIM 越接近 1。 * `PNSR` 峰值信噪比,是针对于像素绝对误差,通过均方误差(MSE)进行定义,当两幅图像的 PSNR 小于 30 时,那么这两幅图像可以说是比较相似的。 * 返回 {number} 比较两幅图片的相似性,返回相似度。 例如 ```javascript log( images.getSimilarity(img1, img2, { type: "PNSR", }), ); ``` ## images.matToImage(mat) **\[v4.1.0 新增]** * `mat` {Mat} OpenCV 的 Mat 对象 * 返回 {Image} 把 Mat 对象转换为 Image 对象。 ## 找图找色 找图找色介绍了通过截图、匹配等搜索目标的不同方式,可以根据实际情况选择不同的方式: * [找色、多点找色](./images.html#images-findcolor-image-color-options):通过描述颜色或颜色路径匹配图片中的像素,效率较高 * [基于 ColorMaping 的找色或多点找色](./images.html#colormapping):适合每次截图都有多次找色、多点找色的情况,可以进一步提升找色效率,效率最高 * [找图(模板匹配)](./images.html#images-findimage-img-template-options):通过在大图中依次匹配小图,搜索小图的位置,效率中等,但不同分辨率兼容性较差 * [**全分辨率找图(特征匹配)**](./images.html#images-detectandcomputefeatures-img-options):通过计算大图和小图的特征点,匹配特征而计算小图的位置,效率比普通找图低,但兼容分辨率、旋转等变化 ## images.requestScreenCapture(\[landscape]) * `landscape` {boolean} 布尔值, 表示将要执行的截屏是否为横屏。如果 landscape 为 false, 则表示竖屏截图; true 为横屏截图。 向系统申请屏幕截图权限,返回是否请求成功。 第一次使用该函数会弹出截图权限请求,建议选择“总是允许”。(某些系统没有总是允许选项) 这个函数只是申请截图权限,并不会真正执行截图,真正的截图函数是`captureScreen()`。 该函数在截图脚本中只需执行一次,而无需每次调用`captureScreen()`都调用一次;若已有截图权限,则抛出异常。 **如果不指定 landscape 值,则截图方向由当前设备屏幕方向决定**,因此务必注意执行该函数时的屏幕 截图权限无法在脚本引擎之间共享。 建议在本软件界面运行该函数,在其他软件界面运行时容易出现一闪而过的黑屏现象;另外,**某些定制 ROM 或者高版本 Android 不允许在后台弹出界面,在后台运行此函数时可能会一直阻塞**。 示例: ```javascript // 请求截图 if (!requestScreenCapture()) { toast("请求截图失败"); exit(); } // 连续截图10张图片(间隔1秒)并保存到存储卡目录 for (var i = 0; i < 10; i++) { captureScreen("/sdcard/screen_capture_" + i + ".png"); sleep(1000); } ``` 该函数也可以作为全局函数使用。 ## $images.requestScreenCapture(options) **\[[Pro 8.0 新增](https://pro.autojs.org/)]** * `options` {object} 申请截图选项 * `width` {number} 截图宽度,默认为-1,即自动设置为设备屏幕宽度 * `height` {number} 截图高度,默认为-1,即自动设置为设备屏幕高度 * `orientation` {number} 截图方向,默认为 0 * -1:ORIENTATION\_CURRENT, 检测当前的屏幕方向,用该方向作为申请截图的屏幕方向 * 0: ORIENTATION\_AUTO, 自动适应截图方向(转屏时自动切换方向) * 1: ORIENTATION\_PORTRAIT, 竖屏截图 * 2: ORIENTATION\_LANDSCAPE, 横屏截图 * `async` {boolean} 是否为异步截图。默认为 false 向系统申请屏幕截图权限,返回是否请求成功。对于 width 和 height 参数,系统只会匹配相邻的合适的宽高。截图宽高不一定和指定的宽高完全一致。 ```javascript requestScreenCapture(\{orientation: 0\}); ``` 更多参数和说明参见上面的`images.requestScreenCapture([landscape])`函数,这里只特别解释`async`参数。 当`async`为 true 时,申请截图将为异步截图,也即无法通过`captureScreen()`来截图,而是通过事件`screen_capture`来监听截图。 该事件将在屏幕变化时自动触发,对于屏幕刷新少的软件界面更加节能省电,对于游戏界面则可能无法达到省电效果。 ```javascript // 请求截图权限, 注意参数 async: true requestScreenCapture(\{async: true\}); let target = $images.read('./test.png'); $events.on('exit', () => target.recycle()); // 监听屏幕截图 $images.on("screen_capture", capture => { // 找图 let pos = $images.findImage(capture, target); // 打印 console.log(pos); }); ``` ## $images.getScreenCaptureOptions() **\[[Pro 8.8.12 新增](https://pro.autojs.org/)]** * 返回 {object} | {null} 获取当前截图配置选项。如果并未申请截图权限,则返回`null`。返回的对象有以下字段: \_ `width` {number} 截图宽度 \_ `height` {number} 截图高度 \_ `orientation` {number} 截图方向 \_ 0: ORIENTATION*AUTO, 自动适应截图方向 \* 1: ORIENTATION*PORTRAIT, 竖屏截图 \* 2: ORIENTATION\*LANDSCAPE, 横屏截图 \* `density` {number} 截图像素密度 \_ `async` {boolean} 是否为异步截图 ## $images.stopScreenCapture() **\[[Pro 8.8.12 新增](https://pro.autojs.org/)]** 释放截图权限。如果并未申请截图权限,则此函数没有任何作用。 ## images.captureScreen() 截取当前屏幕并返回一个 Image 对象。 没有截图权限时执行该函数会抛出 SecurityException。 该函数不会返回 null,两次调用可能返回相同的 Image 对象。这是因为设备截图的更新需要一定的时间,短时间内(一般来说是 16ms)连续调用则会返回同一张截图。 截图需要转换为 Bitmap 格式,从而该函数执行需要一定的时间(0~20ms)。 另外在 requestScreenCapture()执行成功后需要一定时间后才有截图可用,因此如果立即调用 captureScreen(),会等待一定时间后(一般为几百 ms)才返回截图。 例子: ```javascript // 请求横屏截图 requestScreenCapture(true); // 截图 var img = captureScreen(); // 获取在点(100, 100)的颜色值 var color = images.pixel(img, 100, 100); // 显示该颜色值 toast(colors.toString(color)); ``` 该函数也可以作为全局函数使用。 ## images.captureScreen(path) * `path` {string} 截图保存路径 截取当前屏幕并以 PNG 格式保存到 path 中。如果文件不存在会被创建;文件存在会被覆盖。 该函数不会返回任何值。该函数也可以作为全局函数使用。 ## images.pixel(image, x, y) * `image` {Image} 图片 * `x` {number} 要获取的像素的横坐标。 * `y` {number} 要获取的像素的纵坐标。 返回图片 image 在点(x, y)处的像素的 ARGB 值。 该值的格式为 0xAARRGGBB,是一个"32 位整数"(虽然 JavaScript 中并不区分整数类型和其他数值类型)。 坐标系以图片左上角为原点。以图片左侧边为 y 轴,上侧边为 x 轴。 ## images.findColor(image, color, options) * `image` {Image} 图片 * `color` {number} | {string} 要寻找的颜色的 RGB 值。如果是一个整数,则以 0xRRGGBB 的形式代表 RGB 值(A 通道会被忽略);如果是字符串,则以"#RRGGBB"代表其 RGB 值。 * `options` {Object} 选项 在图片中寻找颜色 color。找到时返回找到的点 Point,找不到时返回 null。 选项包括: * `region` {Array} 找色区域。是一个两个或四个元素的数组。(region\[0], region\[1])表示找色区域的左上角;region\[2]\*region\[3]表示找色区域的宽高。如果只有 region 只有两个元素,则找色区域为(region\[0], region\[1])到图片右下角。如果不指定 region 选项,则找色区域为整张图片。 * `threshold` {number} 找色时颜色相似度的临界值,范围为 0 ~ 255(越小越相似,0 为颜色相等,255 为任何颜色都能匹配)。默认为 4。threshold 和浮点数相似度(0.0~1.0)的换算为 similarity = (255 - threshold) / 255. 该函数也可以作为全局函数使用。 一个循环找色的例子如下: ```javascript requestScreenCapture(); // 循环找色,找到红色(#ff0000)时停止并报告坐标 while (true) { var img = captureScreen(); var point = findColor(img, "#ff0000"); if (point) { toast("找到红色,坐标为(" + point.x + ", " + point.y + ")"); } } ``` 一个区域找色的例子如下: ```javascript // 读取本地图片/sdcard/1.png var img = images.read("/sdcard/1.png"); // 判断图片是否加载成功 if (!img) { toast("没有该图片"); exit(); } // 在该图片中找色,指定找色区域为在位置(400, 500)的宽为300长为200的区域,指定找色临界值为4 var point = findColor(img, "#00ff00", { region: [400, 500, 300, 200], threshold: 4, }); if (point) { toast("找到啦:" + point); } else { toast("没找到"); } ``` ## images.findColorInRegion(img, color, x, y\[, width, height, threshold]) 区域找色的简便方法。 相当于 ```javascript images.findColor(img, color, { region: [x, y, width, height], threshold: threshold, }); ``` 该函数也可以作为全局函数使用。 ## images.findAllPointsForColor(img, color, options) * `img` {Image} 图片 * `color` {number} | {string} 要检测的颜色 * `options` {Object} 选项包括: * `region` {Array} 找色区域。是一个两个或四个元素的数组。(region\[0], region\[1])表示找色区域的左上角;region\[2]\*region\[3]表示找色区域的宽高。如果只有 region 只有两个元素,则找色区域为(region\[0], region\[1])到图片右下角。如果不指定 region 选项,则找色区域为整张图片。 * `similarity` {number} 找色时颜色相似度,范围为 0~1(越大越相似,1 为颜色相等,0 为任何颜色都能匹配)。 * `threshold` {number} 找色时颜色相似度的临界值,范围为 0 ~ 255(越小越相似,0 为颜色相等,255 为任何颜色都能匹配)。默认为 4。threshold 和浮点数相似度(0.0~1.0)的换算为 similarity = (255 - threshold) / 255 。相似度与阈值二选一,同时存在则以相似度为准。 * 返回 {Array} 在图片中寻找所有颜色为 color 的点。找到时返回找到的点 Point 的数组,找不到时返回 null。 例如找出所有白色的点: ```javascript log(images.findAllPointsForColor(img, "#ffffff")); ``` ## images.findColorEquals(img, color\[, x, y, width, height]) * `img` {Image} 图片 * `color` {number} | {string} 要寻找的颜色 * `x` {number} 找色区域的左上角横坐标 * `y` {number} 找色区域的左上角纵坐标 * `width` {number} 找色区域的宽度 * `height` {number} 找色区域的高度 * 返回 {Point} 在图片 img 指定区域中找到颜色和 color 完全相等的某个点,并返回该点的左边;如果没有找到,则返回`null`。 找色区域通过`x`, `y`, `width`, `height`指定,如果不指定找色区域,则在整张图片中寻找。 该函数也可以作为全局函数使用。 示例: (通过找 QQ 红点的颜色来判断是否有未读消息) ```javascript requestScreenCapture(); launchApp("QQ"); sleep(1200); var p = findColorEquals(captureScreen(), "#f64d30"); if (p) { toast("有未读消息"); } else { toast("没有未读消息"); } ``` ## images.findMultiColors(img, firstColor, colors\[, options]) * `img` {Image} 要找色的图片 * `firstColor` {number} | {string} 第一个点的颜色 * `colors` {Array} 表示剩下的点相对于第一个点的位置和颜色的数组,数组的每个元素为\[x, y, color] * `options` {Object} 选项,包括: * `region` {Array} 找色区域。是一个两个或四个元素的数组。(region\[0], region\[1])表示找色区域的左上角;region\[2]\*region\[3]表示找色区域的宽高。如果只有 region 只有两个元素,则找色区域为(region\[0], region\[1])到图片右下角。如果不指定 region 选项,则找色区域为整张图片。 * `threshold` {number} 找色时颜色相似度的临界值,范围为 0 ~ 255(越小越相似,0 为颜色相等,255 为任何颜色都能匹配)。默认为 4。threshold 和浮点数相似度(0.0~1.0)的换算为 similarity = (255 - threshold) / 255. 多点找色,类似于按键精灵的多点找色,其过程如下: 1. 在图片 img 中找到颜色 firstColor 的位置(x0, y0) 2. 对于数组 colors 的每个元素\[x, y, color],检查图片 img 在位置(x + x0, y + y0)上的像素是否是颜色 color,是的话返回(x0, y0),否则继续寻找 firstColor 的位置,重新执行第 1 步 3. 整张图片都找不到时返回`null` 例如,对于代码`images.findMultiColors(img, "#123456", [[10, 20, "#ffffff"], [30, 40, "#000000"]])`,假设图片在(100, 200)的位置的颜色为#123456, 这时如果(110, 220)的位置的颜色为#fffff 且(130, 240)的位置的颜色为#000000,则函数返回点(100, 200)。 如果要指定找色区域,则在 options 中指定,例如: ```javascript var p = images.findMultiColors( img, "#123456", [ [10, 20, "#ffffff"], [30, 40, "#000000"], ], { region: [0, 960, 1080, 960], }, ); ``` ## images.detectsColor(image, color, x, y\[, threshold = 16, algorithm = "diff"]) * `image` {Image} 图片 * `color` {number} | {string} 要检测的颜色 * `x` {number} 要检测的位置横坐标 * `y` {number} 要检测的位置纵坐标 * `threshold` {number} 颜色相似度临界值,默认为 16。取值范围为 0 ~ 255。 * `algorithm` {string} 颜色匹配算法,包括: * "equal": 相等匹配,只有与给定颜色 color 完全相等时才匹配。 * "diff": 差值匹配。与给定颜色的 R、G、B 差的绝对值之和小于 threshold 时匹配。 * "rgb": rgb 欧拉距离相似度。与给定颜色 color 的 rgb 欧拉距离小于等于 threshold 时匹配。 * "rgb+": 加权 rgb 欧拉距离匹配([LAB Delta E](https://en.wikipedia.org/wiki/Color_difference))。 * "hs": hs 欧拉距离匹配。hs 为 HSV 空间的色调值。 返回图片 image 在位置(x, y)处是否匹配到颜色 color。用于检测图片中某个位置是否是特定颜色。 一个判断微博客户端的某个微博是否被点赞过的例子: ```javascript requestScreenCapture(); // 找到点赞控件 var like = id("ly_feed_like_icon").findOne(); // 获取该控件中点坐标 var x = like.bounds().centerX(); var y = like.bounds().centerY(); // 截图 var img = captureScreen(); // 判断在该坐标的颜色是否为橙红色 if (images.detectsColor(img, "#fed9a8", x, y)) { // 是的话则已经是点赞过的了,不做任何动作 } else { // 否则点击点赞按钮 like.click(); } ``` ## images.detectsMultiColors(img, x, y, firstColor, colors, options) * `img` {Image} 目标图片 * `x` {number} 第一个点的 x 坐标 * `y` {number} 第一个点的 y 坐标 * `firstColor` {number} | {string} 第一个点的颜色 * `colors` {Array} 表示剩下的点相对于第一个点的位置和颜色的数组,数组的每个元素为\[x, y, color] * `options` {Object} 选项,包括: * `region` {Array} 找色区域。是一个两个或四个元素的数组。(region\[0], region\[1])表示找色区域的左上角;region\[2]\*region\[3]表示找色区域的宽高。如果只有 region 只有两个元素,则找色区域为(region\[0], region\[1])到图片右下角。如果不指定 region 选项,则区域为整张图片。 * `threshold` {number} 比色时颜色相似度的临界值,范围为 0 ~ 255(越小越相似,0 为颜色相等,255 为任何颜色都能匹配)。默认为 4。threshold 和浮点数相似度(0.0~1.0)的换算为 similarity = (255 - threshold) / 255. * 返回 `boolean` 多点比色,返回 img 在起始位置(x, y)处的多个点的颜色是否匹配。 参见`images.findMultiColors()`多点找色的文档。 ```javascript log( images.detectsMultiColors(img, 100, 200, "#000000", [ [3, 4, "#123456"], [8, 10, "#ff0000"], ]), ); ``` ## images.findImage(img, template\[, options]) **\[v8.5.5 新增]** * `img` {Image} 大图片 * `template` {Image} 小图片(模板) * `options` {Object} 找图选项 找图。在大图片 img 中查找小图片 template 的位置(模块匹配),找到时返回位置坐标(Point),找不到时返回 null。 选项包括: * `threshold` {number} 图片相似度。取值范围为 0~1 的浮点数。默认值为 0.9。 * `region` {Array} 找图区域。参见 findColor 函数关于 region 的说明。 * `level` {number} **一般而言不必修改此参数**。不加此参数时该参数会根据图片大小自动调整。找图算法是采用图像金字塔进行的, level 参数表示金字塔的层次, level 越大可能带来越高的找图效率,但也可能造成找图失败(图片因过度缩小而无法分辨)或返回错误位置。因此,除非您清楚该参数的意义并需要进行性能调优,否则不需要用到该参数。 该函数也可以作为全局函数使用。 一个最简单的找图例子如下: ```javascript var img = images.read("/sdcard/大图.png"); var templ = images.read("/sdcard/小图.png"); var p = findImage(img, templ); if (p) { toast("找到啦:" + p); } else { toast("没找到"); } ``` 稍微复杂点的区域找图例子如下: ```javascript auto(); requestScreenCapture(); var wx = images.read("/sdcard/微信图标.png"); // 返回桌面 home(); // 截图并找图 var p = findImage(captureScreen(), wx, { region: [0, 50], threshold: 0.8, }); if (p) { toast("在桌面找到了微信图标啦: " + p); } else { toast("在桌面没有找到微信图标"); } ``` ## images.findImageInRegion(img, template, x, y\[, width, height, threshold]) 区域找图的简便方法。相当于: ```javascript images.findImage(img, template, { region: [x, y, width, height], threshold: threshold, }); ``` 该函数也可以作为全局函数使用。 ## images.matchTemplate(img, template, options) **\[v4.1.0 新增]** * `img` {Image} 大图片 * `template` {Image} 小图片(模板) * `options` {Object} 找图选项: * `threshold` {number} 图片相似度。取值范围为 0~1 的浮点数。默认值为 0.9。 * `region` {Array} 找图区域。参见 findColor 函数关于 region 的说明。 * `max` {number} 找图结果最大数量,默认为 5 * `transparentMask` {boolean} 是否使用透明模板找图。此选项开启后,传入的 template 参数可以是一个透明背景的图片对象用于匹配。此选项为 **\[[Pro 8.0 新增](https://pro.autojs.org/)]** 。 * `level` {number} **一般而言不必修改此参数**。不加此参数时该参数会根据图片大小自动调整。找图算法是采用图像金字塔进行的, level 参数表示金字塔的层次, level 越大可能带来越高的找图效率,但也可能造成找图失败(图片因过度缩小而无法分辨)或返回错误位置。因此,除非您清楚该参数的意义并需要进行性能调优,否则不需要用到该参数。 * 返回 {MatchingResult} 在大图片中搜索小图片,并返回搜索结果 MatchingResult。该函数可以用于找图时找出多个位置,可以通过 max 参数控制最大的结果数量。也可以对匹配结果进行排序、求最值等操作。 ## images.findCircles(gray, options) * `gray` {Image} 灰度图片 * `options` {Object} 选项包括: * `region` {Array} 找圆区域。是一个两个或四个元素的数组。(region\[0], region\[1])表示找圆区域的左上角;region\[2]\*region\[3]表示找圆区域的宽高。如果只有 region 只有两个元素,则找圆区域为(region\[0], region\[1])到图片右下角。如果不指定 region 选项,则找圆区域为整张图片。 * `dp` {number} dp 是累加面与原始图像相比的分辨率的反比参数,dp=2 时累计面分辨率是元素图像的一半,宽高都缩减为原来的一半,dp=1 时,两者相同。默认为 1。 * `minDst` {number} minDist 定义了两个圆心之间的最小距离。默认为图片高度的八分之一。 * `param1` {number} param1 是 Canny 边缘检测的高阈值,低阈值被自动置为高阈值的一半。默认为 100,范围为 0-255。 * `param2` {number} param2 是累加平面对是否是圆的判定阈值,默认为 100。 * `minRadius` {number} 定义了检测到的圆的半径的最小值,默认为 0。 * `maxRadius` {number} 定义了检测到的圆的半径的最大值,0 为不限制最大值,默认为 0。 * 返回 {Array} 在图片中寻找圆(做霍夫圆变换)。找到时返回找到的所有圆{x,y,radius}的数组,找不到时返回 null。 一个寻找圆的例子: ```javascript // 请求截图 requestScreenCapture(); // 截图 let img = captureScreen(); // 灰度化图片 let gray = images.grayscale(img); // 找圆 let arr = findCircles(gray, { dp: 1, minDst: 80, param1: 100, param2: 100, minRadius: 50, maxRadius: 80, }); // 回收图片 gray.recycle(); ``` ## $images.detectAndComputeFeatures(img\[, options]) **\[v9.2 新增]** * `img` {Image} 图片,要计算特征的图片 * `options` {object} 特征计算选项,可选参数: * `scale` {number} 计算特征时图片的缩放比例,缩放比例越小,计算特征越快,但可能因为放缩过度导致特征计算错误。对于宽度 \* 高度 > 1000000 的图片,scale 参数默认为 0.5,否则 scale 默认为 1 * `grayscale` {boolean} 是否灰度化后再计算特征,默认为`true` * `method` {string} 图像特征匹配的方法,默认为`SIFT`,也可指定为`ORB`(不推荐) * `region` {Array} 图像的匹配区域,不填此字段时则为整个图片计算特征 * 返回 {[ImageFeatures](./images.html#imagefeatures)} 保存图片特征的对象,不用时需要调用`recycle()`回收 对给定图片计算特征,将图片计算后的特征信息返回。该特征信息对象可用于后续使用[matchFeatures](./images.html#images-matchfeatures-scene-object-options)函数做特征匹配。 一般而言,小图的特征可以在程序开始时就计算,在程序结束时再回收。如果每次截图时都去读取小图、计算小图特征,不仅会导致程序运行效率低,而且会导致内存碎片,从而使得内存难以利用和更快耗尽。 ## $images.matchFeatures(scene, object\[, options]) **\[v9.2 新增]** * `scene` {ImageFeatures} 场景图片的特征对象(大图特征) * `object` {ImageFeatures} 目标图片的特征对象(小图特征) * `options` {object} 可选参数: * `matcher` {string} 特征匹配方式,默认为`FLANNBASED`,可选的值有`"FLANNBASED"`, `"BRUTEFORCE"`, `"BRUTEFORCE_L1"`, `"BRUTEFORCE_HAMMING"`, `"BRUTEFORCE_HAMMINGLUT"`, `"BRUTEFORCE_SL2"`,除了`FLANNBASED`外其他匹配方式未经过充分测试 * `drawMatches` {string} 绘制图片匹配详情的路径,若为空则不绘制匹配详情。此选项一般为调试使用,在真正匹配时请勿指定,否则会增加耗时。 * `threshold` {number} 匹配阈值,默认为`0.7` * 返回 {[ObjectFrame](./images.html#objectframe)} | {null} 小图在大图中的匹配位置,若未找到则返回`null` 特征匹配提供了全分辨率找图功能,可以识别检测图像中明显的特征并根据特征来查找类似图片。即使图片的分辨率、形状、旋转有差异也能识别出来,但匹配速度相对较慢。 需要注意的是,计算特征的过程也比较耗时,因此请勿在每次匹配时才计算小图的特征,小图若不变可以提前计算特征并复用特征对象。 以下是一个简单的示例,也在 Auto.js Pro 内置示例"图片与图色处理 - 找图找色"文件夹中,可直接运行。 ```javascript // 读取小图 let hellokitty = $images.read('./hellokitty.jpg'); // 计算小图特征 let objectFeatures = $images.detectAndComputeFeatures(hellokitty); // 请求截图权限 requestScreenCapture(); // 打开HelloKitty图片 $app.openUrl('https://baike.baidu.com/item/Hello%20Kitty/984270') let n = 3; for (let i = 0; i < n; i++) { sleep(3000); let capture = captureScreen(); // 若要提高效率,可以在计算大图特征时调整scale参数,默认为0.5, // 越小越快,但可以放缩过度导致匹配错误。若在特征匹配时无法搜索到正确结果,可以调整这里的参数,比如\{scale: 1\} // 也可以在这里指定\{region: [...]\}参数只计算这个区域的特征提高效率 let sceneFeatures = $images.detectAndComputeFeatures(capture); // 最后一次匹配时,我们将特征和匹配绘制出来,在调试时更容易看出匹配效果,但会增加耗时 let drawMatches = (i === n - 1 ? './matches.jpg' : undefined); let result = $images.matchFeatures(sceneFeatures, objectFeatures, \{ drawMatches \}); // 打印结果和中心点,可使用click(reuslt.centerX, result.centerY)点击 console.log(result, result ? result.center : null); // 回收特征对象 sceneFeatures.recycle(); if (drawMatches) { // 可以在当前目录查看matches.jpg图片,会绘制详细匹配详情 app.viewFile('./matches.jpg'); } } // 回收小图特征对象 objectFeatures.recycle(); hellokitty.recycle(); ``` ## ImageFeatures **\[v9.2 新增]** 存储特征信息的类,仅用于特征匹配。 ### ImageFeatures.recycle() 回收特征对象。必须显式在不使用该对象时调用,否则会导致内存泄露而程序崩溃。 ## ObjectFrame **\[v9.2 新增]** 特征匹配返回的结果,表示一个四边形。 ### ObjectFrame.topLeft * {[Point](./images.html#point)} 四边形的左上角坐标。 ### ObjectFrame.topRight * {[Point](./images.html#point)} 四边形的右上角坐标。 ### ObjectFrame.bottomLeft * {[Point](./images.html#point)} 四边形的左下角坐标。 ### ObjectFrame.bottomRight * {[Point](./images.html#point)} 四边形的右下角坐标。 ### ObjectFrame.center * {[Point](./images.html#point)} 四边形的中心点坐标。 ### ObjectFrame.centerX * {number} 四边形的中心点 x 坐标。 ### ObjectFrame.centerY * {number} 四边形的中心点 y 坐标。 ## MatchingResult **\[v4.1.0 新增]** ### MatchingResult.matches * {Array} 匹配结果的数组。 数组的元素是一个 Match 对象: * `point` {Point} 匹配位置 * `similarity` {number} 相似度 例如: ```javascript var result = images.matchTemplate(img, template, { max: 100, }); result.matches.forEach((match) => { log("point = " + match.point + ", similarity = " + match.similarity); }); ``` ### MatchingResult.points * {Array} 匹配位置的数组。 ### MatchingResult.first() * 返回 {Match} 第一个匹配结果。如果没有任何匹配,则返回`null`。 ### MatchingResult.last() * 返回 {Match} 最后一个匹配结果。如果没有任何匹配,则返回`null`。 ### MatchingResult.leftmost() * 返回 {Match} 位于大图片最左边的匹配结果。如果没有任何匹配,则返回`null`。 ### MatchingResult.topmost() * 返回 {Match} 位于大图片最上边的匹配结果。如果没有任何匹配,则返回`null`。 ### MatchingResult.rightmost() * 返回 {Match} 位于大图片最右边的匹配结果。如果没有任何匹配,则返回`null`。 ### MatchingResult.bottommost() * 返回 {Match} 位于大图片最下边的匹配结果。如果没有任何匹配,则返回`null`。 ### MatchingResult.best() * 返回 {Match} 相似度最高的匹配结果。如果没有任何匹配,则返回`null`。 ### MatchingResult.worst() * 返回 {Match} 相似度最低的匹配结果。如果没有任何匹配,则返回`null`。 ### MatchingResult.sortBy(cmp) * `cmp` {Function} | {string} 比较函数,或者是一个字符串表示排序方向。例如"left"表示将匹配结果按匹配位置从左往右排序、"top"表示将匹配结果按匹配位置从上往下排序,"left-top"表示将匹配结果按匹配位置从左往右、从上往下排序。方向包括`left`(左), `top` (上), `right` (右), `bottom`(下)。 * {MatchingResult} 对匹配结果进行排序,并返回排序后的结果。 ```javascript var result = images.matchTemplate(img, template, { max: 100, }); log(result.sortBy("top-right")); ``` ## Image 表示一张图片,可以是截图的图片,或者本地读取的图片,或者从网络获取的图片。 ### Image.getWidth() 返回以像素为单位图片宽度。 ### Image.getHeight() 返回以像素为单位的图片高度。 ### Image.width * {number} 图片宽度(像素)。语义与 `Image.getWidth()` 一致。 ### Image.height * {number} 图片高度(像素)。语义与 `Image.getHeight()` 一致。 ### Image.getPointer() * 返回 {number} 返回图片底层对象的原生指针地址(仅用于调试或底层能力排查)。 ### Image.getBitmap() * 返回 {Bitmap} 返回图片对应的 Android `Bitmap` 对象。 ### Image.getMat() * 返回 {Mat} 返回图片对应的 OpenCV `Mat` 对象。 ### Image.clone() * 返回 {Image} 复制当前图片并返回一个新的图片对象。复制后的对象需要单独回收。 ### Image.recycle() 回收图片占用的资源。回收后的图片对象不应再继续使用。 ### Image.isRecycled() * 返回 {boolean} 返回图片是否已被回收。 ### Image.recycled * {boolean} 图片是否已回收的快捷属性。语义与 `Image.isRecycled()` 一致。 ### Image.ensureNotRecycled() 确保图片对象尚未被回收。若对象已回收,该函数会抛出异常。 ### Image.saveTo(path) * `path` {string} 路径 把图片保存到路径 path。(如果文件存在则覆盖) ### Image.pixel(x, y) * `x` {number} 横坐标 * `y` {number} 纵坐标 返回图片 image 在点(x, y)处的像素的 ARGB 值。 该值的格式为 0xAARRGGBB,是一个"32 位整数"(虽然 JavaScript 中并不区分整数类型和其他数值类型)。 坐标系以图片左上角为原点。以图片左侧边为 y 轴,上侧边为 x 轴。 ## Point findColor, findImage 返回的对象。表示一个点(坐标)。 ### Point.x 横坐标。 ### Point.y 纵坐标。 ## ColorMapping 通过颜色映射的实现一种找色方式,对于同一张图找多次色,每次找色相比 images 模块里的函数非常快,只是需要相比一般找色需要一个初始化过程。 注意!ColorMapping 仅能使用**截图的图片对象**初始化颜色映射。 初始化方式: ```javascript // 申请截图权限 $images.requestScreenCapture(); // 初始化ColorMapping let ColorMapping = $colors.mapping; // 创建ColorMapping实例 let cm = new ColorMapping(); // 截屏 let img = $images.captureScreen(); // 初始化颜色映射 cm.reset(img); // 使用完后及时回收 cm.recycle(); ``` 如果不想手动回收,可以用 ColorMapping 的单例,这个单例会自动在脚本结束时回收。 ```javascript // 申请截图权限 $images.requestScreenCapture(); // 初始化ColorMapping let ColorMapping = $colors.mapping; // 创建ColorMapping实例 let cm = ColorMapping.singleton; // 截屏 let img = $images.captureScreen(); // 初始化颜色映射 cm.reset(img); // 找色 cm.findColor("#ffffff"); ``` ### ColorMapping.singleton * {ColorMapping} ColorMapping 的全局单例对象。 ### ColorMapping.reset(img) * `img` {Image} 截图 此操作会重新初始化颜色映射的数据。 ### ColorMapping.recycle() 此操作会释放 ColorMapping 对象。 ### ColorMapping.findColor(color\[, options]) * `color` {number} | {string} 要检测的颜色 * `options` {Object} 选项包括: * `region` {Array} 找色区域。是一个两个或四个元素的数组。(region\[0], region\[1])表示找色区域的左上角;region\[2]\*region\[3]表示找色区域的宽高。如果只有 region 只有两个元素,则找色区域为(region\[0], region\[1])到图片右下角。如果不指定 region 选项,则找色区域为整张图片。 * `similarity` {number} 找色时颜色相似度,范围为 0~1(越大越相似,1 为颜色相等,0 为任何颜色都能匹配)。 * `threshold` {number} 找色时颜色相似度的临界值,范围为 0 ~ 255(越小越相似,0 为颜色相等,255 为任何颜色都能匹配)。默认为 4。threshold 和浮点数相似度(0.0~1.0)的换算为 similarity = (255 - threshold) / 255 。相似度与阈值二选一,同时存在则以相似度为准。 * 返回 {Point} 在图片中寻找颜色 color。找到时返回找到的点 Point,找不到时返回 null。 一个同一张图多次找色的例子如下: ```javascript // 申请截图权限 $images.requestScreenCapture(); // 初始化ColorMapping let ColorMapping = $colors.mapping; // 创建ColorMapping实例 let cm = new ColorMapping(); // 使用ColorMapping找色 while (true) { // 截屏 let img = $images.captureScreen(); // 初始化颜色映射 cm.reset(img); let p1 = cm.findColor("#ffffff"); if (p1) { // ... console.log("白色点坐标" + p1); continue; } let p2 = cm.findColor("#000000"); if (p2) { // ... console.log("黑色点坐标" + p2); continue; } } // 释放ColorMapping cm.recycle(); ``` 一个区域找色的例子如下: ```javascript // 申请截图权限 $images.requestScreenCapture(); // 初始化ColorMapping let ColorMapping = $colors.mapping; // 创建ColorMapping实例 let cm = new ColorMapping(); // 截屏 let img = $images.captureScreen(); // 初始化颜色映射 cm.reset(img); // 使用ColorMapping找色,指定找色区域为在位置(400, 500)的宽为300长为200的区域,指定找色临界值为4 let point = cm.findColor("#00ff00", { region: [400, 500, 300, 200], threshold: 4, }); if (point) { toast("找到啦:" + point); } else { toast("没找到"); } // 释放ColorMapping cm.recycle(); ``` ### ColorMapping.findMultiColors(firstColor, colors, options) * `firstColor` {number} | {string} 第一个点的颜色 * `colors` {Array} 表示剩下的点相对于第一个点的位置和颜色的数组,数组的每个元素为\[x, y, color] * `options` {Object} 选项,包括: * `region` {Array} 找色区域。是一个两个或四个元素的数组。(region\[0], region\[1])表示找色区域的左上角;region\[2]\*region\[3]表示找色区域的宽高。如果只有 region 只有两个元素,则找色区域为(region\[0], region\[1])到图片右下角。如果不指定 region 选项,则找色区域为整张图片。 * `threshold` {number} 找色时颜色相似度的临界值,范围为 0 ~ 255(越小越相似,0 为颜色相等,255 为任何颜色都能匹配)。默认为 4。threshold 和浮点数相似度(0.0~1.0)的换算为 similarity = (255 - threshold) / 255。 * 返回 {Point} 多点找色,与 images.findMultiColors 类似,但多次在同一张图片中找色速度极快。 一个同一张图片多次多点找色的例子: ```javascript // 申请截图权限 $images.requestScreenCapture(); // 初始化ColorMapping let ColorMapping = $colors.mapping; // 创建ColorMapping实例 let cm = new ColorMapping(); // 截屏 let img = $images.captureScreen(); // 初始化颜色映射 cm.reset(img); // 使用ColorMapping多点找色 let p1 = cm.findMultiColors("#ff00ff", [ [10, 20, "#ffffff"], [30, 40, "#000000"], ]); let p2 = cm.findMultiColors("#ff00ff", [ [10, 20, "#ffffff"], [30, 40, "#000000"], ]); log("p1" + p1 + "p2" + p2); // 释放ColorMapping cm.recycle(); ``` ### ColorMapping.findAllPointsForColor(color, options) * `color` {number} | {string} 要检测的颜色 * `options` {Object} 选项包括: * `region` {Array} 找色区域。是一个两个或四个元素的数组。(region\[0], region\[1])表示找色区域的左上角;region\[2]\*region\[3]表示找色区域的宽高。如果只有 region 只有两个元素,则找色区域为(region\[0], region\[1])到图片右下角。如果不指定 region 选项,则找色区域为整张图片。 * `similarity` {number} 找色时颜色相似度,范围为 0~1(越大越相似,1 为颜色相等,0 为任何颜色都能匹配)。 * `threshold` {number} 找色时颜色相似度的临界值,范围为 0—255(越小越相似,0 为颜色相等,255 为任何颜色都能匹配)。默认为 4。threshold 和浮点数相似度(0.0~1.0)的换算为 similarity = (255 - threshold) / 255 。相似度与阈值二选一,同时存在则以相似度为准。 * 返回 {Array} 在图片中寻找所有颜色为 color 的点。找到时返回找到的点 Point 的数组,找不到时返回 null。 找出所有白色点和所有黑色点的例子: ```javascript // 申请截图权限 $images.requestScreenCapture(); // 初始化ColorMapping let ColorMapping = $colors.mapping; // 创建ColorMapping实例 let cm = new ColorMapping(); // 截屏 let img = $images.captureScreen(); // 初始化颜色映射 cm.reset(img); // 使用ColorMapping多点找色 let whitePoints = cm.findAllPointsForColor("#ffffff"); let blackPoints = cm.findAllPointsForColor("#000000"); if (whitePoints != null) { log("白色点有" + whitePoints.length + "个"); } else { log("未找到白色点"); } if (blackPoints != null) { log("黑色点有" + blackPoints.length + "个"); } else { log("未找到黑色点"); } // 释放ColorMapping cm.recycle(); ``` --- --- url: 'https://www.wuyunai.com/docs/v8/keys.html' --- # keys - 按键模拟 按键模拟部分提供了一些模拟物理按键的全局函数,包括Home、音量键、照相键等,有的函数依赖于无障碍服务,有的函数依赖于root权限。 一般来说,以大写字母开头的函数都依赖于root权限。执行此类函数时,如果没有root权限,则函数执行后没有效果,并会在控制台输出一个警告。 ## back() * 返回 {boolean} 模拟按下返回键。返回是否执行成功。 此函数依赖于无障碍服务。 ## home() * 返回 {boolean} 模拟按下Home键。返回是否执行成功。 此函数依赖于无障碍服务。 ## powerDialog() * 返回 {boolean} 弹出电源键菜单。返回是否执行成功。 此函数依赖于无障碍服务。 ## notifications() * 返回 {boolean} 拉出通知栏。返回是否执行成功。 此函数依赖于无障碍服务。 ## quickSettings() * 返回 {boolean} 显示快速设置(下拉通知栏到底)。返回是否执行成功。 此函数依赖于无障碍服务。 ## recents() * 返回 {boolean} 显示最近任务。返回是否执行成功。 此函数依赖于无障碍服务。 ## splitScreen() * 返回 {boolean} 分屏。返回是否执行成功。 此函数依赖于无障碍服务, 并且需要系统自身功能的支持。 ## Home() 模拟按下Home键。 此函数依赖于root权限。 ## Back() 模拟按下返回键。 此函数依赖于root权限。 ## Power() 模拟按下电源键。 此函数依赖于root权限。 ## Menu() 模拟按下菜单键。 此函数依赖于root权限。 ## VolumeUp() 按下音量上键。 此函数依赖于root权限。 ## VolumeDown() 按键音量上键。 此函数依赖于root权限。 ## Camera() 模拟按下照相键。 ## Up() 模拟按下物理按键上。 此函数依赖于root权限。 ## Down() 模拟按下物理按键下。 此函数依赖于root权限。 ## Left() 模拟按下物理按键左。 此函数依赖于root权限。 ## Right() 模拟按下物理按键右。 此函数依赖于root权限。 ## OK() 模拟按下物理按键确定。 此函数依赖于root权限。 ## Text(text) * `text` {string} 要输入的文字,只能为英文或英文符号 输入文字text。例如`Text("aaa");` ## KeyCode(code) * `code` {number} | {string} 要按下的按键的数字代码或名称。参见下表。 模拟物理按键。例如`KeyCode(29)`和`KeyCode("KEYCODE_A")`是按下A键。 ## 附录: KeyCode对照表 | KeyCode | KeyEvent value | | --------------------- | -------------- | | KEYCODE\_MENU | 1 | | KEYCODE\_SOFT\_RIGHT | 2 | | KEYCODE\_HOME | 3 | | KEYCODE\_BACK | 4 | | KEYCODE\_CALL | 5 | | KEYCODE\_ENDCALL | 6 | | KEYCODE\_0 | 7 | | KEYCODE\_1 | 8 | | KEYCODE\_2 | 9 | | KEYCODE\_3 | 10 | | KEYCODE\_4 | 11 | | KEYCODE\_5 | 12 | | KEYCODE\_6 | 13 | | KEYCODE\_7 | 14 | | KEYCODE\_8 | 15 | | KEYCODE\_9 | 16 | | KEYCODE\_STAR | 17 | | KEYCODE\_POUND | 18 | | KEYCODE\_DPAD\_UP | 19 | | KEYCODE\_DPAD\_DOWN | 20 | | KEYCODE\_DPAD\_LEFT | 21 | | KEYCODE\_DPAD\_RIGHT | 22 | | KEYCODE\_DPAD\_CENTER | 23 | | KEYCODE\_VOLUME\_UP | 24 | | KEYCODE\_VOLUME\_DOWN | 25 | | KEYCODE\_POWER | 26 | | KEYCODE\_CAMERA | 27 | | KEYCODE\_CLEAR | 28 | | KEYCODE\_A | 29 | | KEYCODE\_B | 30 | | KEYCODE\_C | 31 | | KEYCODE\_D | 32 | | KEYCODE\_E | 33 | | KEYCODE\_F | 34 | | KEYCODE\_G | 35 | | KEYCODE\_H | 36 | | KEYCODE\_I | 37 | | KEYCODE\_J | 38 | | KEYCODE\_K | 39 | | KEYCODE\_L | 40 | | KEYCODE\_M | 41 | | KEYCODE\_N | 42 | | KEYCODE\_O | 43 | | KEYCODE\_P | 44 | | KEYCODE\_Q | 45 | | KEYCODE\_R | 46 | | KEYCODE\_S | 47 | | KEYCODE\_T | 48 | | KEYCODE\_U | 49 | | KEYCODE\_V | 50 | | KEYCODE\_W | 51 | | KEYCODE\_X | 52 | | KEYCODE\_Y | 53 | | KEYCODE\_Z | 54 | | KEYCODE\_COMMA | 55 | | KEYCODE\_PERIOD | 56 | | KEYCODE\_ALT\_LEFT | 57 | | KEYCODE\_ALT\_RIGHT | 58 | | KEYCODE\_SHIFT\_LEFT | 59 | | KEYCODE\_SHIFT\_RIGHT | 60 | | KEYCODE\_TAB | 61 | | KEYCODE\_SPACE | 62 | | KEYCODE\_SYM | 63 | | KEYCODE\_EXPLORER | 64 | | KEYCODE\_ENVELOPE | 65 | | KEYCODE\_ENTER | 66 | | KEYCODE\_DEL | 67 | | KEYCODE\_GRAVE | 68 | | KEYCODE\_MINUS | 69 | | KEYCODE\_EQUALS | 70 | | KEYCODE\_LEFT\_BRACKET | 71 | | KEYCODE\_RIGHT\_BRACKET | 72 | | KEYCODE\_BACKSLASH | 73 | | KEYCODE\_SEMICOLON | 74 | | KEYCODE\_APOSTROPHE | 75 | | KEYCODE\_SLASH | 76 | | KEYCODE\_AT | 77 | | KEYCODE\_NUM | 78 | | KEYCODE\_HEADSETHOOK | 79 | | KEYCODE\_FOCUS | 80 | | KEYCODE\_PLUS | 81 | | KEYCODE\_MENU | 82 | | KEYCODE\_NOTIFICATION | 83 | | KEYCODE\_SEARCH | 84 | | TAG\_LAST\_ KEYCODE | 85 | --- --- url: 'https://www.wuyunai.com/docs/v8/media.html' --- # media - 多媒体 > Stability: 2 - Stable media模块提供多媒体编程的支持。目前仅支持音乐播放和媒体文件扫描。后续会结合UI加入视频播放等功能。 需要注意是,使用该模块播放音乐时是在后台异步播放的,在脚本结束后会自动结束播放,因此可能需要插入诸如`sleep()`的语句来使脚本保持运行。例如: ```javascript //播放音乐 media.playMusic("/sdcard/1.mp3"); //让音乐播放完 sleep(media.getMusicDuration()); ``` ## media.scanFile(path) * `path` {string} 媒体文件路径 扫描路径path的媒体文件,将它加入媒体库中;或者如果该文件以及被删除,则通知媒体库移除该文件。 媒体库包括相册、音乐库等,因此该函数可以用于把某个图片文件加入相册。 ## media.playMusic(path\[, volume, looping]) * `path` {string} 音乐文件路径 * `volume` {number} 播放音量,为0~1的浮点数,默认为1 * `looping` {boolean} 是否循环播放,如果looping为`true`则循环播放,默认为`false` 播放音乐文件path。该函数不会显示任何音乐播放界面。如果文件不存在或者文件不是受支持的音乐格式,则抛出`UncheckedIOException`异常。 ```javascript //播放音乐 media.playMusic("/sdcard/1.mp3"); //让音乐播放完 sleep(media.getMusicDuration()); ``` 如果要循环播放音乐,则使用looping参数: ```javascript //传递第三个参数为true以循环播放音乐 media.playMusic("/sdcard/1.mp3", 1, true); //等待三次播放的时间 sleep(media.getMusicDuration() * 3); ``` 如果要使用音乐播放器播放音乐,调用`app.viewFile(path)`函数。 ## media.musicSeekTo(msec) * `msec` {number} 毫秒数,表示音乐进度 把当前播放进度调整到时间msec的位置。如果当前没有在播放音乐,则调用函数没有任何效果。 例如,要把音乐调到1分钟的位置,为`media.musicSeekTo(60 * 1000)`。 ```javascript //播放音乐 media.playMusic("/sdcard/1.mp3"); //调整到30秒的位置 media.musicSeekTo(30 * 1000); //等待音乐播放完成 sleep(media.getMusicDuration() - 30 * 1000); ``` ## media.pauseMusic() 暂停音乐播放。如果当前没有在播放音乐,则调用函数没有任何效果。 ## media.resumeMusic() 继续音乐播放。如果当前没有播放过音乐,则调用该函数没有任何效果。 ## media.stopMusic() 停止音乐播放。如果当前没有在播放音乐,则调用函数没有任何效果。 ## media.isMusicPlaying() * 返回 {boolean} 返回当前是否正在播放音乐。 ## media.getMusicDuration() * 返回 {number} 返回当前音乐的时长。单位毫秒。 ## media.getMusicCurrentPosition() * 返回 {number} 返回当前音乐的播放进度(已经播放的时间),单位毫秒。 --- --- url: 'https://www.wuyunai.com/docs/v8/modules.html' --- # module - 模块 > Stability: 2 - Stable Auto.js 有一个简单的模块加载系统。 在 Auto.js 中,文件和模块是一一对应的(每个文件被视为一个独立的模块)。 例子,假设有一个名为 foo.js 的文件: ```javascript var circle = require('circle.js'); console.log("半径为 4 的圆的面积是 %d", circle.area(4)); ``` 在第一行中,foo.js 加载了同一目录下的 circle.js 模块。 circle.js 文件的内容为: ```javascript const PI = Math.PI; var circle = {}; circle.area = function (r) { return PI * r * r; }; circle.circumference = (r) => 2 * PI * r; module.exports = circle; ``` circle.js 模块导出了 area() 和 circumference() 两个函数。 通过在特殊的 exports 对象上指定额外的属性,函数和对象可以被添加到模块的根部。 模块内的本地变量是私有的。 在这个例子中,变量 PI 是 circle.js 私有的,不会影响到加载他的脚本的变量环境。 module.exports属性可以被赋予一个新的值(例如函数或对象)。 如下,bar.js 会用到 square 模块,square 导出一个构造函数: ```javascript const square = require('square.js'); const mySquare = square(2); console.log("正方形的面积是 %d", mySquare.area()); square 模块定义在 square.js 中: // 赋值给 `exports` 不会修改模块,必须使用 `module.exports` module.exports = function(width) { return { area: () => width ** 2 }; }; ``` --- --- url: 'https://www.wuyunai.com/docs/v8/ocr.html' description: >- Auto.js Pro v8 OCR 模块 API 文档 - 基于 PaddleOCR 实现的光学文字识别功能,用于识别图片中的文字。支持打包时合并插件到 apk,无需单独安装。 --- > Pro 9.2 版本新增 $ocr 模块即光学文字识别,用于识别图片中的文字。该内置模块基于 PaddleOCR 实现,需要先在 Auto.js Pro 的插件商店中下载官方 PaddleOCR 插件才能使用。打包时插件可一并打包到 apk 中,无需单独安装插件。 另外,官方提供了另一个基于谷歌 MLKit 的 OCR 插件,参见[官方 MLKitOCR 插件](/blog/mlkit-ocr-plugin.html)。 # 插件下载 蓝奏云下载: 提示 特别感谢 Auto.js 爱好者 L(QQ: 2056968162,[7Zip 插件作者](/blog/7zip-plugin.html))提供的初始对接代码,并在后续提供了一些 Bug 修复和优化的帮助,大大节省了开发时间 ❤️。 ## $ocr.create(\[options]) * options {object} 可选参数,选项 option 包括以下内容: * `models` {string} 模型,`slim`指定精度相对低但速度更快的模型,若不指定则为`default`模型,精度高一点但速度慢一点。也可直接指定自定义模型的绝对路径。 * `labelsFile` {string} 模型的标签文件,默认为`null`,需要配合`models`字段使用。 * `cpuPowerMode` {string} CPU 模式,默认为 * `LITE_POWER_HIGH`,可选值有: * `LITE_POWER_HIGH` 绑定大核运行模式。如果 ARM CPU 支持 big.LITTLE,则优先使用并绑定 Big cluster,如果设置的线程数大于大核数量,则会将线程数自动缩放到大核数量。如果系统不存在大核或者在一些手机的低电量情况下会出现绑核失败,如果失败则进入不绑核模式。 * `LITE_POWER_LOW` 绑定小核运行模式。如果 ARM CPU 支持 big.LITTLE,则优先使用并绑定 Little cluster,如果设置的线程数大于小核数量,则会将线程数自动缩放到小核数量。如果找不到小核,则自动进入不绑核模式。 * `LITE_POWER_FULL` 大小核混用模式。线程数可以大于大核数量,当线程数大于核心数量时,则会自动将线程数缩放到核心数量。 * `LITE_POWER_NO_BIND` 不绑核运行模式(推荐)。系统根据负载自动调度任务到空闲的 CPU 核心上。 * `LITE_POWER_RAND_HIGH` 轮流绑定大核模式。如果 Big cluster 有多个核心,则每预测 10 次后切换绑定到下一个核心。 * `LITE_POWER_RAND_LOW` 轮流绑定小核模式。如果 Little cluster 有多个核心,则每预测 10 次后切换绑定到下一个核心。 * `parallelThreads` {number} 并行线程数,默认为`4` * `useOpenCL` {boolean} 是否使用 OpenCL,默认为`false` * 返回 {[OCR](#OCR)} 返回新的 OCR 对象 根据给定选项,创建 OCR 对象,可用于文字识别。一般而已不必自定义参数,使用`$ocr.create()`即可创建有效的 OCR 对象。 一个简单的截图并识别文字例子如下: ```javascript // 创建OCR对象,需要先在Auto.js Pro的插件商店中下载官方PaddleOCR插件。 let ocr = $ocr.create({ models: 'slim', // 指定精度相对低但速度更快的模型,若不指定则为default模型,精度高一点但速度慢一点 }); requestScreenCapture(); for (let i = 0; i < 5; i++) { let capture = captureScreen(); // 检测截图文字并计算检测时间,首次检测的耗时比较长 // 检测时间取决于图片大小、内容、文字数量 // 可通过调整$ocr.create()的线程、CPU模式等参数调整检测效率 let start = Date.now(); let result = ocr.detect(capture); let end = Date.now(); console.log(result); toastLog(`第$\{i + 1}次检测: ${end - start\}ms`); sleep(3000); } ocr.release(); ``` 有关资料参见[PaddleOCR 文档](https://paddle-lite.readthedocs.io/zh/latest/api_reference/java_api_doc.html)。 # OCR `$ocr.create()`返回的对象,用于具体的文字识别。该对象不再需要时,需要调用`release()`函数释放资源。 ## OCR.detect(image\[, options]) * `image` {Image} 图片,要识别文字的图片。 * options {object} 可选参数,选项 options 包括以下内容: * `max` {number} 识别文本的数量上限,默认为`1000` * `detectRotation` {boolean} 是否检测文字旋转,默认为`false` * `region` {Array} OCR 识别区域。是一个两个或四个元素的数组。(region\[0], region\[1])表示区域的左上角;region\[2]\*region\[3]表示区域的宽高。如果只有 region 只有两个元素,则区域为(region\[0], region\[1])到图片右下角。如果不指定 region 选项,则识别区域为整张图片。**此选项为 9.3 版本新增。** * 返回 {Array<[OCRResult](#OCRResult)>} 文字识别结果的数组,包括可信度、文本内容、文本范围等 对给定图片根据给定选项进行文字识别,将文字识别的结果作为数组返回。 ```javascript requestScreenCapture(); sleep(1000); let ocr = $ocr.create(); let capture = captureScreen(); let result = ocr.detect(capture); // 遍历结果,打印其文本 result.forEach(item => { console.log(item.text, item.confidence); }); // 过滤可信度0.9以上的文本 let filtered = result.filter(item => item.confidence > 0.9); // 模糊搜索文字内容为"Auto.js"的文本结果 let autojs = filtered.find(item => item.text.includes("编辑")); console.log(autojs); // 若搜索到则打印其可信度、范围和中点位置并点击 if (autojs) { console.log(`confidence = $\{autojs.confidence}, bounds = ${autojs.bounds}, center = (${autojs.bounds.centerX()}, ${autojs.bounds.centerY()\})`); autojs.clickCenter(); } ocr.release(); ``` ## OCR.release() 释放 OCR 资源,默认会在程序退出时自动释放,但请在不使用 OCR 及时释放以释放资源。 # OCRResult `$ocr.detect()`返回的数组的元素对象,包含了文字识别的可信度、文本内容、文本范围、文本旋转度以及文本旋转度的可信度等。 ## OCRResult.confidence * {number} OCR 文字的可信度,范围为\[0, 1],越接近 1 表示结果越准确、可信。 ## OCRResult.text * {string} OCR 识别的文字内容。 ## OCRResult.bounds * {[Rect](/v8/automator/api.html#rect-left)} 该识别文字在图片中的范围。 ## OCRResult.rotation * {number} 该识别文字在图片中的旋转角度,范围为\[0, 360),一般取值为 0 和 180 度。该字段仅在 detect 时指定`detectRotation`为`true`时有效。 ## OCRResult.rotationConfidence * {number} 该识别文字的旋转角度可信度,范围为\[0, 1]。该字段仅在 detect 时指定`detectRotation`为`true`时有效。 ## OCRResult.javaObject * {object} OCR 识别结果的原始 Java 对象。在官方 PaddleOCR 中没有什么用,在其他官方 OCR 中可能可以获取附加的额外信息,比如行、字段落、词语分割。 ## OCRResult.clickCenter() * 返回 {boolean} 在屏幕上点击 OCR 结果在图片中范围的中点位置,返回是否点击成功。实际上相当于`click(result.bounds.centerX(), result.bounds.centerY())`。 --- --- url: 'https://www.wuyunai.com/docs/v8/plugins.html' --- # plugins - 插件 Auto.js提供了加载插件的机制,允许用户编写带有Activity, Service, C/C++库等的apk,安装到Android设备上,并用Auto.js加载和调用。 一个插件是一个可独立安装的apk文件,用户安装后,再通过`$plugins`模块加载插件和调用其中的API。 从Pro 9.2开始,插件支持打包时合并到apk中,打包后无需再单独安装插件。 ::: tip Pro 9.2中,Auto.js Pro应用内置了插件商店,现在可在插件商店下载插件,参见博客[Auto.js Pro 9.2版本正式发布](https://blog.autojs.org/2022/09/13/pro-9-2-release-notes/)。 ::: ## $plugins.load(packageName) * `packageName` {string} 加载的插件包名 加载一个插件,并返回插件模块中module.exports导出的对象。 如果插件未安装,则抛出`PluginLoadException`异常。 ## 如何开发一个插件 提示 以下示例代码可在这里找到完整项目:[插件SDK](https://github.com/bowyn/Auto.js-Plugin-SDK) 本示例中的包名均为`org.autojs.plugin.sdk.demo`,在实际项目中插件包名可能有所不同。 ### 插件SDK集成 新建一个Android项目,在项目的build.gradle文件中添加: ```groovy allprojects { repositories { // ... maven { url 'https://jitpack.io' } } } ``` 在具体模块(比如app)的build.gradle文件中添加: ```groovy dependencies { // ... implementation 'com.github.hyb1996:Auto.js-Plugin-SDK:0.2' } ``` 更多信息参见[Jitpack.io](https://jitpack.io/#hyb1996/Auto.js-Plugin-SDK/0.2)。 ### 插件配置 #### 1. 新建`PluginHelloWorld`文件,继承于Plugin. ```java public class PluginHelloWorld extends Plugin { public PluginHelloWorld(Context context, Context selfContext, Object runtime, Object topLevelScope) { super(context, selfContext, runtime, topLevelScope); } // 返回插件的JavaScript胶水层代码的assets目录路径 @Override public String getAssetsScriptDir() { return "plugin-helloworld"; } // 插件public API,可被JavaScript代码调用 public String getStringFromJava() { return "Hello, Auto.js!"; } // 插件public API,可被JavaScript代码调用 public void say(String message) { getSelfContext().startActivity(new Intent(getSelfContext(), HelloWorldActivity.class) .putExtra("message", message) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } } ``` #### 2. 新增`MyPluginRegistry`文件,继承于PluginRegistry: ```java public class MyPluginRegistry extends PluginRegistry { static { // 注册默认插件 registerDefaultPlugin(new PluginLoader() { @Override public Plugin load(Context context, Context selfContext, Object runtime, Object topLevelScope) { return new PluginHelloWorld(context, selfContext, runtime, topLevelScope); } }); } } ``` 在AndroidManifest.xml中配置以下meta-data, `name`为`"org.autojs.plugin.sdk.registry"`,`value`为MyPluginRegistry的包名。 ```xml ``` #### 3. 编写JavaScript胶水层 在assets的相应目录(由Plugin.getAssetsScriptDir返回)中添加`index.js`文件,用于作为胶水层导出插件API。 ```javascript module.exports = function (plugin) { let runtime = plugin.runtime; let scope = plugin.topLevelScope; function helloworld() { } helloworld.stringFromJava = plugin.getStringFromJava(); helloworld.say = function (message) { plugin.say(message); } return helloworld; } ``` #### 4. 在Auto.js Pro中调用 编译插件为apk(assembleDebug/assembleRelease),安装到设备上。在Auto.js Pro中使用以下代码调用: ```javascript let helloworld = $plugins.load("org.autojs.plugin.sdk.demo"); console.log(helloworld.stringFromJava); helloworld.say("Hello, Auto.js Pro Plugin"); ``` #### 5. 独立服务AIDL方式调用 可以在插件中编写一个`Service`,由Auto.js Pro唤起和绑定,并可在js中通过aidl调用Service的接口。 在Plugin中重写方法`getService`。 ```java // 插件服务类,可选,用于AIDL方式和Auto.js Pro本体通信。可返回null @Override public ComponentName getService() { return new ComponentName(getSelfContext().getPackageName(), HelloworldPluginService.class.getName()); } ``` 新建一个Service组件(**注意在AndroidManifest中注册时需要是exported="true"**),继承于`PluginService`。 ```java public class HelloworldPluginService extends PluginService { private static final String ACTION_ADD = "add"; @Override protected Result onRemoteCall(@NonNull String action, @NonNull Map args, @Nullable RemoteCallback callback) throws RuntimeException { switch (action) { case ACTION_ADD: return invokeAdd(args); } return Result.notImplemented(action); } private Result invokeAdd(Map args) { Number a = PluginUtils.getNotNull(args, "a"); Number b = PluginUtils.getNotNull(args, "b"); double sum = a.doubleValue() + b.doubleValue(); return new Result(Collections.singletonMap("sum", sum)); } } ``` 在index.js中添加胶水层代码: ```javascript helloworld.remoteAdd = function (a, b) { return plugin.waitForConnection().call('add', { a: a, b: b }, null).get('sum'); } ``` 然后可以在Auto.js Pro中调用: ```javascript let helloworld = $plugins.load("org.autojs.plugin.sdk.demo"); console.log(helloworld.remoteAdd(1, 2)); ``` --- --- url: 'https://www.wuyunai.com/docs/v8/powerManager.html' --- # power_manager - 电源管理 **\[v8.3.3新增]** 此模块可让您控制设备的电源状态。使用此API有可能影响设备的电池寿命。 ## $power\_manager.isIgnoringBatteryOptimizations(\[pkg]) * `pkg` {string} 包名,默认为本应用包名 * 返回 {boolean} 返回当前是否对应用pkg启用了【忽略电池优化】。 ```javascript log("忽略电池优化是否开启: " + $power_manager.isIgnoringBatteryOptimizations()) ``` ## $power\_manager.requestIgnoreBatteryOptimizations(forceRequest, pkg) * `forceRequest` {boolean} 如果为false,并且当前已经开启了忽略电池优化,则不执行请求;如果为true,则都请求忽略电池优化。默认为false。 * `pkg` {boolean} 需要忽略电池优化的包名。默认为本应用包名。 请求用户忽略对应用pkg的电池优化。系统将会弹出一个弹窗提示用户确认,这个过程是异步的,确认结果不会返回。 ```javascript if (!$power_manager.isIgnoringBatteryOptimizations()) { toastLog("未开启忽略电池优化,请求中..."); $power_manager.requestIgnoreBatteryOptimizations(); } ``` --- --- url: 'https://www.wuyunai.com/docs/v8/scriptingJava.html' --- # 和Java交互 Rhino提供了非常方便地和Java交互的能力。 ## liveConnect:与JavaScript的Java通信 Rhino允许您从JavaScript中创建Java类并调用Java方法。例如: ```javascript let builder = new java.lang.Builder(); builder.append('test'); builder.append(1); console.log(builder.toString()); ``` ## 访问JavaBean属性 Java类可以使用getter和Setter方法定义JavaBean属性。例如,以下类定义了两个属性: ```java public class Me { public int getAge() \{ return mAge; \} public void setAge(int anAge) \{ mAge = anAge; \} public String getSex() \{ return "male"; \} private int mAge; }; ``` 定义的两个属性是 *age\_和\_sex*。 \_sex\_属性是只读的:它没有Setter。 使用Rhino我们可以访问Bean属性,就像它们一样的JavaScript属性。我们也可以继续调用定义属性的方法。 ```javascript let me = new Me(); console.log(me.sex); me.age = 33; console.log(me.age); console.log(me.getAge()); ``` 由于\_sex\_属性是只读的,因此我们不允许写入它。 ## 导入Java类和包 上面我们看到了importPackage函数的使用来从特定的Java包导入所有类。还有importClass,它导入单个类。 你可以直接使用`android.view.View`来表示Android中的View类,默认支持的顶级包名前缀为`com`, `android`, `java`, `org`,对于其他包名,需要使用`Packages`对象,比如`Packages.javax.xml.xpath.XPath`或`Packages["javax.xml.xpath.XPath"]`。 也可以使用`importClass`或`importPackage`函数来导入Java/Android中的包名或类,比如: ```javascript importClass("android.view.KeyEvent"); // 或者 let KeyEvent = android.view.KeyEvent; // 或者 importPackage("android.view"); ``` ## 扩展Java类并使用JavaScript实现Java接口 例如为某个UI中的控件设置点击监听OnClickListener: ```javascript "ui"; $ui.layout(