用JS任意控制手机上的APP


目录

用JS任意控制手机上的APP#

1. 控制代码#

  • 可以用如下简单的JS代码,控制手机上的第三方APP的行为,实现自动测试等目的 *
launchApp("微信"); 
click("发现");
click("朋友圈");
sleep(3000);
click(desc("评论"));

本项目完全兼容 uni-app + autoJs#

本项目完全兼容 uni-app + autoJs#

声明#

⚠️ 警告:本项目仅供大家学习交流,请勿用于非法目的 ⚠️
⚠️ 警告:本项目之衍生产品,其用途均与本项目作者无关 ⚠️

QQ群: 714554851#

  如果喜欢这个项目,可以请赠我一包华子 

2.开发环境#

2.1 下载代码 https://github.com/yooge/robot#

2.2 下载NodeJs#

2.3 下载安装开发工具HbuilderX#

建议下载这个版本:

链接: https://pan.baidu.com/s/1W0IEokddywK5iqoIx7biKw?pwd=1234 
提取码: 1234

3.初始化环境#

3.0 用Hbuilder打开项目 (菜单/文件/打开目录)#

点击如图菜单可完成如下2步操作#

3.1 更新支持库(依赖包),点击如下菜单,会执行 npm i#

3.2 更新运行基座,点击如下菜单,会执行 robot-tools init#

4.运行程序#

5.代码结构#

QQ群: 714554851#

  如果喜欢这个项目,可以请赠我一包华子 

vue与控制脚本的交互#

参数解释#

在VUE页面,立即执行控制脚本代码(不建议)

//vue代码
var {autojs} = require('robot-tools');

//【不建议】立即执行控制脚本(无需js文件或执行robotjs脚本,可立即执行本代码)
autojs.exec(function(){ //【不建议】
    //脚本代码:
    console.log('开始振动 ');
    console.show();
    device.vibrate(2000);
    launchApp('抖音'); 
});
//偶尔用一下,不要太依赖
//todo:
//1. 作用域在控制脚本内,此处暂不支持对VUE的访问,下一版补上;(如果需要访问VUE,看下面的start方法)
//2. 如果需要用require方法引用其他的js文件,请先用autojs.robot.setJsDir("/sdcard/")设置工作路径;
//   或者用autojs.init({file:'demo.js'})设置个假的文件,工作路径参考下文

执行控制脚本文件#

//vue代码
var {autojs} = require('robot-tools');
var param = { 
    file: 'demo.js', //[必选],脚本(static/robots/demo.js),或绝对路径/sdcard/xxx.js,或远程URL(也可以用发布的打包加密代码)
    vue:  this, //可选, 将本vue对象传递给脚本
    arguments: {}, //可选, json,传递给脚本的参数。[提示]如果不传递,则系统会默认使用'当时'的vue的data数据;
    onMessage: (data)=>{}, //可选,回调函数,脚本给VUE发送消息, 感觉快淘汰了
    start: ()=>{}, //可选,脚本启动事件
    finish: (obj)=>{},//可选,脚本执行完毕事件
    fail: (msg)=>{},//可选,脚本发生意外事件
}
/*

file:  
//1. 脚本(static/robots/目录下的demo.js),
//2. 或绝对路径/sdcard/xxx.js,
//3. 或远程URL(也可以用发布的打包加密代码)

arguments: 
//可选, json,传递给脚本的参数。
//可以在控制脚本中用app.args获取
//如果不传递参数,则系统会默认传递'当时'的vue的$data数据;
//如果控制脚本想动态获取vue的$data的数据,请往下看
*/

//启动脚本
autojs.start(param); 

//停止脚本
autojs.stop();

//仅设置初始化参数,不执行代码
autojs.init(param); 
//根据上面设置好的参数,执行代码; 也可以在悬浮脚本上点启动按钮
autojs.start(); 

autojs.menu.show(); //显示悬浮脚本图标 
autojs.menu.move(x,y); //移动悬浮脚本图标
autojs.menu.close(); //隐藏悬浮脚本图标
autojs.showMenu(param); //执行脚本并显示脚本图标
autojs.closeMenu(); //隐藏图标


var isEnabled = autojs.permission();//检查是否启动了无障碍

立即执行(当前实例中)#

//在当前已经运行的脚本中,立即执行小段脚本(需要在robotjs脚本启动后,再调用该方法)
autojs.eval(function(){ 
    //在vue的代码文件里,直接执行脚本代码,
   launchApp('抖音');
   sleep(4000);
   console.log(text('我')); 
});

立即执行(新脚本)#

//立即执行控制脚本(代码可以是字符串)
autojs.exec(function(){
   console.log(device.getIMEI());
   launchApp('抖音'); 
 }
);
autojs.exec(
 `
   console.log(device.getIMEI());
   launchApp('抖音'); 
 `
);

robotjs脚本获取VUE发过来的参数(启动脚本时传递的)#

//[autojs代码]
app.args //json对象, 
app.arguments  //参数字符串

//内置参数
app.args.scriptStartFrom //null 或 'menu'; 可以通过不同的启动方式,执行不同的代码
app.args._debug //调试模式,还是正式模式

robotjs脚本给VUE层发消息#

//[autojs代码]
app.post2host("hello vue"); 
//robotjs脚本用这个方法给VUE层发消息,(在上面的onMessage中接收消息)

robotjs脚本直接访问VUE页面对象#

//[autojs代码]
app.vue  //控制脚本直接访问vue的对象,上面传递进来的对象this(或别的对象)
app.vue.abc   //访问data里的abc变量
app.vue.abc = 999; //给data里面的abc赋值
app.vue.test() //访问VUE的methods里面的test函数。 此用法可以淘汰上面的onMessage回调

例index.vue#

<template>
<view> 
    {{abc}}
</view>
</template>
<script> 
var {autojs} = require('robot-tools');
export default {
    data: {
        abc: 123
    }
    methods: {
        test() {
            console.log('autojs called test.');
        }
    }
}
</script>

常用控制脚本API#

完整的API文档查看#

1. 启动app#

launchApp("浏览器");

1. 点击#

click全局函数[推荐]#

无论元素是否有clickable属性,都会强制点击到对应的坐标上#

click("按钮1");
click("赞", 0);  //点击第0个赞

点击文字为“按钮1”的界面元素(按钮,文字等)

click("*入购物车");  //点击包含这个文字的元素

click(desc('购物车').findOne());    
click(desc('购物车'),  0);    

点击文字包含"入购物车"的界面元素(按钮,文字等)

click(300,500); //点击坐标(300,500)


click('购物车', 10,10);  //右下角偏移 10,10
click('购物车', -10,10); //左下角
click('购物车', 10,-10); //右上角
click('购物车', -10,-10); //左上角 
click(desc('购物车'));     //新
click(desc('购物车'), 2);     //新
click(desc('购物车').findOne());     //
click(desc('购物车').findOne(), -10,-10);     //购物车的左上角 10,10点击一下 ,以购物车的左上角为参考
click(desc('购物车').findOne(), 10,10);     //购物车的右下角 10,10点击一下 ,以购物车的 右下角为参考
click(desc('购物车'), 10,10);
text('购物车').findOne().click(); //点击购物车,此方法点击的是元素本身(如果元素只是普通文本,则会失效)
click(text('购物车').findOne()); //(推荐)点击购物车,此方法点击的是屏幕,如果自己不可点击,会把事件传递给父控件

点击进入页面,然后再回来#

  • 适合多页面跳来跳去的交互场景 *

click(desc('购物车'), function(){
    ;;//进去页面了,干点啥
    //处理完毕后,会回到原页面
    //如图期待的页面没出现 请用 return 'click_fail'; 
});

//点击粉丝列表
click('粉丝', () => { 
        //打开了粉丝列表页面
        var pp = desc('粉丝列表').findOne();
        var fanslist = listChildren(pp);
        //在粉丝列表页面上进行一系列操作
        dosomthing(fanslist);
    });

2. 输入#

向本界面中的输入框输入文字

setText("你好");
setText(0, "你好"); //第几个输入框

3. 查找控件#

//1. 查找界面上是否有文字为“消息”的控件,返回数组
text("消息"); 
text("消息").find(); 
//2. 界面上是否有控件包含“入购物车”这几个字,返回数组
text("*入购物车").find(); 
desc("*个赞")

//3. 查找并返回一个
text("消息").findOne(); 
//4. 查找并返回一个,超时5秒
text("消息").findOne(5000);
// 是否存在
text("消息").exists();

//5. 等待界面出现“”这个字的控件,等5秒(默认20秒)
text("消息").waitFor(5000); 
waitFor("消息");
waitFor("消息", 5000);

//6. 等待这个文字消失, 默认超时20秒
waitGone("消息");
waitGone("消息", 5000);
//7. 
textEx  //是text的临时补充版
textEx("#") //查找输入框
textEx("#139101") //查找输入框,里面内容为139101
textEx("#*请输入") //查找输入框,里面内容 包含 请输入

//8.
desc("赞").findOne(); //控件的Content-Description属性
descContains("评论数").findOne(); 

//9
className('RecyclerView');//控件的类
classNameContains('RecyclerView');//类


//10.
//遍历过滤器
var uc = className("TextView").filter(function(w){
    return w.text().length == 10;
});

//11.
//对象在屏幕内,比如查找抖音的点赞按钮时,会有两个在屏幕外。就需要这个函数来锁定
inScreen(desc('赞'));
inScreenOne(desc('赞'));
//或者 用扩展搜索
desc('赞').visibleToUser(true);  //控件是否可见。

//10 其他扩展搜索条件:
/*
cls('classname')
bounds(0,0, 600, 900) 控件在屏幕上的范围。
indexInParent(2) 控件在父控件的位置。
editable(true) 控件是否可编辑。
visibleToUser(true) 控件是否可见。
drawingOrder(2) 控件在父控件的绘制顺序。
clickable(true) 控件是否可点击。
longClickable(true) 控件是否可长按。
checkable(true) 控件是否可勾选。
checked(true) 控件是否可已勾选。
scrollable(true) 控件是否可滑动。
selected(true) 控件是否已选择。
enabled(true)
*/

3.0 控件的[属性]#

** 控件有很多属性,请打印一下获取的对象看看

3.1 控件的[方法]#

var box = desc('body').findOne();
box.click(); //点击控件
box.longClick();
box.text();  //获取控件的文本
box.setText('xxx'); //设置文本
box.bounds();  //获取控件的坐标 top, left, right, bottom
box.paste();; //粘贴 , setClip("你好");
box.children(); //子元素的数组
listChildren(box); //新增,同上, 增加了 textAll属性,所有的子元素的文本
box.child(i); //第3个子元素
box.parent(); //父控件
box.findOne(desc("赞")); //查找子元素
box.find(desc("赞")).get(1); //查找子元素,获取第2个
box.find(desc("赞")).empty(); //是否为空

3.2 页面是否匹配某些内容#

根据页面是否匹配某些内容,来判断页面功能

//函数
matchContents(matchkeys);

//查找当前页面是否同时包含 这4个项(主界面)
matchContents("消息 & 通讯录 & 工作台 & *待办");
//查找当前页面是否有如下情况(搜索手机号页面)
// A. 手机号 与 输入框  B. 出现文字‘搜索’ 与输入框
matchContents("手机号 & # | *搜索 & #");
//添加好友页面
matchContents("*好友中添加 & 已发邀请 | *好友中添加 & 添加");

3.3 复杂的查找/等待分支#

在流程中,可能出现任意个不同界面的时候,需要进行分支切换

//等待出现两个控件中的一个, 返回 0或1,失败返回-1;超时5秒(默认20秒)
//这个功能 主要用在一个动作后,可能出现两个窗口中的一个。
var i = either("*加为好友", "用户不存在");
var i = either("*加为好友", "用户不存在", 5000);

//1. 等待出现多个控件中的一个, 返回 0,1,2,3,失败返回-1; 超时5秒(默认20秒)
//这个功能 主要用在比如一个点击动作后,可能出现多个窗口中的一个。
var i = appear(["*加为好友", "用户不存在", "*无权限"], 5000);

//2. 或者,返回key, 失败则返回-1
var key = appear({"ok":"*加为好友", "unexist":"用户不存在", "denial": "*无权限"}, 5000);

3.3.4 逆向搜索#

//逆向搜索,从我开始,向其他节点搜索

var mynode = text('回复').findOne();
var targetSelector = className('androidx.recyclerview.widget.RecyclerView')
var obj = lookup(mynode, targetSelector);

3.3.5 图片查找#

//查找尺寸的图片
var ckRect= {w1:100}  //宽度大于100的图片
var ckRect = {w2:200} //宽度小于200的图片
var ckRect = {w1:100, w2:200} //宽度在100-200之间的图片
var ckRect = {h1:100, h2:500} //高度在100-500之间的图片
//或者数组 ckRect = [w1,w2,h1,h2] 

//查找所有符合尺寸的图片
var images = findPictures(parentObj, ckRect);

//查找尺寸的图片,取第一个
var img = findPicture(parentObj, ckRect);

//查找大控件
bigSize(classname);//d大于半个屏幕大小的类
bigSize(classname, 500);

3.3.6 按尺寸查找#

findsize(list, chRect)  
//list控件数组, ckRect参考上面找图的参数

//过滤器
var uc = className("ImageView").filter(function(w){
    var b = w.bounds();
    return b.width() > 10; //宽度大于10的图片
});

3.3.6 其他#

//寻祖
parent(obj, 2) //祖父 
parent(obj, 3) //祖父的父亲

//查找本控件及所有子控件,并返回文本及控件
loopElementsText(obj, false);
loopElementsText(obj, true); //只返回文本
alltext(obj) //同上,只返回文本(压在一起了)

listChildren(obj)//返回数组,枚举每个子控件(及内部所有文本)

4. 窗口操作(设计的不好)#

  1. 启动app
    launchApp("浏览器");
    
  2. 获取当前的包名 //如 com.tencent.mm

    currentPackage();
    
  3. 等待窗口出现

    waitForActivity("*.launch.WwMainActivity", 4000);
    //建议多使用,等待内容出现的函数 waitFor
    
  4. (从深层堆栈中回退)一直点击返回键,让页面一直返回,直至遇到某窗口(# 有点费劲, 推荐使用click的回调功能)
    //参数:窗口类名/或数组,包名,超时时间,忽略其他窗口的存在;
    //返回: 该类名(有点复杂,可以放弃,请参考4.2)
    back2activity("*.launch.WwMainActivity");
    back2activity("*.launch.WwMainActivity","com.tencent.wework");
    back2activity("*.launch.WwMainActivity","com.tencent.wework", 12000);
    back2activity("*.launch.WwMainActivity","com.tencent.wework", 12000, ["*.xxActivity","*.yyyActivity"]);
    //也可以是数组,返回对应的类名
    back2activity(["*.launch.WwMainActivity", "*.LaunchSplashActivity"]);
    
    4.2 从深层堆栈中回退) 一直点击返回键,让页面一直返回,直至遇到某窗口 `js //自动点击返回键,回调页面包含xx内容的窗口 //matchList可以为字符串,或数组, 回到包含该内容的页面,参考 matchContents函数 back2activityEx(matchList, timeout)

back2activityEx("消息 & 通讯录", 5000); back2activityEx(["消息 & 通讯录", "已发邀请 | 添加"], 5000);


5. 等待窗口消失
```js
activityWaitGone("*.launch.WwMainActivity", 5000);

推荐使用waitGone('文字')

5. 按键#

home(); //桌面
back(); //返回
recents(); //任务列表
notifications(); //拉出通知栏

6. 其他#

sleep(1000);//暂停1秒
//
console.log("xxxxx"); //会同时打印到电脑,手机
console.show();  //打开悬浮日志窗口(日志同时会往这里输出)
//---
//用新的控制台
console.useNew(); //使用新的模式!!(使下面的这些功能),老控制台则用这个访问console_old
console.useNew('patchs/log_layout.xml'); //指定layout
console.log2ide("xxxx"); //仅仅显示在电脑里
console.log2app("xxxx");  //仅仅显示在手机上
console.title('修改标题内容');
console.toast('修改标题内容');
toast('修改标题内容');

console.msg('覆盖日志区域内容');//内容可使用HTML,可作为自定义统计页
console.log('日志');
console.log('<b>日志</b>'); //内容可使用HTML
console.log('<font color=red>红色文字</font>'); //内容可使用HTML
console.info('蓝色提示');
console.warn('黄色警告');
console.verbose('低级日志');
console.error('红色警告');

console.error.color = '#DD0099';//设置颜色
console.error.color = 'red';//设置颜色
console.info.color = 'blue';
console.warn.color = 'yellow';
console.log.color = 'white';
console.verbose.color = 'gray';

console.resize(400, 400);  //设置日志浮窗 宽度,高度
console.resize(-1, 400);  //宽度为全屏
console.setPosition(0, 502);  //设置位置

console.window //浮窗对象 

//可以控制layout中的节点,比如:
console.window.resize(400, 400)
console.window.title.setText('hello') //设置文字
console.window.myid.setTextSize(20) //设置字体大小(请在自定义layout中加入这个myid节点)

console.setText(console.window.body, '<b>粗体文字</b>'); //添加html文字
console.addText(console.window.myid, '<font color=red>红色文字</font>'); //添加html文字

//设置日志本地存储
console.logfile ; 
console.setLogfile('/sdcard/1.txt');
console.removeLogfile();//删除log内容
console.getLog();//获取log内容

7. 手指操作#

//滑动
swipeUp() //上滑
swipeUp(800)  //800px

swipeTo('张三') //滑到某个内容
swipeTo(desc('张三')) 
swipeTo({key:'张三'}) //滑到某个内容
swipeTo({key,timeout,length,to}) //滑到某个内容

//
scrollTo('添加');
scrollUp(1)
scrollDown(0)
scrollForward()
scrollLeft()
scrollRight()

longClick(text[, i])
click(text[, i])


swipe(200, 1500, 200, 400, 500); //500毫秒
swipe(x1, y1, x2, y2, 500); //500毫秒
//连续滑动
gesture(1000, [0, 0], [500, 500], [500, 1000])
//多指连续滑动
gestures([delay1, duration1, [x1, y1], [x2, y2], ...], [delay2, duration2, [x3, y3], [x4, y4], ...], ...)

gestures([0, 500, [800, 300], [500, 1000]],
         [0, 500, [300, 1500], [500, 1000]]);

8. 截图, 找图 (原API,没有新增,老手请请忽略)#


//请求
requestScreenCapture(true);
//1,截图
var img = captureScreen(); 

var color = images.pixel(img, 100, 100);
//
toast(colors.toString(color));


//2,找到红色
while(true){
    var img = captureScreen();
    var point = findColor(img, "#ff0000");
    if(point){
        toast("找到红色,坐标为(" + point.x + ", " + point.y + ")");
    }
}

//3.
//指定找色区域(400, 500),宽为300,长为200的区域,色临界值 默认4 (范围:0-255) 
var point = findColor(img, "#00ff00", {
     region: [400, 500, 300, 200],
     threshold: 4
 });

 //4, 根据颜色找坐标
 var p = findColorEquals(captureScreen(), "#f64d30");

 //5. 找图片
 var img = images.read("/sdcard/大图.png");
 var template = images.read("/sdcard/小图.png");
 var p = findImage(img, template);
 if(p){
     toast("找到啦:" + p);
 }else{
     toast("没找到");
 }
 img.recycle();
 template.recycle();

 //在一定范围内找
 var p = findImage(captureScreen(), template, {
     region: [0, 50],
     threshold: 0.8 //相似度,默认值0.9
 });

 //
 var j=findImage(img, template, {
     region: [x, y, width, height],
     threshold: 0.8 //相似度
 })

 //找100个给给我
 var result = images.matchTemplate(img, template, {
     region: [x, y, width, height],
     threshold:0.8,
     max: 100
 });
 //result.matches 

. .

vue与控制脚本的交互#

建议先大致了解Vue项目结构后进行

* 项目主体UI:pages/   各种UI样式举例(可忽略)
* 项目主体UI:pages/robots/        2个启动界面举例 
* 项目AJ脚本:static/robots/    (默认)用于存放控制脚本代码

从UI启动控制脚本(举例)#

项目文件#

不想看例子的人,请直接看这两个路径的源码

pages/robots/
static/robots/

【第1步】. 新建robotjs脚本:#

在项目路径~/static/robots下新建文件demo.js

launchApp("微信"); 
click("发现"); 
click("朋友圈");
desc("评论").findOne().click();
click("赞");

【第2步】. 修改UI界面内容#

页面: pages/index/index.vue

<template>
<view> 
    <button class="cu-btn bg-green shadow" @tap="start">启动</button>
    <button class="cu-btn bg-green shadow" @tap="stop">停止</button>
</view>
</template>
<script> 
var {autojs} = require('robot-tools');
export default {
    methods: {
        start() {
            autojs.start({
                file: 'demo.js', //文件路径为./static/robots/demo.js 
            });
        },
        stop(){
          autojs.stop();
        }
    }
}
</script>

【第3步】 从Hbuilder中启动app,#

菜单:运行/手机或模拟器/选择你的手机
(此时手机上会自动安装调试app),
APP启动后,请为这个app授权”无障碍“,”悬浮窗“,”从后台启动”

QQ群: 714554851#

  如果喜欢这个项目,可以请赠我一包华子 

综述#

RobotJs的脚本继承自Auto.js,使用JavaScript作为脚本语言,目前使用Rhino 1.7.7.2作为脚本引擎,支持ES5与部分ES6特性。

本文档的章节大致上是以模块来分的,总体上可以分成"自动操作"类模块(控件操作、触摸模拟、按键模拟等)和其他类模块(设备、应用、界面等)。

"自动操作"的部分又可以大致分为基于控件和基于坐标的操作。基于坐标的操作是传统按键精灵、触摸精灵等脚本软件采用的方式,通过屏幕坐标来点击、长按指定位置模拟操作,从而到达目的。例如click(100, 200), press(100, 200, 500)等。这种方式在游戏类脚本中比较有可行性,结合找图找色、坐标放缩功能也能达到较好的兼容性。但是,这种方式对一般软件脚本却难以达到想要的效果,而且这种方式需要安卓7.0版本以上或者root权限才能执行。所以对于一般软件脚本(例如批量添加联系人、自动提取短信验证码等等),我们采用基于控件的模拟操作方式,结合通知事情、按键事情等达成更好的工作流。这些部分的文档参见基于控件的操作基于坐标的操作

其他部分主要包括:

  • 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

Q & A#

问题交流QQ群:714554851#

1.运行基座时报错:Robot对象不存在#

初始化项目环境配置 不对: 手机上未安装正确的调试基座,会提示Robot对象不存在 请参考:Demo,调试,运行

2.打包异常#

  1. 本项目仅供学习交流,不提供打包技术支持 deploy.html
  2. 离线打包,请学者自行研究方法,不提供技术支持

3. Auto.js自带的模块和函数中没有的功能如何实现#

由于Auto.js支持直接调用Android的API,对于Auto.js没有内置的函数,可以直接通过修改Android代码为JavaScript代码实现。例如旋转图片的Android代码为:

import android.graphics.Bitmap;
import android.graphics.Matrix;

public static Bitmap rotate(final Bitmap src,
                            final int degrees,
                            final float px,
                            final float py) {
    if (degrees == 0) return src;
    Matrix matrix = new Matrix();
    matrix.setRotate(degrees, px, py);
    Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
    return ret;
}

转换为JavaScript的代码后为:

importClass(android.graphics.Bitmap);
importClass(android.graphics.Matrix);

function rotate(src, degrees, px, py){
    if (degrees == 0) return src;
    var matrix = new Matrix();
    matrix.setRotate(degrees, px, py);
    var ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
    return ret;
}

有关调用Android和Java的API的更多信息,参见Work with Java

App#

app模块提供一系列函数,用于使用其他应用、与其他应用交互。例如发送意图、打开文件、发送邮件等。

同时提供了方便的进阶函数startActivity和sendBroadcast,用他们可完成app模块没有内置的和其他应用的交互。

app.versionCode#

当前软件版本号,整数值。例如160, 256等。

如果在Auto.js中运行则为Auto.js的版本号;在打包的软件中则为打包软件的版本号。

toastLog(app.versionCode);

app.versionName#

当前软件的版本名称,例如"3.0.0 Beta"。

如果在Auto.js中运行则为Auto.js的版本名称;在打包的软件中则为打包软件的版本名称。

toastLog(app.verionName);

app.autojs.versionCode#

Auto.js版本号,整数值。例如160, 256等。

app.autojs.versionName#

Auto.js版本名称,例如"3.0.0 Beta"。

app.launchApp(appName)#

通过应用名称启动应用。如果该名称对应的应用不存在,则返回false; 否则返回true。如果该名称对应多个应用,则只启动其中某一个。

该函数也可以作为全局函数使用。

launchApp("Auto.js");

app.launch(packageName)#

通过应用包名启动应用。如果该包名对应的应用不存在,则返回false;否则返回true。

该函数也可以作为全局函数使用。

//启动微信
launch("com.tencent.mm");

app.launchPackage(packageName)#

相当于app.launch(packageName)

app.getPackageName(appName)#

获取应用名称对应的已安装的应用的包名。如果该找不到该应用,返回null;如果该名称对应多个应用,则只返回其中某一个的包名。

该函数也可以作为全局函数使用。

var name = getPackageName("QQ"); //返回"com.tencent.mobileqq"

app.getAppName(packageName)#

获取应用包名对应的已安装的应用的名称。如果该找不到该应用,返回null。

该函数也可以作为全局函数使用。

var name = getAppName("com.tencent.mobileqq"); //返回"QQ"

app.openAppSetting(packageName)#

打开应用的详情页(设置页)。如果找不到该应用,返回false; 否则返回true。

该函数也可以作为全局函数使用。

app.viewFile(path)#

用其他应用查看文件。文件不存在的情况由查看文件的应用处理。

如果找不出可以查看该文件的应用,则抛出ActivityNotException

//查看文本文件
app.viewFile("/sdcard/1.txt");

app.editFile(path)#

用其他应用编辑文件。文件不存在的情况由编辑文件的应用处理。

如果找不出可以编辑该文件的应用,则抛出ActivityNotException

