溫馨提示×

溫馨提示×

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

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

Appium Android Bootstrap源碼分析之命令解析執(zhí)行

發(fā)布時間:2020-07-22 09:44:36 來源:網(wǎng)絡(luò) 閱讀:257 作者:zhukev 欄目:移動開發(fā)

通過上一篇文章《Appium Android Bootstrap源碼分析之控件AndroidElement》我們知道了Appium從pc端發(fā)送過來的命令如果是控件相關(guān)的話,最終目標(biāo)控件在bootstrap中是以AndroidElement對象的方式呈現(xiàn)出來的,并且該控件對象會在AndroidElementHash維護(hù)的控件哈希表中保存起來。但是appium觸發(fā)一個命令除了需要提供是否與控件相關(guān)這個信息外,還需要其他的一些信息,比如,這個是什么命令?這個就是我們這篇文章需要討論的話題了。

下面我們還是先看一下從pc端發(fā)過來的json的格式是怎么樣的:

Appium Android Bootstrap源碼分析之命令解析執(zhí)行

可以看到里面除了params指定的是哪一個控件之外,還指定了另外兩個信息:

  • cmd: 這是一個action還是一個shutdown
  • action:如果是一個action的話,那么是什么action

開始前我們先簡要描述下我們需要涉及到幾個關(guān)鍵類:

Class

Key Method

Key Member

Parent

Description

Comment

AndroidComma

ndType


enum AndroidCommandType {

ACTION,SHUTDOWN

}


安卓命令的類型,只有兩種,shutdown的處理方式和普通的action會不一樣


AndroidComma

nd

action/getElement

JSONObject json;

AndroidCommandType cmdType;


從用戶發(fā)過來的json命令信息得到真正的命令


CommandHand

ler

execute



虛擬類,其他真實CommandHandlerclick的父類


AndroidComma

ndExecutor

execute

HashMap<

String, 

CommandHan

dler> map



map是所有的命令字串和真實的CommandHandler的一個映射。

其成員函數(shù)execute就是通過字串命令找到map對應(yīng)的handler然后執(zhí)行的


getText

execute


CommandHandler

處理獲取指定控件文本信息的類。

真正執(zhí)行的是傳進(jìn)來的AndroidCommand對應(yīng)UiObjectgetText方法

其他click,find,drag,setText等命令同理


1. Appium命令解析器AndroidCommand

AndroidCommand這個類真實的作用其實就是去把Appium從pc端發(fā)送過來的那串json命令解析出來,它擁有兩個成員變量:
  JSONObject         json;   AndroidCommandType cmdType;
json就是pc過來的json格式的那串命令,cmdType就是action或者shutdown,其實就是用來把這個類偽裝成更像個命令類而已,我認(rèn)為如果不提供這個成員變量而直接修改其getType的實現(xiàn)去解析json字串直接獲得對應(yīng)的AndroidCommandType,然后把這個類的名字改成AndroidCommandParser得了。

那么我們往下看下AndroidCommand究竟是怎么對客戶端命令進(jìn)行解析的,它的方法都很短,所以我把它做成一個表,這樣比較清晰點(diǎn):

Method

Return

Code

Description

AndroidCommand

N/A

  public AndroidCommand(final String jsonStr)  		  throws JSONException, 		  CommandTypeException {     json = new JSONObject(jsonStr);     setType(json.getString("cmd"));   }


構(gòu)造函數(shù)構(gòu)造函數(shù),把客戶端過

來的json格式命

令保存起來并根

據(jù)命令的cmd

設(shè)置好cmdType

action()

String

  public String action()  		  throws JSONException {     if (isElementCommand()) {       return json.getString("action").     		  substring(8);     }     return json.getString("action");   }


解析出客戶端過

來的json字串的

action這個項并

返回

commandType()

AndroidCom

mandType 

  public AndroidCommandType commandType() {     return cmdType;   }


ACTION還是SHUTDOWN

getDestElement

AndroidElement

  public AndroidElement getDestElement()  		  throws JSONException {     String destElId = (String) params().     		get("destElId");     return AndroidElementsHash.     		getInstance().     		getElement(destElId);   }


解析出json字串

params項的子

destElId,然后

