其实上一篇讲对find的源码分析讲的不太好,因为讲的时候,没让大家明白关于AndroidElement,以及AndroidElementsHash的定义。以及例如我们通过driver.find_element_by_name('hello').send)_keys('haha')
的时候appium是根据什么来对这个元素进行操作的呢,是根据前面我们传入的hello吗?
所以这次在讲find的源码剩余内容时,我们先穿插着讲下以上的内容
AndroidElement与UiObject的关系
我们来看看AndroidElement的成员变量就一目了然了
private final UiObject el;
private String id;
AndroidElement(final String id, final UiObject el) {
this.el = el;
this.id = id;
}
AndroidElement中定义了一个Uiobject的元素对象 以及一个String类型的id值,这里的UiObject元素对象,就是我们要操作的元素了。实际上我们进行点击操作时,可以看看AndroidElement的click方法
public boolean click() throws UiObjectNotFoundException {
return el.click();
}
实际上它直接就是调用了UiAutomator中的click方法。
再来说下String id这个值又是什么呢?或者我们先看看appium 的log是否有什么收获
由这个log大概就能够的出来,elementId 就是我们之前说的String id了。那个elementId这个值又是从哪里来的呢。
AndroidElementsHash与ElementId
我们先来看看AndroidElementsHash吧,AndroidElementsHash拥有两个成员变量
private final Hashtable<String, AndroidElement> elements;
private Integer counter;
elements:是有个哈希表,它的key实际上就是AndroidElement的Id值,value就是AndroidElement
counter:实际上代表的是当前一共用到的控件的个数,以及后续新增一个控件的话,它就会自增1.
public static AndroidElementsHash getInstance() {
if (AndroidElementsHash.instance == null) {
AndroidElementsHash.instance = new AndroidElementsHash();
}
return AndroidElementsHash.instance;
}
private static AndroidElementsHash instance;
/**
* Constructor
*/
public AndroidElementsHash() {
counter = 0;
elements = new Hashtable<String, AndroidElement>();
}
以上的代码要说下的是AndroidElementHash是一个单例模式,这样子也保证了elements 不会再重新被初始化。构造函数则是初始化counter的值为0
我们看下addElement方法就大概能知道,AndroidElementsHash是怎么生成的了。
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;
}
这段代码应该不难理解了,每当一个新的元素增加counter的值就加1
最后我们在来分析下getElement,实际上getElement对应的就是我们平时用的find_element的方法,
但是查找实际上分为两种
- 基于Appium driver进行的查找
基于父控件的查找
有了前面这些的了解 我们再来看看getElement的代码吧
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();
}
}
从上面的代码就可以看出来,如果说baseEl为空的情况下,这种情况就是直接通过driver进行查找
当不为空的时候,就是基于父控件的查找了。以上的查找是查找当个元素的。但是各位别忘了我们有时候还会使用到driver.find_elements()这种查看多个元素的方法。
那查找多个元素的时候与查找单个元素是一样的吗,我们来看看代码吧。
public ArrayList<AndroidElement> getElements(final UiSelector sel,
final String key) throws UiObjectNotFoundException {
boolean keepSearching = true;
final String selectorString = sel.toString();
final boolean useIndex = selectorString.contains("CLASS_REGEX=");
final boolean endsWithInstance = endsWithInstancePattern.matcher(selectorString).matches();
Logger.debug("getElements selector:" + selectorString);
final ArrayList<AndroidElement> elements = new ArrayList<AndroidElement>();
// If sel is UiSelector[CLASS=android.widget.Button, INSTANCE=0]
// then invoking instance with a non-0 argument will corrupt the selector.
//
// sel.instance(1) will transform the selector into:
// UiSelector[CLASS=android.widget.Button, INSTANCE=1]
//
// The selector now points to an entirely different element.
if (endsWithInstance) {
Logger.debug("Selector ends with instance.");
// There's exactly one element when using instance.
UiObject instanceObj = new UiObject(sel);
if (instanceObj != null && instanceObj.exists()) {
elements.add(addElement(instanceObj));
}
return elements;
}
UiObject lastFoundObj;
final AndroidElement baseEl = this.getElement(key);
UiSelector tmp;
int counter = 0;
while (keepSearching) {
if (baseEl == null) {
Logger.debug("Element[" + key + "] is null: (" + counter + ")");
if (useIndex) {
Logger.debug(" using index...");
tmp = sel.index(counter);
} else {
tmp = sel.instance(counter);
}
Logger.debug("getElements tmp selector:" + tmp.toString());
lastFoundObj = new UiObject(tmp);
} else {
Logger.debug("Element[" + key + "] is " + baseEl.getId() + ", counter: "
+ counter);
lastFoundObj = baseEl.getChild(sel.instance(counter));
}
counter++;
if (lastFoundObj != null && lastFoundObj.exists()) {
elements.add(addElement(lastFoundObj));
} else {
keepSearching = false;
}
}
return elements;
}
查找多个元素确实会复杂很多
- 首先getElements会判断传参来的UiSelector是否是以Instance结尾的,如果包含的话,那就直接new UiObject进行查找就可以了。
如果并不是以Instance结尾的时候,还是分两种情况,首先是基于appium driver进行查找还是基于父控件的查找,再来就是直接selector查找时,后面跟上instance(counter) ,每当能够查找到元素时,counter就会自增1接着继续查找。直到没要找到对应的元素后,才会跳出循环。
例:
self.driver.find_elements_by_class_name("android.widget.TextView")
运行的log记录即为
分析
回到最初find的源代码处
found = foundElements.size() > 0;
result = elementsToJSONArray(foundElements);
当找到的元素大于0时,将list类型的元素转化成json数组返回给server,可以从上图的log中看出。