UiAutomator系列——Appium Android Bootstrap源码分析之命令解析执行(008)

本文深入探讨了Appium中如何解析客户端发送的json命令,特别关注了命令类型(action或shutdown)、参数解析及如何将命令映射到实际的Action执行。通过解析JSON命令,Appium能够识别具体的Action并调用相应的CommandHandler类执行,从而实现对Android应用的自动化控制。

通过上一篇文章《Appium Android Bootstrap源码分析之控件AndroidElement》我们知道了Appium从pc端发送过来的命令如果是控件相关的话,最终目标控件在bootstrap中是以AndroidElement对象的方式呈现出来的,并且该控件对象会在AndroidElementHash维护的控件哈希表中保存起来。但是appium触发一个命令除了需要提供是否与控件相关这个信息外,还需要其他的一些信息,比如,这个是什么命令?这个就是我们这篇文章需要讨论的话题了。

下面我们还是先看一下从pc端发过来的json的格式是怎么样的:


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

  • cmd: 这是一个action还是一个shutdown
  • action:如果是一个action的话,那么是什么action

开始前我们先简要描述下我们需要涉及到几个关键类:

Class

Key Method

Key Member

Parent

Description

Comment

AndroidComma

ndType


enum AndroidCommandType {

ACTION,SHUTDOWN

}


安卓命令的类型,只有两种,shutdown的处理方式和普通的action会不一样


AndroidComma

nd

action/getElement

JSONObject json;

AndroidCommandType cmdType;


从用户发过来的json命令信息得到真正的命令


CommandHand

ler

execute



虚拟类,其他真实CommandHandlerclick的父类


AndroidComma

ndExecutor

execute

HashMap<

String, 

CommandHan

dler> map



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

其成员函数execute就是通过字串命令找到map对应的handler然后执行的


getText

execute


CommandHandler

处理获取指定控件文本信息的类。

真正执行的是传进来的AndroidCommand对应UiObjectgetText方法

其他clickfind,drag,setText等命令同理


1. Appium命令解析器AndroidCommand

AndroidCommand这个类真实的作用其实就是去把Appium从pc端发送过来的那串json命令解析出来,它拥有两个成员变量:
  1. JSONObject         json;  
  2. AndroidCommandType cmdType;  
json就是pc过来的json格式的那串命令,cmdType就是action或者shutdown,其实就是用来把这个类伪装成更像个命令类而已,我认为如果不提供这个成员变量而直接修改其getType的实现去解析json字串直接获得对应的 AndroidCommandType,然后把这个类的名字改成 AndroidCommandParser得了。

那么我们往下看下AndroidCommand究竟是怎么对客户端命令进行解析的,它的方法都很短,所以我把它做成一个表,这样比较清晰点:

Method

Return

Code

Description

AndroidCommand

N/A

  1. public AndroidCommand(final String jsonStr)   
  2.   throws JSONException,  
  3.   CommandTypeException {  
  4.   json = new JSONObject(jsonStr);  
  5.   setType(json.getString("cmd"));  
  6. }  

构造函数构造函数,把客户端过

来的json格式命

令保存起来并根

据命令的cmd

设置好cmdType

action()

String

  1. public String action()   
  2.   throws JSONException {  
  3.   if (isElementCommand()) {  
  4.     return json.getString("action").  
  5.           substring(8);  
  6.   }  
  7.   return json.getString("action");  
  8. }  

解析出客户端过

来的json字串的

action这个项并

返回

commandType()

AndroidCom

mandType 

  1. public AndroidCommandType commandType() {  
  2.   return cmdType;  
  3. }  

ACTION还是SHUTDOWN

getDestElement

AndroidElement

  1. public AndroidElement getDestElement()   
  2.   throws JSONException {  
  3.   String destElId = (String) params().  
  4.         get("destElId");  
  5.   return AndroidElementsHash.  
  6.         getInstance().  
  7.         getElement(destElId);  
  8. }  

解析出json字串

params项的子

destElId,然后

从控件哈希表中

找到目标

AndroidElement

控件返回

getElement

AndroidElement

  1. public AndroidElement getElement()   
  2.   throws JSONException {  
  3.   String elId = (String) params().  
  4.         get("elementId");  
  5.   return AndroidElementsHash.getInstance().  
  6.         getElement(elId);  
  7. }  

