溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Appium Android Bootstrap源碼分析之控件AndroidElement

發(fā)布時(shí)間:2020-06-30 19:28:39 來(lái)源:網(wǎng)絡(luò) 閱讀:1574 作者:zhukev 欄目:移動(dòng)開(kāi)發(fā)

通過(guò)上一篇文章《Appium Android Bootstrap源碼分析之簡(jiǎn)介》我們對(duì)bootstrap的定義以及其在appium和uiautomator處于一個(gè)什么樣的位置有了一個(gè)初步的了解,那么按照正常的寫(xiě)書(shū)的思路,下一個(gè)章節(jié)應(yīng)該就要去看bootstrap是如何建立socket來(lái)獲取數(shù)據(jù)然后怎樣進(jìn)行處理的了。但本人覺(jué)得這樣子做并不會(huì)太好,因?yàn)榈綍r(shí)整篇文章會(huì)變得非常的冗長(zhǎng),因?yàn)槟阍诰帉?xiě)的過(guò)程中碰到不認(rèn)識(shí)的類(lèi)又要跳入進(jìn)去進(jìn)行說(shuō)明分析。這里我覺(jué)得應(yīng)該嘗試吸取著名的《重構(gòu)》這本書(shū)的建議:一個(gè)方法的代碼不要寫(xiě)得太長(zhǎng),不然可讀性會(huì)很差,盡量把其分解成不同的函數(shù)。那我們這里就是用類(lèi)似的思想,不要嘗試在一個(gè)文章中把所有的事情都做完,而是嘗試先把關(guān)鍵的類(lèi)給描述清楚,最后才去把這些類(lèi)通過(guò)一個(gè)實(shí)例分析給串起來(lái)呈現(xiàn)給讀者,這樣大家就不會(huì)因?yàn)橐粋€(gè)文章太長(zhǎng)影響可讀性而放棄往下學(xué)習(xí)了。

那么我們這里為什么先說(shuō)bootstrap對(duì)控件的處理,而非剛才提到的socket相關(guān)的socket服務(wù)器的建立呢?我是這樣子看待的,大家看到本人這篇文章的時(shí)候,很有可能之前已經(jīng)了解過(guò)本人針對(duì)uiautomator源碼分析那個(gè)系列的文章了,或者已經(jīng)有uiautomator的相關(guān)知識(shí),所以腦袋里會(huì)比較迫切的想知道究竟appium是怎么運(yùn)用了uiautomator的,那么在appium中于這個(gè)問(wèn)題最貼切的就是appium在服務(wù)器端是怎么使用了uiautomator的控件的。

這里我們主要會(huì)分析兩個(gè)類(lèi):

  • AndroidElement:代表了bootstrap持有的一個(gè)ui界面的控件的類(lèi),它擁有一個(gè)UiObject成員對(duì)象和一個(gè)代表其在下面的哈希表的鍵值的String類(lèi)型成員變量id
  • AndroidElementsHash:持有了一個(gè)包含所有bootstrap(也就是appium)曾經(jīng)見(jiàn)到過(guò)的(也就是腳本代碼中findElement方法找到過(guò)的)控件的哈希表,它的key就是AndroidElement中的id,每當(dāng)appium通過(guò)findElement找到一個(gè)新控件這個(gè)id就會(huì)+1,Appium的pc端和bootstrap端都會(huì)持有這個(gè)控件的id鍵值,當(dāng)需要調(diào)用一個(gè)控件的方法時(shí)就需要把代表這個(gè)控件的id鍵值傳過(guò)來(lái)讓bootstrap可以從這個(gè)哈希表找到對(duì)應(yīng)的控件

1. AndroidElement和UiObject的組合關(guān)系

從上面的描述我們可以知道,AndroidElement這個(gè)類(lèi)里面擁有一個(gè)UiObject這個(gè)變量:
public class AndroidElement {    private final UiObject el;   private String         id;   ... }
大家都知道UiObject其實(shí)就是UiAutomator里面代表一個(gè)控件的類(lèi),通過(guò)它就能夠?qū)丶M(jìn)行操作(當(dāng)然最終還是通過(guò)UiAutomation框架). AnroidElement就是通過(guò)它來(lái)跟UiAutomator發(fā)生關(guān)系的。我們可以看到下面的AndroidElement的點(diǎn)擊click方法其實(shí)就是很干脆的調(diào)用了UiObject的click方法:
  public boolean click() throws UiObjectNotFoundException {     return el.click();   }
