用户在命令行运行monkeyrunner命令来执行测试脚本的时候ADB服务器有可能还没有起来,AndroidDebugBridge类的主要作用之一就是去开启一个新的进程来启动ADB服务器,这样我们的测试脚本才能发送命令给ADB服务器去驱动目标设备做事情,比如安装或者删除待测应用的安装包等。
MonkeyRunner在启动的过程中会牵涉到一系列的调用并关联到不同的类来做不同的事情。
图8-3-1 启动AndroidDebugBridge涉及的类关系
以上类图列出了启动AndroidDebugBridge涉及的关键类的关系,同时列出了在启动过程中每个类设计的关键成员方法和成员变量,在进入代码分析之前我们先对这些做一些描述:
下面我们通过源码分析来阐述个中原理。在通过上一节分析的处理好命令行参数之后,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 }
ChimpChat实例化所做的事情有两点,这也就是我们这一小节的重点所在了:
往下我们继续看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
这里做了几个很重要的事情:
这一小节我们先看第一个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
这里所做的事情就是:
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”,也就是开启adb服务器的命令行字串了,最终把这个字串打散成字串数组返回。这里注意port默认值就是ADB服务器的默认监听端口5037。
startAdb方法获得命令之后下一步就是直接调用java的ProcessBuilder构造函数来创建一个ADB服务器进程了。创建好后就可以通过972行的‘processBuilder.start()‘把这个进程启动起来。
迄今为止AndroidDebugBridge启动函数start()所做事情的第一点“1. 启动AndroidDebugBridge"已经完成了,adb服务器进程已经运行起来了。
注:更多文章请关注公众号:techgogogo或个人博客http://techgogogo.com。当然,也非常欢迎您直接微信(zhubaitian1)勾搭。本文由天地会珠海分舵原创。转载请自觉,是否投诉维权看心情。