Stability: 2 - Stable
images模块提供了一些手机设备中常见的图片处理函数,包括截图、读写图片、图片剪裁、旋转、二值化、找色找图等。
该模块分为两个部分,找图找色部分和图片处理部分。
需要注意的是,image对象创建后尽量在不使用时进行回收,同时避免循环创建大量图片。因为图片是一种占用内存比较大的资源,尽管Auto.js通过各种方式(比如图片缓存机制、垃圾回收时回收图片、脚本结束时回收所有图片)尽量降低图片资源的泄漏和内存占用,但是糟糕的代码仍然可以占用大量内存。
Image对象通过调用recycle()函数来回收。例如:
// 读取图片
var img = images.read("./1.png");
//对图片进行操作
...
// 回收图片
img.recycle();
例外的是,caputerScreen()返回的图片不需要回收。
读取在路径path的图片文件并返回一个Image对象。如果文件不存在或者文件无法解码则返回null。
加载在地址URL的网络图片并返回一个Image对象。如果地址不存在或者图片无法解码则返回null。
复制一张图片并返回新的副本。该函数会完全复制img对象的数据。
把图片image以PNG格式保存到path中。如果文件不存在会被创建;文件存在会被覆盖。
//把图片压缩为原来的一半质量并保存
var img = images.read("/sdcard/1.png");
images.save(img, "/sdcard/1.jpg", "jpg", 50);
app.viewFile("/sdcard/1.jpg");
解码Base64数据并返回解码后的图片Image对象。如果base64无法解码则返回null。
把图片编码为base64数据并返回。
解码字节数组bytes并返回解码后的图片Image对象。如果bytes无法解码则返回null。
把图片编码为字节数组并返回。
从图片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");
[v4.1.0新增]
调整图片大小,并返回调整后的图片。例如把图片放缩为200*300:images.resize(img, [200, 300])。
参见Imgproc.resize。
[v4.1.0新增]
放缩图片,并返回放缩后的图片。例如把图片变成原来的一半:images.scale(img, 0.5, 0.5)。
参见Imgproc.resize。
[v4.1.0新增]
将图片逆时针旋转degress度,返回旋转后的图片对象。
例如逆时针旋转90度为images.rotate(img, 90)。
[v4.1.0新增]
连接两张图片,并返回连接后的图像。如果两张图片大小不一致,小的那张将适当居中。
[v4.1.0新增]
灰度化图片,并返回灰度化后的图片。
[v4.1.0新增]
将图片阈值化,并返回处理后的图像。可以用这个函数进行图片二值化。例如:images.threshold(img, 100, 255, "BINARY"),这个代码将图片中大于100的值全部变成255,其余变成0,从而达到二值化的效果。如果img是一张灰度化图片,这个代码将会得到一张黑白图片。
可以参考有关博客(比如threshold函数的使用)或者OpenCV文档threshold。
[v4.1.0新增]
对图片进行自适应阈值化处理,并返回处理后的图像。
可以参考有关博客(比如threshold与adaptiveThreshold)或者OpenCV文档adaptiveThreshold。
[v4.1.0新增]
对图像进行颜色空间转换,并返回转换后的图像。
可以参考有关博客(比如颜色空间转换)或者OpenCV文档cvtColor。
[v4.1.0新增]
将图片二值化,在lowerBound~upperBound范围以外的颜色都变成0,在范围以内的颜色都变成255。
例如images.inRange(img, "#000000", "#222222")。
[v4.1.0新增]
将图片二值化,在color-interval ~ color+interval范围以外的颜色都变成0,在范围以内的颜色都变成255。这里对color的加减是对每个通道而言的。
例如images.interval(img, "#888888", 16),每个通道的颜色值均为0x88,加减16后的范围是[0x78, 0x98],因此这个代码将把#787878~#989898的颜色变成#FFFFFF,而把这个范围以外的变成#000000。
[v4.1.0新增]
对图像进行模糊(平滑处理),返回处理后的图像。
可以参考有关博客(比如实现图像平滑处理)或者OpenCV文档blur。
[v4.1.0新增]
对图像进行中值滤波,返回处理后的图像。
可以参考有关博客(比如实现图像平滑处理)或者OpenCV文档blur。
[v4.1.0新增]
对图像进行高斯模糊,返回处理后的图像。
可以参考有关博客(比如实现图像平滑处理)或者OpenCV文档GaussianBlur。
[v4.1.0新增]
把Mat对象转换为Image对象。
向系统申请屏幕截图权限,返回是否请求成功。
第一次使用该函数会弹出截图权限请求,建议选择“总是允许”。
这个函数只是申请截图权限,并不会真正执行截图,真正的截图函数是captureScreen()。
该函数在截图脚本中只需执行一次,而无需每次调用captureScreen()都调用一次。
如果不指定landscape值,则截图方向由当前设备屏幕方向决定,因此务必注意执行该函数时的屏幕方向。
建议在本软件界面运行该函数,在其他软件界面运行时容易出现一闪而过的黑屏现象。
示例:
//请求截图
if(!requestScreenCapture()){
toast("请求截图失败");
exit();
}
//连续截图10张图片(间隔1秒)并保存到存储卡目录
for(var i = 0; i < 10; i++){
captureScreen("/sdcard/screencapture" + i + ".png");
sleep(1000);
}
该函数也可以作为全局函数使用。
截取当前屏幕并返回一个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));
该函数也可以作为全局函数使用。
截取当前屏幕并以PNG格式保存到path中。如果文件不存在会被创建;文件存在会被覆盖。
该函数不会返回任何值。该函数也可以作为全局函数使用。
返回图片image在点(x, y)处的像素的ARGB值。
该值的格式为0xAARRGGBB,是一个"32位整数"(虽然JavaScript中并不区分整数类型和其他数值类型)。
坐标系以图片左上角为原点。以图片左侧边为y轴,上侧边为x轴。
在图片中寻找颜色color。找到时返回找到的点Point,找不到时返回null。
选项包括:
该函数也可以作为全局函数使用。
一个循环找色的例子如下:
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.findColor(img, color, {
region: [x, y, width, height],
threshold: threshold
});
该函数也可以作为全局函数使用。
在图片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, "#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]
});
返回图片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();
}
找图。在大图片img中查找小图片template的位置(模块匹配),找到时返回位置坐标(Point),找不到时返回null。
选项包括:
该函数也可以作为全局函数使用。
一个最简单的找图例子如下:
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.findImage(img, template, {
region: [x, y, width, height],
threshold: threshold
})
该函数也可以作为全局函数使用。
[v4.1.0新增]
在大图片中搜索小图片,并返回搜索结果MatchingResult。该函数可以用于找图时找出多个位置,可以通过max参数控制最大的结果数量。也可以对匹配结果进行排序、求最值等操作。
[v4.1.0新增]
数组的元素是一个Match对象:
例如:
var result = images.matchTemplate(img, template, {
max: 100
});
result.matches.forEach(match => {
log("point = " + match.point + ", similarity = " + match.similarity);
});
第一个匹配结果。如果没有任何匹配,则返回null。
最后一个匹配结果。如果没有任何匹配,则返回null。
位于大图片最左边的匹配结果。如果没有任何匹配,则返回null。
位于大图片最上边的匹配结果。如果没有任何匹配,则返回null。
位于大图片最右边的匹配结果。如果没有任何匹配,则返回null。
位于大图片最下边的匹配结果。如果没有任何匹配,则返回null。
相似度最高的匹配结果。如果没有任何匹配,则返回null。
相似度最低的匹配结果。如果没有任何匹配,则返回null。
对匹配结果进行排序,并返回排序后的结果。
var result = images.matchTemplate(img, template, {
max: 100
});
log(result.sortBy("top-right"));
表示一张图片,可以是截图的图片,或者本地读取的图片,或者从网络获取的图片。
返回以像素为单位图片宽度。
返回以像素为单位的图片高度。
把图片保存到路径path。(如果文件存在则覆盖)
返回图片image在点(x, y)处的像素的ARGB值。
该值的格式为0xAARRGGBB,是一个"32位整数"(虽然JavaScript中并不区分整数类型和其他数值类型)。
坐标系以图片左上角为原点。以图片左侧边为y轴,上侧边为x轴。
##
findColor, findImage返回的对象。表示一个点(坐标)。
横坐标。
纵坐标。
canvas提供了使用画布进行2D画图的支持,可用于简单的小游戏开发或者图片编辑。使用canvas可以轻松地在一张图片或一个界面上绘制各种线与图形。
canvas的坐标系为平面直角坐标系,以屏幕左上角为原点,屏幕上边为x轴正方向,屏幕左边为y轴正方向。例如分辨率为1920*1080的屏幕上,画一条从屏幕左上角到屏幕右下角的线段为:
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可以绘制基本图形、图片等。
按键模拟部分提供了一些模拟物理按键的全局函数,包括Home、音量键、照相键等,有的函数依赖于无障碍服务,有的函数依赖于root权限。
一般来说,以大写字母开头的函数都依赖于root权限。执行此类函数时,如果没有root权限,则函数执行后没有效果,并会在控制台输出一个警告。
模拟按下返回键。返回是否执行成功。 此函数依赖于无障碍服务。
模拟按下Home键。返回是否执行成功。 此函数依赖于无障碍服务。
弹出电源键菜单。返回是否执行成功。 此函数依赖于无障碍服务。
拉出通知栏。返回是否执行成功。 此函数依赖于无障碍服务。
显示快速设置(下拉通知栏到底)。返回是否执行成功。 此函数依赖于无障碍服务。
显示最近任务。返回是否执行成功。 此函数依赖于无障碍服务。
分屏。返回是否执行成功。 此函数依赖于无障碍服务, 并且需要系统自身功能的支持。
模拟按下Home键。 此函数依赖于root权限。
模拟按下返回键。 此函数依赖于root权限。
模拟按下电源键。 此函数依赖于root权限。
模拟按下菜单键。 此函数依赖于root权限。
按下音量上键。 此函数依赖于root权限。
按键音量上键。 此函数依赖于root权限。
模拟按下照相键。
模拟按下物理按键上。 此函数依赖于root权限。
模拟按下物理按键下。 此函数依赖于root权限。
模拟按下物理按键左。 此函数依赖于root权限。
模拟按下物理按键右。 此函数依赖于root权限。
模拟按下物理按键确定。 此函数依赖于root权限。
KeyCode KeyEvent Value
Stability: 2 - Stable
media模块提供多媒体编程的支持。目前仅支持音乐播放和媒体文件扫描。后续会结合UI加入视频播放等功能。
需要注意是,使用该模块播放音乐时是在后台异步播放的,在脚本结束后会自动结束播放,因此可能需要插入诸如sleep()的语句来使脚本保持运行。例如:
//播放音乐
media.playMusic("/sdcard/1.mp3");
//让音乐播放完
sleep(media.getMusicDuration());
扫描路径path的媒体文件,将它加入媒体库中;或者如果该文件以及被删除,则通知媒体库移除该文件。
媒体库包括相册、音乐库等,因此该函数可以用于把某个图片文件加入相册。
//请求截图
requestScreenCapture(false);
//截图
var im = captureScreen();
var path = "/sdcard/screenshot.png";
//保存图片
im.saveTo(path);
//把图片加入相册
media.scanFile(path);
播放音乐文件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)函数。
把当前播放进度调整到时间msec的位置。如果当前没有在播放音乐,则调用函数没有任何效果。
例如,要把音乐调到1分钟的位置,为media.musicSeekTo(60 * 1000)。
//播放音乐
media.playMusic("/sdcard/1.mp3");
//调整到30秒的位置
media.musicSeekTo(30 * 1000);
//等待音乐播放完成
sleep(media.getMusicDuration() - 30 * 1000);
暂停音乐播放。如果当前没有在播放音乐,则调用函数没有任何效果。
继续音乐播放。如果当前没有播放过音乐,则调用该函数没有任何效果。
停止音乐播放。如果当前没有在播放音乐,则调用函数没有任何效果。
返回当前是否正在播放音乐。
返回当前音乐的时长。单位毫秒。
返回当前音乐的播放进度(已经播放的时间),单位毫秒。
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
};
};
基于控件的操作指的是选择屏幕上的控件,获取其信息或对其进行操作。对于一般软件而言,基于控件的操作对不同机型有很好的兼容性;但是对于游戏而言,由于游戏界面并不是由控件构成,无法采用本章节的方法,也无法使用本章节的函数。有关游戏脚本的编写,请参考《基于坐标的操作》。
基于控件的操作依赖于无障碍服务,因此最好在脚本开头使用auto()函数来确保无障碍服务已经启用。如果运行到某个需要权限的语句无障碍服务并没启动,则会抛出异常并跳转到无障碍服务界面。这样的用户体验并不好,因为需要重新运行脚本,后续会加入等待无障碍服务启动并让脚本继续运行的函数。
您也可以在脚本开头使用"auto";表示这个脚本需要无障碍服务,但是不推荐这种做法,因为这个标记必须在脚本的最开头(前面不能有注释或其他语句、空格等),我们推荐使用auto()函数来确保无障碍服务已启用。
检查无障碍服务是否已经启用,如果没有启用则抛出异常并跳转到无障碍服务启用界面;同时设置无障碍模式为mode。mode的可选值为:
如果不加mode参数,则为正常模式。
建议使用auto.waitFor()和auto.setMode()代替该函数,因为auto()函数如果无障碍服务未启动会停止脚本;而auto.waitFor()则会在在无障碍服务启动后继续运行。
示例:
auto("fast");
示例2:
auto();
检查无障碍服务是否已经启用,如果没有启用则跳转到无障碍服务启用界面,并等待无障碍服务启动;当无障碍服务启动后脚本会继续运行。
设置无障碍模式为mode。mode的可选值为:
Stability: 2 - Stable
SimpleActionAutomator提供了一些模拟简单操作的函数,例如点击文字、模拟按键等。这些函数可以直接作为全局函数使用。
返回是否点击成功。当屏幕中并未包含该文本,或者该文本所在区域不能点击时返回false,否则返回true。
该函数可以点击大部分包含文字的按钮。例如微信主界面下方的"微信", "联系人", "发现", "我"的按钮。
通常与while同时使用以便点击按钮直至成功。例如:
while(!click("扫一扫"));
当不指定参数i时则会尝试点击屏幕上出现的所有文字text并返回是否全部点击成功。
i是从0开始计算的, 也就是, click("啦啦啦", 0)表示点击屏幕上第一个"啦啦啦", click("啦啦啦", 1)表示点击屏幕上第二个"啦啦啦"。
文本所在区域指的是,从文本处向其父视图寻找,直至发现一个可点击的部件为止。
注意,该函数一般只用于录制的脚本中使用,在自己写的代码中使用该函数一般不要使用该函数。
点击在指定区域的控件。当屏幕中并未包含与该区域严格匹配的区域,或者该区域不能点击时返回false,否则返回true。
有些按钮或者部件是图标而不是文字(例如发送朋友圈的照相机图标以及QQ下方的消息、联系人、动态图标),这时不能通过click(text, i)来点击,可以通过描述图标所在的区域来点击。left, bottom, top, right描述的就是点击的区域。
至于要定位点击的区域,可以在悬浮窗使用布局分析工具查看控件的bounds属性。
通过无障碍服务录制脚本会生成该语句。
返回是否点击成功。当屏幕中并未包含该文本,或者该文本所在区域不能点击时返回false,否则返回true。
当不指定参数i时则会尝试点击屏幕上出现的所有文字text并返回是否全部长按成功。
找到第i+1个可滑动控件上滑或左滑。返回是否操作成功。屏幕上没有可滑动的控件时返回false。
另外不加参数时scrollUp()会寻找面积最大的可滑动的控件上滑或左滑,例如微信消息列表等。
参数为一个整数i时会找到第i + 1个可滑动控件滑动。例如scrollUp(0)为滑动第一个可滑动控件。
找到第i+1个可滑动控件下滑或右滑。返回是否操作成功。屏幕上没有可滑动的控件时返回false。
另外不加参数时scrollUp()会寻找面积最大的可滑动的控件下滑或右滑。
参数为一个整数i时会找到第i + 1个可滑动控件滑动。例如scrollUp(0)为滑动第一个可滑动控件。
返回是否输入成功。当找不到对应的文本框时返回false。
不加参数i则会把所有输入框的文本都置为text。例如setText("测试")。
这里的输入文本的意思是,把输入框的文本置为text,而不是在原来的文本上追加。
返回是否输入成功。当找不到对应的文本框时返回false。
不加参数i则会把所有输入框的文本追加内容text。例如input("测试")。
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属性,那么这个属性很可能是唯一的,除了以下几种情况:
尽管id属性很方便,但也不总是最方便的,例如对于微信和网易云音乐,每次更新他的控件id都会变化,导致了相同代码对于不同版本的微信、网易云音乐并不兼容。
除了这些属性外,主要还有以下几种属性:
有时候只靠一个属性并不能唯一确定一个控件,这时需要通过属性的组合来完成定位,例如className("ImageView").depth(10).findOne().click(),通过链式调用来组合条件。
通常用这些技巧便可以解决大部分问题,即使解决不了问题,也可以通过布局分析的"生成代码"功能来尝试生成一些选择器代码。接下来的问题便是对选取的控件进行操作,包括:
这些操作包含了绝大部分控件操作。根据这些我们可以很容易写出一个"刷屏"脚本(代码仅为示例,请不要在别人的群里测试,否则容易被踢):
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()为向前滑,包括下滑和右滑。
选择器的入门教程暂且要这里,更多信息可以查看下面的文档和选择器进阶。
创建一个新的选择器。但一般情况不需要使用该函数,因为可以直接用相应条件的语句创建选择器。
由于历史遗留原因,本不应该这样设计(不应该让id(), text()等作为全局函数,而是应该用By.id(), By.text()),但为了后向兼容性只能保留这个设计。
这样的API设计会污染全局变量,后续可能会支持"去掉这些全局函数而使用By.*"的选项。
为当前选择器附加控件"text等于字符串str"的筛选条件。
控件的text(文本)属性是文本控件上的显示的文字,例如微信左上角的"微信"文本。
为当前选择器附加控件"text需要包含字符串str"的筛选条件。
这是一个比较有用的条件,例如QQ动态页和微博发现页上方的"大家都在搜...."的控件可以用textContains("大家都在搜").findOne()来获取。
为当前选择器附加控件"text需要以prefix开头"的筛选条件。
这也是一个比较有用的条件,例如要找出Auto.js脚本列表中名称以"QQ"开头的脚本的代码为textStartsWith("QQ").find()。
为当前选择器附加控件"text需要以suffix结束"的筛选条件。
为当前选择器附加控件"text需要满足正则表达式reg"的条件。
有关正则表达式,可以查看正则表达式 - 菜鸟教程。
需要注意的是,如果正则表达式是字符串,则需要使用\\来表达\(也即Java正则表达式的形式),例如textMatches("\\d+")匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如textMatches(/\d+/)。但如果使用字符串的正则表达式则该字符串不能以"/"同时以"/"结束,也即不能写诸如textMatches("/\\d+/")的表达式,否则会被开头的"/"和结尾的"/"会被忽略。
为当前选择器附加控件"desc等于字符串str"的筛选条件。
控件的desc(描述,全称为Content-Description)属性是对一个控件的描述,例如网易云音乐右上角的放大镜图标的描述为搜索。要查看一个控件的描述,同样地可以借助悬浮窗查看。
desc属性同样是定位控件的利器。
为当前选择器附加控件"desc需要包含字符串str"的筛选条件。
为当前选择器附加控件"desc需要以prefix开头"的筛选条件。
为当前选择器附加控件"desc需要以suffix结束"的筛选条件。
为当前选择器附加控件"desc需要满足正则表达式reg"的条件。
有关正则表达式,可以查看正则表达式 - 菜鸟教程。
需要注意的是,如果正则表达式是字符串,则需要使用\\来表达\(也即Java正则表达式的形式),例如textMatches("\\d+")匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如textMatches(/\d+/)。但如果使用字符串的正则表达式则该字符串不能以"/"同时以"/"结束,也即不能写诸如textMatches("/\\d+/")的表达式,否则会被开头的"/"和结尾的"/"会被忽略。
为当前选择器附加"id等于resId"的筛选条件。
控件的id属性通常是可以用来确定控件的唯一标识,如果一个控件有id,那么使用id来找到他是最好的方法。要查看屏幕上的控件的id,可以开启悬浮窗并使用界面工具,点击相应控件即可查看。若查看到的控件id为null, 表示该控件没有id。另外,在列表中会出现多个控件的id相同的情况。例如微信的联系人列表,每个头像的id都是一样的。此时不能用id来唯一确定控件。
在QQ界面经常会出现多个id为"name"的控件,在微信上则每个版本的id都会变化。对于这些软件而言比较难用id定位控件。
为当前选择器附加控件"id包含字符串str"的筛选条件。比较少用。
为当前选择器附加"id需要以prefix开头"的筛选条件。比较少用。
为当前选择器附加"id需要以suffix结束"的筛选条件。比较少用。
附加id需要满足正则表达式。
需要注意的是,如果正则表达式是字符串,则需要使用\\来表达\(也即Java正则表达式的形式),例如textMatches("\\d+")匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如textMatches(/\d+/)。但如果使用字符串的正则表达式则该字符串不能以"/"同时以"/"结束,也即不能写诸如textMatches("/\\d+/")的表达式,否则会被开头的"/"和结尾的"/"会被忽略。
idMatches("[a-zA-Z]+")
为当前选择器附加控件"className等于字符串str"的筛选条件。
控件的className(类名)表示一个控件的类别,例如文本控件的类名为android.widget.TextView。
如果一个控件的类名以"android.widget."开头,则可以省略这部分,例如文本控件可以直接用className("TextView")的选择器。
常见控件的类名如下:
为当前选择器附加控件"className需要包含字符串str"的筛选条件。
为当前选择器附加控件"className需要以prefix开头"的筛选条件。
为当前选择器附加控件"className需要以suffix结束"的筛选条件。
为当前选择器附加控件"className需要满足正则表达式reg"的条件。
有关正则表达式,可以查看正则表达式 - 菜鸟教程。
需要注意的是,如果正则表达式是字符串,则需要使用\\来表达\(也即Java正则表达式的形式),例如textMatches("\\d+")匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如textMatches(/\d+/)。但如果使用字符串的正则表达式则该字符串不能以"/"同时以"/"结束,也即不能写诸如textMatches("/\\d+/")的表达式,否则会被开头的"/"和结尾的"/"会被忽略。
为当前选择器附加控件"packageName等于字符串str"的筛选条件。
控件的packageName表示控件所属界面的应用包名。例如微信的包名为"com.tencent.mm", 那么微信界面的控件的packageName为"com.tencent.mm"。
要查看一个应用的包名,可以用函数app.getPackageName()获取,例如toast(app.getPackageName("微信"))。
为当前选择器附加控件"packageName需要包含字符串str"的筛选条件。
为当前选择器附加控件"packageName需要以prefix开头"的筛选条件。
为当前选择器附加控件"packageName需要以suffix结束"的筛选条件。
为当前选择器附加控件"packageName需要满足正则表达式reg"的条件。
有关正则表达式,可以查看正则表达式 - 菜鸟教程。
一个控件的bounds属性为这个控件在屏幕上显示的范围。我们可以用这个范围来定位这个控件。尽管用这个方法定位控件对于静态页面十分准确,却无法兼容不同分辨率的设备;同时对于列表页面等动态页面无法达到效果,因此使用不推荐该选择器。
注意参数的这四个数字不能随意填写,必须精确的填写控件的四个边界才能找到该控件。例如,要点击QQ主界面的右上角加号,我们用布局分析查看该控件的属性,如下图:
可以看到bounds属性为(951, 67, 1080, 196),此时使用代码bounds(951, 67, 1080, 196).clickable().click()即可点击该控件。
为当前选择器附加控件"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来获取屏幕高度。
为当前选择器附加控件"bounds需要包含left, top, right, buttom构成的范围"的条件。
这个条件用于限制控件的范围必须包含所给定的范围。例如给定一个点(500, 300), 寻找在这个点上的可点击控件的代码为:
var w = boundsContains(500, 300, device.width - 500, device.height - 300).clickable().findOne();
w.click();
为当前选择器附加控件"drawingOrder等于order"的条件。
drawingOrder为一个控件在父控件中的绘制顺序,通常可以用于区分同一层次的控件。
但该属性在Android 7.0以上才能使用。
为当前选择器附加控件是否可点击的条件。但并非所有clickable为false的控件都真的不能点击,这取决于控件的实现。对于自定义控件(例如显示类名为android.view.View的控件)很多的clickable属性都为false都却能点击。
需要注意的是,可以省略参数b而表示选择那些可以点击的控件,例如className("ImageView").clickable()表示可以点击的图片控件的条件,className("ImageView").clickable(false)表示不可点击的图片控件的条件。
为当前选择器附加控件是否可长按的条件。
为当前选择器附加控件是否可勾选的条件。勾选通常是对于勾选框而言的,例如图片多选时左上角通常有一个勾选框。
为当前选择器附加控件是否已选中的条件。被选中指的是,例如QQ聊天界面点击下方的"表情按钮"时,会出现自己收藏的表情,这时"表情按钮"便处于选中状态,其selected属性为true。
为当前选择器附加控件是否已启用的条件。大多数控件都是启用的状态(enabled为true),处于“禁用”状态通常是灰色并且不可点击。
为当前选择器附加控件是否可滑动的条件。滑动包括上下滑动和左右滑动。
可以用这个条件来寻找可滑动控件来滑动界面。例如滑动Auto.js的脚本列表的代码为:
className("android.support.v7.widget.RecyclerView").scrollable().findOne().scrollForward();
//或者classNameEndsWith("RecyclerView").scrollable().findOne().scrollForward();
为当前选择器附加控件是否可编辑的条件。一般来说可编辑的控件为输入框(EditText),但不是所有的输入框(EditText)都可编辑。
为当前选择器附加控件是否文本或输入框控件是否是多行显示的条件。
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件。如果找不到控件,当屏幕内容发生变化时会重新寻找,直至找到。
需要注意的是,如果屏幕上一直没有出现所描述的控件,则该函数会阻塞,直至所描述的控件出现为止。因此此函数不会返回null。
该函数本来应该命名为untilFindOne(),但由于历史遗留原因已经无法修改。如果想要只在屏幕上搜索一次而不是一直搜索,请使用findOnce()。
另外,如果屏幕上有多个满足条件的控件,findOne()采用深度优先搜索(DFS),会返回该搜索算法找到的第一个控件。注意控件找到的顺序有时会起到作用。
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件;如果在timeout毫秒的时间内没有找到符合条件的控件,则终止搜索并返回null。
该函数类似于不加参数的findOne(),只不过加上了时间限制。
示例:
//启动Auto.js
launchApp("Auto.js");
//在6秒内找出日志图标的控件
var w = id("action_log").findOne(6000);
//如果找到控件则点击
if(w != null){
w.click();
}else{
//否则提示没有找到
toast("没有找到日志图标");
}
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,如果找到符合条件的控件则返回该控件;否则返回null。
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,并返回第 i + 1 个符合条件的控件;如果没有找到符合条件的控件,或者符合条件的控件个数 < i, 则返回null。
注意这里的控件次序,是搜索算法深度优先搜索(DSF)决定的。
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,找到所有满足条件的控件集合并返回。这个搜索只进行一次,并不保证一定会找到,因而会出现返回的控件集合为空的情况。
不同于findOne()或者findOnce()只找到一个控件并返回一个控件,find()函数会找出所有满足条件的控件并返回一个控件集合。之后可以对控件集合进行操作。
可以通过empty()函数判断找到的是否为空。例如:
var c = className("AbsListView").find();
if(c.empty()){
toast("找到啦");
}else{
toast("没找到╭(╯^╰)╮");
}
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到找到至少一个满足条件的控件为止,并返回所有满足条件的控件集合。
该函数与find()函数的区别在于,该函数永远不会返回空集合;但是,如果屏幕上一直没有出现满足条件的控件,则该函数会保持阻塞。
判断屏幕上是否存在控件符合选择器所确定的条件。例如要判断某个文本出现就执行某个动作,可以用:
if(text("某个文本").exists()){
//要支持的动作
}
等待屏幕上出现符合条件的控件;在满足该条件的控件出现之前,该函数会一直保持阻塞。
例如要等待包含"哈哈哈"的文本控件出现的代码为:
textContains("哈哈哈").waitFor();
为当前选择器附加自定义的过滤条件。
例如,要找出屏幕上所有文本长度为10的文本控件的代码为:
var uc = className("TextView").filter(function(w){
return w.text().length == 10;
});
UiObject表示一个控件,可以通过这个对象获取到控件的属性,也可以对控件进行点击、长按等操作。
获取一个UiObject通常通过选择器的findOne(), findOnce()等函数,也可以通过UiCollection来获取,或者通过UiObject.child(), UiObject.parent()等函数来获取一个控件的子控件或父控件。
点击该控件,并返回是否点击成功。
如果该函数返回false,可能是该控件不可点击(clickable为false),当前界面无法响应该点击等。
长按该控件,并返回是否点击成功。
如果该函数返回false,可能是该控件不可点击(longClickable为false),当前界面无法响应该点击等。
设置输入框控件的文本内容,并返回是否设置成功。
该函数只对可编辑的输入框(editable为true)有效。
对输入框文本的选中内容进行复制,并返回是否操作成功。
该函数只能用于输入框控件,并且当前输入框控件有选中的文本。可以通过setSelection()函数来设置输入框选中的内容。
var et = className("EditText").findOne();
//选中前两个字
et.setSelection(0, 2);
//对选中内容进行复制
if(et.copy()){
toast("复制成功");
}else{
toast("复制失败");
}
对输入框文本的选中内容进行剪切,并返回是否操作成功。
该函数只能用于输入框控件,并且当前输入框控件有选中的文本。可以通过setSelection()函数来设置输入框选中的内容。
对输入框控件进行粘贴操作,把剪贴板内容粘贴到输入框中,并返回是否操作成功。
//设置剪贴板内容为“你好”
setClip("你好");
var et = className("EditText").findOne();
et.paste();
对输入框控件设置选中的文字内容,并返回是否操作成功。
索引是从0开始计算的;并且,选中内容不包含end位置的字符。例如,如果一个输入框内容为"123456789",要选中"4567"的文字的代码为et.setSelection(3, 7)。
该函数也可以用来设置光标位置,只要参数的end等于start,即可把输入框光标设置在start的位置。例如et.setSelection(1, 1)会把光标设置在第一个字符的后面。
对控件执行向前滑动的操作,并返回是否操作成功。
向前滑动包括了向右和向下滑动。如果一个控件既可以向右滑动和向下滑动,那么执行scrollForward()的行为是未知的(这是因为Android文档没有指出这一点,同时也没有充分的测试可供参考)。
对控件执行向后滑动的操作,并返回是否操作成功。
向后滑动包括了向右和向下滑动。如果一个控件既可以向右滑动和向下滑动,那么执行scrollForward()的行为是未知的(这是因为Android文档没有指出这一点,同时也没有充分的测试可供参考)。
对控件执行"选中"操作,并返回是否操作成功。"选中"和isSelected()的属性相关,但该操作十分少用。
对控件执行折叠操作,并返回是否操作成功。
对控件执行操作,并返回是否操作成功。
对集合中所有控件执行显示操作,并返回是否全部操作成功。
对集合中所有控件执行向上滑的操作,并返回是否全部操作成功。
对集合中所有控件执行向下滑的操作,并返回是否全部操作成功。
对集合中所有控件执行向左滑的操作,并返回是否全部操作成功。
返回该控件的所有子控件组成的控件集合。可以用于遍历一个控件的子控件,例如:
className("AbsListView").findOne().children()
.forEach(function(child){
log(child.className());
});
返回子控件数目。
返回第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());
}
返回该控件的父控件。如果该控件没有父控件,返回null。
返回控件在屏幕上的范围,其值是一个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());
返回控件在父控件中的范围,其值是一个Rect对象。
返回控件在父控件中的绘制次序。该函数在安卓7.0及以上才有效,7.0以下版本调用会返回0。
获取控件的id,如果一个控件没有id,则返回null。
获取控件的文本,如果控件没有文本,返回""。
根据文本text在子控件中递归地寻找并返回文本或描述(desc)包含这段文本str的控件,返回它们组成的集合。
该函数会在当前控件的子控件,孙控件,曾孙控件...中搜索text或desc包含str的控件,并返回它们组合的集合。
根据选择器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();
}
});
根据选择器selector在该控件的子控件、孙控件...中搜索符合该选择器条件的控件,并返回它们组合的集合。
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()等等,不再赘述。
返回集合中的控件数。
历史遗留函数,相当于属性length。
返回集合中第i+1个控件(UiObject)。
历史遗留函数,建议直接使用数组下标的方式访问元素。
遍历集合。
历史遗留函数,相当于forEach。参考forEach。
返回控件集合是否为空。
返回控件集合是否非空。
根据selector所确定的条件在该控件集合的控件、子控件、孙控件...中找到所有符合条件的控件并返回找到的控件集合。
注意这会递归地遍历控件集合里所有的控件以及他们的子控件。和数组的filter函数不同。
例如:
var names = id("name").find();
//在集合
var clickableNames = names.find(clickable());
根据选择器selector在该控件集合的控件的子控件、孙控件...中搜索符合该选择器条件的控件,并返回找到的第一个控件;如果没有找到符合条件的控件则返回null。
UiObject.bounds(), UiObject.boundsInParent()返回的对象。表示一个长方形(范围)。
长方形左边界的x坐标、
长方形右边界的x坐标、
长方形上边界的y坐标、
长方形下边界的y坐标、
长方形中点x坐标。
长方形中点y坐标。
长方形宽度。通常可以作为控件宽度。
长方形高度。通常可以作为控件高度。
返回是否包含另一个长方形r。包含指的是,长方形r在该长方形的里面(包含边界重叠的情况)。
返回是否和另一个长方形相交。
未完待续。
Stability: 2 - Stable
sensors模块提供了获取手机上的传感器的信息的支持,这些传感器包括距离传感器、光线光感器、重力传感器、方向传感器等。需要指出的是,脚本只能获取传感器的数据,不能模拟或伪造传感器的数据和事件,因此诸如模拟摇一摇的功能是无法实现的。
要监听一个传感器时,需要使用sensors.register()注册监听器,之后才能开始监听;不需要监听时则调用sensors.unregister()注销监听器。在脚本结束时会自动注销所有的监听器。同时,这种监听会使脚本保持运行状态,如果不注销监听器,脚本会一直保持运行状态。
例如,监听光线传感器的代码为:
//光线传感器监听
sensors.register("light").on("change", (event, light)=>{
log("当前光强度为", light);
});
要注意的是,每个传感器的数据并不相同,所以对他们调用on()监听事件时的回调函数参数也不是相同,例如光线传感器参数为(event, light),加速度传感器参数为(event, ax, ay, az)。甚至在某些设备上的传感器参数有所增加,例如华为手机的距离传感器为三个参数,一般手机只有一个参数。
常用的传感器及其事件参数如下表:
!
注册一个传感器监听并返回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);
});
更多信息,参见SensorEventEmitter和sensors.ignoresUnsupportedSensor。
注销该传感器监听器。被注销的监听器将不再能监听传感器数据。
//注册一个传感器监听器
var sensor = sensors.register("gravity");
if(sensor == null){
exit();
}
//2秒后注销该监听器
setTimeout(()=> {
sensors.unregister(sensor);
}, 2000);
注销所有传感器监听器。
表示是否忽略不支持的传感器。如果该值被设置为true,则函数sensors.register()即使对不支持的传感器也会返回一个无任何数据的虚拟传感器监听,也就是sensors.register()不会返回null从而避免非空判断,并且此时会触发sensors的"unsupported_sensor"事件。
//忽略不支持的传感器
sensors.ignoresUnsupportedSensor = true;
//监听有不支持的传感器时的事件
sensors.on("unsupported_sensor", function(sensorName){
toastLog("不支持的传感器: " + sensorName);
});
//随便注册一个不存在的传感器。
log(sensors.register("aaabbb"));
当sensors.ignoresUnsupportedSensor被设置为true并且有不支持的传感器被注册时触发该事件。事件参数的传感器名称。
注册传感器返回的对象,其本身是一个EventEmmiter,用于监听传感器事件。
当传感器数据改变时触发该事件;该事件触发的最高频繁由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);
当传感器精度改变时会触发的事件。比较少用。
shell即Unix Shell,在类Unix系统提供与操作系统交互的一系列命令。
很多程序可以用来执行shell命令,例如终端模拟器。
在Auto.js大致等同于用adb执行命令"adb shell"。其实现包括两种方式:
Stability: 2 - Stable
一次性执行命令cmd, 并返回命令的执行结果。返回对象的其属性如下:
示例(强制停止微信) :
var result = shell("am force-stop com.tencent.mm", true);
log(result);
console.show();
if(result.code == 0){
toast("执行成功");
}else{
toast("执行失败!请到控制台查看错误信息");
}
Stability: 2 - Stable
shell函数通过用来一次性执行单条命令并获取结果。如果有多条命令需要执行,用Shell对象的效率更高。这是因为,每次运行shell函数都会打开一个单独的shell进程并在运行结束后关闭他,这个过程需要一定的时间;而Shell对象自始至终使用同一个shell进程。
Shell对象的"构造函数"。
var sh = new Shell(true);
//强制停止微信
sh.exec("am force-stop com.tencent.mm");
sh.exit();
执行命令cmd。该函数不会返回任何值。
注意,命令执行是"异步"的、非阻塞的。也就是不会等待命令完成后才继续向下执行。
尽管这样的设计使用起来有很多不便之处,但受限于终端模拟器,暂时没有解决方式;如果后续能找到解决方案,则将提供Shell.execAndWaitFor函数。
直接退出shell。正在执行的命令会被强制退出。
执行"exit"命令并等待执行命令执行完成、退出shell。
此函数会执行exit命令来正常退出shell。
设置该Shell的回调函数,以便监听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命令的资料来自AndroidStudio用户指南:Shell命令。
am命令即Activity Manager命令,用于管理应用程序活动、服务等。
以下命令均以"am "开头,例如shell('am start -p com.tencent.mm');(启动微信)
启动 intent 指定的 Activity(应用程序活动)。
请参阅 intent 参数的规范。
选项包括:
启动 intent 指定的 Service(服务)。
请参阅 intent 参数的规范。
选项包括:
强行停止与 package(应用包名)关联的所有应用。
终止与 package(应用包名)关联的所有进程。此命令仅终止可安全终止且不会影响用户体验的进程。
选项包括:
终止所有后台进程。
发出广播 intent。 请参阅 intent 参数的规范。
选项包括:
使用 Instrumentation 实例启动监控。通常,目标 component 是表单 test_package/runner_class。
选项包括:
转储 process 的堆,写入 file。
选项包括:
选项包括:
选项包括:
控制 package 的屏幕兼容性模式。
替换模拟器/设备显示尺寸。此命令对于在不同尺寸的屏幕上测试您的应用非常有用,它支持使用大屏设备模仿小屏幕分辨率(反之亦然)。
示例:
shell("am display-size 1280x800", true);
替换模拟器/设备显示密度。此命令对于在不同密度的屏幕上测试您的应用非常有用,它支持使用低密度屏幕在高密度环境环境上进行测试(反之亦然)。
示例:
shell("am display-density 480", true);
将给定的 intent 规范以 URI 的形式输出。 请参阅 intent 参数的规范。
将给定的 intent 规范以 intent:URI 的形式输出。 请参阅 intent 参数的规范。
对于采用 intent 参数的 am 命令,您可以使用以下选项指定 intent:
URI component package
如果不受上述某一选项的限制,您可以直接指定 URI、软件包名称和组件名称。当参数不受限制时,如果参数包含一个“:”(冒号),则此工具假定参数是一个 URI;如果参数包含一个“/”(正斜杠),则此工具假定参数是一个组件名称;否则,此工具假定参数是一个软件包名称。
所谓应用包名,是唯一确定应用的标识。例如微信的包名是"com.tencent.mm", QQ的包名是"com.tencent.mobileqq"。
要获取一个应用的包名,可以通过函数getPackageName(appName)获取。参见帮助->其他一般函数。
pm命令用于管理应用程序,例如卸载应用、冻结应用等。
以下命令均以"pm "开头,例如"shell(\"pm disable com.tencent.mm\");"(冻结微信)
输出所有软件包,或者,仅输出包名称包含 filter 中的文本的软件包。
选项:
输出所有已知的权限组。
输出所有已知权限,或者,仅输出 group 中的权限。
选项:
列出所有测试软件包。
选项:
输出系统的所有功能。
输出当前设备支持的所有库。
输出系统上的所有用户。
输出给定 package 的 APK 的路径。
将软件包(通过 path 指定)安装到系统。
选项:
从系统中卸载软件包。
选项:
删除与软件包关联的所有数据。
启用给定软件包或组件(作为“package/class”写入)。
停用给定软件包或组件(作为“package/class”写入)。
选项:
向应用授予权限。在运行 Android 6.0(API 级别 23)及更高版本的设备上,可以是应用清单中声明的任何权限。在运行 Android 5.1(API 级别 22)和更低版本的设备上,必须是应用定义的可选权限。
从应用中撤销权限。在运行 Android 6.0(API 级别 23)及更高版本的设备上,可以是应用清单中声明的任何权限。在运行 Android 5.1(API 级别 22)和更低版本的设备上,必须是应用定义的可选权限。
更改默认安装位置。位置值:
注:此命令仅用于调试目的;使用此命令会导致应用中断和其他意外行为。
返回当前安装位置。返回值:
指定是否应强制执行给定的权限。
减少缓存文件以达到给定的可用空间。
使用给定的 user_name 创建新用户,输出新用户的标识符。
移除具有给定的 user_id 的用户,删除与该用户关联的所有数据。
输出设备支持的最大用户数。
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, Array用JSON.stringify序列化存取。
storages保存的数据在脚本之间是共享的,任何脚本只要知道storage名称便可以获取到相应的数据,因此它不能用于敏感数据的储存。 storages无法像Web开发中LocalStorage一样提供根据域名独立的存储,因为脚本的路径随时可能改变。
storages.create(name)
name {string} 本地存储名称
创建一个本地存储并返回一个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("[email protected]:ABC");
storages.remove(name)
name {string} 本地存储名称
删除一个本地存储以及他的全部数据。如果该存储不存在,返回false;否则返回true。
Storages
Storage.get(key[, defaultValue])
key {string} 键值
defaultValue {any} 可选,默认值
从本地存储中取出键值为key的数据并返回。
如果该存储中不包含该数据,这时若指定了默认值参数则返回默认值,否则返回undefined。
返回的数据可能是任意数据类型,这取决于使用Storage.put保存该键值的数据时的数据类型。
Storage.put(key, value)
key {string} 键值
value {any} 值
把值value保存到本地存储中。value可以是undefined以外的任意数据类型。如果value为undefined则抛出TypeError。
存储的过程实际上是使用JSON.stringify把value转换为字符串再保存,因此value必须是可JSON化的才能被接受。
Storage.remove(key)
key {string} 键值
移除键值为key的数据。不返回任何值。
Storage.contains(key)
key {string} 键值
返回该本地存储是否包含键值为key的数据。是则返回true,否则返回false。
Storage.clear()
移除该本地存储的所有数据。不返回任何值。
Stability: 1 - Experiment
threads模块提供了多线程支持,可以启动新线程来运行脚本。
脚本主线程会等待所有子线程执行完成后才停止执行,因此如果子线程中有死循环,请在必要的时候调用exit()来直接停止脚本或threads.shutDownAll()来停止所有子线程。
通过threads.start()启动的所有线程会在脚本被强制停止时自动停止。
由于JavaScript自身没有多线程的支持,因此您可能会遇到意料之外的问题。
启动一个新线程并执行action。
例如:
threads.start(function(){
//在新线程执行的代码
while(true){
log("子线程");
}
});
while(true){
log("脚本主线程");
}
通过该函数返回的Thread对象可以获取该线程的状态,控制该线程的运行中。例如:
var thread = threads.start(function(){
while(true){
log("子线程");
}
});
//停止线程执行
thread.interrupt();
更多信息参见Thread。
停止所有通过threads.start()启动的子线程。
返回当前线程。
新建一个Disposable对象,用于等待另一个线程的某个一次性结果。更多信息参见线程通信以及Disposable。
新建一个整数原子变量。更多信息参见线程安全以及AtomicLong。
新建一个可重入锁。更多信息参见线程安全以及ReentrantLock。
线程对象,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();
中断线程运行。
等待线程执行完成。如果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);
返回线程是否存活。如果线程仍未开始或已经结束,返回false; 如果线程已经开始或者正在运行中,返回true。
等待线程开始执行。调用threads.start()以后线程仍然需要一定时间才能开始执行,因此调用此函数会等待线程开始执行;如果线程已经处于执行状态则立即返回。
var thread = threads.start(function(){
//do something
});
thread.waitFor();
thread.setTimeout(function(){
//do something
}, 1000);
参见timers.setTimeout()。
区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出IllegalStateException。
log("当前线程(主线程):" + threads.currentThread());
var thread = threads.start(function(){
//设置一个空的定时来保持线程的运行状态
setInterval(function(){}, 1000);
});
sleep(1000);
thread.setTimeout(function(){
log("当前线程(子线程):" + threads.currentThread());
exit();
}, 1000);
参见timers.setInterval()。
区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出IllegalStateException。
参见timers.setImmediate()。
区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出IllegalStateException。
参见timers.clearInterval()。
区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出IllegalStateException。
参见timers.clearTimeout()。
区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出IllegalStateException。
参见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();
给函数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。
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);
预定每隔 delay 毫秒重复执行的 callback。 返回一个用于 clearInterval() 的 id。
当 delay 小于 0 时,delay 会被设为 0。
预定在 delay 毫秒之后执行的单次 callback。 返回一个用于 clearTimeout() 的 id。
callback 可能不会精确地在 delay 毫秒被调用。 Auto.js 不能保证回调被触发的确切时间,也不能保证它们的顺序。 回调会在尽可能接近所指定的时间上调用。
当 delay 小于 0 时,delay 会被设为 0。
预定立即执行的 callback,它是在 I/O 事件的回调之后被触发。 返回一个用于 clearImmediate() 的 id。
当多次调用 setImmediate() 时,callback 函数会按照它们被创建的顺序依次执行。 每次事件循环迭代都会处理整个回调队列。 如果一个立即定时器是被一个正在执行的回调排入队列的,则该定时器直到下一次事件循环迭代才会被触发。
setImmediate()、setInterval() 和 setTimeout() 方法每次都会返回表示预定的计时器的id。 它们可用于取消定时器并防止触发。
取消一个由 setInterval() 创建的循环定时任务。
例如:
//每5秒就发出一次hello
var id = setInterval(function(){
toast("hello");
}, 5000);
//1分钟后取消循环
setTimeout(function(){
clearInterval(id);
}, 60 * 1000);
取消一个由 setTimeout() 创建的定时任务。
取消一个由 setImmediate() 创建的 Immediate 对象。
ui模块提供了编写用户界面的支持。
带有ui的脚本的的最前面必须使用"ui";指定ui模式,否则脚本将不会以ui模式运行。正确示范:
"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(
);
在这个例子中,第3~6行的部分就是xml,指定了界面的具体内容。代码的第3行的标签
代码的第5行和第4行一样,也是一个按钮控件,只不过他的文本内容为"第二个按钮"。这两个控件在垂直布局中,因此会纵向排列,效果如图:
如果我们把这个例子的垂直布局(vertical)改成水平布局(horizontal),也即:
"ui";
ui.layout(
);
则这两个按钮会横向排列,效果如图:
一个控件可以指定多个属性(甚至可以不指定任何属性),用空格隔开即可;布局同样也可以指定属性,例如:
"ui";
ui.layout(
);
第三行bg="#ff0000"指定了垂直布局的背景色(bg)为"#ff0000",这是一个RGB颜色,表示红色(有关RGB的相关知识参见RGB颜色对照表)。第四行的textSize="20sp"则指定了按钮控件的字体大小(textSize)为"20sp",sp是一个字体单位,暂时不用深入理会。上述代码的效果如图:
一个界面便由一些布局和控件组成。为了便于文档阅读,我们再说明一下以下术语:
控件和布局都属于视图(View)。在这个章节中将介绍所有控件和布局的共有的属性和函数。例如属性背景,宽高等(所有控件和布局都能设置背景和宽高),函数click()设置视图(View)被点击时执行的动作。
View的宽度,是属性width的缩写形式。可以设置的值为*, auto和具体数值。其中*表示宽度尽量填满父布局,而auto表示宽度将根据View的内容自动调整(自适应宽度)。例如:
"ui";
ui.layout(
);
在这个例子中,第一个按钮为自适应宽度,第二个按钮为填满父布局,显示效果为:
如果不设置该属性,则不同的控件和布局有不同的默认宽度,大多数为auto。
宽度属性也可以指定一个具体数值。例如w="20",w="20px"等。不加单位的情况下默认单位为dp,其他单位包括px(像素), mm(毫米), in(英寸)。有关尺寸单位的更多内容,参见尺寸的单位: Dimension。
"ui";
ui.layout(
);
View的高度,是属性height的缩写形式。可以设置的值为*, auto和具体数值。其中*表示宽度尽量填满父布局,而auto表示宽度将根据View的内容自动调整(自适应宽度)。
如果不设置该属性,则不同的控件和布局有不同的默认高度,大多数为auto。
宽度属性也可以指定一个具体数值。例如h="20",h="20px"等。不加单位的情况下默认单位为dp,其他单位包括px(像素), mm(毫米), in(英寸)。有关尺寸单位的更多内容,参见尺寸的单位: Dimension。
View的id,用来区分一个界面下的不同控件和布局,一个界面的id在同一个界面下通常是唯一的,也就是一般不存在两个View有相同的id。id属性也是连接xml布局和JavaScript代码的桥梁,在代码中可以通过一个View的id来获取到这个View,并对他进行操作(设置点击动作、设置属性、获取属性等)。例如:
"ui";
ui.layout(
);
//通过ui.ok获取到按钮控件
toast(ui.ok.getText());
这个例子中有一个按钮控件"确定",id属性为"ok",那么我们可以在代码中使用ui.ok来获取他,再通过getText()函数获取到这个按钮控件的文本内容。 另外这个例子中使用帧布局(frame)是因为,我们只有一个控件,因此用于最简单的布局帧布局。
View的"重力"。用于决定View的内容相对于View的位置,可以设置的值为:
例如对于一个按钮控件,gravity="right"会使其中的文本内容靠右显示。例如:
"ui";
ui.layout(
);
显示效果为:
这些属性是可以组合的,例如gravity="right|bottom"的View他的内容会在右下角。
View在布局中的"重力",用于决定View本身在他的父布局的位置,可以设置的值和gravity属性相同。注意把这个属性和gravity属性区分开来。
"ui";
ui.layout(
);
在这个例子中,我们让帧布局(frame)的大小占满整个屏幕,通过给第一个按钮设置属性layout_gravity="center"来使得按钮在帧布局中居中,通过给第二个按钮设置属性layout_gravity="right|bottom"使得他在帧布局中位于右下角。效果如图:
要注意的是,layout_gravity的属性不一定总是生效的,具体取决于布局的类别。例如不能让水平布局中的第一个子控件靠底部显示(否则和水平布局本身相违背)。
margin为View和其他View的间距,即外边距。margin属性包括四个值:
而margin属性本身的值可以有三种格式:
用一个例子来具体理解外边距的含义:
"ui";
ui.layout(
);
第一个按钮的margin属性指定了他的边距为30dp, 也就是他与水平布局以及第二个按钮的间距都是30dp, 其显示效果如图:
如果把margin="30"改成margin="10 40"那么第一个按钮的左右间距为10dp, 上下间距为40dp, 效果如图:
有关margin属性的单位,参见尺寸的单位: Dimension。
View的左外边距。如果该属性和margin属性指定的值冲突,则在后面的属性生效,前面的属性无效,例如margin="20" marginLeft="10"的左外边距为10dp,其他外边距为20dp。
"ui";
ui.layout(
);
第一个按钮指定了左外边距为50dp,则他和他的父布局水平布局(horizontal)的左边的间距为50dp, 效果如图:
View的右外边距。如果该属性和margin属性指定的值冲突,则在后面的属性生效,前面的属性无效。
View的上外边距。如果该属性和margin属性指定的值冲突,则在后面的属性生效,前面的属性无效。
View的下外边距。如果该属性和margin属性指定的值冲突,则在后面的属性生效,前面的属性无效。
View和他的自身内容的间距,也就是内边距。注意和margin属性区分开来,margin属性是View之间的间距,而padding是View和他自身内容的间距。举个例子,一个文本控件的padding也即文本控件的边缘和他的文本内容的间距,paddingLeft即文本控件的左边和他的文本内容的间距。
paddding属性的值同样有三种格式:
用一个例子来具体理解内边距的含义:
"ui";
ui.layout(
);
这个例子是一个居中的按钮(通过父布局的gravity="center"属性设置),背景色为红色(bg="#ff0000"),文本内容为"HelloWorld",左边距为10dp,上边距为20dp,下边距为30dp,右边距为40dp,其显示效果如图:
View的左内边距。如果该属性和padding属性指定的值冲突,则在后面的属性生效,前面的属性无效。
View的右内边距。如果该属性和padding属性指定的值冲突,则在后面的属性生效,前面的属性无效。
View的上内边距。如果该属性和padding属性指定的值冲突,则在后面的属性生效,前面的属性无效。
View的下内边距。如果该属性和padding属性指定的值冲突,则在后面的属性生效,前面的属性无效。
View的背景。其值可以是一个链接或路径指向的图片,或者RGB格式的颜色,或者其他背景。具体参见Drawables。
例如,bg="#00ff00"设置背景为绿色,bg="file:///sdcard/1.png"设置背景为图片"1.png",bg="?attr/selectableItemBackground"设置背景为点击时出现的波纹效果(可能需要同时设置clickable="true"才生效)。
View的透明度,其值是一个0~1之间的小数,0表示完全透明,1表示完全不透明。例如alpha="0.5"表示半透明。
View的前景。前景即在一个View的内容上显示的内容,可能会覆盖掉View本身的内容。其值和属性bg的值类似。
View的最小高度。该值不总是生效的,取决于其父布局是否有足够的空间容纳。
例:
有关该属性的单位,参见尺寸的单位: Dimension。
View的最小宽度。该值不总是生效的,取决于其父布局是否有足够的空间容纳。
例:
有关该属性的单位,参见尺寸的单位: Dimension。
View的可见性,该属性可以决定View是否显示出来。其值可以为:
View的旋转角度。通过该属性可以让这个View顺时针旋转一定的角度。例如rotation="90"可以让他顺时针旋转90度。
如果要设置旋转中心,可以通过transformPivotX, transformPivotY属性设置。默认的旋转中心为View的中心。
View的变换中心坐标x。用于View的旋转、放缩等变换的中心坐标。例如transformPivotX="10"。
该坐标的坐标系以View的左上角为原点。也就是x值为变换中心到View的左边的距离。
有关该属性的单位,参见尺寸的单位: Dimension。
View的变换中心坐标y。用于View的旋转、放缩等变换的中心坐标。例如transformPivotY="10"。
该坐标的坐标系以View的左上角为原点。也就是y值为变换中心到View的上边的距离。
有关该属性的单位,参见尺寸的单位: Dimension。
设置View的样式。不同控件有不同的可选的内置样式。具体参见各个控件的说明。
需要注意的是,style属性只支持安卓5.1及其以上。
文本控件用于显示文本,可以控制文本的字体大小,字体颜色,字体等。
以下介绍该控件的主要属性和方法,如果要查看他的所有属性和方法,请阅读TextView。
设置文本的内容。例如text="一段文本"。
设置字体的颜色,可以是RGB格式的颜色(例如#ff00ff),或者颜色名称(例如red, green等),具体参见颜色。
示例, 红色字体:
设置字体的大小,单位一般是sp。按照Material Design的规范,正文字体大小为14sp,标题字体大小为18sp,次标题为16sp。
示例,超大字体:
设置字体的样式,比如斜体、粗体等。可选的值为:
可以用或("|")把他们组合起来,比如粗斜体为"bold|italic"。
例如,粗体:`
设置文本控件的行数。即使文本内容没有达到设置的行数,控件也会留出相应的宽度来显示空白行;如果文本内容超出了设置的行数,则超出的部分不会显示。
另外在xml中是不能设置多行文本的,要在代码中设置。例如:
"ui";
ui.layout(
)
//通过\n换行
ui.myText.setText("第一行\n第二行\n第三行\n第四行");
设置文本控件的最大行数。
设置字体。可选的值为:
示例,等宽字体:
设置文本的省略号位置。文本的省略号会在文本内容超出文本控件时显示。可选的值为:
当设置该属性后,TextView显示的字符长度(单位是em),超出的部分将不显示,或者根据ellipsize属性的设置显示省略号。
例如,限制文本最长为5em: `
控制是否自动找到url和电子邮件地址等链接,并转换为可点击的链接。默认值为“none”。
设置该值可以让文本中的链接、电话等变成可点击状态。
可选的值为以下的值以其通过或("|")的组合:
示例:
按钮控件是一个特殊的文本控件,因此所有文本控件的函数的属性都适用于按钮控件。
除此之外,按钮控件有一些内置的样式,通过style属性设置,包括:
这些样式的具体效果参见"示例/界面控件/按钮控件.js"。
例如:
输入框控件也是一个特殊的文本控件,因此所有文本控件的函数的属性和函数都适用于按钮控件。输入框控件有自己的属性和函数,要查看所有这些内容,阅读EditText。
对于一个输入框控件,我们可以通过text属性设置他的内容,通过lines属性指定输入框的行数;在代码中通过getText()函数获取输入的内容。例如:
"ui";
ui.layout(
);
//指定确定按钮点击时要执行的动作
ui.ok.click(function(){
//通过getText()获取输入的内容
var name = ui.name.getText();
toast(name + "您好!");
});
效果如图:
除此之外,输入框控件有另外一些主要属性(虽然这些属性对于文本控件也是可用的但一般只用于输入框控件):
输入提示。这个提示会在输入框为空的时候显示出来。如图所示:
上面图片效果的代码为:
"ui";
ui.layout(
)
指定输入提示的字体颜色。
指定输入提示的字体大小。
指定输入框可以输入的文本类型。可选的值为以下值及其用"|"的组合:
例如,想指定一个输入框的输入类型为小数数字,为:
指定输入框输入框是否为密码输入框。默认为false。
例如:
指定输入框输入框是否为数字输入框。默认为false。
例如:
指定输入框输入框是否为电话号码输入框。默认为false。
例如:
指定输入框可以输入的字符。例如,要指定输入框只能输入"1234567890+-",为。
指定输入框是否为单行输入框。默认为false。您也可以通过lines="1"来指定单行输入框。
例如:
图片控件用于显示来自网络、本地或者内嵌数据的图片,并可以指定图片以圆角矩形、圆形等显示。但是不能用于显示gif动态图。
这里只介绍他的主要方法和属性,如果要查看他的所有方法和属性,阅读ImageView。
使用一个Uri指定图片的来源。可以是图片的地址(http://....),本地路径(file://....)或者base64数据("data:image/png;base64,...")。
如果使用图片地址或本地路径,Auto.js会自动使用适当的缓存来储存这些图片,减少下次加载的时间。
例如,显示百度的logo:
"ui";
ui.layout(
);
再例如,显示文件/sdcard/1.png的图片为 。 再例如,使base64显示一张钱包小图片为:
"ui";
ui.layout(
);
图片着色,其值是一个颜色名称或RGB颜色值。使用该属性会将图片中的非透明区域都涂上同一颜色。可以用于改变图片的颜色。
例如,对于上面的base64的图片: ,则钱包图标颜色会变成红色。
控制图片根据图片控件的宽高放缩时的模式。可选的值为:
默认的scaleType为fitCenter;除此之外最常用的是fitXY, 他能使图片放缩到控件一样的大小,但图片可能会变形。
图片控件的半径。如果设置为控件宽高的一半并且控件的宽高相同则图片将剪切为圆形显示;否则图片为圆角矩形显示,半径即为四个圆角的半径,也可以通过radiusTopLeft, radiusTopRight, radiusBottomLeft, radiusBottomRight等属性分别设置四个圆角的半径。
例如,圆角矩形的Auto.js图标:
有关该属性的单位,参见尺寸的单位: Dimension。
图片控件的左上角圆角的半径。有关该属性的单位,参见尺寸的单位: Dimension。
图片控件的右上角圆角的半径。有关该属性的单位,参见尺寸的单位: Dimension。
图片控件的左下角圆角的半径。有关该属性的单位,参见尺寸的单位: Dimension。
图片控件的右下角圆角的半径。有关该属性的单位,参见尺寸的单位: Dimension。
图片控件的边框宽度。用于在图片外面显示一个边框,边框会随着图片控件的外形(圆角等)改变而相应变化。 例如, 圆角矩形带灰色边框的Auto.js图标:
图片控件的边框颜色。
指定该图片控件的图片是否剪切为圆形显示。如果为true,则图片控件会使其宽高保持一致(如果宽高不一致,则保持高度等于宽度)并使圆形的半径为宽度的一半。
例如,圆形的Auto.js图标:
垂直布局是一种比较简单的布局,会把在它里面的控件按照垂直方向依次摆放,如下图所示:
垂直布局:
—————
| 控件1 |
| 控件2 |
| 控件3 |
| ............ |
——————
垂直布局中的控件可以通过layout_weight属性来控制控件高度占垂直布局高度的比例。如果为一个控件指定layout_weight, 则这个控件的高度=垂直布局剩余高度 * layout_weight / weightSum;如果不指定weightSum, 则weightSum为所有子控件的layout_weight之和。所谓"剩余高度",指的是垂直布局中减去没有指定layout_weight的控件的剩余高度。 例如:
"ui";
ui.layout(
);
在这个布局中,三个控件的layout_weight都是1,也就是他们的高度都会占垂直布局高度的1/3,都是33.3dp. 再例如:
"ui";
ui.layout(
);
在这个布局中,第一个控件高度为1/4, 第二个控件为2/4, 第三个控件为1/4. 再例如:
"ui";
ui.layout(
);
在这个布局中,因为指定了weightSum为5, 因此第一个控件高度为1/5, 第二个控件为2/5, 第三个控件为1/5. 再例如:
"ui";
ui.layout(
);
在这个布局中,第一个控件并没有指定layout_weight, 而是指定高度为40dp, 因此不加入比例计算,此时布局剩余高度为60dp。第二个控件高度为剩余高度的2/3,也就是40dp,第三个控件高度为剩余高度的1/3,也就是20dp。
垂直布局的layout_weight属性还可以用于控制他的子控件高度占满剩余空间,例如:
"ui";
ui.layout(
);
在这个布局中,第三个控件的高度会占满除去控件1和控件2的剩余空间。
水平布局是一种比较简单的布局,会把在它里面的控件按照水平方向依次摆放,如下图所示: 水平布局: ————————————————————————————
| 控件1 | 控件2 | 控件3 | ... |
————————————————————————————
水平布局中也可以使用layout_weight属性来控制子控件的宽度占父布局的比例。和垂直布局中类似,不再赘述。
实际上,垂直布局和水平布局都属于线性布局。线性布局有一个orientation的属性,用于指定布局的方向,可选的值为vertical和horizontal。
例如
线性布局的默认方向是横向的,因此,一个没有指定orientation属性的线性布局就是横向布局。
帧布局
(完善中...)
语言 编辑 高级
这篇文章描述了如何在rhino中使用java。使用脚本调用Java有很多用途,它使得我们可以利用Java中现有的库,来帮助我们构建强大的脚本。我们可以通过编写脚本,来对Java程序进行测试。可以通过脚本来进行探索式编程,辅助Java的开发,所谓探索式编程,就是通过快速地编程调用库或API来探索这些库或API可以做什么,显而易见,脚本语言很适合探索式编程。
这里注意,ECMA标准并没有包含和Java(或者其他任何对象系统)交互的标准。本文所描述的所有内容,应该被认为是一个扩展。
访问 Java Packages 和 Classes节
Java的每段代码都是类的一部分,每一个JAVA类都是包的一部分。在Javascript中,脚本不属于任何package。我们可以访问Java包中的类么?
Rhino定义了一个顶层的变量Packages。Packages的所有属性都是Java中顶层的包,比如java和com。比如我们可以访问java包:
js> Packages.java
[JavaPackage java]
还有一种更方便的方式,Rhino定义了一个顶层的变量java,等价于Packages.java。所以上面的例子可以更简介地写成:
js> java
[JavaPackage java]
我们可以通过访问包的下层,来直接访问java类:
js> java.io.File
[JavaClass java.io.File]
如果你的脚本需要访问很多的Java类,每次都附带完整的包名会使得编程很麻烦。Rhino提供了一个顶层的方法importPackage,它的功能和Java的import一样。比如,我们可以导入java.io包中的所有类,然后直接通过类名File来访问java.io.File:
js> importPackage(java.io)
js> File
[JavaClass java.io.File]
这里importPackage(java.io)使得java.io包中的所有类(例如File)可以在顶层被访问。这和Java中的java.io.*;等价。
要注意Java会暗中导入java.lang.*,但是Rhino不会。因为JavaScript的顶层对象Boolean、Math、Number、Object和String和java.lang包中同名的类并不相同。因为这种冲突,建议不要用importPackage来导入java.lang包。
有一点要注意的,就是Rhino对于指定包名或类名时是如何处理错误的。如果java.Myclass是可访问的,Rhino会试图加载名为java.MyClass的类,如果加载失败,它会假设java.MyClass是一个包名,不会报错:
js> java.MyClass
[JavaPackage java.MyClass]
只有在你试图将这个对象当作类使用时,才会报错。
额外的包和类
额外的包和类也可以在Rhino中使用。确认你的.jar或.class文件在你的classpath里,你就可以在你的JavaScript应用中导入它们。这些包基本不会在java包中,所以你在使用时,需要在包前加上前缀"Packages"。 比如你想导入 org.mozilla.javascript 包,你应该像下面这样去使用importPackage():
$ java org.mozilla.javascript.tools.shell.Main
js> importPackage(Packages.org.mozilla.javascript);
js> Context.currentContext;
org.mozilla.javascript.Context@bb6ab6
偶尔,我们也会见到在一些例子中使用包的完整名称,而没有使用importPackage()。这也是可以的,只是会让你多打一些字。如果使用完整的名称,上面的例子就会变成下面这样:
$ java org.mozilla.javascript.tools.shell.Main
js> jsPackage = Packages.org.mozilla.javascript;
[JavaPackage org.mozilla.javascript]
js> jsPackage.Context.currentContext;
org.mozilla.javascript.Context@bb6ab6
同样,你可以通过importClass()来导入一个类,上面的例子也可以像这样写:
$ java org.mozilla.javascript.tools.shell.Main
js> importClass(Packages.org.mozilla.javascript.Context);
js> Context.currentContext;
org.mozilla.javascript.Context@bb6ab6
和Java一起工作节
现在我们可以访问Java类,下一步就是要创建一个对象。方法就和在Java中一样, 用new来创建对象:
js> new java.util.Date()
Thu Jan 24 16:18:17 EST 2002
如果我们将创建的对象存放在JavaScript变量中,我们可以调用它的方法:
js> f = new java.io.File("test.txt")
test.txt
js> f.exists()
true
js> f.getName()
test.txt
静态方法和属性可以直接通过类对象来访问:
js> java.lang.Math.PI
3.141592653589793
js> java.lang.Math.cos(0)
1
不像Java,在JavaScript里,方法就是一个对象。它可以被评估,也可以被调用。如果我们去查看这个方法,我们可以看到这个方法所有重载的形式:
js> f.listFiles
function listFiles() {/*
java.io.File[] listFiles()
java.io.File[] listFiles(java.io.FilenameFilter)
java.io.File[] listFiles(java.io.FileFilter)
*/}
输出告诉我们,File类有listFiles方法的三种重载:一种不包含参数的,另一种包含一个FilenameFilter类型的参数,第三个包含一个FileFilter类型的参数。所有的方法都返回一个File对象数组。可以观察到Java方法的参数和返回类型在探索式编程中是非常有用的,尤其是在对一个方法的参数和返回对象不确定的时候。
另一个有助于探索式编程的特性,是可以看到对象中定义的所有方法和属性。用JavaScript的for..in , 我们可以打印这些值:
js> for (i in f) { print(i) }
exists
parentFile
mkdir
toString
wait
[44 others]
注意这里不仅列出了File类中的所有方法,也列出了从基类java.lang.Object中继承的方法,例如wait。这使得我们可以更好地处理那些有复杂继承关系的对象,因为我们可以看到对象中所有可用的方法。
Rhino可以通过属性名来方便地访问JavaBean的属性。一个JavaBean的属性foo被方法getFoo和setFoo定义,另外,一个也叫foo的boolean类型的属性,可以被isFoo来定义。比如, 下面的代码实际上调用了File对象的getName和isDirectory方法。
js> f.name
test.txt
js> f.directory
false
调用重载方法节
根据参数类型选择调用方法的过程称为重载决议。在 Java 中, 重载决议在编译时执行, 而在rhino中则在运行时发生。这种差异是不可避免的, 因为 JavaScript 使用动态类型, 在2章中: 由于变量的类型直到运行时才知道, 才会发生重载决议。
例如, 请查看下面的 Java 类, 它定义了许多重载方法并调用它们。
public class Overload {
public String f(Object o) { return "f(Object)"; }
public String f(String s) { return "f(String)"; }
public String f(int i) { return "f(int)"; }
public String g(String s, int i) { return "g(String,int)"; }
public String g(int i, String s) { return "g(int,String)"; }
public static void main(String[] args) {
Overload o = new Overload();
Object[] a = new Object[] { new Integer(3), "hi", Overload.class };
for (int i = 0; i != a.length; ++i)
System.out.println(o.f(a[i]));
}
}
当我们编译和执行程序, 它产生输出
f(Object)
f(Object)
f(Object)
但是, 如果我们编写一个类似的脚本
var o = new Packages.Overload();
var a = [ 3, "hi", Packages.Overload ];
for (var i = 0; i != a.length; ++i)
print(o.f(a[i]));
并且运行它,将会输出
f(int)
f(String)
f(Object)
因为Rhino在运行时选择重载方法, 所以它会调用与该参数匹配的更具体的类型。 同时, 在编译时, Java 只在参数的类型上选择重载方法。
尽管这有利于选择一种方法,这种方法可能是每个调用的更好匹配,但它确实对性能有影响,因为每次调用时都要做更多的工作。事实上,这种性能代价在实际应用中并不明显。
因为重载决议发生在运行时,它可能在运行时失败。例如,如果我们用两个整数调用重载方法g,我们就会得到一个错误,因为方法的两个形式都比另一个更接近参数类型:
js> o.g(3,4)
js:"
matching JavaScript argument types (number,number) is ambiguous;
candidate methods are:
class java.lang.String g(java.lang.String,int)
class java.lang.String g(int,java.lang.String)
http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html 提供了一个更精确的重载语义定义。
实现Java接口节
现在我们可以访问Java类,创建Java对象,并访问这些对象的字段、方法和属性,我们就可以轻松掌握大量的功能。但是,在少数情况下是不够用的:Java中的很多API通过提供客户端必须实现的接口来工作。其中一个例子就是Thread类:其构造函数Runnable包含一个run方法,这个方法在新线程启动时被调用。
为了满足这种需求,Rhino提供了创建新的Java对象实现的接口的能力。首先,我们必须定义一个JavaScript对象,其中的函数属性的名称与Java接口所需的方法名称相匹配。要实现一个Runnable ,我们只需要定义一个不带参数的run单方法。如果你还记得第3章,可以用{ propertyName: value}符号定义一个JavaScript对象。我们可以在这里结合函数表达式使用这个语法来用一个 run方法定义一个JavaScript对象:
js> obj = { run: function () { print("\nrunning"); } }
[object Object]
js> obj.run()
running
现在我们可以通过构建一个 Runnable 来实现 Runnable 接口的对象:
js> r = new java.lang.Runnable(obj);
js> r = new java.lang.Runnable(obj);
[object JavaObject]
在Java中,不可能在接口上使用new运算符,因为没有可用的实现。Rhino从JavaScript对象中获取实现obj。现在我们有一个对象实现Runnable,我们可以创建Thread并运行它。我们定义的函数run 将在新线程上调用。
js> t = new java.lang.Thread(r)
Thread[Thread-2,5,main]
js> t.start()
js>
running
最终js提示和新线程的输出可能以任意顺序显示,具体取决于线程调度。
在后台,Rhino为一个新的Java类生成字节码,该类实现 Runnable 并转发对其 run方法的所有调用,并转发给关联的JavaScript对象。实现此类的对象称为Java适配器。因为转发到JavaScript是在运行时发生的,所以可能会延迟定义实现接口的方法直到它们被调用。虽然省略必要的方法对大编程来说是一种糟糕的做法,但它对小脚本和探索性编程很有用。
JavaAdapter构造函数节
在前面的章节中,我们使用 new 运算符与Java接口创建Java适配器。这种方法有其局限性:不可能实现多个接口,也不能扩展非抽象类。因为这些原因,有一个 JavaAdapter 构造函数。
JavaAdapter构造函数的语法是:
new JavaAdapter(javaIntfOrClass, [javaIntf, ..., javaIntf,] javascriptObject)
这里javaIntfOrClass是一个实现的接口或一个扩展的类,并且javaIntf是实现接口的接口。而javascriptObject 则包含从Java适配器调用的方法的JavaScript对象。
在实践中,几乎不需要JavaAdapter 直接调用构造函数。大多数情况下,使用new运算符之前的语法就足够了。
作为Java接口的JavaScript函数节
通常我们只需要使用一种方法实现一个接口,就像前面的 Runnable 例子或者提供各种事件监听器实现一样。为了方便这个,Rhino允许在这种接口传递JavaScript函数。该函数被称为接口方法的实现。
这里是简化的 Runnable 实例:
js> t = java.lang.Thread(function () { print("\nrunning"); });
Thread[Thread-0,5,main]
js> t.start()
js>
running
如果所有的方法都具有相同的签名,Rhino还允许使用JavaScript函数作为Java接口的实现方法。当调用函数时,Rhino将方法的名称作为附加参数传递。函数可以使用它来代表被调用的方法:
js> var frame = new Packages.javax.swing.JFrame();
js> frame.addWindowListener(function(event, methodName) {
if (methodName == "windowClosing") {
print("Calling System.exit()..."); java.lang.System.exit(0);
}
});
js> frame.setSize(100, 100);
js> frame.visible = true;
true
js> Calling System.exit()...
创建Java数组节
Rhino不提供创建Java数组的特殊语法。你必须使用这个 java.lang.reflect.Array 类来达到这个目的。要创建一个由五个Java字符串组成的数组,可以进行以下调用:
js> a = java.lang.reflect.Array.newInstance(java.lang.String, 5);
[Ljava.lang.String;@7ffe01
要创建一个基本类型数组,我们必须使用 java.lang 包中相关对象类中定义的特殊TYPE字段。例如,要创建一个字节数组,我们必须使用特殊字段 java.lang.Byte.TYPE:
js> a = java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 2);
[C@7a84e4
而且结果值是允许被使用在该类型的Java数组的任何地方。
js> a[0] = 104
104
js> a[1] = 105
105
js> new java.lang.String(a)
hi
Java字符串和JavaScript字符串节
请记住,Java字符串和JavaScript字符串是不一样的。Java字符串类型的实例,java.lang.String ,并具有由该类定义的所有方法。JavaScript字符串具有由...定义的方法,String.prototype. 最常见的绊脚石是 length, 这是Java字符串方法和JavaScript字符串的动态属性:
js> javaString = new java.lang.String("Java")
Java
js> jsString = "JavaScript"
JavaScript
js> javaString.length()
4
js> jsString.length
10
Rhino 在减少这两种类型之间的差异方面提供了一些帮助。首先,您可以将JavaScript字符串传递给需要Java字符串的Java方法,Rhino将执行转换。实际上,我们在前面java.lang.String 例子中的构造函数调用中看到了这个特性。
如果java.lang.String 类尚未定义它们,Rhino还会使JavaScript方法可用于Java字符串。例如:
js> javaString.match(/a.*/)
ava
JavaImporter 构造函数节
JavaImporter是一个新的全局构造函数,它允许在脚本化Java时省略显式的包名称:
var SwingGui = JavaImporter(Packages.javax.swing,
Packages.javax.swing.event,
Packages.javax.swing.border,
java.awt.event,
java.awt.Point,
java.awt.Rectangle,
java.awt.Dimension);
...
with (SwingGui) {
var mybutton = new JButton(test);
var mypoint = new Point(10, 10);
var myframe = new JFrame();
...
}
以前,这样的功能仅适用于将 org.mozilla.javascript.ImporterTopLevel 用作顶级作用域的嵌入。这个类提供额外的 importPackage() 和importClass() 全局函数的脚本,但其广泛的使用有污染Java类名的全局命名空间的趋势,还有防止垃圾收集加载类。
详情请参阅 Bugzilla 245882.
Java 异常节
JavaScript代码使用 try ... catch 语句可以捕获Java方法抛出的异常。Rhino将Java异常封装到具有以下属性的错误对象中:
javaException:Java方法抛出的原始异常
rhinoException:由Rhino运行时包装的异常
instanceof运算符可用于查询异常的类型:
try {
java.lang.Class.forName("NonExistingClass");
} catch (e) {
if (e.javaException instanceof java.lang.ClassNotFoundException) {
print("Class not found");
}
}
Rhino 还支持对 try... catch 语句的扩展,允许定义条件捕获异常:
function classForName(name) {
try {
return java.lang.Class.forName(name);
} catch (e if e.javaException instanceof java.lang.ClassNotFoundException) {
print("Class " + name + " not found");
} catch (e if e.javaException instanceof java.lang.NullPointerException) {
print("Class name is null");
}
}
classForName("NonExistingClass");
classForName(null);