當(dāng)然這里除了click還有很多控件相關(guān)的操作,比如dragTo,getText,longClick等,但無(wú)一例外,都是通過(guò)UiObject來(lái)實(shí)現(xiàn)的,這里就不一一列舉了。

2. 腳本的WebElement和Bootstrap的AndroidElement的映射關(guān)系

我們?cè)谀_本上對(duì)控件的認(rèn)識(shí)就是一個(gè)WebElement:

WebElement addNote =  driver.findElementByAndroidUIAutomator("new UiSelector().text(\"Add note\")");
而在Bootstrap中一個(gè)對(duì)象就是一個(gè)AndroidElement. 那么它們是怎么映射到一起的呢?我們其實(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)試信息是怎樣的:

Appium Android Bootstrap源碼分析之控件AndroidElement
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ù),這里提供了一個(gè)elementId的鍵值對(duì)
從上面的兩條調(diào)試信息看來(lái),其實(shí)沒(méi)有明顯的看到究竟使用的是哪個(gè)控件。其實(shí)這里不起眼的elementId就是確定用的是哪個(gè)控件的,注意這個(gè)elementId并不是一個(gè)控件在界面上的資源id,它其實(shí)是Bootstrap維護(hù)的一個(gè)保存所有已經(jīng)獲取過(guò)的控件的哈希表的鍵值。如上一小節(jié)看到的,每一個(gè)AndroidElement都有兩個(gè)重要的成員變量:

  • UiObject el :uiautomator框架中代表了一個(gè)真實(shí)的窗口控件
  • Sting id :  一個(gè)唯一的自動(dòng)增加的字串類(lèi)型整數(shù),pc端就是通過(guò)它來(lái)在AndroidElementHash這個(gè)類(lèi)中找到想要的控件的

3. AndroidElement控件哈希表

上一節(jié)我們說(shuō)到appium pc端是通過(guò)id把WebElement和目標(biāo)機(jī)器端的AndroidElement映射起來(lái)的,那么我們這一節(jié)就來(lái)看下維護(hù)AndroidElement的這個(gè)哈希表是怎么實(shí)現(xiàn)的。

首先,它擁有兩個(gè)成員變量:

  private final Hashtable<String, AndroidElement> elements;   private       Integer                           counter;
  • elements :一個(gè)以AndroidElement 的id的字串類(lèi)型為key,以AndroidElement的實(shí)例為value的的哈希表
  • counter : 一個(gè)整型變量,有兩個(gè)作用:其一是它代表了當(dāng)前已經(jīng)用到的控件的數(shù)目(其實(shí)也不完全是,你在腳本中對(duì)同一個(gè)控件調(diào)用兩次findElement其實(shí)會(huì)產(chǎn)生兩個(gè)不同id的AndroidElement控件),其二是它代表了一個(gè)新用到的控件的id,而這個(gè)id就是上面的elements哈希表的鍵
這個(gè)哈希表的鍵值都是從0開(kāi)始的,請(qǐng)看它的構(gòu)造函數(shù):
  /**    * Constructor    */   public AndroidElementsHash() {     counter = 0;     elements = new Hashtable<String, AndroidElement>();   }
而它在整個(gè)Bootstrap中是有且只有一個(gè)實(shí)例的,且看它的單例模式實(shí)現(xiàn):
  public static AndroidElementsHash getInstance() {     if (AndroidElementsHash.instance == null) {       AndroidElementsHash.instance = new AndroidElementsHash();     }     return AndroidElementsHash.instance;   }
