Appium Android Bootstrap源码分析之命令解析执行

2023-05-10,,

通过上一篇文章《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命令解析出来,它拥有两个成员变量:

  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()

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,然后

从控件哈希表中

找到目标

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 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.");     }   } }

关键代码就是里面通过AndroidCommand的getElement方法:

  • 解析传进来的AndroidCommand实例保存的pc端过来的json字串,找到’params‘项的子项’elementId'
  • 通过这个获得的id去控件哈希表(请查看《Appium Android Bootstrap源码分析之控件AndroidElement》)中找到目标AndroidElement控件对象

然后调用获得的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

扫描码:

<p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; color:rgb(62,62,62); font-family:'Helvetica Neue',Helvetica,'Hiragino Sans GB','Microsoft YaHei',

《Appium Android Bootstrap源码分析之命令解析执行.doc》

下载本文的Word格式文档,以方便收藏与打印。

  • springboot项目中如何使用bootstrap
    springboot项目中如何使用bootstrap

    这篇文章给大家介绍springboot项目中如何使用bootstrap,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。 首先 需要在 application.properties 文件中添加这句 spring.mvc.static-path-patt...

    2024-03-14编程代码,
  • bootstrap和jquery有哪些区别?
    bootstrap和jquery有哪些区别?

    bootstrap和jquery有哪些区别?今天就带大家了解bootstrap和jquery的四个区别,让大家对bootstrap和jquery有进一步的了解,希望对你们有所帮助。 bootstrap是什么? Bootstrap是一个使用HTML,CSS,Sass,Less和...

    2023-10-27编程代码,,
  • bootstrap框架介绍及使用方法
    bootstrap框架介绍及使用方法

    本篇文章和大家了解一下bootstrap框架介绍及使用方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 Bootstrap是一种免费的开源前端开发框架,主要用于创建网站和Web应用程序。Bootstrap框...

    2023-10-27编程代码,
  • springboot用bootstrap的方法
    springboot用bootstrap的方法

    这篇文章主要介绍了springboot用bootstrap的方法,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获。下面让小编带着大家一起了解一下。 首先 需要在 application.properties 文件中添...

    2023-10-26编程代码,
  • Appium UI自动化的那些梗
    Appium UI自动化的那些梗

    @作者 彭海波 转载请注明出处 前言 由于需求的快速迭代和敏捷测试的要求,在测试过程中引入自动化成为必不可少的手段。作为一个互联网测试团队,我们自然也引入了自动化测试这个环节。在众多的测试框架中,我们...

    2023-07-12编程代码,,
  • day47:Bootstrap
    day47:Bootstrap

    什么是Bootstrap? Bootstrap是一个开源框架,是对html\css\js\jquery等的封装,用法,复制黏贴一把梭. 关于Bootstrap的一些常用网址 网址: https://www.bootcss.com/ font-awesome http://www.fontawesome.com.cn/ i...

    2023-07-11编程代码,
  • Arthas Bootstrap的源代码示例分析
    Arthas Bootstrap的源代码示例分析

    今天就跟大家聊聊有关Arthas Bootstrap的源代码示例分析,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。 Arthas(阿尔萨斯,https://github.com/ali...

    2023-06-26编程教程,
  • bootstrap日期控件问题(双日期、清空等问题解决)
    bootstrap日期控件问题(双日期、清空等问题解决)

    bootstrap以它优美的外观和丰富的组件,使它成为目前最流行的前端框架。在项目开发中,我们使用它的日期控件确实遇到了一些问题:     1.日期控件后面两个图标点击触发失效     2.双日期关联问题    ...

    2023-06-26编程教程,,