//编辑文本文件
app.editFile("/sdcard/1.txt/);

app.uninstall(packageName)#

卸载应用。执行后会会弹出卸载应用的提示框。如果该包名的应用未安装,由应用卸载程序处理,可能弹出"未找到应用"的提示。

//卸载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

//发送邮件给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内的界面,在打包应用中运行则会打开打包应用的相应界面。

app.startActivity("console");

进阶: 意图Intent#

Intent(意图) 是一个消息传递对象,您可以使用它从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件之间的通信,但其基本用例主要包括以下三个:

  • 启动活动(Activity): Activity 表示应用中的一个"屏幕"。例如应用主入口都是一个Activity,应用的功能通常也以Activity的形式独立,例如微信的主界面、朋友圈、聊天窗口都是不同的Activity。通过将 Intent 传递给 startActivity(),您可以启动新的 Activity 实例。Intent 描述了要启动的 Activity,并携带了任何必要的数据。

  • 启动服务(Service): Service 是一个不使用用户界面而在后台执行操作的组件。通过将 Intent 传递给 startService(),您可以启动服务执行一次性操作(例如,下载文件)。Intent 描述了要启动的服务,并携带了任何必要的数据。

  • 传递广播: 广播是任何应用均可接收的消息。系统将针对系统事件(例如:系统启动或设备开始充电时)传递各种广播。通过将 Intent 传递给 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast(),您可以将广播传递给其他应用。

本模块提供了构建Intent的函数(app.intent()), 启动Activity的函数app.startActivity(), 发送广播的函数app.sendBroadcast()

使用这些方法可以用来方便的调用其他应用。例如直接打开某个QQ号的个人卡片页,打开某个QQ号的聊天窗口等。

var qq = "2732014414";
app.startActivity({ 
    action: "android.intent.action.VIEW", 
    data:"mqq://im/chat?chat_type=wpa&version=1&src_type=web&uin=" + qq, 
    packageName: "com.tencent.mobileqq", 
});

app.intent(options)#

  • options <Object> 选项,包括:

    • action <string> 意图的Action,指意图要完成的动作,是一个字符串常量,比如"android.intent.action.SEND"。当action以"android.intent.action"开头时,可以省略前缀,直接用"SEND"代替。参见Actions

    • type <string> 意图的MimeType,表示和该意图直接相关的数据的类型,表示比如"text/plain"为纯文本类型。

    • data <string> 意图的Data,表示和该意图直接相关的数据,是一个Uri, 可以是文件路径或者Url等。例如要打开一个文件, action为"android.intent.action.VIEW", data为"file:///sdcard/1.txt"。

    • category <Array> 意图的类别。比较少用。参见Categories

    • packageName <string> 目标包名

    • className <string> 目标Activity或Service等组件的名称

    • extras <Object> 以键值对构成的这个Intent的Extras(额外信息)。提供该意图的其他信息,例如发送邮件时的邮件标题、邮件正文。参见Extras

    • flags <Array> intent的标识,字符串数组,例如["activity_new_task", "grant_read_uri_permission"]。参见Flags

      [v4.1.0新增]

    • root <Boolea> 是否以root权限启动、发送该intent。使用该参数后,不能使用context.startActivity()等方法,而应该直接使用诸如app.startActivity({...})的方法。

      [v4.1.0新增]

根据选项,构造一个意图Intent对象。

例如:

//打开应用来查看图片文件
var i = app.intent({
    action: "VIEW",
    type: "image/png",
    data: "file:///sdcard/1.png"
});
context.startActivity(i);

需要注意的是,除非应用专门暴露Activity出来,否则在没有root权限的情况下使用intent是无法跳转到特定Activity、应用的特定界面的。例如我们能通过Intent跳转到QQ的分享界面,是因为QQ对外暴露了分享的Activity;而在没有root权限的情况下,我们无法通过intent跳转到QQ的设置界面,因为QQ并没有暴露这个Activity。

但如果有root权限,则在intent的参数加上"root": true即可。例如使用root权限跳转到Auto.js的设置界面为:

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或参考Android指南: Intent

app.startActivity(options)#

根据选项构造一个Intent,并启动该Activity。

app.startActivity({
    action: "SEND",
    type: "text/plain",
    data: "file:///sdcard/1.txt"
});

app.sendBroadcast(options)#

根据选项构造一个Intent,并发送该广播。

app.startService(options)#

根据选项构造一个Intent,并启动该服务。

app.sendBroadcast(name)#

[v4.1.0新增]

  • name <string> 特定的广播名称,包括:
    • inspect_layout_hierarchy 布局层次分析
    • inspect_layout_bounds 布局范围

发送以上特定名称的广播可以触发Auto.js的布局分析,方便脚本调试。这些广播在Auto.js发送才有效,在打包的脚本上运行将没有任何效果。

app.sendBroadcast("inspect_layout_bounds");

app.intentToShell(options)#

[v4.1.0新增]

根据选项构造一个Intent,转换为对应的shell的intent命令的参数。

例如:

shell("am start " + app.intentToShell({
    packageName: "org.autojs.autojs",
    className: "org.autojs.autojs.ui.settings.SettingsActivity_"
}), true);

参见intent参数的规范

app.parseUri(uri)#

[v4.1.0新增]

解析uri字符串并返回相应的Uri对象。即使Uri格式错误,该函数也会返回一个Uri对象,但之后如果访问该对象的scheme, path等值可能因解析失败而返回null

需要注意的是,在高版本Android上,由于系统限制直接在Uri暴露文件的绝对路径,因此如果uri字符串是文件file://...,返回的Uri会是诸如content://...的形式。

app.getUriForFile(path)#

[v4.1.0新增]

  • path <string> 文件路径,例如"/sdcard/1.txt"
  • 返回 <Uri> 一个指向该文件的Uri的对象,参见android.net.Uri

从一个文件路径创建一个uri对象。需要注意的是,在高版本Android上,由于系统限制直接在Uri暴露文件的绝对路径,因此返回的Uri会是诸如content://...的形式。

app.getInstalledApps([options])#

[Pro 8.0.0新增]

  • 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\<ApplicationInfo>>

返回为当前用户安装的所有应用程序包的列表。如果设置了match选项 uninstalled_packages,则包括被删除但保留了数据的应用程序。 获取安装的应用列表。

返回值是ApplicationInfo对象的数组。 如果没有安装任何应用,则返回一个空数组。

选项options的match选项用于指定要返回哪些应用程序,get选项用于指定返回的应用程序携带哪些信息。

let apps = $app.getInstalledApps({
    matcg
})

Canvas#

canvas提供了使用画布进行2D画图的支持,可用于简单的小游戏开发或者图片编辑。使用canvas可以轻松地在一张图片或一个界面上绘制各种线与图形。

canvas的坐标系为平面直角坐标系,以控件左上角为原点,控件上边沿为x轴正方向,控件左边沿为y轴正方向。例如分辨率为1920*1080的屏幕上,canvas控件覆盖全屏,画一条从屏幕左上角到屏幕右下角的线段为:

canvas.drawLine(0, 0, 1080, 1920, paint);

canvas的绘制依赖于画笔Paint, 通过设置画笔的粗细、颜色、填充等可以改变绘制出来的图形。例如绘制一个红色实心正方形为:

var paint = new Paint();
//设置画笔为填充,则绘制出来的图形都是实心的
paint.setStyle(Paint.STYLE.FILL);
//设置画笔颜色为红色
paint.setColor(colors.RED);
//绘制一个从坐标(0, 0)到坐标(100, 100)的正方形
canvas.drawRect(0, 0, 100, 100, paint);

如果要绘制正方形的边框,则通过设置画笔的Style来实现:

var paint = new Paint();
//设置画笔为描边,则绘制出来的图形都是轮廓
paint.setStyle(Paint.STYLE.STROKE);
//设置画笔颜色为红色
paint.setColor(colors.RED);
//绘制一个从坐标(0, 0)到坐标(100, 100)的正方形
canvas.drawRect(0, 0, 100, 100, paint);

结合画笔,canvas可以绘制基本图形、图片等。

canvas.getWidth()#

返回画布当前图层的宽度。

canvas.getHeight()#

返回画布当前图层的高度。

canvas.drawRGB(r, int g, int b)#

将整个可绘制区域填充为r、g、b指定的颜色。相当于 canvas.drawColor(colors.rgb(r, g, b))

canvas.drawARGB(a, r, g, b)#

将整个可绘制区域填充为a、r、g、b指定的颜色。相当于 canvas.drawColor(colors.argb(a, r, g, b))

canvas.drawColor(color)#

将整个可绘制区域填充为color指定的颜色。

canvas.drawColor(color, mode)#

  • color <number> 颜色值
  • mode <PorterDuff.Mode> Porter-Duff操作

将整个可绘制区域填充为color指定的颜色。

canvas.drawPaint(paint)#

  • paint <Paint> 画笔

将整个可绘制区域用paint指定的画笔填充。相当于绘制一个无限大的矩形,但是更快。 通过该方法可以绘制一个指定的着色器的图案。

canvas.drawPoint(x, y, paint)#

在可绘制区域绘制由坐标(x, y)指定的点。 点的形状由画笔的线帽决定(参见paint.setStrokeCap(cap))。 点的大小由画笔的宽度决定(参见paint.setStrokeWidth(width))。

如果画笔宽度为0,则也会绘制1个像素至4个(若抗锯齿启用)。

相当于 canvas.drawPoints([x, y], paint)

canvas.drawPoints(pts, paint)#

  • pts <Array> 点坐标数组 [x0, y0, x1, y1, x2, y2, ...]
  • paint <Paint> 画笔

在可绘制区域绘制由坐标数组指定的多个点。

canvas.drawLine(startX, startY, stopX, stopY, paint)#

在可绘制区域绘制由起点坐标(startX, startY)和终点坐标(endX, endY)指定的线。 绘制时会忽略画笔的样式(Style)。也就是说,即使样式设为“仅填充(FILL)”也会绘制。 退化为点的线(长度为0)不会被绘制。

canvas.drawRect(r, paint)#

  • r <Rect> 矩形边界
  • paint <Paint> 画笔

在可绘制区域绘制由矩形边界r指定的矩形。 绘制时画笔的样式(Style)决定了是否绘制矩形界线和填充矩形。

canvas.drawRect(left, top, right, bottom, android.graphics.Paint paint)#

  • left <number> 矩形左边界x坐标
  • top <number> 矩形上边界y坐标
  • right <number> 矩形右边界x坐标
  • bottom <number> 矩形下边界y坐标
  • paint <Paint> 画笔

在可绘制区域绘制由矩形边界(left, top, right, bottom)指定的矩形。 绘制时画笔的样式(Style)决定了是否绘制矩形界线和填充矩形。

canvas.drawOval(android.graphics.RectF oval, android.graphics.Paint paint)#

canvas.drawOval(float left, float top, float right, float bottom, android.graphics.Paint paint)#

canvas.drawCircle(float cx, float cy, float radius, android.graphics.Paint paint)#

canvas.drawArc(android.graphics.RectF oval, float startAngle, float sweepAngle, boolean useCenter, android.graphics.Paint paint)#

canvas.drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, android.graphics.Paint paint)#

canvas.drawRoundRect(android.graphics.RectF rect, float rx, float ry, android.graphics.Paint paint)#

canvas.drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, android.graphics.Paint paint)#

canvas.drawPath(android.graphics.Path path, android.graphics.Paint paint)#

canvas.drawBitmap(android.graphics.Bitmap bitmap, float left, float top, android.graphics.Paint paint)#

canvas.drawText(java.lang.String text, float x, float y, android.graphics.Paint paint)#

canvas.drawTextOnPath(java.lang.String text, android.graphics.Path path, float hOffset, float vOffset, android.graphics.Paint paint)#

canvas.translate(dx, dy)#

  • dx <number> 向x轴正方向平移的距离,负数表示反方向平移
  • dy <number> 向y轴正方向平移的距离,负数表示反方向平移

平移指定距离。

canvas.scale(sx, sy)#

  • sx <number> 向x轴正方向平移的距离,负数表示反方向平移
  • sy <number> 向y轴正方向平移的距离,负数表示反方向平移

以原点为中心,将坐标系平移缩放指定倍数。

canvas.scale(float sx, float sy, float px, float py)#

canvas.rotate(float degrees)#

canvas.rotate(float degrees, float px, float py)#

canvas.skew(float sx, float sy)#

画笔#

变换矩阵#

路径#

Porter-Duff操作#

着色器#

遮罩过滤器#

颜色过滤器#

路径特效#

区域#

Console#

Stability: 2 - Stable

控制台模块提供了一个和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()。

该函数也可以作为全局函数使用。

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新增]

停止之前通过调用 console.time() 启动的定时器,并打印结果到控制台。 调用 console.timeEnd() 后定时器会被删除。如果不存在标签指定的定时器则会打印 NaNms

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类似,同时会打印出调用这个函数所在的调用栈信息(即当前运行的文件、行数等信息)。

console.trace('Show me');
// 打印: (堆栈跟踪会根据被调用的跟踪的位置而变化)
// Show me
//  at <test>:7

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)#

设置控制台的大小,单位像素。

console.show();
//设置控制台大小为屏幕的四分之一
console.setSize(device.width / 2, device.height / 2);

console.setPosition(x, y)#

设置控制台的位置,单位像素。

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

设置日志保存的路径和配置。例如把日志保存到"/sdcard/1.txt":

console.setGlobalLogConfig({
    "file": "/sdcard/1.txt"
});

注意该函数会影响所有脚本的日志记录。

print(text)#

相当于log(text)

基于坐标的触摸模拟#

Stability: 2 - Stable

本章节介绍了一些使用坐标进行点击、滑动的函数。这些函数有的需要安卓7.0以上,有的需要root权限。

要获取要点击的位置的坐标,可以在开发者选项中开启"指针位置"。

基于坐标的脚本通常会有分辨率的问题,这时可以通过setScreenMetrics()函数来进行自动坐标放缩。这个函数会影响本章节的所有点击、长按、滑动等函数。通过设定脚本设计时的分辨率,使得脚本在其他分辨率下自动放缩坐标。

控件和坐标也可以相互结合。一些控件是无法点击的(clickable为false), 无法通过.click()函数来点击,这时如果安卓版本在7.0以上或者有root权限,就可以通过以下方式来点击:

//获取这个控件
var widget = id("xxx").findOne();
//获取其中心位置并点击
click(widget.bounds().centerX(), widget.bounds().centerY());
//如果用root权限则用Tap

setScreenMetrics(width, height)#

  • width <number> 屏幕宽度,单位像素
  • height <number> 屏幕高度,单位像素

设置脚本坐标点击所适合的屏幕宽高。如果脚本运行时,屏幕宽度不一致会自动放缩坐标。

例如在1920*1080的设备中,某个操作的代码为

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, y),并返回是否点击成功。只有在点击执行完成后脚本才继续执行。

一般而言,只有点击过程(大约150毫秒)中被其他事件中断(例如用户自行点击)才会点击失败。

使用该函数模拟连续点击时可能有点击速度过慢的问题,这时可以用press()函数代替。

longClick(x, y)#

模拟长按坐标(x, y), 并返回是否成功。只有在长按执行完成(大约600毫秒)时脚本才会继续执行。

一般而言,只有长按过程中被其他事件中断(例如用户自行点击)才会长按失败。

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);
}

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, [800, 300], [500, 1000]],
         [0, 500, [300, 1500], [500, 1000]]);

RootAutomator#

Stability: 2 - Stable

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> 滑动时长,单位毫秒,默认值为300
  • id <number> 多点触摸id,可选,默认为1

模拟一次从(x1, y1)到(x2, y2)的时间为duration毫秒的滑动。

RootAutomator.press(x, y, duration[, id])#

模拟按下位置(x, y),时长为duration毫秒。

RootAutomator.longPress(x, y[\, id])#

模拟长按位置(x, y)。

以上为简单模拟触摸操作的函数。如果要模拟一些复杂的手势,需要更底层的函数。

RootAutomator.touchDown(x, y[, id])#

模拟手指按下位置(x, y)。

RootAutomator.touchMove(x, y[, id])#

模拟移动手指到位置(x, y)。

RootAutomator.touchUp([id])#

  • id <number> 多点触摸id,可选,默认为1

模拟手指弹起。

使用root权限点击和滑动的简单命令#

Stability: 1 - Experimental

注意:本章节的函数在后续版本很可能有改动!请勿过分依赖本章节函数的副作用。推荐使用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), 您可以通过"开发者选项"开启指针位置来确定点击坐标。

Swipe(x1, y1, x2, y2, [duration])#

  • x1, y1 <number> 滑动起点的坐标
  • x2, y2 <number> 滑动终点的坐标
  • duration <number> 滑动动作所用的时间

滑动。从(x1, y1)位置滑动到(x2, y2)位置。

Crypto#

[Pro 8.0.0新增]

$crypto模块提供了对称加密(例如AES)、非对称加密(例如RSA)、消息摘要(例如MD5, SHA)等支持。

$crypto.digest(message, algorithm[, options])#

  • message <any>
  • algorithm <string> 消息摘要算法,包括:
    • MD5
    • SHA-1
    • SHA-256
    • SHA-384
    • SHA-512
  • options <any>

对信息message使用消息摘要算法algorithm进行摘要并返回结果,默认的输出格式为hex。

参数message的类型默认为字符串,返回值默认为hex;可以通过options来指定参数message的类型和返回值的类型、格式,比如文件、base64、字节数组、hex等。参见《输入和输出的类型和格式》。

// 计算字符串abc的md5
toastLog($crypto.digest("abc", "MD5"));
// 计算字符串abc的sha-256
toastLog($crypto.digest("abc", "SHA-256"));
// 计算文件/sdcard/1.txt的md5
toastLog($crypto.digest("/sdcard/1.txt", "MD5", {
    input: "file"
}));

$crypto.encrypt(data, key, algorithm, options)#

  • data <any> 明文消息
  • 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
  • options <Object> 加密选项

输入和输出的类型和格式#

Device#

Stability: 2 - Stable

device模块提供了与设备有关的信息与操作,例如获取设备宽高,内存使用率,IMEI,调整设备亮度、音量等。

此模块的部分函数,例如调整音量,需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。

device.width#

设备屏幕分辨率宽度。例如1080。

device.height#

设备屏幕分辨率高度。例如1920。

device.buildId#

Either a changelist number, or a label like "M4-rc20".

修订版本号,或者诸如"M4-rc20"的标识。

device.broad#

The name of the underlying board, like "goldfish".

设备的主板(?)型号。

device.brand#

The consumer-visible brand with which the product/hardware will be associated, if any.

与产品或硬件相关的厂商品牌,如"Xiaomi", "Huawei"等。

device.device#

The name of the industrial design.

设备在工业设计中的名称。

device.model#

The end-user-visible name for the end product.

设备型号。

device.product#

The name of the overall product.

整个产品的名称。

device.bootloader#

The system bootloader version number.

设备Bootloader的版本。

device.hardware#

The name of the hardware (from the kernel command line or /proc).

设备的硬件名称(来自内核命令行或者/proc)。

device.fingerprint#

A string that uniquely identifies this build. Do not attempt to parse this value.

构建(build)的唯一标识码。

device.serial#

A hardware serial number, if available. Alphanumeric only, case-insensitive.

硬件序列号。

device.sdkInt#

The user-visible SDK version of the framework; its possible values are defined in Build.VERSION_CODES.

安卓系统API版本。例如安卓4.4的sdkInt为19。

device.incremental#

The internal value used by the underlying source control to represent this build. E.g., a perforce changelist number or a git hash.

device.release#

The user-visible version string. E.g., "1.0" or "3.4b5".

Android系统版本号。例如"5.0", "7.1.1"。

device.baseOS#

The base OS build the product is based on.

device.securityPatch#

The user-visible security patch level.

安全补丁程序级别。

device.codename#

The current development codename, or the string "REL" if this is a release build.

开发代号,例如发行版是"REL"。

device.getIMEI()#

返回设备的IMEI.

device.getAndroidId()#

返回设备的Android ID。

Android ID为一个用16进制字符串表示的64位整数,在设备第一次使用时随机生成,之后不会更改,除非恢复出厂设置。

device.getMacAddress()#

返回设备的Mac地址。该函数需要在有WLAN连接的情况下才能获取,否则会返回null。

可能的后续修改:未来可能增加有root权限的情况下通过root权限获取,从而在没有WLAN连接的情况下也能返回正确的Mac地址,因此请勿使用此函数判断WLAN连接。

device.getBrightness()#

返回当前的(手动)亮度。范围为0~255。

device.getBrightnessMode()#

返回当前亮度模式,0为手动亮度,1为自动亮度。

device.setBrightness(b)#

设置当前手动亮度。如果当前是自动亮度模式,该函数不会影响屏幕的亮度。

此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。

device.setBrightnessMode(mode)#

  • mode <number> 亮度模式,0为手动亮度,1为自动亮度

设置当前亮度模式。

此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。

device.getMusicVolume()#

返回当前媒体音量。

device.getNotificationVolume()#

返回当前通知音量。

device.getAlarmVolume()#

返回当前闹钟音量。

device.getMusicMaxVolume()#

返回媒体音量的最大值。

device.getNotificationMaxVolume()#

返回通知音量的最大值。

device.getAlarmMaxVolume()#

返回闹钟音量的最大值。

device.setMusicVolume(volume)#

设置当前媒体音量。

此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。

device.setNotificationVolume(volume)#

设置当前通知音量。

此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。

device.setAlarmVolume(volume)#

设置当前闹钟音量。

此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。

device.getBattery()#

返回当前电量百分比。

device.isCharging()#

返回设备是否正在充电。

device.getTotalMem()#

返回设备内存总量,单位字节(B)。1MB = 1024 * 1024B。

device.getAvailMem()#

返回设备当前可用的内存,单位字节(B)。

device.isScreenOn()#

返回设备屏幕是否是亮着的。如果屏幕亮着,返回true; 否则返回false

需要注意的是,类似于vivo xplay系列的息屏时钟不属于"屏幕亮着"的情况,虽然屏幕确实亮着但只能显示时钟而且不可交互,此时isScreenOn()也会返回false

device.wakeUp()#

唤醒设备。包括唤醒设备CPU、屏幕等。可以用来点亮屏幕。

device.wakeUpIfNeeded()#

如果屏幕没有点亮,则唤醒设备。

device.keepScreenOn([timeout])#

  • timeout <number> 屏幕保持常亮的时间, 单位毫秒。如果不加此参数,则一直保持屏幕常亮。

保持屏幕常亮。

此函数无法阻止用户使用锁屏键等正常关闭屏幕,只能使得设备在无人操作的情况下保持屏幕常亮;同时,如果此函数调用时屏幕没有点亮,则会唤醒屏幕。

在某些设备上,如果不加参数timeout,只能在Auto.js的界面保持屏幕常亮,在其他界面会自动失效,这是因为设备的省电策略造成的。因此,建议使用比较长的时长来代替"一直保持屏幕常亮"的功能,例如device.keepScreenOn(3600 * 1000)

可以使用device.cancelKeepingAwake()来取消屏幕常亮。

//一直保持屏幕常亮
device.keepScreenOn()

device.keepScreenDim([timeout])#

  • timeout <number> 屏幕保持常亮的时间, 单位毫秒。如果不加此参数,则一直保持屏幕常亮。

保持屏幕常亮,但允许屏幕变暗来节省电量。此函数可以用于定时脚本唤醒屏幕操作,不需要用户观看屏幕,可以让屏幕变暗来节省电量。

此函数无法阻止用户使用锁屏键等正常关闭屏幕,只能使得设备在无人操作的情况下保持屏幕常亮;同时,如果此函数调用时屏幕没有点亮,则会唤醒屏幕。

可以使用device.cancelKeepingAwake()来取消屏幕常亮。

device.cancelKeepingAwake()#

取消设备保持唤醒状态。用于取消device.keepScreenOn(), device.keepScreenDim()等函数设置的屏幕常亮。

device.vibrate(millis)#

  • millis <number> 震动时间,单位毫秒

使设备震动一段时间。

//震动两秒
device.vibrate(2000);

device.cancelVibration()#

如果设备处于震动状态,则取消震动。

Dialogs#

Stability: 2 - Stable

dialogs 模块提供了简单的对话框支持,可以通过对话框和用户进行交互。最简单的例子如下:

alert("您好");

这段代码会弹出一个消息提示框显示"您好",并在用户点击"确定"后继续运行。稍微复杂一点的例子如下:

var clear = confirm("要清除所有缓存吗?");
if(clear){
    alert("清除成功!");
}

confirm()会弹出一个对话框并让用户选择"是"或"否",如果选择"是"则返回true。

需要特别注意的是,对话框在ui模式下不能像通常那样使用,应该使用回调函数或者Promise的形式。理解这一点可能稍有困难。举个例子:

"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模式。

显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。

该函数也可以作为全局函数使用。

