Appium基础学习之 | Bootstrap源码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ouyanggengcheng/article/details/85210829

Appium执行日志流程分析这里已经讲解到Bootstrap的重要作用,今天就来切洋葱切一下它,看看它的真实面目:

源码地址:https://github.com/appium-boneyard/appium-android-bootstrap/tree/master/bootstrap,下载后导入Eclipse如下

1.Bootstrap类(在io.appium.android.bootstrap包下)

public class Bootstrap extends UiAutomatorTestCase {

  public void testRunServer() {
    Find.params = getParams();
    //disableAndroidWatchers、acceptSslCerts默认是false
    boolean disableAndroidWatchers = Boolean.parseBoolean(getParams().getString("disableAndroidWatchers"));
    boolean acceptSSLCerts = Boolean.parseBoolean(getParams().getString("acceptSslCerts"));
    SocketServer server;
    try {
      //启动Socket服务,监听4724端口
      server = new SocketServer(4724);
      server.listenForever(disableAndroidWatchers, acceptSSLCerts);
    } catch (final SocketServerException e) {
      Logger.error(e.getError());
      System.exit(1);
    }

  }
}

2.listenForever方法(在io.appium.android.bootstrap包下SocketServer类中)

public void listenForever(boolean disableAndroidWatchers, boolean acceptSSLCerts) throws SocketServerException {
    //可以在Appium日志里面找到这些内容的输出
    Logger.debug("Appium Socket Server Ready");
    UpdateStrings.loadStringsJson();
    if (disableAndroidWatchers) {
      Logger.debug("Skipped registering crash watchers.");
    } else {
        //dismissCrashAlerts注册监听ANR与Crash异常,如果有异常会弹出一个系统对话框
      dismissCrashAlerts();

      final TimerTask updateWatchers = new TimerTask() {
        @Override
        public void run() {
          try {
              //check()方法就是获取系统UI的对话框,如果得到则返回true
            watchers.check();
          } catch (final Exception e) {
          }
        }
      };
      //定时器每隔0.1S检查一次
      timer.scheduleAtFixedRate(updateWatchers, 100, 100);
    }

    if (acceptSSLCerts) {
      Logger.debug("Accepting SSL certificate errors.");
      acceptSSLCertificates();
    }

    try {
        //接受client发来的消息,注意一下,这里的client实际上是Appium Server,相对于设备端的Socker服务它就是client
      client = server.accept();
      Logger.debug("Client connected");
      in = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
      out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(), "UTF-8"));
      while (keepListening) {
        handleClientData();
      }
      in.close();
      out.close();
      client.close();
      Logger.debug("Closed client connection");
    } catch (final IOException e) {
      throw new SocketServerException("Error when client was trying to connect");
    }
  }

3.loadStringsJson方法(在io.appium.android.bootstrap.handler包下UpdateStrings类中)

public static boolean loadStringsJson() {
    Logger.debug("Loading json...");
    try {
        //这个/data/local/tmp/strings.json,是由Android应用的String.xml转化而来,这个肯定会存在,Appium如果无法将String.xml转化,会形成一个空的json文件放在/data/local/tmp/目录下
      String filePath = "/data/local/tmp/strings.json";
      final File jsonFile = new File(filePath);
      // json will not exist for apks that are only on device
      // because the node server can't extract the json from the apk.
      if (!jsonFile.exists()) {
        return false;
      }
      final DataInputStream dataInput = new DataInputStream(
          new FileInputStream(jsonFile));
      final byte[] jsonBytes = new byte[(int) jsonFile.length()];
      dataInput.readFully(jsonBytes);
      // this closes FileInputStream
      dataInput.close();
      final String jsonString = new String(jsonBytes, "UTF-8");
      Find.apkStrings = new JSONObject(jsonString);
      Logger.debug("json loading complete.");
    } catch (final Exception e) {
      Logger.error("Error loading json: " + e.getMessage());
      return false;
    }
    return true;
  }

从这里开始进入代码,这个方法主要是解析将String.xml文件转化成的String.json文件,然后后面具体用来干什么,在Appium现在的使用中并没有看到,如果后面涉及到再补充,继续往下。

4.回到listenForever的方法中

if (disableAndroidWatchers) {
      Logger.debug("Skipped registering crash watchers.");
    } else {
        //dismissCrashAlerts注册监听ANR与Crash异常,如果有异常会弹出一个系统对话框
      dismissCrashAlerts();

      final TimerTask updateWatchers = new TimerTask() {
        @Override
        public void run() {
          try {
              //check()方法就是获取系统UI的对话框,如果得到则返回true
            watchers.check();
          } catch (final Exception e) {
          }
        }
      };
      //定时器每隔0.1S检查一次
      timer.scheduleAtFixedRate(updateWatchers, 100, 100);
    }

首先disableAndroidWatchers的值默认是false所以走else语句块,else语句块的内容dismissCrashAlerts()方法是注册ANR、Crash两种异常错误监听(代码就不贴了);然后通过TimerTask对象的scheduleAtFixedRate()方法实现订单检查;watchers.check()方法是获取当前窗口中是否有系统的alertDialog对象,有责抛出异常。通过这里可以理解disableAndroidWatchers是作为这个注册监听的开关了。

5.继续在listenForever方法中往下走

 try {
        //接受client发来的消息,注意一下,这里的client实际上是Appium Server,相对于设备端的Socker服务它就是client
      client = server.accept();
      Logger.debug("Client connected");
      in = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
      out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(), "UTF-8"));
      while (keepListening) {
        handleClientData();
      }
      in.close();
      out.close();
      client.close();
      Logger.debug("Closed client connection");
    } catch (final IOException e) {
      throw new SocketServerException("Error when client was trying to connect");
    }

接收client发来的数据和返回的响应数据,然后往下看handleClientData()方法

6.handleClientData方法与listenForever在同一个类里

private void handleClientData() throws SocketServerException {
    try {
      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.
      while ((a = in.read()) != -1 && in.ready()) {
        input.append((char) a);
      }
      String inputString = input.toString();
      Logger.debug("Got data from client: " + inputString);
      try {
        AndroidCommand cmd = getCommand(inputString);
        Logger.debug("Got command of type " + cmd.commandType().toString());
        res = runCommand(cmd);
        Logger.debug("Returning result: " + res);
      } catch (final CommandTypeException e) {
        res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage())
            .toString();
      } catch (final JSONException e) {
        res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR,
            "Error running and parsing command").toString();
      }
      out.write(res);
      out.flush();
    } catch (final IOException e) {
      throw new SocketServerException("Error processing data to/from socket ("
          + e.toString() + ")");
    }
  }

把client获取的数据读出来赋值给input序列并转为String类型,然后往下执行getCommand()方法

private AndroidCommand getCommand(final String data) throws JSONException,
      CommandTypeException {
    return new AndroidCommand(data);
  }

getCommand()方法返回的是AndroidCommand对象

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

在这里把String转换成了JSON,并且调用setType()方法设置了cmdType的值,代码如下

 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);
    }
  }

7.继续回到handleClientData方法往下执行runCommand方法

runCommand方法也是与handleClientData方法在同一个类

private String runCommand(final AndroidCommand cmd) {
    AndroidCommandResult res;
    if (cmd.commandType() == AndroidCommandType.SHUTDOWN) {
      keepListening = false;
      res = new AndroidCommandResult(WDStatus.SUCCESS, "OK, shutting down");
    } else if (cmd.commandType() == AndroidCommandType.ACTION) {
      try {
        res = executor.execute(cmd);
      } catch (final NoSuchElementException e) {
         res = new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage());
      } catch (final Exception e) { // Here we catch all possible exceptions and return a JSON Wire Protocol UnknownError
                                    // This prevents exceptions from halting the bootstrap app
        Logger.debug("Command returned error:" + e);
        res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage());
      }
    } else {
      // this code should never be executed, here for future-proofing
      res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR,
          "Unknown command type, could not execute!");
    }
    return res.toString();
  }

通过AndroidCommand对象cmd的commandType()方法取出值比较,主要这个值在上面的方法中调用setType的时候已经设置过属性了。如果是aciton则运行else if代码块,这里说一下SHUTDOWN,是Appium服务发出一个DELETE请求或者60S都没有发命令请求过来时则为SHUTDOWN,其他情况都为ACTION。往下执行到execute()方法。

8.execute方法(在io.appium.android.bootstrap包的AndroidCommandExecutor中)

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!");
    }
  }

首先map.containsKey()方法代码如下

public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }

主要是判断入参的值是否为空,所以接下来看看入参的command.action()方法做了什么

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

isElementCommand()方法代码

public boolean isElementCommand() {
    if (cmdType == AndroidCommandType.ACTION) {
      try {
        return json.getString("action").startsWith("element:");
      } catch (final JSONException e) {
        return false;
      }
    }
    return false;
  }

主要是判断一下client客户端传过来的数据中action字段,如果action的值是以element:开头,则取出action字段的第8位开始后面值,不是以element:开头则直接取出action的值,然后判断是否为空,如果有值则为true。然后执行map.get(command.action()).execute(command);

在execute方法上面初始化map的数据来看,就非常清晰了,如下

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("getRect", new GetRect());
    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.put("configurator", new ConfiguratorHandler());
  }

如取出click的key,则调用new Click().excute()方法。

public AndroidCommandResult execute(final AndroidCommand command)
      throws JSONException {
    if (command.isElementCommand()) {
      try {
        final AndroidElement el = command.getElement();
        el.click();
        return getSuccessResult(true);
      } catch (final UiObjectNotFoundException e) {
        return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
            e.getMessage());
      } catch (final Exception e) { // handle NullPointerException
        return getErrorResult("Unknown error");
      }
    } else {
      final Hashtable<String, Object> params = command.params();
      Point coords = new Point(Double.parseDouble(params.get("x").toString()),
          Double.parseDouble(params.get("y").toString()) );

      try {
        coords = PositionHelper.getDeviceAbsPos(coords);
      } catch (final UiObjectNotFoundException e) {
        return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
            e.getMessage());
      } catch (final InvalidCoordinatesException e) {
        return new AndroidCommandResult(WDStatus.INVALID_ELEMENT_COORDINATES,
            e.getMessage());
      }

      final boolean res = UiDevice.getInstance().click(coords.x.intValue(), coords.y.intValue());
      return getSuccessResult(res);
    }
  }

整个Bootstrap的执行很粗的大概走了下来,剩下后面的就是把click等各方法由Bootstrap转为UiAutomator能识别的代码去执行啦。下次再补上,不然这篇文章我都看不下去了,太长了。

猜你喜欢

转载自blog.csdn.net/ouyanggengcheng/article/details/85210829
今日推荐