從控件哈希表中

找到目標(biāo)

AndroidElement

控件返回

getElement

AndroidElement

  public AndroidElement getElement()  		  throws JSONException {     String elId = (String) params().     		get("elementId");     return AndroidElementsHash.getInstance().     		getElement(elId);   }


解析出json字串

params項的子

elementId,

后從控件哈希表

中找到目標(biāo)

AndroidElement

控件返回

isElementCommand

boolean

  public boolean isElementCommand() {     if (cmdType == AndroidCommandType.ACTION) {       try {         return json.getString("action").         		startsWith("element:");       } catch (final JSONException e) {         return false;       }     }     return false;   }


解析json字串中

’action’項的值,如果是以’element:’

字串開始的話就證

明是個控件相關(guān)的

命令,否則就不是


params

Hashtable

<String,

Object>

  public Hashtable<String, Object> params()  		  throws JSONException {     final JSONObject paramsObj =      		json.getJSONObject("params");     final Hashtable<String, Object> newParams =     		new Hashtable<String, Object>();     final Iterator<?> keys = paramsObj.keys();      while (keys.hasNext()) {       final String param = (String) keys.next();       newParams.put(param, paramsObj.get(param));     }     return newParams;   }


json字串中的params項解析器

setType

void

  public void setType(final String stringType)  		  throws CommandTypeException {     if (stringType.equals("shutdown")) {       cmdType = AndroidCommandType.SHUTDOWN;     } else if (stringType.equals("action")) {       cmdType = AndroidCommandType.ACTION;     } else {       throw new CommandTypeException(     		  "Got bad command type: "     				  	+ stringType);     }   }


就是構(gòu)造函數(shù)根

據(jù)json字串的

’cmd’這個項的值

來調(diào)用這個方法

來設(shè)置的AndroidCommand

Type

從表中的這些方法可以看出來,這個類所做的事情基本上都是怎么去解析appium從pc端過來的那串json字串。

2. Action與CommandHandler的映射關(guān)系

從上面描述可以知道,一個action就是一個代表該命令的字串,比如‘click’。但是一個字串是不能去執(zhí)行的啊,所以我們需要有一種方式把它轉(zhuǎn)換成可以執(zhí)行的代碼,這個就是AndroidCommandExecutor維護(hù)的一個靜態(tài)HashMap map所做的事情:
class AndroidCommandExecutor {    private static HashMap<String, CommandHandler> map = new HashMap<String, CommandHandler>();    static {     map.put("waitForIdle", new WaitForIdle());     map.put("clear", new Clear());     map.put("orientation", new Orientation());     map.put("swipe", new Swipe());     map.put("flick", new Flick());     map.put("drag", new Drag());     map.put("pinch", new Pinch());     map.put("click", new Click());     map.put("touchLongClick", new TouchLongClick());     map.put("touchDown", new TouchDown());     map.put("touchUp", new TouchUp());     map.put("touchMove", new TouchMove());     map.put("getText", new GetText());     map.put("setText", new SetText());     map.put("getName", new GetName());     map.put("getAttribute", new GetAttribute());     map.put("getDeviceSize", new GetDeviceSize());     map.put("scrollTo", new ScrollTo());     map.put("find", new Find());     map.put("getLocation", new GetLocation());     map.put("getSize", new GetSize());     map.put("wake", new Wake());     map.put("pressBack", new PressBack());     map.put("pressKeyCode", new PressKeyCode());     map.put("longPressKeyCode", new LongPressKeyCode());     map.put("takeScreenshot", new TakeScreenshot());     map.put("updateStrings", new UpdateStrings());     map.put("getDataDir", new GetDataDir());     map.put("performMultiPointerGesture", new MultiPointerGesture());     map.put("openNotification", new OpenNotification());     map.put("source", new Source());     map.put("compressedLayoutHierarchy", new CompressedLayoutHierarchy());   }
這個map指定了我們支持的pc端過來的所有action,以及對應(yīng)的處理該action的類的實例,其實這些類都是CommandHandler的子類基本上就只有一個:去實現(xiàn)CommandHandler的虛擬方法execute!要做的事情就大概就這幾類:
  • 控件相關(guān)的action:調(diào)用AndroidElement控件的成員變量UiObject el對應(yīng)的方法來執(zhí)行真實的操作
  • UiDevice相關(guān)的action:調(diào)用UiDevice提供的方法
  • UiScrollable相關(guān)的action:調(diào)用UiScrollable提供的方法
  • UiAutomator那5個對象都沒有的action:該調(diào)用InteractionController的就反射調(diào)用,該調(diào)用QueryController的就反射調(diào)用。注意這兩個類UiAutomator是沒有提供直接調(diào)用的方法的,所以只能通過反射。更多這兩個類的信息請翻看之前的UiAutomator源碼分析相關(guān)的文章
  • 其他:如取得compressedLayoutHierarchy
指導(dǎo)action向CommandHandler真正發(fā)生轉(zhuǎn)換的地方是在這個AndroidCommandExecutor的execute方法中:
  public AndroidCommandResult execute(final AndroidCommand command) {     try {       Logger.debug("Got command action: " + command.action());        if (map.containsKey(command.action())) {         return map.get(command.action()).execute(command);       } else {         return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND,             "Unknown command: " + command.action());       }     } catch (final JSONException e) {       Logger.error("Could not decode action/params of command");       return new AndroidCommandResult(WDStatus.JSON_DECODER_ERROR,           "Could not decode action/params of command, please check format!");     }   }
  • 它首先叫上面的AndroidCommand解析器把json字串的action給解析出來
  • 然后通過剛提到的map把這個action對應(yīng)的CommandHandler的實現(xiàn)類給實例化
  • 然后調(diào)用這個命令處理類的execute方法開始執(zhí)行命令

