【Android Monkey源码解析二】- 参数解析
2025-10-05 / 龙之叶   

在【Android Monkey源码解析一】-系统执行 中,分析了Android系统中的Monkey命令的系统执行逻辑,通过app_process启动了Monkey的模块实现monkey.jar,入口函数为com.android.commands.monkey.Monkey,今天我们就先分析下该类的入口函数。

Monkey的实现是基于Java的,Java的程序主入口都是通过main方法来实现的,我们看下com.android.commands.monkey.Monkey类中的main方法定义,源码:https://android.googlesource.com/platform/development/+/refs/heads/android12-release/cmds/monkey/src/com/android/commands/monkey/Monkey.java#557

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 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");

Logger.err.println("args: " + Arrays.toString(args));
int resultCode = (new Monkey()).run(args);
System.exit(resultCode);
}

main方法做了三件事情:

  1. 首先,设置启动的Monkey程序进程名称为:com.android.commands.monkey,可以在ps或者top命令中通过此名称来查看Monkey进程的相关信息。
  2. 其次,打印出所有的参数,并新建一个Monkey实例,调用其run方法,此方法返回一个结果码;
  3. 最后,通过第二步返回的结果码传给System的静态方法exit,结束程序。这里,二次开发Monkey的程序,可以通过命令行启动Monkey执行等待测试完成后,根据该返回值判断Monkey执行是否有异常(非0即存在异常)。

下面,重点来看下Monkey类的run方法,源码地址:https://android.googlesource.com/platform/development/+/refs/heads/android12-release/cmds/monkey/src/com/android/commands/monkey/Monkey.java#572

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 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) {
/** 先遍历所有命令行给定的参数列表,如果Monkey命令行参数设置了--wait-dbg,则先等待调试器连接后再执行后续的操作。*/
// Super-early debugger wait
for (String s : args) {
if ("--wait-dbg".equals(s)) {
Debug.waitForDebugger();
}
}

/** 设置默认的参数值,mVerbose主要用来设置日志的输出级别,mCount代表默认的事件数,mSeed代表随机种子值(与后续要介绍的伪随机事件有联系,主要传给Random类),mThrottle主要代表事件与事件之间的间隔值。*/
// Default values for some command-line options
mVerbose = 0;
mCount = 1000;
mSeed = 0;
mThrottle = 0;

/** 打印出所有参数。*/
// prepare for command-line processing
mArgs = args;
for (String a: args) {
Logger.err.println(" arg: \"" + a + "\"");
}
mNextArg = 0;

/** 设置默认的各类事件因子,此因子主要跟后续各类事件的发生比例有关系,后续会具体介绍。*/
// set a positive value, indicating none of the factors is provided yet
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
mFactors[i] = 1.0f;
}

/** 处理输出的参数列表,设置对应的值到变量中。如果解析命令行参数有异常的话,程序退出。*/
if (!processOptions()) {
return -1;
}

/** 主要是设置测试目标应用的包名信息,检查合法性。如果存在包信息异常,程序退出。*/
if (!loadPackageLists()) {
return -1;
}

下面,分别介绍processOptions和loadPackageLists的流程。

processOptions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/**
* Process the command-line options
*
* @return Returns true if options were parsed with no apparent errors.
*/
private boolean processOptions() {
// quick (throwaway) check for unadorned command
// 如果没有设置任何参数,则显示Monkey的参数说明,程序退出
if (mArgs.length < 1) {
showUsage();
return false;
}
try {
String opt;
// 用Set集合来保存所有通过-p指定的目标测试应用,如果有重复的,Set可以去重
Set<String> validPackages = new HashSet<>();
// 循环调用nextOption取下一个参数,并判断每一个参数的名称,通过下面的if语句进行判断
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中
MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
} catch (RuntimeException ex) {
Logger.err.println("** Error: " + ex.toString());
showUsage();
return false;
}
// If a server port hasn't been specified, we need to specify
// a count
if (mServerPort == -1) {
// 最后,如果没有指定mServerPort ,则解析最后的事件总数值且必须是整数
String countStr = nextArg();
if (countStr == null) {
Logger.err.println("** Error: Count not specified");
showUsage();
return false;
}
try {
mCount = Integer.parseInt(countStr);
} catch (NumberFormatException e) {
Logger.err.println("** Error: Count is not a number: \"" + countStr + "\"");
showUsage();
return false;
}
}
return true;
}

