程序员人生 网站导航

Appium Android Bootstrap源码分析之控件AndroidElement

栏目:互联网时间:2014-11-17 08:28:57

通过上1篇文章《Appium Android Bootstrap源码分析之简介》我们对bootstrap的定义和其在appium和uiautomator处于1个甚么样的位置有了1个初步的了解,那末依照正常的写书的思路,下1个章节应当就要去看bootstrap是如何建立socket来获得数据然后怎样进行处理的了。但本人觉得这模样做其实不会太好,由于到时整篇文章会变得非常的冗杂,由于你在编写的进程中碰到不认识的类又要跳入进去进行说明分析。这里我觉得应当尝试吸取著名的《重构》这本书的建议:1个方法的代码不要写得太长,不然可读性会很差,尽可能把其分解成不同的函数。那我们这里就是用类似的思想,不要尝试在1个文章中把所有的事情都做完,而是尝试先把关键的类给描写清楚,最后才去把这些类通过1个实例分析给串起来显现给读者,这样大家就不会由于1个文章太长影响可读性而放弃往下学习了。

那末我们这里为何先说bootstrap对控件的处理,而非刚才提到的socket相干的sockethttp://www.wfuyu.com/server/的建立呢?我是这模样看待的,大家看到本人这篇文章的时候,很有可能之前已了解过本人针对uiautomator源码分析那个系列的文章了,或已有uiautomator的相干知识,所以脑袋里会比较迫切的想知道究竟appium是怎样应用了uiautomator的,那末在appium中于这个问题最贴切的就是appium在http://www.wfuyu.com/server/端是怎样使用了uiautomator的控件的。

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

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

1. AndroidElement和UiObject的组合关系

从上面的描写我们可以知道,AndroidElement这个类里面具有1个UiObject这个变量:
public class AndroidElement { private final UiObject el; private String id; ... }
大家都知道UiObject其实就是UiAutomator里面代表1个控件的类,通过它就可以够对控件进行操作(固然终究还是通过UiAutomation框架). AnroidElement就是通过它来跟UiAutomator产生关系的。我们可以看到下面的AndroidElement的点击click方法其实就是很干脆的调用了UiObject的click方法:
public boolean click() throws UiObjectNotFoundException { return el.click(); }
固然这里除click还有很多控件相干的操作,比如dragTo,getText,longClick等,但无1例外,都是通过UiObject来实现的,这里就不逐一罗列了。

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

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

WebElement addNote = driver.findElementByAndroidUIAutomator("new UiSelector().text("Add note")");
而在Bootstrap中1个对象就是1个AndroidElement. 那末它们是怎样映照到1起的呢?我们其实可以先看以下的代码:

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: 提供的参数,这里提供了1个elementId的键值对
从上面的两条调试信息看来,其实没有明显的看到究竟使用的是哪一个控件。其实这里不起眼的elementId就是肯定用的是哪一个控件的,注意这个elementId其实不是1个控件在界面上的资源id,它实际上是Bootstrap保护的1个保存所有已获得过的控件的哈希表的键值。如上1小节看到的,每个AndroidElement都有两个重要的成员变量:

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

3. AndroidElement控件哈希表

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

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

private final Hashtable<String, AndroidElement> elements; private Integer counter;
  • elements :1个以AndroidElement 的id的字串类型为key,以AndroidElement的实例为value的的哈希表
  • counter : 1个整型变量,有两个作用:其1是它代表了当前已用到的控件的数目(其实也不完全是,你在脚本中对同1个控件调用两次findElement其实会产生两个不同id的AndroidElement控件),其2是它代表了1个新用到的控件的id,而这个id就是上面的elements哈希表的键
这个哈希表的键值都是从0开始的,请看它的构造函数:
/** * Constructor */ public AndroidElementsHash() { counter = 0; elements = new Hashtable<String, AndroidElement>(); }
而它在全部Bootstrap中是有且只有1个实例的,且看它的单例模式实现:
public static AndroidElementsHash getInstance() { if (AndroidElementsHash.instance == null) { AndroidElementsHash.instance = new AndroidElementsHash(); } return AndroidElementsHash.instance; }
以下增加1个控件的方法addElement充分描写了为何说counter是1个自增加的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 plaincopy
  1. WebElement addNote = driver.findElement(By.name("Add note"));  
  • 2. 基于父控件查找:
[java] view plaincopy
  1. WebElement el = driver.findElement(By.className("android.widget.ListView")).findElement(By.name("Note1"));  
以上的脚本会先尝试找到Note1这个日记的父控件ListView,并把这个控件保存到控件哈希表,然后再根据父控件的哈希表键值和子控件的选择子找到想要的Note1:

AndroidElementHash的这个getElement命令要做的事情就是针对这两点来根据不同情况取得目标控件
[java] view plaincopy
  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.   
  17.   if (baseEl == null) {  
  18.     el = new UiObject(sel);  
  19.   } else {  
  20.     try {  
  21.       el = baseEl.getChild(sel);  
  22.     } catch (final UiObjectNotFoundException e) {  
  23.       throw new ElementNotFoundException();  
  24.     }  
  25.   }  
  26.   
  27.   if (el.exists()) {  
  28.     return addElement(el);  
  29.   } else {  
  30.     throw new ElementNotFoundException();  
  31.   }  
  32. }  
  • 如果是第1种情况就直接通过选择子构建UiObject对象,然后通过addElement把UiObject对象转换成AndroidElement对象保存到控件哈希表
  • 如果是第2种情况就先根据appium传过来的控件哈希表键值取得父控件,再通过子控件的选择子在父控件的基础上查找到目标UiObject控件,最后跟上面1样把该控件通过上面的addElement把UiObject控件转换成AndroidElement控件对象保存到控件哈希表




4. 求证

上面有提过,如果pc真个脚本履行对同1个控件的两次findElement会创建两个不同id的AndroidElement并寄存到控件哈希表中,那末为何appium的团队没有做1个增强,增加1个keyMap的方法(算法)和1些额外的信息来让同1个控件使用不同的key的时候对应的还是同1个AndroidElement控件呢?毕竟这才是哈希表实用的特性之1了,不然你直接用1个Dictionary不就完事了?网上说了几点hashtable和dictionary的差别,如多线程环境最好使用哈希表而非字典等,但在bootstrap这个控件哈希表的情况下我不是很佩服这些说法,有谁清楚的还劳烦指导12了
这里至于为何appium不去提供额外的key信息并且实现keyMap算法,我个人倒是认为有以下缘由:
  • 有谁这么无聊在同1个测试方法中对同1个控件查找两次?
  • 如果同1个控件应用不同的选择子查找两次的话,由于终究底层的UiObject的成员变量UiSelector mSelector不1样,所以确切可以认为是不同的控件
但以下两个如果用一样的UiSelector选择子来查找控件的情况我就解析不了了,毕竟在我看来bootstrap这边应当把它们看成是同1个对象的:
  • 同1个脚本不同的方法中分别对同1控件用一样的UiSelelctor选择子进行查找呢?
  • 不同脚本中呢?
这些或许在今后深入了解中得到解决,但看家如果知道的,还望不吝赐教

5. 小结

最后我们对bootstrap的控件相干知识点做1个总结
  • AndroidElement的1个实例代表了1个bootstrap的控件
  • AndroidElement控件的成员变量UiObject el代表了uiautomator框架中的1个真实窗口控件,通过它就能够直接透过uiautomator框架对控件进行实质性操作
  • pc真个WebElement元素和Bootstrap的AndroidElement控件是通过AndroidElement控件的String id进行映照关联的
  • AndroidElementHash类保护了1个以AndroidElement的id为键值,以AndroidElement的实例为value的全局唯1哈希表,pc端想要取得1个控件的时候会先从这个哈希表查找,如果没有了再创建新的AndroidElement控件并加入到该哈希表中,所以该哈希表中保护的是1个当前已使用过的控件
------分隔线----------------------------
------分隔线----------------------------

最新技术推荐