alert("出现错误~", "出现未知错误,请联系脚本作者”);

在ui模式下该函数返回一个Promise。例如:

"ui";
alert("嘿嘿嘿").then(()=>{
    //当点击确定后会执行这里
});

dialogs.confirm(title[, content, callback])#

  • title <string> 对话框的标题。
  • content <string> 可选,对话框的内容。默认为空。
  • callback <Function> 回调函数,可选。当用户点击确定时被调用,一般用于ui模式。

显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 true ,否则返回 false

该函数也可以作为全局函数使用。

在ui模式下该函数返回一个Promise。例如:

"ui";
confirm("确定吗").then(value=>{
    //当点击确定后会执行这里, value为true或false, 表示点击"确定"或"取消"
});

dialogs.rawInput(title[, prefill, callback])#

  • title <string> 对话框的标题。
  • prefill <string> 输入框的初始内容,可选,默认为空。
  • callback <Function> 回调函数,可选。当用户点击确定时被调用,一般用于ui模式。

显示一个包含输入框的对话框,等待用户输入内容,并在用户点击确定时将输入的字符串返回。如果用户取消了输入,返回null。

该函数也可以作为全局函数使用。

var name = rawInput("请输入您的名字", "小明");
alert("您的名字是" + name);

在ui模式下该函数返回一个Promise。例如:

"ui";
rawInput("请输入您的名字", "小明").then(name => {
    alert("您的名字是" + name);
});

当然也可以使用回调函数,例如:

rawInput("请输入您的名字", "小明", name => {
     alert("您的名字是" + name);
});

dialogs.input(title[, prefill, callback])#

等效于 eval(dialogs.rawInput(title, prefill, callback)), 该函数和rawInput的区别在于,会把输入的字符串用eval计算一遍再返回,返回的可能不是字符串。

可以用该函数输入数字、数组等。例如:

var age = dialogs.input("请输入您的年龄", "18");
// new Date().getYear() + 1900 可获取当前年份
var year = new Date().getYear() + 1900 - age;
alert("您的出生年份是" + year);

在ui模式下该函数返回一个Promise。例如:

"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。

var options = ["选项A", "选项B", "选项C", "选项D"]
var i = dialogs.select("请选择一个选项", options);
if(i >= 0){
    toast("您选择的是" + options[i]);
}else{
    toast("您取消了选择");
}

在ui模式下该函数返回一个Promise。例如:

"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.build(properties)#

  • properties <Object> 对话框属性,用于配置对话框。
  • 返回 <Dialog>

创建一个可自定义的对话框,例如:

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为一倍行高
  • 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> 对话框输入框的默认输入内容

通过这些选项可以自定义一个对话框,并通过监听返回的Dialog对象的按键、输入事件来实现交互。下面是一些例子。

模拟alert对话框:

dialogs.build({
    title: "你好",
    content: "今天也要元气满满哦",
    positive: "好的"
}).show();

模拟confirm对话框:

dialogs.build({
    title: "你好",
    content: "请问你是笨蛋吗?",
    positive: "是的",
    negative: "我是大笨蛋"
}).on("positive", ()=>{
    alert("哈哈哈笨蛋");
}).on("negative", ()=>{
    alert("哈哈哈大笨蛋");
}).show();

模拟单选框:

dialogs.build({
    title: "单选",
    items: ["选项1", "选项2", "选项3", "选项4"],
    itemsSelectMode: "single",
    itemsSelectedIndex: 3
}).on("single_choice", (index, item)=>{
    toast("您选择的是" + item);
}).show();

"处理中"对话框:

var d = dialogs.build({
    title: "下载中...",
    progress: {
        max: -1
    },
    cancelable: false
}).show();

setTimeout(()=>{
    d.dismiss();
}, 3000);

输入对话框:

dialogs.build({
    title: "请输入您的年龄",
    inputPrefill: "18"
}).on("input", (input)=>{
    var age = parseInt(input);
    toastLog(age);
}).show();

使用这个函数来构造对话框,一个明显的不同是需要使用回调函数而不能像dialogs其他函数一样同步地返回结果;但也可以通过threads模块的方法来实现。例如显示一个输入框并获取输入结果为:

var input = threads.disposable();
dialogas.build({
    title: "请输入您的年龄",
    inputPrefill: "18"
}).on("input", text => {
    input.setAndNotify(text);
}).show();
var age = parseInt(input.blockedGet());
tosatLog(age);

Dialog#

dialogs.build()返回的对话框对象,内置一些事件用于响应用户的交互,也可以获取对话框的状态和信息。

事件: show#

  • dialog <Dialog> 对话框

对话框显示时会触发的事件。例如:

dialogs.build({
    title: "标题"
}).on("show", (dialog)=>{
    toast("对话框显示了");
}).show();

事件: cancel#

  • dialog <Dialog> 对话框

对话框被取消时会触发的事件。一个对话框可能按取消按钮、返回键取消或者点击对话框以外区域取消。例如:

dialogs.build({
    title: "标题",
    positive: "确定",
    negative: "取消"
}).on("cancel", (dialog)=>{
    toast("对话框取消了");
}).show();

事件: dismiss#

  • dialog <Dialog> 对话框

对话框消失时会触发的事件。对话框被取消或者手动调用dialog.dismiss()函数都会触发该事件。例如:

var d = dialogs.build({
    title: "标题",
    positive: "确定",
    negative: "取消"
}).on("dismiss", (dialog)=>{
    toast("对话框消失了");
}).show();

setTimeout(()=>{
    d.dismiss();
}, 5000);

事件: positive#

  • dialog <Dialog> 对话框

确定按钮按下时触发的事件。例如:

var d = dialogs.build({
    title: "标题",
    positive: "确定",
    negative: "取消"
}).on("positive", (dialog)=>{
    toast("你点击了确定");
}).show();

事件: negative#

  • dialog <Dialog> 对话框

取消按钮按下时触发的事件。例如:

var d = dialogs.build({
    title: "标题",
    positive: "确定",
    negative: "取消"
}).on("negative", (dialog)=>{
    toast("你点击了取消");
}).show();

事件: neutral#

  • dialog <Dialog> 对话框

中性按钮按下时触发的事件。例如:

var d = dialogs.build({
    title: "标题",
    positive: "确定",
    negative: "取消",
    neutral: "稍后提示"
}).on("positive", (dialog)=>{
    toast("你点击了稍后提示");
}).show();

事件: any#

  • dialog <Dialog> 对话框
  • action <string> 被点击的按钮,可能的值为:
    • positive 确定按钮
    • negative 取消按钮
    • neutral 中性按钮

任意按钮按下时触发的事件。例如:

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")的项目被点击选中时触发的事件。例如:

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")的项目被选中并点击确定时触发的事件。例如:

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")的项目被选中并点击确定时触发的事件。例如:

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> 对话框

带有输入框的对话框当点击确定时会触发的事件。例如:

dialogs.build({
    title: "请输入",
    positive: "确定",
    negative: "取消",
    inputPrefill: ""
}).on("input", (text, dialog)=>{
    toast("你输入的是" + text);
}).show();

事件: input_change#

  • text <string> 输入框的内容
  • dialog <Dialog> 对话框

对话框的输入框的文本发生变化时会触发的事件。例如:

dialogs.build({
    title: "请输入",
    positive: "确定",
    negative: "取消",
    inputPrefill: ""
}).on("input_change", (text, dialog)=>{
    toast("你输入的是" + text);
}).show();

dialog.getProgress()#

获取当前进度条的进度值,是一个整数

dialog.getMaxProgress()#

获取当前进度条的最大进度值,是一个整数

dialog.getActionButton(action)#

  • action <string> 动作,包括:
    • positive
    • negative
    • neutral

Engines#

Stability: 2 - Stable

engines模块包含了一些与脚本环境、脚本运行、脚本引擎有关的函数,包括运行其他脚本,关闭脚本等。

例如,获取脚本所在目录:

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。返回一个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.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[, config])#

  • path <string> 要运行的脚本路径。
  • config <Object> 运行配置项
    • delay <number> 延迟执行的毫秒数,默认为0
    • loopTimes <number> 循环运行次数,默认为1。0为无限循环。
    • interval <number> 循环运行时两次运行之间的时间间隔,默认为0
    • path <Array> | <string> 指定脚本运行的目录。这些路径会用于require时寻找模块文件。

在新的脚本环境中运行脚本文件path。返回一个ScriptExecution对象。

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对象。

engines.execAutoFile("/sdcard/脚本/1.auto");

engines.stopAll()#

停止所有正在运行的脚本。包括当前脚本自身。

engines.stopAllAndToast()#

停止所有正在运行的脚本并显示停止的脚本数量。包括当前脚本自身。

engines.myEngine()#

返回当前脚本的脚本引擎对象(ScriptEngine)

[v4.1.0新增] 特别的,该对象可以通过execArgv来获取他的运行参数,包括外部参数、intent等。例如:

log(engines.myEngine().execArgv);

普通脚本的运行参数通常为空,通过定时任务的广播启动的则可以获取到启动的intent。

engines.all()#

返回当前所有正在运行的脚本的脚本引擎ScriptEngine的数组。

log(engines.all());

ScriptExecution#

执行脚本时返回的对象,可以通过他获取执行的引擎、配置等,也可以停止这个执行。

要停止这个脚本的执行,使用exectuion.getEngine().forceStop().

ScriptExecution.getEngine()#

返回执行该脚本的脚本引擎对象(ScriptEngine)

ScriptExecution.getConfig()#

返回该脚本的运行配置(ScriptConfig)

ScriptEngine#

脚本引擎对象。

ScriptEngine.forceStop()#

停止脚本引擎的执行。

ScriptEngine.cwd()#

返回脚本执行的路径。对于一个脚本文件而言为这个脚本所在的文件夹;对于其他脚本,例如字符串脚本,则为null或者执行时的设置值。

ScriptEngine.getSource()#

返回当前脚本引擎正在执行的脚本对象。

log(engines.myEngine().getSource());

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", "你好");

ScriptConfig#

脚本执行时的配置。

delay#

延迟执行的毫秒数

interval#

循环运行时两次运行之间的时间间隔

loopTimes#

循环运行次数

getPath()#

返回一个字符串数组表示脚本运行时模块寻找的路径。

Events#

Stability: 2 - Stable

events模块提供了监听手机通知、按键、触摸的接口。您可以用他配合自动操作函数完成自动化工作。

events本身是一个EventEmiiter, 但内置了一些事件、包括按键事件、通知事件、Toast事件等。

需要注意的是,事件的处理是单线程的,并且仍然在原线程执行,如果脚本主体或者其他事件处理中有耗时操作、轮询等,则事件将无法得到及时处理(会进入事件队列等待脚本主体或其他事件处理完成才执行)。例如:

auto();
events.observeNotification();
events.on('toast', function(t){
    //这段代码将得不到执行
    log(t);
});
while(true){
    //死循环
}

events.emitter()#

返回一个新的EventEmitter。这个EventEmitter没有内置任何事件。

events.observeKey()#

启用按键监听,例如音量键、Home键。按键监听使用无障碍服务实现,如果无障碍服务未启用会抛出异常并提示开启。

只有这个函数成功执行后, onKeyDown, onKeyUp等按键事件的监听才有效。

该函数在安卓4.3以上才能使用。

events.onKeyDown(keyName, listener)#

注册一个按键监听函数,当有keyName对应的按键被按下会调用该函数。可用的按键名称参见Keys

例如:

//启用按键监听
events.observeKey();
//监听音量上键按下
events.onKeyDown("volume_up", function(event){
    toast("音量上键被按下了");
});
//监听菜单键按下
events.onKeyDown("menu", function(event){
    toast("菜单键被按下了");
    exit();
});

events.onKeyUp(keyName, listener)#

注册一个按键监听函数,当有keyName对应的按键弹起会调用该函数。可用的按键名称参见Keys

一次完整的按键动作包括了按键按下和弹起。按下事件会在手指按下一个按键的"瞬间"触发, 弹起事件则在手指放开这个按键时触发。

例如:

//启用按键监听
events.observeKey();
//监听音量下键弹起
events.onKeyDown("volume_down", function(event){
    toast("音量上键弹起");
});
//监听Home键弹起
events.onKeyDown("home", function(event){
    toast("Home键弹起");
    exit();
});

events.onceKeyDown(keyName, listener)#

注册一个按键监听函数,当有keyName对应的按键被按下时会调用该函数,之后会注销该按键监听器。

也就是listener只有在onceKeyDown调用后的第一次按键事件被调用一次。

events.onceKeyUp(keyName, listener)#

注册一个按键监听函数,当有keyName对应的按键弹起时会调用该函数,之后会注销该按键监听器。

也就是listener只有在onceKeyUp调用后的第一次按键事件被调用一次。

events.removeAllKeyDownListeners(keyName)#

删除该按键的KeyDown(按下)事件的所有监听。

events.removeAllKeyUpListeners(keyName)#

删除该按键的KeyUp(弹起)事件的所有监听。

events.setKeyInterceptionEnabled([key, ]enabled)#

设置按键屏蔽是否启用。所谓按键屏蔽指的是,屏蔽原有按键的功能,例如使得音量键不再能调节音量,但此时仍然能通过按键事件监听按键。

如果不加参数key则会屏蔽所有按键。

例如,调用events.setKeyInterceptionEnabled(true)会使系统的音量、Home、返回等键不再具有调节音量、回到主页、返回的作用,但此时仍然能通过按键事件监听按键。

该函数通常于按键监听结合,例如想监听音量键并使音量键按下时不弹出音量调节框则为:

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()#

返回触摸事件的最小时间间隔。

events.onTouch(listener)#

注册一个触摸监听函数。相当于on("touch", listener)

例如:

//启用触摸监听
events.observeTouch();
//注册触摸监听器
events.onTouch(function(p){
    //触摸事件发生时, 打印出触摸的点的坐标
    log(p.x + ", " + p.y);
});

events.removeAllTouchListeners()#

删除所有事件监听函数。

事件: 'key'#

  • keyCode <number> 键值
  • event <KeyEvent> 事件

当有按键被按下或弹起时会触发该事件。 例如:

auto();
events.observeKey();
events.on("key", function(keyCode, event){
    //处理按键事件
});

其中监听器的参数KeyCode包括:

  • keys.home 主页键
  • keys.back 返回键
  • keys.menu 菜单键
  • keys.volume_up 音量上键
  • keys.volume_down 音量下键

例如:

auto();
events.observeKey();
events.on("key", function(keyCode, event){
    if(keyCode == keys.menu && event.getAction() == event.ACTION_UP){
        toast("菜单键按下");
    }
});

事件: 'key_down'#

  • keyCode <number> 键值
  • event <KeyEvent> 事件

当有按键被按下时会触发该事件。

auto();
events.observeKey();
events.on("key_down", function(keyCode, event){
    //处理按键按下事件
});

事件: 'key_up'#

  • keyCode <number> 键值
  • event <KeyEvent> 事件

当有按键弹起时会触发该事件。

auto();
events.observeKey();
events.on("key_up", function(keyCode, event){
    //处理按键弹起事件
});

事件: 'exit`#

当脚本正常或者异常退出时会触发该事件。事件处理中如果有异常抛出,则立即中止exit事件的处理(即使exit事件有多个处理函数)并在控制台和日志中打印该异常。

一个脚本停止运行时,会关闭该脚本的所有悬浮窗,触发exit事件,之后再回收资源。如果exit事件的处理中有死循环,则后续资源无法得到及时回收。 此时脚本会停留在任务列表,如果在任务列表中关闭,则会强制结束exit事件的处理并回收后续资源。

log("开始运行")
events.on("exit", function(){
    log("结束运行");
});
log("即将结束运行");

events.observeNotification()#

开启通知监听。例如QQ消息、微信消息、推送等通知。

通知监听依赖于通知服务,如果通知服务没有运行,会抛出异常并跳转到通知权限开启界面。(有时即使通知权限已经开启通知服务也没有运行,这时需要关闭权限再重新开启一次)

例如:

events.obverseNotification();
events.onNotification(function(notification){
    log(notification.getText());
});

events.observeToast()#

开启Toast监听。

Toast监听依赖于无障碍服务,因此此函数会确保无障碍服务运行。

事件: 'toast'#

  • toast <Object>
    • getText() 获取Toast的文本内容
    • getPackageName() 获取发出Toast的应用包名

当有应用发出toast(气泡消息)时会触发该事件。但Auto.js软件本身的toast除外。

例如,要记录发出所有toast的应用:

events.observeToast();
events.onToast(function(toast){
    log("Toast内容: " + toast.getText() + " 包名: " + toast.getPackageName());
});

事件: 'notification'#

当有应用发出通知时会触发该事件,参数为Notification

例如:

events.observeNotification();
events.on("notification", function(n){
    log("收到新通知:\n 标题: %s, 内容: %s, \n包名: %s", n.getTitle(), n.getText(), n.getPackageName());
});

Notification#

通知对象,可以获取通知详情,包括通知标题、内容、发出通知的包名、时间等,也可以对通知进行操作,比如点击、删除。

Notification.number#

通知数量。例如QQ连续收到两条消息时number为2。

Notification.when#

通知发出时间的时间戳,可以用于构造Date对象。例如:

events.observeNotification();
events.on("notification", function(n){
    log("通知时间为}" + new Date(n.when));
});

Notification.getPackageName()#

获取发出通知的应用包名。

Notification.getTitle()#

获取通知的标题。

Notification.getText()#

获取通知的内容。

Notification.click()#

点击该通知。例如对于一条QQ消息,点击会进入具体的聊天界面。

Notification.delete()#

删除该通知。该通知将从通知栏中消失。

KeyEvent#

Stability: 2 - Stable

KeyEvent.getAction()#

返回事件的动作。包括:

  • KeyEvent.ACTION_DOWN 按下事件
  • KeyEvent.ACTION_UP 弹起事件

KeyEvent.getKeyCode()#

返回按键的键值。包括:

  • KeyEvent.KEYCODE_HOME 主页键
  • KeyEvent.KEYCODE_BACK 返回键
  • KeyEvent.KEYCODE_MENU 菜单键
  • KeyEvent.KEYCODE_VOLUME_UP 音量上键
  • KeyEvent.KEYCODE_VOLUME_DOWN 音量下键

KeyEvent.getEventTime()#

返回事件发生的时间戳。

KeyEvent.getDownTime()#

返回最近一次按下事件的时间戳。如果本身是按下事件,则与getEventTime()相同。

KeyEvent.keyCodeToString(keyCode)#

把键值转换为字符串。例如KEYCODE_HOME转换为"KEYCODE_HOME"。

keys#

Stability: 2 - Stable

按键事件中所有可用的按键名称为:

  • volume_up 音量上键
  • volume_down 音量下键
  • home 主屏幕键
  • back 返回键
  • menu 菜单键

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。

emitter.setMaxListeners(emitter.getMaxListeners() + 1);
emitter.once('event', () => {
  // 做些操作
  emitter.setMaxListeners(Math.max(emitter.getMaxListeners() - 1, 0));
});

EventEmitter.addListener(eventName, listener)#

emitter.on(eventName, listener) 的别名。

EventEmitter.emit(eventName[, ...args])#

  • eventName <any>
  • args <any>

按监听器的注册顺序,同步地调用每个注册到名为 eventName 事件的监听器,并传入提供的参数。

如果事件有监听器,则返回 true ,否则返回 false。

EventEmitter.eventNames()#

返回一个列出触发器已注册监听器的事件的数组。 数组中的值为字符串或符号。

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 的事件的监听器数组的副本。

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 被添加与调用多次。

server.on('connection', (stream) => {
  console.log('有连接!');
});

返回一个 EventEmitter 引用,可以链式调用。

默认情况下,事件监听器会按照添加的顺序依次调用。 emitter.prependListener() 方法可用于将事件监听器添加到监听器数组的开头。

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 事件时,监听器会被移除,然后调用。

server.once('connection', (stream) => {
  console.log('首次调用!');
});

返回一个 EventEmitter 引用,可以链式调用。

默认情况下,事件监听器会按照添加的顺序依次调用。 emitter.prependOnceListener() 方法可用于将事件监听器添加到监听器数组的开头。

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 被添加与调用多次。

server.prependListener('connection', (stream) => {
  console.log('有连接!');
});

返回一个 EventEmitter 引用,可以链式调用。

EventEmitter.prependOnceListener(eventName, listener)#

  • eventName <any> 事件名
  • listener <Function> 回调函数

添加一个单次 listener 函数到名为 eventName 的事件的监听器数组的开头。 下次触发 eventName 事件时,监听器会被移除,然后调用。

server.prependOnceListener('connection', (stream) => {
  console.log('首次调用!');
});

返回一个 EventEmitter 引用,可以链式调用。

EventEmitter.removeAllListeners([eventName])#

  • eventName <any>

移除全部或指定 eventName 的监听器。

注意,在代码中移除其他地方添加的监听器是一个不好的做法,尤其是当 EventEmitter 实例是其他组件或模块创建的。

返回一个 EventEmitter 引用,可以链式调用。

EventEmitter.removeListener(eventName, listener)#

从名为 eventName 的事件的监听器数组中移除指定的 listener。

const callback = (stream) => {
  console.log('有连接!');
};
server.on('connection', callback);
// ...
server.removeListener('connection', callback);

removeListener 最多只会从监听器数组里移除一个监听器实例。 如果任何单一的监听器被多次添加到指定 eventName 的监听器数组中,则必须多次调用 removeListener 才能移除每个实例。

注意,一旦一个事件被触发,所有绑定到它的监听器都会按顺序依次触发。 这意味着,在事件触发后、最后一个监听器完成执行前,任何 removeListener() 或 removeAllListeners() 调用都不会从 emit() 中移除它们。 随后的事件会像预期的那样发生。

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)#

默认情况下,如果为特定事件添加了超过 10 个监听器,则 EventEmitter 会打印一个警告。 此限制有助于寻找内存泄露。 但是,并不是所有的事件都要被限为 10 个。 emitter.setMaxListeners() 方法允许修改指定的 EventEmitter 实例的限制。 值设为 Infinity(或 0)表明不限制监听器的数量。

返回一个 EventEmitter 引用,可以链式调用。

events.broadcast: 脚本间广播#

脚本间通信除了使用engines模块提供的ScriptEngine.emit()方法以外,也可以使用events模块提供的broadcast广播。

events.broadcast本身是一个EventEmitter,但它的事件是在脚本间共享的,所有脚本都能发送和监听这些事件;事件处理会在脚本主线程执行(后续可能加入函数onThisThread(eventName, ...args)来提供在其他线程执行的能力)。

例如在一个脚本发送一个广播hello:

events.broadcast.emit("hello", "小明");

在其他脚本中监听并处理:

events.broadcast.on("hello", function(name){
    toast("你好, " + name);
});
//保持脚本运行
setInterval(()=>{}, 1000);

Floaty#

floaty模块提供了悬浮窗的相关函数,可以在屏幕上显示自定义悬浮窗,控制悬浮窗大小、位置等。

悬浮窗在脚本停止运行时会自动关闭,因此,要保持悬浮窗不被关闭,可以用一个空的setInterval来实现,例如:

setInterval(()=>{}, 1000);

floaty.window(layout)#

  • 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("文本");
});

有关返回的FloatyWindow对象的说明,参见下面的FloatyWindow章节。

floaty.rawWindow(layout)#

  • layout <xml> | <View> 悬浮窗界面的XML或者View

指定悬浮窗的布局,创建并显示一个原始悬浮窗,返回一个FloatyRawWindow对象。

floaty.window()函数不同的是,该悬浮窗不会增加任何额外设施(例如调整大小、位置按钮),您可以根据自己需要编写任何布局。

而且,该悬浮窗支持完全全屏,可以覆盖状态栏,因此可以做护眼模式之类的应用。

var w = floaty.rawWindow(
    <frame gravity="center">
        <text id="text">悬浮文字</text>
    </frame>
);

w.setPosition(500, 500);

setTimeout(()=>{
    w.close();
}, 2000);

这段代码运行后将会在屏幕上显示悬浮文字,并在两秒后消失。

有关返回的FloatyRawWindow对象的说明,参见下面的FloatyRawWindow章节。

floaty.closeAll()#

关闭所有本脚本的悬浮窗。

FloatyWindow#

悬浮窗对象,可通过FloatyWindow.{id}获取悬浮窗界面上的元素。例如, 悬浮窗window上一个控件的id为aaa, 那么window.aaa即可获取到该控件,类似于ui。

window.setAdjustEnabled(enabled)#

  • enabled <boolean> 是否启用悬浮窗调整(大小、位置)

如果enabled为true,则在悬浮窗左上角、右上角显示可供位置、大小调整的标示,就像控制台一样; 如果enabled为false,则隐藏上述标示。

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)#

设置悬浮窗是否可触摸,如果为true, 则悬浮窗将接收到触摸、点击等事件并且无法继续传递到悬浮窗下面;如果为false, 悬浮窗上的触摸、点击等事件将被直接传递到悬浮窗下面。处于安全考虑,被悬浮窗接收的触摸事情无法再继续传递到下层。

可以用此特性来制作护眼模式脚本。

var w = floaty.rawWindow(
    <frame gravity="center" bg="#44ffcc00"/>
);

w.setSize(-1, -1);
w.setTouchable(false);

setTimeout(()=>{
    w.close();
}, 4000);

window.setPosition(x, y)#

设置悬浮窗位置。

window.getX()#

返回悬浮窗位置的X坐标。

window.getY()#

返回悬浮窗位置的Y坐标。

window.setSize(width, height)#

设置悬浮窗宽高。

特别地,如果设置为-1,则为占满全屏;设置为-2则为根据悬浮窗内容大小而定。例如:

var w = floaty.rawWindow(
    <frame gravity="center" bg="#77ff0000">
        <text id="text">悬浮文字</text>
    </frame>
);

w.setSize(-1, -1);

setTimeout(()=>{
    w.close();
}, 2000);

window.getWidht()#

返回悬浮窗宽度。

window.getHeight()#

返回悬浮窗高度。

window.close()#

关闭悬浮窗。如果悬浮窗已经是关闭状态,则此函数将不执行任何操作。

被关闭后的悬浮窗不能再显示。

window.exitOnClose()#

使悬浮窗被关闭时自动结束脚本运行。

Files#

Stability: 2 - Stable

files模块提供了一些常见的文件处理,包括文件读写、移动、复制、删掉等。

一次性的文件读写可以直接使用files.read(), files.write(), files.append()等方便的函数,但如果需要频繁读写或随机读写,则使用open()函数打开一个文件对象来操作文件,并在操作完毕后调用close()函数关闭文件。

files.isFile(path)#

返回路径path是否是文件。

log(files.isDir("/sdcard/文件夹/")); //返回false
log(files.isDir("/sdcard/文件.txt")); //返回true

files.isDir(path)#

返回路径path是否是文件夹。

log(files.isDir("/sdcard/文件夹/")); //返回true
log(files.isDir("/sdcard/文件.txt")); //返回false

files.isEmptyDir(path)#

返回文件夹path是否为空文件夹。如果该路径并非文件夹,则直接返回false

files.join(parent, child)#

连接两个路径并返回,例如files.join("/sdcard/", "1.txt")返回"/sdcard/1.txt"。

files.create(path)#

创建一个文件或文件夹并返回是否创建成功。如果文件已经存在,则直接返回false

files.create("/sdcard/新文件夹/");

files.createWithDirs(path)#

创建一个文件或文件夹并返回是否创建成功。如果文件所在文件夹不存在,则先创建他所在的一系列文件夹。如果文件已经存在,则直接返回false

files.createWithDirs("/sdcard/新文件夹/新文件夹/新文件夹/1.txt");

files.exists(path)#

返回在路径path处的文件是否存在。

files.ensureDir(path)#

确保路径path所在的文件夹存在。如果该路径所在文件夹不存在,则创建该文件夹。

例如对于路径"/sdcard/Download/ABC/1.txt",如果/Download/文件夹不存在,则会先创建Download,再创建ABC文件夹。

files.read(path[, encoding = "utf-8"])#

读取文本文件path的所有内容并返回。如果文件不存在,则抛出FileNotFoundException

log(files.read("/sdcard/1.txt"));

files.readBytes(path)#

读取文件path的所有内容并返回一个字节数组。如果文件不存在,则抛出FileNotFoundException

注意,该数组是Java的数组,不具有JavaScript数组的forEach, slice等函数。

一个以16进制形式打印文件的例子如下:

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"])#

把text写入到文件path中。如果文件存在则覆盖,不存在则创建。

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'])#

