第8章3节《MonkeyRunner源代码剖析》MonkeyRunner启动执行过程-启动AndroidDebugBridge
用户在命令行执行monkeyrunner命令来执行測试脚本的时候ADBserver有可能还没有起来,AndroidDebugBridge类的主要作用之中的一个就是去开启一个新的进程来启动ADBserver,这样我们的測试脚本才干发送命令给ADBserver去驱动目标设备做事情。比方安装或者删除待測应用的安装包等。
MonkeyRunner在启动的过程中会牵涉到一系列的调用并关联到不同的类来做不同的事情。
图8-3-1 启动AndroidDebugBridge涉及的类关系
以上类图列出了启动AndroidDebugBridge涉及的关键类的关系,同一时候列出了在启动过程中每一个类设计的关键成员方法和成员变量。在进入代码分析之前我们先对这些做一些描写叙述:
- MonkeyRunnerStarter: 这个类我们在上一小节已经碰到过,它就是monkeyrunner这个jar包的入口类。它拥有的options对象我们已经分析过。
这一小节我们主要分析的是从monkeyRunnerStarter构造函数引发的一系列以启动AndroidDebugBridge为目标的调用。过程中它会实例化ChimpChat这个类,并把实例通过MonkeyRunner的setChimpChat方法保存到MonkeyRunner的chimpchat这个成员变量里面保存起来。保存起来有什么用呢?大家应该注意到MonkeyRunner,ChimpChat和AdbBackend类都拥有waitForConnection这种方法,该方法通常是在我们的測试脚本最開始的时候调用的,目的是获得一个叫做AdbChimpDevice的高层抽象设备对象。这个我们在第6小节”启动Monkey”中会具体描写叙述。这里我们主要向说明的是MonkeyRunner保存了ChimpChat实例后。它的waitForConnection方法就能通过chimpchat对象直接调用ChimpChat类的waitForConnection方法。
- ChimpChat: 这个类基本的功能是组合AdbBackend这个类来实现对AndroidDebugBridge的创建。MonkeyRunnerStarter依赖这个类来创建AdbBackend类的实例并把该实例保存起来到该类的mBackend这个成员变量里面。保存起来的目的跟上面MonkeyRunner组合ChimpChat的目的一样,这样它的waitForConnection方法就能通过mBackend这个对象来调用AdbBackend类的waitForConnection方法
- AdbBackend: 负责创建AndroidDebugBridge实例。
它在自身实例化的时候会创建AndroidDebugBridge的实例并把该实例保存起来到bridge这个成员变量里面,这样仅仅要拥有了AdbBackend实例的类就能通过bridge来获得AndroidDebugBridge维护的最新的设备列表。比方AdbBackend在实现waitForConnection方法时就调用了bridge.getDevices方法来获得全部连接上来的设备列表。然后通过设备序列号来找到目标设备来进行连接
- AndroidDebugBridge: 这个类有两个重要功能,其一是启动ADBserver。其二是启动设备监控线程DeviceMonitor。第二点我们会在下一小节进行阐述。我们这一小节重点是分析第一点看它是怎么启动ADBserver的。从上图中列出来的该类的成员变量和成员方法能够看到它们主要都是跟启动ADBserver相关的,比方DEFAULT_ADB_HOST和DEFAULT_ADB_PORT变量主要是指定ADBserver默认须要监听的地址和端口,getAdbLaunchCommand方法是去获得对应的ADB启动或者停止命令字串来启动ADBserver(开创新进程”adb start-server”)或停止ADBserver(“adb kill-server”)
以下我们通过源代码分析来阐述个中原理。
在通过上一节分析的处理好命令行參数之后,monkeyrunner入口main函数的下一步就是去尝试依据这些參数来调用MonkeyRunnerStarter的构造函数:
178 public static void main(String[] args) { 179 MonkeyRunnerOptions options = MonkeyRunnerOptions.processOptions(args); 180 181 if (options == null) { 182 return; 183 } 184 185 186 replaceAllLogFormatters(MonkeyFormatter.DEFAULT_INSTANCE, options.getLogLevel()); 187 188 MonkeyRunnerStarter runner = new MonkeyRunnerStarter(options); 189 int error = runner.run(); 190 191 192 System.exit(error); 193 } 194 }代码8-3-1 初始化MonkeyRunnerStarter
当中的options參数就是上一节最后依据全部參数创建的MonkeyRunnerOptions对象。里面保存了全部的參数。
往下我们进入MonkeyRunnerStarter的构造函数:
55 public MonkeyRunnerStarter(MonkeyRunnerOptions options) 56 { 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); 62 }代码8-3-2 MonkeyRunnerStarter构造函数
仅从这种方法的几行代码我们能够看到它事实上做的事情就是去依据‘backend’来初始化ChimpChat 并把该实例保存到MonkeyRunnerStarter的chimp成员变量中,同一时候也会调用MonkeyRunner的静态方法setChimpChat把该ChimpChat对象设置到MonkeyRunner的静态成员变量里面,为什么说它一定是静态成员变量呢?由于第61行保存该实例调用的是MonkeyRunner这个类的方法,而不是一个实例,所以该方法肯定就是静态的。而一个静态方法里面的成员函数也必定是静态的。大家跳进去MonkeyRunner这个类就能够看到:
51 static void setChimpChat(ChimpChat chimp) 52 { 53 chimpchat = chimp; 54 }代码8-3-3 MonkeyRunner - setChimpChat
我们返回来继续看ChimpChat是怎么启动的,首先我们MonkeyRunnerStarter构造函数第58行的optionsGetBackendName()是怎么获得backend的名字的,从上一节命令行參数分析我们能够知道它默认是用‘adb’的。所以它获得的就是‘adb’。或者用户指定的其它backend(事实上这样的情况不支持,往下继续分析我们就会清楚了).
取得backend的名字之后就会调用60行的ChimpChat.getInstance来对ChimpChat进行实例化:
46 public static ChimpChat getInstance(Map<String, String> options) 47 { 48 sAdbLocation = (String)options.get("adbLocation"); 49 sNoInitAdb = Boolean.valueOf((String)options.get("noInitAdb")).booleanValue(); 50 51 IChimpBackend backend = createBackendByName((String)options.get("backend")); 52 if (backend == null) { 53 return null; 54 } 55 ChimpChat chimpchat = new ChimpChat(backend); 56 return chimpchat; 57 }
代码8-3-4 ChimpChat - getInstance
ChimpChat实例化所做的事情有两点,这也就是我们这一小节的重点所在了:
- 依据backend的名字来创建一个backend。事实上就是创建一个AndroidDebugBridge - ADB
- 调用构造函数把这个backend保存到ChimChat的成员变量
往下我们继续看ChimpChat中AndroidDebugBridge这个backend是怎么创建的。我们进入到51行调用的createBackendByName这个函数:
75 private static IChimpBackend createBackendByName(String backendName) 76 { 77 if ("adb".equals(backendName)) { 78 return new AdbBackend(sAdbLocation, sNoInitAdb); 79 } 80 return null; 81 }代码8-3-5 ChimpChat - createBackendByName
这里注意第77行,这就是为什么我之前说backend事实上仅仅是支持‘adb’而已。起码临时的代码是这样子。假设今后google决定支持其它更新的backend,就另当别论了。
这还是有可能的。毕竟google留了这个接口。
56 public AdbBackend(String adbLocation, boolean noInitAdb) 57 { 58 this.initAdb = (!noInitAdb); 59 60 61 if (adbLocation == null) { 62 adbLocation = findAdb(); 63 } 64 65 if (this.initAdb) { 66 AndroidDebugBridge.init(false); 67 } 68 69 this.bridge = AndroidDebugBridge.createBridge(adbLocation, true); 70 }代码8-3-6 AdbBackend构造函数
创建AndroidDebugBridge之前我们先要确定我们的adb程序的位置,这就是通过62行来实现的。我们进去findAdb去看下它是怎么找到我们的sdk中的adb的:
72 private String findAdb() 73 { 74 String mrParentLocation = System.getProperty("com.android.monkeyrunner.bindir"); 75 76 77 78 79 80 if ((mrParentLocation != null) && (mrParentLocation.length() != 0)) 81 { 82 File platformTools = new File(new File(mrParentLocation).getParent(), "platform-tools"); 83 84 if (platformTools.isDirectory()) { 85 return platformTools.getAbsolutePath() + File.separator + SdkConstants.FN_ADB; 86 } 87 88 return mrParentLocation + File.separator + SdkConstants.FN_ADB; 89 } 90 91 return SdkConstants.FN_ADB; 92 }代码8-3-7 AdbBackend - findAdb
首先它通过查找JVM中的System Property来找到"com.android.monkeyrunner.bindir"这个属性的值,记得前面小节执行环境初始化的时候在monkeyrunner这个shell脚本里面它是怎么通过java的-D參数把该值保存到JVM里面的吧?事实上它就是你的文件系统中保存sdk的monkeyrunner这个bin(shell)文件的路径,在我的机器上是"com.android.monkeyrunner.bindir:/Users/apple/Develop/sdk/tools".
找到这个路径后通过第82行的代码再取得它的父文件夹,也就是sdk的文件夹,再加上'platform-tools'这个子文件夹,然后再通过85或者88这行加上adb这个名字。这里的FN_ADB就是adb的名字,在windows下会加上个'.exe'变成'adb.exe' ,类linux系统下就仅仅是‘adb’。
在本人的机器里面就是"Users/apple/Develop/sdk/platform-tools/adb"
好。找到了adb所在路经后,AdbBackend的构造函数就会依据这个參数去调用AndroidDebugBridge的createBridge这个静态方法:
265 public static AndroidDebugBridge createBridge() ... 271 try 272 { 273 sThis = new AndroidDebugBridge(); 274 sThis.start(); 275 } catch (InvalidParameterException e) { 276 sThis = null; 277 } ... 297 return sThis; 298 } 299 }代码8-3-8 AndroidDebugBridge - createBridge
第273行AndroidDebugBridge的构造函数做的事情就是实例化AndroidDebugBridge。ADB真正启动起来是调用274行的start()这个成员方法:
713 boolean start() 714 { 715 if ((this.mAdbOsLocation != null) && (sAdbServerPort != 0) && ((!this.mVersionCheck) || (!startAdb()))) { 716 return false; 717 } 718 719 this.mStarted = true; 720 721 722 this.mDeviceMonitor = new DeviceMonitor(this); 723 this.mDeviceMonitor.start(); 724 725 return true; 726 }代码8-3-9 AndroidDebugBridge - start
这里做了几个非常重要的事情:
- 715行startAdb:开启AndroidDebugBridge
- 722-723行: 初始化android设备监控并启动DeviceMonitor设备监控线程。
这一小节我们先看第一个startAdb。看它是怎样把AndroidDebugBridge给开启起来的。第2点我们将会在下一小节描写叙述。
943 synchronized boolean startAdb() 944 { 945 if (this.mAdbOsLocation == null) { 946 Log.e("adb", "Cannot start adb when AndroidDebugBridge is created without the location of adb."); 947 948 return false; 949 } 950 951 if (sAdbServerPort == 0) { 952 Log.w("adb", "ADB server port for starting AndroidDebugBridge is not set."); 953 return false; 954 } 955 956 957 int status = -1; 958 959 String[] command = getAdbLaunchCommand("start-server"); 960 String commandString = Joiner.on(',').join(command); 961 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())) 967 { 968 Map<String, String> env = processBuilder.environment(); 969 env.put("ADBHOST", adbHostValue); 970 } 971 } 972 Process proc = processBuilder.start(); 973 974 ArrayList<String> errorOutput = new ArrayList(); 975 ArrayList<String> stdOutput = new ArrayList(); 976 status = grabProcessOutput(proc, errorOutput, stdOutput, false); 977 } catch (IOException ioe) { 978 Log.e("ddms", "Unable to run 'adb': " + ioe.getMessage()); 979 } 980 catch (InterruptedException ie) { 981 Log.e("ddms", "Unable to run 'adb': " + ie.getMessage()); 982 } 983 984 985 if (status != 0) { 986 Log.e("ddms", String.format("'%1$s' failed -- run manually if necessary", new Object[] { commandString })); 987 988 return false; 989 } 990 Log.d("ddms", String.format("'%1$s' succeeded", new Object[] { commandString })); 991 return true; 992 }代码8-3-10 AndroidDebugBridge - startAdb
这里所做的事情就是:
- 准备好启动db server的command字串
- 通过ProcessBuilder启动command字串指定的adb server
- 错误处理
command字串通过959行的getAdbLauncherCommand('start-server')来实现:
994 private String[] getAdbLaunchCommand(String option) 995 { 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)); 1001 } 1002 command.add(option); 1003 return (String[])command.toArray(new String[command.size()]); 1004 }代码8-3-11 AndroidDebugBridge - getAdbLaunchCommand
整个函数玩的就是字串组合,最后获得的字串就是”adb -P $port start-server”,也就是开启adbserver的命令行字串了。终于把这个字串打散成字串数组返回。这里注意port默认值就是ADBserver的默认监听端口5037。
startAdb方法获得命令之后下一步就是直接调用java的ProcessBuilder构造函数来创建一个ADBserver进程了。
创建好后就能够通过972行的‘processBuilder.start()‘把这个进程启动起来。
迄今为止AndroidDebugBridge启动函数start()所做事情的第一点“1. 启动AndroidDebugBridge"已经完毕了。adbserver进程已经执行起来了。
注:很多其它文章请关注公众号:techgogogo或个人博客http://techgogogo.com。
当然。也非常欢迎您直接微信(zhubaitian1)勾搭。本文由天地会珠海分舵原创。
转载请自觉,是否投诉维权看心情。