monkey源码位置:development/cmds/monkey/cmds/monkey/src/com/android/commands/monkey/
adb shell monkey时是执行了位于/system/bin/下的monkey脚本:
# Script to start "monkey" on the device, which has a very rudimentary
# shell.
#
base=/system #将base设为system路径
export CLASSPATH=$base/framework/monkey.jar #将monkey.jar路径设置为拉萨市path环境变量
trap "" HUP
exec app_process $base/bin com.android.commands.monkey.Monkey $* #通过app_process启动monkey
一.从monkey.java入手,源代码:
https://github.com/aosp-mirror/platform_development/blob/master/cmds/monkey/src/com/android/commands/monkey/Monkey.java
二.monkey命令入口
monkey.java::main()如下:
public static void main(String[] args) {
// Set the process name showing in "ps" or "top"
Process.setArgV0("com.android.commands.monkey");
Logger.err.println("args: " + Arrays.toString(args));
int resultCode = (new Monkey()).run(args);
System.exit(resultCode);
}
……
1.将进程名"com.android.commands.monkey"加入进程显示列表,以便通过ps
或者top
查看
2.log打印
3.通过(new Monkey()).run()运行monkey,处理参数
三.run()
monkey.java::run()第一部分源码——传参处理:
/**
* Run the command!
*
* @param args The command-line arguments
* @return Returns a posix-style result code. 0 for no error.
*/
private int run(String[] args) {
// 如果参数带有“--wait-debug”进入debug状态
for (String s : args) {
if ("--wait-dbg".equals(s)) {
Debug.waitForDebugger();
}
}
// 为部分命令行参数设置默认值
mVerbose = 0;
mCount = 1000;
mSeed = 0;
mThrottle = 0;
// 将参数字符串赋值给mArgs
mArgs = args;
for (String a: args) {
Logger.err.println(" arg: \"" + a + "\"");
}
mNextArg = 0;
// 对mFactors[]数组赋初值,这些元素将用于存储“--pct-xxxx”参数
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
mFactors[i] = 1.0f;
}
1.很容易就发现run(args)是用来处理输入参数的
2.为了了解FACTORZ_COUNT这个参数,我们先去monkeySourceRandom.java看一下相关源码:
public static final int FACTORZ_COUNT = 12; // should be last+1
FACTORZ_COUNT应该为最后一个值加1,那么前面都是些什么值呢?前面定义了12个事件,即为monkey参数“–pct-xxxx”触发的相应事件,且这些参数都是final常量,不能修改其值(0一定是FACTOR_TOUCH),源码如下:
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;
……
public MonkeySourceRandom(Random random, List MainApps,
long throttle, boolean randomizeThrottle, boolean permissionTargetSystem) {
// 各事件初始百分占比
mFactors[FACTOR_TOUCH] = 15.0f;
mFactors[FACTOR_MOTION] = 10.0f;
mFactors[FACTOR_TRACKBALL] = 15.0f;
mFactors[FACTOR_ROTATION] = 0.0f;
mFactors[FACTOR_NAV] = 25.0f;
mFactors[FACTOR_MAJORNAV] = 15.0f;
mFactors[FACTOR_SYSOPS] = 2.0f;
mFactors[FACTOR_APPSWITCH] = 2.0f;
mFactors[FACTOR_FLIP] = 1.0f;
// disbale permission by default
mFactors[FACTOR_PERMISSION] = 0.0f;
mFactors[FACTOR_ANYTHING] = 13.0f;
mFactors[FACTOR_PINCHZOOM] = 2.0f;
上一篇有提到说各个事件有初始占比,在构造函数中的源码如上所示,同时monkey可以通过设置参数改变事件的占用百分比,但是总百分比不能超过100%。如果保持默认比例,运行结果应该如下图:
3.monkeySourceRandom.java实现了monkeyEventSource中提供的接口public class MonkeySourceRandom implements MonkeyEventSource
,monkeyEventSource接口源码如下:
public interface MonkeyEventSource {
public MonkeyEvent getNextEvent(); //返回下一个monkey event
public void setVerbose(int verbose); //设置log等级
public boolean validate(); //验证是否有效
}
monkey.java::run()第二部分源码——参数分析:
if (!processOptions()) {
return -1;
}
if (!loadPackageLists()) {
return -1;
}
4.processOptions()是一个重要的方法,用于解析命令行传入的参数,然后将传入的参数结合默认的参数进行校验和匹配:
private boolean processOptions() {
//如果参数长度小于1,则显示help usage
if (mArgs.length < 1) {
showUsage();
return false;
}
try {
String opt;
Set 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("--match-description")) {
mMatchDescription = nextOptionData();
} else if (opt.equals("--pct-touch")) {
int i = MonkeySourceRandom.FACTOR_TOUCH;
mFactors[i] = -nextOptionLong("touch events percentage");
} else if (opt.equals("--pct-motion")) {
int i = MonkeySourceRandom.FACTOR_MOTION;
mFactors[i] = -nextOptionLong("motion events percentage");
} else if (opt.equals("--pct-trackball")) {
int i = MonkeySourceRandom.FACTOR_TRACKBALL;
mFactors[i] = -nextOptionLong("trackball events percentage");
} else if (opt.equals("--pct-rotation")) {
int i = MonkeySourceRandom.FACTOR_ROTATION;
mFactors[i] = -nextOptionLong("screen rotation events percentage");
} else if (opt.equals("--pct-syskeys")) {
int i = MonkeySourceRandom.FACTOR_SYSOPS;
mFactors[i] = -nextOptionLong("system (key) operations percentage");
} else if (opt.equals("--pct-nav")) {
int i = MonkeySourceRandom.FACTOR_NAV;
mFactors[i] = -nextOptionLong("nav events percentage");
} else if (opt.equals("--pct-majornav")) {
int i = MonkeySourceRandom.FACTOR_MAJORNAV;
mFactors[i] = -nextOptionLong("major nav events percentage");
} else if (opt.equals("--pct-appswitch")) {
int i = MonkeySourceRandom.FACTOR_APPSWITCH;
mFactors[i] = -nextOptionLong("app switch events percentage");
} else if (opt.equals("--pct-flip")) {
int i = MonkeySourceRandom.FACTOR_FLIP;
mFactors[i] = -nextOptionLong("keyboard flip percentage");
} else if (opt.equals("--pct-anyevent")) {
int i = MonkeySourceRandom.FACTOR_ANYTHING;
mFactors[i] = -nextOptionLong("any events percentage");
} else if (opt.equals("--pct-pinchzoom")) {
int i = MonkeySourceRandom.FACTOR_PINCHZOOM;
mFactors[i] = -nextOptionLong("pinch zoom events percentage");
} else if (opt.equals("--pct-permission")) {
int i = MonkeySourceRandom.FACTOR_PERMISSION;
mFactors[i] = -nextOptionLong("runtime permission toggle events percentage");
} else if (opt.equals("--pkg-blacklist-file")) {
mPkgBlacklistFile = nextOptionData();
} else if (opt.equals("--pkg-whitelist-file")) {
mPkgWhitelistFile = nextOptionData();
} else if (opt.equals("--throttle")) {
mThrottle = nextOptionLong("delay (in milliseconds) to wait between events");
} else if (opt.equals("--randomize-throttle")) {
mRandomizeThrottle = true;
} else if (opt.equals("--wait-dbg")) {
// do nothing - it's caught at the very start of run()
} else if (opt.equals("--dbg-no-events")) {
mSendNoEvents = true;
} else if (opt.equals("--port")) {
mServerPort = (int) nextOptionLong("Server port to listen on for commands");
} else if (opt.equals("--setup")) {
mSetupFileName = nextOptionData();
} else if (opt.equals("-f")) {
mScriptFileNames.add(nextOptionData());
} else if (opt.equals("--profile-wait")) {
mProfileWaitTime = nextOptionLong("Profile delay" +
" (in milliseconds) to wait between user action");
} else if (opt.equals("--device-sleep-time")) {
mDeviceSleepTime = nextOptionLong("Device sleep time" +
"(in milliseconds)");
} else if (opt.equals("--randomize-script")) {
mRandomizeScript = true;
} else if (opt.equals("--script-log")) {
mScriptLog = true;
} else if (opt.equals("--bugreport")) {
mRequestBugreport = true;
} else if (opt.equals("--periodic-bugreport")){
mGetPeriodicBugreport = true;
mBugreportFrequency = nextOptionLong("Number of iterations");
} else if (opt.equals("--permission-target-system")){
mPermissionTargetSystem = true;
} else if (opt.equals("-h")) {
showUsage();
return false;
} else {
Logger.err.println("** Error: Unknown option: " + opt);
showUsage();
return false;
}
}
MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
} catch (RuntimeException ex) {
Logger.err.println("** Error: " + ex.toString());
showUsage();
return false;
}
1)nextOption()
private String nextOption() {
if (mNextArg >= mArgs.length) { //如果参数序号大于等于存储参数的数组的总长度(数组是从0开始的,0~length-1)
return null;
}
String arg = mArgs[mNextArg]; //否则就获取序号为mNextArg的参数
if (!arg.startsWith("-")) { //如果参数不是以“-”开头,返回null
return null;
}
mNextArg++;
if (arg.equals("--")) { //如果参数只有“--”,返回null
return null;
}
if (arg.length() > 1 && arg.charAt(1) != '-') { //如果参数长度大于1,且只有一个“-”:-z 或者 -z args
if (arg.length() > 2) { //如果参数长度大于2,从第3位开始是参数后面跟的args并赋值给mCurArgData,返回“-参数”
mCurArgData = arg.substring(2);
return arg.substring(0, 2);
} else { //否则该参数后面没有args,直接返回
mCurArgData = null;
return arg;
}
}
mCurArgData = null;
Logger.err.println("arg=\"" + arg + "\" mCurArgData=\"" + mCurArgData + "\" mNextArg="
+ mNextArg + " argwas=\"" + mArgs[mNextArg-1] + "\"" + " nextarg=\"" +
mArgs[mNextArg] + "\"");
return arg;
}
如果参数有“–”或者不含数值args直接返回该参数,如果只有“-”且含数值args只返回该参数的前2位,然后将args交给nextOptionLong()处理。
2)nextOptionLong()
private long nextOptionLong(final String opt) {
long result;
try {
result = Long.parseLong(nextOptionData()); //还是调用nextOptionData()
} catch (NumberFormatException e) {
Logger.err.println("** Error: " + opt + " is not a number");
throw e;
}
return result;
}
args的处理又再交给nextOptionData()去完成。
会调用nextOptionLong()的参数:
3)nextOptionData()
private String nextOptionData() { //返回参数,如果参数有带args也返回
if (mCurArgData != null) {
return mCurArgData;
}
if (mNextArg >= mArgs.length) {
return null;
}
String data = mArgs[mNextArg];
Logger.err.println("data=\"" + data + "\"");
mNextArg++;
return data;
}
会调用nextOptionData()的参数:
4)会返回true的参数:
5)比较特殊的几个参数:
5.loadPackageLists()
//导入package列表,白名单和黑名单
private boolean loadPackageLists() {
//检查白名单是不是空的
if (((mPkgWhitelistFile != null) || (MonkeyUtils.getPackageFilter().hasValidPackages()))
&& (mPkgBlacklistFile != null)) {
Logger.err.println("** Error: you can not specify a package blacklist "
+ "together with a whitelist or individual packages (via -p).");
return false;
}
Set validPackages = new HashSet<>();
//检查黑名单是不是空的
if ((mPkgWhitelistFile != null)
&& (!loadPackageListFromFile(mPkgWhitelistFile, validPackages))) {
return false;
}
MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
Set invalidPackages = new HashSet<>();
if ((mPkgBlacklistFile != null)
&& (!loadPackageListFromFile(mPkgBlacklistFile, invalidPackages))) {
return false;
}
MonkeyUtils.getPackageFilter().addInvalidPackages(invalidPackages);
return true;
}
这部分我没有太深究,应该就是加载名单为true之后才能进行之后的操作,否则会有相应的错误提示之类的。。。