MonkeyRunner源代码分析之启动
Posted yutingliuyl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MonkeyRunner源代码分析之启动相关的知识,希望对你有一定的参考价值。
在工作中由于要追求完毕目标的效率,所以很多其它是强调实战。注重招式。关注怎么去用各种框架来实现目的。可是假设一味仅仅是注重招式。缺少对原理这个内功的了解,相信自己非常难对各种框架有更深入的理解。
从几个月前開始接触ios和android的自己主动化測试。原来是本着只为了提高測试团队工作效率的心态先行作浅尝即止式的研究,然后交给測试团队去边实现边自己研究。最后由于各种原因果然是浅尝然后就止步了,而自己终于也离开了上一家公司。
换了工作这段时间抛开全部杂念和曾经的困扰专心去学习研究各个框架的使用,逐渐发现这还是非常有意思的事情。期间也会使得你没有太多的时间去胡思乱想。所以说,爱好还真的是须要培养的。换工作已经有大半个月时间了。但算来除去国庆和跑去香港參加电子展的时间,真正上班的时间可能两个星期都不到,但自己在下班和假日期间还是继续花时间去学习研究这些东西。这让我认为有那么一点像曾经还在学校的时候研究minix操作系统源代码的那个劲头,这可能应了我的兄弟Red.Lin所说的我的骨子里还是挺喜欢去作研究的。
所以这也就催生了我打算把MonkeyRunner,Robotium,Uiautomator,Appium以及今后会接触到的iOS相关的自己主动化測试框架的原理好好研究一下的想法。了解一个事物的工作原理是什么往往我们须要去深入到事物的内部看它是怎么构成的。对于我们这些框架来说。它的内部也就是它的源码的。
事实上上几天我已经開始尝试对MonkeyRunner的源代码进行过一些分析了,有兴趣的同学能够去看下本人下面的两篇文章:
但敬请注意的一点是,大家写过博客的都应该知道。写一篇文章事实上是挺耗时间的,所以我今后的分析都会尝试在一篇文章中不会把代码跟踪的太深,对涉及到的重要但不影响对文章主旨理解的会考虑另行开篇描写叙述。
1. MonkeyRunner 执行环境初始化
exec java -Xmx128M $os_opts $java_debug -Djava.ext.dirs="$frameworkdir:$swtpath" -Djava.library.path="$libdir" -Dcom.android.monkeyrunner.bindir="$progdir" -jar "$jarpath" "[email protected]"这个命令非常明显就是通过java来运行一个指定的jar包,到底是哪个jar包呢?我们往下会描写叙述。但在此之前我们先看下这个命令的‘-D‘參数是怎么回事。我们假设对java不是非常熟悉的话能够在命令行运行‘java -h‘来查看帮助:
‘-D‘參数是通过指定一个键值对来设置系统属性,而这个系统属性是保存在JVM里面的,终于我们能够通过例如以下面的演示样例代码调用取得相应的一个键的值:
/* */ private String findAdb() /* */ { /* 74 */ String mrParentLocation = System.getProperty("com.android.monkeyrunner.bindir"); /* */这里我们把这些变量都打印出来。看下都设置了哪些值以及启动的是哪个jar包:
我们能够看到monkeyrunner这个shell脚本事实上终于就是通过java运行启动了sdk里面的哪个monkeyrunner.jar这个jar包。
除此之外还设置了如图的几个系统属性,这里请注意‘com.android.monkeyrunner.bindir‘这个属性,我们今天的分析会碰到。它指定的就是monkeyrunner这个可运行shell 脚本在sdk中的绝对位置。
Usage: monkeyrunner [options] SCRIPT_FILE -s MonkeyServer IP Address. -p MonkeyServer TCP Port. -v MonkeyServer Logging level (ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, OFF)迄今我们就了解了启动monkeyrunn这个shell脚本所作的事情就是涉及了以上几个系统属性然后通过用户指定的对应參数来用java运行sdk里面的monkerunner.jar这个jar包,往下我们就须要去查看monkeyrunner的入口函数main了。
2.命令行显式和隐藏參数处理
/* */ public static void main(String[] args) { /* 179 */ MonkeyRunnerOptions options = MonkeyRunnerOptions.processOptions(args); /* */ /* 181 */ if (options == null) { /* 182 */ return; /* */ } /* */ /* */ /* 186 */ replaceAllLogFormatters(MonkeyFormatter.DEFAULT_INSTANCE, options.getLogLevel()); /* */ /* 188 */ MonkeyRunnerStarter runner = new MonkeyRunnerStarter(options); /* 189 */ int error = runner.run(); /* */ /* */ /* 192 */ System.exit(error); /* */ } /* */ }这里主要做了三件事情:
- 179行去处理用户启动monkeyrunner的时候输入的命令行參数
- 188行去初始化MonkeyRunnerStarter,里面主要是初始化了ChimpChat,ChimpChat又去开启AndroidDebugBridge进程和开启DeviceMonitor设备监控线程
- 189行去把monkeyrunner执行起来,包含带脚本參数的情况和不待脚本參数直接提供jython命令行的情况
/* */ public static MonkeyRunnerOptions processOptions(String[] args) /* */ { /* 95 */ int index = 0; /* */ /* 97 */ String hostname = DEFAULT_MONKEY_SERVER_ADDRESS; /* 98 */ File scriptFile = null; /* 99 */ int port = DEFAULT_MONKEY_PORT; /* 100 */ String backend = "adb"; /* 101 */ Level logLevel = Level.SEVERE; /* */ /* 103 */ ImmutableList.Builder<File> pluginListBuilder = ImmutableList.builder(); /* 104 */ ImmutableList.Builder<String> argumentBuilder = ImmutableList.builder(); /* 105 */ while (index < args.length) { /* 106 */ String argument = args[(index++)]; /* */ /* 108 */ if ("-s".equals(argument)) { /* 109 */ if (index == args.length) { /* 110 */ printUsage("Missing Server after -s"); /* 111 */ return null; /* */ } /* 113 */ hostname = args[(index++)]; /* */ } /* 115 */ else if ("-p".equals(argument)) /* */ { /* 117 */ if (index == args.length) { /* 118 */ printUsage("Missing Server port after -p"); /* 119 */ return null; /* */ } /* 121 */ port = Integer.parseInt(args[(index++)]); /* */ } /* 123 */ else if ("-v".equals(argument)) /* */ { /* 125 */ if (index == args.length) { /* 126 */ printUsage("Missing Log Level after -v"); /* 127 */ return null; /* */ } /* */ /* 130 */ logLevel = Level.parse(args[(index++)]); /* 131 */ } else if ("-be".equals(argument)) /* */ { /* 133 */ if (index == args.length) { /* 134 */ printUsage("Missing backend name after -be"); /* 135 */ return null; /* */ } /* 137 */ backend = args[(index++)]; /* 138 */ } else if ("-plugin".equals(argument)) /* */ { /* 140 */ if (index == args.length) { /* 141 */ printUsage("Missing plugin path after -plugin"); /* 142 */ return null; /* */ } /* 144 */ File plugin = new File(args[(index++)]); /* 145 */ if (!plugin.exists()) { /* 146 */ printUsage("Plugin file doesn't exist"); /* 147 */ return null; /* */ } /* */ /* 150 */ if (!plugin.canRead()) { /* 151 */ printUsage("Can't read plugin file"); /* 152 */ return null; /* */ } /* */ /* 155 */ pluginListBuilder.add(plugin); /* 156 */ } else if (!"-u".equals(argument)) /* */ { /* 158 */ if ((argument.startsWith("-")) && (scriptFile == null)) /* */ { /* */ /* */ /* 162 */ printUsage("Unrecognized argument: " + argument + "."); /* 163 */ return null; /* */ } /* 165 */ if (scriptFile == null) /* */ { /* */ /* 168 */ scriptFile = new File(argument); /* 169 */ if (!scriptFile.exists()) { /* 170 */ printUsage("Can't open specified script file"); /* 171 */ return null; /* */ } /* 173 */ if (!scriptFile.canRead()) { /* 174 */ printUsage("Can't open specified script file"); /* 175 */ return null; /* */ } /* */ } else { /* 178 */ argumentBuilder.add(argument); /* */ } /* */ } /* */ } /* */ /* 183 */ return new MonkeyRunnerOptions(hostname, port, scriptFile, backend, logLevel, pluginListBuilder.build(), argumentBuilder.build()); /* */ } /* */ }这里首先请看97-101行的几个变量初始化,假设用户在命令行中没有指定相应的參数,那么这些默认參数就会被使用,我们且看下这些默认值各自是什么:
- hostname:相应‘-s‘參数,默认值是‘127.0.0.1‘,也就是本机。将会forward给目标设备执行的monkey,所以加上以下的转发port等同于目标机器在listen的monkey服务
- port :相应‘-p‘參数。默认值是‘12345‘
- backend :相应‘-be‘參数,默认值是‘adb‘,事实上往后看代码我们会发现它也仅仅是支持’adb‘而已。
这里须要注意的是这是一个隐藏參数。命令行的help没有显示该參数
- logLevel :相应‘-v‘參数,默认值是‘SEVERE‘,也就是说仅仅打印严重的log
- -u :咋一看以为这是一个什么特别的參数。从156-178行能够看到这个參数处理的意义是:当用户输入‘-u‘的时候不会作不论什么处理。但当用户输入的是由‘-’開始的但又不是monkeyrunner声称支持的那几个參数的时候。就会依据不同的情况给用户报错。
所以这段代码的意思事实上就是在用户输入了不支持的參数的时候依据不同的情况给用户提示而已。
- -be :backend,如前所述,仅仅支持‘adb‘
- -plugin :这里须要一个背景知识。在google官网又说明。用户能够通过遵循一定的规范去编写插件来扩展monkeyrunner的功能,比方在monkeydevice里面按下这个动作是须要通过MonkeyDevice.DOWN这个參数来传给press这种方法的,假设你认为这样子不好,你希望添加个pressDown这种方法,里面默认就是用MonkeyDevice.DOWN来驱动MonkeyDevice的press方法,而用户仅仅须要给出坐标点就能够了,那么你就能够遵循google描写叙述的规范去编写一个这方面的插件。到时使用的时候就能够通过python方式直接import进来使用了。往后有机会的话会尝试另开一篇文章编写一个样例放上来大家共同学习下插件应该怎么编写。这里如文章開始所述,就不深究下去了。仅仅须要知道插件这个概念就足够了
3. 开启ChimpChat之启动AndroidDebugBridge和DeviceMonitor
/* 188 */ MonkeyRunnerStarter runner = new MonkeyRunnerStarter(options);我们进入到该构造函数看下它到底做了什么事情:
/* */ public MonkeyRunnerStarter(MonkeyRunnerOptions options) /* */ { /* 57 */ Map<String, String> chimp_options = new TreeMap(); /* 58 */ chimp_options.put("backend", options.getBackendName()); /* 59 */ this.options = options; /* 60 */ this.chimp = ChimpChat.getInstance(chimp_options); /* 61 */ MonkeyRunner.setChimpChat(this.chimp); /* */ }仅从这种方法的几行代码我们能够看到它事实上做的事情就是去依据‘backend’来初始化ChimpChat ,然后用组合(这里要大家有面向对象的聚合和耦合的概念)的方式的方式把该ChimpChat对象保留到MonkeyRunner的静态成员变量里面,为什么说它一定是静态成员变量呢?由于第61行保存该实例调用的是MonkeyRunner这个类的方法,而不是一个实例,所以该方法肯定就是静态的。而一个静态方法里面的成员函数也必定是静态的。
大家跳进去MonkeyRunner这个类就能够看到:
/* */ private static ChimpChat chimpchat; /* */ static void setChimpChat(ChimpChat chimp) /* */ { /* 53 */ chimpchat = chimp; /* */ }好。我们返回来继续看ChimpChat是怎么启动的。首先我们看58行的optionsGetBackendName()是怎么获得backend的名字的,从上面命令行參数分析我们能够知道它默认是用‘adb’的,所以它获得的就是‘adb’,或者用户指定的其它backend(事实上这样的情况不支持,往下继续分析我们就会清楚了).
/* */ public static ChimpChat getInstance(Map<String, String> options) /* */ { /* 48 */ sAdbLocation = (String)options.get("adbLocation"); /* 49 */ sNoInitAdb = Boolean.valueOf((String)options.get("noInitAdb")).booleanValue(); /* */ /* 51 */ IChimpBackend backend = createBackendByName((String)options.get("backend")); /* 52 */ if (backend == null) { /* 53 */ return null; /* */ } /* 55 */ ChimpChat chimpchat = new ChimpChat(backend); /* 56 */ return chimpchat; /* */ }ChimpChat实例化所做的事情有两点,这就是我们这一章节的重点。
- 依据backend的名字来创建一个backend,事实上就是创建一个AndroidDebugBridge
- 调用构造函数把这个backend保存到ChimChat的成员变量
/* */ private static IChimpBackend createBackendByName(String backendName) /* */ { /* 77 */ if ("adb".equals(backendName)) { /* 78 */ return new AdbBackend(sAdbLocation, sNoInitAdb); /* */ } /* 80 */ return null; /* */ }这里注意第77行,这就是为什么我之前说backend事实上仅仅是支持‘adb’而已,起码临时的代码是这样子,假设今后google决定支持其它更新的backend,就另当别论了。
这还是有可能的,毕竟google留了这个接口
。/* */ public AdbBackend(String adbLocation, boolean noInitAdb) /* */ { /* 58 */ this.initAdb = (!noInitAdb); /* */ /* */ /* 61 */ if (adbLocation == null) { /* 62 */ adbLocation = findAdb(); /* */ } /* */ /* 65 */ if (this.initAdb) { /* 66 */ AndroidDebugBridge.init(false); /* */ } /* */ /* 69 */ this.bridge = AndroidDebugBridge.createBridge(adbLocation, true); /* */ }创建AndroidDebugBridge之前我们先要确定我们的adb程序的位置,这就是通过61行来实现的,我们进去findAdb去看下它是怎么找到我们的sdk中的adb的:
/* */ private String findAdb() /* */ { /* 74 */ String mrParentLocation = System.getProperty("com.android.monkeyrunner.bindir"); /* */ /* */ /* */ /* */ /* */ /* 80 */ if ((mrParentLocation != null) && (mrParentLocation.length() != 0)) /* */ { /* 82 */ File platformTools = new File(new File(mrParentLocation).getParent(), "platform-tools"); /* */ /* 84 */ if (platformTools.isDirectory()) { /* 85 */ return platformTools.getAbsolutePath() + File.separator + SdkConstants.FN_ADB; /* */ } /* */ /* 88 */ return mrParentLocation + File.separator + SdkConstants.FN_ADB; /* */ } /* */ /* 91 */ return SdkConstants.FN_ADB; /* */ }首先它通过查找JVM中的System Property来找到"com.android.monkeyrunner.bindir"这个属性的值。记得第一章节执行环境初始化的时候在monkeyrunner这个shell脚本里面它是怎么通过java的-D參数把该值保存到System Property的吧?事实上它就是你的文件系统中保存sdk的monkeyrunner这个bin(shell)文件的路径,在我的机器上是"com.android.monkeyrunner.bindir:/Users/apple/Develop/sdk/tools".
adb的名字。在windows下会加上个‘.exe‘变成‘adb.exe‘ ,类linux系统下就仅仅是‘adb’。在本人的机器里面就是"Users/apple/Develop/sdk/platform-tools/adb"
好,找到了adb所在路经后,AdbBackend的构造函数就会依据这个參数去调用AndroidDebugBridge的createBridge这个静态方法,里面重要的是下面代码:
/* */ try /* */ { /* 325 */ sThis = new AndroidDebugBridge(osLocation); /* 326 */ sThis.start(); /* */ } catch (InvalidParameterException e) { /* 328 */ sThis = null; /* */ }第325行AndroidDebugBridge的构造函数做的事情就是实例化AndroidDebugBridge,去检查一下adb的版本号是否满足要求。设置一些成员变量之类的。
adb真正启动起来是调用326行的start()这个成员方法:
/* */ boolean start() /* */ { /* 715 */ if ((this.mAdbOsLocation != null) && (sAdbServerPort != 0) && ((!this.mVersionCheck) || (!startAdb()))) { /* 716 */ return false; /* */ } /* */ /* 719 */ this.mStarted = true; /* */ /* */ /* 722 */ this.mDeviceMonitor = new DeviceMonitor(this); /* 723 */ this.mDeviceMonitor.start(); /* */ /* 725 */ return true; /* */ }这里做了几个非常重要的事情:
- startAdb:开启AndroidDebugBridge
- New DeviceMonitor并传入已经开启的adb:初始化android设备监控
- DeviceMonitor.start:启动DeviceMonitor设备监控线程。
/* */ synchronized boolean startAdb() /* */ { /* 945 */ if (this.mAdbOsLocation == null) { /* 946 */ Log.e("adb", "Cannot start adb when AndroidDebugBridge is created without the location of adb."); /* */ /* 948 */ return false; /* */ } /* */ /* 951 */ if (sAdbServerPort == 0) { /* 952 */ Log.w("adb", "ADB server port for starting AndroidDebugBridge is not set."); /* 953 */ return false; /* */ } /* */ /* */ /* 957 */ int status = -1; /* */ /* 959 */ String[] command = getAdbLaunchCommand("start-server"); /* 960 */ String commandString = Joiner.on(',').join(command); /* */ try { /* 962 */ Log.d("ddms", String.format("Launching '%1$s' to ensure ADB is running.", new Object[] { commandString })); /* 963 */ ProcessBuilder processBuilder = new ProcessBuilder(command); /* 964 */ if (DdmPreferences.getUseAdbHost()) { /* 965 */ String adbHostValue = DdmPreferences.getAdbHostValue(); /* 966 */ if ((adbHostValue != null) && (!adbHostValue.isEmpty())) /* */ { /* 968 */ Map<String, String> env = processBuilder.environment(); /* 969 */ env.put("ADBHOST", adbHostValue); /* */ } /* */ } /* 972 */ Process proc = processBuilder.start(); /* */ /* 974 */ ArrayList<String> errorOutput = new ArrayList(); /* 975 */ ArrayList<String> stdOutput = new ArrayList(); /* 976 */ status = grabProcessOutput(proc, errorOutput, stdOutput, false); /* */ } catch (IOException ioe) { /* 978 */ Log.e("ddms", "Unable to run 'adb': " + ioe.getMessage()); /* */ } /* */ catch (InterruptedException ie) { /* 981 */ Log.e("ddms", "Unable to run 'adb': " + ie.getMessage()); /* */ } /* */ /* */ /* 985 */ if (status != 0) { /* 986 */ Log.e("ddms", String.format("'%1$s' failed -- run manually if necessary", new Object[] { commandString })); /* */ /* 988 */ return false; /* */ } /* 990 */ Log.d("ddms", String.format("'%1$s' succeeded", new Object[] { commandString })); /* 991 */ return true; /* */ }这里所做的事情就是
- 准备好启动db server的command字串
- 通过ProcessBuilder启动command字串指定的adb server
- 错误处理
/* */ private String[] getAdbLaunchCommand(String option) /* */ { /* 996 */ List<String> command = new ArrayList(4); /* 997 */ command.add(this.mAdbOsLocation); /* 998 */ if (sAdbServerPort != 5037) { /* 999 */ command.add("-P"); /* 1000 */ command.add(Integer.toString(sAdbServerPort)); /* */ } /* 1002 */ command.add(option); /* 1003 */ return (String[])command.toArray(new String[command.size()]); /* */ }整个函数玩的就是字串组合,最后获得的字串就是‘adb -P $port start-server‘,也就是开启adb服务器的命令行字串了。终于把这个字串打散成字串array返回。
那么我们往下看第二点“2.初始化DeviceMonitor".
/* */ DeviceMonitor(AndroidDebugBridge server) /* */ { /* 72 */ this.mServer = server; /* */ /* 74 */ this.mDebuggerPorts.add(Integer.valueOf(DdmPreferences.getDebugPortBase())); /* */ }
/* */ void start() /* */ { /* 81 */ new Thread("Device List Monitor") /* */ { /* */ public void run() { /* 84 */ DeviceMonitor.this.deviceMonitorLoop(); /* */ } /* */ }.start(); /* */ }
4. 启动MonkeyRunner
MonkeyRunner入口函数main在开启了AndroidDebugBridge进程和开启了DeviceMonitor设备监控线程之后。下一步要做的是事情就是去把MonkeyRunner真正启动起来:/* */ private int run() /* */ { /* 68 */ String monkeyRunnerPath = System.getProperty("com.android.monkeyrunner.bindir") + File.separator + "monkeyrunner"; /* */ /* */ /* 71 */ Map<String, Predicate<PythonInterpreter>> plugins = handlePlugins(); /* 72 */ if (this.options.getScriptFile() == null) { /* 73 */ ScriptRunner.console(monkeyRunnerPath); /* 74 */ this.chimp.shutdown(); /* 75 */ return 0; /* */ } /* 77 */ int error = ScriptRunner.run(monkeyRunnerPath, this.options.getScriptFile().getAbsolutePath(), this.options.getArguments(), plugins); /* */ /* 79 */ this.chimp.shutdown(); /* 80 */ return error; /* */ }这里又分了两种情况:
- 开启一个jython的console:在用户没有指定脚本參数的情况下。
直接调用eclipse上Preference设定的jython这个interpreter的console,事实上就类似于你直接在命令行打个‘python‘命令,然后弹出一个console让你能够直接在上面编写代码执行了
- 直接执行脚本:调用我们在eclipse上Preference设定的jython这个interpreter来直接解析执行指定的脚本
/* */ public static int run(String executablePath, String scriptfilename, Collection<String> args, Map<String, Predicate<PythonInterpreter>> plugins) /* */ { /* 79 */ File f = new File(scriptfilename); /* */ /* */ /* 82 */ Collection<String> classpath = Lists.newArrayList(new String[] { f.getParent() }); /* 83 */ classpath.addAll(plugins.keySet()); /* */ /* 85 */ String[] argv = new String[args.size() + 1]; /* 86 */ argv[0] = f.getAbsolutePath(); /* 87 */ int x = 1; /* 88 */ for (String arg : args) { /* 89 */ argv[(x++)] = arg; /* */ } /* */ /* 92 */ initPython(executablePath, classpath, argv); /* */ /* 94 */ PythonInterpreter python = new PythonInterpreter(); /* */ /* */ /* 97 */ for (Map.Entry<String, Predicate<PythonInterpreter>> entry : plugins.entrySet()) { /* */ boolean success; /* */ try { /* 100 */ success = ((Predicate)entry.getValue()).apply(python); /* */ } catch (Exception e) { /* 102 */ LOG.log(Level.SEVERE, "Plugin Main through an exception.", e); } /* 103 */ continue; /* */ /* 105 */ if (!success) { /* 106 */ LOG.severe("Plugin Main returned error for: " + (String)entry.getKey()); /* */ } /* */ } /* */ /* */ /* 111 */ python.set("__name__", "__main__"); /* */ /* 113 */ python.set("__file__", scriptfilename); /* */ try /* */ { /* 116 */ python.execfile(scriptfilename); /* */ } catch (PyException e) { /* 118 */ if (Py.SystemExit.equals(e.type)) /* */ { /* 120 */ return ((Integer)e.value.__tojava__(Integer.class)).intValue(); /* */ } /* */ /* 123 */ LOG.log(Level.SEVERE, "Script terminated due to an exception", e); /* 124 */ return 1; /* */ } /* 126 */ return 0; /* */ }从82。83和92行能够看到MonkeyRunner会默认把下面两个位置增加到classpath里面
- 运行脚本的父文件夹
- plugins
5. 总结
- monkeyrunner这个shell脚本会先设置一些执行环境的系统属性保存到JVM的System.Propery里面
- 然后该脚本会通过java -jar直接执行sdk以下的monkeyruner.jar
- 然后操作系统直接回调到monkeyrunner在MonkeyRunnerStarter里面的入口函数main
- 入口函数会先尝试实例化MonkeyRunnerStarter的实例
- 实例化MonkeyRunnerStarter时会去实例化ChimpChat这个类
- 实例化ChimpChat这个类的时候会去创建AndroidDebugBridge对象启动一个adb进程来进行与adbserver以及目标设备的adb守护进程通讯
- 实例化ChimpChat时还会在上面创建的adb对象的基础上创建DeviceMonitor对象并启动一个线程来监控和维护连接到主机pc的android设备信息,由于监控设备时须要通过adb来实现的
- 最后在以上都准备好后就会尝试启动jython编译器的console或者直接调用jython编译器去解析执行脚本
作者 |
自主博客 |
微信 |
CSDN |
天地会珠海分舵 |
|
服务号:TechGoGoGo 扫描码:
|
http://blog.csdn.net/zhubaitian |
以上是关于MonkeyRunner源代码分析之启动的主要内容,如果未能解决你的问题,请参考以下文章
转载:monkeyrunner之eclipse中运行monkeyrunner脚本之环境搭建