Appium Android Bootstrap控制源代码的分析AndroidElement

通过上一篇文章中《Appium Android Bootstrap源代码分析之简单介绍》我们对bootstrap的定义以及其在appium和uiautomator处于一个什么样的位置有了一个初步的了解,那么依照正常的写书的思路,下一个章节应该就要去看bootstrap是如何建立socket来获取数据然后如何进行处理的了。

但本人认为这样子做并不会太好,由于到时整篇文章会变得非常的冗长,由于你在编写的过程中碰到不认识的类又要跳入进去进行说明分析。

这里我认为应该尝试吸取著名的《重构》这本书的建议:一个方法的代码不要写得太长,不然可读性会非常差,尽量把其分解成不同的函数。

那我们这里就是用类似的思想,不要尝试在一个文章中把全部的事情都做完,而是尝试先把关键的类给描写叙述清楚。最后才去把这些类通过一个实例分析给串起来呈现给读者,这样大家就不会由于一个文章太长影响可读性而放弃往下学习了。

那么我们这里为什么先说bootstrap对控件的处理,而非刚才提到的socket相关的socketserver的建立呢?我是这样子看待的。大家看到本人这篇文章的时候,非常有可能之前已经了解过本人针对uiautomator源代码分析那个系列的文章了,或者已经有uiautomator的相关知识,所以脑袋里会比較迫切的想知道到底appium是怎么运用了uiautomator的,那么在appium中于这个问题最贴切的就是appium在server端是怎么使用了uiautomator的控件的。

这里我们主要会分析两个类:

  • AndroidElement:代表了bootstrap持有的一个ui界面的控件的类。它拥有一个UiObject成员对象和一个代表其在以下的哈希表的键值的String类型成员变量id
  • AndroidElementsHash:持有了一个包括全部bootstrap(也就是appium)以前见到过的(也就是脚本代码中findElement方法找到过的)控件的哈希表,它的key就是AndroidElement中的id,每当appium通过findElement找到一个新控件这个id就会+1,Appium的pc端和bootstrap端都会持有这个控件的id键值,当须要调用一个控件的方法时就须要把代表这个控件的id键值传过来让bootstrap能够从这个哈希表找到相应的控件

1. AndroidElement和UiObject的组合关系

从上面的描写叙述我们能够知道,AndroidElement这个类里面拥有一个UiObject这个变量:

public class AndroidElement {

  private final UiObject el;
  private String         id;
  ...
}

大家都知道UiObject事实上就是UiAutomator里面代表一个控件的类,通过它就行对控件进行操作(当然终于还是通过UiAutomation框架). AnroidElement就是通过它来跟UiAutomator发生关系的。我们可以看到以下的AndroidElement的点击click方法事实上就是非常干脆的调用了UiObject的click方法:

  public boolean click() throws UiObjectNotFoundException {
    return el.click();
  }

当然这里除了click还有非常多控件相关的操作。比方dragTo,getText,longClick等,但无一例外,都是通过UiObject来实现的。这里就不一一列举了。

2. 脚本的WebElement和Bootstrap的AndroidElement的映射关系

我们在脚本上对控件的认识就是一个WebElement:

WebElement addNote =  driver.findElementByAndroidUIAutomator("new UiSelector().text(\"Add note\")");

而在Bootstrap中一个对象就是一个AndroidElement. 那么它们是怎么映射到一起的呢?我们事实上能够先看例如以下的代码:

        WebElement addNote = driver.findElementByAndroidUIAutomator("new UiSelector().text(\"Add note\")");
        addNote.getText();
        addNote.click();

做的事情就是获得Notes这个app的菜单,然后调用控件的getText来获得‘Add note‘控件的文本信息,以及通过控件的click方法来点击该控件。那么我们看下调试信息是如何的:

pc端传过来的json字串有几个fields:

  • cmd:代表这个是什么命令类型,事实上就是AndroidCommandType的那两个值
package io.appium.android.bootstrap;

/**
 * Enumeration for all the command types.
 *
 */
public enum AndroidCommandType {
  ACTION, SHUTDOWN
}
  • action: 详细命令
  • params: 提供的參数。这里提供了一个elementId的键值对

从上面的两条调试信息看来,事实上没有明显的看到到底使用的是哪个控件。事实上这里不起眼的elementId就是确定用的是哪个控件的,注意这个elementId并非一个控件在界面上的资源id。它事实上是Bootstrap维护的一个保存全部已经获取过的控件的哈希表的键值。

如上一小节看到的。每个AndroidElement都有两个重要的成员变量:

  • UiObject el :uiautomator框架中代表了一个真实的窗体控件
  • Sting id :  一个唯一的自己主动添加的字串类型整数,pc端就是通过它来在AndroidElementHash这个类中找到想要的控件的

3. AndroidElement控件哈希表

上一节我们说到appium pc端是通过id把WebElement和目标机器端的AndroidElement映射起来的,那么我们这一节就来看下维护AndroidElement的这个哈希表是怎么实现的。

首先,它拥有两个成员变量:

  private final Hashtable<String, AndroidElement> elements;
  private       Integer                           counter;
  • elements :一个以AndroidElement 的id的字串类型为key,以AndroidElement的实例为value的的哈希表
  • counter : 一个整型变量。有两个作用:其一是它代表了当前已经用到的控件的数目(事实上也不全然是,你在脚本中对同一个控件调用两次findElement事实上会产生两个不同id的AndroidElement控件),其二是它代表了一个新用到的控件的id,而这个id就是上面的elements哈希表的键

这个哈希表的键值都是从0開始的。请看它的构造函数:

  /**
   * Constructor
   */
  public AndroidElementsHash() {
    counter = 0;
    elements = new Hashtable<String, AndroidElement>();
  }

而它在整个Bootstrap中是有且仅仅有一个实例的。且看它的单例模式实现:

  public static AndroidElementsHash getInstance() {
    if (AndroidElementsHash.instance == null) {
      AndroidElementsHash.instance = new AndroidElementsHash();
    }
    return AndroidElementsHash.instance;
  }

下面添加一个控件的方法addElement充分描写叙述了为什么说counter是一个自添加的key,且是每一个新发现的AndroidElement控件的id:

  public AndroidElement addElement(final UiObject element) {
    counter++;
    final String key = counter.toString();
    final AndroidElement el = new AndroidElement(key, element);
    elements.put(key, el);
    return el;
  }

从Appium发过来的控件查找命令慷慨向上分两类:

  • 1. 直接基于Appium Driver来查找,这样的情况下appium发过来的json命令是不包括控件哈希表的键值信息的

[java] view
plain
copy

  1. WebElement addNote = driver.findElement(By.name("Add note"));
  • 2. 基于父控件查找:

[java] view
plain
copy

  1. WebElement el = driver.findElement(By.className("android.widget.ListView")).findElement(By.name("Note1"));

以上的脚本会先尝试找到Note1这个日记的父控件ListView,并把这个控件保存到控件哈希表,然后再依据父控件的哈希表键值以及子控件的选择子找到想要的Note1:

AndroidElementHash的这个getElement命令要做的事情就是针对这两点来依据不同情况获得目标控件

[java] view
plain
copy

  1. /**
  2. * Return an elements child given the key (context id), or uses the selector
  3. * to get the element.
  4. *
  5. * @param sel
  6. * @param key
  7. *          Element id.
  8. * @return {@link AndroidElement}
  9. * @throws ElementNotFoundException
  10. */
  11. public AndroidElement getElement(final UiSelector sel, final String key)
  12. throws ElementNotFoundException {
  13. AndroidElement baseEl;
  14. baseEl = elements.get(key);
  15. UiObject el;
  16. if (baseEl == null) {
  17. el = new UiObject(sel);
  18. } else {
  19. try {
  20. el = baseEl.getChild(sel);
  21. } catch (final UiObjectNotFoundException e) {
  22. throw new ElementNotFoundException();
  23. }
  24. }
  25. if (el.exists()) {
  26. return addElement(el);
  27. } else {
  28. throw new ElementNotFoundException();
  29. }
  30. }
  • 假设是第1种情况就直接通过选择子构建UiObject对象,然后通过addElement把UiObject对象转换成AndroidElement对象保存到控件哈希表
  • 假设是第2种情况就先依据appium传过来的控件哈希表键值获得父控件。再通过子控件的选择子在父控件的基础上查找到目标UiObject控件,最后跟上面一样把该控件通过上面的addElement把UiObject控件转换成AndroidElement控件对象保存到控件哈希表

