Appium基础学习之 | Bootstrap源码分析
在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); // clearString 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源码分析相关推荐
- sheng的学习笔记-Vector源码分析
概述 Vector底层也是数组,跟ArrayList很像(先看下ArrayList,再看Vector会很轻松),ArrayList可参考下文,并且由于效率低,已经被淘汰了,大概瞅瞅得了 sheng的学 ...
- Appium Android Bootstrap源码分析之简介
在上一个系列中我们分析了UiAutomator的核心源码,对UiAutomator是怎么运行的原理有了根本的了解.今天我们会开始另外一个在安卓平台上基于UiAutomator的新起之秀--Appium ...
- Java设计模式学习以及底层源码分析
源码在分支master 工厂模式 把具体创建产品的细节封装起来,你要什么产品,我给你什么产品即可. 简单工厂模式 工厂方法模式 缓存层:抽象类 抽象工厂模式 缓存层是:接口 原型模式 问题: 原型模式 ...
- Netty学习十七:源码分析之HashWheelTimer
一.常见定时任务实现 定时器的使用场景包括:成月统计报表.财务对账.会员积分结算.邮件推送等,它一般有三种表现形式:按固定周期定时执行.延迟一定时间后执行.指定某个时刻执行. 定时器的本质是设计一种数 ...
- bootstrap源码分析之Carousel
源码文件: Carousel.scss Carousel.js 实现原理: 隐藏所有要显示的元素,然后指定当前要显示的为block,宽.高自适应 源码分析: 1.Html结构:主要分为以四个部分 ...
- Licode入门学习:MediaStream源码分析(二)
Licode服务与启动过程分析 MediaStream源码分析(一) MediaStream源码分析(二) MediaStream源码分析(三) WebRtcConnection源码分析(一) Web ...
- Licode入门学习:MediaStream源码分析(三)
Licode服务与启动过程分析 MediaStream源码分析(一) MediaStream源码分析(二) MediaStream源码分析(三) WebRtcConnection源码分析(一) Web ...
- Licode入门学习:WebRtcConnection源码分析(一)
Licode服务与启动过程分析 WebRtcConnection源码分析(一) WebRtcConnection源码分析(二) WebRtcConnection源码分析(三) MediaStream源 ...
- Licode入门学习:MediaStream源码分析(一)
Licode服务与启动过程分析 MediaStream源码分析(一) MediaStream源码分析(二) MediaStream源码分析(三) WebRtcConnection源码分析(一) Web ...
最新文章
- win10右键闪退到桌面_【雷粉百科】windows10鼠标点击右键出现卡顿或者转圈
- python中类型错误、计数不采用关键字的错误怎么改_Learning/Python-面试问题.md at master · yxxyyx1314/Learning · GitHub...
- 三种集中式总线判优控制
- “正尝试安装的adobe flash player不是最新版本”的解决方法:
- 关于Swift中Struct,Class和Enum的哪些事儿
- 简单阻容降压电路图_升压降压芯片电路
- Android单元测试研究与实践
- python伪代码书写规范_代码这样写更优雅(Python 版)(转载)
- PHP基础题带详细答案,PHP基础语法试题(一)答案版.docx
- 解决 git branch -a 无法全部显示远程的分支,只显示master分支
- 自己设计过App的数据库框架?还是只是停留在使用ormlite greenDao这类框架,一篇文章帮你解答...
- 【数据集】PASCAL VOC2012数据集百度网盘链接
- elasticsearch安装部署
- 二手书店APP软件开发定制
- 运放搭建的跟随电路作用与分析
- 深度学习——keras教程系列基础知识
- 福建省2021高考成绩如何查询,2021福建省地区高考成绩排名查询,福建省高考各高中成绩喜报榜单...
- java-php-python-springboo垃圾分类网站计算机毕业设计
- 五万字 | Hive知识体系保姆级教程
- VC++6.0 报错error spawning cl.exe解决方法【推荐】
热门文章
- java谐音_谐 音 梗 生 成 器
- adb命令行刷机 使用adb sideload命令推送刷机包到recovery直接刷机
- 专利申请流程所需时间是怎样的?
- Docker仓库harbor
- tomcat 配置日志
- 车载媒体服务器停止运行,智能电视提示应用停止运行 三种方法亲测有效
- [华语][张国荣][16CD][1978-1987][APE+CUE][3.94G][115][sqhhj0622#HD2PT]
- arch-linux+xfce4+lightdm+uefi个人安装记录
- Ubuntu 13.04双显卡安装NVIDIA GT630M驱动
- TorchDrug教程--逆合成