Appium Android Bootstrap之控件AndroidElement

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


/**

* Return an elements child given the key (context id), or uses the selector

* to get the element.

*

* @param sel

* @param key

*          Element id.

* @return {@link AndroidElement}

* @throws ElementNotFoundException

*/

public AndroidElement getElement(final UiSelector sel, final String key)

throws ElementNotFoundException {

AndroidElement baseEl;

baseEl = elements.get(key);

UiObject el;

if (baseEl == null) {

el = new UiObject(sel);

} else {

try {

el = baseEl.getChild(sel);

} catch (final UiObjectNotFoundException e) {

throw new ElementNotFoundException();

}

}

if (el.exists()) {

return addElement(el);

} else {

throw new ElementNotFoundException();

}

}

  如果是第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控件并加入到该哈希表中,所以该哈希表中维护的是一个当前已经使用过的控件

相关文章:

Appium Android Bootstrap源码分析之简介

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

  那么我们这里为什么先说bootstrap对控件的处理,而非刚才提到的socket相关的socket服务器的建立呢?我是这样子看待的,大家看到本人这篇文章的时候,很有可能之前已经了解过本人针对uiautomator源码分析那个系列的文章了,或者已经有uiautomator的相关知识,所以脑袋里会比较迫切的想知道究竟appium是怎么运用了uiautomator的,那么在appium中于这个问题最贴切的就是appium在服务器端是怎么使用了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命令是不包含控件哈希表的键值信息的

  WebElement addNote = driver.findElement(By.name("Add note"));

  2. 基于父控件查找:

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

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

最新内容请见作者的GitHub页:http://qaseven.github.io/

时间: 2024-10-30 18:16:03

Appium Android Bootstrap之控件AndroidElement的相关文章

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

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

Android常用布局控件之RelativeLayout

我们使用LinearLayout和TableLayout可以满足开发应用程序界面基本的要求 .但是有时候实现界面的时候不够灵活,我们还可以使用另外一种控件 RelativeLayout.RelativeLayout是一种相对布局的控件,这个容器内部的子元 素们可以使用彼此之间的相对位置或者和容器间的相对位置来进行定位,类似于 网页设计中的CSS.在指定控件的位置时,我们需要指定这个控件与其它控件之 间的相对位置关系,比如说与另一个控件的左边对齐,右对齐,位于另一个控件 的上方,下方等等.一个控件

Android常见布局控件之LinearLayout和TableLayout

一.LinearLayout布局控件 xml属性 android:baselineAligned:是否允许用户调整它内容的基线. android:baselineAlignedChildIndex:当一个线性布局与另一个布局是按基 线对齐的一部分,它可以指定其内容的基线对齐方式. android:gravity:指定控件中内容的基本内容. android:orientation:设置它内容的对其方向,有两个可以选择的值: horizontal和vertical.分别表示水平排列和垂直排列. Li

android之SeekBar控件用法详解_Android

MainActivity.java package com.example.mars_2400_seekbar; import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBar; import android.support.v4.app.Fragment; import android.app.Activity; import android.os.Bundle; import a

android之RatingBar控件用法详解_Android

MainActivity.java package com.example.mars_2500_ratingbar; import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBar; import android.support.v4.app.Fragment; import android.app.Activity; import android.os.Bundle; import

分享Android中ExpandableListView控件使用教程_Android

本文采用一个Demo来展示Android中ExpandableListView控件的使用,如如何在组/子ListView中绑定数据源.直接上代码如下: 程序结构图: layout目录下的 main.xml 文件源码如下: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android

Android编程布局控件之AbsoluteLayout用法实例分析_Android

本文实例讲述了Android编程布局控件之AbsoluteLayout用法.分享给大家供大家参考,具体如下: AbsoluteLayout是绝对布局管理器,指的是指定组件的左上角绝对坐标来指定组件的布局 <?xml version="1.0" encoding="utf-8"?> <AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"

Android Material Design控件学习(三)——使用TextInputLayout实现酷市场登录效果

前言 前两次,我们学习了 Android Material Design控件学习(一)--TabLayout的用法 Android Material Design控件学习(二)--NavigationView的学习和使用 今天我们继续MD控件的学习和使用.在学习之前,我们先来看一下酷市场的登录效果. 实现这种效果的正是我们今天的主角--TextInputLayout. 学习 不管学习什么,首先看它的官方文档.这是最权威,最高效的学习途径. 文档地址:http://developer.androi

android中设置控件获得焦点

android中,要使控件获得焦点,需要先setFocus,再requestFocus. 以Button为例:                 btn.setFocusable(true);                 btn.setFocusableInTouchMode(true);                 btn.requestFocus();                 btn.requestFocusFromTouch(); //获得失去焦点的监听器 btn.setOn