把text追加到文件path的末尾。如果文件不存在则创建。

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)#

复制文件,返回是否复制成功。例如files.copy("/sdcard/1.txt", "/sdcard/Download/1.txt")

files.move(fromPath, toPath)#

移动文件,返回是否移动成功。例如files.move("/sdcard/1.txt", "/sdcard/Download/1.txt")会把1.txt文件从sd卡根目录移动到Download文件夹。

files.rename(path, newName)#

重命名文件,并返回是否重命名成功。例如files.rename("/sdcard/1.txt", "2.txt")

files.renameWithoutExtension(path, newName)#

重命名文件,不包含拓展名,并返回是否重命名成功。例如files.rename("/sdcard/1.txt", "2")会把"1.txt"重命名为"2.txt"。

files.getName(path)#

返回文件的文件名。例如files.getName("/sdcard/1.txt")返回"1.txt"。

files.getNameWithoutExtension(path)#

返回不含拓展名的文件的文件名。例如files.getName("/sdcard/1.txt")返回"1"。

files.getExtension(path)#

返回文件的拓展名。例如files.getExtension("/sdcard/1.txt")返回"txt"。

files.remove(path)#

删除文件或空文件夹,返回是否删除成功。

files.removeDir(path)#

删除文件夹,如果文件夹不为空,则删除该文件夹的所有内容再删除该文件夹,返回是否全部删除成功。

files.getSdcardPath()#

返回SD卡路径。所谓SD卡,即外部存储器。

files.cwd()#

返回脚本的"当前工作文件夹路径"。该路径指的是,如果脚本本身为脚本文件,则返回这个脚本文件所在目录;否则返回null获取其他设定路径。

例如,对于脚本文件"/sdcard/脚本/1.js"运行files.cwd()返回"/sdcard/脚本/"。

files.path(relativePath)#

返回相对路径对应的绝对路径。例如files.path("./1.png"),如果运行这个语句的脚本位于文件夹"/sdcard/脚本/"中,则返回"/sdcard/脚本/1.png"

files.listDir(path[, filter])#

  • path <string> 路径
  • filter <Function> 过滤函数,可选。接收一个string参数(文件名),返回一个boolean值。

列出文件夹path下的满足条件的文件和文件夹的名称的数组。如果不加filter参数,则返回所有文件和文件夹。

列出sdcard目录下所有文件和文件夹为:

var arr = files.listDir("/sdcard/");
log(arr);

列出脚本目录下所有js脚本文件为:

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写入到文件中。

PWritableTextFile.writeline(line)#

把文本line写入到文件中并写入一个换行符。

PWritableTextFile.writelines(lines)#

把很多行写入到文件中....

PWritableTextFile.flush()#

把缓冲区内容输出到文件中。

PWritableTextFile.close()#

关闭文件。同时会被缓冲区内容输出到文件。

打开一个文件写入后,不再使用时务必关闭,否则文件可能会丢失

全局变量与函数#

全局变量和函数在所有模块中均可使用。 但以下变量的作用域只在模块内,详见 module

  • exports
  • module
  • require() 以下的对象是特定于 Auto.js 的。 有些内置对象是 JavaScript 语言本身的一部分,它们也是全局的。

一些模块中的函数为了使用方便也可以直接全局使用,这些函数在此不再赘述。例如timers模块的setInterval, setTimeout等函数。

sleep(n)#

暂停运行n毫秒的时间。1秒等于1000毫秒。

//暂停5秒
sleep(5000);

currentPackage()#

返回最近一次监测到的正在运行的应用的包名,一般可以认为就是当前正在运行的应用的包名。

此函数依赖于无障碍服务,如果服务未启动,则抛出异常并提示用户启动。

currentActivity()#

返回最近一次监测到的正在运行的Activity的名称,一般可以认为就是当前正在运行的Activity的名称。

此函数依赖于无障碍服务,如果服务未启动,则抛出异常并提示用户启动。

setClip(text)#

设置剪贴板内容。此剪贴板即系统剪贴板,在一般应用的输入框中"粘贴"既可使用。

setClip("剪贴板文本");

getClip()#

返回系统剪贴板的内容。

toast("剪贴板内容为:" + getClip());

toast(message)#

以气泡显示信息message几秒。(具体时间取决于安卓系统,一般都是2秒)

注意,信息的显示是"异步"执行的,并且,不会等待信息消失程序才继续执行。如果在循环中执行该命令,可能出现脚本停止运行后仍然有不断的气泡信息出现的情况。 例如:

for(var i = 0; i < 100; i++){
  toast(i);
}

运行这段程序以后,会很快执行完成,且不断弹出消息,在任务管理中关闭所有脚本也无法停止。 要保证气泡消息才继续执行可以用:

for(var i = 0; i < 100; i++){
  toast(i);
  sleep(2000);
}

或者修改toast函数:

var _toast_ = toast;
toast = function(message){
  _toast_(message);
  sleep(2000);
}
for(var i = 0; i < 100; i++){
  toast(i);
}

toastLog(message)#

相当于toast(message);log(message)。显示信息message并在控制台中输出。参见console.log。

waitForActivity(activity[, period = 200])#

  • activity Activity名称
  • period 轮询等待间隔(毫秒)

等待指定的Activity出现,period为检查Activity的间隔。

waitForPackage(package[, period = 200])#

  • package 包名
  • period 轮询等待间隔(毫秒)

等待指定的应用出现。例如waitForPackage("com.tencent.mm")为等待当前界面为微信。

exit()#

立即停止脚本运行。

立即停止是通过抛出ScriptInterrupttedException来实现的,因此如果用try...catch把exit()函数的异常捕捉,则脚本不会立即停止,仍会运行几行后再停止。

random(min, max)#

返回一个在[min...max]之间的随机数。例如random(0, 2)可能产生0, 1, 2。

random()#

返回在[0, 1)的随机浮点数。

requiresApi(api)#

  • api 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)#

表示此脚本需要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.versionCodeapp.autojs.versionName获取当前的Auto.js版本号和版本。

runtime.requestPermissions(permissions)#

  • permissions <Array> 权限的字符串数组

动态申请安卓的权限。例如:

//请求GPS权限
runtime.requestPermissions(["access_fine_location"]);

尽管安卓有很多权限,但必须写入Manifest才能动态申请,为了防止权限的滥用,目前Auto.js只能额外申请两个权限:

  • access_fine_location GPS权限
  • record_audio 录音权限

您可以通过APK编辑器来增加Auto.js以及Auto.js打包的应用的权限。

安卓所有的权限列表参见Permissions Overview。(并没有用)

runtime.loadJar(path)#

加载目标jar文件,加载成功后将可以使用该Jar文件的类。

// 加载jsoup.jar
runtime.loadJar("./jsoup.jar");
// 使用jsoup解析html
importClass(org.jsoup.Jsoup);
log(Jsoup.parse(files.read("./test.html")));

(jsoup是一个Java实现的解析Html DOM的库,可以在Jsoup下载)

runtime.loadDex(path)#

加载目标dex文件,加载成功后将可以使用该dex文件的类。

因为加载jar实际上是把jar转换为dex再加载的,因此加载dex文件会比jar文件快得多。可以使用Android SDK的build tools的dx工具把jar转换为dex。

context#

全局变量。一个android.content.Context对象。

注意该对象为ApplicationContext,因此不能用于界面、对话框等的创建。

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请求如下:

console.show();
var r = http.get("www.baidu.com");
log("code = " + r.statusCode);
log("html = " + r.body.string());

采用回调形式的GET请求如下:

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参数中添加,例如:

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结果的例子如下:

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函数的默认方式。

一个模拟表单提交登录淘宝的例子如下:

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函数的默认方式。

一个调用图灵机器人接口的例子如下:

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为文件参数。举个例子,最简单的文件上传的请求为:

var res = http.postMultipart(url, {
    file: open("/sdcard/1.txt")
});
log(res.body.string());

如果使用格式2,则代码为

var res = http.postMultipart(url, {
    file: ["1.txt", "/sdcard/1.txt"]
});
log(res.body.string());

如果使用格式3,则代码为

var res = http.postMultipart(url, {
    file: ["1.txt", "text/plain", "/sdcard/1.txt"]
});
log(res.body.string());

如果使用格式2的同时要附带非文件参数"appId=abcdefghijk",则为:

var res = http.postMultipart(url, {
    appId: "adcdefghijk",
    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可以包含以下属性:

该函数是get, post, postJson等函数的基础函数。因此除非是PUT, DELET等请求,或者需要更高定制的HTTP请求,否则直接使用get, post, postJson等函数会更加方便。

Response#

HTTP请求的响应。

Response.statusCode#

当前响应的HTTP状态码。例如200(OK), 404(Not Found)等。

有关HTTP状态码的信息,参见菜鸟教程:HTTP状态码

Response.statusMessage#

当前响应的HTTP状态信息。例如"OK", "Bad Request", "Forbidden"。

有关HTTP状态码的信息,参见菜鸟教程:HTTP状态码

例子:

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#

当前响应的HTTP头部信息。该对象的键是响应头名称,值是各自的响应头值。 所有响应头名称都是小写的(吗)。

有关HTTP头部信息,参见菜鸟教程:HTTP响应头信息

例子:

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#

当前响应的内容。他有以下属性和函数:

  • 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"等。

colors#

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)#

返回颜色值的字符串,格式为 "#AARRGGBB"。

colors.red(color)#

返回颜色color的R通道的值,范围0~255.

colors.green(color)#

返回颜色color的G通道的值,范围0~255.

colors.blue(color)#

返回颜色color的B通道的值,范围0~255.

colors.alpha(color)#

返回颜色color的Alpha通道的值,范围0~255.

colors.rgb(red, green, blue)#

返回这些颜色通道构成的整数颜色值。Alpha通道将是255(不透明)。

colors.argb(alpha, red, green, blue)#

返回这些颜色通道构成的整数颜色值。

colors.parseColor(colorStr)#

返回颜色的整数值。

colors.isSimilar(color2, color2[, threshold, algorithm])#

  • color1 <number> | <string> 颜色值1
  • color1 <number> | <string> 颜色值2
  • threshold <number> 颜色相似度临界值,默认为4。取值范围为0~255。这个值越大表示允许的相似程度越小,如果这个值为0,则两个颜色相等时该函数才会返回true。
  • algorithm <string> 颜色匹配算法,默认为"diff", 包括:
    • "diff": 差值匹配。与给定颜色的R、G、B差的绝对值之和小于threshold时匹配。
    • "rgb": rgb欧拉距离相似度。与给定颜色color的rgb欧拉距离小于等于threshold时匹配。
    • "rgb+": 加权rgb欧拉距离匹配(LAB Delta E)。
    • "hs": hs欧拉距离匹配。hs为HSV空间的色调值。
  • 返回 <Boolean>

返回两个颜色是否相似。

colors.equals(color1, color2)#

返回两个颜色是否相等。*注意该函数会忽略Alpha通道的值进行比较

log(colors.equals("#112233", "#112234"));
log(colors.equals(0xFF112233, 0xFF223344));

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

Images#

Stability: 2 - Stable

images模块提供了一些手机设备中常见的图片处理函数,包括截图、读写图片、图片剪裁、旋转、二值化、找色找图等。

该模块分为两个部分,找图找色部分和图片处理部分。

需要注意的是,image对象创建后尽量在不使用时进行回收,同时避免循环创建大量图片。因为图片是一种占用内存比较大的资源,尽管Auto.js通过各种方式(比如图片缓存机制、垃圾回收时回收图片、脚本结束时回收所有图片)尽量降低图片资源的泄漏和内存占用,但是糟糕的代码仍然可以占用大量内存。

Image对象通过调用recycle()函数来回收。例如:

// 读取图片
var img = images.read("./1.png");
//对图片进行操作
... 
// 回收图片
img.recycle();

例外的是,caputerScreen()返回的图片不需要回收。

图片处理#

images.read(path)#

读取在路径path的图片文件并返回一个Image对象。如果文件不存在或者文件无法解码则返回null。

images.load(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中。如果文件不存在会被创建;文件存在会被覆盖。

//把图片压缩为原来的一半质量并保存
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.clip(img, x, y, w, h)#

  • img <Image> 图片
  • x <number> 剪切区域的左上角横坐标
  • y <number> 剪切区域的左上角纵坐标
  • w <number> 剪切区域的宽度
  • h <number> 剪切区域的高度
  • 返回 <Image>

从图片img的位置(x, y)处剪切大小为w * h的区域,并返回该剪切区域的新图片。

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
  • 返回 <Image>

调整图片大小,并返回调整后的图片。例如把图片放缩为200*300:images.resize(img, [200, 300])

参见Imgproc.resize

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
  • 返回 <Image>

放缩图片,并返回放缩后的图片。例如把图片变成原来的一半:images.scale(img, 0.5, 0.5)

参见Imgproc.resize

images.rotate(img, degress[, x, y])#

[v4.1.0新增]

  • img <Image> 图片
  • degress <number> 旋转角度。
  • x <number> 旋转中心x坐标,默认为图片中点
  • y <number> 旋转中心y坐标,默认为图片中点
  • 返回 <Image>

将图片逆时针旋转degress度,返回旋转后的图片对象。

例如逆时针旋转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>

灰度化图片,并返回灰度化后的图片。

image.threshold(img, threshold, maxVal[, type])#

[v4.1.0新增]

  • img <Image> 图片
  • threshold <number> 阈值
  • maxVal <number> 最大值
  • type <string> 阈值化类型,默认为"BINARY",参见ThresholdTypes, 可选的值:

    • BINARY
    • BINARY_INV
    • TRUNC
    • TOZERO
    • TOZERO_INV
    • OTSU
    • TRIANGLE
  • 返回 <Image>

将图片阈值化,并返回处理后的图像。可以用这个函数进行图片二值化。例如:images.threshold(img, 100, 255, "BINARY"),这个代码将图片中大于100的值全部变成255,其余变成0,从而达到二值化的效果。如果img是一张灰度化图片,这个代码将会得到一张黑白图片。

可以参考有关博客(比如threshold函数的使用)或者OpenCV文档threshold

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)或者OpenCV文档adaptiveThreshold

images.cvtColor(img, code[, dstCn])#

[v4.1.0新增]

  • img <Image> 图片
  • code <string> 颜色空间转换的类型,可选的值有一共有205个(参见ColorConversionCodes),这里只列出几个:
    • BGR2GRAY BGR转换为灰度
    • BGR2HSV BGR转换为HSV
  • dstCn <number> 目标图像的颜色通道数量,如果不填写则根据其他参数自动决定。
  • 返回 <Image>

对图像进行颜色空间转换,并返回转换后的图像。

可以参考有关博客(比如颜色空间转换)或者OpenCV文档cvtColor

images.inRange(img, lowerBound, upperBound)#

[v4.1.0新增]

将图片二值化,在lowerBound~upperBound范围以外的颜色都变成0,在范围以内的颜色都变成255。

例如images.inRange(img, "#000000", "#222222")

images.interval(img, color, interval)#

[v4.1.0新增]

将图片二值化,在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>

对图像进行模糊(平滑处理),返回处理后的图像。

可以参考有关博客(比如实现图像平滑处理)或者OpenCV文档blur

images.medianBlur(img, size)#

[v4.1.0新增]

  • img <Image> 图片
  • size <Array> 定义滤波器的大小,如[3, 3]
  • 返回 <Image>

对图像进行中值滤波,返回处理后的图像。

可以参考有关博客(比如实现图像平滑处理)或者OpenCV文档blur

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>

对图像进行高斯模糊,返回处理后的图像。

可以参考有关博客(比如实现图像平滑处理)或者OpenCV文档GaussianBlur

images.matToImage(mat)#

[v4.1.0新增]

  • mat <Mat> OpenCV的Mat对象
  • 返回 <Image>

把Mat对象转换为Image对象。

找图找色#

images.requestScreenCapture([landscape])#

  • landscape <boolean> 布尔值, 表示将要执行的截屏是否为横屏。如果landscape为false, 则表示竖屏截图; true为横屏截图。

向系统申请屏幕截图权限,返回是否请求成功。

第一次使用该函数会弹出截图权限请求,建议选择“总是允许”。

这个函数只是申请截图权限,并不会真正执行截图,真正的截图函数是captureScreen()

该函数在截图脚本中只需执行一次,而无需每次调用captureScreen()都调用一次。

如果不指定landscape值,则截图方向由当前设备屏幕方向决定,因此务必注意执行该函数时的屏幕方向。

建议在本软件界面运行该函数,在其他软件界面运行时容易出现一闪而过的黑屏现象。

示例:

//请求截图
if(!requestScreenCapture()){
    toast("请求截图失败");
    exit();
}
//连续截图10张图片(间隔1秒)并保存到存储卡目录
for(var i = 0; i < 10; i++){
    captureScreen("/sdcard/screencapture" + i + ".png");
    sleep(1000);
}

该函数也可以作为全局函数使用。

images.captureScreen()#

截取当前屏幕并返回一个Image对象。

没有截图权限时执行该函数会抛出SecurityException。

该函数不会返回null,两次调用可能返回相同的Image对象。这是因为设备截图的更新需要一定的时间,短时间内(一般来说是16ms)连续调用则会返回同一张截图。

截图需要转换为Bitmap格式,从而该函数执行需要一定的时间(0~20ms)。

另外在requestScreenCapture()执行成功后需要一定时间后才有截图可用,因此如果立即调用captureScreen(),会等待一定时间后(一般为几百ms)才返回截图。

例子:

//请求横屏截图
requestScreenCapture(true);
//截图
var img = captureScreen();
//获取在点(100, 100)的颜色值
var color = images.pixel(img, 100, 100);
//显示该颜色值
toast(colors.toString(color));

该函数也可以作为全局函数使用。

images.captureScreen(path)#

截取当前屏幕并以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.

该函数也可以作为全局函数使用。

一个循环找色的例子如下:

requestScreenCapture();

//循环找色,找到红色(#ff0000)时停止并报告坐标
while(true){
    var img = captureScreen();
    var point = findColor(img, "#ff0000");
    if(point){
        toast("找到红色,坐标为(" + point.x + ", " + point.y + ")");
    }
}

一个区域找色的例子如下:

//读取本地图片/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])#

区域找色的简便方法。

相当于

images.findColor(img, color, {
     region: [x, y, width, height],
     threshold: threshold
});

该函数也可以作为全局函数使用。

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.

多点找色,类似于按键精灵的多点找色,其过程如下:

  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中指定,例如:

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)。

    • "hs": hs欧拉距离匹配。hs为HSV空间的色调值。

返回图片image在位置(x, y)处是否匹配到颜色color。用于检测图片中某个位置是否是特定颜色。

一个判断微博客户端的某个微博是否被点赞过的例子:

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.findImage(img, template[, options])#

  • img <Image> 大图片
  • template <Image> 小图片(模板)
  • options <Object> 找图选项

找图。在大图片img中查找小图片template的位置(模块匹配),找到时返回位置坐标(Point),找不到时返回null。

选项包括:

  • threshold <number> 图片相似度。取值范围为0~1的浮点数。默认值为0.9。
  • region <Array> 找图区域。参见findColor函数关于region的说明。
  • level <number> 一般而言不必修改此参数。不加此参数时该参数会根据图片大小自动调整。找图算法是采用图像金字塔进行的, level参数表示金字塔的层次, level越大可能带来越高的找图效率,但也可能造成找图失败(图片因过度缩小而无法分辨)或返回错误位置。因此,除非您清楚该参数的意义并需要进行性能调优,否则不需要用到该参数。

该函数也可以作为全局函数使用。

一个最简单的找图例子如下:

var img = images.read("/sdcard/大图.png");
var templ = images.read("/sdcard/小图.png");
var p = findImage(img, templ);
if(p){
    toast("找到啦:" + p);
}else{
    toast("没找到");
}

稍微复杂点的区域找图例子如下:

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])#

区域找图的简便方法。相当于:

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
    • level <number> 一般而言不必修改此参数。不加此参数时该参数会根据图片大小自动调整。找图算法是采用图像金字塔进行的, level参数表示金字塔的层次, level越大可能带来越高的找图效率,但也可能造成找图失败(图片因过度缩小而无法分辨)或返回错误位置。因此,除非您清楚该参数的意义并需要进行性能调优,否则不需要用到该参数。
  • 返回 <MatchingResult>

在大图片中搜索小图片,并返回搜索结果MatchingResult。该函数可以用于找图时找出多个位置,可以通过max参数控制最大的结果数量。也可以对匹配结果进行排序、求最值等操作。

MatchingResult#

[v4.1.0新增]

matches#

数组的元素是一个Match对象:

  • point <Point> 匹配位置
  • similarity <number> 相似度

例如:

var result = images.matchTemplate(img, template, {
    max: 100
});
result.matches.forEach(match => {
    log("point = " + match.point + ", similarity = " + match.similarity);
});

points#

first()#

  • 返回 <Match>

第一个匹配结果。如果没有任何匹配,则返回null

last()#

  • 返回 <Match>

最后一个匹配结果。如果没有任何匹配,则返回null

leftmost()#

  • 返回 <Match>

位于大图片最左边的匹配结果。如果没有任何匹配,则返回null

topmost()#

  • 返回 <Match>

位于大图片最上边的匹配结果。如果没有任何匹配,则返回null

rightmost()#

  • 返回 <Match>

位于大图片最右边的匹配结果。如果没有任何匹配,则返回null

bottommost()#

  • 返回 <Match>

位于大图片最下边的匹配结果。如果没有任何匹配,则返回null

best()#

  • 返回 <Match>

相似度最高的匹配结果。如果没有任何匹配,则返回null

worst()#

  • 返回 <Match>

相似度最低的匹配结果。如果没有任何匹配,则返回null

sortBy(cmp)#

  • cmp <Function>|<string> 比较函数,或者是一个字符串表示排序方向。例如"left"表示将匹配结果按匹配位置从左往右排序、"top"表示将匹配结果按匹配位置从上往下排序,"left-top"表示将匹配结果按匹配位置从左往右、从上往下排序。方向包括left(左), top (上), right (右), bottom(下)。
  • <MatchingResult>

对匹配结果进行排序,并返回排序后的结果。

var result = images.matchTemplate(img, template, {
    max: 100
});
log(result.sortBy("top-right"));

Image#

表示一张图片,可以是截图的图片,或者本地读取的图片,或者从网络获取的图片。

Image.getWidth()#

返回以像素为单位图片宽度。

Image.getHeight()#

返回以像素为单位的图片高度。

Image.saveTo(path)#

把图片保存到路径path。(如果文件存在则覆盖)

Image.pixel(x, y)#

返回图片image在点(x, y)处的像素的ARGB值。

该值的格式为0xAARRGGBB,是一个"32位整数"(虽然JavaScript中并不区分整数类型和其他数值类型)。

坐标系以图片左上角为原点。以图片左侧边为y轴,上侧边为x轴。

##

Point#

findColor, findImage返回的对象。表示一个点(坐标)。

Point.x#

横坐标。

Point.y#

纵坐标。

Keys#

按键模拟部分提供了一些模拟物理按键的全局函数,包括Home、音量键、照相键等,有的函数依赖于无障碍服务,有的函数依赖于root权限。

一般来说,以大写字母开头的函数都依赖于root权限。执行此类函数时,如果没有root权限,则函数执行后没有效果,并会在控制台输出一个警告。

back()#

模拟按下返回键。返回是否执行成功。 此函数依赖于无障碍服务。

home()#

模拟按下Home键。返回是否执行成功。 此函数依赖于无障碍服务。

powerDialog()#

弹出电源键菜单。返回是否执行成功。 此函数依赖于无障碍服务。

notifications()#

拉出通知栏。返回是否执行成功。 此函数依赖于无障碍服务。

quickSettings()#

显示快速设置(下拉通知栏到底)。返回是否执行成功。 此函数依赖于无障碍服务。

recents()#

显示最近任务。返回是否执行成功。 此函数依赖于无障碍服务。

splitScreen()#

分屏。返回是否执行成功。 此函数依赖于无障碍服务, 并且需要系统自身功能的支持。

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> | 要按下的按键的数字代码或名称。参见下表。 模拟物理按键。例如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

Media#

Stability: 2 - Stable

media模块提供多媒体编程的支持。目前仅支持音乐播放和媒体文件扫描。后续会结合UI加入视频播放等功能。

需要注意是,使用该模块播放音乐时是在后台异步播放的,在脚本结束后会自动结束播放,因此可能需要插入诸如sleep()的语句来使脚本保持运行。例如:

//播放音乐
media.playMusic("/sdcard/1.mp3");
//让音乐播放完
sleep(media.getMusicDuration());

media.scanFile(path)#

扫描路径path的媒体文件,将它加入媒体库中;或者如果该文件以及被删除,则通知媒体库移除该文件。

媒体库包括相册、音乐库等,因此该函数可以用于把某个图片文件加入相册。

//请求截图
requestScreenCapture(false);
//截图
var im = captureScreen();
var path = "/sdcard/screenshot.png";
//保存图片
im.saveTo(path);
//把图片加入相册
media.scanFile(path);

media.playMusic(path[, volume, looping])#

  • path <string> 音乐文件路径
  • volume <number> 播放音量,为0~1的浮点数,默认为1
  • looping <boolean> 是否循环播放,如果looping为true则循环播放,默认为false

播放音乐文件path。该函数不会显示任何音乐播放界面。如果文件不存在或者文件不是受支持的音乐格式,则抛出UncheckedIOException异常。

//播放音乐
media.playMusic("/sdcard/1.mp3");
//让音乐播放完
sleep(media.getMusicDuration());

如果要循环播放音乐,则使用looping参数:


//传递第三个参数为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)

//播放音乐
media.playMusic("/sdcard/1.mp3");
//调整到30秒的位置
media.musicSeekTo(30 * 1000);
//等待音乐播放完成
sleep(media.getMusicDuration() - 30 * 1000);

media.pauseMusic()#

暂停音乐播放。如果当前没有在播放音乐,则调用函数没有任何效果。

media.resumeMusic()#

继续音乐播放。如果当前没有播放过音乐,则调用该函数没有任何效果。

media.stopMusic()#

停止音乐播放。如果当前没有在播放音乐,则调用函数没有任何效果。

media.isMusicPlaying()#

返回当前是否正在播放音乐。

media.getMusicDuration()#

返回当前音乐的时长。单位毫秒。

media.getMusicCurrentPosition()#

返回当前音乐的播放进度(已经播放的时间),单位毫秒。

module (模块)#

Stability: 2 - Stable

