根据上一篇文章《UiAutomator源码分析之注入事件》开始时提到的计划,这一篇文章我们要分析的是第二点:

  • 如何获取控件信息

我们在测试脚本中初始化一个UiObject的时候通常是像以下这个样子:
UiObject appsTab = new UiObject(new UiSelector().text("Apps"));
appsTab.click()

那么这个过程发生了什么呢?这就是我们接下来要说的事情了。

 

1. 获取控件信息顺序图

这里依然是一个手画的不规范的顺序图,描述了UiObject尝试获得一个控件的过程中与相关的类的交互,这些类的关系在《UiAutomator源码分析之UiAutomatorBridge框架》中已经进行了描述。

这里整一个过程并不复杂,简单说明下就这几点:
  • 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格式从上面获得根节点开始遍历窗口控件树,以获得我们的目标控件
好,我们继续往下进入getRootNode:
/*     */   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格式从上面获得根节点开始遍历窗口控件树,以获得我们的目标控件
里面的算法细节我就不打算去研究了,里面考虑到选择子嵌套的情况,分析起来也比较费力,且了解了它的算法对我去立交uiautomator的运行原理并没有非常大的帮助,我只需要知道给定一棵树的根,然后制定了我想要的叶子的属性,那么我遍历整棵树肯定是可以找到我想要的那个/些满足要求的控件的。大家由兴趣了解其算法的话还是自行去研究吧。
 

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.系列结语

UiAutomator源码分析这个系列到了这篇文章算是完结了,从启动运行,到核心的UiAutomatorBridge架构,到实例解剖,通过这些文章我相信大家已经很清楚uiautomator这个运用了UiAutomation框架与AccessibilityService通信的测试框架是怎么回事了,置于uiautomator那5个专供测试用例调用的类是怎么回事,网上可获得的信息不少,我这里就没有必要做从新造轮子的事情了,况且这些已经不是uiautomator这个框架的核心了,它们只是运用了UiAutomatorBridge这个核心的一些类而已。
作者 自主博客 微信服务号及扫描码 CSDN
天地会珠海分舵 http://techgogogo.com 服务号:TechGoGoGo扫描码: http://blog.csdn.net/zhubaitian

UiAutomator源码分析之获取控件信息的更多相关文章

  1. UiAutomator源代码分析之获取控件信息

    依据上一篇文章<UiAutomator源代码分析之注入事件>開始时提到的计划,这一篇文章我们要分析的是第二点: 怎样获取控件信息 我们在測试脚本中初始化一个UiObject的时候一般是像下 ...

  2. UiAutomator源码分析之UiAutomatorBridge框架

    上一篇文章<UIAutomator源码分析之启动和运行>我们描述了uitautomator从命令行运行到加载测试用例运行测试的整个流程,过程中我们也描述了UiAutomatorBridge ...

  3. UiAutomator源码分析之注入事件

    上一篇文章<UiAutomator源码分析之UiAutomatorBridge框架>中我们把UiAutomatorBridge以及它相关的类进行的描述,往下我们会尝试根据两个实例将这些类给 ...

  4. Android自动化测试中AccessibilityService获取控件信息(1)

    Android自动化测试中AccessibilityService获取控件信息(1) 分类: android自动化测试2014-03-24 15:31 3455人阅读 评论(16) 收藏 举报 and ...

  5. Android自动化测试中AccessibilityService获取控件信息(2)-三种方式对比

    Android自动化测试中AccessibilityService获取控件信息(2)-三种方式对比   上一篇文章: Android自动化测试中AccessibilityService获取控件信息(1 ...

  6. UIAutomator源码分析之启动和运行

    通过上一篇<Android4.3引入的UiAutomation新框架官方简介>我们可以看到UiAutomator其实就是使用了UiAutomation这个新框架,通过调用Accessibi ...

  7. 【转】UIAutomator源码分析之启动和运行

    我们可以看到UiAutomator其实就是使用了UiAutomation这个新框架,通过调用AccessibilitService APIs来获取窗口界面控件信息已经注入用户行为事件,那么今天开始我们 ...

  8. lodash源码分析之获取数据类型

    所有的悲伤,总会留下一丝欢乐的线索,所有的遗憾,总会留下一处完美的角落,我在冰峰的深海,寻找希望的缺口,却在惊醒时,瞥见绝美的阳光! --几米 本文为读 lodash 源码的第十八篇,后续文章会更新到 ...

  9. springMVC源码分析--AbstractHandlerMethodMapping获取url和HandlerMethod对应关系(十)

    在之前的博客springMVC源码分析--AbstractHandlerMapping(二)中我们介绍了AbstractHandlerMethodMapping的父类AbstractHandlerMa ...

随机推荐

  1. 基于android 社会的app短信分享 发送回调事件的实现

    摘要 前一段时间.由于项目的需要,采用ShareSDK该共享功能.其中包含 短信股吧.和呼叫系统,以分享要与成功处理服务器交互的消息后,(我不在乎在这里,收到.仅仅关心发出去了).可是ShareSDk ...

  2. Binary Tree Inorder Traversal(转)

    Given a binary tree, return the inorder traversal of its nodes' values. For example: Given binary tr ...

  3. nginx源代码分析--读请求主体(1)

    首先,读取请求体已进入HTTP要求11相,我们需要做的请求正文部分处理一些模块,所以这个模块需要注册功能在这个阶段,在阅读功能要求的身体ngx_http_read_client_request_bod ...

  4. 初步boost之pool图书馆学习笔记

    pool 内存池概述 通常我们习惯直接使用new.malloc等API申请分配内存,这样做的缺点在于:因为所申请内存块的大小不定.当频繁使用时会造成大量的内存碎片并进而减少性能. 内存池则是在真正使用 ...

  5. 中文乱码?不,是 HTML 实体编码!(转)

    在 如何用 Nodejs 分析一个简单页面 一文中,我们爬取了博客园首页的 20 篇文章标题,输出部分拼接了一个字符串: var $ = cheerio.load(sres.text); var an ...

  6. RH253读书笔记(8)-Lab 8 Securing Data

    Lab 8 Securing Data Goal: Gain familiarity with encryption utilities Sequence 1: Using SSH keys with ...

  7. 新秀nginx源代码分析数据结构篇(四)红黑树ngx_rbtree_t

    新秀nginx源代码分析数据结构篇(四)红黑树ngx_rbtree_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csd ...

  8. Sql示例说明如何分组后求中间值--【叶子】

    原文:Sql示例说明如何分组后求中间值--[叶子] 这里所谓的分组后求中间值是个什么概念呢? 我举个例子来说明一下: 假设我们现在有下面这样一个表: type        name price -- ...

  9. Java Date API demo

    package date; import java.text.DateFormat; import java.util.Calendar; import java.util.Date; /*2015- ...

  10. Angular绑定数据时转义html标签

    AngularJs在绑定数据时默认会以文本的形式出现在页面上,比如我现在有这样一段代码 <div ng-controller="testCtrl">{{data}}&l ...