appium框架之bootstrap

原文地址:http://www.it165.net/pro/html/201407/17696.html

bootstrap结构

如图所示为bootstrap的项目结构

\

bootstrap作用

bootstrap在appium中是以jar包的形式存在的,它实际上是一个uiautomator写的case包,通过PC端的命令可以在手机端执行。

bootstrap源码分析

首先程序的入口为Bootstrap类。所以从该类开始一步一步解释这个项目

Bootstrap.java

01. package io.appium.android.bootstrap;
02.  
03. import io.appium.android.bootstrap.exceptions.socketServerException;
04. 05. com.android.uiautomator.testrunner.UiAutomatorTestCase;
06. 07. /**
08. * The Bootstrap class runs the socket server. uiautomator开发的脚本,可以直接在pc端启动
09. */
10. public class Bootstrap extends UiAutomatorTestCase {
11.  
12. void testRunServer() {
13. SocketServer server;
14. try {
15. // 启动socket服务器,监听4724端口。
16. server = new SocketServer(4724);
17. server.listenForever();
18. catch (final SocketServerException e) {
19. Logger.error(e.getError());
20. System.exit(121. }
22. 23. 24. }

该类继承自UiAutomatorTestCase。所以它才能通过adb shell uiautomator runtest AppiumBootstrap.jar -c io.appium.android.bootstrap.Bootstrap被执行。

该类很简单,就是启动线程,监听4724端口,该端口与appium通信。

然后走server.listenForever()方法

SocketServer.java

* Listens on the socket for data,and calls {@link #handleClientData()} when
* it's available.
*
* @throws SocketServerException
*/
listenForever() throws SocketServerException {
Logger.debug("Appium Socket Server Ready"//读取strings.json文件的数据
UpdateStrings.loadStringsJson();
// 注册两种监听器:AND和Crash
dismissCrashAlerts();
TimerTask updateWatchers = TimerTask() {
@Override
run() {
// 检查系统是否有异常
watchers.check();
Exception e) {
}
};
// 计时器,0.1秒后开始,每隔0.1秒执行一次。
timer.scheduleAtFixedrate(updateWatchers, 100,92)">25. 26. 27. client = server.accept();
28. "Client connected"29. in = BufferedReader(InputStreamReader(client.getInputStream(),
30. "UTF-8"));
31. out = BufferedWriter(OutputStreamWriter(client.getoutputStream(),92)">32. 33. while (keepListening) {
34. // 获取客户端数据
35. handleClientData();
36. 37. in.close();
38. out.close();
39. client.close();
40. "Closed client connection"41. IOException e) {
42. throw SocketServerException("Error when client was trying to connect"43. 44. }

方法中首先调用UpdateStrings.loadStringsJson();该方法如下:

UpdateStrings

* strings.json文件保存的是apk的strings.xml里的内容,在Bootstrap启动前由appium服务器解析并push到设备端的
*
* @return
static boolean loadStringsJson() {
"Loading json...");
String filePath = "/data/local/tmp/strings.json";
File jsonFile = File(filePath);
// json will not exist for apks that are only on device
// 你的case必须写明apk的路径,如果启动设备上已有的应用而case中没有app路径,此时json文件是不存在的
// because the node server can't extract the json from the apk.
if (!jsonFile.exists()) {
return falseDataInputStream dataInput = DataInputStream(
FileInputStream(jsonFile));
final byte[] jsonBytes = new [(int) jsonFile.length()];
dataInput.readFully(jsonBytes);
// this closes FileInputStream
dataInput.close();
String jsonString = String(jsonBytes, // 将读取出来的信息赋给Find类中的属性,以做后用
Find.apkStrings = JSONObject(jsonString);
"json loading complete."Logger.error("Error loading json: " + e.getMessage());
true然后回到ServerSocket类的listenForever(),此时执行到dismissCrashAlerts();该方法作用是注册一些监听器,观察是否有糖出口或者AND和crash的异常。

1. dismissCrashAlerts() {
2. 3. UiWatchers().registeranrAndCrashWatchers();
4. "Registered crash watchers."5. 6. "Unable to register crash watchers."7. 8. 此时listenForever()方法里执行到注册心跳程序,每隔0.1秒开始执行一遍上面注册的监听器来检查系统是否存在异常。

);

然后启动数据通道,接受客户端发来的数据和返回结果给客户端。

));

接下来就是最重要的方法handleClientData();到此listenForever()方法的主要作用就完成了。现在来看handleClientData()方法做了啥。

* When data is available on the socket,this method is called to run the
* command or throw an error if it can't.
private handleClientData() input.setLength(0); // clear
String res;
int a;
// (char) -1 is not equal to -1.
// ready is checked to ensure the read call doesn't block.
((a = in.read()) != -1 && in.ready()) {
input.append((char) a);
String inputString = input.toString();
"Got data from client: " + inputString);
<a href="http://www.it165.net/pro/ydad/" target="_blank" class="keylink">Android</a>Command cmd = getCommand(inputString);
"Got command of type " + cmd.commandType().toString());
res = runcommand(cmd);
"Returning result: " + res);
CommandTypeException e) {
res = "_blank">Android</a>CommandResult(WDStatus.UNKNowN_ERROR,e.getMessage())
.toString();
JSONException e) {
AndroidCommandResult(WDStatus.UNKNowN_ERROR,248); float:none; vertical-align:baseline; position:static; left:auto; top:auto; right:auto; bottom:auto; height:auto; width:auto; display:block">"Error running and parsing command").toString();
out.write(res);
out.flush();
IOException e) {
"Error processing data to/from socket ("
+ e.toString() + ")"

方法中读取客户端发来的数据,利用getCommand()方法获得AndroidCommand对象,然后执行runcommand()方法获取直接的结果。那么该方法的作用就转移到了runcommand()。所以现在就来看runcommand()方法是啥意思啦。

* When {@link #handleClientData()} has valid data,this method delegates the
* command.
* @param cmd
*          AndroidCommand
* @return Result
private String runcommand(AndroidCommand cmd) {
AndroidCommandResult res;
(cmd.commandType() == AndroidCommandType.SHUTDOWN) {
keepListening = ;
AndroidCommandResult(WDStatus.SUCCESS,宋体">"OK,shutting down"else (cmd.commandType() == AndroidCommandType.ACTION) {
{
res = executor.execute(cmd);
ottom:auto; height:auto; width:3em; font-family:'Courier New',e.getMessage());
else // this code should never be executed,here for future-proofing
ottom:auto; height:auto; width:3em; font-family:'Courier New',
"UnkNown command type,Could not execute!"return res.toString();
方法首先做了判断,判断命令数据哪种类型,主要有关机命令和动作命令,我们主要关注动作命令,因为动作有很多种。所以来关注第一个else if中的AndroidCommandExecutor.execute()方法。主线又转移到了该方法中了,切去瞅一眼。

AndroidCommandExecutor.java

* Gets the handler out of the map,and executes the command.
* @param command
*          The {@link AndroidCommand}
* @return {@link AndroidCommandResult}
public AndroidCommandResult execute(AndroidCommand command) {
"Got command action: " + command.action());
(map.containsKey(command.action())) {
map.get(command.action()).execute(command);
AndroidCommandResult(WDStatus.UNKNowN_COMMAND,宋体">"UnkNown command: " "Could not decode action/params of command"AndroidCommandResult(WDStatus.JSON_DECODER_ERROR,宋体">"Could not decode action/params of command,please check format!"方法中终于要执行命令的实体啦

(map.containsKey(command.action())) {
map.get(command.action()).execute(command);
+ command.action());
关键是上面这几行代码调用了map.get(command.action()).execute(command).看来要想弄懂这个命令的意思,肯定得知道map里存放的对象是哪些,那么在该类中找到map的初始化代码:

static map.put("waitForIdle"ottom:auto; height:auto; width:auto; font-family:'Courier New', WaitForIdle());
"clear"Clear());
"orientation"Orientation());
"swipe"Swipe());
"flick"Flick());
"drag"Drag());
"pinch"Pinch());
"click"Click());
"touchLongClick"TouchLongClick());
"touchDown"TouchDown());
"touchUp"TouchUp());
"touchMove"TouchMove());
"getText"GetText());
"setText"SetText());
"getName"GetName());
"getAttribute"GetAttribute());
"getDeviceSize"GetDeviceSize());
"scrollTo"ScrollTo());
"find"Find());
"getLocation"GetLocation());
"getSize"GetSize());
"wake"Wake());
"pressBack"PressBack());
"dumpWindowHierarchy"DumpWindowHierarchy());
"pressKeyCode"PressKeyCode());
"longPressKeyCode"LongPressKeyCode());
"takeScreenshot"TakeScreenshot());
"updateStrings"UpdateStrings());
"getDataDir"GetDataDir());
"performMultiPointerGesture"MultiPointerGesture());
"openNotification"OpenNotification());
豁然开朗,该map是<String,CommandHandler>形式的map。value值对应的都是一个个的对象,这些对象都继承与CommandHandler,里面都有execute方法,该方法就是根据命令的不同调用不同的对象来执行相关代码获取结果。从map的定义可以看出,appium可以操作手机的命令还不少,我用过的有scrollTo,updateStrings,getDataDir等,上面还有截图、打开通知栏、按下等还没用过,但通过这些命令你也可以了解appium可以做哪些事。

