1, 概述
Monkey模拟用户的按键输入,手势输入等各种事件,看设备多长时间会出异常,是一种简单粗暴的压力测试。
同其他大多数adb命令一样,Monkey命令也是shell脚本,system/bin 目录下有一个monkey文件脚本。主要内容如下
base=/system
export CLASSPATH=$base/framework/monkey.jar
trap "" HUP
exec app_process $base/bin com.android.commands.monkey.Monkey $*
首先会导入monkey jar包, 然后通过app_process来指定命令工作路径为'/system/bin/'
以启动指定类com.android.commands. monkey.Monkey,启动该类传入的参数就是指定的测试用例类. 最后会调用Monkey类中的main方法。
路径: development\cmds\monkey\src\com\android\commands\monkey
所有和monkey有关的代码都在该目录下,结构也很容易。
Monkey命令的使用在此就不多论述了,直接看实现原理。
2,main方法
Monkey的main方法如下,
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); //调用run方法
System.exit(resultCode);// 退出
}
run方法首先设置默认参数
mVerbose = 0;
mCount = 1000;
mSeed = 0;
mThrottle = 0;
// prepare for command-line processing
mArgs = args;//命令参数
mNextArg = 0;
为字符数组mFactors赋初值,
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
mFactors[i] = 1.0f;
}
该数组定义如下,
float[] mFactors = new float[MonkeySourceRandom.FACTORZ_COUNT];
数组大小为12
public static final int FACTORZ_COUNT = 12;
然后依次调用以下方法。
2.1 processOptions
processOptions主要解析输入命令。
首先解析命令,为对应的变量赋值
try {
String opt;
Set<String> validPackages = new HashSet<>();
while ((opt = nextOption()) != null) {
if (opt.equals("-s")) {
mSeed = nextOptionLong("Seed");
} else if (opt.equals("-p")) {
validPackages.add(nextOptionData());
} else if (opt.equals("-c")) {
mMainCategories.add(nextOptionData());
} else if (opt.equals("-v")) {
mVerbose += 1;
} else if (opt.equals("--ignore-crashes")) {
mIgnoreCrashes = true;
} else if (opt.equals("--ignore-timeouts")) {
mIgnoreTimeouts = true;
} else if (opt.equals("--ignore-security-exceptions")) {
mIgnoreSecurityExceptions = true;
} else if (opt.equals("--monitor-native-crashes")) {
mMonitorNativeCrashes = true;
} else if (opt.equals("--ignore-native-crashes")) {
mIgnoreNativeCrashes = true;
} else if (opt.equals("--kill-process-after-error")) {
mKillProcessAfterError = true;
} else if (opt.equals("--hprof")) {
mGenerateHprof = true;
} else if (opt.equals("--pct-touch")) {
int i = MonkeySourceRandom.FACTOR_TOUCH;
mFactors[i] = -nextOptionLong("touch events percentage");
}•••
其中调用的nextOption方法主要解析命令参数名
private String nextOption() {
if (mNextArg >= mArgs.length) {
return null;
}
String arg = mArgs[mNextArg];
if (!arg.startsWith("-")) {
return null;
}
mNextArg++;
if (arg.equals("--")) {
return null;
}
if (arg.length() > 1 && arg.charAt(1) != '-') {
if (arg.length() > 2) {
mCurArgData = arg.substring(2);
return arg.substring(0, 2);
} else {
mCurArgData = null;
return arg;
}
}
mCurArgData = null;
return arg;
}
nextOptionLong以及nextOptionData主要是解析命令参数名后面的参数值。
private String nextOptionData() {
if (mCurArgData != null) {
return mCurArgData;
}
if (mNextArg >= mArgs.length) {
return null;
}
String data = mArgs[mNextArg];
mNextArg++;
return data;
}
这样就可以解析出对应的参数了,而且写monkey命令时,不需要写出所有的命令参数。
待测试的apk包名都放在validPackages哈希表中,
Set<String> validPackages = new HashSet<>();
解析完之后,将该值保存在MonkeyUtils内部类PackageFilter的mValidPackages变量中。
MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
其addValidPackages方法如下,
public void addValidPackages(Set<String> validPackages) {
mValidPackages.addAll(validPackages);
}
通过解析方法知道,变量mFactors数组中对应的位置存放的是对应UI界面各种事件的百分比。
public static final int FACTOR_TOUCH = 0;
public static final int FACTOR_MOTION = 1;
public static final int FACTOR_PINCHZOOM = 2;
public static final int FACTOR_TRACKBALL = 3;
public static final int FACTOR_ROTATION = 4;
public static final int FACTOR_PERMISSION = 5;
public static final int FACTOR_NAV = 6;
public static final int FACTOR_MAJORNAV = 7;
public static final int FACTOR_SYSOPS = 8;
public static final int FACTOR_APPSWITCH = 9;
public static final int FACTOR_FLIP = 10;
public static final int FACTOR_ANYTHING = 11;
2.2 系统日志
if (!loadPackageLists()) { //导入package列表
return -1;
}
// now set up additional data in preparation for launch
if (mMainCategories.size() == 0) {
mMainCategories.add(Intent.CATEGORY_LAUNCHER);
mMainCategories.add(Intent.CATEGORY_MONKEY);
}
if (mSeed == 0) {
mSeed = System.currentTimeMillis() + System.identityHashCode(this);
}
if (mVerbose > 0) { // 添加系统日志
System.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
MonkeyUtils.getPackageFilter().dump();
if (mMainCategories.size() != 0) {
Iterator<String> it = mMainCategories.iterator();
while (it.hasNext()) {
System.out.println(":IncludeCategory: " + it.next());
}
}
}
if (!checkInternalConfiguration()) { //检查内部配置
return -2;
}
if (!getSystemInterfaces()) { // 获取系统接口
return -3;
}
getSystemInterfaces方法如下,
private boolean getSystemInterfaces() {
mAm = ActivityManagerNative.getDefault();
if (mAm == null) {
System.err.println("** Error: Unable to connect to activity manager; is the system "
+ "running?");
return false;
}
mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
if (mWm == null) {
System.err.println("** Error: Unable to connect to window manager; is the system "
+ "running?");
return false;
}
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
if (mPm == null) {
System.err.println("** Error: Unable to connect to package manager; is the system "
+ "running?");
return false;
}
try {
mAm.setActivityController(new ActivityController());
mNetworkMonitor.register(mAm);
} catch (RemoteException e) {
System.err.println("** Failed talking with activity manager!");
return false;
}
return true;
}
if (!getMainApps()) { // 获取合法的package列表
return -4;
}
三个重要服务,
通过AM调用setActivityController方法来控制整个测试生命周期。
通过WM中提供的方法可以顺利注入各种事件。
通过PM可以在测试时在多个应用之间进行切换。
mNetworkMonitor变量是MonkeyNetworkMonitor对象,主要监听网络状态的变化。
各种环境设置好之后,开始进行测试。
mRandom = new Random(mSeed);
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, mPermissionTargetSystem);
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();
}
Monkey运行模式主要有4种:
1,直接运行脚本
2,运行setup脚本/随机运行所有脚本
3,运行远程调用
4,直接随机运行
主要是构造不同的MonkeyEventSource子类,子类的构造方法在此就不分析了。
最后终于调用runMonkeyCycles方法进行测试了
mNetworkMonitor.start();
int crashedAtCycle = 0;
try {
crashedAtCycle = runMonkeyCycles();
} finally {
// Release the rotation lock if it's still held and restore the
// original orientation.
new MonkeyRotationEvent(Surface.ROTATION_0, false).injectEvent(
mWm, mAm, mVerbose);
}
mNetworkMonitor.stop();
2.3 runMonkeyCycles
runMonkeyCycles代码虽然长,最重要的代码如下,
MonkeyEvent ev = mEventSource.getNextEvent();
if (ev != null) {
int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
根据mEventSource对象,取出下一条消息,然后调用MonkeyEvent的injectEvent方法注入事件。