以下增加一個(gè)控件的方法addElement充分描述了為什么說(shuō)counter是一個(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)查找,這種情況下appium發(fā)過(guò)來(lái)的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"));  
以上的腳本會(huì)先嘗試找到Note1這個(gè)日記的父控件ListView,并把這個(gè)控件保存到控件哈希表,然后再根據(jù)父控件的哈希表鍵值以及子控件的選擇子找到想要的Note1:
Appium Android Bootstrap源碼分析之控件AndroidElement
AndroidElementHash的這個(gè)getElement命令要做的事情就是針對(duì)這兩點(diǎn)來(lái)根據(jù)不同情況獲得目標(biāo)控件
[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種情況就直接通過(guò)選擇子構(gòu)建UiObject對(duì)象,然后通過(guò)addElement把UiObject對(duì)象轉(zhuǎn)換成AndroidElement對(duì)象保存到控件哈希表
  • 如果是第2種情況就先根據(jù)appium傳過(guò)來(lái)的控件哈希表鍵值獲得父控件,再通過(guò)子控件的選擇子在父控件的基礎(chǔ)上查找到目標(biāo)UiObject控件,最后跟上面一樣把該控件通過(guò)上面的addElement把UiObject控件轉(zhuǎn)換成AndroidElement控件對(duì)象保存到控件哈希表




4. 求證

上面有提過(guò),如果pc端的腳本執(zhí)行對(duì)同一個(gè)控件的兩次findElement會(huì)創(chuàng)建兩個(gè)不同id的AndroidElement并存放到控件哈希表中,那么為什么appium的團(tuán)隊(duì)沒(méi)有做一個(gè)增強(qiáng),增加一個(gè)keyMap的方法(算法)和一些額外的信息來(lái)讓同一個(gè)控件使用不同的key的時(shí)候?qū)?yīng)的還是同一個(gè)AndroidElement控件呢?畢竟這才是哈希表實(shí)用的特性之一了,不然你直接用一個(gè)Dictionary不就完事了?網(wǎng)上說(shuō)了幾點(diǎn)hashtable和dictionary的差別,如多線程環(huán)境最好使用哈希表而非字典等,但在bootstrap這個(gè)控件哈希表的情況下我不是很信服這些說(shuō)法,有誰(shuí)清楚的還勞煩指點(diǎn)一二了
這里至于為什么appium不去提供額外的key信息并且實(shí)現(xiàn)keyMap算法,我個(gè)人倒是認(rèn)為有如下原因:
  • 有誰(shuí)這么無(wú)聊在同一個(gè)測(cè)試方法中對(duì)同一個(gè)控件查找兩次?
  • 如果同一個(gè)控件運(yùn)用不同的選擇子查找兩次的話(huà),因?yàn)樽罱K底層的UiObject的成員變量UiSelector mSelector不一樣,所以確實(shí)可以認(rèn)為是不同的控件
但以下兩個(gè)如果用同樣的UiSelector選擇子來(lái)查找控件的情況我就解析不了了,畢竟在我看來(lái)bootstrap這邊應(yīng)該把它們看成是同一個(gè)對(duì)象的:
  • 同一個(gè)腳本不同的方法中分別對(duì)同一控件用同樣的UiSelelctor選擇子進(jìn)行查找呢?
  • 不同腳本中呢?
這些也許在今后深入了解中得到解決,但看家如果知道的,還望不吝賜教

5. 小結(jié)

最后我們對(duì)bootstrap的控件相關(guān)知識(shí)點(diǎn)做一個(gè)總結(jié)
  • AndroidElement的一個(gè)實(shí)例代表了一個(gè)bootstrap的控件
  • AndroidElement控件的成員變量UiObject el代表了uiautomator框架中的一個(gè)真實(shí)窗口控件,通過(guò)它就可以直接透過(guò)uiautomator框架對(duì)控件進(jìn)行實(shí)質(zhì)性操作
  • pc端的WebElement元素和Bootstrap的AndroidElement控件是通過(guò)AndroidElement控件的String id進(jìn)行映射關(guān)聯(lián)的
  • AndroidElementHash類(lèi)維護(hù)了一個(gè)以AndroidElement的id為鍵值,以AndroidElement的實(shí)例為value的全局唯一哈希表,pc端想要獲得一個(gè)控件的時(shí)候會(huì)先從這個(gè)哈希表查找,如果沒(méi)有了再創(chuàng)建新的AndroidElement控件并加入到該哈希表中,所以該哈希表中維護(hù)的是一個(gè)當(dāng)前已經(jīng)使用過(guò)的控件

    <del id="cgnlc"></del>
     

    作者

    自主博客

    微信

    CSDN

    天地會(huì)珠海分舵

    http://techgogogo.com


    服務(wù)號(hào):TechGoGoGo

    掃描碼:

    Appium Android Bootstrap源碼分析之控件AndroidElement

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI

        1. <mark id="cgnlc"><strong id="cgnlc"><rp id="cgnlc"></rp></strong></mark>