继承CommandHandler的对象有很多,我挑一个来讲讲它具体是干嘛的,其他的我以后会挨个讲,就挑click吧。

加入现在传过来的命令后缀是click的话,那么它会调用Click对象的execute方法

Click.java

io.appium.android.bootstrap.handler;
com.android.uiautomator.core.UiDevice;
com.android.uiautomator.core.UiObjectNotFoundException;
io.appium.android.bootstrap.*;
org.json.JSONException;
java.util.ArrayList;
java.util.Hashtable;
* This handler is used to click elements in the Android UI.
* Based on the element Id,click that element.
Click CommandHandler {
/*
* @param command The {@link AndroidCommand}
* @throws JSONException
* @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android.
* bootstrap.AndroidCommand)
@Override
AndroidCommand command)
JSONException {
(command.isElementCommand()) {
AndroidElement el = command.getElement();
el.click();
getSuccessResult(UiObjectNotFoundException e) {
AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,宋体">e.getMessage());
Exception e) { // handle NullPointerException
getErrorResult("UnkNown error"Hashtable<String,Object> params = command.params();
45. Double[] coords = { Double.parseDouble(params.get("x").toString()),92)">46. Double.parseDouble(params.get("y").toString()) };
47. ArrayList<Integer> posVals = absPosFromCoords(coords);
48. res = UiDevice.getInstance().click(posVals.get(),92)">49. posVals.get());
50. getSuccessResult(res);
51. 52. 53. 该类就一个execute方法这根独苗,execute方法中会先判断传入的参数对象是坐标值还是元素值,如果是元素值那么直接调用AndroidElement中的click方法,一会我们再去看这个方法。如果是坐标的话,它会干什么呢。它会调用UiDevice的click方法,用过UiAutomator的人都知道它是uiautomator包中的类。所以说appium在api16以上的机器上使用的uiautomator机制。貌似有人觉得这好像easy了点。那好吧,我们再分析一个touchDown命令,如果传过来的命令后缀是touchDown,那么它会调用TouchDown对象的execute方法

TouchDown());

这个类里面的execute方法就有点意思啦。

TouchDown.java

com.android.uiautomator.common.ReflectionUtils;
io.appium.android.bootstrap.Logger;
java.lang.reflect.Method;
* This handler is used to perform a touchDown event on an element in the
* Android UI.
TouchDown TouchEvent {
protected executetouchEvent() UiObjectNotFoundException {
printEventDebugLine("TouchDown"ReflectionUtils utils = ReflectionUtils();
Method touchDown = utils.getControllerMethod(.(Boolean) touchDown.invoke(utils.getController(),clickX,clickY);
Exception e) {
"Problem invoking touchDown: " + e);
方法里用到了反射,调用uiautomator里的隐藏api来执行按下操作。就不具体讲了,后面会挨个说一遍的。

总结

说了这么多废话,尝试着用流程图描述一遍吧。

相关文章

Bootstrip HTML 查询搜索常用格式模版 &lt;form class=&...
如何在按钮上加红色数字 您可以使用Bootstrap的badge组件来在...
要让两个按钮左右排列,你可以使用 Bootstrap 的网格系统将它...
是的,可以将status设置为布尔类型,这样可以在前端使用复选...
前端工程师一般用的是Bootstrap的框架而不是样式,样式一般自...
起步导入:<linkrel="stylesheet"href="b...