4. 求证

上面有提过。假设pc端的脚本运行对同一个控件的两次findElement会创建两个不同id的AndroidElement并存放到控件哈希表中,那么为什么appium的团队没有做一个增强,添加一个keyMap的方法(算法)和一些额外的信息来让同一个控件使用不同的key的时候相应的还是同一个AndroidElement控件呢?毕竟这才是哈希表有用的特性之中的一个了,不然你直接用一个Dictionary不就完事了?网上说了几点hashtable和dictionary的区别,如多线程环境最好使用哈希表而非字典等。但在bootstrap这个控件哈希表的情况下我不是非常信服这些说法。有谁清楚的还劳烦指点一二了

这里至于为什么appium不去提供额外的key信息而且实现keyMap算法,我个人倒是觉得有例如以下原因:

  • 有谁这么无聊在同一个測试方法中对同一个控件查找两次?
  • 假设同一个控件运用不同的选择子查找两次的话。由于终于底层的UiObject的成员变量UiSelector mSelector不一样,所以确实能够觉得是不同的控件

但下面两个假设用相同的UiSelector选择子来查找控件的情况我就解析不了了。毕竟在我看来bootstrap这边应该把它们看成是同一个对象的:

  • 同一个脚本不同的方法中分别对同一控件用相同的UiSelelctor选择子进行查找呢?
  • 不同脚本中呢?

这些或许在今后深入了解中得到解决。但看家假设知道的,还望指教

5. 小结

最后我们对bootstrap的控件相关知识点做一个总结

  • AndroidElement的一个实例代表了一个bootstrap的控件
  • AndroidElement控件的成员变量UiObject el代表了uiautomator框架中的一个真实窗体控件,通过它就能够直接透过uiautomator框架对控件进行实质性操作
  • pc端的WebElement元素和Bootstrap的AndroidElement控件是通过AndroidElement控件的String id进行映射关联的
  • AndroidElementHash类维护了一个以AndroidElement的id为键值,以AndroidElement的实例为value的全局唯一哈希表,pc端想要获得一个控件的时候会先从这个哈希表查找,假设没有了再创建新的AndroidElement控件并增加到该哈希表中。所以该哈希表中维护的是一个当前已经使用过的控件
 

作者


自主博客


微信


CSDN


天地会珠海分舵


http://techgogogo.com


服务号:TechGoGoGo

扫描码:

tp=webp" style="max-width:100%; margin:0px; padding:0px; height:auto!important; word-wrap:break-word!important; width:auto!important; visibility:visible!important">


http://blog.csdn.net/zhubaitian

版权声明:本文博主原创文章,博客,未经同意不得转载。

时间: 2024-10-05 16:49:46

Appium Android Bootstrap控制源代码的分析AndroidElement的相关文章

Appium Android Bootstrap源码分析之控件AndroidElement

通过上一篇文章<Appium Android Bootstrap源码分析之简介>我们对bootstrap的定义以及其在appium和uiautomator处于一个什么样的位置有了一个初步的了解,那么按照正常的写书的思路,下一个章节应该就要去看bootstrap是如何建立socket来获取数据然后怎样进行处理的了.但本人觉得这样子做并不会太好,因为到时整篇文章会变得非常的冗长,因为你在编写的过程中碰到不认识的类又要跳入进去进行说明分析.这里我觉得应该尝试吸取著名的<重构>这本书的建议

Appium Android Bootstrap源码分析之命令解析执行

