通过上一篇文章《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     虚拟类,其他真实CommandHandler如click的父类  
AndroidCommandExecutor execute HashMap<String,  CommandHan dler> map   map是所有的命令字串和真实的CommandHandler的一个映射。其成员函数execute就是通过字串命令找到map对应的handler然后执行的  
getText execute   CommandHandler 处理获取指定控件文本信息的类。真正执行的是传进来的AndroidCommand对应UiObject的getText方法 其他click,find,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扫描码: http://blog.csdn.net/zhubaitian
 

Appium Android Bootstrap源码分析之命令解析执行的更多相关文章

  1. Appium Android Bootstrap源码分析之启动运行

    通过前面的两篇文章<Appium Android Bootstrap源码分析之控件AndroidElement>和<Appium Android Bootstrap源码分析之命令解析 ...

  2. Appium Android Bootstrap源码分析之控件AndroidElement

    通过上一篇文章<Appium Android Bootstrap源码分析之简介>我们对bootstrap的定义以及其在appium和uiautomator处于一个什么样的位置有了一个初步的 ...

  3. Appium Android Bootstrap源码分析之简介

    在上一个系列中我们分析了UiAutomator的核心源码,对UiAutomator是怎么运行的原理有了根本的了解.今天我们会开始另外一个在安卓平台上基于UiAutomator的新起之秀--Appium ...

  4. Android HandlerThread 源码分析

    HandlerThread 简介: 我们知道Thread线程是一次性消费品,当Thread线程执行完一个耗时的任务之后,线程就会被自动销毁了.如果此时我又有一 个耗时任务需要执行,我们不得不重新创建线 ...

  5. Bootstrap源码分析系列之初始化和依赖项

    在上一节中我们介绍了Bootstrap整体架构,本节我们将介绍Bootstrap框架第二部分初始化及依赖项,这部分内容位于源码的第8~885行,打开源码这部分内容似乎也不是很难理解.但是请站在一个开发 ...

  6. Bootstrap源码分析系列之整体架构

    作为一名合格的前端工程师,你肯定听说过Bootstarp框架.确实可以说Bootstrap框架是最流行的前端框架之一.可是也有人说Bootstrap是给后端和前端小白用的,我认为只要学习它能给我们前端 ...

  7. Android Choreographer 源码分析

    Choreographer 的作用主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整, ...

  8. angularjs源码分析之:angularjs执行流程

    angularjs用了快一个月了,最难的不是代码本身,而是学会怎么用angular的思路思考问题.其中涉及到很多概念,比如:directive,controller,service,compile,l ...

  9. jQuery 2.0.3 源码分析Sizzle引擎解析原理

    jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理 声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 先来回答博友的提问: 如何解析 div > p + ...

随机推荐

  1. UVA 847 - A Multiplication Game(游戏)

    UVA 847 - A Multiplication Game 题目链接 题意:一个数一開始是1,每次轮流乘2-9,谁先大于n谁就赢,问谁胜 思路:博弈,找出必胜态.2-9为stan,10-18为ol ...

  2. JDK自带的监控分析工具JConsole

    非常多开发人员认为自己懂Java编程.事实是大多数开发人员都仅仅领会到了Java平台的皮毛.所学也仅仅够应付工作. 作者将深度挖掘Java平台的核心功能.揭示一些鲜为人知的事实.帮助您解决最棘手的编程 ...

  3. 如何获得SQL Server索引使用情况

    原文:如何获得SQL Server索引使用情况 原文出自: http://www.mssqltips.com/sqlservertip/1239/how-to-get-index-usage-info ...

  4. android adb 不同的方式使用特定的解释

    本文介绍windows 在程序中使用adb 方法.没有引进adb 该命令. 1) 启动adb 流程.获得输出从管道. 这样的方式的弊端有多少,我也不知道.反正就是各种问题吧.可是眼下我问过非常多朋友. ...

  5. Computer Science 学习第四章--CPU 指令集和指令处理

    Instruction set Y86 指令集 运算符:addl, subl, andl, and xorl 跳转符:jmp,jle,jl,je,jne,jge, andjg 条件符:cmovle, ...

  6. TestNg它@Factory详细解释------如何更改参数值测试

    原创文章,版权所有所有.转载,归因:http://blog.csdn.net/wanghantong TestNg的@Factory注解从字面意思上来讲就是採用工厂的方法来创建測试数据并配合完毕測试 ...

  7. hdu 3683 Gomoku (模拟、搜索)

    Gomoku Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Sub ...

  8. 两年前实习时的文档——MMC学习总结

    1概述 驱动程序实际上是硬件与应用程序之间的中间层.在Linux操作系统中,设备驱动程序对各种不同的设备提供了一致的訪问接口,把设备映射成一个特殊的设备文件,用户程序能够像其它文件一样对设备文件进行操 ...

  9. hdu 5045 Contest(状态压缩DP)

    题解:我们使用一个二位数组dp[i][j]记录进行到第i个任务时,人组合为j时的最大和(这里的j我们用二进制的每位相应一个人). 详细见代码: #include <iostream> #i ...

  10. iis7、iis8配置备份还原

    原文 iis7.iis8配置备份还原 方法1: 1.打开我们的IIS管理器,在功能视图里找到“共享的配置”这个功能然后双击进入. 2.进入“共享的配置”后单机右上方的“导出配置”选项,选择导出配置文件 ...