中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Appium Android Bootstrap源碼分析之啟動運行

發布時間:2020-07-17 07:57:20 來源:網絡 閱讀:513 作者:zhukev 欄目:移動開發

通過前面的兩篇文章《Appium Android Bootstrap源碼分析之控件AndroidElement》和《Appium Android Bootstrap源碼分析之命令解析執行》我們了解到了Appium從pc端發送過來的命令是如何定位到命令相關的控件以及如何解析執行該命令。那么我們剩下的問題就是bootstrap是怎么啟動運行的,我們會通過本篇文章的分析來闡述這個問題,以及把之前學習的相關的類給串起來看它們是怎么互動的。


1.啟動方式

Bootstrap的啟動是由Appium從pc端通過adb發送命令來控制的:
Appium Android Bootstrap源碼分析之啟動運行
從上面的調試信息我們可以看到AppiumBootstrap.jar是通過uiautomator這個命令作為一個測試包,它指定的測試類是io.appium.android.bootstrap.Bootstrap這個類。大家如果看了本人之前的文章《UIAutomator源碼分析之啟動和運行》的話應該對uiautomator的啟動原理很熟悉了。
  • 啟動命令:uiautomator runtest AppiumBootstrap.jar -c io.appium.android.bootstrap.Bootstrap
那么我們進入到Bootstrap這個類看下它是怎么實現的:
public class Bootstrap extends UiAutomatorTestCase {    public void testRunServer() {     SocketServer server;     try {       server = new SocketServer(4724);       server.listenForever();     } catch (final SocketServerException e) {       Logger.error(e.getError());       System.exit(1);     }    } }
從代碼中可以看到,這個類是繼承與UiAutomatorTestCase的,這樣它就能被uiautomator作為測試用例類來執行了。
這個類只有一個測試方法testRunServer,所有事情發生的源頭就在這里:
  • 創建一個socket服務器并監聽4724端口,Appium在pc端就是通過連接這么端口來把命令發送過來的
  • 循環監聽獲取Appium從pc端發送過來的命令數據,然后進行相應的處理

2. 創建socket服務器并初始化Action到CommandHandler的映射

我們先看下SocketServer的構造函數:
  public SocketServer(final int port) throws SocketServerException {     keepListening = true;     executor = new AndroidCommandExecutor();     try {       server = new ServerSocket(port);       Logger.debug("Socket opened on port " + port);     } catch (final IOException e) {       throw new SocketServerException(           "Could not start socket server listening on " + port);     }    }
它做的第一個事情是先去創建一個AndroidCommandExecutor的實例,大家應該還記得上一篇文章說到的這個類里面保存了一個靜態的很重要的action到命令處理類CommandHandler的實例的映射表吧?如果沒有看過的請先去看下。
建立好這個靜態映射表之后,構造函數下一步就似乎去創建一個ServerSocket來給Appium從PC端進行連接通信了。

3.獲取并執行Appium命令數據

Bootstrap在創建好socket服務器后,下一步就是調用SocketServer的listenForever的方法去循環讀取處理appium發送出來的命令數據了:
  public void listenForever() throws SocketServerException {     Logger.debug("Appium Socket Server Ready");     ...     try {       client = server.accept();       Logger.debug("Client connected");       in = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));       out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(), "UTF-8"));       while (keepListening) {         handleClientData();       }       in.close();       out.close();       client.close();       Logger.debug("Closed client connection");     } catch (final IOException e) {       throw new SocketServerException("Error when client was trying to connect");     }     ... }
