我們?cè)谀_本上對(duì)控件的認(rèn)識(shí)就是1個(gè)WebElement:
WebElement addNote = driver.findElementByAndroidUIAutomator("new UiSelector().text("Add note")");
而在Bootstrap中1個(gè)對(duì)象就是1個(gè)AndroidElement. 那末它們是怎樣映照到1起的呢?我們其實(shí)可以先看以下的代碼:
WebElement addNote = driver.findElementByAndroidUIAutomator("new UiSelector().text("Add note")");
addNote.getText();
addNote.click();
做的事情就是取得Notes這個(gè)app的菜單,然后調(diào)用控件的getText來(lái)取得‘Add note'控件的文本信息,和通過(guò)控件的click方法來(lái)點(diǎn)擊該控件。那末我們看下調(diào)試信息是怎樣的:

pc端傳過(guò)來(lái)的json字串有幾個(gè)fields:
- cmd:代表這個(gè)是甚么命令類(lèi)型,其實(shí)就是AndroidCommandType的那兩個(gè)值
package io.appium.android.bootstrap;
/**
* Enumeration for all the command types.
*
*/
public enum AndroidCommandType {
ACTION, SHUTDOWN
}
- action: 具體命令
- params: 提供的參數(shù),這里提供了1個(gè)elementId的鍵值對(duì)
從上面的兩條調(diào)試信息看來(lái),其實(shí)沒(méi)有明顯的看到究竟使用的是哪一個(gè)控件。其實(shí)這里不起眼的elementId就是肯定用的是哪一個(gè)控件的,注意這個(gè)elementId其實(shí)不是1個(gè)控件在界面上的資源id,它實(shí)際上是Bootstrap保護(hù)的1個(gè)保存所有已獲得過(guò)的控件的哈希表的鍵值。如上1小節(jié)看到的,每個(gè)AndroidElement都有兩個(gè)重要的成員變量:
- UiObject el :uiautomator框架中代表了1個(gè)真實(shí)的窗口控件
- Sting id : 1個(gè)唯1的自動(dòng)增加的字串類(lèi)型整數(shù),pc端就是通過(guò)它來(lái)在AndroidElementHash這個(gè)類(lèi)中找到想要的控件的
3. AndroidElement控件哈希表
上1節(jié)我們說(shuō)到appium pc端是通過(guò)id把WebElement和目標(biāo)機(jī)器真?zhèn)€AndroidElement映照起來(lái)的,那末我們這1節(jié)就來(lái)看下保護(hù)AndroidElement的這個(gè)哈希表是怎樣實(shí)現(xiàn)的。
首先,它具有兩個(gè)成員變量:
private final Hashtable<String, AndroidElement> elements;
private Integer counter;
- elements :1個(gè)以AndroidElement 的id的字串類(lèi)型為key,以AndroidElement的實(shí)例為value的的哈希表
- counter : 1個(gè)整型變量,有兩個(gè)作用:其1是它代表了當(dāng)前已用到的控件的數(shù)目(其實(shí)也不完全是,你在腳本中對(duì)同1個(gè)控件調(diào)用兩次findElement其實(shí)會(huì)產(chǎn)生兩個(gè)不同id的AndroidElement控件),其2是它代表了1個(gè)新用到的控件的id,而這個(gè)id就是上面的elements哈希表的鍵
這個(gè)哈希表的鍵值都是從0開(kāi)始的,請(qǐng)看它的構(gòu)造函數(shù):
/**
* Constructor
*/
public AndroidElementsHash() {
counter = 0;
elements = new Hashtable<String, AndroidElement>();
}
而它在全部Bootstrap中是有且只有1個(gè)實(shí)例的,且看它的單例模式實(shí)現(xiàn):
public static AndroidElementsHash getInstance() {
if (AndroidElementsHash.instance == null) {
AndroidElementsHash.instance = new AndroidElementsHash();
}
return AndroidElementsHash.instance;
}
以下增加1個(gè)控件的方法addElement充分描寫(xiě)了為何說(shuō)counter是1個(gè)自增加的key,且是每一個(gè)新發(fā)現(xiàn)的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發(fā)過(guò)來(lái)的控件查找命令大方向上分兩類(lèi):
- 1. 直接基于Appium Driver來(lái)查找,這類(lèi)情況下appium發(fā)過(guò)來(lái)的json命令是不包括控件哈希表的鍵值信息的
-
WebElement addNote = driver.findElement(By.name("Add note"));
-
WebElement el = driver.findElement(By.className("android.widget.ListView")).findElement(By.name("Note1"));
以上的腳本會(huì)先嘗試找到Note1這個(gè)日記的父控件ListView,并把這個(gè)控件保存到控件哈希表,然后再根據(jù)父控件的哈希表鍵值和子控件的選擇子找到想要的Note1:

AndroidElementHash的這個(gè)getElement命令要做的事情就是針對(duì)這兩點(diǎn)來(lái)根據(jù)不同情況取得目標(biāo)控件
-
-
-
-
-
-
-
-
-
-
-
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種情況就直接通過(guò)選擇子構(gòu)建UiObject對(duì)象,然后通過(guò)addElement把UiObject對(duì)象轉(zhuǎn)換成AndroidElement對(duì)象保存到控件哈希表
- 如果是第2種情況就先根據(jù)appium傳過(guò)來(lái)的控件哈希表鍵值取得父控件,再通過(guò)子控件的選擇子在父控件的基礎(chǔ)上查找到目標(biāo)UiObject控件,最后跟上面1樣把該控件通過(guò)上面的addElement把UiObject控件轉(zhuǎn)換成AndroidElement控件對(duì)象保存到控件哈希表
4. 求證
上面有提過(guò),如果pc真?zhèn)€腳本履行對(duì)同1個(gè)控件的兩次findElement會(huì)創(chuàng)建兩個(gè)不同id的AndroidElement并寄存到控件哈希表中,那末為何appium的團(tuán)隊(duì)沒(méi)有做1個(gè)增強(qiáng),增加1個(gè)keyMap的方法(算法)和1些額外的信息來(lái)讓同1個(gè)控件使用不同的key的時(shí)候?qū)?yīng)的還是同1個(gè)AndroidElement控件呢?畢竟這才是哈希表實(shí)用的特性之1了,不然你直接用1個(gè)Dictionary不就完事了?網(wǎng)上說(shuō)了幾點(diǎn)hashtable和dictionary的差別,如多線程環(huán)境最好使用哈希表而非字典等,但在bootstrap這個(gè)控件哈希表的情況下我不是很佩服這些說(shuō)法,有誰(shuí)清楚的還勞煩指導(dǎo)12了
這里至于為何appium不去提供額外的key信息并且實(shí)現(xiàn)keyMap算法,我個(gè)人倒是認(rèn)為有以下緣由:
- 有誰(shuí)這么無(wú)聊在同1個(gè)測(cè)試方法中對(duì)同1個(gè)控件查找兩次?
- 如果同1個(gè)控件應(yīng)用不同的選擇子查找兩次的話,由于終究底層的UiObject的成員變量UiSelector mSelector不1樣,所以確切可以認(rèn)為是不同的控件
但以下兩個(gè)如果用一樣的UiSelector選擇子來(lái)查找控件的情況我就解析不了了,畢竟在我看來(lái)bootstrap這邊應(yīng)當(dāng)把它們看成是同1個(gè)對(duì)象的:
- 同1個(gè)腳本不同的方法中分別對(duì)同1控件用一樣的UiSelelctor選擇子進(jìn)行查找呢?
- 不同腳本中呢?
這些或許在今后深入了解中得到解決,但看家如果知道的,還望不吝賜教
5. 小結(jié)
最后我們對(duì)bootstrap的控件相干知識(shí)點(diǎn)做1個(gè)總結(jié)
- AndroidElement的1個(gè)實(shí)例代表了1個(gè)bootstrap的控件
- AndroidElement控件的成員變量UiObject el代表了uiautomator框架中的1個(gè)真實(shí)窗口控件,通過(guò)它就能夠直接透過(guò)uiautomator框架對(duì)控件進(jìn)行實(shí)質(zhì)性操作
- pc真?zhèn)€WebElement元素和Bootstrap的AndroidElement控件是通過(guò)AndroidElement控件的String id進(jìn)行映照關(guān)聯(lián)的
- AndroidElementHash類(lèi)保護(hù)了1個(gè)以AndroidElement的id為鍵值,以AndroidElement的實(shí)例為value的全局唯1哈希表,pc端想要取得1個(gè)控件的時(shí)候會(huì)先從這個(gè)哈希表查找,如果沒(méi)有了再創(chuàng)建新的AndroidElement控件并加入到該哈希表中,所以該哈希表中保護(hù)的是1個(gè)當(dāng)前已使用過(guò)的控件