前面一遍blog Monkey源码分析讲到Monkey的代码结构和代码执行流程,相信通过介绍大家应该对monkey的运行原理和核心逻辑有了很深刻的了解。我们做的这一切都是为了更好的了解monkey的内部逻辑进行二次开发。
为什么要二次开发前面的文章也大概说了,它毕竟是一款为稳定性测试而准备的小工具,所以存在很多局限性:
本节重点介绍的就是如何通过Monkey源码改造的方法来解决上述问题,以更好地提升Monkey的使用效果。
1.保持随机
2.尽可能让其他控件有相同机会
3.尽可能操作有意义的控件
4.尽可能覆盖到每个Activity
monkey.jar的源码位于Android源码的
\development\cmds\monkey\src\com\android\commands\monkey 目录下,如图所示
Monkey重编译的方法有两种:
因为在Windows环境下编译更为常见,所以这里会重点介绍第二种方法。
在Linux环境下,下载要测试Android系统版本对应的全部源代码,进入源码目录。
Windows环境下的编译要稍微复杂一点。
其中android.jar可以从Android Sdk中platforms\android-X\目录下获取;framework.jar可以通过以下两种方式获取。
(1)(推荐)从在Linux环境下Android源码根目录执行make update-api编译生成,如截图中的classes_dex2jar.jar文件就是通过Android源码编译生成的。
(2)直接从Android手机上/system/framework目录下获取已经编译好的framework.jar文件,把这个framework.jar解压,取出其dex,然后把它的dex通过dex2jar工具转换为jar包,导入工程。添加android和framework的jar包后,还需要将framework的jar包顺序调整到顶部,如图所示:
编译生成jar包。选择Monkey项目,单击右键→单击Export→选择输出的Jar包类型为“JAR file”类,单击“Next”按钮;选择对应的构建工程,填写jar包输出路径,单击“Next”按钮;进入打包选项页面,这里用默认选项即可,直接单击“Next”按钮;
选择工程中main函数所在的类,单击“Finish”按钮;编译完成后,在指定目录下就会生成对应的Monkey.jar包了。
转换Monkey.jar包。Eclipse编译出来的jar包是不能直接放到Android手机上运行的,在Android上无法像Java中那样方便地动态加载jar。原因是:Android的虚拟机(Dalvik VM)是不能识别Java打出的jar的byte code的,这里需要通过Android sdk中的dx工具来优化转换成Dalvik byte code才行。
将打包好的jar复制到SDK安装目录android-sdk-windows\platform-tools下,打开命令行进入platform-tools目录,执行命令:
dx –dex –output=<生成的目标文件> <要转换的文件>
要运行重编译后的Monkey.jar有以下两个前提条件。
·手机拥有root权限。
·手机Android版本与Monkey.jar包的Android版本一致。
(由于不同版本的Android系统API不同,因此不同版本的Monkey包也是不能通用的。例如:Android 4.2版本的Monkey只能在Android 4.2的系统上运行。)
步骤1:创建启动shell脚本。
在本地新建一个用于启动Monkey的shell脚本,输入以下命令,并保存成Monkey。这个文件是用来启动和执行Monkey.jar的,如下面的代码所示。
# Script to start "monkeytest"on the device, which has a very rudimentary
# shell.
#这里要填写编译后生成的jar文件名称
export CLASSPATH=/data/ Monkey.jar
#这里要填写jar文件中的入口函数所在类
exec app_process /data com.android.debug.monkey.Monkey $*
步骤2:上传脚本和jar包到手机。
将步骤1创建的Monkey脚本和Monkey.jar包上传到手机的/data/loal目录(可自己定义,与shell脚本中的目录一致即可),并将Monkey文件修改成可执行权限,如下面代码所示。
adb push Monkey.jar /data
adb push monkey /data
adb shell chmod777 /data/monkey
个别手机上执行chmod命令时会报Segmentation Fault错误,这时可以先adb shell进入,通过sw root命令切换到root下,再执行chrnod 777/data/monkey即可。
步骤3:运行monkey。
通过命令行窗口,输入 adb shell./data/local/monkey 命令启动Monkey.jar包即可运行Monkey。
代码目录如下:
掌握重编译Monkey的方法后,接下来要开始进行Monkey源码改造了。第一个改造就是截图改造。Monkey使用过程中最大的难题就是如何获取异常出现的场景。虽然Monkey在执行过程中提供了日志来记录事件执行顺序,但是光靠日志来定位异常出现的场景并复现它是非常困难的。当Monkey执行过程中出现异常时,若可以对应进行截图并记录异常出现前执行的操作,就可以清晰地知道异常出现的场景,也便于定位和解决问题。
具体改造方法如下:
测试期望实现的是在每个事件执行过程中增加截图并在图片上画出事件轨迹。这里以屏幕触摸操作为例,首先找到触摸事件所在的文件MonkeyMotionEvent.java,找到负责执行该事件的injectMotionEvent方法。代码清单如下。
public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
MotionEvent me = getEvent();
if ((verbose > 0 && !mIntermediateNote) || verbose > 1) {
StringBuilder msg = new StringBuilder(":Sending ");
msg.append(getTypeLabel()).append(" (");
switch (me.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
msg.append("ACTION_DOWN");
//截图
ImageUtils.takeScreenshot();
//获取当前点击坐标,存到队列中
ImageUtils.addPoint(me.getX(),me.getY());
break;
case MotionEvent.ACTION_UP:
msg.append("ACTION_UP");
//获取当前点击坐标,存到队列中
ImageUtils.addPoint(me.getX(),me.getY());
//把队列中的点击坐标画到图片上
Bitmap bc=ImageUtils.drawPoint(ImageUtils.scaleBitmap());
//bc=ImageUtils.lessenBitmap(bc,0.6f);//等比压缩
//bc=ImageUtils.zoomBitmap(bc,400);//等高压缩
ImageUtils.saveBitmap(bc);//保存图片
//清空队列
ImageUtils.removePointList();
break;
case MotionEvent.ACTION_MOVE:
msg.append("ACTION_MOVE");
//获取当前点击坐标,存到队列中
ImageUtils.addPoint(me.getX(),me.getY());
break;
case MotionEvent.ACTION_CANCEL:
msg.append("ACTION_CANCEL");
break;
case MotionEvent.ACTION_POINTER_DOWN:
msg.append("ACTION_POINTER_DOWN ").append(me.getPointerId(me.getActionIndex()));
break;
case MotionEvent.ACTION_POINTER_UP:
msg.append("ACTION_POINTER_UP ").append(me.getPointerId(me.getActionIndex()));
break;
default:
msg.append(me.getAction());
break;
}
msg.append("):");
int pointerCount = me.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
msg.append(" ").append(me.getPointerId(i));
msg.append(":(").append(me.getX(i)).append(",").append(me.getY(i)).append(")");
}
System.out.println(msg.toString());
}
try {
if (!InputManager.getInstance().injectInputEvent(me,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)) {
return MonkeyEvent.INJECT_FAIL;
}
} finally {
me.recycle();
}
return MonkeyEvent.INJECT_SUCCESS;
}
参照上面的修改思路,将Monkey的其他方法也进行类似的修改。这样,Monkey每执行一个操作,系统就会自动对其进行截图描点了。
我们知道大部分的应用程序是需要联网的,假如Monkey在执行过程中Wi-Fi断开了怎么办?由于Monkey执行的是随机事件流,过程中的操作无法控制,用户很容易误点到工具栏而导致Wi-Fi断开。对于需要联网的应用,当Wi-Fi断开后,很多页面都会无法打开,此时Monkey执行的效果会相当不理想。相信这也是绝大多数用户遇到的问题,当前小节介绍的就是如何通过Monkey改造来实现Wi-Fi断开重连的功能。
首先,新增一个用于Wi-Fi监控的事件MonkeyWifiEvent。在Monkey中新增一类事件有以下两个步骤。
publicabstractclassMonkeyEvent {
protectedinteventType;
publicstaticfinalintEVENT_TYPE_KEY = 0;
publicstaticfinalintEVENT_TYPE_TOUCH = 1;
publicstaticfinalintEVENT_TYPE_TRACKBALL = 2;
publicstaticfinalintEVENT_TYPE_ROTATION = 3; // Screen rotation
publicstaticfinalintEVENT_TYPE_ACTIVITY = 4;
publicstaticfinalintEVENT_TYPE_FLIP = 5;
publicstaticfinalintEVENT_TYPE_THROTTLE = 6;
publicstaticfinalintEVENT_TYPE_NOOP = 7;
#新增一个Wi-Fi监控的事件类型
publicstaticfinalintEVENT_TYPE_WifiCheck = 9;
…
publicclassMonkeyWifiEvent extendsMonkeyEvent{
//初始方法
publicMonkeyWifiEvent() {
super(MonkeyEvent.EVENT_TYPE_WifiCheck);
}
//调用CheckWifiConnection()方法检查Wi-Fi连接
publicintinjectEvent(IWindowManager iwm, IActivityManager iam,intverbose){
System.out.println("Check Wifi Conection.");
wifiManager.CheckWifiConnection();
returnMonkeyEvent.INJECT_SUCCESS;
}
从上面的代码可以看到,该事件是通过调用CheckWifiConnection()方法来检查Wi-Fi连接并自动重连的。CheckWifiConnection()方法的实现很简单,首先初始化一个WifiManager的对象,调用其getWifiEnabledState方法,检查当前Wi-Fi是否连接,当判断为Wi-Fi无连接时,调用setWifiEnabled方法打开Wi-Fi。等待Wi-Fi打开后,通过getConfiguredNetworks方法获取Wi-Fi列表,并遍历列表查找需要连接的Wi-Fi的SSID。查找到后,连接到对应的Wi-Fi上。具体实现如代码如下:
publicstaticvoidCheckWifiConnection(){
IWifiManager im=IWifiManager.Stub.asInterface(ServiceManager
.getService("wifi"));
try{
intstate=im.getWifiEnabledState();
System.out.println(state);
WifiInfo wi=im.getConnectionInfo();
if(state!=3){
//打开Wi-Fi
System.out.println("Wifi not conect, connecting wifi.");
im.setWifiEnabled(true);
//等待Wi-Fi打开,然后连接freewifi
for(inti=0;i<90;i++){
if(im.getWifiEnabledState()==3){
//连接freewifi
List t=im.getConfiguredNetworks();
if(t!=null){
for(int j=0;j
if(t.get(j).SSID.indexOf("Tencent-FreeWiFi")!=-1){
intnetworkid=t.get(j).networkId;
im.enableNetwork(networkid, true);
Thread.sleep(7000);
}
break;
}
}
break;
}else{
hread.sleep(2000);
}
} catch(RemoteException e) {
e.printStackTrace();
} catch(InterruptedException e) {
e.printStackTrace();
}catch(SecurityException e) {
e.printStackTrace();
}
}
前面说的需求是实现定时监控,所以需要在Monkey.java中的runMonkeyCycles()下每隔1000个事件就插入一个Wi-Fi监控事件,实现如代码清单如下:
privateintrunMonkeyCycles() {
inteventCounter = 0;
intcycleCounter = 0;
booleanshouldReportAnrTraces = false;
booleanshouldReportDumpsysMemInfo = false;
booleanshouldAbort = false;
booleansystemCrashed = false;
// TO DO : The count should apply to each of the script file.
while(!systemCrashed && cycleCounter < mCount) {
...
//添加Wi-Fi检查的事件—sharon
if(cycleCounter%1000==0){
try{
addWifiEvent();
} catch(RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
System.out.println("Events injected: "+ eventCounter);
returneventCounter;
}
这样,当Monkey每执行完1000个事件后,就会去检测一下Wi-Fi的连接状态,当发现Wi-Fi断开就会自动重连。重新编译一下Monkey,然后看一下效果,当Monkey检查到Wi-Fi断开会自动重连。
Monkey是Android测试中常用的一个稳定性测试工具,掌握Monkey工具本身的使用方法是非常简单的。但是真正能深入了解Monkey的代码实现逻辑,并且具备优化Monkey能力,还是需要一定难度。通过本blog,大家应该学习到Monkey的一些基本知识和基本使用方法,还可以通过对Monkey代码逻辑和扩展实例的学习,有所启发,掌握新的自动化测试的方案。
上面我说分析的只是截图功能和wifi重连功能,还有其他的功能,大家都可以去尝试开发,由于篇幅太长,我就不一一阐述啦,有问题随时留言,或加我微信一起探讨。。