Auto.js 有一个简单的模块加载系统。 在 Auto.js 中,文件和模块是一一对应的(每个文件被视为一个独立的模块)。

例子,假设有一个名为 foo.js 的文件:

var circle = require('circle.js');
console.log("半径为 4 的圆的面积是 %d", circle.area(4));

在第一行中,foo.js 加载了同一目录下的 circle.js 模块。

circle.js 文件的内容为:

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 导出一个构造函数:

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
  };
};

Sensors#

Stability: 2 - Stable

sensors模块提供了获取手机上的传感器的信息的支持,这些传感器包括距离传感器、光线光感器、重力传感器、方向传感器等。需要指出的是,脚本只能获取传感器的数据,不能模拟或伪造传感器的数据和事件,因此诸如模拟摇一摇的功能是无法实现的。

要监听一个传感器时,需要使用sensors.register()注册监听器,之后才能开始监听;不需要监听时则调用sensors.unregister()注销监听器。在脚本结束时会自动注销所有的监听器。同时,这种监听会使脚本保持运行状态,如果不注销监听器,脚本会一直保持运行状态。

例如,监听光线传感器的代码为:

//光线传感器监听
sensors.register("light").on("change", (event, light)=>{
    log("当前光强度为", light);
});

要注意的是,每个传感器的数据并不相同,所以对他们调用on()监听事件时的回调函数参数也不是相同,例如光线传感器参数为(event, light),加速度传感器参数为(event, ax, ay, az)。甚至在某些设备上的传感器参数有所增加,例如华为手机的距离传感器为三个参数,一般手机只有一个参数。

常用的传感器及其事件参数如下表:

  • accelerometer 加速度传感器,参数(event, ax, ay, az):

    • event SensorEvent 传感器事件,用于获取传感器数据变化时的所有信息
    • ax <number> x轴上的加速度,单位m/s^2
    • ay <number> y轴上的加速度,单位m/s^2
    • az <number> z轴上的加速度,单位m/s^2 这里的x轴,y轴,z轴所属的坐标系统如下图(其中z轴垂直于设备屏幕表面):

      !axis_device

  • orientation 方向传感器,参数(event, azimuth, pitch, roll):

    • event SensorEvent 传感器事件,用于获取传感器数据变化时的所有信息
    • azimuth <number> 方位角,从地磁指北方向线起,依顺时针方向到y轴之间的水平夹角,单位角度,范围0~359
    • pitch <number> 绕x轴旋转的角度,当设备水平放置时该值为0,当设备顶部翘起时该值为正数,当设备尾部翘起时该值为负数,单位角度,范围-180~180
    • roll <number> 绕y轴顺时针旋转的角度,单位角度,范围-90~90
  • gyroscope 陀螺仪传感器,参数(event, wx, wy, wz):

    • event SensorEvent 传感器事件,用于获取传感器数据变化时的所有信息
    • wx <number> 绕x轴的角速度,单位弧度/s
    • wy <number> 绕y轴的角速度,单位弧度/s
    • wz <number> 绕z轴的角速度,单位弧度/s
  • magnetic_field 磁场传感器,参数(event, bx, by, bz):

    • event SensorEvent 传感器事件,用于获取传感器数据变化时的所有信息
    • bx <number> x轴上的磁场强度,单位uT
    • by <number> y轴上的磁场强度,单位uT
    • bz <number> z轴上的磁场强度,单位uT
  • gravity 重力传感器,参数(event, gx, gy, gz):

    • event SensorEvent 传感器事件,用于获取传感器数据变化时的所有信息
    • gx <number> x轴上的重力加速度,单位m/s^2
    • gy <number> y轴上的重力加速度,单位m/s^2
    • gz <number> z轴上的重力加速度,单位m/s^2
  • linear_acceleration 线性加速度传感器,参数(event, ax, ay, az):

    • event SensorEvent 传感器事件,用于获取传感器数据变化时的所有信息
    • ax <number> x轴上的线性加速度,单位m/s^2
    • ay <number> y轴上的线性加速度,单位m/s^2
    • az <number> z轴上的线性加速度,单位m/s^2
  • ambient_temperature 环境温度传感器,大部分设备并不支持,参数(event, t):

    • event SensorEvent 传感器事件,用于获取传感器数据变化时的所有信息
    • t <number> 环境温度,单位摄氏度。
  • light 光线传感器,参数(event, light):

    • event SensorEvent 传感器事件,用于获取传感器数据变化时的所有信息
    • light <number> 环境光强度,单位lux
  • pressure 压力传感器,参数(event, p):

    • event SensorEvent 传感器事件,用于获取传感器数据变化时的所有信息
    • p <number> 大气压,单位hPa
  • proximity 距离传感器,参数(event, distance):

    • event SensorEvent 传感器事件,用于获取传感器数据变化时的所有信息
    • distance <number> 一般指设备前置摄像头旁边的距离传感器到前方障碍物的距离,并且很多设备上这个值只有两种情况:当障碍物较近时该值为0,当障碍物较远或在范围内没有障碍物时该值为5
  • relative_humidity 湿度传感器,大部分设备并不支持,参数(event, rh):

    • event SensorEvent 传感器事件,用于获取传感器数据变化时的所有信息
    • rh <number> 相对湿度,范围为0~100(百分比)

sensors.register(sensorName[, delay])#

  • sensorName <string> 传感器名称,常用的传感器名称如上面所述
  • delay <number> 传感器数据更新频率,可选,默认为sensors.delay.normal。可用的值如下:
    • sensors.delay.normal 正常频率
    • sensors.delay.ui 适合于用户界面的更新频率
    • sensors.delay.game 适合于游戏的更新频率
    • sensors.delay.fastest 最快的更新频率】
  • 返回 SensorEventEmiiter

注册一个传感器监听并返回SensorEventEmitter

例如:

console.show();
//注册传感器监听
var sensor = sensors.register("gravity");
if(sensor == null){
    toast("不支持重力传感器");
    exit();
}
//监听数据
sensor.on("change", (gx, gy, gz)=>{
    log("重力加速度: %d, %d, %d", gx, gy, gz);
});

可以通过delay参数来指定传感器数据的更新频率,例如:

var sensor = sensors.register("gravity", sensors.delay.game);

另外,如果不支持sensorName所指定的传感器,那么该函数将返回null;但如果sensors.ignoresUnsupportedSensor的值被设置为true, 则该函数会返回一个不会分发任何传感器事件的SensorEventEmitter

例如:

sensors.ignoresUnsupportedSensor = true;
//无需null判断
sensors.register("gravity").on("change", (gx, gy, gz)=>{
    log("重力加速度: %d, %d, %d", gx, gy, gz);
});

更多信息,参见SensorEventEmittersensors.ignoresUnsupportedSensor

sensors.unregister(emitter)#

注销该传感器监听器。被注销的监听器将不再能监听传感器数据。

//注册一个传感器监听器
var sensor = sensors.register("gravity");
if(sensor == null){
    exit();
}
//2秒后注销该监听器
setTimeout(()=> {
    sensors.unregister(sensor);
}, 2000);

sensors.unregisterAll()#

注销所有传感器监听器。

sensors.ignoresUnsupportedSensor#

表示是否忽略不支持的传感器。如果该值被设置为true,则函数sensors.register()即使对不支持的传感器也会返回一个无任何数据的虚拟传感器监听,也就是sensors.register()不会返回null从而避免非空判断,并且此时会触发sensors的"unsupported_sensor"事件。

//忽略不支持的传感器
sensors.ignoresUnsupportedSensor = true;
//监听有不支持的传感器时的事件
sensors.on("unsupported_sensor", function(sensorName){
    toastLog("不支持的传感器: " + sensorName);
});
//随便注册一个不存在的传感器。
log(sensors.register("aaabbb"));

事件: 'unsupported_sensor'#

  • sensorName <string> 不支持的传感器名称

sensors.ignoresUnsupportedSensor被设置为true并且有不支持的传感器被注册时触发该事件。事件参数的传感器名称。

SensorEventEmitter#

注册传感器返回的对象,其本身是一个EventEmmiter,用于监听传感器事件。

事件: 'change'#

  • ..args <Any> 传感器参数

当传感器数据改变时触发该事件;该事件触发的最高频繁由sensors.register()指定的delay参数决定。

事件参数根据传感器类型不同而不同,具体参见本章最前面的列表。

一个监听光线传感器和加速度传感器并且每0.5秒获取一个数据并最终写入一个csv表格文件的例子如下:

//csv文件路径
cosnt csvPath = "/sdcard/sensors_data.csv";
//记录光线传感器的数据
var light = 0;
//记录加速度传感器的数据
var ax = 0;
var ay = 0;
var az = 0;
//监听光线传感器
sensors.register("light", sensors.delay.fastest)
    .on("change", l => {
        light = l;
    });
//监听加速度传感器
sensors.register("accelerometer", sensors.delay.fastest)
    .on("change", (ax0, ay0, az0) => {
        ax = ax0;
        ay = ay0;
        az = az0;
    });

var file = open(csvPath, "w");
//写csv表格头
file.writeline("light,ax,ay,az")
//每0.5秒获取一次数据并写入文件
setInterval(()=>{
    file.writeline(util.format("%d,%d,%d,%d", light, ax, ay, az));
}, 500);
//10秒后退出并打开文件
setTimeout(()=>{
    file.close();
    sensors.unregsiterAll();
    app.viewFile(csvPath);
}, 10 * 1000);

事件: 'accuracy_change'#

  • accuracy <number> 表示传感器精度。为以下值之一:
    • -1 传感器未连接
    • 0 传感器不可读
    • 1 低精度
    • 2 中精度
    • 3 高精度

当传感器精度改变时会触发的事件。比较少用。

shell即Unix Shell,在类Unix系统提供与操作系统交互的一系列命令。

很多程序可以用来执行shell命令,例如终端模拟器。

在Auto.js大致等同于用adb执行命令"adb shell"。其实现包括两种方式:

  • 通过java.lang.Runtime.exec执行(shell, Tap, Home等函数)
  • 通过内嵌终端模拟器执行(RootAutomator, Shell等对象)

shell函数#

Stability: 2 - Stable

shell(cmd[, root])#

  • cmd <string> 要执行的命令
  • root <Boolean> 是否以root权限运行,默认为false。

一次性执行命令cmd, 并返回命令的执行结果。返回对象的其属性如下:

  • code <number> 返回码。执行成功时为0,失败时为非0的数字。
  • result <string> 运行结果(stdout输出结果)
  • error <string> 运行的错误信息(stderr输出结果)。例如执行需要root权限的命令但没有授予root权限会返回错误信息"Permission denied"。

示例(强制停止微信) :

var result = shell("am force-stop com.tencent.mm", true);
log(result);
console.show();
if(result.code == 0){
  toast("执行成功");
}else{
  toast("执行失败!请到控制台查看错误信息");
}

Shell#

Stability: 2 - Stable

shell函数通过用来一次性执行单条命令并获取结果。如果有多条命令需要执行,用Shell对象的效率更高。这是因为,每次运行shell函数都会打开一个单独的shell进程并在运行结束后关闭他,这个过程需要一定的时间;而Shell对象自始至终使用同一个shell进程。

new Shell(root)#

  • root <Boolean> 是否以root权限运行一个shell进程,默认为false。这将会影响其后使用该Shell对象执行的命令的权限

Shell对象的"构造函数"。

var sh = new Shell(true);
//强制停止微信
sh.exec("am force-stop com.tencent.mm");
sh.exit();

Shell.exec(cmd)#

执行命令cmd。该函数不会返回任何值。

注意,命令执行是"异步"的、非阻塞的。也就是不会等待命令完成后才继续向下执行。

尽管这样的设计使用起来有很多不便之处,但受限于终端模拟器,暂时没有解决方式;如果后续能找到解决方案,则将提供Shell.execAndWaitFor函数。

Shell.exit()#

直接退出shell。正在执行的命令会被强制退出。

Shell.exitAndWaitFor()#

执行"exit"命令并等待执行命令执行完成、退出shell。

此函数会执行exit命令来正常退出shell。

Shell.setCallback(callback)#

设置该Shell的回调函数,以便监听Shell的输出。可以包括以下属性:

  • onOutput <Function> 每当shell有新的输出时便会调用该函数。其参数是一个字符串。
  • onNewLine <Function> 每当shell有新的一行输出时便会调用该函数。其参数是一个字符串(不包括最后的换行符)。

例如:

var sh = new Shell();
sh.setCallback({
    onNewLine: function(line){
        //有新的一行输出时打印到控制台
        log(line);
    }
})
while(true){
    //循环输入命令
    var cmd = dialogs.rawInput("请输入要执行的命令,输入exit退出");
    if(cmd == "exit"){
        break;
    }
    //执行命令
    sh.exec(cmd);
}
sh.exit();

附录: shell命令简介#

以下关于shell命令的资料来自AndroidStudio用户指南:Shell命令

am命令#

am命令即Activity Manager命令,用于管理应用程序活动、服务等。

以下命令均以"am "开头,例如shell('am start -p com.tencent.mm');(启动微信)

start [options] intent#

启动 intent 指定的 Activity(应用程序活动)。
请参阅 intent 参数的规范

选项包括:

  • -D:启用调试。
  • -W:等待启动完成。
  • --start-profiler file:启动分析器并将结果发送到 file。
  • -P file:类似于 --start-profiler,但当应用进入空闲状态时分析停止。
  • -R count:重复 Activity 启动 count 次数。在每次重复前,将完成顶部 Activity。
  • -S:启动 Activity 前强行停止目标应用。
  • --opengl-trace:启用 OpenGL 函数的跟踪。
  • --user user_id | current:指定要作为哪个用户运行;如果未指定,则作为当前用户运行。

startservice [options] intent#

启动 intent 指定的 Service(服务)。
请参阅 intent 参数的规范
选项包括:

  • --user user_id | current:指定要作为哪个用户运行;如果未指定,则作为当前用户运行。

force-stop package#

强行停止与 package(应用包名)关联的所有应用。

kill [options] package#

终止与 package(应用包名)关联的所有进程。此命令仅终止可安全终止且不会影响用户体验的进程。
选项包括:

  • --user user_id | all | current:指定将终止其进程的用户;如果未指定,则终止所有用户的进程。

kill-all#

终止所有后台进程。

broadcast [options] intent#

发出广播 intent。 请参阅 intent 参数的规范

选项包括:

  • [--user user_id | all | current]:指定要发送到的用户;如果未指定,则发送到所有用户。

instrument [options] component#

使用 Instrumentation 实例启动监控。通常,目标 component 是表单 test_package/runner_class。
选项包括:

  • -r:输出原始结果(否则对 report_key_streamresult 进行解码)。与 [-e perf true] 结合使用以生成性能测量的原始输出。
  • -e name value:将参数 name 设为 value。对于测试运行器,通用表单为 -e testrunner_flag value[,value...]。
  • -p file:将分析数据写入 file。
  • -w:先等待仪器完成,然后再返回。测试运行器需要使用此选项。
  • --no-window-animation:运行时关闭窗口动画。
  • --user user_id | current:指定仪器在哪个用户中运行;如果未指定,则在当前用户中运行。
  • profile start process file 启动 process 的分析器,将结果写入 file。
  • profile stop process 停止 process 的分析器。

dumpheap [options] process file#

转储 process 的堆,写入 file。

选项包括:

  • --user [user_id|current]:提供进程名称时,指定要转储的进程用户;如果未指定,则使用当前用户。
  • -n:转储原生堆,而非托管堆。
  • set-debug-app [options] package 将应用 package 设为调试。

选项包括:

  • -w:应用启动时等待调试程序。
  • --persistent:保留此值。
  • clear-debug-app 使用 set-debug-app 清除以前针对调试用途设置的软件包。

monitor [options] 启动对崩溃或 ANR 的监控。#

选项包括:

  • --gdb:在崩溃/ANR 时在给定端口上启动 gdbserv。

    screen-compat <on> | <off> package#

    控制 package 的屏幕兼容性模式。

display-size [reset|widthxheight]#

替换模拟器/设备显示尺寸。此命令对于在不同尺寸的屏幕上测试您的应用非常有用,它支持使用大屏设备模仿小屏幕分辨率(反之亦然)。
示例:

shell("am display-size 1280x800", true);

display-density dpi#

替换模拟器/设备显示密度。此命令对于在不同密度的屏幕上测试您的应用非常有用,它支持使用低密度屏幕在高密度环境环境上进行测试(反之亦然)。
示例:

shell("am display-density 480", true);

to-uri intent#

将给定的 intent 规范以 URI 的形式输出。 请参阅 intent 参数的规范

to-intent-uri intent#

将给定的 intent 规范以 intent:URI 的形式输出。 请参阅 intent 参数的规范。

intent参数的规范#

对于采用 intent 参数的 am 命令,您可以使用以下选项指定 intent:

  • -a action
    指定 intent 操作,如“android.intent.action.VIEW”。此指定只能声明一次。
  • -d data_uri
    指定 intent 数据 URI,如“content://contacts/people/1”。此指定只能声明一次。
  • -t mime_type
    指定 intent MIME 类型,如“image/png”。此指定只能声明一次。
  • -c category
    指定 intent 类别,如“android.intent.category.APP_CONTACTS”。
  • -n component
    指定带有软件包名称前缀的组件名称以创建显式 intent,如“com.example.app/.ExampleActivity”。
  • -f flags
    将标志添加到 setFlags() 支持的 intent。
  • --esn extra_key
    添加一个 null extra。URI intent 不支持此选项。
  • -e|--es extra_key extra_string_value
    添加字符串数据作为键值对。
  • --ez extra_key extra_boolean_value
    添加布尔型数据作为键值对。
  • --ei extra_key extra_int_value
    添加整数型数据作为键值对。
  • --el extra_key extra_long_value
    添加长整型数据作为键值对。
  • --ef extra_key extra_float_value
    添加浮点型数据作为键值对。
  • --eu extra_key extra_uri_value
    添加 URI 数据作为键值对。
  • --ecn extra_key extra_component_name_value
    添加组件名称,将其作为 ComponentName 对象进行转换和传递。
  • --eia extra_key extra_int_value[,extra_int_value...]
    添加整数数组。
  • --ela extra_key extra_long_value[,extra_long_value...]
    添加长整型数组。
  • --efa extra_key extra_float_value[,extra_float_value...]
    添加浮点型数组。
  • --grant-read-uri-permission
    包含标志 FLAG_GRANT_READ_URI_PERMISSION。
  • --grant-write-uri-permission
    包含标志 FLAG_GRANT_WRITE_URI_PERMISSION。
  • --debug-log-resolution
    包含标志 FLAG_DEBUG_LOG_RESOLUTION。
  • --exclude-stopped-packages
    包含标志 FLAG_EXCLUDE_STOPPED_PACKAGES。
  • --include-stopped-packages
    包含标志 FLAG_INCLUDE_STOPPED_PACKAGES。
  • --activity-brought-to-front
    包含标志 FLAG_ACTIVITY_BROUGHT_TO_FRONT。
  • --activity-clear-top
    包含标志 FLAG_ACTIVITY_CLEAR_TOP。
  • --activity-clear-when-task-reset
    包含标志 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET。
  • --activity-exclude-from-recents
    包含标志 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS。
  • --activity-launched-from-history
    包含标志 FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY。
  • --activity-multiple-task
    包含标志 FLAG_ACTIVITY_MULTIPLE_TASK。
  • --activity-no-animation
    包含标志 FLAG_ACTIVITY_NO_ANIMATION。
  • --activity-no-history
    包含标志 FLAG_ACTIVITY_NO_HISTORY。
  • --activity-no-user-action
    包含标志 FLAG_ACTIVITY_NO_USER_ACTION。
  • --activity-previous-is-top
    包含标志 FLAG_ACTIVITY_PREVIOUS_IS_TOP。
  • --activity-reorder-to-front
    包含标志 FLAG_ACTIVITY_REORDER_TO_FRONT。
  • --activity-reset-task-if-needed
    包含标志 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED。
  • --activity-single-top
    包含标志 FLAG_ACTIVITY_SINGLE_TOP。
  • --activity-clear-task
    包含标志 FLAG_ACTIVITY_CLEAR_TASK。
  • --activity-task-on-home
    包含标志 FLAG_ACTIVITY_TASK_ON_HOME。
  • --receiver-registered-only
    包含标志 FLAG_RECEIVER_REGISTERED_ONLY。
  • --receiver-replace-pending
    包含标志 FLAG_RECEIVER_REPLACE_PENDING。
  • --selector
    需要使用 -d 和 -t 选项以设置 intent 数据和类型。

    URI component package#

    如果不受上述某一选项的限制,您可以直接指定 URI、软件包名称和组件名称。当参数不受限制时,如果参数包含一个“:”(冒号),则此工具假定参数是一个 URI;如果参数包含一个“/”(正斜杠),则此工具假定参数是一个组件名称;否则,此工具假定参数是一个软件包名称。

应用包名#

所谓应用包名,是唯一确定应用的标识。例如微信的包名是"com.tencent.mm", QQ的包名是"com.tencent.mobileqq"。
要获取一个应用的包名,可以通过函数getPackageName(appName)获取。参见帮助->其他一般函数。

pm命令#

pm命令用于管理应用程序,例如卸载应用、冻结应用等。
以下命令均以"pm "开头,例如"shell(\"pm disable com.tencent.mm\");"(冻结微信)

list packages [options] filter#

输出所有软件包,或者,仅输出包名称包含 filter 中的文本的软件包。
选项:

  • -f:查看它们的关联文件。
  • -d:进行过滤以仅显示已停用的软件包。
  • -e:进行过滤以仅显示已启用的软件包。
  • -s:进行过滤以仅显示系统软件包。
  • -3:进行过滤以仅显示第三方软件包。
  • -i:查看软件包的安装程序。
  • -u:也包括卸载的软件包。
  • --user user_id:要查询的用户空间。

list permission-groups#

输出所有已知的权限组。

list permissions [options] group#

输出所有已知权限,或者,仅输出 group 中的权限。
选项:

  • -g:按组加以组织。
  • -f:输出所有信息。
  • -s:简短摘要。
  • -d:仅列出危险权限。
  • -u:仅列出用户将看到的权限。

list instrumentation [options]#

列出所有测试软件包。
选项:

  • -f:列出用于测试软件包的 APK 文件。
  • target_package:列出仅用于此应用的测试软件包。

list features#

输出系统的所有功能。

list libraries#

输出当前设备支持的所有库。

list users#

输出系统上的所有用户。

path package#

输出给定 package 的 APK 的路径。

install [options] path#

将软件包(通过 path 指定)安装到系统。
选项:

  • -l:安装具有转发锁定功能的软件包。
  • -r:重新安装现有应用,保留其数据。
  • -t:允许安装测试 APK。
  • -i installer_package_name:指定安装程序软件包名称。
  • -s:在共享的大容量存储(如 sdcard)上安装软件包。
  • -f:在内部系统内存上安装软件包。
  • -d:允许版本代码降级。
  • -g:授予应用清单文件中列出的所有权限。

uninstall [options] package#

从系统中卸载软件包。
选项:

  • -k:移除软件包后保留数据和缓存目录。

    clear package#

    删除与软件包关联的所有数据。

enable package_or_component#

启用给定软件包或组件(作为“package/class”写入)。

disable package_or_component#

停用给定软件包或组件(作为“package/class”写入)。

disable-user [options] package_or_component#

选项:

  • --user user_id:要停用的用户。

    grant package_name permission#

    向应用授予权限。在运行 Android 6.0(API 级别 23)及更高版本的设备上,可以是应用清单中声明的任何权限。在运行 Android 5.1(API 级别 22)和更低版本的设备上,必须是应用定义的可选权限。

revoke package_name permission#

从应用中撤销权限。在运行 Android 6.0(API 级别 23)及更高版本的设备上,可以是应用清单中声明的任何权限。在运行 Android 5.1(API 级别 22)和更低版本的设备上,必须是应用定义的可选权限。

set-install-location location#

更改默认安装位置。位置值:

  • 0:自动—让系统决定最佳位置。
  • 1:内部—安装在内部设备存储上。
  • 2:外部—安装在外部介质上。

注:此命令仅用于调试目的;使用此命令会导致应用中断和其他意外行为。

get-install-location#

返回当前安装位置。返回值:

  • 0 [auto]:让系统决定最佳位置。
  • 1 [internal]:安装在内部设备存储上
  • 2 [external]:安装在外部介质上

set-permission-enforced permission [true|false]#

指定是否应强制执行给定的权限。

trim-caches desired_free_space#

减少缓存文件以达到给定的可用空间。

create-user user_name#

使用给定的 user_name 创建新用户,输出新用户的标识符。

remove-user user_id#

移除具有给定的 user_id 的用户,删除与该用户关联的所有数据。

get-max-users#

输出设备支持的最大用户数。

其他命令#

进行屏幕截图#

screencap 命令是一个用于对设备显示屏进行屏幕截图的 shell 实用程序。在 shell 中,此语法为:

screencap filename

例如:

$ shell("screencap /sdcard/screen.png");

列表文件#

ls filepath

例如:

log(shell("ls /system/bin").result);

Storages#

Stability: 2 - Stable

storages模块提供了保存简单数据、用户配置等的支持。保存的数据除非应用被卸载或者被主动删除,否则会一直保留。

storages支持number, boolean, string等数据类型以及把Object, ArrayJSON.stringify序列化存取。

storages保存的数据在脚本之间是共享的,任何脚本只要知道storage名称便可以获取到相应的数据,因此它不能用于敏感数据的储存。 storages无法像Web开发中LocalStorage一样提供根据域名独立的存储,因为脚本的路径随时可能改变。

storages.create(name)#

创建一个本地存储并返回一个Storage对象。不同名称的本地存储的数据是隔开的,而相同名称的本地存储的数据是共享的。

例如在一个脚本中,创建名称为ABC的存储并存入a=123:

var storage = storages.create("ABC");
storage.put("a", 123);

而在另一个脚本中是可以获取到ABC以及a的值的:

var storage = storages.create("ABC");
log("a = " + storage.get("a"));

因此,本地存储的名称比较重要,尽量使用含有域名、作者邮箱等唯一信息的名称来避免冲突,例如:

var storage = storages.create("2732014414@qq.com:ABC");

storages.remove(name)#

删除一个本地存储以及他的全部数据。如果该存储不存在,返回false;否则返回true。

Storages#

Storage.get(key[, defaultValue])#

  • key <string> 键值
  • defaultValue <any> 可选,默认值

从本地存储中取出键值为key的数据并返回。

如果该存储中不包含该数据,这时若指定了默认值参数则返回默认值,否则返回undefined。

