节点

# 节点信息 节点对象NodeInfo,可以通过获取getNodeInfo方法获取到节点信息的数组 节点包含的信息如下 |名称|数据类型|说明| |-|-|-| |id | 字符串 | 资源的ID | |clz | 字符串 | 视图类名 ,例如 android.widget.TextView | |pkg | 字符串 | 包名 ,例如com.xx | |desc | 字符串 | 内容描述 | |text | 字符串 | 文本 | |checkable | 布尔型 | 是否可选中 | |checked | 布尔型 | 是否选中 | |clickable | 布尔型 | 是否可点击 | |enabled | 布尔型 | 是否启用 | |focusable | 布尔型 | 是否可获取焦点 | |focused | 布尔型 | 是否聚焦 | |longClickable | 布尔型 | 是否可长点击 | |scrollable | 布尔型 | 是否滚动 | |editable | 布尔型 | 是否可输入 | |selected | 布尔型 | 是否被选择 | |childCount | 整型 | 子节点的个数 | |index | 整型 | 节点的索引 | |depth | 整型 | 节点的层级深度 | |drawingOrder | 整型 | 节点的绘制顺序 | |bounds | Rect型 | 空间对象 | |top | 整型 | 顶部位置 | |bottom | 整型 | 底部位置 | |left | 整型 | 左边位置 | |right | 整型 | 右边位置 | |visibleBounds | Rect型 | 可视空间对象 | ## getOneNodeInfo(timeout) 通过选择器获取第一个节点对象 @param timeout 等待时间,单位是毫秒, 如果是0,代表不等待 @return NodeInfo 对象 或者null ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.CheckBox所有节点, var node = clz("android.widget.CheckBox").getOneNodeInfo(10000); if (node) { var x= node.click(); logd(x); } else { toast("无节点"); } } main(); ``` ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.ViewGroup 所有节点, var node = clz("android.widget.ViewGroup").getOneNodeInfo(10000); if (node) { //获取子节点 node =node.getOneNodeInfo(text("广告"),10000); if (!node){ toast("无子节点"); return; } var x= node.click(); logd(x); } else { toast("无节点"); } } main(); ``` ## getNodeInfo(timeout) 获取节点信息集合 @param timeout 等待时间,单位是毫秒, 如果是0,代表不等待 @return NodeInfo 节点信息集合 ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.CheckBox所有节点, var node = clz("android.widget.CheckBox").getNodeInfo(10000); logd(node); } main(); ``` ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.ViewGroup 所有节点, var node = clz("android.widget.ViewGroup").getNodeInfo(10000); if (node) { //获取子节点 node =node.getNodeInfo(text("广告").clz("android.widget.TextView"),10000); if (!node){ toast("无子节点"); return; } var x= node.click(); logd(x); } else { toast("无节点"); } } main(); ``` ## parent() 该节点的父级节点对象 @return NodeInfo 对象 或者null ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.CheckBox所有节点 var node = clz("android.widget.CheckBox").getOneNodeInfo(10000); if (node) { var x= node.parent(); logd(x); } else { toast("无节点"); } } main(); ``` ## child(index) 取得单个子节点对象 @param index 子节点索引 @return NodeInfo 对象 或者null ```js function main(){ //选择 节点 clz=android.widget.ViewGroup 所有节点 var node = clz("android.widget.ViewGroup").getOneNodeInfo(10000); if (node) { var x= node.child(0); logd(x); } else { toast("无节点"); } } main(); ``` ## allChildren() 获取所有子节点集合 @return NodeInfo 节点集合 ```js function main(){ //选择 节点 clz=android.widget.ViewGroup 所有节点 var node = clz("android.widget.ViewGroup").getOneNodeInfo(10000); if (node) { var x= node.allChildren(); logd(x); } else { toast("无节点"); } } main(); ``` ## siblings() 当前节点的所有兄弟节点集合 @return NodeInfo 节点集合 ```js function main(){ //选择 节点 clz=android.widget.ViewGroup 所有节点 var node = clz("android.widget.ViewGroup").getOneNodeInfo(10000); if (node) { var x= node.siblings(); logd(x); } else { toast("无节点"); } } main(); ``` ## previousSiblings() 在当前节点前面的兄弟节点集合 @return NodeInfo 节点集合 ```js function main(){ //选择 节点 clz=android.widget.ViewGroup 所有节点 var node = clz("android.widget.ViewGroup").getOneNodeInfo(10000); if (node!=null) { var x= node.previousSiblings(); logd(x); } else { toast("无节点"); } } main(); ``` ## nextSiblings() 在当前节点后面的兄弟节点集合 @return NodeInfo 节点集合(数组) ```js function main(){ //选择 节点 clz=android.widget.ViewGroup 所有节点 var node = clz("android.widget.ViewGroup").getOneNodeInfo(10000); if (node) { var x= node.nextSiblings(); logd(x); } else { toast("无节点"); } } main(); ``` ```JS let selector = id('com.ss.android.ugc.aweme:id/dch').getOneNodeInfo(5000) if (selector != null) { var x = selector.previousSiblings(); logd(x.length, x[0].bounds); } ``` ## click() > 执行条件:无障碍7.0以上或者手势执行为代理服务 点击节点,节点区域随机点击 @return bool, true 成功 ,false 失败 ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.CheckBox所有节点 var node = clz("android.widget.CheckBox").getOneNodeInfo(10000); if (node) { node.click() } else { toast("无节点"); } } main(); ``` ## clickEx() > 执行条件:无障碍5.0以上或者手势执行为代理服务 无指针方式点击选择器,节点必须是可点击的才行 @return {boolean|布尔型} ```js function main(){ var node = text("我是文本").getOneNodeInfo(10000); var result = node.clickEx(); if (result){ toast("点击成功"); } else { toast("点击失败"); } } main(); ``` ## longClick() > 执行条件:无障碍7.0以上或者手势执行为代理服务 长点击节点 @return bool, true 成功 ,false 失败 ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.CheckBox所有节点 var node = clz("android.widget.CheckBox").getOneNodeInfo(10000); if (node) { node.longClick() } else { toast("无节点"); } } main(); ``` ## longClickEx() > 执行条件:无障碍5.0以上或者手势执行为代理服务 无指针方式长点击选择器,节点必须是可点击的才行 @return {boolean|布尔型} ```js function main(){ var node = text("我是文本").getOneNodeInfo(10000); var result = node.longClickEx(); if (result){ toast("点击成功"); } else { toast("点击失败"); } } main(); ``` ## clickCenter() > 执行条件:无障碍7.0以上或者手势执行为代理服务 节点点击中心点 @return bool, true 成功 ,false 失败 ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.CheckBox所有节点 var node = clz("android.widget.CheckBox").getOneNodeInfo(10000); if (node) { node.clickCenter(); } else { toast("无节点"); } } main(); ``` ## refresh() 刷新节点缓存 ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.EditText 所有节点 var node = clz("android.widget.EditText").getOneNodeInfo(10000); if (node) { node.refresh(); } else { toast("无节点"); } } main(); ``` ## isValid() 节点信息是否有效 @return bool|布尔型 true代表有 ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.EditText 所有节点 var node = clz("android.widget.EditText").getOneNodeInfo(10000); if (node) { var x =node.isValid(); toast("节点有效性:"+x); } else { toast("无节点"); } } main(); ``` ## scrollForward() > 执行条件:无障碍5.0以上或者手势执行为代理服务 向前滚动 @return {boolean|布尔型} ```js function main(){ var node = scrollable(true).getOneNodeInfo(10000); var result = node.scrollForward(); if (result){ toast("滚动成功"); } else { toast("滚动失败"); } } main(); ``` ## scrollBackward() > 执行条件:无障碍5.0以上或者手势执行为代理服务 向后滚动 @return {boolean|布尔型} ```js function main(){ var node = scrollable(true).getOneNodeInfo(10000); var result = node.scrollBackward(); if (result){ toast("滚动成功"); } else { toast("滚动失败"); } } main(); ``` ## inputText(content) > 执行条件:无障碍5.0以上或者手势执行为代理服务 对某个节点输入数据 @param content 要输入的内容 @return bool, true 成功 ,false 失败 ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.EditText 所有节点 var node = clz("android.widget.EditText").getOneNodeInfo(10000); if (node) { node.inputText("飞云编程学院") } else { toast("无节点"); } } main(); ``` ## imeInputText(content) > 执行条件:无障碍5.0以上或者手势执行为代理服务 使用输入法对某个节点输入数据,前提是已经设置本程序的输入法为默认输入法 @param content 要输入的内容 @return bool, true 成功 ,false 失败 ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.EditText 所有节点 var node = clz("android.widget.EditText").getOneNodeInfo(10000); if (node) { logd(node.imeInputText("飞云编程学院")) } else { toast("无节点"); } } main(); ``` ## imeInputKeyCode(selectors,KEYCODE) 使用输入法输入内容,前提是已经设置本程序的输入法为默认输入法 适合没有节点的情况,例如游戏等 @param selectors 选择器,可以为空,如果为空,前提是输入框是聚焦的状态 @param KEYCODE 具体请看 KeyEvent.KEYCODE_*的值,例如66 = enter 67=del,84=SEARCH @return {boolean|布尔型} ```js // Demo By 飞云编程学院 if(agentEvent.setCurrentIme()) { var selector = clz("android.widget.EditText"); var node = selector.getOneNodeInfo(1000) if (selector) { node.imeInputText("学脚本那里去?") imeInputKeyCode(selector,66) sleep(200); node.imeInputText("飞云编程学院") imeInputKeyCode(selector,66) sleep(200); node.imeInputText("网址是多少?") imeInputKeyCode(selector,66) sleep(200); node.imeInputText("www.feiyunjs.com") } else { toast("无节点"); } logd('恢复输入法:' + agentEvent.restoreIme()); } else { loge('设置输入法失败'); } ``` ## clearText() > 执行条件:无障碍5.0以上或者手势执行为代理服务 清除节点文本数据 ```js function main(){ //获取选择器对象 //选择 节点 clz=android.widget.EditText 所有节点 var node = clz("android.widget.EditText").getOneNodeInfo(); if (node) { var r =node.clearText(); logd("r -=> "+r); } else { toast("无节点"); } } main(); ``` # 节点操作 ## setFetchNodeMode(mode,fetchInvisibleNode,fetchNotImportantNode,algorithm) 设置获取节点的模式 @param mode 1 是增强型, 2 是快速型,默认是增强型 @param fetchInvisibleNode 是否抓取隐藏的元素,默认不抓取 @param fetchNotImportantNode 是否抓取不重要的元素 @param algorithm 节点查找算法,默认是nsf - nsf 节点静态算法 - bsf 广度优先 - dsf 深度优先 @return {boolean|*} ```js function main(){ var result = setFetchNodeMode(1,true,true,"nsf"); toast("result:"+result); } main(); ``` ## lastNodeEventTime() > 适用版本(EC 5.14.0+) 获取最近的节点事件触发时间,可通过时间判断节点服务是否可用 @return {long} 长整型时间,毫秒级别 ```js function main(){ startEnv(); logd("开始监听"); while(true){ let d = lastNodeEventTime(); logd("time-> "+d); sleep(1000) } } main(); ``` ## has(selectors) 直接通过选择器判断元素是否存在 @param selectors 选择器 @return {null|布尔型} ```js function main(){ var selectors = text("设置"); var result = has(selectors); if (result){ toast("存在节点"); } else { toast("不存在节点"); } } main(); ``` ```js // Demo By 飞云编程学院 let selectors = text('我').visible(true); if (has(selectors)) { logd('存在'); } else { logw('不存在'); } ``` ## waitExistNode(selectors,timeout) 等待时长,通过选择器判断元素是否存在 @param selectors 选择器 @param timeout 超时时间,单位毫秒 @return {null|布尔型} ```js function main(){ var selectors = text("设置"); var result = waitExistNode(selectors,10000); if (result){ toast("存在节点"); } else { toast("不存在节点"); } } main(); ``` ```js // Demo By 飞云编程学院 //等待组件出现,使用visible(true)筛选客户区组件 let selectors = text('喜欢').visible(true); if(waitExistNode(selectors,5000)) { logd('组件出现'); } else { logw('组件未出现'); }; ``` ## waitExistActivity(activity,timeout) 等待activity界面出现 @param activity 界面名称 @param timeout 超时时间,单位毫秒 @return {null|布尔型} ```js function main(){ var ac = "com.xxx.MainActivity"; var result = waitExistActivity(ac,10000); if (result){ toast("存在界面"); } else { toast("不存在界面"); } } main(); ``` ```js // Demo By 飞云编程学院 // 页面没出现,返回null // 页面出现,返回true if(waitExistActivity('com.ss.android.ugc.aweme.main.MainActivity',5000)) { logd('页面出现'); } else { logw('页面未出现'); }; ``` ## getText(selectors) 获取选择器文本 @param selectors 选择器 @return {字符串数组|null|字符串集合} ```js function main(){ var selectors = clz("android.widget.TextView"); var result = getText(selectors); toast("result:"+result); } main(); ``` ```js // Demo By 飞云编程学院 let selectors = id("com.ss.android.ugc.aweme:id/bfk").visible(true); logd(getText(selectors)); ``` ## getOneNodeInfo(selectors,timeout) 通过选择器 获取第一个节点信息 @param selectors 选择器 @param timeout 等待时间,单位是毫秒 @return NodeInfo 对象或者null ```js function main(){ var result = getOneNodeInfo(clz("android.widget.TextView"),10*1000); toast("result:"+result); if (result){ result.click(); } } main(); ``` ```js // Demo By 飞云编程学院 let selectors = id("com.ss.android.ugc.aweme:id/enk").visible(true); let result = getOneNodeInfo(selectors,2000); logd(result); logd(result.text); ``` ## getNodeInfo(selectors,timeout) 获取节点信息集合 @param selectors 选择器 @param timeout 等待时间,单位是毫秒 @return {null|NodeInfo数组|节点信息集合} ```js function main(){ var result = getNodeInfo(clz("android.widget.TextView"),10*1000); toast("result:"+result); } main(); ``` ```js // Demo By 飞云编程学院 let selectors = id("com.ss.android.ugc.aweme:id/enk").visible(true); let nodes = getNodeInfo(selectors,2000); logd(nodes); logd(nodes.length); for (let i = 0; i < nodes.length; i++) { logd(nodes[i].text); } ``` #### 抖音发送随机表情 ```js // Demo By 飞云编程学院 let selectors = id("com.ss.android.ugc.aweme:id/bfc").clickable(true); if (has(selectors)) { // 遍历组件 var result = getNodeInfo(selectors, 1000); if (result.length > 0) { let num = random(0, result.length); // 随机点击一个表情 if (result[num].click()) { logd('已发送第 ' + (num + 1) + '个表情') } } } else { loge('未找到随机表情'); } ``` ## getNodeAttrs(selectors,attr) 获取节点属性信息 @param selectors 选择器 @param attr 属性值,例如 text,className,更多的属性请参考NodeInfo对象属性 @return {null|字符串数组|Rect对象数组} ```js function main(){ var selectors = clz("android.widget.TextView"); //获取所有text属性 var result = getNodeAttrs(selectors,"text"); toast("result:"+result); //获取所有bounds属性 result = getNodeAttrs(selectors,"bounds"); toast("result:"+result); } main(); ``` ```js // Demo By 飞云编程学院 let selectors = id("com.ss.android.ugc.aweme:id/enk").visible(true); logd(getNodeAttrs(selectors,'text')); ``` ## addNodeFlag(flag) 加上节点获取的某个标志位 @param flag 参见 AccessibilityServiceInfo.FLAG_*,如果是0是强制刷新 @return {null|boolean} ```js function main(){ var result = addNodeFlag(0); toast("result:"+result); } main(); ``` ## removeNodeFlag(flag) 移除节点获取的某个标志位 @param flag 参见 AccessibilityServiceInfo.FLAG_*,如果是0是强制刷新 @return {null|boolean} ```js function main(){ var result = removeNodeFlag(0); toast("result:"+result); } main(); ``` ## dumpXml() 将元素节点变成XML @return string string|null ```js function main(){ var result = dumpXml(); if (result){ toast("ok"); } else { toast("no"); } } main(); ``` ## lockNode() 锁定当前节点,锁定后,后面就算界面刷新,但是节点还是老的信息,需要和releaseNode进行配合才能进行解锁 ```js function main(){ logd("锁住节点...") //锁住节点,界面刷新也不动 lockNode(); for (var i = 0; i < 10; i++) { var n = text("设置").getOneNodeInfo(1000); logd("lock "+n) sleep(1000); } logd("释放节点锁...") //释放节点锁 releaseNode(); for (var i = 0; i < 10; i++) { var n = text("设置").getOneNodeInfo(1000); logd("unlocked "+n) sleep(1000); } } main(); ``` ## releaseNode() 释放节点的锁,释放后,当界面刷新的时候,节点信息会变成最新的 ```js function main(){ logd("锁住节点...") //锁住节点,界面刷新也不动 lockNode(); for (var i = 0; i < 10; i++) { var n = text("设置").getOneNodeInfo(1000); logd("lock "+n) sleep(1000); } logd("释放节点锁...") //释放节点锁 releaseNode(); for (var i = 0; i < 10; i++) { var n = text("设置").getOneNodeInfo(1000); logd("unlocked "+n) sleep(1000); } } main(); ``` # 实例 ```js let selector = id("com.ss.android.ugc.aweme:id/bfg"); if (selector) { logd(JSON.stringify(getNodeRectEx(selector))); var x = getNodeRectEx(selector).centerX; var y = getNodeRectEx(selector).centerY; logd(clickPoint(x,y)); } /** @description 获取节点尺寸等信息 @version 20201229 @author 飞云<283054503@qq.com> @param selectors {object} :组件选择器 @return {object | null}:返回节点信息对象,失败返回null */ function getNodeRectEx(selector) { if (selector) { let node = selector.getOneNodeInfo(10 * 1000); if (node) { let nodeInfo = { 'top': node.bounds.top, 'right': node.bounds.right, 'bottom': node.bounds.bottom, 'left': node.bounds.left, 'width': node.bounds.right - node.bounds.left, 'height': node.bounds.bottom - node.bounds.top, 'centerX': Math.round((node.bounds.right + node.bounds.left) / 2), 'centerY': Math.round((node.bounds.bottom + node.bounds.top) / 2), } // logd(JSON.stringify(nodeInfo)); return nodeInfo; } } } ``` ```js let selectors = id("com.ss.android.ugc.aweme:id/b3n").visible(true); if (selectors) { logd(getNodeCenterX(selectors)); logd(getNodeCenterY(selectors)); logd(getNodeWidth(selectors)); logd(getNodeHeight(selectors)); } /** @description 取组件中心点X坐标 @version 20201129 @author 飞云<283054503@qq.com> @param selectors {object} :组件选择器 @return {int}:返回整数型坐标,失败返回null */ function getNodeCenterX(selectors) { let node = selectors.getOneNodeInfo(1000); if (node) { let bounds = node.bounds; let centerX = (bounds.left + bounds.right) / 2; return Math.round(centerX); } } /** @description 取组件中心点Y坐标 @version 20201129 @author 飞云<283054503@qq.com> @param selectors {object} :组件选择器 @return {int}:返回整数型坐标,失败返回null */ function getNodeCenterY(selectors) { let node = selectors.getOneNodeInfo(1000); if (node) { let bounds = node.bounds; let centerY = (bounds.top + bounds.bottom) / 2; return Math.round(centerY); } } /** @description 取组件宽度 @version 20201129 @author 飞云<283054503@qq.com> @param selectors {object} :组件选择器 @return {int}:返回整数型,失败返回null */ function getNodeWidth(selectors) { let node = selectors.getOneNodeInfo(1000); if (node) { let bounds = node.bounds; let width = bounds.right - bounds.left; return Math.round(width); } } /** @description 取组件高度 @version 20201129 @author 飞云<283054503@qq.com> @param selectors {object} :组件选择器 @return {int}:返回整数型,失败返回null */ function getNodeHeight(selectors) { let node = selectors.getOneNodeInfo(1000); if (node) { let bounds = node.bounds; let height = bounds.bottom - bounds.top; return Math.round(height); } } ```