通过上一篇文章<Appium Android Bootstrap源码分析之控件AndroidElement>我们知道了Appium从pc端发送过来的命令如果是控件相关的话,最终目标控件在bootstrap中是以AndroidElement对象的方式呈现出来的,并且该控件对象会在AndroidElementHash维护的控件哈希表中保存起来.但是appium触发一个命令除了需要提供是否与控件相关这个信息外,还需要其他的一些信息,比如,这个是什么命令?这个就是我们这篇文章需要讨论的话题了. 下面我

Appium Android Bootstrap源码分析之简介

在上一个系列中我们分析了UiAutomator的核心源码,对UiAutomator是怎么运行的原理有了根本的了解.今天我们会开始另外一个在安卓平台上基于UiAutomator的新起之秀--Appium的源码分析之旅. 本文在真个系列中会扮演一个简介的角色,不会分析任何的代码,只会先给大家一个基本的印象,方便大家在持有这个印象的基础上往下和本人一块分析. 1. Bootstrap定义及在Appium中扮演的角色 我们先看一下本人大概一个月之前刚接触appium时整理的一个高层架构图 下面一部分就是

《Android系统源代码情景分析》连载回忆录:灵感之源

上个月,在花了一年半时间之后,写了55篇文章,分析完成了Chromium在Android上的实现,以及Android基于Chromium实现的WebView.学到了很多东西,不过也挺累的,平均不到两个星期一篇文章.本来想休息一段时间后,再继续分析Chromium使用的JS引擎V8.不过某天晚上,躺在床上睡不着,鬼使神差想着去创建一个个人站点,用来连载<Android系统源代码情景分析>一书的内容. 事情是这样的,躺在床上睡不着,就去申请了一个域名,0xcc0xcd.com.域名申请到了,总不能

Appium 连手机失败Error: Android bootstrap socket crashed: Error: getaddrinfo ENOTFOUND localhost undefined:4724

问题:Appium执行,连接手机报下面的错误 Error: Android bootstrap socket crashed: Error: getaddrinfo ENOTFOUND localhost undefined:4724 at Socket.<anonymous> (lib/bootstrap.js:87:21) at emitOne (events.js:90:13) at Socket.emit (events.js:182:7) at connectErrorNT (net

Android日志系统Logcat源代码简要分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6606957 在前面两篇文章Android日志系统驱动程序Logger源代码分析和Android应用程序框架层和系统运行库层日志系统源代码中,介绍了Android内核空间层.系统运行库层和应用程序框架层日志系统相关的源代码,其中,后一篇文章着重介绍了日志的写入操作.为了描述完整性,这篇文章着重介绍日志的读取操作,这就是我们在开发Android应用

Android Bitmap 开源图片框架分析(精华三)

主要介绍这三个框架,都挺有名的,其他的框架估计也差不多了 Android-Universal-Image-Loaderhttps://github.com/nostra13/Android-Universal-Image-Loader ImageLoaderhttps://github.com/novoda/ImageLoader Volley(综合框架,包含图片部分)https://github.com/mcxiaoke/android-volley 扯淡时间,可以跳过这段这些开源框架的源码还

SEAndroid安全机制对Android属性访问的保护分析

Android系统通过属性暴露设备和运行时信息,并且可以通过设置属性来控制系统行为.因此,属性也像文件一样,是一种需要保护的资源.在启用SEAndroid之前,敏感属性只能被预先设定的进程进行设置.启用SEAndroid之后,敏感属性会进一步被SEAndroid安全策略保护.这样就可以更有效地保护系统属性了.在本文中,我们就详细分析SEAndroid安全机制对Android属性设置保护提供的支持. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! 在分析SE

Android Bitmap 开源图片框架分析(精华五)

本帖最后由 boredream 于 2014-5-27 09:07 编辑 ImageLoader和Volley图片部分还包括其他大部分图片框架,基本上图片处理都差不多,区别仅在于部分优化了,而优化方面UIL即Universal-Image-Loader框架做的最好,所以这部分章节算是温习一下图片处理以及寻找下其他框架里面一些不一样的图片处理方式(只关注图片方面) 首先是ImageLoaderhttps://github.com/novoda/ImageLoader主要还是分析图片加载的核心代码部