monkey源码分析
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
一.从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<ComponentName> 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<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("--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()的参数:
- “-s”:随机数seed,赋值给mSeed
- “–throttle”:输入延迟,赋值给mThrottle
- “–port”:tcp端口,赋值给mServerPort
- “–device-sleep-time”:设备空闲时间,赋值给mDevicesSleepTime
- “–periodic-bugreport”:bugreport频率,赋值给mBugreportFrequency
- “–pct-touch”:对应monkeySourceRandom.FACTOR_TOUCH,赋值给mFactors[i]
- “–pct-motion”:对应monkeySourceRandom.FACTOR_MOTION,赋值给mFactors[i]
- “–pct-trackball”:对应monkeySourceRandom.FACTOR_TRACKBALL,赋值给mFactors[i]
- “–pct-rotation”:对应monkeySourceRandom.FACTOR_ROTATION,赋值给mFactors[i]
- “–pct-syskeys”:对应monkeySourceRandom.FACTOR_SYSOPS,赋值给mFactors[i]
- “–pct-nav”:对应monkeySourceRandom.FACTOR_NAV,赋值给mFactors[i]
- “–pct-majornav”:对应monkeySourceRandom.FACTOR_MAJORNAV,赋值给mFactors[i]
- “–pct-appswitch”:对应monkeySourceRandom.FACTOR_APPSWITCH,赋值给mFactors[i]
- “–pct-flip”:对应monkeySourceRandom.FACTOR_FLIP,赋值给mFactors[i]
- “–pct-anyevent”:对应monkeySourceRandom.FACTOR_ANYTHING,赋值给mFactors[i]
- “–pct-pinchzoom”:对应monkeySourceRandom.FACTOR_PINCHZOOM,赋值给mFactors[i]
- “–pct-permission”:对应monkeySourceRandom.FACTOR_PERMISSION,赋值给mFactors[i]
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()的参数:
- “-p”:添加允许的包名,添加到mValidPackages
- “-c”:添加允许的类名,添加到mmainCategories
- “–pkg-blacklist-file”:列入黑名单的包,添加到mPkgBlacklistfile
- “–pkg-whitelist-file”:列入白名单的包,添加到mPkgWhitelistfile
- “–setup”:setup脚本名,赋值给mSetupfileName
- “-f”:monkey脚本名,赋值给mScriptfileNames
4)会返回true的参数:
- “–ignore-crashes”:运行时忽略所有崩溃,赋值给mIgnoreCrashes
- “–ignore-timeout”:运行时忽略相应超时,赋值给mIgnoreTimeouts
- “–ignore-security-exceptions”:运行时忽略安全异常,赋值给mIgnoreSecurityExceptions
- “–monitor-native-crashes”:运行时若出现native异常则监控,且将错误报告存储在/data/tombstones/下,赋值给mMonitorNativeCrashes
- “–ignore-native-crashes”:运行时忽略所有系统级native异常,赋值给mIgnoreNativeCrashes
- “–kill-process-after-error”:运行时出现error则杀掉进程,赋值给mKillProcessAfterError
- “–hprof”:生成hprof报告,赋值给mGenerateHprof
- “–randomize-throttle”:插入随机输入延迟,赋值给mRandomizeThrottle
- “–dbg-no-events”:不发送任何时间只作为长延时观察用户操作,赋值给mSendNoEvents
- “–randomize-script”:随机运行monkey脚本,赋值给mRandomizeScript
- “–script-log”:记录monkey脚本日志
- “–bugreport”:运行崩溃时捕获bugreport,赋值给mRequestBugreport
- “–periodic-bugreport”:根据bugreport频率来捕获bugreport,赋值给mGetPeriodicBugreport
- “–permission-target-system”:赋值给mPermissionTargetSystem
5)比较特殊的几个参数:
- “-v”:增加log日志级别,每写一个-v,mVerbose+=1(初始值为0)
- “-h”/错误参数:显示help usage
- “–wait-dbg”:什么都不做,在run()方法一开始就会判断是否有这个参数是否要进入debug模式
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<String> validPackages = new HashSet<>();
//检查黑名单是不是空的
if ((mPkgWhitelistFile != null)
&& (!loadPackageListFromFile(mPkgWhitelistFile, validPackages))) {
return false;
}
MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
Set<String> invalidPackages = new HashSet<>();
if ((mPkgBlacklistFile != null)
&& (!loadPackageListFromFile(mPkgBlacklistFile, invalidPackages))) {
return false;
}
MonkeyUtils.getPackageFilter().addInvalidPackages(invalidPackages);
return true;
}
这部分我没有太深究,应该就是加载名单为true之后才能进行之后的操作,否则会有相应的错误提示之类的。。。