Appium Appium 入门到原理之 Appium Android Bootstrap 源码分析之命令解析执行

TechoGoGoGo · 2015年02月04日 · 最后由 星修 回复于 2017年08月31日 · 1803 次阅读

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

可以看到里面除了 params 指定的是哪一个控件之外,还指定了另外两个信息:
cmd: 这是一个 action 还是一个 shutdown
action:如果是一个 action 的话,那么是什么 action
开始前我们先简要描述下我们需要涉及到几个关键类:

  1. Appium 命令解析器 AndroidCommand

AndroidCommand 这个类真实的作用其实就是去把 Appium 从 pc 端发送过来的那串 json 命令解析出来,它拥有两个成员变量:
JSONObject json;
AndroidCommandType cmdType;
json 就是 pc 过来的 json 格式的那串命令,cmdType 就是 action 或者 shutdown,其实就是用来把这个类伪装成更像个命令类而已,我认为如果不提供这个成员变量而直接修改其 getType 的实现去解析 json 字串直接获得对应的 AndroidCommandType,然后把这个类的名字改成 AndroidCommandParser 得了。
那么我们往下看下 AndroidCommand 究竟是怎么对客户端命令进行解析的,它的方法都很短,所以我把它做成一个表,这样比较清晰点:





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

  1. Action 与 CommandHandler 的映射关系

从上面描述可以知道,一个 action 就是一个代表该命令的字串,比如 ‘click’。但是一个字串是不能去执行的啊,所以我们需要有一种方式把它转换成可以执行的代码,这个就是 AndroidCommandExecutor 维护的一个静态 HashMap map 所做的事情:
class AndroidCommandExecutor {

private static HashMap map = new HashMap();

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 方法开始执行命令

  1. 命令处理示例

我们这里就示例性的看下 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 方法: 解析传进来的 AndroidCommand 实例保存的 pc 端过来的 json 字串,找到’params‘项的子项’elementId' 通过这个获得的 id 去控件哈希表(请查看《Appium Android Bootstrap 源码分析之控件 AndroidElement》)中找到目标 AndroidElement 控件对象 然后调用获得的 AndroidElement 控件对象的 getText 方法: 最终通过调用 AndroidElement 控件成员 UiObject 控件对象的 getText 方法取得控件文本信息
  1. 小结

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

作者:天地会珠海分舵
http://techgogogo.com
http://blog.csdn.net/zhubaitian

共收到 1 条回复 时间 点赞
恒温 Appium 入门到原理合集 中提及了此贴 03月16日 08:33

赞赞赞

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册