重庆分公司,新征程启航

为企业提供网站建设、域名注册、服务器等服务

appium框架之bootstrap

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

成都创新互联于2013年创立,是专业互联网技术服务公司,拥有项目成都做网站、成都网站制作、成都外贸网站建设网站策划,项目实施与项目整合能力。我们以让每一个梦想脱颖而出为使命,1280元万源做网站,已为上家服务,为万源各地企业和个人服务,联系电话:18980820575

bootstrap结构

如图所示为bootstrap的项目结构

appium框架之bootstrap

view sourceprint?
01.package io.appium.android.bootstrap;
02. 
03.import io.appium.android.bootstrap.exceptions.SocketServerException;
04. 
05.import 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.public 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(1);
21.}
22. 
23.}
24.}

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

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

然后走server.listenForever()方法。

SocketServer.java

view sourceprint?
01./**
02.* Listens on the socket for data, and calls {@link #handleClientData()} when
03.* it's available.
04.*
05.* @throws SocketServerException
06.*/
07.public void listenForever() throws SocketServerException {
08.Logger.debug("Appium Socket Server Ready");
09.//读取strings.json文件的数据
10.UpdateStrings.loadStringsJson();
11.// 注册两种监听器:AND和Crash
12.dismissCrashAlerts();
13.final TimerTask updateWatchers = new TimerTask() {
14.@Override
15.public void run() {
16.try {
17.// 检查系统是否有异常
18.watchers.check();
19.catch (final Exception e) {
20.}
21.}
22.};
23.// 计时器,0.1秒后开始,每隔0.1秒执行一次。
24.timer.scheduleAtFixedRate(updateWatchers, 100100);
25. 
26.try {
27.client = server.accept();
28.Logger.debug("Client connected");
29.in = new BufferedReader(new InputStreamReader(client.getInputStream(),
30."UTF-8"));
31.out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),
32."UTF-8"));
33.while (keepListening) {
34.// 获取客户端数据
35.handleClientData();
36.}
37.in.close();
38.out.close();
39.client.close();
40.Logger.debug("Closed client connection");
41.catch (final IOException e) {
42.throw new SocketServerException("Error when client was trying to connect");
43.}
44.}

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

UpdateStrings

view sourceprint?
01./**
02.* strings.json文件保存的是apk的strings.xml里的内容,在Bootstrap启动前由appium服务器解析并push到设备端的
03.*
04.* @return
05.*/
06.public static boolean loadStringsJson() {
07.Logger.debug("Loading json...");
08.try {
09.final String filePath = "/data/local/tmp/strings.json";
10.final File jsonFile = new File(filePath);
11.// json will not exist for apks that are only on device
12.// 你的case必须写明apk的路径,如果启动设备上已有的应用而case中没有app路径,此时json文件是不存在的
13.// because the node server can't extract the json from the apk.
14.if (!jsonFile.exists()) {
15.return false;
16.}
17.final DataInputStream dataInput = new DataInputStream(
18.new FileInputStream(jsonFile));
19.final byte[] jsonBytes = new byte[(int) jsonFile.length()];
20.dataInput.readFully(jsonBytes);
21.// this closes FileInputStream
22.dataInput.close();
23.final String jsonString = new String(jsonBytes, "UTF-8");
24.// 将读取出来的信息赋给Find类中的属性,以做后用
25.Find.apkStrings = new JSONObject(jsonString);
26.Logger.debug("json loading complete.");
27.catch (final Exception e) {
28.Logger.error("Error loading json: " + e.getMessage());
29.return false;
30.}
31.return true;
32.}

然后回到ServerSocket类的listenForever(),此时执行到dismissCrashAlerts();该方法作用是注册一些监听器,观察是否有糖出口或者AND和crash的异常。

view sourceprint?
1.public void dismissCrashAlerts() {
2.try {
3.new UiWatchers().registerAnrAndCrashWatchers();
4.Logger.debug("Registered crash watchers.");
5.catch (final Exception e) {
6.Logger.debug("Unable to register crash watchers.");
7.}
8.}

此时listenForever()方法里执行到注册心跳程序,每隔0.1秒开始执行一遍上面注册的监听器来检查系统是否存在异常。

view sourceprint?
01.final TimerTask updateWatchers = new TimerTask() {
02.@Override
03.public void run() {
04.try {
05.// 检查系统是否有异常
06.watchers.check();
07.catch (final Exception e) {
08.}
09.}
10.};
11.// 计时器,0.1秒后开始,每隔0.1秒执行一次。
12.timer.scheduleAtFixedRate(updateWatchers, 100100);

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

view sourceprint?
1.client = server.accept();
2.Logger.debug("Client connected");
3.in = new BufferedReader(new InputStreamReader(client.getInputStream(),
4."UTF-8"));
5.out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),
6."UTF-8"));

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