解析出json字串

params项的子

elementId,

后从控件哈希表

中找到目标

AndroidElement

控件返回

isElementCommand

boolean

  1. public boolean isElementCommand() {  
  2.   if (cmdType == AndroidCommandType.ACTION) {  
  3.     try {  
  4.       return json.getString("action").  
  5.             startsWith("element:");  
  6.     } catch (final JSONException e) {  
  7.       return false;  
  8.     }  
  9.   }  
  10.   return false;  
  11. }  

解析json字串中

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

字串开始的话就证

明是个控件相关的

命令,否则就不是


params

Hashtable

<String,

Object>

  1. public Hashtable<String, Object> params()   
  2.   throws JSONException {  
  3.   final JSONObject paramsObj =   
  4.         json.getJSONObject("params");  
  5.   final Hashtable<String, Object> newParams =  
  6.         new Hashtable<String, Object>();  
  7.   final Iterator<?> keys = paramsObj.keys();  
  8.   
  9.   while (keys.hasNext()) {  
  10.     final String param = (String) keys.next();  
  11.     newParams.put(param, paramsObj.get(param));  
  12.   }  
  13.   return newParams;  
  14. }  

json字串中的params项解析器

setType

void

  1. public void setType(final String stringType)   
  2.   throws CommandTypeException {  
  3.   if (stringType.equals("shutdown")) {  
  4.     cmdType = AndroidCommandType.SHUTDOWN;  
  5.   } else if (stringType.equals("action")) {  
  6.     cmdType = AndroidCommandType.ACTION;  
  7.   } else {  
  8.     throw new CommandTypeException(  
  9.           "Got bad command type: "  
  10.                     + stringType);  
  11.   }  
  12. }  

就是构造函数根

json字串的

’cmd’这个项的值

来调用这个方法

来设置的AndroidCommand

Type

从表中的这些方法可以看出来,这个类所做的事情基本上都是怎么去解析appium从pc端过来的那串json字串。

2. Action与CommandHandler的映射关系

从上面描述可以知道,一个action就是一个代表该命令的字串,比如‘click’。但是一个字串是不能去执行的啊,所以我们需要有一种方式把它转换成可以执行的代码,这个就是 AndroidCommandExecutor维护的一个静态HashMap map所做的事情:
  1. class AndroidCommandExecutor {  
  2.   
  3.   private static HashMap<String, CommandHandler> map = new HashMap<String, CommandHandler>();  
  4.   
  5.   static {  
  6.     map.put("waitForIdle"new WaitForIdle());  
  7.     map.put("clear"new Clear());  
  8.     map.put("orientation"new Orientation());  
  9.     map.put("swipe"new Swipe());  
  10.     map.put("flick"new Flick());  
  11.     map.put("drag"new Drag());  
  12.     map.put("pinch"new Pinch());  
  13.     map.put("click"new Click());  
  14.     map.put("touchLongClick"new TouchLongClick());  
  15.     map.put("touchDown"new TouchDown());  
  16.     map.put("touchUp"new TouchUp());  
  17.     map.put("touchMove"new TouchMove());  
  18.     map.put("getText"new GetText());  
  19.     map.put("setText"new SetText());  
  20.     map.put("getName"new GetName());  
  21.     map.put("getAttribute"new GetAttribute());  
  22.     map.put("getDeviceSize"new GetDeviceSize());  
  23.     map.put("scrollTo"new ScrollTo());  
  24.     map.put("find"new Find());  
  25.     map.put("getLocation"new GetLocation());  
  26.     map.put("getSize"new GetSize());  
  27.     map.put("wake"new Wake());  
  28.     map.put("pressBack"new PressBack());  
  29.     map.put("pressKeyCode"new PressKeyCode());  
  30.     map.put("longPressKeyCode"new LongPressKeyCode());  
  31.     map.put("takeScreenshot"new TakeScreenshot());  
  32.     map.put("updateStrings"new UpdateStrings());  
  33.     map.put("getDataDir"new GetDataDir());  
  34.     map.put("performMultiPointerGesture"new MultiPointerGesture());  
  35.     map.put("openNotification"new OpenNotification());  
  36.     map.put("source"new Source());  
  37.     map.put("compressedLayoutHierarchy"new CompressedLayoutHierarchy());  
  38.   }  
这个map指定了我们支持的pc端过来的所有action,以及对应的处理该action的类的实例,其实这些类都是CommandHandler的子类基本上就只有一个:去实现 CommandHandler的虚拟方法execute!要做的事情就大概就这几类:
  • 控件相关的action:调用AndroidElement控件的成员变量UiObject el对应的方法来执行真实的操作
  • UiDevice相关的action:调用UiDevice提供的方法
  • UiScrollable相关的action:调用UiScrollable提供的方法
  • UiAutomator那5个对象都没有的action:该调用InteractionController的就反射调用,该调用QueryController的就反射调用。注意这两个类UiAutomator是没有提供直接调用的方法的,所以只能通过反射。更多这两个类的信息请翻看之前的UiAutomator源码分析相关的文章
  • 其他:如取得compressedLayoutHierarchy
指导action向 CommandHandler真正发生转换的地方是在这个 AndroidCommandExecutor的execute方法中:
  1. public AndroidCommandResult execute(final AndroidCommand command) {  
  2.   try {  
  3.     Logger.debug("Got command action: " + command.action());  
  4.   
  5.     if (map.containsKey(command.action())) {  
  6.       return map.get(command.action()).execute(command);  
  7.     } else {  
  8.       return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND,  
  9.           "Unknown command: " + command.action());  
  10.     }  
  11.   } catch (final JSONException e) {  
  12.     Logger.error("Could not decode action/params of command");  
  13.     return new AndroidCommandResult(WDStatus.JSON_DECODER_ERROR,  
  14.         "Could not decode action/params of command, please check format!");  
  15.   }  
  16. }  
  • 它首先叫上面的AndroidCommand解析器把json字串的action给解析出来
  • 然后通过刚提到的map把这个action对应的CommandHandler的实现类给实例化
  • 然后调用这个命令处理类的execute方法开始执行命令

