手游脚本开发课程--auto.js入门教程:https://www.bilibili.com/video/BV1Pr4y1H7R9
手游辅助教程:https://search.bilibili.com/all?keyword=手游辅助教程
AutomateIt、Automate、按键精灵、AutoJS…等Android自动化工具有什么特点?:https://www.zhihu.com/question/59503646
- Auto.js:是一个开源的自动化程序,因为多用在黑灰产,被某些资本警告,导致该工具无法对某些应用进行操作 ( 例如:微信、支付宝、抖音 等 )。4.1 版本是免费版的最后一个版本,可以在所有 app 上进行操作。 Auto.js 中文文档:https://pro.autojs.org/docs/
- autojs pro:是原作者在 autojs 上提供了一个付费的工具,拥有更强大的功能,但是同样无法操作某些软件。
- AutoX.js:是在 Auto.js4.1 版本基础上 fork而来,可操作所有软件,并拥有大量 autojs pro 的功能。 AutoX.js :http://doc.autoxjs.com/#/
- AutoJs6:https://github.com/SuperMonster003/AutoJs6
- 一触即发:http://www.yicuba.com/index.html
- 点击助手Pro:https://www.ghxi.com/djzs.html
- Hamibot:类似 autojs 的自动化工具,可以通过浏览器远程控制。适用于安卓系统的自动化工具,能全自动操控任意 APP。( 需要注册账号才能进行操作 )。 Hamibot :https://hamibot.com/
Hamibot 连接手机:https://www.i3zh.com/22556.html- 冰狐智能辅助 (类似 Autojs,比 autojs 简单,需要注册账号):https://aznfz.com/
- Ctrl.js 开发文档:https://ctrljs.ikaiwei.com/ctrljsapi/#/
Ctrl.js ( 类似auto.js ):http://www.feiyunjs.com/2266.html- EasyClick 和Auto.js区别:http://www.feiyunjs.com/2456.html
EasyClick 开发文档:http://ecdoc.laoleng.vip/docs/
关键字:类似 autojs 、autojs 综合实战
AutoJs Pro 7.0.4-1 实战教程---史上最全快手、抖音极速版
- :https://blog.csdn.net/zy0412326/article/details/107180887/
- :https://blog.csdn.net/zy0412326/article/details/107190828
示例:
- AutoJs 4.1.0 实战教程---终极福利Apk:https://blog.csdn.net/zy0412326/article/details/105120435
- 自动化篇 | 使用 AutoJS 自动领京豆:https://www.bianchengquan.com/article/589931.html
- 2021 Autojs 全网最全几十种小游戏和自阅合集 (含源码):脚本赚钱:https://blog.csdn.net/qq_29117491/article/details/118634537
- 最新热门脚本Autojs源码分享:https://www.jb51.net/article/212542.htm
资料:
- Auto.js 快速入门实战教:https://github.com/ErazerControl/2019double11
- Auto.js 从入门到精通( 95集 ):https://www.bilibili.com/video/BV1pQ4y1R7Us
- Auto.js(基础、速成、初中、高阶、终极、实战)
:https://www.songma.com/product/view338899.html
【autojs】中级进阶教程:https://www.bilibili.com/video/BV1eg411L7DDauto.js 安卓脚本游戏脚本( 37集 ):https://www.bilibili.com/video/BV1Po4y1Q7bE
- autojs 入门视频( 23集 ):https://www.bilibili.com/video/BV1g5411L7G6
- B站搜索的 autojs :https://search.bilibili.com/all?keyword=autojs
Autojs 从入门到放弃(AcFun 视频)
:https://www.acfun.cn/v/ac17943282
:https://zhuanlan.zhihu.com/p/156660560AutoJS4.1.0实战教程:https://blog.csdn.net/zy0412326/article/details/104767602/
1、Auto.js、AutoX.js 介绍
Auto.js 是一个支持无障碍服务的 Android 平台上的 JavaScript IDE。Autojs 主要是基于安卓系统的无障碍服务,实现自动化操控和监控手机信息处理。
根据官方文档定义:Auto.js 是一款无需 root 权限的 JavaScript 自动化软件。如何理解它?
- Auto.js 是一款安卓手机应用,和微信一样,安装在手机上使用
- Auto.js 是一款自动化软件,根据脚本内容便可以自动地执行相关的操作,并且手机无需root
- Auto.js 的脚本需要使用JavaScript编写
特性:
- 由无障碍服务实现的简单易用的自动操作函数
- 悬浮窗录制和运行
- 更专业&强大的选择器 API,提供对屏幕上的控件的寻找、遍历、获取信息、操作等。类似于 Google 的 UI 测试框架 UiAutomator,您也可以把他当做移动版UI测试框架使用
- 采用 JavaScript 为脚本语言,并支持代码补全、变量重命名、代码格式化、查找替换等功能,可以作为一个 JavaScript IDE 使用
- 支持使用 e4x 编写界面,并可以将 JavaScript 打包为 apk 文件,您可以用它来开发小工具应用
- 支持使用 Root 权限以提供更强大的屏幕点击、滑动、录制功能和运行 shell 命令。录制录制可产生js文件或二进制文件,录制动作的回放比较流畅
- 提供截取屏幕、保存截图、图片找色、找图等函数
- 可作为 Tasker 插件使用,结合 Tasker 可胜任日常工作流
- 带有界面分析工具,类似 Android Studio 的 LayoutInspector,可以分析界面层次和范围、获取界面上的控件信息
app: 应用。启动应用,卸载应用,使用应用查看、编辑文件、访问网页,发送应用间广播等。
console: 控制台。记录运行的日志、错误、信息等。
device: 设备。获取设备屏幕宽高、系统版本等信息,控制设备音量、亮度等。
engines: 脚本引擎。用于启动其他脚本。
events: 事件与监听。按键监听,通知监听,触摸监听等。
floaty: 悬浮窗。用于显示自定义的悬浮窗。
files: 文件系统。文件创建、获取信息、读写。
http: HTTP。发送HTTP请求,例如GET, POST等。
images, colors: 图片和图色处理。截图,剪切图片,找图找色,读取保存图片等。
keys: 按键模拟。比如音量键、Home键模拟等。
shell: Shell命令。
threads: 多线程支持。
ui: UI界面。用于显示自定义的UI界面,和用户交互。
除此之外,Auto.js 内置了对 Promise。
2、安装 AutoX.js、编写脚本
安装 AutoX.js
- 手机安装 AutoX.js 应用
- 开启手机无障碍服务,目的是为了让脚本能执行。
- 开启悬浮框(为了查看控件信息)
配置 开发环境
AutoX.js 使用 JavaScript 作为脚本语言,目前使用 Rhino 1.7.13 作为脚本引擎,支持 ES5 与部分 ES6 特性。
- 学习 AutoX.js 的 API 之前,建议先学习 JavaScript 的基本语法。
- 如果想要在电脑上开发 AutoX.js,可以使用 VSCode 以及 AutoX.js 插件。
- 如果想要使用 TypeScript 来开发,目前有开发者公布了一个 相关工具。
这里直接使用 VSCode 和 AutoX.js 插件进行开发。安装好 VSCode 和 AutoX.js 插件后,VSCode 打开 命令面板 ( Ctrl + shift + p ),输入 autoxjs 即可看到 插件支持的命令
找到 start server 点击即可启动服务。然后手机上打开悬浮窗并连接电脑。至此环境配置成功
编写脚本
上面插件安装成功后,就可以在vscode上编写并运行,手机就会相应执行脚本
也可以直接在手机的 Auto.js 应用中直接码代码,不过那酸爽谁用谁知道。。。
示例脚本:
// 购物车按钮的id
const carId = id('com.yaya.zone:id/rl_car_layout');
// 控制台与手机弹窗输出 toastLog autox.js 全局函数,请查看官方文档
//控件是否存在
toastLog('控件是否存在:'+carId.exists())
//搜索到唯一元素并点击
carId.findOnce().click()
开发 相关
使用如下代码可以直接跳转到APP内的某个页面
// 这样启动没有广告,有点android开发经验,贼爽
app.startActivity({
action: "android.intent.action.VIEW",
className: "com.yaya.zone.home.HomeActivity",
packageName: "com.yaya.zone",
});
className 与 packageName 查看方法:MT管理器 -> 左上角菜单->Activity记录
打包成 app
使用 autox.js 打包成 app,打包的软件报毒
这个问题是因为打包后的app用了 autojs 作为包名,需要用MT管理器把apk 的xml中的autojs字符串换成任意其他值就可以了。
autojs打包的软件报毒:https://zhuanlan.zhihu.com/p/425140138
autojs 之权限设置
示例:申请截图代码
if (!requestScreenCapture()) {
toastLog("请求截图权限 失败");
} else {
toastLog("请求截图权限 成功");
}
执行以上代码后, 正常应该弹出一个框, 让用户选择是否允许截图。但是有的时候, 不会弹出这个界面,这个时候就要去 设置-->应用管理-->AutoJsPro-->权限管理-->后台弹出界面-->允许
悬浮窗权限。
判断是否有悬浮窗权限的代码
floaty.checkPermission();
返回一个布尔值, true就是有权限, false就是没有权限
如果app没有悬浮窗权限,提示用户,然后跳转悬浮窗权限设置界面, 方便用户勾选
跳转悬浮窗权限设置界面代码
app.startActivity({
packageName: "com.android.settings",
className: "com.android.settings.Settings$AppDrawOverlaySettingsActivity",
data: "package:" + context.getPackageName(),
});
后台自启权限,跳转启动管理页面的代码
let intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
let pkg = "com.huawei.systemmanager";
let cls = "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity";
let componentName = new android.content.ComponentName(pkg, cls);
intent.setComponent(componentName);
context.startActivity(intent);
关于 官网 文档
AutoX.js 基于 Auto.js4.1 版本,使用的是 Rhino(第一代API):https://pro.autojs.org/docs/zh/v8/
由于官方4.1.1文档 经常会打不开,所以这里重新copy了一份
auto.js文档:https://easydoc.net/doc/84873548/LvEnNZ0d/VuWWeK5N
第二代API和第一代API的区别
Node.js(第二代API)对比 Rhino(第一代API)的优势是:
- Node.js引擎的JavaScript执行性能是Rhino的100倍以上
- 使用Node.js引擎的代码加密强度高,目前不能被还原
- Node.js支持ES2021以上语言标准,Rhino仅支持ES5和部分ES6特性
- Node.js引擎本身的Bug基本很少,而Rhino引擎的模块系统、语言实现本身有不少Bug
- Node.js对应的第二代API设计较好、更加标准
- 可以使用第三方npm包
- Node.js的网络资料较多
Node.js(第二代API)对比 Rhino(第一代API)的劣势是:
- Node.js对应的第二代API上手门槛较高,需要对Promise、异步有一定了解,尤其对新手来说
- 第二代API的文档阅读较难,并且目前正在完善中
- Rhino和第一代API的社区的源码、资料、示例较多
- 第一代API使用上比较方便
刚接触 Auto.js Pro 时如何选择引擎
如果你是:
- 没有编程基础的新手,并且不想深入学习编程
- 代码能跑就行,不追求可维护性、可读性
- 不追求最新语言标准,能容忍引擎、API设计本身有不符合标准的地方和bug
那么建议你使用Rhino引擎和第一代API,上手较快。你无需特别配置,代码默认都以该引擎执行。
如果你是:
- 计算机专业出身或者有一定开发经验
- 第一次学习编程,但想学习行业标准和规范,为以后学习深入或学习Android/JavaScript/Web等打基础
- 有一定的代码素养和追求
- 有较高的软件安全、加密需求
- 想用npm包,实现比如连接mysql等需求
- 追求更高的JS运行性能
- 热爱编程,或者热爱探索,热爱学习
那么建议你使用Node.js引擎和第二代API,对Rhino引擎和第一代API了解即可。
引擎的选择并非绝对,可以一边使用Rhino引擎一边使用Node.js引擎,或者在学习一段时间后再看另一个引擎/API。
示例:领取淘宝喵币
1. 打开淘宝 ---> 点击领喵币按钮
为了编写脚本简单,淘宝预先打开喵铺主页
auto.waitFor()
let app_name = "微信";
launchApp(app_name);
sleep(10000);
// 根据 "文字信息" 寻找按钮, 并点击
let btn_1 = text("用短信验证码登录").findOnce();
if (btn_1) {
toast("定位到 微信");
btn_1.click();
sleep(1000);
text("获取验证码").findOnce().click();
}
else {
toast("未检测到验证码按钮");
//中止脚本
exit();
}
详解:
- Auto.js无需root,但是需要对该应用开启无障碍模式,开启后才可以进行屏幕点击等操作auto.waitFor()表示直到检查该应用开启无障碍后才执行其后面的代码,否则一直卡在这里,一般放到脚本的第一行
- launchApp()可以打开对应应用,由于不同手机响应速度不同,本代码让它睡眠3s。
- 定位组件是autojs的最常见的操作,这很类似前端的定位dom元素。在任何点击之前都需要找到对应的组件,这不同于点击某像素位置,点击组件更能适配不同分辨率的手机。在auto.js中通过各种条件选取到的控件称为UiSelector ( https://hyb1996.github.io/AutoJs-Docs/#/widgetsBasedAutomation?id=uiselector )。那么筛选条件是如何确定呢?打开Auto.js应用的悬浮窗,在喵铺主页,点击Auto.js悬浮窗后选择出现的蓝色按钮,点击布局范围分析后选择领喵币按钮查看控件信息,你就能看到如图2所示的信息。
- click() 表示点击该元素。
- toast() 表示展示一个消息框。
2. 点击去进店/去浏览
//开始执行任务
execTask();
function execTask() {
while(true) {
var target = text("去进店").findOnce() || text("去浏览").findOnce();
if (target == null) {
toast("任务完成");
break;
}
target.click();
sleep(3000);
//浏览网页20s
viewWeb(20);
back();
sleep(1000);
}
}
viewWeb 是一会要写的函数,目的是模拟浏览网页20s的操作,虽说淘宝要求15s就行了,但是可能部分手机加载耗时比较多,所以多写了5s。当判断任务栏有"去进店"、"去浏览"的组件时,点击跳转至浏览广告,浏览完毕后,返回至任务栏页面,循环执行该操作直到找不到"去进店"、"去浏览"的组件结束(任务完成后按钮文字会变成"已完成")。
3. 浏览广告
function viewWeb(time) {
gesture(1000, [300, 600], [300, 300]);
var cnt = 1;
while(true) {
var finish = desc("任务完成").exists() || textStartsWith("已获得").exists();
if (finish || cnt > time) {
break;
}
sleep(1000);
cnt += 1;
}
//模拟返回键,返回到任务栏页面
back();
}
函数的参数为当前页面的最大停留时间(防止意外而一直停留该页面)。
浏览广告完成的标志:
- 当前页面出现"任务完成"或"已获得*****"的组件
- 位于当前页面的时间大于所设定的最大限制
gesture 是指屏幕滑动操作,这是本文唯一涉及屏幕像素的语句。gesture(duration, [x1, y1], [x2, y2]表示用duration的时间,从(x1,y1)点滑到(x2,y2)点,代码中表示如图用1s从黄点滑向红点,故是上滑操作浏览广告。
本次淘宝的活动最开始滑动一次,之后等着时间够了即可,故代码中没有再额外滑动。在浏览广告完毕后back()模拟返回键返回值任务栏页面。
这段代码定位组件用到了desc(),之所于用desc是因为该控件的desc信息是"任务完成",还记得如何查看控件信息吧?总之,想定位控件,就先去查它的控件信息。
4. 测试
代码终于写完了,将脚本发到手机中,在Auto.js应用点击右下角的+号,选择导入,在文件目中寻找对应的脚本加载。
将淘宝打开至喵铺主页,返回 Auto.js,点击运行即可执行脚本,在日志处可以查看脚本运行日志,我还没提到日志?在脚本中你可以使用 log() 函数记录日志,这等同于 print,常用于调试或记录日志信息。
5. 打包成 apk
编写完代码,将代码打包成一个独立的 apk。
- 右下角+号,新建文件夹命名为 double11
- 在 double11 文件夹中加载对应脚本
- 如图选择,打包 apk
示例:滚动浏览朋友圈
示例代码:
function print_lian_xi_ren(){
/* 找控件 */
log(text("通讯录").findOne().parent().parent());
/* 当找到的控件 的 "点击属性" 是 false 时,可能是这个控件本身嵌套在另一个控件中,
外层控件是内层控件的父控件,查看父控件是否可点击,
如果父控件 的 "点击属性" 仍然是 false 时,继续查找父控件,
直到找到 "点击属性" 是 true 时,即可 点击
*/
var btn_txl = text("通讯录").findOne().parent().parent();
btn_txl.click();
sleep(1000)
/* 打印联系人 */
var lxr = id("com.tencent.mm:id/ft6").find()
lxr.forEach(element => {
people_name = element.text();
log(people_name);
click(people_name);
sleep(1000);
back();
sleep(1000);
});
}
function cha_kan_peng_you_quan(){
/* 控件坐标 */
var bounds = text("发现").findOne().bounds()
var x = bounds.centerX();
var y = bounds.centerY();
click(x,y);
sleep(3000)
var btn_pyq = text("朋友圈").findOne();
x = btn_pyq.bounds().centerX();
y = btn_pyq.bounds().centerY();
click(x,y);
sleep(1000);
var android_widget = className("android.widget.ListView")
while(true){
/* 滚动浏览朋友圈 */
android_widget.scrollDown();
sleep(1000);
}
}
auto.waitFor();
// home();
var app_name = "微信"
app.launchApp(app_name);
sleep(1000)
print_lian_xi_ren();
cha_kan_peng_you_quan()
搜索、定位
搜索和定位对象之前,需要先了解下安卓的布局、组件、以及组件属性
Android 布局、组件、组件的属性
Android 常用布局
Tables | Are |
---|---|
线性布局 | linearLayout |
表格布局 | TableLayout |
相对布局 | RelativelLayout |
帧布局 | FrameLayout |
网格布局 | GridLayout |
绝对布局 | AbsoluteLayout |
Android 常用组件
文本框、编辑框、按钮、单选按钮、复选框、状态开关按钮、拖动条 等等
Android 组件的属性
属性值 | 值类型 | 例子 |
---|---|---|
index | int | 0 |
instance | int | 5 |
class | String | android.widget.TextView |
package | String | com.jian.test |
Content desc | String | string |
checkable | boolean | false |
clecked | boolean | false |
clickable | boolean | true |
enabled | boolean | false |
focusable | boolean | false |
focused | boolean | false |
Scrollable | boolean | false |
Long-clickable | boolean | false |
password | boolean | false |
selected | boolean | false |
bounds | Rect | [366,999][708,1197] |
4种匹配关系的介绍
- 完全匹配(默认)
- 包含匹配(Contains)
- 正则匹配 (Matches) – 可以包含完全匹配、包含匹配、起始匹配
- 起始匹配 (StartWith)
节点 关系
搜索、定位
确定要控件(唯一性),以下三个方法基本可满足日常:
- ① depth、desc、id、classname 等控件的常规的唯一属性.
- ② findone.parent().child(xx) 等控件的父子依存顺序关系.
- ③ 最后实在无法确定可用坐标法 bounds,click(x,y) 操作控件
UiSelector 选择器:https://easydoc.net/doc/84873548/LvEnNZ0d/6SafZjmz
UiSelector即选择器,用于通过各种条件选取屏幕上的控件,再对这些控件进行点击、长按等动作。这里需要先简单介绍一下控件和界面的相关知识。
一般软件的界面是由一个个控件构成的,例如图片部分是一个图片控件(ImageView),文字部分是一个文字控件(TextView);同时,通过各种布局来决定各个控件的位置,例如,线性布局(LinearLayout)里面的控件都是按水平或垂直一次叠放的,列表布局(AbsListView)则是以列表的形式显示控件。
控件有各种属性,包括文本(text), 描述(desc), 类名(className), id等等。我们通常用一个控件的属性来找到这个控件,例如,想要点击QQ聊天窗口的"发送"按钮,我们就可以通过他的文本属性为"发送"来找到这个控件并点击他,具体代码为:
var sendButton = text("发送").findOne(); sendButton.click();
在这个例子中, text("发送")
表示一个条件(文本属性为"发送"),findOne()
表示基于这个条件找到一个符合条件的控件,从而我们可以得到发送按钮sendButton,再执行sendButton.click()
即可点击"发送"按钮。
用文本属性来定位按钮控件、文本控件通常十分有效。但是,如果一个控件是图片控件,比如Auto.js主界面右上角的搜索图标,他没有文本属性,这时需要其他属性来定位他。我们如何查看他有什么属性呢?首先打开悬浮窗和无障碍服务,点击蓝色的图标(布局分析), 可以看到以下界面:
之后我们点击搜索图标,可以看到他有以下属性:
我们注意到这个图标的desc(描述)属性为"搜索",那么我们就可以通过desc属性来定位这个控件,得到点击搜索图标的代码为:
desc("搜索").findOne().click();
可能心细的你可能注意到了,这个控件还有很多其他的属性,例如checked, className, clickable等等,为什么不用这些属性来定位搜索图标呢?答案是,其他控件也有这些值相同的属性、尝试一下你就可以发现很多其他控件的checked属性和搜索控件一样都是false
,如果我们用checked(false)
作为条件,将会找到很多控件,而无法确定哪一个是搜索图标。因此,要找到我们想要的那个控件,选择器的条件通常需要是可唯一确定控件的。我们通常用一个独一无二的属性来定位一个控件,例如这个例子中就没有其他控件的desc(描述)属性为"搜索"。
另外,对于这个搜索图标而言,id 属性也是唯一的,我们也可以用id("action_search").findOne().click()
来点击这个控件。如果一个控件有id属性,那么这个属性很可能是唯一的,除了以下几种情况:
- QQ的控件的id属性很多都是"name",也就是在QQ界面难以通过id来定位一个控件
- 列表中的控件,比如QQ联系人列表,微信联系人列表等
尽管id属性很方便,但也不总是最方便的,例如对于微信和网易云音乐,每次更新他的控件id都会变化,导致了相同代码对于不同版本的微信、网易云音乐并不兼容。
除了这些属性外,主要还有以下几种属性:
className
类名。类名表示一个控件的类型,例如文本控件为"android.widget.TextView", 图片控件为"android.widget.ImageView"等。packageName
包名。包名表示控件所在的应用包名,例如QQ界面的控件的包名为"com.tencent.mobileqq"。bounds
控件在屏幕上的范围。drawingOrder
控件在父控件的绘制顺序。indexInParent
控件在父控件的位置。clickable
控件是否可点击。longClickable
控件是否可长按。checkable
控件是否可勾选。checked
控件是否可已勾选。scrollable
控件是否可滑动。selected
控件是否已选择。editable
控件是否可编辑。visibleToUser
控件是否可见。enabled
控件是否已启用。depth
控件的布局深度。
有时候只靠一个属性并不能唯一确定一个控件,这时需要通过属性的组合来完成定位,例如className("ImageView").depth(10).findOne().click()
,通过链式调用来组合条件。
通常用这些技巧便可以解决大部分问题,即使解决不了问题,也可以通过布局分析的"生成代码"功能来尝试生成一些选择器代码。接下来的问题便是对选取的控件进行操作,包括:
click()
点击。点击一个控件,前提是这个控件的clickable属性为truelongClick()
长按。长按一个控件,前提是这个控件的longClickable属性为truesetText()
设置文本,用于编辑框控件设置文本。scrollForward()
,scrollBackward()
滑动。滑动一个控件(列表等), 前提是这个控件的scrollable属性为trueexits()
判断控件是否存在waitFor()
等待控件出现
这些操作包含了绝大部分控件操作。根据这些我们可以很容易写出一个"刷屏"脚本(代码仅为示例,请不要在别人的群里测试,否则容易被踢):
while(true){
className("EditText").findOne().setText("刷屏...");
text("发送").findOne().clicK();
}
上面这段代码也可以写成:
while(true){
className("EditText").setText("刷屏...");
text("发送").clicK();
}
如果不加findOne()
而直接进行操作,则选择器会找出所有符合条件的控件并操作。
另外一个比较常用的操作的滑动。滑动操作的第一步是找到需要滑动的控件,例如要滑动QQ消息列表则在悬浮窗布局层次分析中找到AbsListView
,这个控件就是消息列表控件,如下图:
长按可查看控件信息,注意到其scrollable属性为true,并找出其id为"recent_chat_list",从而下滑QQ消息列表的代码为:
id("recent_chat_list").className("AbsListView").findOne().scrollForward();
scrollForward()
为向前滑,包括下滑和右滑。
选择器的入门教程暂且要这里,更多信息可以查看 UiSelector 和 选择器进阶。
定位选择器:findOnce() 表示找到第一个满足条件的控件,找不到返回null;另外还有findOne(),findOne(time)等方法,具体的可以看文档,这里有一个坑,findOne() 如果没找到匹配的组件会一直找,直至所描述的控件出现为止,故该函数不会返回null,找不到可能会卡在这里,所以谨慎使用。
click():当定位的元素的clickable属性为true时,才可以点击,如果你查看某控件的clickable为false,那说明此控件不能点!!你可能是想点击它的子/父控件。
UiSelector 下的所有定位方法
通过 "文本属性" 定位对象
返回值 | API | 说明 |
---|---|---|
UiSelector | text(String text) | 文本 |
UiSelector | textContains(String text) | 文本包含 |
UiSelector | textMatches(String regex) | 文本正则 |
UiSelector | textStartsWith(String text) | 文本起始匹配 |
通过 "描述属性" 定位对象
返回值 | API | 说明 |
---|---|---|
UiSelector | description(String desc) | 描述 |
UiSelector | descriptionContains(String desc) | 描述包含 |
UiSelector | descriptionMatches(String regex) | 描述正则 |
UiSelector | descriptionStartsWith(String desc) | 描述开始字符匹配 |
通过 "类名、包名" 定位对象
返回值 | API | 说明 |
---|---|---|
UiSelector | className(String className) | 类名 |
UiSelector | classNameMatches(String regex) | 正则类名 |
包名属性定位
回值 | API | 说明 |
---|---|---|
UiSelector | packageName(String name) | 包名 |
UiSelector | packageNameMatches(String regex) | 包名正则 |
通过 "特殊属性与节点" 定位对象
返回值 | API | 说明 |
---|---|---|
UiSelector | checked(booleean val) | 选择属性 |
UiSelector | clickable(boolean val) | 可点击属性 |
UiSelector | enabled(boolean val) | enabled属性 |
UiSelector | focusable(boolean val) | 焦点属性 |
UiSelector | focused(boolean val) | 当前焦点属性 |
UiSelector | longClickable(boolean val) | 长按属性 |
UiSelector | scrollable(boolean val) | 滚动属性 |
UiSelector | selected(boolean val) | 背景选择属性 |
通过 "id" 定位对象 ( 推荐使用 )
每一个组件都会有编号,也就是ID
事件与监听 - Events 详细讲解
From ( 事件与监听 - Events详细讲解 QQ消息、微信消息 通知服务 ):https://www.bilibili.com/video/BV14g4y1q7mr
等待 界面 出现
/*等待界面出现的三种方法*/
//方法 1
while(true){
if(text("控件上的文字").exists()){
break;
}
}//方法2
text("控件上的文字").waitFor();//方法3
text("控件上的文字").findOne();
示例:
// 启用按键监听
evenntsevents.observeKey();// 监听音量 上键 按下
events.onKeyDown("volume_up", function(event){
toast("音量上键被按下");
});events.onKeyDown('volume_down', function(){
toast("菜单键被按下");
exit();
});
events.on("key", function(key_code, event){
if(key_code == keys.volume_up && envets.getAction() == event.ACTION_UP){
toast("菜单键被按下");
}
});
events.observeNotification();
events.onNotification(function(notification){
printNotification(notification)
});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);
}
事件与监听 - Events
按键事件
- events.emitter()
- events.observeKey()
- events.onKeyDown(keyName, listener)
- events.onKeyUp(keyName, listener)
- events.onceKeyDown(keyName, listener)
- events.onceKeyUp(keyName, listener)
- events.removeAllKeyDownListeners(keyName)
- events.removeAllKeyUpListeners(keyName)
- events.setKeyInterceptionEnabled([key, ]enabled)
- events.observeTouch()
- events.setTouchEventTimeout(timeout)
- events.getTouchEventTimeout()
- events.onTouch(listener)
- events.removeAllTouchListeners()
用其他按键强行停止脚本
events.onKeyDown("home", function (event) {//按home停止
toast("程序结束")
console.hide()
threads.shutDownAll()
})
events.onKeyDown("volume_down", function (event) {//按音量下停止
toast("程序结束")
console.hide()
threads.shutDownAll()
})
事件
- 事件: 'key'
- 事件: 'key_down'
- 事件: 'key_up'
- 事件: 'exit`
- events.observeNotification()
- events.observeToast()
- 事件: 'toast'
通知
- Notification
- KeyEvent
- keys
- EventEmitter
- EventEmitter.defaultMaxListeners
- EventEmitter.addListener(eventName, listener)
- EventEmitter.emit(eventName[, ...args])
- EventEmitter.eventNames()
- EventEmitter.getMaxListeners()
- EventEmitter.listenerCount(eventName)
- EventEmitter.listeners(eventName)
- EventEmitter.on(eventName, listener)
- EventEmitter.once(eventName, listener)#
- EventEmitter.prependListener(eventName, listener)
- EventEmitter.prependOnceListener(eventName, listener)
- EventEmitter.removeAllListeners([eventName])
- EventEmitter.removeListener(eventName, listener)
- EventEmitter.setMaxListeners(n)
- events.broadcast: 脚本间广播
4、图片 和 颜色
Auto.js 从入门到精通:https://www.bilibili.com/video/av715713093/
找图、找色
images 模块提供了一些手机设备中常见的图片处理函数,包括截图、读写图片、图片剪裁、旋转、二值化、找色找图等。
:https://hyb1996.github.io/AutoJs-Docs/#/images?id=images
该模块分为两个部分:
- 找图找色部分
- 图片处理部分
示例代码:
颜色转换方法
/*
// 返回颜色值得字符串,格式为 "#AARRGGBB"
colors.toString(colorNum)
colors.red(Num | str) // 返回颜色 color 的 R 通道的值,范围 0~255
colors.green(Num | str) // 返回颜色 color 的 G 通道的值,范围 0~255
colors.blue(Num | str) // 返回颜色 color 的 B 通道的值,范围 0~255
colors.alpha(Num | str) // 返回颜色 color 的 alpha (透明度) 的值,范围 0~255
// 返回 redNum, greenNum, blueNum 构成的颜色值,alpha为255,即不透明
colors.rgb(redNum, greenNum, blueNum)
colors.argb(alpha, redNum, greenNum, blueNum)
// 返回颜色的整数值
colors.parseColor(colorStr)
*/
判断颜色是否相似或者相等
if(!requestScreenCapture()) {
toast("请求截图权限失败");
exit();
}
toast("请求截图权限成功")
sleep(5000);
function getImg(x1, y1, x2, y2, imgSavePath){
var screen = images.captureScreen();
var img = images.clip(screen, x1, y1, x2-x1, y2-y1);
img.saveTo(imgSavePath);
img.recycle();
}
// 保存路径的目录必须存在
getImg(0,0,100,500,"/sdcard/Download/test.png");
// 使用线程
// thread.start(function(){
// // 在新线程中执行的代码
// while(true){
// if(text("立即执行").findOne()){
// text("立即执行").findOne().click();
// }else{
// sleep(3000);
// }
// }
// })
images 中的 requestScreenCapture、captureScreen 这两个函数是可以当做全局函数来使用的。注意:captureScreen 返回的图片,不需要进行回收。。。
在图片中寻找颜色、以及 point 对象
示例代码:
// images.findColor(image, color, options)
// findColor(image, color, options) // 全局函数
// 循环找色示例
images.requestScreenCapture();
// 循环找色,找打红色 (#ff0000) 时停止,并返回坐标
while(true){
var img = images.captureScreen();
var point = images.findColor(img, "#ff0000");
if(point){
toast("找到 '红色',坐标为(" + point.x + "," + point.y + ")")
break
}
sleep(500);
}
//区域找色示例
//读取本地图片 /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("没有找到");
}
区域找色 --- 简单方法
findColorInRegion(img, color, x, y, width, height, threshold);
// 或者
images.findColorInRegion(img, color, x, y, width, height, threshold);
图片中寻找颜色完全相等的颜色点
示例:Auto.js
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红点的颜色来判断是否有未读消息)
示例代码:
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.
多点找色,类似于按键精灵的多点找色,其过程如下:
- 在图片img中找到颜色firstColor的位置(x0, y0)
- 对于数组colors的每个元素[x, y, color],检查图片img在位置(x + x0, y + y0)上的像素是否是颜色color,是的话返回(x0, y0),否则继续寻找firstColor的位置,重新执行第1步
- 整张图片都找不到时返回
null
例如,对于代码images.findMultiColors(img, "#123456", [[10, 20, "#ffffff"], [30, 40, "#000000"]])
,假设图片在(100, 200)的位置的颜色为#123456, 这时如果(110, 220)的位置的颜色为#fffff且(130, 240)的位置的颜色为#000000,则函数返回点(100, 200)。
如果要指定找色区域,则在options中指定,例如:
var p = images.findMultiColors(img, "#123456", [[10, 20, "#ffffff"], [30, 40, "#000000"]], {
region: [0, 960, 1080, 960]
});
示例代码:
/*
images.findMultiColors(img, firstColor, [color_1, color_2, ...], options);
示例:
images.findMultiColors(img, "#123456", [[10,20,"#ffffff"], [30,40,"#000000"]], options);
images.findMultiColors(img, "#123456", [[5,10,"#ffffff"], [15,20,"#123456"]], {threshold:0});
*/
if(!images.requestScreenCapture()){
toast("请求截图权限失败");
exit();
}
text("微信").waitFor();
sleep(1500);
var img = images.captureScreen();
sleep(500);
// 获取图片中某一点的颜色
var color_1 = img.pixel(100, 200);
log(colors.toString(color_1));
var color_point = images.findMultiColors(
img, "#0d0e22",
[[10,20, "#ffffff"], [30,40, "#000000"] ]
);
// 打印找到的颜色点的坐标
log(color_point);
图片中某个位置是否是特定颜色
images.detectsColor(image, color, x, y[, threshold = 16, algorithm = "diff"]):Auto.js
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)。
- "hs": hs欧拉距离匹配。hs为HSV空间的色调值。
返回图片image在位置(x, y)处是否匹配到颜色color。用于检测图片中某个位置是否是特定颜色。
一个判断微博客户端的某个微博是否被点赞过的例子:
if(!requestScreenCapture()){
toast("请求截图权限失败");
exit();
}
text("排行榜").waitFor();
sleep(1000);
//找到点赞控件。
// find() 不会阻塞操作,
// findOne() 会阻塞操作,直到找到一个在继续后续流程
var like = id("com.tencent.mm:id/bo_").find();
//获取该控件中点坐标
var x = like.bounds().centerX();
var y = like.bounds().centerY();
//截图
var img = captureScreen();
//判断在该坐标的颜色是否为橙红色
if(images.detectsColor(img, "#fed9a8", x, y)){
//是的话则已经是点赞过的了,不做任何动作
}else{
//否则点击点赞按钮
like.click();
}
5、应用 --- App
- app.versionCode
- app.versionName
- app.autojs.versionCode
- app.autojs.versionName
- app.launchApp(appName)
- app.launch(packageName)
- app.launchPackage(packageName)
- app.getPackageName(appName)
- app.getAppName(packageName)
- app.openAppSetting(packageName) :
打开应用的详情页(设置页)。如果找不到该应用,返回false; 否则返回true。该函数也可以作为全局函数使用。
- app.viewFile(path)
- app.editFile(path)
- app.uninstall(packageName)
- app.openUrl(url):用浏览器打开网站 url。如果没有安装浏览器应用,则抛出ActivityNotException。
- app.sendEmail(options):
options
{Object} 发送邮件的参数。包括:email
{string} | {Array} 收件人的邮件地址。如果有多个收件人,则用字符串数组表示cc
{string} | {Array} 抄送收件人的邮件地址。如果有多个抄送收件人,则用字符串数组表示bcc
{string} | {Array} 密送收件人的邮件地址。如果有多个密送收件人,则用字符串数组表示subject
{string} 邮件主题(标题)text
{string} 邮件正文attachment
{string} 附件的路径。
-
根据选项 options 调用邮箱应用发送邮件。这些选项均是可选的。如果没有安装邮箱应用,则抛出
ActivityNotException
。//发送邮件给10086@qq.com和10001@qq.com。 app.sendEmail({ email: ["10086@qq.com", "10001@qq.com"], subject: "这是一个邮件标题", text: "这是邮件正文" });
- app.startActivity(name):启动 Auto.js 的特定界面。该函数在 Auto.js 内运行则会打开 Auto.js内的界面,在打包应用中运行则会打开打包应用的相应界面。name {string} 活动名称,可选的值为:console 日志界面、settings 设置界面
示例代码:
// app.openAppSetting(app.getPackageName("微信"));
// sleep(1000)
// app.openAppSetting(app.getPackageName("QQ"));
// sleep(1000)
app.startActivity("console");
sleep(1000)
app.startActivity("settings")
//app.openUrl("https://www.baidu.com")
//sleep(1000)
6、进阶: 意图 Intent
- app.intent(options)
- app.startActivity(options)
- app.sendBroadcast(options)
- app.startService(options)
- app.sendBroadcast(name)
- app.intentToShell(options)
- app.parseUri(uri)
- app.getUriForFile(path)
7、悬浮窗 --- Floaty
floaty 模块提供了悬浮窗的相关函数,可以在屏幕上显示自定义悬浮窗,控制悬浮窗大小、位置等。悬浮窗在脚本停止运行时会自动关闭,因此,要保持悬浮窗不被关闭,可以用一个空的setInterval 来实现,例如:setInterval(()=>{}, 1000);
floaty.window(layout) --- 创建、关闭 悬浮窗
创建和关闭悬浮窗( ui 界面设置 ):Auto.js从入门到精通_哔哩哔哩_bilibili
layout
{xml} | {View} 悬浮窗界面的XML或者View
指定悬浮窗的布局,创建并显示一个悬浮窗,返回一个FloatyWindow
对象。
该悬浮窗自带关闭、调整大小、调整位置按键,可根据需要调用setAdjustEnabled()
函数来显示或隐藏。其中layout参数可以是xml布局或者一个View,更多信息参见ui模块的说明。例子:
var w = floaty.window(
<frame gravity="center">
<text id="text">悬浮文字</text>
</frame>
);
setTimeout(()=>{
w.close();
}, 2000);
这段代码运行后将会在屏幕上显示悬浮文字,并在两秒后消失。另外,因为脚本运行的线程不是UI线程,而所有对控件的修改操作需要在UI线程执行,此时需要用ui.run
,例如:
ui.run(function(){
w.text.setText("文本");
});
完整示例:
// frame 是一个重叠布局,如果再添加 一个 button,则两个会重叠到一块
// 更多布局,可以参看:https://hyb1996.github.io/AutoJs-Docs/#/ui?id=垂直布局-vertical
var w = floaty.window(
<frame gravity="center">
<text id="text">悬浮文字</text>
</frame>
);
// setTimeout(()=>{
// w.close();
// }, 2000);
setInterval(()=>{}, 1000); // 保持悬浮窗不被关闭
ui.run(function(){
w.text.setText("文本");
});
简单使用 "垂直布局" 示例:
更多布局,可以参看:https://hyb1996.github.io/AutoJs-Docs/#/ui?id=垂直布局-vertical
var xuanfu_window = floaty.window(
<vertical h="100dp">
<text id="text_1">悬浮文字 text_1</text>
<text id="text_2">悬浮文字 text_2</text>
<text layout_weight="1" text="控件1" bg="#ff0000"/>
<text layout_weight="1" text="控件2" bg="#00ff00"/>
<text layout_weight="1" text="控件3" bg="#0000ff"/>
<button>按钮_1</button>
</vertical>
);
setInterval(()=>{}, 1000);
ui.run(function(){
xuanfu_window.text_1.setText("text_1_text_1_text_1");
xuanfu_window.text_2.setText("text_2_text_2_text_2");
});
floaty.rawWindow(layout) --- 指定悬浮窗的布局
layout
{xml} | {View} 悬浮窗界面的XML或者View
指定悬浮窗的布局,创建并显示一个原始悬浮窗,返回一个FloatyRawWindow
对象。与floaty.window()
函数不同的是,该悬浮窗不会增加任何额外设施(例如调整大小、位置按钮),您可以根据自己需要编写任何布局。而且,该悬浮窗支持完全全屏,可以覆盖状态栏,因此可以做护眼模式之类的应用。 示例代码:
var xfc_1 = floaty.rawWindow(
<frame gravity="center">
<text id="text">悬浮文字</text>
</frame>
);
var xfc_2 = floaty.rawWindow(
<frame gravity="center">
<text id="text">悬浮文字</text>
</frame>
);
setInterval(()=>{}, 1000);
xfc_2.setPosition(500, 500);
floaty.closeAll() --- 关闭所有本脚本的悬浮窗。
悬浮窗对象 --- FloatyWindow
获取悬浮窗界面上的元素 --- FloatyWindow.{id}
悬浮窗对象,可通过 FloatyWindow.{id}
获取悬浮窗界面上的元素。例如, 悬浮窗 window 上一个控件的 id 为 aaa, 那么 window.aaa
即可获取到该控件,类似于 ui。
下面的 window 都是指 FloatyWindow 对象,而不是 FloatyRawWindow
window.setAdjustEnabled(enabled)
enabled
{boolean} 是否启用悬浮窗调整(大小、位置)。如果 enabled 为 true,则在悬浮窗左上角、右上角显示可供位置、大小调整的标示,就像控制台一样; 如果 enabled 为 false,则隐藏上述标示。
var xfc_1 = floaty.window(
<frame gravity="center">
<text id="text">悬浮文字</text>
</frame>
);
xfc_1.setAdjustEnabled(true);
// xfc_1.setSize(-1, -1); // -1 表示按最大值计算,占满整个屏幕
xfc_1.setSize(500, 500);
setInterval(()=>{}, 1000);
window.setPosition(x, y)
设置悬浮窗位置。
window.getX()
返回悬浮窗位置的X坐标。
window.getY()
返回悬浮窗位置的Y坐标。
window.setSize(width, height)
设置悬浮窗宽高。
window.getWidht()
返回悬浮窗宽度。
window.getHeight()
返回悬浮窗高度。
window.close()
关闭悬浮窗。如果悬浮窗已经是关闭状态,则此函数将不执行任何操作。
window.exitOnClose()
使悬浮窗被关闭时自动结束脚本运行。
原始 悬浮窗对象 --- FloatyRawWindow
原始悬浮窗对象,可通过window.{id}
获取悬浮窗界面上的元素。例如, 悬浮窗window上一个控件的id为aaa, 那么window.aaa
即可获取到该控件,类似于ui。
window.setTouchable(touchable)
touchable
{Boolean} 是否可触摸
设置悬浮窗是否可触摸,如果为true, 则悬浮窗将接收到触摸、点击等事件并且无法继续传递到悬浮窗下面;如果为false, 悬浮窗上的触摸、点击等事件将被直接传递到悬浮窗下面。处于安全考虑,被悬浮窗接收的触摸事情无法再继续传递到下层。
可以用此特性来制作护眼模式脚本,示例代码:
var w = floaty.rawWindow(
<frame gravity="center" bg="#44ffcc00"/>
);
w.setSize(-1, -1); // -1 表示按最大值计算,占满整个屏幕
w.setTouchable(false);
setTimeout(()=>{
w.close();
}, 4000);
8、脚本引擎
在脚本引擎中运行脚本
engines.execScript(name, script[, config])
name
{string} 要运行的脚本名称。这个名称和文件名称无关,只是在任务管理中显示的名称。script
{string} 要运行的脚本内容。config
{Object} 运行配置项delay
{number} 延迟执行的毫秒数,默认为0loopTimes
{number} 循环运行次数,默认为1。0为无限循环。interval
{number} 循环运行时两次运行之间的时间间隔,默认为0path
{Array} | {string} 指定脚本运行的目录。这些路径会用于require时寻找模块文件。
在新的脚本环境中运行脚本script。返回一个ScriptExectuion对象。
所谓新的脚本环境,指定是,脚本中的变量和原脚本的变量是不共享的,并且,脚本会在新的线程中运行。
最简单的例子如下:engines.execScript("hello world", "toast('hello world')");
如果要循环运行,则:
//每隔3秒运行一次脚本,循环10次
engines.execScript("hello world", "toast('hello world')", {
loopTimes: 10,
interval: 3000
});
用字符串来编写脚本非常不方便,可以结合 Function.toString()
的方法来执行特定函数:
function helloWorld(){
//注意,这里的变量和脚本主体的变量并不共享
toast("hello world");
}
engines.execScript("hello world", "helloWorld();\n" + helloWorld.toString());
如果要传递变量,则可以把这些封装成一个函数:
function exec(action, args){
args = args || {};
engines.execScript(action.name, action + "(" + JSON.stringify(args) + ");\n" + action.toString());
}
//要执行的函数,是一个简单的加法
function add(args){
toast(args.a + args.b);
}
//在新的脚本环境中执行 1 + 2
exec(add, {a: 1, b:2});
综合示例代码:
// toast("脚本引擎示例")
// // 示例 1
// engines.execScript("js_engines_test", 'toast("示例 1");home();', {
// delay: 2000,
// loopTimes: 3,
// interval: 2000
// });
// // 示例 2
// function func_test(){
// toast("示例 2");
// }
// engines.execScript("js_test", func_test.toString() + "func_test();")
// 示例 3
function func_aaa(args){
toast(args.a + args.b);
}
function func_exec(func, args){
console.log(func.toString());
console.log(args.toString());
args = args || {};
var func_to_string = func.toString();
var re_string = /function\s*(\w*)/i;
var result_list = re_string.exec(func_to_string);
var func_name = result_list[1];
engines.execScript(func_name, func.toString()+func_name+"("+JSON.stringify(args)+");")
}
func_exec(func_aaa, {a:10, b:20});
在脚本引擎中运行 js 文件
engines.execScriptFile(path[, config])
path
{string} 要运行的脚本路径。config
{Object} 运行配置项delay
{number} 延迟执行的毫秒数,默认为0loopTimes
{number} 循环运行次数,默认为1。0为无限循环。interval
{number} 循环运行时两次运行之间的时间间隔,默认为0path
{Array} | {string} 指定脚本运行的目录。这些路径会用于require时寻找模块文件。
在新的脚本环境中运行脚本文件path。返回一个ScriptExecution对象。
engines.execScriptFile("/sdcard/脚本/1.js");
在脚本引擎中运行录制的脚本文件
fasengines.execAutoFile(path[, config])
path
{string} 要运行的录制文件路径。config
{Object} 运行配置项delay
{number} 延迟执行的毫秒数,默认为0loopTimes
{number} 循环运行次数,默认为1。0为无限循环。interval
{number} 循环运行时两次运行之间的时间间隔,默认为0path
{Array} | {string} 指定脚本运行的目录。这些路径会用于require时寻找模块文件。
在新的脚本环境中运行录制文件path。返回一个ScriptExecution对象。
engines.execAutoFile("/sdcard/脚本/1.auto");
脚本引擎的控制方法
engines.stopAll()
停止所有正在运行的脚本。包括当前脚本自身。
engines.stopAllAndToast()
停止所有正在运行的脚本并显示停止的脚本数量。包括当前脚本自身。
engines.execScript("test_test", "sleep(10000);");
sleep(3000);
engines.stopAllAndToast();
engines.myEngine()
返回当前脚本的脚本引擎对象(ScriptEngine)
[v4.1.0新增] 特别的,该对象可以通过execArgv
来获取他的运行参数,包括外部参数、intent等。例如:
log(engines.myEngine().execArgv);
普通脚本的运行参数通常为空,通过定时任务的广播启动的则可以获取到启动的intent。
engines.all()
- 返回 {Array}
返回当前所有正在运行的脚本的脚本引擎ScriptEngine的数组。
log(engines.all());
脚本执行对象 --- ScriptExecution
执行字符串 js 或者 文件 js 时,返回的对象,就是 ScriptExecution 对象
脚本执行对象:Auto.js
执行脚本时返回的对象,可以通过他获取执行的引擎、配置等,也可以停止这个执行。
要停止这个脚本的执行,使用exectuion.getEngine().forceStop()
.
ScriptExecution.getEngine()
返回执行该脚本的脚本引擎对象(ScriptEngine)
ScriptExecution.getConfig()
返回该脚本的运行配置(ScriptConfig)
脚本引擎对象 -- ScriptEngine
脚本执行的过程中,会返回一个 js 引擎,即 js 的执行环境,就是 脚本引擎对象
脚本引擎对象。
ScriptEngine.forceStop()
停止脚本引擎的执行。
ScriptEngine.cwd()
- 返回 {string}
返回脚本执行的路径。对于一个脚本文件而言为这个脚本所在的文件夹;对于其他脚本,例如字符串脚本,则为null
或者执行时的设置值。
ScriptEngine.getSource()
- 返回 ScriptSource
返回当前脚本引擎正在执行的脚本对象。
log(engines.myEngine().getSource());
示例代码:
var script_execution_obj = engines.execScriptFile("/sdcard/脚本/demo.js");
sleep(1000);
var script_engine = script_execution_obj.getEngine()
log("脚本引擎对象 ---> " + script_engine);
var script_config = script_execution_obj.getConfig()
log("脚本运行配置 ---> " + script_config);
log("执行路径 ---> " + script_engine.cwd());
log("脚本源码 ---> " + script_engine.getSource());
script_engine.forceStop()
基本引擎之间的通信
ScriptEngine.emit(eventName[, ...args])
eventName
{string} 事件名称...args
{any} 事件参数
向该脚本引擎发送一个事件,该事件可以在该脚本引擎对应的脚本的events模块监听到并在脚本主线程执行事件处理。
例如脚本receiver.js的内容如下:
//监听say事件
events.on("say", function(words){
toastLog(words);
});
//保持脚本运行
setInterval(()=>{}, 1000);
同一目录另一脚本可以启动他并发送该事件:
//运行脚本
var e = engines.execScriptFile("./receiver.js");
//等待脚本启动
sleep(2000);
//向该脚本发送事件
e.getEngine().emit("say", "你好");
脚本配置
脚本执行时的配置。
delay
- {number}
延迟执行的毫秒数
interval
- {number}
循环运行时两次运行之间的时间间隔
loopTimes
- {number}
循环运行次数
getPath()
- 返回 {Array}
返回一个字符串数组表示脚本运行时模块寻找的路径。
9、用户界面 --- UI
From:https://www.bilibili.com/video/BV1pQ4y1R7Us?p=61
Auto.js教程(第十四期)-界面UI教程(全网最全最详细):https://www.bilibili.com/video/BV1cy4y117iY
界面设计类似 HTML
ui 模块提供了编写用户界面的支持。带有 ui 的脚本的的最前面必须使用 "ui";
指定 ui 模式,否则脚本将不会以 ui 模式运行。正确示范:
"ui";
//脚本的其他代码
字符串 "ui" 的前面可以有注释、空行和空格[v4.1.0新增],但是不能有其他代码。
视图(View) --- ( 控件、布局 )
界面 是由 视图(View) 组成的。View 分成两种:
- 控件(Widget)。控件 (Widget) 用来具体显示文字、图片、网页等,比如文本控件(text)用来显示文字,按钮控件(button)则可以显示一个按钮并提供点击效果,图片控件(img)则用来显示来自网络或者文件的图片,除此之外还有输入框控件(input)、进度条控件(progressbar)、单选复选框控件(checkbox)等;
- 布局(Layout)。布局 (Layout) 则是装着一个或多个控件的 "容器",用于控制在他里面的控件的位置,比如 垂直布局(vertical) 会把他里面的控件从上往下依次显示(即纵向排列),水平布局(horizontal) 则会把他里面的控件从左往右依次显示(即横向排列),以及 帧布局(frame),他会把他里面的控件直接在左上角显示,如果有多个控件,后面的控件会重叠在前面的控件上。
我们使用 xml 来编写界面,并通过 ui.layout()
函数指定界面的布局 xml。举个例子:
"ui";
ui.layout(
<vertical>
<button text="第一个按钮"/>
<button text="第二个按钮"/>
</vertical>
);
垂直布局(vertical) 改成 水平布局(horizontal)
"ui";
ui.layout(
<horizontal>
<button text="第一个按钮"/>
<button text="第二个按钮"/>
</horizontal>
);
一个控件可以指定多个属性(甚至可以不指定任何属性),用空格隔开即可;布局同样也可以指定属性,例如:
"ui";
ui.layout(
<vertical bg="#ff0000">
<button text="第一个按钮" textSize="20sp"/>
<button text="第二个按钮"/>
</vertical>
);
第三行bg="#ff0000"
指定了垂直布局的背景色(bg)为"#ff0000",这是一个RGB颜色,表示红色(有关RGB的相关知识参见RGB颜色对照表)。第四行的textSize="20sp"
则指定了按钮控件的字体大小(textSize)为"20sp",sp是一个字体单位,暂时不用深入理会。
一个界面便由一些布局和控件组成。
- 子视图, 子控件: 布局里面的控件是这个布局的子控件/子视图。实际上布局里面不仅仅只能有控件,还可以是嵌套的布局。因此用子视图(Child View)更准确一些。在上面的例子中,按钮便是垂直布局的子控件。
- 父视图,父布局:直接包含一个控件的布局是这个控件的父布局/父视图(Parent View)。在上面的例子中,垂直布局便是按钮的父布局。
更多 view 的属性:https://hyb1996.github.io/AutoJs-Docs/#/ui?id=视图-view
Autojs 示例代码
视频教程:Auto.js从入门到精通_哔哩哔哩_bilibili
如图所示:
-
文本控件
-
输入框控件
-
按钮控件
-
图片控件
-
表格控件
-
单选框控件、复选框控件
-
进度条控件
-
卡片布局
-
列表控件
-
时间日期控件
-
下拉菜单控件
一般全局函数
全局变量和函数在所有模块中均可使用。 但以下变量的作用域只在模块内,详见 module:
- exports
- module
- require() 以下的对象是特定于 Auto.js 的。 有些内置对象是 JavaScript 语言本身的一部分,它们也是全局的。
一些模块中的函数为了使用方便也可以直接全局使用,这些函数在此不再赘述。例如timers模块的setInterval, setTimeout等函数。
- sleep(n)
- currentPackage()
- currentActivity()
- setClip(text)
- getClip()
- toast(message)
- toastLog(message)
- waitForActivity(activity[, period = 200])
- waitForPackage(package[, period = 200])
- exit()
- random(min, max)
- random()
- requiresApi(api)
- requiresAutojsVersion(version)
- runtime.requestPermissions(permissions)
- runtime.loadJar(path)
- runtime.loadDex(path)
- context
示例代码:
var _toast = toast;
toast = function(msg){
_toast(msg);
sleep(2000);
}
toast("123");
toast("456");
toast("789");
// text("微信").waitFor();
// sleep(1000);
// 没有出现等待的页面时,则一直等待
// waitForActivity("com.tencent.mm.plugin.sns.ui.SnsTimeLineUI");
// toast("已经到达微信界面");
waitForPackage("com.tencent.mm");
toast("已经到了某个应用");
var 控件集合 = id("com.miui.home:id/icon_icon").findOne();
var 控件数量 = 控件数量.size();
var r = random(0, 控件数量-1);
var 控件坐标 = 控件集合.get(r).bounds();
click(控件坐标.centerX(), 控件坐标.centerY())
控制台
控制台出现的悬浮窗,进行模拟点击等操作是无法进行穿透操作后面的内容的。。。
控制台模块提供了一个和 Web 浏览器中相似的用于调试的控制台。用于输出一些调试信息、中间结果等。 console 模块中的一些函数也可以直接作为全局函数使用,例如 log, print 等。
显示、隐藏 控制台
- console.show():显示控制台。这会显示一个控制台的悬浮窗(需要悬浮窗权限)。
- console.hide():隐藏控制台悬浮窗。
控制台信息 清理、打印
console.clear()
清空控制台。
console.log([data][, ...args])
data
{any}...args
{any}
打印到控制台,并带上换行符。 可以传入多个参数,第一个参数作为主要信息,其他参数作为类似于 printf(3) 中的代替值(参数都会传给 util.format())。
const count = 5;
console.log('count: %d', count);
// 打印: count: 5 到 stdout
console.log('count:', count);
// 打印: count: 5 到 stdout
详见 util.format()。该函数也可以作为全局函数使用。
print(text)
- text {string} | {Object} 要打印到控制台的信息
相当于log(text)
。
console.trace([data][, ...args])
[v4.1.0新增]
data
{any}...args
{any}
与 console.log 类似,同时会打印出调用这个函数所在的调用栈信息(即当前运行的文件、行数等信息)。
console.trace('Show me');
// 打印: (堆栈跟踪会根据被调用的跟踪的位置而变化)
// Show me
// at <test>:7
控制台信息输出样式
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并停止脚本运行。
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
。
console.time('求和');
var sum = 0;
for(let i = 0; i < 100000; i++){
sum += i;
}
console.timeEnd('求和');
// 打印 求和: xxx ms
控制台输入
console.input(data[, ...args])
data
{any}...args
{any}
与console.log一样输出信息,并在控制台显示输入框等待输入。按控制台的确认按钮后会将输入的字符串用eval计算后返回。
部分机型可能会有控制台不显示输入框的情况,属于bug。
例如:
var n = console.input("请输入一个数字:");
//输入123之后:
toast(n + 1);
//显示124
console.rawInput(data[, ...args])
data
{any}...args
{any}
与console.log一样输出信息,并在控制台显示输入框等待输入。按控制台的确认按钮后会将输入的字符串直接返回。
部分机型可能会有控制台不显示输入框的情况,属于bug。
例如:
var n = console.rawInput("请输入一个数字:");
//输入123之后:
toast(n + 1);
//显示1231
控制台大小、位置、配置的设置
console.setSize(w, h)
w
{number} 宽度h
{number} 高度
设置控制台的大小,单位像素。
console.show();
sleep(2000);
console.setSize(device.width/2, device.height/2)
console.setPosition(x, y)
x
{number} 横坐标y
{number} 纵坐标
设置控制台的位置,单位像素。
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} 日志备份文件最大数量,默认为5filePattern
{string} 日志写入格式,参见PatternLayout
设置日志保存的路径和配置。例如把日志保存到"/sdcard/1.txt":
console.setGlobalLogConfig({
"file": "/sdcard/1.txt"
});
注意该函数会影响所有脚本的日志记录。
安卓7.0 以上的 触摸 和 手势模拟
:https://pro.autojs.org/docs/#/zh-cn/coordinatesBasedAutomation?id=安卓70以上的触摸和手势模拟
免 root 手机 "模拟点击" 的三种方式
1. click(x, y)
x
{number} 要点击的坐标的x值y
{number} 要点击的坐标的y值
模拟点击坐标(x, y),并返回是否点击成功。只有在点击执行完成后脚本才继续执行。
一般而言,只有点击过程 ( 大约 150ms ) 中被其他事件中断 ( 例如 用户自行点击 ) 才会点击失败。
使用该函数模拟连续点击时,可能有点击速度过慢的问题,这时可以用 press() 函数代替
2. longClick(x, y)
x
{number} 要长按的坐标的x值y
{number} 要长按的坐标的y值
模拟长按坐标(x, y), 并返回是否成功。只有在长按执行完成(大约600毫秒)时脚本才会继续执行。一般而言,只有长按过程中被其他事件中断(例如用户自行点击)才会长按失败。
3. press(x, y, duration)
x
{number} 要按住的坐标的x值y
{number} 要按住的坐标的y值duration
{number} 按住时长,单位毫秒
模拟按住坐标(x, y), 并返回是否成功。只有按住操作执行完成时脚本才会继续执行。
如果按住时间过短,那么会被系统认为是点击;如果时长超过500毫秒,则认为是长按。
一般而言,只有按住过程中被其他事件中断才会操作失败。
一个连点器的例子如下:
//循环100次
for(var i = 0; i < 100; i++){
//点击位置(500, 1000), 每次用时1毫秒
press(500, 1000, 1);
}
免 root 手机 "模拟滑动" 的三种方式
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。
例如手指捏合:
gestures(
[0, 500, [100, 500], [100, 1000]],
[0, 500, [300, 500], [300, 1000]],
[0, 500, [500, 500], [500, 1000]]
);
使用 root 权限 点击 和 滑动
RootAutomator 是一个使用 root 权限来模拟触摸的对象,用它可以完成触摸与多点触摸,并且这些动作的执行没有延迟。一个脚本中最好只存在一个RootAutomator,并且保证脚本结束退出他。可以在exit事件中退出RootAutomator,例如:
var ra = new RootAutomator();
events.on('exit', function(){
ra.exit();
});
//执行一些点击操作
...
注意:以下命令需要root权限
RootAutomator
RootAutomator是一个使用root权限来模拟触摸的对象,用它可以完成触摸与多点触摸,并且这些动作的执行没有延迟。
一个脚本中最好只存在一个RootAutomator,并且保证脚本结束退出他。可以在exit事件中退出RootAutomator,例如:
var ra = new RootAutomator();
events.on('exit', function(){
ra.exit();
});
//执行一些点击操作
...
注意以下命令需要root权限
RootAutomator.tap(x, y[, id])
x
{number} 横坐标y
{number} 纵坐标id
{number} 多点触摸id,可选,默认为1,可以通过setDefaultId指定。
点击位置(x, y)。其中id是一个整数值,用于区分多点触摸,不同的id表示不同的"手指",例如:
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} 滑动时长,单位毫秒,默认值为300id
{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
模拟手指弹起。
下面的函数在后续版本很可能有改动!请勿过分依赖。推荐使用 RootAutomator
代替本章节的触摸函数。
以下函数均需要root权限,可以实现任意位置的点击、滑动等。
- 这些函数通常首字母大写以表示其特殊的权限。
- 这些函数均不返回任何值。
- 并且,这些函数的执行是异步的、非阻塞的,在不同机型上所用的时间不同。脚本不会等待动作执行完成才继续执行。因此最好在每个函数之后加上适当的sleep来达到期望的效果。
例如:
Tap(100, 100);
sleep(500);
注意,动作的执行可能无法被停止,例如:
for(var i = 0; i < 100; i++){
Tap(100, 100);
}
这段代码执行后可能会出现在任务管理中停止脚本后点击仍然继续的情况。 因此,强烈建议在每个动作后加上延时:
for(var i = 0; i < 100; i++){
Tap(100, 100);
sleep(500);
}
Tap(x, y)
- x, y {number} 要点击的坐标。
点击位置(x, y), 您可以通过"开发者选项"开启指针位置来确定点击坐标。
Swipe(x1, y1, x2, y2, [duration])
- x1, y1 {number} 滑动起点的坐标
- x2, y2 {number} 滑动终点的坐标
- duration {number} 滑动动作所用的时间
滑动。从(x1, y1)位置滑动到(x2, y2)位置。