view sourceprint?
01./**
02.* When data is available on the socket, this method is called to run the
03.* command or throw an error if it can't.
04.*
05.* @throws SocketServerException
06.*/
07.private void handleClientData() throws SocketServerException {
08.try {
09.input.setLength(0); // clear
10. 
11.String res;
12.int a;
13.// (char) -1 is not equal to -1.
14.// ready is checked to ensure the read call doesn't block.
15.while ((a = in.read()) != -1 && in.ready()) {
16.input.append((char) a);
17.}
18.final String inputString = input.toString();
19.Logger.debug("Got data from client: " + inputString);
20.try {
22.Logger.debug("Got command of type " + cmd.commandType().toString());
23.res = runCommand(cmd);
24.Logger.debug("Returning result: " + res);
25.catch (final CommandTypeException e) {
26.res = new "http://www.it165.net/pro/ydad/" target="_blank"class="keylink">AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage())
27..toString();
28.catch (final JSONException e) {
29.res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR,
30."Error running and parsing command").toString();
31.}
32.out.write(res);
33.out.flush();
34.catch (final IOException e) {
35.throw new SocketServerException("Error processing data to/from socket ("
36.+ e.toString() + ")");
37.}
38.}

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

view sourceprint?
01./**
02.* When {@link #handleClientData()} has valid data, this method delegates the
03.* command.
04.*
05.* @param cmd
06.*          AndroidCommand
07.* @return Result
08.*/
09.private String runCommand(final AndroidCommand cmd) {
10.AndroidCommandResult res;
11.if (cmd.commandType() == AndroidCommandType.SHUTDOWN) {
12.keepListening = false;
13.res = new AndroidCommandResult(WDStatus.SUCCESS, "OK, shutting down");
14.else if (cmd.commandType() == AndroidCommandType.ACTION) {
15.try {
16.res = executor.execute(cmd);
17.catch (final Exception e) {
18.res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage());
19.}
20.else {
21.// this code should never be executed, here for future-proofing
22.res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR,
23."Unknown command type, could not execute!");
24.}
25.return res.toString();
26.}
27.}

该方法首先做了判断,判断命令数据哪种类型,主要有关机命令和动作命令,我们主要关注动作命令,因为动作有很多种。所以来关注第一个else if中的AndroidCommandExecutor.execute()方法。主线又转移到了该方法中了,切去瞅一眼。

AndroidCommandExecutor.java

view sourceprint?
01./**
02.* Gets the handler out of the map, and executes the command.
03.*
04.* @param command
05.*          The {@link AndroidCommand}
06.* @return {@link AndroidCommandResult}
07.*/
08.public AndroidCommandResult execute(final AndroidCommand command) {
09.try {
10.Logger.debug("Got command action: " + command.action());
11. 
12.if (map.containsKey(command.action())) {
13.return map.get(command.action()).execute(command);
14.else {
15.return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND,
16."Unknown command: " + command.action());
17.}
18.catch (final JSONException e) {
19.Logger.error("Could not decode action/params of command");
20.return new AndroidCommandResult(WDStatus.JSON_DECODER_ERROR,
21."Could not decode action/params of command, please check format!");
22.}
23.}

该方法中终于要执行命令的实体啦

view sourceprint?
1.if (map.containsKey(command.action())) {
2.return map.get(command.action()).execute(command);
3.else {
4.return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND,
5."Unknown command: " + command.action());
6.}

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

view sourceprint?
01.static {
02.map.put("waitForIdle"new WaitForIdle());
03.map.put("clear"new Clear());
04.map.put("orientation"new Orientation());
05.map.put("swipe"new Swipe());
06.map.put("flick"new Flick());
07.map.put("drag"new Drag());
08.map.put("pinch"new Pinch());
09.map.put("click"new Click());
10.map.put("touchLongClick"new TouchLongClick());
11.map.put("touchDown"new TouchDown());
12.map.put("touchUp"new TouchUp());
13.map.put("touchMove"new TouchMove());
14.map.put("getText"new GetText());
15.map.put("setText"new SetText());
16.map.put("getName"new GetName());
17.map.put("getAttribute"new GetAttribute());
18.map.put("getDeviceSize"new GetDeviceSize());
19.map.put("scrollTo"new ScrollTo());
20.map.put("find"new Find());
21.map.put("getLocation"new GetLocation());
22.map.put("getSize"new GetSize());
23.map.put("wake"new Wake());
24.map.put("pressBack"new PressBack());
25.map.put("dumpWindowHierarchy"new DumpWindowHierarchy());
26.map.put("pressKeyCode"new PressKeyCode());
27.map.put("longPressKeyCode"new LongPressKeyCode());
28.map.put("takeScreenshot"new TakeScreenshot());
29.map.put("updateStrings"new UpdateStrings());
30.map.put("getDataDir"new GetDataDir());
31.map.put("performMultiPointerGesture"new MultiPointerGesture());
32.map.put("openNotification"new OpenNotification());
33.}

豁然开朗,该map是形式的map。value值对应的都是一个个的对象,这些对象都继承与CommandHandler,里面都有execute方法,该方法就是根据命令的不同调用不同的对象来执行相关代码获取结果。从map的定义可以看出,appium可以操作手机的命令还不少,我用过的有scrollTo,updateStrings,getDataDir等,上面还有截图、打开通知栏、按下等还没用过,但通过这些命令你也可以了解appium可以做哪些事。

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

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

Click.java