返回的数据可能是任意数据类型,这取决于使用Storage.put保存该键值的数据时的数据类型。

Storage.put(key, value)#

把值value保存到本地存储中。value可以是undefined以外的任意数据类型。如果value为undefined则抛出TypeError。

存储的过程实际上是使用JSON.stringify把value转换为字符串再保存,因此value必须是可JSON化的才能被接受。

Storage.remove(key)#

移除键值为key的数据。不返回任何值。

Storage.contains(key)#

返回该本地存储是否包含键值为key的数据。是则返回true,否则返回false。

Storage.clear()#

移除该本地存储的所有数据。不返回任何值。

Threads#

Stability: 1 - Experiment

threads模块提供了多线程支持,可以启动新线程来运行脚本。

脚本主线程会等待所有子线程执行完成后才停止执行,因此如果子线程中有死循环,请在必要的时候调用exit()来直接停止脚本或threads.shutDownAll()来停止所有子线程。

通过threads.start()启动的所有线程会在脚本被强制停止时自动停止。

由于JavaScript自身没有多线程的支持,因此您可能会遇到意料之外的问题。

threads.start(action)#

启动一个新线程并执行action。

例如:

threads.start(function(){
    //在新线程执行的代码
    while(true){
        log("子线程");
    }
});
while(true){
    log("脚本主线程");
}

通过该函数返回的Thread对象可以获取该线程的状态,控制该线程的运行中。例如:

var thread = threads.start(function(){
    while(true){
        log("子线程");
    }
});
//停止线程执行
thread.interrupt();

更多信息参见Thread

threads.shutDownAll()#

停止所有通过threads.start()启动的子线程。

threads.currentThread()#

返回当前线程。

threads.disposable()#

新建一个Disposable对象,用于等待另一个线程的某个一次性结果。更多信息参见线程通信以及Disposable

threads.atomic([initialValue])#

新建一个整数原子变量。更多信息参见线程安全以及AtomicLong

threads.lock()#

新建一个可重入锁。更多信息参见线程安全以及ReentrantLock

Thread#

线程对象,threads.start()返回的对象,用于获取和控制线程的状态,与其他线程交互等。

Thread对象提供了和timers模块一样的API,例如setTimeout(), setInterval()等,用于在该线程执行相应的定时回调,从而使线程之间可以直接交互。例如:

var thread = threads.start(function(){
    //在子线程执行的定时器
    setInterval(function(){
        log("子线程:" + threads.currentThread());
    }, 1000);
});

log("当前线程为主线程:" + threads.currentThread());

//等待子线程启动
thread.waitFor();
//在子线程执行的定时器
thread.setTimeout(function(){
    //这段代码会在子线程执行
    log("当前线程为子线程:" + threads.currentThread());
}, 2000);

sleep(30 * 1000);
thread.interrupt();

Thread.interrupt()#

中断线程运行。

Thread.join([timeout])#

  • timeout <number> 等待时间,单位毫秒

等待线程执行完成。如果timeout为0,则会一直等待直至该线程执行完成;否则最多等待timeout毫秒的时间。

例如:

var sum = 0;
//启动子线程计算1加到10000
var thread = threads.start(function(){
    for(var i = 0; i < 10000; i++){
        sum += i;
    }
});
//等待该线程完成
thread.join();
toast("sum = " + sum);

isAlive()#

返回线程是否存活。如果线程仍未开始或已经结束,返回false; 如果线程已经开始或者正在运行中,返回true

waitFor()#

等待线程开始执行。调用threads.start()以后线程仍然需要一定时间才能开始执行,因此调用此函数会等待线程开始执行;如果线程已经处于执行状态则立即返回。

var thread = threads.start(function(){
    //do something
});
thread.waitFor();
thread.setTimeout(function(){
    //do something
}, 1000);

Thread.setTimeout(callback, delay[, ...args])#

参见timers.setTimeout()

区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出IllegalStateException

log("当前线程(主线程):" + threads.currentThread());

var thread = threads.start(function(){
    //设置一个空的定时来保持线程的运行状态
    setInterval(function(){}, 1000);
});

sleep(1000);
thread.setTimeout(function(){
    log("当前线程(子线程):" + threads.currentThread());
    exit();
}, 1000);

Thread.setInterval(callback, delay[, ...args])#

参见timers.setInterval()

区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出IllegalStateException

Thread.setImmediate(callback[, ...args])#

参见timers.setImmediate()

区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出IllegalStateException

Thread.clearInterval(id)#

参见timers.clearInterval()

区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出IllegalStateException

Thread.clearTimeout(id)#

参见timers.clearTimeout()

区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出IllegalStateException

Thread.clearImmediate(id)#

参见timers.clearImmediate()

区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出IllegalStateException

线程安全#

线程安全问题是一个相对专业的编程问题,本章节只提供给有需要的用户。

引用维基百科的解释:

线程安全是编程中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。

在Auto.js中,线程间变量在符合JavaScript变量作用域规则的前提下是共享的,例如全局变量在所有线程都能访问,并且保证他们在所有线程的可见性。但是,不保证任何操作的原子性。例如经典的自增"i++"将不是原子性操作。

Rhino和Auto.js提供了一些简单的设施来解决简单的线程安全问题,如锁threads.lock(), 函数同步锁sync(), 整数原子变量threads.atomic()等。

例如,对于多线程共享下的整数的自增操作(自增操作会导致问题,是因为自增操作实际上为i = i + 1,也就是先读取i的值, 把他加1, 再赋值给i, 如果两个线程同时进行自增操作,可能出现i的值只增加了1的情况),应该使用threads.atomic()函数来新建一个整数原子变量,或者使用锁threads.lock()来保证操作的原子性,或者用sync()来增加同步锁。

线程不安全的代码如下:

var i = 0;
threads.start(function(){
    while(true){
        log(i++);
    }
});
while(true){
    log(i++);
}

此段代码运行后打开日志,可以看到日志中有重复的值出现。

使用threads.atomic()的线程安全的代码如下:

//atomic返回的对象保证了自增的原子性
var i = threads.atomic();
threads.start(function(){
    while(true){
        log(i.getAndIncrement());
    }
});
while(true){
    log(i.getAndIncrement());
}

或者:

//锁保证了操作的原子性
var lock = threads.lock();
var i = 0;
threads.start(function(){
    while(true){
        lock.lock();
        log(i++);
        lock.unlock();
    }
});
while(true){
    lock.lock();
    log(i++);
    lock.unlock();
}

或者:

//sync函数会把里面的函数加上同步锁,使得在同一时刻最多只能有一个线程执行这个函数
var i = 0;
var getAndIncrement = sync(function(){
    return i++;
});
threads.start(function(){
    while(true){
        log(getAndIncrement());
    }
});
while(true){
    log(getAndIncrement());
}

另外,数组Array不是线程安全的,如果有这种复杂的需求,请用Android和Java相关API来实现。例如CopyOnWriteList, Vector等都是代替数组的线程安全的类,用于不同的场景。例如:

var nums = new java.util.Vector();
nums.add(123);
nums.add(456);
toast("长度为" + nums.size());
toast("第一个元素为" + nums.get(0));

但很明显的是,这些类不像数组那样简便易用,也不能使用诸如slice()之类的方便的函数。在未来可能会加入线程安全的数组来解决这个问题。当然您也可以为每个数组的操作加锁来解决线程安全问题:

var nums = [];
var numsLock = threads.lock();
threads.start(function(){
    //向数组添加元素123
    numsLock.lock();
    nums.push(123);
    log("线程: %s, 数组: %s", threads.currentThread(), nums);
    numsLock.unlock();
});

threads.start(function(){
    //向数组添加元素456
    numsLock.lock();
    nums.push(456);
    log("线程: %s, 数组: %s", threads.currentThread(), nums);
    numsLock.unlock();
});

//删除数组最后一个元素
numsLock.lock();
nums.pop();
log("线程: %s, 数组: %s", threads.currentThread(), nums);
numsLock.unlock();

sync(func)#

给函数func加上同步锁并作为一个新函数返回。

var i = 0;
function add(x){
    i += x;
}

var syncAdd = sync(add);
syncAdd(10);
toast(i);

线程通信#

Auto.js提供了一些简单的设施来支持简单的线程通信。threads.disposable()用于一个线程等待另一个线程的(一次性)结果,同时Lock.newCondition()提供了Condition对象用于一般的线程通信(await, signal)。另外,events模块也可以用于线程通信,通过指定EventEmiiter的回调执行的线程来实现。

使用threads.disposable()可以简单地等待和获取某个线程的执行结果。例如要等待某个线程计算"1+.....+10000":

var sum = threads.disposable();
//启动子线程计算
threads.start(function(){
    var s = 0;
    //从1加到10000
    for(var i = 1; i <= 10000; i++){
        s += i;
    }
    //通知主线程接收结果
    sum.setAndNotify(s);
});
//blockedGet()用于等待结果
toast("sum = " + sum.blockedGet());

如果上述代码用Condition实现:

//新建一个锁
var lock = threads.lock();
//新建一个条件,即"计算完成"
var complete = lock.newCondition();
var sum = 0;
threads.start(function(){
    //从1加到10000
    for(var i = 1; i <= 10000; i++){
        sum += i;
    }
    //通知主线程接收结果
    lock.lock();
    complete.signal();
    lock.unlock();
});
//等待计算完成
lock.lock();
complete.await();
lock.unlock();
//打印结果
toast("sum = " + sum);

如果上诉代码用events模块实现:

//新建一个emitter, 并指定回调执行的线程为当前线程
var sum = events.emitter(threads.currentThread());
threads.start(function(){
    var s = 0;
    //从1加到10000
    for(var i = 1; i <= 10000; i++){
        s += i;
    }
    //发送事件result通知主线程接收结果
    sum.emit('result', s);
});
sum.on('result', function(s){
    toastLog("sum = " + s + ", 当前线程: " + threads.currentThread());
});

有关线程的其他问题,例如生产者消费者等问题,请用Java相关方法解决,例如java.util.concurrent.BlockingQueue

Timers#

Stability: 2 - Stable

timers 模块暴露了一个全局的 API,用于在某个未来时间段调用调度函数。 因为定时器函数是全局的,所以使用该 API 无需调用 timers.***

Auto.js 中的计时器函数实现了与 Web 浏览器提供的定时器类似的 API,除了它使用了一个不同的内部实现,它是基于 Android Looper-Handler消息循环机制构建的。其实现机制与Node.js比较相似。

例如,要在5秒后发出消息"hello":

setTimeout(function(){
    toast("hello")
}, 5000);

需要注意的是,这些定时器仍然是单线程的。如果脚本主体有耗时操作或死循环,则设定的定时器不能被及时执行,例如:

setTimeout(function(){
    //这里的语句会在15秒后执行而不是5秒后
    toast("hello")
}, 5000);
//暂停10秒
sleep(10000);

再如:

setTimeout(function(){
    //这里的语句永远不会被执行
    toast("hello")
}, 5000);
//死循环
while(true);

setInterval(callback, delay[, ...args])#

  • callback <Function> 当定时器到点时要调用的函数。
  • delay <number> 调用 callback 之前要等待的毫秒数。
  • ...args <any> 当调用 callback 时要传入的可选参数。

预定每隔 delay 毫秒重复执行的 callback。 返回一个用于 clearInterval() 的 id。

当 delay 小于 0 时,delay 会被设为 0。

setTimeout(callback, delay[, ...args])#

  • callback <Function> 当定时器到点时要调用的函数。
  • delay <number> 调用 callback 之前要等待的毫秒数。
  • ...args <any> 当调用 callback 时要传入的可选参数。

预定在 delay 毫秒之后执行的单次 callback。 返回一个用于 clearTimeout() 的 id。

callback 可能不会精确地在 delay 毫秒被调用。 Auto.js 不能保证回调被触发的确切时间,也不能保证它们的顺序。 回调会在尽可能接近所指定的时间上调用。

当 delay 小于 0 时,delay 会被设为 0。

setImmediate(callback[, ...args])#

  • callback <Function> 在Looper循环的当前回合结束时要调用的函数。
  • ...args <any> 当调用 callback 时要传入的可选参数。

预定立即执行的 callback,它是在 I/O 事件的回调之后被触发。 返回一个用于 clearImmediate() 的 id。

当多次调用 setImmediate() 时,callback 函数会按照它们被创建的顺序依次执行。 每次事件循环迭代都会处理整个回调队列。 如果一个立即定时器是被一个正在执行的回调排入队列的,则该定时器直到下一次事件循环迭代才会被触发。

setImmediate()、setInterval() 和 setTimeout() 方法每次都会返回表示预定的计时器的id。 它们可用于取消定时器并防止触发。

clearInterval(id)#

  • id <number> 一个 setInterval() 返回的 id。

取消一个由 setInterval() 创建的循环定时任务。

例如:

//每5秒就发出一次hello
var id = setInterval(function(){
    toast("hello");
}, 5000);
//1分钟后取消循环
setTimeout(function(){
    clearInterval(id);
}, 60 * 1000);

clearTimeout(id)#

  • id <number> 一个 setTimeout() 返回的 id。

取消一个由 setTimeout() 创建的定时任务。

clearImmediate(id)#

  • id <number> 一个 setImmediate() 返回的 id。

取消一个由 setImmediate() 创建的 Immediate 对象。

用户界面: UI#

ui模块提供了编写用户界面的支持。

给Android开发者或者高阶用户的提醒,Auto.js的UI系统来自于Android,所有属性和方法都能在Android源码中找到。如果某些代码或属性没有出现在Auto.js的文档中,可以参考Android的文档。
View: https://developer.android.google.cn/reference/android/view/View?hl=cn
Widget: https://developer.android.google.cn/reference/android/widget/package-summary?hl=cn

带有ui的脚本的的最前面必须使用"ui";指定ui模式,否则脚本将不会以ui模式运行。正确示范:s

"ui";

//脚本的其他代码

字符串"ui"的前面可以有注释、空行和空格[v4.1.0新增],但是不能有其他代码。

界面是由视图(View)组成的。View分成两种,控件(Widget)和布局(Layout)。控件(Widget)用来具体显示文字、图片、网页等,比如文本控件(text)用来显示文字,按钮控件(button)则可以显示一个按钮并提供点击效果,图片控件(img)则用来显示来自网络或者文件的图片,除此之外还有输入框控件(input)、进度条控件(progressbar)、单选复选框控件(checkbox)等;布局(Layout)则是装着一个或多个控件的"容器",用于控制在他里面的控件的位置,比如垂直布局(vertical)会把他里面的控件从上往下依次显示(即纵向排列),水平布局(horizontal)则会把他里面的控件从左往右依次显示(即横向排列),以及帧布局(frame),他会把他里面的控件直接在左上角显示,如果有多个控件,后面的控件会重叠在前面的控件上。

我们使用xml来编写界面,并通过ui.layout()函数指定界面的布局xml。举个例子:

"ui";
$ui.layout(
    <vertical>
        <button text="第一个按钮"/>
        <button text="第二个按钮"/>
    </vertical>
);

在这个例子中,第3~6行的部分就是xml,指定了界面的具体内容。代码的第3行的标签<vertical> ... </vertical>表示垂直布局,布局的标签通常以<...>开始,以</...>结束,两个标签之间的内容就是布局里面的内容,例如<frame> ... </frame>。在这个例子中第4, 5行的内容就是垂直布局(vertical)里面的内容。代码的第4行是一个按钮控件(button),控件的标签通常以<...开始,以/>结束,他们之间是控件的具体属性,例如<text ... />。在这个例子中text="第一个按钮"的部分就是按钮控件(button)的属性,这个属性指定了这个按钮控件的文本内容(text)为"第一个按钮"。

代码的第5行和第4行一样,也是一个按钮控件,只不过他的文本内容为"第二个按钮"。这两个控件在垂直布局中,因此会纵向排列,效果如图:

ex1

如果我们把这个例子的垂直布局(vertical)改成水平布局(horizontal),也即:

"ui";
ui.layout(
    <horizontal>
        <button text="第一个按钮"/>
        <button text="第二个按钮"/>
    </horizontal>
);

则这两个按钮会横向排列,效果如图:

ex1-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是一个字体单位,暂时不用深入理会。上述代码的效果如图:

ex-properties

一个界面便由一些布局和控件组成。为了便于文档阅读,我们再说明一下以下术语:

  • 子视图, 子控件: 布局里面的控件是这个布局的子控件/子视图。实际上布局里面不仅仅只能有控件,还可以是嵌套的布局。因此用子视图(Child View)更准确一些。在上面的例子中,按钮便是垂直布局的子控件。
  • 父视图,父布局:直接包含一个控件的布局是这个控件的父布局/父视图(Parent View)。在上面的例子中,垂直布局便是按钮的父布局。

视图: View#

控件和布局都属于视图(View)。在这个章节中将介绍所有控件和布局的共有的属性和函数。例如属性背景,宽高等(所有控件和布局都能设置背景和宽高),函数click()设置视图(View)被点击时执行的动作。

attr(name, value)#

设置属性的值。属性指定是View在xml中的属性。例如可以通过语句attr("text", "文本")来设置文本控件的文本值。

"ui";

$ui.layout(
    <frame>
        <text id="example" text="Hello"/>
    </frame>
);

// 5秒后执行
$ui.post(() => {
    // 修改文本
    $ui.example.attr("text", "Hello, Auto.js UI");
    // 修改背景
    $ui.example.attr("bg", "#ff00ff");
    // 修改高度
    $ui.example.attr("h", "500dp");
}, 5000);

注意:并不是所有属性都能在js代码设置,有一些属性只能在布局创建时设置,例如style属性;还有一些属性虽然能在代码中设置,但是还没支持;对于这些情况,在Auto.js Pro 8.1.0+会抛出异常,其他版本则不会抛出异常。

attr(name)#

获取属性的值。

"ui";

$ui.layout(
    <frame>
        <text id="example" text="1"/>
    </frame>
);

plusOne();

function plusOne() {
    // 获取文本
    let text = $ui.example.attr("text");
    // 解析为数字
    let num = parseInt(text);
    // 数字加1
    num++;
    // 设置文本
    $ui.example.attr("text", String(num));
    // 1秒后继续
    $ui.post(plusOne, 1000);
}

w#

View的宽度,是属性width的缩写形式。可以设置的值为*, auto和具体数值。其中*表示宽度尽量填满父布局,而auto表示宽度将根据View的内容自动调整(自适应宽度)。例如:

"ui";
ui.layout(
    <horizontal>
        <button w="auto" text="自适应宽度"/>
        <button w="*" text="填满父布局"/>
    </horizontal>
);

在这个例子中,第一个按钮为自适应宽度,第二个按钮为填满父布局,显示效果为:

ex-w

如果不设置该属性,则不同的控件和布局有不同的默认宽度,大多数为auto

宽度属性也可以指定一个具体数值。例如w="20"w="20px"等。不加单位的情况下默认单位为dp,其他单位包括px(像素), mm(毫米), in(英寸)。有关尺寸单位的更多内容,参见尺寸的单位: Dimension

"ui";
ui.layout(
    <horizontal>
        <button w="200" text="宽度200dp"/>
        <button w="100" text="宽度100dp"/>
    </horizontal>
);

h#

View的高度,是属性height的缩写形式。可以设置的值为*, auto和具体数值。其中*表示宽度尽量填满父布局,而auto表示宽度将根据View的内容自动调整(自适应宽度)。

如果不设置该属性,则不同的控件和布局有不同的默认高度,大多数为auto

宽度属性也可以指定一个具体数值。例如h="20"h="20px"等。不加单位的情况下默认单位为dp,其他单位包括px(像素), mm(毫米), in(英寸)。有关尺寸单位的更多内容,参见尺寸的单位: Dimension

id#

View的id,用来区分一个界面下的不同控件和布局,一个界面的id在同一个界面下通常是唯一的,也就是一般不存在两个View有相同的id。id属性也是连接xml布局和JavaScript代码的桥梁,在代码中可以通过一个View的id来获取到这个View,并对他进行操作(设置点击动作、设置属性、获取属性等)。例如:

"ui";
ui.layout(
    <frame>
        <button id="ok" text="确定"/>
    </frame>
);
//通过ui.ok获取到按钮控件
toast(ui.ok.getText());

这个例子中有一个按钮控件"确定",id属性为"ok",那么我们可以在代码中使用ui.ok来获取他,再通过getText()函数获取到这个按钮控件的文本内容。 另外这个例子中使用帧布局(frame)是因为,我们只有一个控件,因此用于最简单的布局帧布局。

gravity#

View的"重力"。用于决定View的内容相对于View的位置,可以设置的值为:

  • left 靠左
  • right 靠右
  • top 靠顶部
  • bottom 靠底部
  • center 居中
  • center_vertical 垂直居中
  • center_horizontal 水平居中

例如对于一个按钮控件,gravity="right"会使其中的文本内容靠右显示。例如:

"ui";
ui.layout(
    <frame>
        <button gravity="right" w="*" h="auto" text="靠右的文字"/>
    </frame>
);

显示效果为:

ex-gravity

这些属性是可以组合的,例如gravity="right|bottom"的View他的内容会在右下角。

layout_gravity#

View在布局中的"重力",用于决定View本身在他的父布局的位置,可以设置的值和gravity属性相同。注意把这个属性和gravity属性区分开来。

"ui";
ui.layout(
    <frame w="*" h="*">
        <button layout_gravity="center" w="auto" h="auto" text="居中的按钮"/>
        <button layout_gravity="right|bottom" w="auto" h="auto" text="右下角的按钮"/>
    </frame>
);

在这个例子中,我们让帧布局(frame)的大小占满整个屏幕,通过给第一个按钮设置属性layout_gravity="center"来使得按钮在帧布局中居中,通过给第二个按钮设置属性layout_gravity="right|bottom"使得他在帧布局中位于右下角。效果如图:

ex-layout-gravity

要注意的是,layout_gravity的属性不一定总是生效的,具体取决于布局的类别。例如不能让水平布局中的第一个子控件靠底部显示(否则和水平布局本身相违背)。

margin#

margin为View和其他View的间距,即外边距。margin属性包括四个值:

  • marginLeft 左外边距
  • marginRight 右外边距
  • marginTop 上外边距
  • marginBottom 下外边距

而margin属性本身的值可以有三种格式:

  • margin="marginAll" 指定各个外边距都是该值。例如margin="10"表示左右上下边距都是10dp。
  • margin="marginLeft marginTop marginRight marginBottom" 分别指定各个外边距。例如margin="10 20 30 40"表示左边距为10dp, 上边距为20dp, 右边距为30dp, 下边距为40dp
  • margin="marginHorizontal marginVertical" 指定水平外边距和垂直外边距。例如margin="10 20"表示左右边距为10dp, 上下边距为20dp。

用一个例子来具体理解外边距的含义:

"ui";
ui.layout(
    <horizontal>
        <button margin="30" text="距离四周30"/>
        <button text="普通的按钮"/>
    </horizontal>
);

第一个按钮的margin属性指定了他的边距为30dp, 也就是他与水平布局以及第二个按钮的间距都是30dp, 其显示效果如图:

ex1-margin

如果把margin="30"改成margin="10 40"那么第一个按钮的左右间距为10dp, 上下间距为40dp, 效果如图:

ex2-margin

有关margin属性的单位,参见尺寸的单位: Dimension

marginLeft#

View的左外边距。如果该属性和margin属性指定的值冲突,则在后面的属性生效,前面的属性无效,例如margin="20" marginLeft="10"的左外边距为10dp,其他外边距为20dp。

"ui";
ui.layout(
    <horizontal>
        <button marginLeft="50" text="距离左边50"/>
        <button text="普通的按钮"/>
    </horizontal>
);

第一个按钮指定了左外边距为50dp,则他和他的父布局水平布局(horizontal)的左边的间距为50dp, 效果如图:

ex-marginLeft

marginRight#

View的右外边距。如果该属性和margin属性指定的值冲突,则在后面的属性生效,前面的属性无效。

marginTop#

View的上外边距。如果该属性和margin属性指定的值冲突,则在后面的属性生效,前面的属性无效。

marginBottom#

View的下外边距。如果该属性和margin属性指定的值冲突,则在后面的属性生效,前面的属性无效。

padding#

View和他的自身内容的间距,也就是内边距。注意和margin属性区分开来,margin属性是View之间的间距,而padding是View和他自身内容的间距。举个例子,一个文本控件的padding也即文本控件的边缘和他的文本内容的间距,paddingLeft即文本控件的左边和他的文本内容的间距。

paddding属性的值同样有三种格式:

  • padding="paddingAll" 指定各个内边距都是该值。例如padding="10"表示左右上下内边距都是10dp。
  • padding="paddingLeft paddingTop paddingRight paddingBottom" 分别指定各个内边距。例如padding="10 20 30 40"表示左内边距为10dp, 上内边距为20dp, 右内边距为30dp, 下内边距为40dp
  • padding="paddingHorizontal paddingVertical" 指定水平内边距和垂直内边距。例如padding="10 20"表示左右内边距为10dp, 上下内边距为20dp。

用一个例子来具体理解内边距的含义:

"ui";
ui.layout(
    <frame w="*" h="*" gravity="center">
        <text padding="10 20 30 40" bg="#ff0000" w="auto" h="auto" text="HelloWorld"/>
    </frame>
);

这个例子是一个居中的按钮(通过父布局的gravity="center"属性设置),背景色为红色(bg="#ff0000"),文本内容为"HelloWorld",左边距为10dp,上边距为20dp,下边距为30dp,右边距为40dp,其显示效果如图:

ex-padding

paddingLeft#

View的左内边距。如果该属性和padding属性指定的值冲突,则在后面的属性生效,前面的属性无效。

paddingRight#

View的右内边距。如果该属性和padding属性指定的值冲突,则在后面的属性生效,前面的属性无效。

paddingTop#

View的上内边距。如果该属性和padding属性指定的值冲突,则在后面的属性生效,前面的属性无效。

paddingBottom#

View的下内边距。如果该属性和padding属性指定的值冲突,则在后面的属性生效,前面的属性无效。

bg#

View的背景。其值可以是一个链接或路径指向的图片,或者RGB格式的颜色,或者其他背景。具体参见Drawables

例如,bg="#00ff00"设置背景为绿色,bg="file:///sdcard/1.png"设置背景为图片"1.png",bg="?attr/selectableItemBackground"设置背景为点击时出现的波纹效果(可能需要同时设置clickable="true"才生效)。

alpha#

View的透明度,其值是一个0~1之间的小数,0表示完全透明,1表示完全不透明。例如alpha="0.5"表示半透明。

