Appium Android Bootstrap源碼分析之控件AndroidElement
來源:程序員人生 發布時間:2014-11-17 08:28:57 閱讀次數:4059次
通過上1篇文章《Appium Android Bootstrap源碼分析之簡介》我們對bootstrap的定義和其在appium和uiautomator處于1個甚么樣的位置有了1個初步的了解,那末依照正常的寫書的思路,下1個章節應當就要去看bootstrap是如何建立socket來獲得數據然后怎樣進行處理的了。但本人覺得這模樣做其實不會太好,由于到時整篇文章會變得非常的冗雜,由于你在編寫的進程中碰到不認識的類又要跳入進去進行說明分析。這里我覺得應當嘗試吸取著名的《重構》這本書的建議:1個方法的代碼不要寫得太長,不然可讀性會很差,盡可能把其分解成不同的函數。那我們這里就是用類似的思想,不要嘗試在1個文章中把所有的事情都做完,而是嘗試先把關鍵的類給描寫清楚,最后才去把這些類通過1個實例分析給串起來顯現給讀者,這樣大家就不會由于1個文章太長影響可讀性而放棄往下學習了。
那末我們這里為何先說bootstrap對控件的處理,而非剛才提到的socket相干的sockethttp://www.vxbq.cn/server/的建立呢?我是這模樣看待的,大家看到本人這篇文章的時候,很有可能之前已了解過本人針對uiautomator源碼分析那個系列的文章了,或已有uiautomator的相干知識,所以腦袋里會比較迫切的想知道究竟appium是怎樣應用了uiautomator的,那末在appium中于這個問題最貼切的就是appium在http://www.vxbq.cn/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命令是不包括控件哈希表的鍵值信息的
-
WebElement addNote = driver.findElement(By.name("Add note"));
-
WebElement el = driver.findElement(By.className("android.widget.ListView")).findElement(By.name("Note1"));
以上的腳本會先嘗試找到Note1這個日記的父控件ListView,并把這個控件保存到控件哈希表,然后再根據父控件的哈希表鍵值和子控件的選擇子找到想要的Note1:

AndroidElementHash的這個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();
-
}
-
}
- 如果是第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個當前已使用過的控件
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