3. 命令處理示例

我們這里就示例性的看下getText這個action對應(yīng)的CommandHandler是怎么去通過AndroidElement控件進(jìn)行設(shè)置文本的處理的:
public class GetText extends CommandHandler {    /*    * @param command The {@link AndroidCommand} used for this handler.    *     * @return {@link AndroidCommandResult}    *     * @throws JSONException    *     * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android.    * bootstrap.AndroidCommand)    */   @Override   public AndroidCommandResult execute(final AndroidCommand command)       throws JSONException {     if (command.isElementCommand()) {       // Only makes sense on an element       try {         final AndroidElement el = command.getElement();         return getSucce***esult(el.getText());       } catch (final UiObjectNotFoundException e) {         return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,             e.getMessage());       } catch (final Exception e) { // handle NullPointerException         return getErrorResult("Unknown error");       }     } else {       return getErrorResult("Unable to get text without an element.");     }   } }
關(guān)鍵代碼就是里面通過AndroidCommand的getElement方法:
  • 解析傳進(jìn)來的AndroidCommand實例保存的pc端過來的json字串,找到’params‘項的子項’elementId'
  • 通過這個獲得的id去控件哈希表(請查看《Appium Android Bootstrap源碼分析之控件AndroidElement》)中找到目標(biāo)AndroidElement控件對象
然后調(diào)用獲得的AndroidElement控件對象的getText方法:
  • 最終通過調(diào)用AndroidElement控件成員UiObject控件對象的getText方法取得控件文本信息

4. 小結(jié)

bootstrap接收到appium從pc端發(fā)送過來的json格式的鍵值對字串有多個項:
  • cmd: 這是一個action還是一個shutdown
  • action:如果是一個action的話,那么是什么action,比如click
  • params:擁有其他的一些子項,比如指定操作控件在AndroidElementHash維護(hù)的控件哈希表的控件鍵值的'elementId'
在收到這個json格式命令字串后:
  • AndroidCommandExecutor會調(diào)用AndroidCommand去解析出對應(yīng)的action
  • 然后把a(bǔ)ction去map到對應(yīng)的真實命令處理方法CommandHandler的實現(xiàn)子類對象中
  • 然后調(diào)用對應(yīng)的對象的execute方法來執(zhí)行命令


       

      作者

      自主博客

      微信

      CSDN

      天地會珠海分舵

      http://techgogogo.com


      服務(wù)號:TechGoGoGo

      掃描碼:

      Appium Android Bootstrap源碼分析之命令解析執(zhí)行

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

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

      AI