foreground#

View的前景。前景即在一个View的内容上显示的内容,可能会覆盖掉View本身的内容。其值和属性bg的值类似。

minHeight#

View的最小高度。该值不总是生效的,取决于其父布局是否有足够的空间容纳。

例:<text height="auto" minHeight="50"/>

有关该属性的单位,参见尺寸的单位: Dimension

minWidth#

View的最小宽度。该值不总是生效的,取决于其父布局是否有足够的空间容纳。

例:<input width="auto" minWidth="50"/>

有关该属性的单位,参见尺寸的单位: Dimension

visibility#

View的可见性,该属性可以决定View是否显示出来。其值可以为:

  • gone 不可见。
  • visible 可见。默认情况下View都是可见的。
  • invisible 不可见,但仍然占用位置。

rotation#

View的旋转角度。通过该属性可以让这个View顺时针旋转一定的角度。例如rotation="90"可以让他顺时针旋转90度。

如果要设置旋转中心,可以通过transformPivotX, transformPivotY属性设置。默认的旋转中心为View的中心。

transformPivotX#

View的变换中心坐标x。用于View的旋转、放缩等变换的中心坐标。例如transformPivotX="10"

该坐标的坐标系以View的左上角为原点。也就是x值为变换中心到View的左边的距离。

有关该属性的单位,参见尺寸的单位: Dimension

transformPivotY#

View的变换中心坐标y。用于View的旋转、放缩等变换的中心坐标。例如transformPivotY="10"

该坐标的坐标系以View的左上角为原点。也就是y值为变换中心到View的上边的距离。

有关该属性的单位,参见尺寸的单位: Dimension

style#

设置View的样式。不同控件有不同的可选的内置样式。具体参见各个控件的说明。

需要注意的是,style属性只支持安卓5.1及其以上。

文本控件: text#

文本控件用于显示文本,可以控制文本的字体大小,字体颜色,字体等。

以下介绍该控件的主要属性和方法,如果要查看他的所有属性和方法,请阅读TextView

text#

设置文本的内容。例如text="一段文本"

textColor#

设置字体的颜色,可以是RGB格式的颜色(例如#ff00ff),或者颜色名称(例如red, green等),具体参见颜色

示例, 红色字体:<text text="红色字体" textColor="red"/>

textSize#

设置字体的大小,单位一般是sp。按照Material Design的规范,正文字体大小为14sp,标题字体大小为18sp,次标题为16sp。

示例,超大字体: <text text="超大字体" textSize="40sp"/>

textStyle#

设置字体的样式,比如斜体、粗体等。可选的值为:

  • bold 加粗字体
  • italic 斜体
  • normal 正常字体

可以用或("|")把他们组合起来,比如粗斜体为"bold|italic"。

例如,粗体:`

lines#

设置文本控件的行数。即使文本内容没有达到设置的行数,控件也会留出相应的宽度来显示空白行;如果文本内容超出了设置的行数,则超出的部分不会显示。

另外在xml中是不能设置多行文本的,要在代码中设置。例如:

"ui";
ui.layout(
    <vertical>
        <text id="myText" line="3">
    </vertical>
)
//通过\n换行
ui.myText.setText("第一行\n第二行\n第三行\n第四行");

maxLines#

设置文本控件的最大行数。

typeface#

设置字体。可选的值为:

  • normal 正常字体
  • sans 衬线字体
  • serif 非衬线字体
  • monospace 等宽字体

示例,等宽字体: <text text="等宽字体" typeface="monospace"/>

ellipsize#

设置文本的省略号位置。文本的省略号会在文本内容超出文本控件时显示。可选的值为:

  • end 在文本末尾显示省略号
  • marquee 跑马灯效果,文本将滚动显示
  • middle 在文本中间显示省略号
  • none 不显示省略号
  • start 在文本开头显示省略号

ems#

当设置该属性后,TextView显示的字符长度(单位是em),超出的部分将不显示,或者根据ellipsize属性的设置显示省略号。

例如,限制文本最长为5em: `

autoLink#

控制是否自动找到url和电子邮件地址等链接,并转换为可点击的链接。默认值为“none”。

设置该值可以让文本中的链接、电话等变成可点击状态。

可选的值为以下的值以其通过或("|")的组合:

  • all 匹配所有连接、邮件、地址、电话
  • email 匹配电子邮件地址
  • map 匹配地图地址
  • none 不匹配 (默认)
  • phone 匹配电话号码
  • web 匹配URL地址

示例:<text autoLink="web|phone" text="百度: http://www.baidu.com 电信电话: 10000"/>

按钮控件: button#

按钮控件是一个特殊的文本控件,因此所有文本控件的函数的属性都适用于按钮控件。

除此之外,按钮控件有一些内置的样式,通过style属性设置,包括:

  • Widget.AppCompat.Button.Colored 带颜色的按钮
  • Widget.AppCompat.Button.Borderless 无边框按钮
  • Widget.AppCompat.Button.Borderless.Colored 带颜色的无边框按钮

这些样式的具体效果参见"示例/界面控件/按钮控件.js"。

例如:<button style="Widget.AppCompat.Button.Colored" text="漂亮的按钮"/>

输入框控件: input#

输入框控件也是一个特殊的文本控件,因此所有文本控件的函数的属性和函数都适用于按钮控件。输入框控件有自己的属性和函数,要查看所有这些内容,阅读EditText

对于一个输入框控件,我们可以通过text属性设置他的内容,通过lines属性指定输入框的行数;在代码中通过getText()函数获取输入的内容。例如:

"ui";
ui.layout(
    <vertical padding="16">
        <text textSize="16sp" textColor="black" text="请输入姓名"/>
        <input id="name" text="小明"/>
        <button id="ok" text="确定"/>
    </vertical>
);
//指定确定按钮点击时要执行的动作
ui.ok.click(function(){
    //通过getText()获取输入的内容
    var name = ui.name.getText();
    toast(name + "您好!");
});

效果如图:

ex-input

除此之外,输入框控件有另外一些主要属性(虽然这些属性对于文本控件也是可用的但一般只用于输入框控件):

hint#

输入提示。这个提示会在输入框为空的时候显示出来。如图所示:

ex-hint

上面图片效果的代码为:

"ui";
ui.layout(
    <vertical>
        <input hint="请输入姓名"/>
    </vertical>
)

textColorHint#

指定输入提示的字体颜色。

textSizeHint#

指定输入提示的字体大小。

inputType#

指定输入框可以输入的文本类型。可选的值为以下值及其用"|"的组合:

  • date 用于输入日期。
  • datetime 用于输入日期和时间。
  • none 没有内容类型。此输入框不可编辑。
  • number 仅可输入数字。
  • numberDecimal 可以与number和它的其他选项组合,以允许输入十进制数(包括小数)。
  • numberPassword 仅可输入数字密码。
  • numberSigned 可以与number和它的其他选项组合,以允许输入有符号的数。
  • phone 用于输入一个电话号码。
  • text 只是普通文本。
  • textAutoComplete 可以与text和它的其他选项结合, 以指定此字段将做自己的自动完成, 并适当地与输入法交互。
  • textAutoCorrect 可以与text和它的其他选项结合, 以请求自动文本输入纠错。
  • textCapCharacters 可以与text和它的其他选项结合, 以请求大写所有字符。
  • textCapSentences 可以与text和它的其他选项结合, 以请求大写每个句子里面的第一个字符。
  • textCapWords 可以与text和它的其他选项结合, 以请求大写每个单词里面的第一个字符。
  • textEmailAddress 用于输入一个电子邮件地址。
  • textEmailSubject 用于输入电子邮件的主题。
  • textImeMultiLine 可以与text和它的其他选项结合,以指示虽然常规文本视图不应为多行, 但如果可以, 则IME应提供多行支持。
  • textLongMessage 用于输入长消息的内容。
  • textMultiLine 可以与text和它的其他选项结合, 以便在该字段中允许多行文本。如果未设置此标志, 则文本字段将被限制为单行。
  • textNoSuggestions 可以与text及它的其他选项结合, 以指示输入法不应显示任何基于字典的单词建议。
  • textPassword 用于输入密码。
  • textPersonName 用于输入人名。
  • textPhonetic 用于输入拼音发音的文本, 如联系人条目中的拼音名称字段。
  • textPostalAddress 用于输入邮寄地址。
  • textShortMessage 用于输入短的消息内容。
  • textUri 用于输入一个URI。
  • textVisiblePassword 用于输入可见的密码。
  • textWebEditText 用于输入在web表单中的文本。
  • textWebEmailAddress 用于在web表单里输入一个电子邮件地址。
  • textWebPassword 用于在web表单里输入一个密码。
  • time 用于输入时间。

例如,想指定一个输入框的输入类型为小数数字,为: <input inputType="number|numberDecimal"/>

password#

指定输入框输入框是否为密码输入框。默认为false

例如:<input password="true"/>

numeric#

指定输入框输入框是否为数字输入框。默认为false

例如:<input numeric="true"/>

phoneNumber#

指定输入框输入框是否为电话号码输入框。默认为false

例如:<input phoneNumber="true"/>

digits#

指定输入框可以输入的字符。例如,要指定输入框只能输入"1234567890+-",为<input digits="1234567890+-"/>

singleLine#

指定输入框是否为单行输入框。默认为false。您也可以通过lines="1"来指定单行输入框。

例如:<input singleLine="true"/>

图片控件: img#

图片控件用于显示来自网络、本地或者内嵌数据的图片,并可以指定图片以圆角矩形、圆形等显示。但是不能用于显示gif动态图。

这里只介绍他的主要方法和属性,如果要查看他的所有方法和属性,阅读ImageView

src#

使用一个Uri指定图片的来源。可以是图片的地址(http://....),本地路径(file://....)或者base64数据("data:image/png;base64,...")。

如果使用图片地址或本地路径,Auto.js会自动使用适当的缓存来储存这些图片,减少下次加载的时间。

例如,显示百度的logo:

"ui";
ui.layout(
    <frame>
        <img src="https://www.baidu.com/img/bd_logo1.png"/>
    </frame>
);

再例如,显示文件/sdcard/1.png的图片为 <img src="file:///sdcard/1.png"/>。 再例如,使base64显示一张钱包小图片为:

"ui";
ui.layout(
    <frame>
        <img w="40" h="40" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEu0lEQVRoge3bW4iVVRQH8N+ZnDKxvJUGCSWUlXYle/ChiKAkIiu7UXQjonwNIopM8cHoAhkRGQXdfIiE0Ep8KalQoptRTiFFZiRlOo6TPuSk4zk97G9w5vidc77LPjNi84f1MN+391rrf9a+rL32N4xiFMcUjouo5zyciYPYH0FnBadiNiZiD2oR9JbGRdgiOFPDIXRhCWYU0Dcj6duV6BrQuyWxNaLowBcOO1Uv+7EKc4WINUIlabMq6dNI35eJzRHDWOzS2MEB6cd6XI/OQf07k2frkzat9HQnNkcUG7R2dECq2I53EtmePMvaf+MwcWqKu+RzuqhUcfcwcWqKTvmiXFQ2GDodRhQz0aN9ZHsSG0cVrkGf+GT7MG8YeeTCHeKS7sOdMR1stjcWxY2YH0nXh1gdSdf/E+2I8KVYigkl9ewVUsxNpT1qMzaKN4ejJxrtyEt7IuraE1EX2jOkp+JBnFxSzz68KuTqoyiK2BHuxDO4NpK+j/GoOAWF6BiH98Q/SHyCycPIIxMm4FPZCPTj30SynIFr+A7ThotMK4wXopA1Ym9gSiKv5Oj3bdKnFMpuS514E1fm6NMnbF098s3NS4QS0Ik5+hyBsoSXYkGO9jvxy6C/t+IPIYJZcBWW57AXFfMNrSo2kqqw2l4hvSzcIRTw1sm24FVxb5s4NcR0/JXBuUNYJttI6sDjsi1kvTgrGpsMjq3O4FQNa+SbNhWsyKj7I4wpzSYDbpFtKB/EOSn9ZwpRfx5Xp7yfhN0Z9FdxXxxKjTEe2zI4U8NnKf3PNrT2VcWTKe1eyGjjT+Eapm14IqMjNTyd0n9JSrsDwhmaEN2H8GMOO8viUjyMSfJVJh9O0bGoQdt1eFm2oVwve7UpC1ssX568KEXH6fghp54s8lRkrk7CjpxOrGqg6wQ8IKSKWXPpVtIt8ly+v4ATf2t+yqlgDl5SbCjXy8JIXFXweQEHqngxo43JeEw54l+JVLKaJeypRZzoFxavrIWG6cKPW2SO9+PCMkQHsLiA8fpIv5/DmUn4qaCtpWWIEiLzdUHj9XJA2H5uFRbBZriuoI1NSpatpio+nJtFvFvYd2c1sDsGvxfQ3a/knrwgMtm0qD8rPSprCuq8uRmhVqvanBbvm+EQfsNKIcnvTmnTiUdwQcq73oJ2L2v2stXx6vyCRr8RDuk/C8OMUK24J6VtBaekPG81zxuh0TTJhC7FhtUOHF+n61whGalvu8uRWVJFvgPEYOkqQzhLVSPPXLoYa4Xh3Stcls1NaTdb8Xx7ZxnCvSUIfy/kzWno0Pyzx3dL2C0695Hto7NGUhXy5Lzp3kLZKiqNpNTl2+YShgdIvyXbVck44TB/oKTNzWUIv13S+IDsFmpY84QvZAcwTbh4e04o18SwtbIM4dsiOTFYVgzSv7wN+m9vRqjV/PrA0JuCox1bhYNKQ7Qi3CcU1fpiedRG9AkLXhRfbxCnKlET0s21ifwaSWcPbopBdDDOwGtClTD2vCsq+/C68K8HmVDk7DhFyIsvFzKnGThN+689+oU9dptwQb5B+LB8dx4lMb7xqAhkJwo/xljhFFSfSdUc3mPrcbwj15P+pP0/QiR7hYSkGsHnUYziWMF/mXV4JVcZ8G0AAAAASUVORK5CYII="/>
    </frame>
);

tint#

图片着色,其值是一个颜色名称或RGB颜色值。使用该属性会将图片中的非透明区域都涂上同一颜色。可以用于改变图片的颜色。

例如,对于上面的base64的图片: <img w="40" h="40" tint="red" src="data:image/png;base64,..."/>,则钱包图标颜色会变成红色。

scaleType#

控制图片根据图片控件的宽高放缩时的模式。可选的值为:

  • center 在控件中居中显示图像, 但不执行缩放。
  • centerCrop 保持图像的长宽比缩放图片, 使图像的尺寸 (宽度和高度) 等于或大于控件的相应尺寸 (不包括内边距padding)并且使图像在控件中居中显示。
  • centerInside 保持图像的长宽比缩放图片, 使图像的尺寸 (宽度和高度) 小于视图的相应尺寸 (不包括内边距padding)并且图像在控件中居中显示。
  • fitCenter 保持图像的长宽比缩放图片, 使图片的宽高和控件的宽高相同并使图片在控件中居中显示
  • fitEnd 保持图像的长宽比缩放图片, 使图片的宽高和控件的宽高相同并使图片在控件中靠右下角显示
  • fitStart 保持图像的长宽比缩放图片, 使图片的宽高和控件的宽高相同并使图片在控件靠左上角显示
  • fitXY 使图片和宽高和控件的宽高完全匹配,但图片的长宽比可能不能保持一致
  • matrix 绘制时使用图像矩阵进行缩放。需要在代码中使用setImageMatrix(Matrix)函数才能生效。

默认的scaleType为fitCenter;除此之外最常用的是fitXY, 他能使图片放缩到控件一样的大小,但图片可能会变形。

radius#

图片控件的半径。如果设置为控件宽高的一半并且控件的宽高相同则图片将剪切为圆形显示;否则图片为圆角矩形显示,半径即为四个圆角的半径,也可以通过radiusTopLeft, radiusTopRight, radiusBottomLeft, radiusBottomRight等属性分别设置四个圆角的半径。

例如,圆角矩形的Auto.js图标:<img w="100" h="100" radius="20" bg="white" src="http://www.autojs.org/assets/uploads/profile/3-profileavatar.png" />

有关该属性的单位,参见尺寸的单位: Dimension

radiusTopLeft#

图片控件的左上角圆角的半径。有关该属性的单位,参见尺寸的单位: Dimension

radiusTopRight#

图片控件的右上角圆角的半径。有关该属性的单位,参见尺寸的单位: Dimension

radiusBottomLeft#

图片控件的左下角圆角的半径。有关该属性的单位,参见尺寸的单位: Dimension

radiusBottomRight#

图片控件的右下角圆角的半径。有关该属性的单位,参见尺寸的单位: Dimension

borderWidth#

图片控件的边框宽度。用于在图片外面显示一个边框,边框会随着图片控件的外形(圆角等)改变而相应变化。 例如, 圆角矩形带灰色边框的Auto.js图标:<img w="100" h="100" radius="20" borderWidth="5" borderColor="gray" bg="white" src="http://www.autojs.org/assets/uploads/profile/3-profileavatar.png" />

borderColor#

图片控件的边框颜色。

circle#

指定该图片控件的图片是否剪切为圆形显示。如果为true,则图片控件会使其宽高保持一致(如果宽高不一致,则保持高度等于宽度)并使圆形的半径为宽度的一半。

例如,圆形的Auto.js图标:<img w="100" h="100" circle="true" bg="white" src="http://www.autojs.org/assets/uploads/profile/3-profileavatar.png" />

垂直布局: vertical#

垂直布局是一种比较简单的布局,会把在它里面的控件按照垂直方向依次摆放,如下图所示:

垂直布局:

—————

| 控件1 |

| 控件2 |

| 控件3 |

| ............ |

——————

layout_weight#

垂直布局中的控件可以通过layout_weight属性来控制控件高度占垂直布局高度的比例。如果为一个控件指定layout_weight, 则这个控件的高度=垂直布局剩余高度 * layout_weight / weightSum;如果不指定weightSum, 则weightSum为所有子控件的layout_weight之和。所谓"剩余高度",指的是垂直布局中减去没有指定layout_weight的控件的剩余高度。 例如:

"ui";
ui.layout(
    <vertical h="100dp">
        <text layout_weight="1" text="控件1" bg="#ff0000"/>
        <text layout_weight="1" text="控件2" bg="#00ff00"/>
        <text layout_weight="1" text="控件3" bg="#0000ff"/>
    </vertical>
);

在这个布局中,三个控件的layout_weight都是1,也就是他们的高度都会占垂直布局高度的1/3,都是33.3dp. 再例如:

"ui";
ui.layout(
    <vertical h="100dp">
        <text layout_weight="1" text="控件1" bg="#ff0000"/>
        <text layout_weight="2" text="控件2" bg="#00ff00"/>
        <text layout_weight="1" text="控件3" bg="#0000ff"/>
    </vertical>
);

在这个布局中,第一个控件高度为1/4, 第二个控件为2/4, 第三个控件为1/4. 再例如:

"ui";
ui.layout(
    <vertical h="100dp" weightSum="5">
        <text layout_weight="1" text="控件1" bg="#ff0000"/>
        <text layout_weight="2" text="控件2" bg="#00ff00"/>
        <text layout_weight="1" text="控件3" bg="#0000ff"/>
    </vertical>
);

在这个布局中,因为指定了weightSum为5, 因此第一个控件高度为1/5, 第二个控件为2/5, 第三个控件为1/5. 再例如:

"ui";
ui.layout(
    <vertical h="100dp">
        <text h="40dp" text="控件1" bg="#ff0000"/>
        <text layout_weight="2" text="控件2" bg="#00ff00"/>
        <text layout_weight="1" text="控件3" bg="#0000ff"/>
    </vertical>
);

在这个布局中,第一个控件并没有指定layout_weight, 而是指定高度为40dp, 因此不加入比例计算,此时布局剩余高度为60dp。第二个控件高度为剩余高度的2/3,也就是40dp,第三个控件高度为剩余高度的1/3,也就是20dp。

垂直布局的layout_weight属性还可以用于控制他的子控件高度占满剩余空间,例如:

"ui";
ui.layout(
    <vertical h="100dp">
        <text h="40dp" text="控件1" bg="#ff0000"/>
        <text h="40dp" text="控件2" bg="#00ff00"/>
        <text layout_weight="1" text="控件3" bg="#0000ff"/>
    </vertical>
);

在这个布局中,第三个控件的高度会占满除去控件1和控件2的剩余空间。

水平布局: horizontal#

水平布局是一种比较简单的布局,会把在它里面的控件按照水平方向依次摆放,如下图所示: 水平布局: ————————————————————————————

| 控件1 | 控件2 | 控件3 | ... |

————————————————————————————

layout_weight#

水平布局中也可以使用layout_weight属性来控制子控件的宽度占父布局的比例。和垂直布局中类似,不再赘述。

线性布局: linear#

实际上,垂直布局和水平布局都属于线性布局。线性布局有一个orientation的属性,用于指定布局的方向,可选的值为verticalhorizontal

例如<linear orientation="vertical"></linear>相当于<vertical></vertical>

线性布局的默认方向是横向的,因此,一个没有指定orientation属性的线性布局就是横向布局。

帧布局: frame#

帧布局

相对布局: relative#

勾选框控件: checkbox#

选择框控件: radio#

选择框布局: radiogroup#

开关控件: Switch#

开关控件用于表示一个选项是否被选中。

checked#

表示开关是否被选中。可选的值为:

  • true 打开开关
  • false 关闭开关

text#

对开关进行描述的文字。

进度条控件: progressbar#

拖动条控件: seekbar#

下来菜单控件: spinner#

时间选择控件: timepicker#

日期选择控件: datepicker#

浮动按钮控件: fab#

标题栏控件: toolbar#

卡片: card#

卡片控件是一个拥有圆角、阴影的控件。

cardBackgroundColor#

卡片的背景颜色。

cardCornerRadius#

卡片的圆角半径。

cardElevation#

设置卡片在z轴上的高度,来控制阴影的大小。

contentPadding#

设置卡片的内边距。该属性包括四个值:

  • contentPaddingLeft 左内边距
  • contentPaddingRight 右内边距
  • contentPaddingTop 上内边距
  • contentPaddingBottom 下内边距

foreground#

使用foreground="?selectableItemBackground"属性可以为卡片添加点击效果。

抽屉布局: drawer#

列表: list#

Tab: tab#

ui#

ui.layout(xml)#

  • xml <XML> | <string> 布局XML或者XML字符串

将布局XML渲染为视图(View)对象, 并设置为当前视图。

ui.layoutFile(xmlFile)#

此函数和ui.layout相似,只不过允许传入一个xml文件路径来渲染布局。

ui.inflate(xml[, parent = null, attachToParent = false])#

  • xml <string> | <XML> 布局XML或者XML字符串
  • parent <View> 父视图
  • attachToParent <boolean> 是否渲染的View加到父视图中,默认为false
  • 返回 <View>

将布局XML渲染为视图(View)对象。如果该View将作为某个View的子View,我们建议传入parent参数,这样在渲染时依赖于父视图的一些布局属性能够正确应用。

此函数用于动态创建、显示View。

"ui";

$ui.layout(
    <linear id="container">
    </linear>
);

// 动态创建3个文本控件,并加到container容器中
// 这里仅为实例,实际上并不推荐这种做法,如果要展示列表,
// 使用list组件;动态创建十几个、几十个View会让界面卡顿
for (let i = 0; i < 3; i++) {
    let textView = $ui.inflate(
        <text textColor="#000000" textSize="14sp"/>
    , $ui.container);
    textView.attr("text", "文本控件" + i);
    $ui.container.addView(textView);
}

ui.registerWidget(name, widget)#

注册一个自定义组件。参考示例->界面控件->自定义控件。

ui.isUiThread()#

返回当前线程是否是UI线程。

"ui";

log($ui.isUiThread()); // => true

$threads.start(function () {
    log($ui.isUiThread()); // => false
});

ui.findView(id)#

在当前视图中根据ID查找相应的视图对象并返回。如果当前未设置视图或找不到此ID的视图时返回null

一般我们都是通过ui.xxx来获取id为xxx的控件,如果xxx是一个ui已经有的属性,就可以通过$ui.findView()来获取这个控件。

ui.finish()#

结束当前活动并销毁界面。

ui.setContentView(view)#

  • view <View>

将视图对象设置为当前视图。

ui.post(callback[, delay = 0])#

callback加到UI线程的消息循环中,并延迟delay毫秒后执行(不能准确保证一定在delay毫秒后执行)。

此函数可以用于UI线程中延时执行动作(sleep不能在UI线程中使用),也可以用于子线程中更新UI。

"ui";

ui.layout(
    <frame>
        <text id="result"/>
    </frame>
);

ui.result.attr("text", "计算中");
// 在子线程中计算1+ ... + 10000000
threads.start({
    let sum = 0;
    for (let i = 0; i < 1000000; i++) {
        sum += i;
    }
    // 由于不能在子线程操作UI,所以要抛到UI线程执行
    ui.post(() => {
        ui.result.attr("text", String(sum));
    });
});

ui.run(callback)#

  • callback <Function> 回调函数
  • 返回 callback的执行结果

callback在UI线程中执行。如果当前已经在UI线程中,则直接执行callback;否则将callback抛到UI线程中执行(加到UI线程的消息循环的末尾),并等待callback执行结束(阻塞当前线程)

ui.statusBarColor(color)#

设置当前界面的状态栏颜色。

"ui";
ui.statusBarColor("#000000");

ui.useAndroidResources()#

启用使用Android的布局(layout)、绘图(drawable)、动画(anim)、样式(style)等资源的特性。启用该特性后,在project.json中进行以下配置,就可以像写Android原生一样写界面:

{
    // ...
    androidResources: {
        "resDir": "res",  // 资源文件夹
        "manifest": "AndroidManifest.xml" // AndroidManifest文件路径
    }
}

res文件夹通常为以下结构:

- res
    - layout  // 布局资源
    - drawable // 图片、形状等资源
    - menu // 菜单资源
    - values // 样式、字符串等资源
    // ...

可参考示例->复杂界面->Android原生界面。

尺寸的单位: Dimension#

Drawables#

颜色#

(完善中...)

基于控件的操作#

基于控件的操作指的是选择屏幕上的控件,获取其信息或对其进行操作。对于一般软件而言,基于控件的操作对不同机型有很好的兼容性;但是对于游戏而言,由于游戏界面并不是由控件构成,无法采用本章节的方法,也无法使用本章节的函数。有关游戏脚本的编写,请参考《基于坐标的操作》。

基于控件的操作依赖于无障碍服务,因此最好在脚本开头使用auto()函数来确保无障碍服务已经启用。如果运行到某个需要权限的语句无障碍服务并没启动,则会抛出异常并跳转到无障碍服务界面。这样的用户体验并不好,因为需要重新运行脚本,后续会加入等待无障碍服务启动并让脚本继续运行的函数。

您也可以在脚本开头使用"auto";表示这个脚本需要无障碍服务,但是不推荐这种做法,因为这个标记必须在脚本的最开头(前面不能有注释或其他语句、空格等),我们推荐使用auto()函数来确保无障碍服务已启用。

auto([mode])#

检查无障碍服务是否已经启用,如果没有启用则抛出异常并跳转到无障碍服务启用界面;同时设置无障碍模式为mode。mode的可选值为:

  • fast 快速模式。该模式下会启用控件缓存,从而选择器获取屏幕控件更快。对于需要快速的控件操作的脚本可以使用该模式,一般脚本则没有必要使用该函数。
  • normal 正常模式,默认。

如果不加mode参数,则为正常模式。

建议使用auto.waitFor()auto.setMode()代替该函数,因为auto()函数如果无障碍服务未启动会停止脚本;而auto.waitFor()则会在在无障碍服务启动后继续运行。

示例:

auto("fast");

示例2:

auto();

auto.waitFor()#

检查无障碍服务是否已经启用,如果没有启用则跳转到无障碍服务启用界面,并等待无障碍服务启动;当无障碍服务启动后脚本会继续运行。

因为该函数是阻塞的,因此除非是有协程特性,否则不能在ui模式下运行该函数,建议在ui模式下使用auto()函数。

auto.setMode(mode)#

设置无障碍模式为mode。mode的可选值为:

  • fast 快速模式。该模式下会启用控件缓存,从而选择器获取屏幕控件更快。对于需要快速的控件查看和操作的脚本可以使用该模式,一般脚本则没有必要使用该函数。
  • normal 正常模式,默认。

auto.setFlags(flags)#

[v4.1.0新增]

  • flags <string> | <Array> 一些标志,来启用和禁用某些特性,包括:
    • findOnUiThread 使用该特性后,选择器搜索时会在主进程进行。该特性用于解决线程安全问题导致的次生问题,不过目前貌似已知问题并不是线程安全问题。
    • useUsageStats 使用该特性后,将会以"使用情况统计"服务的结果来检测当前正在运行的应用包名(需要授予"查看使用情况统计"权限)。如果觉得currentPackage()返回的结果不太准确,可以尝试该特性。
    • useShell 使用该特性后,将使用shell命令获取当前正在运行的应用的包名、活动名称,但是需要root权限。

启用有关automator的一些特性。例如:

auto.setFlags(["findOnUiThread", "useShell"]);

auto.serivce#

[v4.1.0新增]

获取无障碍服务。如果无障碍服务没有启动,则返回null

参见AccessibilityService

auto.windows#

[v4.1.0新增]

当前所有窗口(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新增]

设置窗口过滤器。这个过滤器可以决定哪些窗口是目标窗口,并影响选择器的搜索。例如,如果想要选择器在所有窗口(包括状态栏、输入法等)中搜索,只需要使用以下代码:

auto.setWindowFilter(function(window){
    //不管是如何窗口,都返回true,表示在该窗口中搜索
    return true;
});

又例如,当前使用了分屏功能,屏幕上有Auto.js和QQ两个应用,但我们只想选择器对QQ界面进行搜索,则:

auto.setWindowFilter(function(window){
    // 对于应用窗口,他的title属性就是应用的名称,因此可以通过title属性来判断一个应用
    return window.title == "QQ";
});

选择器默认是在当前活跃的窗口中搜索,不会搜索诸如悬浮窗、状态栏之类的,使用WindowFilter则可以控制搜索的窗口。

需要注意的是, 如果WindowFilter返回的结果均为false,则选择器的搜索结果将为空。

另外setWindowFilter函数也会影响auto.windowRoots的结果。

该函数需要Android 5.0以上才有效。

auto.windowRoots#

[v4.1.0新增]

返回当前被WindowFilter过滤的窗口的布局根元素组成的数组。

如果系统是Android5.0以下,则始终返回当前活跃的窗口的布局根元素的数组。

SimpleActionAutomator#

Stability: 2 - Stable

SimpleActionAutomator提供了一些模拟简单操作的函数,例如点击文字、模拟按键等。这些函数可以直接作为全局函数使用。

click(text[, i])#

  • text <string> 要点击的文本
  • i <number> 如果相同的文本在屏幕中出现多次,则i表示要点击第几个文本, i从0开始计算

返回是否点击成功。当屏幕中并未包含该文本,或者该文本所在区域不能点击时返回false,否则返回true。

该函数可以点击大部分包含文字的按钮。例如微信主界面下方的"微信", "联系人", "发现", "我"的按钮。
通常与while同时使用以便点击按钮直至成功。例如:

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+1个可滑动控件上滑或左滑。返回是否操作成功。屏幕上没有可滑动的控件时返回false。

另外不加参数时scrollUp()会寻找面积最大的可滑动的控件上滑或左滑,例如微信消息列表等。

参数为一个整数i时会找到第i + 1个可滑动控件滑动。例如scrollUp(0)为滑动第一个可滑动控件。

scrollDown([i])#

找到第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("测试")

UiSelector#

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属性为true
  • longClick() 长按。长按一个控件,前提是这个控件的longClickable属性为true
  • setText() 设置文本,用于编辑框控件设置文本。
  • scrollForward(), scrollBackward() 滑动。滑动一个控件(列表等), 前提是这个控件的scrollable属性为true
  • exits() 判断控件是否存在
  • 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()为向前滑,包括下滑和右滑。

选择器的入门教程暂且要这里,更多信息可以查看下面的文档和选择器进阶。

selector()#

  • 返回 <UiSelector>

创建一个新的选择器。但一般情况不需要使用该函数,因为可以直接用相应条件的语句创建选择器。

由于历史遗留原因,本不应该这样设计(不应该让id(), text()等作为全局函数,而是应该用By.id(), By.text()),但为了后向兼容性只能保留这个设计。

这样的API设计会污染全局变量,后续可能会支持"去掉这些全局函数而使用By.***"的选项。

UiSelector.algorithm(algorithm)#

[v4.1.0新增]

  • algorithm <string> 搜索算法,可选的值有:
    • DFS 深度优先算法,选择器的默认算法
    • BFS 广度优先算法

指定选择器的搜索算法。例如:

log(selector().text("文本").algorithm("BFS").find());

广度优先在控件所在层次较低时,或者布局的层次不多时,通常能更快找到控件。

UiSelector.text(str)#

  • str <string> 控件文本
  • 返回 <UiSelector> 返回选择器自身以便链式调用

为当前选择器附加控件"text等于字符串str"的筛选条件。

控件的text(文本)属性是文本控件上的显示的文字,例如微信左上角的"微信"文本。

UiSelector.textContains(str)#

为当前选择器附加控件"text需要包含字符串str"的筛选条件。

这是一个比较有用的条件,例如QQ动态页和微博发现页上方的"大家都在搜...."的控件可以用textContains("大家都在搜").findOne()来获取。

UiSelector.textStartsWith(prefix)#

为当前选择器附加控件"text需要以prefix开头"的筛选条件。

这也是一个比较有用的条件,例如要找出Auto.js脚本列表中名称以"QQ"开头的脚本的代码为textStartsWith("QQ").find()

UiSelector.textEndsWith(suffix)#

为当前选择器附加控件"text需要以suffix结束"的筛选条件。

UiSelector.textMatches(reg)#

  • reg <string> | <Regex> 要满足的正则表达式。

为当前选择器附加控件"text需要满足正则表达式reg"的条件。

有关正则表达式,可以查看正则表达式 - 菜鸟教程

需要注意的是,如果正则表达式是字符串,则需要使用\\来表达\(也即Java正则表达式的形式),例如textMatches("\\d+")匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如textMatches(/\d+/)。但如果使用字符串的正则表达式则该字符串不能以"/"同时以"/"结束,也即不能写诸如textMatches("/\\d+/")的表达式,否则会被开头的"/"和结尾的"/"会被忽略。

UiSelector.desc(str)#

  • str <string> 控件文本
  • 返回 <UiSelector> 返回选择器自身以便链式调用

为当前选择器附加控件"desc等于字符串str"的筛选条件。

控件的desc(描述,全称为Content-Description)属性是对一个控件的描述,例如网易云音乐右上角的放大镜图标的描述为搜索。要查看一个控件的描述,同样地可以借助悬浮窗查看。

desc属性同样是定位控件的利器。

UiSelector.descContains(str)#

为当前选择器附加控件"desc需要包含字符串str"的筛选条件。

UiSelector.descStartsWith(prefix)#

为当前选择器附加控件"desc需要以prefix开头"的筛选条件。

UiSelector.descEndsWith(suffix)#

为当前选择器附加控件"desc需要以suffix结束"的筛选条件。

UiSelector.descMatches(reg)#

  • reg <string> | <Regex> 要满足的正则表达式。

为当前选择器附加控件"desc需要满足正则表达式reg"的条件。

有关正则表达式,可以查看正则表达式 - 菜鸟教程

需要注意的是,如果正则表达式是字符串,则需要使用\\来表达\(也即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"),在QQ界面想当于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)#

为当前选择器附加控件"id包含字符串str"的筛选条件。比较少用。

UiSelector.idStartsWith(prefix)#

为当前选择器附加"id需要以prefix开头"的筛选条件。比较少用。

UiSelector.idEndsWith(suffix)#

为当前选择器附加"id需要以suffix结束"的筛选条件。比较少用。

UiSelector.idMatches(reg)#

  • reg <Regex> | <string> id要满足的正则表达式

附加id需要满足正则表达式。

需要注意的是,如果正则表达式是字符串,则需要使用\\来表达\(也即Java正则表达式的形式),例如textMatches("\\d+")匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如textMatches(/\d+/)。但如果使用字符串的正则表达式则该字符串不能以"/"同时以"/"结束,也即不能写诸如textMatches("/\\d+/")的表达式,否则会被开头的"/"和结尾的"/"会被忽略。

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)#

为当前选择器附加控件"className需要包含字符串str"的筛选条件。

UiSelector.classNameStartsWith(prefix)#

为当前选择器附加控件"className需要以prefix开头"的筛选条件。

UiSelector.classNameEndsWith(suffix)#

为当前选择器附加控件"className需要以suffix结束"的筛选条件。

UiSelector.classNameMatches(reg)#

  • reg <string> | <Regex> 要满足的正则表达式。

为当前选择器附加控件"className需要满足正则表达式reg"的条件。

有关正则表达式,可以查看正则表达式 - 菜鸟教程

需要注意的是,如果正则表达式是字符串,则需要使用\\来表达\(也即Java正则表达式的形式),例如textMatches("\\d+")匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如textMatches(/\d+/)。但如果使用字符串的正则表达式则该字符串不能以"/"同时以"/"结束,也即不能写诸如textMatches("/\\d+/")的表达式,否则会被开头的"/"和结尾的"/"会被忽略。

UiSelector.packageName(str)#

  • str <string> 控件文本
  • 返回 <UiSelector> 返回选择器自身以便链式调用

为当前选择器附加控件"packageName等于字符串str"的筛选条件。

控件的packageName表示控件所属界面的应用包名。例如微信的包名为"com.tencent.mm", 那么微信界面的控件的packageName为"com.tencent.mm"。

要查看一个应用的包名,可以用函数app.getPackageName()获取,例如toast(app.getPackageName("微信"))

UiSelector.packageNameContains(str)#

为当前选择器附加控件"packageName需要包含字符串str"的筛选条件。

UiSelector.packageNameStartsWith(prefix)#

为当前选择器附加控件"packageName需要以prefix开头"的筛选条件。

UiSelector.packageNameEndsWith(suffix)#

为当前选择器附加控件"packageName需要以suffix结束"的筛选条件。

UiSelector.packageNameMatches(reg)#

  • reg <string> | <Regex> 要满足的正则表达式。

为当前选择器附加控件"packageName需要满足正则表达式reg"的条件。

有关正则表达式,可以查看正则表达式 - 菜鸟教程

UiSelector.bounds(left, top, right, buttom)#

  • 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, buttom)#

  • left <number> 范围左边缘与屏幕左边的距离
  • top <number> 范围上边缘与屏幕上边的距离
  • right <number> 范围右边缘与屏幕左边的距离
  • bottom <number> 范围下边缘与屏幕上边的距离

为当前选择器附加控件"bounds需要在left, top, right, buttom构成的范围里面"的条件。

这个条件用于限制选择器在某一个区域选择控件。例如要在屏幕上半部分寻找文本控件TextView,代码为:

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, buttom)#

  • left <number> 范围左边缘与屏幕左边的距离
  • top <number> 范围上边缘与屏幕上边的距离
  • right <number> 范围右边缘与屏幕左边的距离
  • bottom <number> 范围下边缘与屏幕上边的距离

