zoukankan      html  css  js  c++  java
  • Appium Android Bootstrap源码分析之命令解析执行

    通过上一篇文章《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

    AndroidCommandType   enum AndroidCommandType {ACTION,SHUTDOWN }   安卓命令的类型,只有两种,shutdown的处理方式和普通的action会不一样  
    AndroidCommand action/getElement JSONObject json;AndroidCommandType cmdType;   从用户发过来的json命令信息得到真正的命令  
    CommandHandler execute     虚拟类,其他真实CommandHandlerclick的父类  
    AndroidCommandExecutor 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命令解析出来,它拥有两个成员变量:
      JSONObject         json;
      AndroidCommandType cmdType;
    json就是pc过来的json格式的那串命令,cmdType就是action或者shutdown,其实就是用来把这个类伪装成更像个命令类而已,我认为如果不提供这个成员变量而直接修改其getType的实现去解析json字串直接获得对应的AndroidCommandType,然后把这个类的名字改成AndroidCommandParser得了。

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

    Method

    Return

    Code

    Description

    AndroidCommand N/A
      public AndroidCommand(final String jsonStr) 
    		  throws JSONException,
    		  CommandTypeException {
        json = new JSONObject(jsonStr);
        setType(json.getString("cmd"));
      }
    构造函数构造函数,把客户端过来的json格式命 令保存起来并根 据命令的cmd 设置好cmdType
    action() String
      public String action() 
    		  throws JSONException {
        if (isElementCommand()) {
          return json.getString("action").
        		  substring(8);
        }
        return json.getString("action");
      }
    解析出客户端过来的json字串的 action这个项并 返回
    commandType() AndroidCommandType 
      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,然后 从控件哈希表中 找到目标 AndroidElement 控件返回
    getElement AndroidElement
      public AndroidElement getElement() 
    		  throws JSONException {
        String elId = (String) params().
        		get("elementId");
        return AndroidElementsHash.getInstance().
        		getElement(elId);
      }
    解析出json字串params项的子 elementId, 后从控件哈希表 中找到目标 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:’ 字串开始的话就证 明是个控件相关的 命令,否则就不是
    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);
        }
      }
    就是构造函数根json字串的 ’cmd’这个项的值 来调用这个方法 来设置的AndroidCommand Type

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

    2. Action与CommandHandler的映射关系

    从上面描述可以知道,一个action就是一个代表该命令的字串,比如‘click’。但是一个字串是不能去执行的啊,所以我们需要有一种方式把它转换成可以执行的代码,这个就是AndroidCommandExecutor维护的一个静态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,以及对应的处理该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方法中:
      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对应的CommandHandler的实现类给实例化
    • 然后调用这个命令处理类的execute方法开始执行命令

    3. 命令处理示例

    我们这里就示例性的看下getText这个action对应的CommandHandler是怎么去通过AndroidElement控件进行设置文本的处理的:
    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 getSuccessResult(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.");
        }
      }
    }
    关键代码就是里面通过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方法来执行命令
    作者 自主博客 微信服务号及扫描码 CSDN
    天地会珠海分舵 http://techgogogo.com 服务号:TechGoGoGo扫描码:qrcode_for_gh_0388b3c825f5_430 http://blog.csdn.net/zhubaitian
     
  • 相关阅读:
    706. Design HashMap 实现哈希表
    5. Longest Palindromic Substring 返回最长的回文子串
    8. String to Integer (atoi) 字符串转成整数
    22. Generate Parentheses产生所有匹配括号的方案
    245. Shortest Word Distance III 单词可以重复的最短单词距离
    java之spring之初始spring
    java之hibernate之hibernate缓存
    java之hibernate之hibernate查询
    java之hibernate之加载策略和抓取策略
    java之hibernate之 cascade和inverse
  • 原文地址:https://www.cnblogs.com/techgogogo/p/4284832.html
Copyright © 2011-2022 走看看