首先調用server.accept去接受appium的連接請求,連接上后就去初始化用于讀取socket的BufferedReader和BufferredWriter這兩個類的實例,最后進入到handleClicentData來進行真正的數據讀取和處理
 private void handleClientData() throws SocketServerException {     try {       input.setLength(0); // clear        String res;       int a;       // (char) -1 is not equal to -1.       // ready is checked to ensure the read call doesn't block.       while ((a = in.read()) != -1 && in.ready()) {         input.append((char) a);       }       String inputString = input.toString();       Logger.debug("Got data from client: " + inputString);       try {         AndroidCommand cmd = getCommand(inputString);         Logger.debug("Got command of type " + cmd.commandType().toString());         res = runCommand(cmd);         Logger.debug("Returning result: " + res);       } catch (final CommandTypeException e) {         res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage())             .toString();       } catch (final JSONException e) {         res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR,             "Error running and parsing command").toString();       }       out.write(res);       out.flush();     } catch (final IOException e) {       throw new SocketServerException("Error processing data to/from socket ("           + e.toString() + ")");     }   }
  • 通過剛才建立的socket讀取對象去讀取appium發送過來的數據
  • 把獲得的的json命令字串發送給getCommand方法來實例化我們的AndroidCommand這個類,然后我們就可以通過這個解析器來獲得我們想要的json命令項了
  private AndroidCommand getCommand(final String data) throws JSONException,       CommandTypeException {     return new AndroidCommand(data);   }
  • 調用runCommand方法來使用我們在第二節構造ServerSocket的時候實例化的AndroidComandExecutor對象的execute方法來執行命令,這個命令最終會通過上面的AndroidCommand這個命令解析器的實例來獲得appium發送過來的action,然后根據map調用對應的CommandHandler來處理命令。而如果命令是控件相關的,比如獲取一個控件的文本信息GetText,處理命令類又會繼續去AndroidElementHash維護的控件哈希表獲取到對應的控件,然后再通過UiObject把命令發送出去等等..不清楚的請查看上篇文章
      private String runCommand(final AndroidCommand cmd) {     AndroidCommandResult res;     if (cmd.commandType() == AndroidCommandType.SHUTDOWN) {       keepListening = false;       res = new AndroidCommandResult(WDStatus.SUCCESS, "OK, shutting down");     } else if (cmd.commandType() == AndroidCommandType.ACTION) {       try {         res = executor.execute(cmd);       } ...   }
  • 通過上面建立的socket寫對象把返回信息寫到socket發送給appium

4.控件是如何加入到控件哈希表的

大家可能奇怪,怎么整個運行流程都說完了,提到了怎么去控件哈希表獲取一個控件,但怎么沒有看到把一個控件加入到控件哈希表呢?其實大家寫腳本的時候給一個控件發送click等命令的時候都需要先取找到這個控件,比如:
WebElement el = driver.findElement(By.name("Add note"));
這里的finElement其實就是一個命令,獲取控件并存放到控件哈希表就是由它對應的CommandHandler實現類Find來完成的。
Appium Android Bootstrap源碼分析之啟動運行
可以看到appium過來的命令包含幾項,有我們之間碰到過的,也有沒有碰到過的:
  • cmd:指定是一個action
  • action:指定這個action是一個find命令
  • params
    • strategy:指定選擇子的策略是根據空間名name來進行查找
    • selector: 指定選擇子的內容是"Add note"
    • context: 指定空間哈希表中目標控件的鍵值id,這里為空,因為該控件我們之前沒有用過
    • multiple: 表明你腳本代碼用的是findElements還是findElement,是否要獲取多個控件
Find重寫父類的execute方法有點長,我們把它breakdown一步一步來看.

  • 第一步:獲得控件的選擇子策略,以便跟著通過該策略來建立uiautomator的UiSelector
  public AndroidCommandResult execute(final AndroidCommand command)       throws JSONException {     final Hashtable<String, Object> params = command.params();      // only makes sense on a device     final Strategy strategy;     try {       strategy = Strategy.fromString((String) params.get("strategy"));     } catch (final InvalidStrategyException e) {       return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND, e.getMessage());     }    ... }
appium支持的策略有以下幾種,這其實在我們寫腳本中findElement經常會指定:
public enum Strategy {   CLASS_NAME("class name"),   CSS_SELECTOR("css selector"),   ID("id"),   NAME("name"),   LINK_TEXT("link text"),   PARTIAL_LINK_TEXT("partial link text"),   XPATH("xpath"),   ACCESSIBILITY_ID("accessibility id"),   ANDROID_UIAUTOMATOR("-android uiautomator");
  • 第二步:獲取appium發過來的選擇子的其他信息如內容,控件哈希表鍵值,是否是符合選擇子等
  public AndroidCommandResult execute(final AndroidCommand command)       throws JSONException {     final Hashtable<String, Object> params = command.params();    ...      final String contextId = (String) params.get("context");     final String text = (String) params.get("selector");     final boolean multiple = (Boolean) params.get("multiple");    ... }
  • 第三步,在獲得一樣的選擇子的信息后,就可以根據該選擇子信息建立真正的UiSelector選擇子列表了,這里用列表應該是考慮到今后的復合選擇子的情況,當前我們并沒有用到,整個列表只會有一個UiSelector選擇子
  public AndroidCommandResult execute(final AndroidCommand command)       throws JSONException {    ...     try {       Object result = null;       List<UiSelector> selectors = getSelectors(strategy, text, multiple);        ...       }     ... }
  • 第四步:組建好選擇子UiSelector列表后,Find會根據你是findElement還是findElement,也就是說是查找一個控件還是多個控件來查找控件,但是無論是多個還是一個,最終都是調用fetchElement這個方法來取查找的
  public AndroidCommandResult execute(final AndroidCommand command)       throws JSONException {    ...     try {       Object result = null;       List<UiSelector> selectors = getSelectors(strategy, text, multiple);        if (!multiple) {         for (final UiSelector sel : selectors) {           try {             Logger.debug("Using: " + sel.toString());             result = fetchElement(sel, contextId);           } catch (final ElementNotFoundException ignored) {           }           if (result != null) {             break;           }         }       }else {         List<AndroidElement> foundElements = new ArrayList<AndroidElement>();         for (final UiSelector sel : selectors) {           // With multiple selectors, we expect that some elements may not           // exist.           try {             Logger.debug("Using: " + sel.toString());             List<AndroidElement> elementsFromSelector = fetchElements(sel, contextId);             foundElements.addAll(elementsFromSelector);           } catch (final UiObjectNotFoundException ignored) {           }         }         if (strategy == Strategy.ANDROID_UIAUTOMATOR) {           foundElements = ElementHelpers.dedupe(foundElements);         }         result = elementsToJSONArray(foundElements);       }    ... } 
而fetchElement最終調用的控件哈希表類的getElements:
  private ArrayList<AndroidElement> fetchElements(final UiSelector sel, final String contextId)       throws UiObjectNotFoundException {      return elements.getElements(sel, contextId);   }
AndroidElementHash的這個方法我們在前一篇文章《Appium Android Bootstrap源碼分析之控件AndroidElement》已經分析過,我們今天再來溫習一下.
從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:
Appium Android Bootstrap源碼分析之啟動運行
AndroidElementHash的這個getElement命令要做的事情就是針對這兩點來根據不同情況獲得目標控件的
[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種情況就直接通過選擇子構建UiObject對象,然后通過addElement把UiObject對象轉換成AndroidElement對象保存到控件哈希表
  • 如果是第2種情況就先根據appium傳過來的控件哈希表鍵值獲得父控件,再通過子控件的選擇子在父控件的基礎上查找到目標UiObject控件,最后跟上面一樣把該控件通過addElement把UiObject控件轉換成AndroidElement控件對象保存到控件哈希表
以下就是把控件添加到控件哈希表的addElement方法
  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;   }

5. 小結

  • Appium的bootstrap這個jar包以及里面的o.appium.android.bootstrap.Bootstrap類是通過uiautomator作為一個uiautomator的測試包和測試方法類啟動起來的
  • Bootstrap測試類繼承于uiautomator可以使用的UiAutomatorTestCase
  • bootstrap會啟動一個socket server并監聽來自4724端口的appium的連接
  • 一旦appium連接上來,bootstrap就會不停的去獲取該端口的appium發送過來的命令數據進行解析和執行處理,然后把結果寫到該端口返回給appium
  • bootstrap獲取到appium過來的json字串命令后,會通過AndroidCommand這個命令解析器解析出命令action,然后通過AndroidCommandExecutor的action到CommandHandler的map把action映射到真正的命令處理類,這些類都是繼承與CommandHandler的實現類,它們都要重寫該父類的execute方法來最終通過UiObject,UiDevice或反射獲得UiAutomator沒有暴露出來的QueryController/InteractionController來把命令真正的在安卓系統中執行
  • appium獲取控件大概有兩類,一類是直接通過Appium/Android Driver獲得,這一種情況過來的appium查找json命令字串是沒有帶控件哈希表的控件鍵值的;另外一種是根據控件的父類控件在控件哈希表中的鍵值和子控件的選擇子來獲得,這種情況過來的appium查找json命令字串是既提供了父控件在控件哈希表的鍵值又提供了子控件的選擇子的
  • 一旦獲取到的控件在控件哈希表中不存在,就需要把這個AndroidElement控件添加到該哈希表里面

 

作者

自主博客

微信

CSDN

天地會珠海分舵

http://techgogogo.com


服務號:TechGoGoGo

掃描碼:

Appium Android Bootstrap源碼分析之啟動運行

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

南木林县| 寿光市| 湟中县| 库尔勒市| 永宁县| 格尔木市| 封开县| 新蔡县| 特克斯县| 泰安市| 锦州市| 永胜县| 利津县| 吐鲁番市| 全南县| 鄄城县| 华蓥市| 保德县| 桐城市| 开远市| 丘北县| 双桥区| 沧源| 达尔| 织金县| 宝兴县| 揭东县| 万宁市| 万盛区| 诸暨市| 河东区| 延长县| 兖州市| 西藏| 神农架林区| 牡丹江市| 于田县| 朝阳市| 安阳县| 山阳县| 明溪县|