为当前选择器附加控件"bounds需要包含left, top, right, buttom构成的范围"的条件。

这个条件用于限制控件的范围必须包含所给定的范围。例如给定一个点(500, 300), 寻找在这个点上的可点击控件的代码为:

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])#

为当前选择器附加控件是否可点击的条件。但并非所有clickable为false的控件都真的不能点击,这取决于控件的实现。对于自定义控件(例如显示类名为android.view.View的控件)很多的clickable属性都为false都却能点击。

需要注意的是,可以省略参数b而表示选择那些可以点击的控件,例如className("ImageView").clickable()表示可以点击的图片控件的条件,className("ImageView").clickable(false)表示不可点击的图片控件的条件。

UiSelector.longClickable([b = true])#

为当前选择器附加控件是否可长按的条件。

UiSelector.checkable([b = true])#

为当前选择器附加控件是否可勾选的条件。勾选通常是对于勾选框而言的,例如图片多选时左上角通常有一个勾选框。

UiSelector.selected([b = true])#

为当前选择器附加控件是否已选中的条件。被选中指的是,例如QQ聊天界面点击下方的"表情按钮"时,会出现自己收藏的表情,这时"表情按钮"便处于选中状态,其selected属性为true。

UiSelector.enabled([b = true])#

为当前选择器附加控件是否已启用的条件。大多数控件都是启用的状态(enabled为true),处于“禁用”状态通常是灰色并且不可点击。

UiSelector.scrollable([b = true])#

为当前选择器附加控件是否可滑动的条件。滑动包括上下滑动和左右滑动。

可以用这个条件来寻找可滑动控件来滑动界面。例如滑动Auto.js的脚本列表的代码为:

className("android.support.v7.widget.RecyclerView").scrollable().findOne().scrollForward();
//或者classNameEndsWith("RecyclerView").scrollable().findOne().scrollForward();

UiSelector.editable([b = true])#

为当前选择器附加控件是否可编辑的条件。一般来说可编辑的控件为输入框(EditText),但不是所有的输入框(EditText)都可编辑。

UiSelector.multiLine([b = true])#

  • b <Boolean> 表示文本或输入框控件是否是多行显示的

为当前选择器附加控件是否文本或输入框控件是否是多行显示的条件。

UiSelector.findOne()#

根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件。如果找不到控件,当屏幕内容发生变化时会重新寻找,直至找到。

需要注意的是,如果屏幕上一直没有出现所描述的控件,则该函数会阻塞,直至所描述的控件出现为止。因此此函数不会返回null

该函数本来应该命名为untilFindOne(),但由于历史遗留原因已经无法修改。如果想要只在屏幕上搜索一次而不是一直搜索,请使用findOnce()

另外,如果屏幕上有多个满足条件的控件,findOne()采用深度优先搜索(DFS),会返回该搜索算法找到的第一个控件。注意控件找到的顺序有时会起到作用。

UiSelector.findOne(timeout)#

根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件;如果在timeout毫秒的时间内没有找到符合条件的控件,则终止搜索并返回null

该函数类似于不加参数的findOne(),只不过加上了时间限制。

示例:

//启动Auto.js
launchApp("Auto.js");
//在6秒内找出日志图标的控件
var w = id("action_log").findOne(6000);
//如果找到控件则点击
if(w != null){
    w.click();
}else{
    //否则提示没有找到
    toast("没有找到日志图标");
}

UiSelector.findOnce()#

根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,如果找到符合条件的控件则返回该控件;否则返回null

UiSelector.findOnce(i)#

根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,并返回第 i + 1 个符合条件的控件;如果没有找到符合条件的控件,或者符合条件的控件个数 < i, 则返回null

注意这里的控件次序,是搜索算法深度优先搜索(DSF)决定的。

UiSelector.find()#

根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,找到所有满足条件的控件集合并返回。这个搜索只进行一次,并不保证一定会找到,因而会出现返回的控件集合为空的情况。

不同于findOne()或者findOnce()只找到一个控件并返回一个控件,find()函数会找出所有满足条件的控件并返回一个控件集合。之后可以对控件集合进行操作。

可以通过empty()函数判断找到的是否为空。例如:

var c = className("AbsListView").find();
if(c.empty()){
    toast("找到啦");
}else{
    toast("没找到╭(╯^╰)╮");
}

UiSelector.untilFind()#

根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到找到至少一个满足条件的控件为止,并返回所有满足条件的控件集合。

该函数与find()函数的区别在于,该函数永远不会返回空集合;但是,如果屏幕上一直没有出现满足条件的控件,则该函数会保持阻塞。

UiSelector.exists()#

判断屏幕上是否存在控件符合选择器所确定的条件。例如要判断某个文本出现就执行某个动作,可以用:

if(text("某个文本").exists()){
    //要支持的动作
}

UiSelector.waitFor()#

等待屏幕上出现符合条件的控件;在满足该条件的控件出现之前,该函数会一直保持阻塞。

例如要等待包含"哈哈哈"的文本控件出现的代码为:

textContains("哈哈哈").waitFor();

UiSelector.filter(f)#

  • f <Function> 过滤函数,参数为UiObject,返回值为boolean

为当前选择器附加自定义的过滤条件。

例如,要找出屏幕上所有文本长度为10的文本控件的代码为:

var uc = className("TextView").filter(function(w){
    return w.text().length == 10;
});

UiObject#

UiObject表示一个控件,可以通过这个对象获取到控件的属性,也可以对控件进行点击、长按等操作。

获取一个UiObject通常通过选择器的findOne(), findOnce()等函数,也可以通过UiCollection来获取,或者通过UiObject.child(), UiObject.parent()等函数来获取一个控件的子控件或父控件。

UiObject.click()#

点击该控件,并返回是否点击成功。

如果该函数返回false,可能是该控件不可点击(clickable为false),当前界面无法响应该点击等。

UiObject.longClick()#

长按该控件,并返回是否点击成功。

如果该函数返回false,可能是该控件不可点击(longClickable为false),当前界面无法响应该点击等。

UiObject.setText(text)#

设置输入框控件的文本内容,并返回是否设置成功。

该函数只对可编辑的输入框(editable为true)有效。

UiObject.copy()#

对输入框文本的选中内容进行复制,并返回是否操作成功。

该函数只能用于输入框控件,并且当前输入框控件有选中的文本。可以通过setSelection()函数来设置输入框选中的内容。

var et = className("EditText").findOne();
//选中前两个字
et.setSelection(0, 2);
//对选中内容进行复制
if(et.copy()){
    toast("复制成功");
}else{
    toast("复制失败");
}

UiObject.cut()#

对输入框文本的选中内容进行剪切,并返回是否操作成功。

该函数只能用于输入框控件,并且当前输入框控件有选中的文本。可以通过setSelection()函数来设置输入框选中的内容。

UiObject.paste()#

对输入框控件进行粘贴操作,把剪贴板内容粘贴到输入框中,并返回是否操作成功。

//设置剪贴板内容为“你好”
setClip("你好");
var et = className("EditText").findOne();
et.paste();

UiObject.setSelection(start, end)#

对输入框控件设置选中的文字内容,并返回是否操作成功。

索引是从0开始计算的;并且,选中内容不包含end位置的字符。例如,如果一个输入框内容为"123456789",要选中"4567"的文字的代码为et.setSelection(3, 7)

该函数也可以用来设置光标位置,只要参数的end等于start,即可把输入框光标设置在start的位置。例如et.setSelection(1, 1)会把光标设置在第一个字符的后面。

UiObject.scrollForward()#

对控件执行向前滑动的操作,并返回是否操作成功。

向前滑动包括了向右和向下滑动。如果一个控件既可以向右滑动和向下滑动,那么执行scrollForward()的行为是未知的(这是因为Android文档没有指出这一点,同时也没有充分的测试可供参考)。

UiObject.scrollBackward()#

对控件执行向后滑动的操作,并返回是否操作成功。

向后滑动包括了向右和向下滑动。如果一个控件既可以向右滑动和向下滑动,那么执行scrollForward()的行为是未知的(这是因为Android文档没有指出这一点,同时也没有充分的测试可供参考)。

UiObject.select()#

对控件执行"选中"操作,并返回是否操作成功。"选中"和isSelected()的属性相关,但该操作十分少用。

UiObject.collapse()#

对控件执行折叠操作,并返回是否操作成功。

UiObject.expand()#

对控件执行操作,并返回是否操作成功。

UiObject.show()#

对集合中所有控件执行显示操作,并返回是否全部操作成功。

UiObject.scrollUp()#

对集合中所有控件执行向上滑的操作,并返回是否全部操作成功。

UiObject.scrollDown()#

对集合中所有控件执行向下滑的操作,并返回是否全部操作成功。

UiObject.scrollLeft()#

对集合中所有控件执行向左滑的操作,并返回是否全部操作成功。

UiObject.scrollRight()#

children()#

返回该控件的所有子控件组成的控件集合。可以用于遍历一个控件的子控件,例如:

className("AbsListView").findOne().children()
    .forEach(function(child){
        log(child.className());
    });

childCount()#

返回子控件数目。

child(i)#

  • i <number> 子控件索引
  • 返回 <UiObject>

返回第i+1个子控件。如果i>=控件数目或者小于0,则抛出异常。

需要注意的是,由于布局捕捉的问题,该函数可能返回null,也就是可能获取不到某个子控件。

遍历子控件的示例:

var list = className("AbsListView").findOne();
for(var i = 0; i < list.childCount(); i++){
    var child = list.child(i);
    log(child.className());
}

parent()#

  • 返回 <UiObject>

返回该控件的父控件。如果该控件没有父控件,返回null

bounds()#

返回控件在屏幕上的范围,其值是一个Rect对象。

示例:

var b = text("Auto.js").findOne().bounds();
toast("控件在屏幕上的范围为" + b);

如果一个控件本身无法通过click()点击,那么我们可以利用bounds()函数获取其坐标,再利用坐标点击。例如:

var b = desc("打开侧拉菜单").findOne().bounds();
click(b.centerX(), b.centerY());
//如果使用root权限,则用 Tap(b.centerX(), b.centerY());

boundsInParent()#

返回控件在父控件中的范围,其值是一个Rect对象。

drawingOrder()#

返回控件在父控件中的绘制次序。该函数在安卓7.0及以上才有效,7.0以下版本调用会返回0。

id()#

获取控件的id,如果一个控件没有id,则返回null

text()#

获取控件的文本,如果控件没有文本,返回""

findByText(str)#

根据文本text在子控件中递归地寻找并返回文本或描述(desc)包含这段文本str的控件,返回它们组成的集合。

该函数会在当前控件的子控件,孙控件,曾孙控件...中搜索text或desc包含str的控件,并返回它们组合的集合。

findOne(selector)#

根据选择器selector在该控件的子控件、孙控件...中搜索符合该选择器条件的控件,并返回找到的第一个控件;如果没有找到符合条件的控件则返回null

例如,对于酷安动态列表,我们可以遍历他的子控件(每个动态列表项),并在每个子控件中依次寻找点赞数量和图标,对于点赞数量小于10的点赞:

//找出动态列表
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();
    }
});

find(selector)#

根据选择器selector在该控件的子控件、孙控件...中搜索符合该选择器条件的控件,并返回它们组合的集合。

UiCollection#

UiCollection, 控件集合, 通过选择器的find(), untilFind()方法返回的对象。

UiCollection"继承"于数组,实际上是一个UiObject的数组,因此可以使用数组的函数和属性,例如使用length属性获取UiCollection的大小,使用forEach函数来遍历UiCollection。

例如,采用forEach遍历屏幕上所有的文本控件并打印出文本内容的代码为:

console.show();
className("TextView").find().forEach(function(tv){
    if(tv.text() != ""){
        log(tv.text());
    }
});

也可以使用传统的数组遍历方式:

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()#

返回集合中的控件数。

历史遗留函数,相当于属性length。

UiCollection.get(i)#

返回集合中第i+1个控件(UiObject)。

历史遗留函数,建议直接使用数组下标的方式访问元素。

UiCollection.each(func)#

  • func <Function> 遍历函数,参数为UiObject。

遍历集合。

历史遗留函数,相当于forEach。参考forEach

empty()#

返回控件集合是否为空。

nonEmpty()#

返回控件集合是否非空。

UiCollection.find(selector)#

根据selector所确定的条件在该控件集合的控件、子控件、孙控件...中找到所有符合条件的控件并返回找到的控件集合。

注意这会递归地遍历控件集合里所有的控件以及他们的子控件。和数组的filter函数不同。

例如:

var names = id("name").find();
//在集合
var clickableNames = names.find(clickable());

UiCollection.findOne(selector)#

根据选择器selector在该控件集合的控件的子控件、孙控件...中搜索符合该选择器条件的控件,并返回找到的第一个控件;如果没有找到符合条件的控件则返回null

Rect#

UiObject.bounds(), UiObject.boundsInParent()返回的对象。表示一个长方形(范围)。

Rect.left#

长方形左边界的x坐标、

Rect.right#

长方形右边界的x坐标、

Rect.top#

长方形上边界的y坐标、

Rect.bottom#

长方形下边界的y坐标、

Rect.centerX()#

长方形中点x坐标。

Rect.centerY()#

长方形中点y坐标。

Rect.width()#

长方形宽度。通常可以作为控件宽度。

Rect.height()#

长方形高度。通常可以作为控件高度。

Rect.contains(r)#

返回是否包含另一个长方形r。包含指的是,长方形r在该长方形的里面(包含边界重叠的情况)。

Rect.intersect(r)#

返回是否和另一个长方形相交。

UiSelector进阶#

未完待续。