UiAutomator源码分析之获取控件信息
根据上一篇文章《UiAutomator源码分析之注入事件》开始时提到的计划,这一篇文章我们要分析的是第二点:
- 如何获取控件信息
UiObject appsTab = new UiObject(new UiSelector().text("Apps"));
appsTab.click()
那么这个过程发生了什么呢?这就是我们接下来要说的事情了。
1. 获取控件信息顺序图
- UiObject对象几经周折通过不同的类最终联系上UiAutomation,然后通知UiAutomation对象它想取得当前活动窗口的所有元素的AccessibilityNodeInfo类型的根节点
- AccessibilityNodeInfo代表了屏幕中控件元素的一个节点,同时它也拥有一些成员方法可以以当前节点为基础来获得其他目标节点。可以把屏幕上的节点想像成是通过类似xml的格式组织起来的,所以一旦知道根节点和由选择子UiSelector指定的目标控件信息,我们就可以遍历整个窗口控件
- QueryController对象获得Root Node之后,就是调用tranlateCompoundSelector这个方法来遍历窗口所有控件,直到找到选择子UiSelector指定的那个控件为止。
- 注意一个AccessibilityNodeInfo只代表一个控件,遍历的时候一旦需要下一个控件的信息是必须要再次通过UiAutomation去获取的。
2.触发控件查找真正发生的地方
在我没有去分析uiautomator的源代码之前,我一直以为空间查找是在通过UiSelector初始化一个UiObject的时候发生的:
UiObject appsTab = new UiObject(new UiSelector().text("Apps"));
这让我有一种先入为主的感觉,一个控件对象初始化好后应该就已经得到了该控件所代表的节点的所有信息了,但看了源码后发现事实并非如此,以上所做的事情只是以一定的格式准备好UiSelector选择子而已,真正触发uiautomator去获取控件节点信息的是在触发控件事件的时候,比如:
appsTab.click()
我们进入到代表一个控件的UiObject对应的操作控件的方法去看下就清楚了,以上面的click为例:
/* */ public boolean click()
/* */ throws UiObjectNotFoundException
/* */ {
/* 389 */ Tracer.trace(new Object[0]);
/* 390 */ AccessibilityNodeInfo node = findAccessibilityNodeInfo(this.mConfig.getWaitForSelectorTimeout());
/* 391 */ if (node == null) {
/* 392 */ throw new UiObjectNotFoundException(getSelector().toString());
/* */ }
/* 394 */ Rect rect = getVisibleBounds(node);
/* 395 */ return getInteractionController().clickAndSync(rect.centerX(), rect.centerY(), this.mConfig.getActionAcknowledgmentTimeout());
/* */ }
正式290行的调用触发uiautomator去调用UiAutomation去获取到我们想要的控件节点AccessibilityNodeInfo信息的。
3.获得根节点
下面我们看下uiautomator是怎么去获取到代表窗口所有控件的根的Root Node的,我们进入UiObject的findAccessibilityNodeInfo这个方法:
/* */ protected AccessibilityNodeInfo findAccessibilityNodeInfo(long timeout)
/* */ {
/* 164 */ AccessibilityNodeInfo node = null;
/* 165 */ long startMills = SystemClock.uptimeMillis();
/* 166 */ long currentMills = 0L;
/* 167 */ while (currentMills <= timeout) {
/* 168 */ node = getQueryController().findAccessibilityNodeInfo(getSelector());
/* 169 */ if (node != null) {
/* */ break;
/* */ }
/* */
/* 173 */ UiDevice.getInstance().runWatchers();
/* */
/* 175 */ currentMills = SystemClock.uptimeMillis() - startMills;
/* 176 */ if (timeout > 0L) {
/* 177 */ SystemClock.sleep(1000L);
/* */ }
/* */ }
/* 180 */ return node;
/* */ }
UiObject对象会首先去获得一个QueryController对象,然后调用该对象的findAccessibilityNodeInfo同名方法:
/* */ protected AccessibilityNodeInfo findAccessibilityNodeInfo(UiSelector selector, boolean isCounting)
/* */ {
/* 143 */ this.mUiAutomatorBridge.waitForIdle();
/* 144 */ initializeNewSearch();
/* */
/* 146 */ if (DEBUG) {
/* 147 */ Log.d(LOG_TAG, "Searching: " + selector);
/* */ }
/* 149 */ synchronized (this.mLock) {
/* 150 */ AccessibilityNodeInfo rootNode = getRootNode();
/* 151 */ if (rootNode == null) {
/* 152 */ Log.e(LOG_TAG, "Cannot proceed when root node is null. Aborted search");
/* 153 */ return null;
/* */ }
/* */
/* */
/* 157 */ UiSelector uiSelector = new UiSelector(selector);
/* 158 */ return translateCompoundSelector(uiSelector, rootNode, isCounting);
/* */ }
/* */ }
这里做了两个重要的事情:
- 150行:通过调用getRootNode来获得根节点,这个就是我们这个章节的重点
- 158行:通过调用translateCompoundSelector来根据用户指定的UiSelector格式从上面获得根节点开始遍历窗口控件树,以获得我们的目标控件
/* */ protected AccessibilityNodeInfo getRootNode()
/* */ {
/* 168 */ int maxRetry = 4;
/* 169 */ long waitInterval = 250L;
/* 170 */ AccessibilityNodeInfo rootNode = null;
/* 171 */ for (int x = 0; x < 4; x++) {
/* 172 */ rootNode = this.mUiAutomatorBridge.getRootInActiveWindow();
/* 173 */ if (rootNode != null) {
/* 174 */ return rootNode;
/* */ }
/* 176 */ if (x < 3) {
/* 177 */ Log.e(LOG_TAG, "Got null root node from accessibility - Retrying...");
/* 178 */ SystemClock.sleep(250L);
/* */ }
/* */ }
/* 181 */ return rootNode;
/* */ }
172调用的是UiAutomatorBridge对象的方法,通过我们上面的几篇文章我们知道UiAutomatorBridge提供的方法大部分都是直接调用UiAutomation的方法的,我们进去看看是否如此:
/* */ public AccessibilityNodeInfo getRootInActiveWindow() {
/* 66 */ return this.mUiAutomation.getRootInActiveWindow();
/* */ }
果不其然,最终简单明了的直接调用UiAutomation的getRootInActiveWindow来获得根AccessibilityNodeInfo.
4.遍历根节点获得选择子UiSelector指定的控件
如前所述,QueryController的方法findAccessibilityNodeInfo在获得根节点后下来做的第二个事情:
- 158行:通过调用translateCompoundSelector来根据用户指定的UiSelector格式从上面获得根节点开始遍历窗口控件树,以获得我们的目标控件
5.最终还是通过坐标点来点击控件
上面UiObject的Click方法通过UiAutomation这个高大上的新框架获得了代表我们目标控件的AccessibilityNodeInfo后,跟着是不是就直接调用这个节点的Click方法进行点击了呢?其实不是的,首先AccessibilityNodeInfo并没有click这个方法,我们继续看代码:
/* */ public boolean click()
/* */ throws UiObjectNotFoundException
/* */ {
/* 389 */ Tracer.trace(new Object[0]);
/* 390 */ AccessibilityNodeInfo node = findAccessibilityNodeInfo(this.mConfig.getWaitForSelectorTimeout());
/* 391 */ if (node == null) {
/* 392 */ throw new UiObjectNotFoundException(getSelector().toString());
/* */ }
/* 394 */ Rect rect = getVisibleBounds(node);
/* 395 */ return getInteractionController().clickAndSync(rect.centerX(), rect.centerY(), this.mConfig.getActionAcknowledgmentTimeout());
/* */ }
从395行可以看到,最终还是把控件节点的信息转换成控件的坐标点进行点击的,至于怎么点击,大家可以参照上一篇文章,无非就是通过建立一个runnable的线程进行点击事件的注入了
6.系列结语
| 作者 | 自主博客 | 微信服务号及扫描码 | CSDN |
| 天地会珠海分舵 | http://techgogogo.com | 服务号:TechGoGoGo扫描码:![]() |
http://blog.csdn.net/zhubaitian |
UiAutomator源码分析之获取控件信息的更多相关文章
- UiAutomator源代码分析之获取控件信息
依据上一篇文章<UiAutomator源代码分析之注入事件>開始时提到的计划,这一篇文章我们要分析的是第二点: 怎样获取控件信息 我们在測试脚本中初始化一个UiObject的时候一般是像下 ...
- UiAutomator源码分析之UiAutomatorBridge框架
上一篇文章<UIAutomator源码分析之启动和运行>我们描述了uitautomator从命令行运行到加载测试用例运行测试的整个流程,过程中我们也描述了UiAutomatorBridge ...
- UiAutomator源码分析之注入事件
上一篇文章<UiAutomator源码分析之UiAutomatorBridge框架>中我们把UiAutomatorBridge以及它相关的类进行的描述,往下我们会尝试根据两个实例将这些类给 ...
- Android自动化测试中AccessibilityService获取控件信息(1)
Android自动化测试中AccessibilityService获取控件信息(1) 分类: android自动化测试2014-03-24 15:31 3455人阅读 评论(16) 收藏 举报 and ...
- Android自动化测试中AccessibilityService获取控件信息(2)-三种方式对比
Android自动化测试中AccessibilityService获取控件信息(2)-三种方式对比 上一篇文章: Android自动化测试中AccessibilityService获取控件信息(1 ...
- UIAutomator源码分析之启动和运行
通过上一篇<Android4.3引入的UiAutomation新框架官方简介>我们可以看到UiAutomator其实就是使用了UiAutomation这个新框架,通过调用Accessibi ...
- 【转】UIAutomator源码分析之启动和运行
我们可以看到UiAutomator其实就是使用了UiAutomation这个新框架,通过调用AccessibilitService APIs来获取窗口界面控件信息已经注入用户行为事件,那么今天开始我们 ...
- lodash源码分析之获取数据类型
所有的悲伤,总会留下一丝欢乐的线索,所有的遗憾,总会留下一处完美的角落,我在冰峰的深海,寻找希望的缺口,却在惊醒时,瞥见绝美的阳光! --几米 本文为读 lodash 源码的第十八篇,后续文章会更新到 ...
- springMVC源码分析--AbstractHandlerMethodMapping获取url和HandlerMethod对应关系(十)
在之前的博客springMVC源码分析--AbstractHandlerMapping(二)中我们介绍了AbstractHandlerMethodMapping的父类AbstractHandlerMa ...
随机推荐
- SQL Server 2008性能故障排查(一)——概论
原文:SQL Server 2008性能故障排查(一)--概论 备注:本人花了大量下班时间翻译,绝无抄袭,允许转载,但请注明出处.由于篇幅长,无法一篇博文全部说完,同时也没那么快全部翻译完,所以按章节 ...
- Visual Studio Team Services使用教程--Readers tfs组成员添加
- 配置SQL Server 2008的资源调控器实现负载均衡
原文:配置SQL Server 2008的资源调控器实现负载均衡 转自:http://www.ithov.com/server/93267.shtml 1.为什么引入"资源调控器" ...
- 使用WireShark简单分析ICMP报文
ICMP协议介绍 1.ICMP是"Internet Control Message Protocol"(Internet控制消息协议)的缩写. 它是TCP/IP协议族的一个子协议. ...
- Mac下Jekyll安装
之前一直用Wordpress,虽然功能强大,各种插件各种bug,如果想弄个主题,折腾得要命.最近改用jekyll+gitHub免费空间.记录一下. 我用的是Mac,所以只讲述Mac下如何安装,Wind ...
- Winpcap网络编程十之Winpcap实战,两台主机通过中间主机通信
注:源码等等的我不会全然公开的,此篇文章写出来为大家的网络编程或者课程设计提供一定的思路.. 好,本次我们须要完毕的任务是: 完毕两台主机通过中间主机的数据通信(网络层) 添加基于IP地址的转发功能 ...
- 《Java并发编程实战》第二章 线程安全 札记
一个.什么是线程安全 编写线程安全的代码 其核心是管理国事访问的操作. 共享,可变的状态的訪问 - 前者表示多个线程訪问, 后者声明周期内发生改变. 线程安全性 核心概念是正确性.某个类的行为与其规范 ...
- 在InstallShield中发布单一的Setup.exe文件
原文:在InstallShield中发布单一的Setup.exe文件 InstallShield默认的Release模式下,生成了一个setup.exe文件,一个msi文件,以及一些其它的杂项文件. ...
- JS添加节点方法与JQuery添加节点方法的比较及总结
原生JS添加节点方法与JQuery添加节点方法的比较及总结 一.首先构建一个简单布局,来供下边讲解使用 1.HTML部分代码: <div id="div1">div ...
- Oracle常见操作汇总(转)
前言:Oracle学习也有十几天了,但是呢,接下来还要学习许多其他的东西,并不能提步不前,所以在此总结了以下Oracle中常用的命令和语句,没有语法都是实例,以便以后工作的时候随时翻看,毕竟是自己的东 ...
