在《MonkeyRunner源码分析之与Android设备通讯方式》中,我们谈及到MonkeyRunner控制目标android设备有多种方法,其中之一就是在目标机器启动一个monkey服务来监听指定的一个端口,然后monkeyrunner再连接上这个端口来发送命令,驱动monkey去完成相应的工作。
当时我们只分析了monkeyrunner这个客户端的代码是怎么实现这一点的,但没有谈monkey那边是如何接受命令,接受到命令又是如何处理的。
所以自己打开源码看了一个晚上,大概有了概念。但今天网上搜索了下,发现已经有网友“chenjie”对monkey的源码做过相应的分析了,而且文章写得非常有概括性,应该是高手所为,果断花了2个积分下载下来,不敢独享,本想贴上来分享给大家,但发现pdf的文档直接拷贝上来会丢失掉图片,所以只好贴上下载地址:http://download.csdn.net/download/zqilu/6884491
但文章主要是架构性得去描述monkey是怎么工作的,按照我自己的习惯,我还是喜欢按照自己的思维和有目的性的去了解我想要的,在这里我想要的是搞清楚monkey是如何处理monkeyrunner过来的命令的。
本文我们就先看下monkey的运行流程。
1. 运行环境设置
和monkeyrunner一样,monkey这个命令也是一个shell脚本,它是在我们的目标android设备的“/system/bin/monkey”,其实这是一个android上面java程序启动的标准流程。
base=/system
export CLASSPATH=$base/framework/monkey.jar
trap "" HUP
exec app_process $base/bin com.android.commands.monkey.Monkey $*
android中可以通过多种方式启动java应用,通过app_process命令启动就是其中一种,它可以帮忙注册android JNI,而绕过dalvik以使用Native API(如我般不清楚的请百度)所做的主要事情如下:
- 设置monkey的CLASSPATH环境变量指向monkey.jar
- 通过app_process指定monkey的入口和传进来的所有参数启动上面CLASSPATH设定的monkey.jar
2.命令行参数解析
通过以上的app_process指定的monkey入口,我们可以知道我们的入口函数main是在com.android.commands.Monkey这个类里面的:
/**
* Command-line entry point.
*
* @param args The command-line arguments
*/
public static void main(String[] args) {
// Set the process name showing in "ps" or "top"
Process.setArgV0("com.android.commands.monkey");
int resultCode = (new Monkey()).run(args);
System.exit(resultCode);
}
入口函数很简单,直接跳到run这个方法,这是一个很重要的方法,里面大概会做以下这些事情:
- 处理命令行参数
- 根据命令行参数启动不同的事件源,也就是我们的测试事件究竟是从网络如monkeyrunner过来的还是monkey内部的random测试数据集过来的还是脚本过来的如此之类
- 跳入runMonkeyCyncle方法针对不同的事件源开始获取并执行不同的事件
这个方法会比较长,我们只看我们现在想要的关键片段,可以看到里面调用了命令行处理函数processOptions.
private int run(String[] args) {
...
if (!processOptions()) {
return -1;
}
...
}
进去之后就是很普通的读取命令行的参数然后一个个进行解析保存了,没有太多特别的东西,这里就直接贴出monkey的参数选项大家看看就好了:
3. 初始化测试事件源
如前所述,run方法里面在获得命令行参数后会进入下一个环节,就是根据不同的参数去初始化不同的事件源
private int run(String[] args) {
...
if (mScriptFileNames != null && mScriptFileNames.size() == 1) {
// script mode, ignore other options
mEventSource = new MonkeySourceScript(mRandom, mScriptFileNames.get(0), mThrottle,
mRandomizeThrottle, mProfileWaitTime, mDeviceSleepTime);
mEventSource.setVerbose(mVerbose);
mCountEvents = false;
} else if (mScriptFileNames != null && mScriptFileNames.size() > 1) {
if (mSetupFileName != null) {
mEventSource = new MonkeySourceRandomScript(mSetupFileName,
mScriptFileNames, mThrottle, mRandomizeThrottle, mRandom,
mProfileWaitTime, mDeviceSleepTime, mRandomizeScript);
mCount++;
} else {
mEventSource = new MonkeySourceRandomScript(mScriptFileNames,
mThrottle, mRandomizeThrottle, mRandom,
mProfileWaitTime, mDeviceSleepTime, mRandomizeScript);
}
mEventSource.setVerbose(mVerbose);
mCountEvents = false;
} else if (mServerPort != -1) {
try {
mEventSource = new MonkeySourceNetwork(mServerPort);
} catch (IOException e) {
System.out.println("Error binding to network socket.");
return -5;
}
mCount = Integer.MAX_VALUE;
} else {
// random source by default
if (mVerbose >= 2) { // check seeding performance
System.out.println("// Seeded: " + mSeed);
}
mEventSource = new MonkeySourceRandom(mRandom, mMainApps, mThrottle, mRandomizeThrottle);
mEventSource.setVerbose(mVerbose);
// set any of the factors that has been set
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
if (mFactors[i] <= 0.0f) {
((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
}
}
// in random mode, we start with a random activity
((MonkeySourceRandom) mEventSource).generateActivity();
}
...
mNetworkMonitor.start();
int crashedAtCycle = runMonkeyCycles();
mNetworkMonitor.stop();
...
}
事件源代表测试数据的事件是从哪里过来的,不同的event source会有不同的类来做相应的实现:
- MonkeySourceNetwork.java: 事件是从网络如monkeyrunner过来的,处理的是《MonkeyRunner源码分析之与Android设备通讯方式》描述的界面控制操作事件
- MonkeySourceNetworkVars.java: 事件也是从网络如monkeyrunner过来的,处理的是《MonkeyRunner源码分析之与Android设备通讯方式》提到的getPropery事件
- MonkeySourceNetworkViews.java:事件也是从网络如monkeyrunner过来的,处理的是《MonkeyRunner源码分析之与Android设备通讯方式》提到的Views相关的事件
- MonkeySourceRandom.java:事件是从monkey内部生成的随机事件集,也就是我们通过命令行启动monkey测试目标app的常用方式
- MonkeySourceRanodomeScript.java: 上面的随机内部数据源也可以通过指定setup脚本来创建
- MonkeySourceScript.java: 用户也可以遵循一定的规则编写monkey脚本来驱动monkey进行相关测试,与上面不同的是它不再是随机的
往后的文章我们会针对其中一个事件源进行分析,在这里我们只需要知道这些事件源代表了事件的不同的来源,它会
- 从指定的源获取命令
- 把命令翻译成monkey事件然后放到命令队列EventQueue
这里需要注意的是每一个EventSource类都是实现了MonkeyEventSource这个接口的,这个接口最重要的就是要求实现类必须实现getNextEvent这个方法来生成并获取事件。这样子做的好处就是屏蔽了每一个具体事件源的实现细节,其他地方的代码只需要调用这个接口的getNextEvent方法获得事件源就行了,而无需关心这些事件是从哪个源过来的。这些都是面向对象的面向接口编程的基础了,大家有不清楚的最好先去了解下java的一些基本知识,这样理解起来会快很多。
4. 循环执行事件
run方法根据参数从不同的事件源获得事件并放入到EventQueue后,就会开始执行一个循环去从EventQueue里获取事件进行执行
private int run(String[] args) {
...
int crashedAtCycle = runMonkeyCycles();
...
}
如前所述,runMonkeyCyles方法会根据不同的数据源开始一条条的获取事件并进行执行:
/**
* Run mCount cycles and see if we hit any crashers.
* <p>
* TODO: Meta state on keys
*
* @return Returns the last cycle which executed. If the value == mCount, no
* errors detected.
*/
private int runMonkeyCycles() {
int eventCounter = 0;
int cycleCounter = 0;
boolean shouldReportAnrTraces = false;
boolean shouldReportDumpsysMemInfo = false;
boolean shouldAbort = false;
boolean systemCrashed = false;
// TO DO : The count should apply to each of the script file.
while (!systemCrashed && cycleCounter < mCount) {
...
MonkeyEvent ev = mEventSource.getNextEvent();
if (ev != null) {
int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
...
}
...
}
....
}
注意这里的mEventSource就是我们上面提到的事件源的接口,它屏蔽了每个事件实现类的具体细节,我们只需要告诉这个接口我们现在需要取一条事件然后执行它,该结构根据面向对象的多态原理,就会自动取事件的实现类获得对应的事件进行返回。
所以这里大家还需要对多态这个概念有所了解,特别是一些从手动测试转到自动化测试的朋友,可能之前没有接触过太多面向对象的知识。本人以前做过开发,所以还ok。这里只是做一个善意的提醒。
获得事件后下一步就是去执行相应的事件了,不同的事件会有不同的处理方式,或只是执行个命令,或调用WindowManager隐藏接口做事件注入等,这些都会在今后文章进行进一步阐述
这一篇文章就到此为止了,目的就是让大家对整一个monkey执行的流程有个初步的了解,方便理解往下的相关文章。
作者 | 自主博客 | 微信 | CSDN |
天地会珠海分舵 | | 服务号:TechGoGoGo 扫描码:
| http://blog.csdn.net/zhubaitian |