您的位置:首页 > 移动开发

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

2015-01-08 13:27 796 查看
通过上一篇文章《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
虚拟类,其他真实CommandHandler如click的父类
AndroidComma
ndExecutor
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命令解析出来,它拥有两个成员变量:

[java]
view plaincopy

JSONObject         json;  
AndroidCommandType cmdType;  

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

Method
Return
Code
Description
AndroidCommand
N/A
[java]
view plaincopy

public AndroidCommand(final String jsonStr)   
  throws JSONException,  
  CommandTypeException {  
  json = new JSONObject(jsonStr);  
  setType(json.getString("cmd"));  
}  

构造函数构造函数,把客户端过
来的json格式命
令保存起来并根
据命令的cmd项
设置好cmdType
action()
String
[java]
view plaincopy

public String action()   
  throws JSONException {  
  if (isElementCommand()) {  
    return json.getString("action").  
          substring(8);  
  }  
  return json.getString("action");  
}  

解析出客户端过
来的json字串的
action这个项并
返回
commandType()
AndroidCom
mandType 
[java]
view plaincopy

public AndroidCommandType commandType() {  
  return cmdType;  
}  

是ACTION还是SHUTDOWN
getDestElement
AndroidElement
[java]
view plaincopy

public AndroidElement getDestElement()   
  throws JSONException {  
  String destElId = (String) params().  
        get("destElId");  
  return AndroidElementsHash.  
        getInstance().  
        getElement(destElId);  
}  

解析出json字串
中params项的子
项destElId,然后
从控件哈希表中
找到目标
AndroidElement
控件返回
getElement
AndroidElement
[java]
view plaincopy

public AndroidElement getElement()   
  throws JSONException {  
  String elId = (String) params().  
        get("elementId");  
  return AndroidElementsHash.getInstance().  
        getElement(elId);  
}  

解析出json字串
中params项的子
项elementId,然
后从控件哈希表
中找到目标
AndroidElement
控件返回
isElementCommand
boolean
[java]
view plaincopy

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>
[java]
view plaincopy

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
[java]
view plaincopy

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所做的事情:

[java]
view plaincopy

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方法中:

[java]
view plaincopy

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控件进行设置文本的处理的:

[java]
view plaincopy

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方法取得控件文本信息

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.csdn.net/zhubaitian




内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: