在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能识别的代码去执行啦。下次再补上,不然这篇文章我都看不下去了,太长了。

Appium基础学习之 | Bootstrap源码分析相关推荐

  1. sheng的学习笔记-Vector源码分析

    概述 Vector底层也是数组,跟ArrayList很像(先看下ArrayList,再看Vector会很轻松),ArrayList可参考下文,并且由于效率低,已经被淘汰了,大概瞅瞅得了 sheng的学 ...

  2. Appium Android Bootstrap源码分析之简介

    在上一个系列中我们分析了UiAutomator的核心源码,对UiAutomator是怎么运行的原理有了根本的了解.今天我们会开始另外一个在安卓平台上基于UiAutomator的新起之秀--Appium ...

  3. Java设计模式学习以及底层源码分析

    源码在分支master 工厂模式 把具体创建产品的细节封装起来,你要什么产品,我给你什么产品即可. 简单工厂模式 工厂方法模式 缓存层:抽象类 抽象工厂模式 缓存层是:接口 原型模式 问题: 原型模式 ...

  4. Netty学习十七:源码分析之HashWheelTimer

    一.常见定时任务实现 定时器的使用场景包括:成月统计报表.财务对账.会员积分结算.邮件推送等,它一般有三种表现形式:按固定周期定时执行.延迟一定时间后执行.指定某个时刻执行. 定时器的本质是设计一种数 ...

  5. bootstrap源码分析之Carousel

    源码文件: Carousel.scss Carousel.js 实现原理: 隐藏所有要显示的元素,然后指定当前要显示的为block,宽.高自适应 源码分析: 1.Html结构:主要分为以四个部分   ...

  6. Licode入门学习:MediaStream源码分析(二)

    Licode服务与启动过程分析 MediaStream源码分析(一) MediaStream源码分析(二) MediaStream源码分析(三) WebRtcConnection源码分析(一) Web ...

  7. Licode入门学习:MediaStream源码分析(三)

    Licode服务与启动过程分析 MediaStream源码分析(一) MediaStream源码分析(二) MediaStream源码分析(三) WebRtcConnection源码分析(一) Web ...

  8. Licode入门学习:WebRtcConnection源码分析(一)

    Licode服务与启动过程分析 WebRtcConnection源码分析(一) WebRtcConnection源码分析(二) WebRtcConnection源码分析(三) MediaStream源 ...

  9. Licode入门学习:MediaStream源码分析(一)

    Licode服务与启动过程分析 MediaStream源码分析(一) MediaStream源码分析(二) MediaStream源码分析(三) WebRtcConnection源码分析(一) Web ...

最新文章

  1. win10右键闪退到桌面_【雷粉百科】windows10鼠标点击右键出现卡顿或者转圈
  2. python中类型错误、计数不采用关键字的错误怎么改_Learning/Python-面试问题.md at master · yxxyyx1314/Learning · GitHub...
  3. 三种集中式总线判优控制
  4. “正尝试安装的adobe flash player不是最新版本”的解决方法:
  5. 关于Swift中Struct,Class和Enum的哪些事儿
  6. 简单阻容降压电路图_升压降压芯片电路
  7. Android单元测试研究与实践
  8. python伪代码书写规范_代码这样写更优雅(Python 版)(转载)
  9. PHP基础题带详细答案,PHP基础语法试题(一)答案版.docx
  10. 解决 git branch -a 无法全部显示远程的分支,只显示master分支
  11. 自己设计过App的数据库框架?还是只是停留在使用ormlite greenDao这类框架,一篇文章帮你解答...
  12. 【数据集】PASCAL VOC2012数据集百度网盘链接
  13. elasticsearch安装部署
  14. 二手书店APP软件开发定制
  15. 运放搭建的跟随电路作用与分析
  16. 深度学习——keras教程系列基础知识
  17. 福建省2021高考成绩如何查询,2021福建省地区高考成绩排名查询,福建省高考各高中成绩喜报榜单...
  18. java-php-python-springboo垃圾分类网站计算机毕业设计
  19. 五万字 | Hive知识体系保姆级教程
  20. VC++6.0 报错error spawning cl.exe解决方法【推荐】

热门文章

  1. java谐音_谐 音 梗 生 成 器
  2. adb命令行刷机 使用adb sideload命令推送刷机包到recovery直接刷机
  3. 专利申请流程所需时间是怎样的?
  4. Docker仓库harbor
  5. tomcat 配置日志
  6. 车载媒体服务器停止运行,智能电视提示应用停止运行 三种方法亲测有效
  7. [华语][张国荣][16CD][1978-1987][APE+CUE][3.94G][115][sqhhj0622#HD2PT]
  8. arch-linux+xfce4+lightdm+uefi个人安装记录
  9. Ubuntu 13.04双显卡安装NVIDIA GT630M驱动
  10. TorchDrug教程--逆合成