nextOption:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
* Return the next command line option. This has a number of special cases
* which closely, but not exactly, follow the POSIX command line options
* patterns:
*
* <pre>
* -- means to stop processing additional options
* -z means option z
* -z ARGS means option z with (non-optional) arguments ARGS
* -zARGS means option z with (optional) arguments ARGS
* --zz means option zz
* --zz ARGS means option zz with (non-optional) arguments ARGS
* </pre>
*
* Note that you cannot combine single letter options; -abc != -a -b -c
*
* @return Returns the option string, or null if there are no more options.
*/
private String nextOption() {
// 如果参数当前已经所有参数解析完,直接返回null
if (mNextArg >= mArgs.length) {
return null;
}

// 通过mNextArg来不断迭代所有的参数值,默认值为0
String arg = mArgs[mNextArg];
// 如果命令不是以-开头,则直接返回null。这里需要注意,在上面的processOptions中,
// 如果解析的参数带-或者--的时候,会继续获取参数名称对应参数值。
if (!arg.startsWith("-")) {
return null;
}
// mNextArg标记值自加1
mNextArg++;
// 异常判断,如果参数等于--,没有具体的参数名,直接返回。
if (arg.equals("--")) {
return null;
}
// 这里是解析这种情况的参数,-XXX。这里有两种情况,如果给定的参数就是-X,那么进入else,直接返回-X;
// 如果参数是-XXX,则代表参数名称为-X,其值为XX,也就是mCurArgData
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;
Logger.err.println("arg=\"" + arg + "\" mCurArgData=\"" + mCurArgData + "\" mNextArg="
+ mNextArg + " argwas=\"" + mArgs[mNextArg-1] + "\"" + " nextarg=\"" +
mArgs[mNextArg] + "\"");
return arg;
}

/**
* Return the next data associated with the current option.
*
* @return Returns the data string, or null of there are no more arguments.
*/
private String nextOptionData() {
// 首先,判断是否mCurArgData非空,非空即返回
if (mCurArgData != null) {
return mCurArgData;
}
// 其次,判断是否是最后一个参数,如果是,返回空
if (mNextArg >= mArgs.length) {
return null;
}
// 最后,取下一个参数集合值,mNextArg标志位自加1
String data = mArgs[mNextArg];
Logger.err.println("data=\"" + data + "\"");
mNextArg++;
return data;
}

loadPackageLists:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* Load package denylist or allowlist (if specified).
*
* @return Returns false if any error occurs.
*/
private boolean loadPackageLists() {
// Monkey是可以设置黑白名单测试应用的,但是黑白名单只能设置一个;如果两个都设置,则抛出异常
// 这里需要注意的是,白名单的设置包括通过文件的方式和通过命令行参数的形式指定(-p)
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;
}
// 如果通过文件设置了白名单,则加载文件里面的所有应用列表;如果读写有异常,则直接退出,返回false
Set<String> validPackages = new HashSet<>();
if ((mPkgWhitelistFile != null)
&& (!loadPackageListFromFile(mPkgWhitelistFile, validPackages))) {
return false;
}
MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
// 如果通过文件设置了黑名单,同样地,加载文件里面的所有应用列表;如果读写有异常,则直接退出,返回false
Set<String> invalidPackages = new HashSet<>();
if ((mPkgBlacklistFile != null)
&& (!loadPackageListFromFile(mPkgBlacklistFile, invalidPackages))) {
return false;
}
MonkeyUtils.getPackageFilter().addInvalidPackages(invalidPackages);
return true;
}

/**
* Load a list of package names from a file.
*
* @param fileName The file name, with package names separated by new line.
* @param list The destination list.
* @return Returns false if any error occurs.
*/
// 加载文件中的应用列表,每一行代表一个应用
private static boolean loadPackageListFromFile(String fileName, Set<String> list) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(fileName));
String s;
while ((s = reader.readLine()) != null) {
s = s.trim();
// 忽略注释以及空字符
if ((s.length() > 0) && (!s.startsWith("#"))) {
list.add(s);
}
}
} catch (IOException ioe) {
Logger.err.println("" + ioe);
return false;
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ioe) {
Logger.err.println("" + ioe);
}
}
}
return true;
}

接下来,我们先看下MonkeyUtils的解析实现。

本文链接:
http://longzhiye.top/2025/10/05/2025-10-05/