天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写《深入理解 MonkeyRunner》书籍“。但因为诸多原因,没有如愿。所以这里把草稿分享出来,所以错误在所难免。有需要的就参考下吧,转发的话还请保留每篇文章结尾的出处等信息。
设置好Monkey的CLASSPATH环境变量以指定”/system/framework /framework/monkey.jar“后,/system/bin/monkey这个shell脚本就会通过app_process命令指定monkey的入口类” “com.android.commands.monkey.Monkey”找到main函数开始运行。在运行之前会有一些初始化的工作需要做,其中很重要的一项就是去把用户提供的命令行选项以及参数值给解析出来。
这里只是以”monkey -port 12345”作为一个情景分析了monkey是如何进行命令行参数解析的,至于其他的参数解析原理是相同的,这里就不一一解析了,以下贴出monkey帮助命令打印出来的帮助文档,大家可以看下monkey都支持哪些命令行参数选项:
图5-4-1 monkey支持命令选项
其中 “–port”选项就是去指定monkey服务需要监听的端口,这样客户端就可以连接上来跟monkey服务进行通信了。
下面我们开始对Monkey应用的源代码进行分析,首先这里我们先进入到Monkey类的main函数:
代码5-4-1 Monkey - Main
414 /**
415 * Command-line entry point.
416 *
417 * @param args The command-line arguments
418 */
419 public static void main(String[] args) {
420 // Set the process name showing in "ps" or "top"
421 Process.setArgV0("com.android.commands.monkey");
422 int resultCode = (new Monkey()).run(args);
423 System.exit(resultCode);
424 }
重点代码422行首先会去创建一个Monkey类的实例,然后调用run成员方法。这个成员方法比较长,做了很多事情,往下会慢慢为你一一道来。其中做的事情之一就是在开始时调用processOptions方法来解析命令行参数。
代码5-4-2 Monkey - run 初始化命令
425 /**
426 * Run the command!
427 *
428 * @param args The command-line arguments
429 * @return Returns a posix-style result code. 0 for no error.
430 */
431 private int run(String[] args)
432 {
...//省略其他一些成员变量的初始化
444 this.mArgs = args;
445 this.mNextArg = 0;
...//省略其他一些成员变量的初始化
450 if (!processOptions()) {
451 return -1;
452 }
...//其他关键方法将在今后小节进行分析,这里先略过
}
代码第450行之前做的都是一些成员变量初始化的动作,初始化后就会调用450行的processOptions进行真正的命令行参数解析并设置真正的来自命令行的成员变量。注意第444行会把来自命令行的参数数组赋给Monkey实例的mArgs成员变量保存起来,然后在445行把mNextArg这个游标设置成0,这个变量是用来遍历mArgs这个命令行参数数组的。
代码5-4-3 Monkey - processOptions
632 /**
633 * Process the command-line options
634 *
635 * @return Returns true if options were parsed with no apparent errors.
636 */
637 private boolean processOptions() {
...
644 String opt;
645 while ((opt = nextOption()) != null)
646 if (opt.equals("-s")) {
647 this.mSeed = nextOptionLong("Seed");
648 }
...//省略类似处理代码
713 else if (opt.equals("--port")) {
714 this.mServerPort = ((int)nextOptionLong("Server port to listen on for commands"));
715 }
...//省略类似处理代码
742 }
...
765 return true;
766 }
代码从645行开始到742行会执行一个while循环,每循环一次都会调用nextOption方法取一个命令行参数选项进来进行解析,与之对应的是取该命令行的参数值的方法nextOptionXXX等,其中XXX根据该参数值是长整型还是字串类型而不同:
- nextOptionData方法:代表要获取的参数值是一个字串类型的值。比如”monkey -p com.android.sample.notepad”限定monkey可以操作的应用包范围中的应用包“com.android.sample.notepad”就是个字串类型
- nextOptionLong方法: 代表要获得参数值是一个数字类型的值,比如”monkey –port 12345”中的12345。
例如713-714行就是去处理MonkeyRunner启动monkey的命令”monkey -port 12345”传过来的参数”-port 12345”的关键,它判断如果参数是”–port”的话,就会去取得紧跟着这个参数选项的参数值,也就是12345,然后把它保存到Monkey的成员变量mServerPort里面以供今后使用。注意这里的nextOptionLong方法的参数“Server port to listen on for commands”其实只是调试用途而已。
最后我们看下nextOption是如何从mArgs数组中根据当前mNextArg的数组游标来来获得下一个参数选项,以及与其对应的nextOptionLong是如何取得对应的下一个参数选贤的参数值的。我们先看nextOption:
代码5-4-4 Monkey - nextOption
1117 private String nextOption() {
1118 if (mNextArg >= mArgs.length) {
1119 return null;
1120 }
1121 String arg = mArgs[mNextArg];
1122 if (!arg.startsWith("-")) {
1123 return null;
1124 }
1125 mNextArg++;
1126 if (arg.equals("--")) {
1127 return null;
1128 }
1129 if (arg.length() > 1 && arg.charAt(1) != ‘-‘) {
1130 if (arg.length() > 2) {
1131 mCurArgData = arg.substring(2);
1132 return arg.substring(0, 2);
1133 } else {
1134 mCurArgData = null;
1135 return arg;
1136 }
1137 }
1138 mCurArgData = null;
1139 return arg;
1140 }
mArgs成员变量是一个String类型的数组,从本节上面的分析可以知道命令行传进来的参数列表是被保存在里面的。这段代码比较短也比较简单。分析之前我们先对monkey支持的命令行格式进行一些描述,这样大家就很容易看明白这些代码的意思了:
- – : 代表停止往下处理选项
- -z : 代表命令行选项”-z”
- -z ARGS : 代表命令行选项”-z”且选项值是”ARGS”,比如”-s 100”的命令行参数选项是指定随即测试种子数的”-s”,值是种子数100
- -zARGS : 同上,”-s 100”也可以写成”-s100”
- –zz : 代表命令行选项“-zz”
- –zz ARGS : 代表命令行选项”-zz”且选项值是”ARGS”
知道了monkey支持的命令行格式之后,还是有必要对nextOption方法用到的几个Monkey类的成员变量做一些解析:
- mArgs: 保存的是用户传进来的命令行参数选项和值的一个数组
- mNextArg: 相当于数组的一个游标,代表当前分析到第几个参数了
- mCurArgData: 于某个参数选项对应的参数值
以下是这个方法的分析,大意就是从mArgs命令行参数数组中取得一个参数,然后经过一系列的判断是否合法,最后如果合法的话就直接返回。分析过程中大家需要注意的是,整个方法做的事情是取得参数选项,而不是取得参数值,所以不要被参数值ARGS给影响你的理解,该ARGS是由其他nextOptionXXX方法来获得的,比如下一个将要分析的去获得Long类型的参数值的方法nextOptionLong,而不是在这里。
- 1118-1120行: 已经没有额外的参数需要处理,返回null代表无效
- 1121行: 根据当前游标获得需要分析的当前参数选项
- 1122-1124行: 参数不是以”-”开始,返回null代表无效
- 1125行: 调整游标mNextArg,指向参数选项/参数值,具体是参数选项还是参数值要看该参数的编写方法是”-zARGS”的形式还是“-z ARGS”的形式。比如以”–port 12345 –wait-dbg”为例,如果当前需要分析的是”–port”,那么执行这一行后就会将游标指向”12345”这一个参数值了。在我们往下需要分析的nextOptionLong方法中就会根据这个mNextArg游标把该数值取出来,并把游标再指向下一行,也就是这里的”–wait-dbg”选项,这样在下一个netOption的调用中,1121行就能继续取出”–wait-dbg”这个当前选项进行新一轮的分析了
- 1126-1128行: 如果是”–”,根据前面参数格式的描述,代表需要停止往下处理选项,返回null代表参数无效
- 1129-1132行: 处理的是”-zARGS”这种情况,把命令行参数选项和参数值都同时解析出来了,参数值是保存在Monkey成员变量mCurArgData里面, 以便在processOption的while循环体中调用如nextOptionLong等方法时直接从Monkey的mCurArgData中获得命令行参数值
- 1134-1135行: 处理的是”-z”和”-z ARGS”的情况,把参数值设置为null,以便在processOption的while循环体中调用如nextOptionLong等方法来决定是否需要解析获得参数值还是直接从mCurArgData中获得命令行参数值。最后在1135行把参数选项”-z”返回
- 1138-1139行: 最后处理的是”-zz”和”-zz ARGS”选项,处理方法同上
还是以命令”monkey –port 12345”这个命令为例子。在通过nextOption方法获得对应的参数选项如”–port”之后,跟着需要做的就是去获得对应的“12345”这个参数选项值了。这里12345被认为是一个长整型的数值,所以调用的是nextOptionLong这个方法。
代码5-4-5 Monkey - nextOptionLong
1157 /**
1158 * Returns a long converted from the next data argument, with error handling
1159 * if not available.
1160 *
1161 * @param opt The name of the option.
1162 * @return Returns a long converted from the argument.
1163 */
1164 private long nextOptionLong(final String opt) {
1165 long result;
1166 try {
1167 result = Long.parseLong(nextOptionData());
1168 } catch (NumberFormatException e) {
1169 System.err.println("** Error: " + opt + " is not a number");
1170 throw e;
1171 }
1172 return result;
1173 }
重点在1167行,通过nextOptionData的调用获得字串类型的命令行参数值,然后通过Long的parseLong方法来将该字串转成长整型的目标值。
不知道大家在前面nextOption分析中有没有注意到一点,凡是参数选项和参数值合并在一起的情况(比如-zARGS),该方法都会在获得需要的参数选项(比如-z)的同时,把参数值(ARGS)也一并分析出来并保存在本Monkey类的成员变量mCurArgData成员变量里面;而凡是参数选项和参数值分开的情况(比如-z ARGS),该nextOption方法都只是把参数选项给分析出来,而把mCurArgData设置成null。其实下面我们要分析的nextOptionData就是要根据这两种情况来获得对应的参数选项的参数值的。
代码5-4-6 Monkey - nextOptionData
1141 /**
1142 * Return the next data associated with the current option.
1143 *
1144 * @return Returns the data string, or null of there are no more arguments.
1145 */
1146 private String nextOptionData() {
1147 if (mCurArgData != null) {
1148 return mCurArgData;
1149 }
1150 if (mNextArg >= mArgs.length) {
1151 return null;
1152 }
1153 String data = mArgs[mNextArg];
1154 mNextArg++;
1155 return data;
1156 }
该方法所做的事情主要就是如前所说的根据mCurArgData这个用来保存参数值的成员变量是否为null(也就是说在之前的nextOption获取参数选项的方法中是否已经顺便把参数值一并获取了)而分不同的方法来获得参数选项对应的参数值:
- 1147-1149行:先去看看mCurArgData这个变量在nextOption中有没有被设置成真实的参数值,也就是说在1147行检查下mCurArgData是否为null,如果不是的话代表在nextOption分析参数选项的时候已经把参数值也一并分析出来了,所以这里只需要直接返回这个值就行了。
- 1153行:如果mCurArgData为null的话代表对应参数选项的参数值还没有解析出来,那么就直接使用上面nextOption已经调整好的游标mNextArg来从命令行参数数组mArgs中取得对应的参数值,然后返回给调用者。
- 1154行:调整游标到指向下一个参数选项,以便在下一个nextOption中获取参数选项列表mArgs中的下一个选项进行分析。还是以前面的”monkey –port 12345 –wait-dbg”为例,前面的nextOption获取到了”–port”这个参数选项,而这里的nextOptionData是把对应的参数值”12345“解析出来,然后就会通过1154这一行代码把游标指向”–wait-dbg”这个选项。
- 1155行:把参数值返回给调用者。在上面的例子中就是把“12345”这个监听端口返回给本小节开始前分析的processOption这个方法,然后在把该端口保存到mServerPort这个成员变量里面,具体代码请查看前面该方法的714行代码的分析。
——— 未完待续———
作者:天地会珠海分舵
微信公众号:TechGoGoGo
微博:http://weibo.com/techgogogo
CSDN:http://blog.csdn.net/zhubaitian