3. 命令处理示例

我们这里就示例性的看下getText这个action对应的 CommandHandler是怎么去通过AndroidElement控件进行设置文本的处理的:
  1. public class GetText extends CommandHandler {  
  2.   
  3.   /* 
  4.    * @param command The {@link AndroidCommand} used for this handler. 
  5.    *  
  6.    * @return {@link AndroidCommandResult} 
  7.    *  
  8.    * @throws JSONException 
  9.    *  
  10.    * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android. 
  11.    * bootstrap.AndroidCommand) 
  12.    */  
  13.   @Override  
  14.   public AndroidCommandResult execute(final AndroidCommand command)  
  15.       throws JSONException {  
  16.     if (command.isElementCommand()) {  
  17.       // Only makes sense on an element  
  18.       try {  
  19.         final AndroidElement el = command.getElement();  
  20.         return getSuccessResult(el.getText());  
  21.       } catch (final UiObjectNotFoundException e) {  
  22.         return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,  
  23.             e.getMessage());  
  24.       } catch (final Exception e) { // handle NullPointerException  
  25.         return getErrorResult("Unknown error");  
  26.       }  
  27.     } else {  
  28.       return getErrorResult("Unable to get text without an element.");  
  29.     }  
  30.   }  
  31. }  
关键代码就是里面通过 AndroidCommand的getElement方法:
然后调用获得的AndroidElement控件对象的getText方法:
  • 最终通过调用AndroidElement控件成员UiObject控件对象的getText方法取得控件文本信息

4. 小结

bootstrap接收到appium从pc端发送过来的json格式的键值对字串有多个项:
  • cmd: 这是一个action还是一个shutdown
  • action:如果是一个action的话,那么是什么action,比如click
  • params:拥有其他的一些子项,比如指定操作控件在AndroidElementHash维护的控件哈希表的控件键值的'elementId'
在收到这个json格式命令字串后:
  • AndroidCommandExecutor会调用AndroidCommand去解析出对应的action
  • 然后把action去map到对应的真实命令处理方法CommandHandler的实现子类对象中
  • 然后调用对应的对象的execute方法来执行命令

Item

Description

Warning

Author

天地会珠海分舵

转载请注明出处!

更多精彩文章请查看本人博客!

Blog Address

http://blog.youkuaiyun.com/zhubaitian

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值