Appium Android Bootstrap源码分析之命令解析执行
通过上一篇文章《Appium Android Bootstrap源码分析之控件AndroidElement》我们知道了Appium从pc端发送过来的命令如果是控件相关的话,最终目标控件在bootstrap中是以AndroidElement对象的方式呈现出来的,并且该控件对象会在AndroidElementHash维护的控件哈希表中保存起来。但是appium触发一个命令除了需要提供是否与控件相关这个信息外,还需要其他的一些信息,比如,这个是什么命令?这个就是我们这篇文章需要讨论的话题了。
下面我们还是先看一下从pc端发过来的json的格式是怎么样的:
可以看到里面除了params指定的是哪一个控件之外,还指定了另外两个信息:
cmd: 这是一个action还是一个shutdownaction:如果是一个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等命令同理
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字串。
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还是一个shutdownaction:如果是一个action的话,那么是什么action,比如clickparams:拥有其他的一些子项,比如指定操作控件在AndroidElementHash维护的控件哈希表的控件键值的'elementId' 在收到这个json格式命令字串后: AndroidCommandExecutor会调用AndroidCommand去解析出对应的action然后把action去map到对应的真实命令处理方法CommandHandler的实现子类对象中然后调用对应的对象的execute方法来执行命令
作者
自主博客
微信
CSDN
天地会珠海分舵
http://techgogogo.com
服务号:TechGoGoGo
扫描码:
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。