大家可能会觉得奇怪,为什么启动目标设备端的monkey进程会放在“运行测试脚本”这一节之后来阐述。
纵观前面整个MonkeyRunner的启动流程,我们看到并没有提及到monkey进程启动的地方。那么就奇怪了,monkey是什么时候被MonkeyRunner启动起来的呢?
我们的测试脚本一开始时几乎毫无例外的都需要执行一个调用:MonkeyRunner.waitForConnection(),如果有多个设备连接到主机的话还需要指定设备序列号,还可以指定等待连接的Timeout时间,比如:MonkeyRunner.waitForConnection(“xxx”,10000),其中xxx代表序列号,10000代表超时Timeout时10秒;如果没有指定参数的话框架会提供默认的参数。其实monkey的启动就是在waitForConnection的一些列调用中完成的。
MonkeyRunner调用waitForConnection的目的是什么呢?顾名思义,当然是等待连接上设备了。但是细想下觉得又不对,在上面MonkeyRunner启动运行过程中,不是已经在启动设备监控线程DeviceMonitor中创建了代表目标设备的Device类型的mDevices列表了吗?表明设备已经连接上来了啊。这时就算我们不运行任何monkeyrunner脚本,发送“adb devices”命令也能够列出来所有已经连接上来的设备了。那么难道waitForConnection起名字Google给起错了吗?其实不是的,这里waitForConnection确实是在等待设备的连接,只是等待的不是上面Device设备的连接,因为确实这个设备在启动设备监控线程的时候已经连接上来的了,这里等待的是去连接上AdbChimpDevice这个设备。这里大家可能迷糊了,这又是什么设备啊?这里其实有几个抽象设备类(抽象设备类在这里的意思不是说这些类是抽象的,而是说这些类代表的设备是真是设备在主机端的一个虚拟抽象),如果我做如下的解析大家应该就很好理解了:
大家看下下面的类图中AdbChimpDevice和Device以及ChimpManager的关系就会更清楚了:AdbChimpDevice拥有两个关键的成员变量,一个是IDevice类型,其实就是Device的父类的一个实例;一个是ChimpManager类型的一个实例。在下一章分析MonkeyDevice的实现原理的时候我们就会看到MonkeyDevice过来的一个调用,比如press,最终是被分发到AdbChimpDevice,AdbChimpDevice再来决定是通过ChimpManager来往monkey发送命令还是通过Device往ADB服务器发送命令。
图8-6-1 启动Monkey过程涉及的关键类
我们先分析下类图中每个类的关系以及简要描述下MonkeyRunner是怎么启动 monkey的,然后再去分析它们相关的实现代码。
有了以下的基本认知之后,我们就可以通过分析代码来阐述Monkey是怎么在用户调用MonkeyRunner.waitForConnection的方法引发的一系列调用过程中启动起来的了,我们先看下MonkeyRunner的这个waitForConnection方法:
62 public static MonkeyDevice waitForConnection(PyObject[] args, String[] kws) 63 { 64 ArgParser ap = JythonUtils.createArgParser(args, kws); 65 Preconditions.checkNotNull(ap); 66 long timeoutMs; 67 try 68 { 69 double timeoutInSecs = JythonUtils.getFloat(ap, 0); 70 timeoutMs = (timeoutInSecs * 1000.0D); 71 } catch (PyException e) { 72 timeoutMs = Long.MAX_VALUE; 73 } 74 75 IChimpDevice device = chimpchat.waitForConnection(timeoutMs, ap.getString(1, ".*")); 76 77 MonkeyDevice chimpDevice = new MonkeyDevice(device); 78 return chimpDevice; 79 }代码8-6-1 MonkeyRunner - waitForConnection
脚本调用到的MonkeyRunner,MonkeyDevice等这些类都是通过JAVA来编写的,而脚本自身却是通过jython(可以被调用JAVA的python)编写的,所以它们之前的参数需要有一个转换的机制,至于它们是怎么转换的不是重点,所以我自己都没有去研究过jython的实现原理,因为这不影响我对MonkeyRunner框架的理解。好,我们继续对上面代码的分析:
我们重点往下分析ChimpChat的waitForConnection方法,至于MonkeyDevice构造函数,我们在下一章会对整个MonkeyDevice的运行原理进行一个详尽的分析,所以这里就不重复了。
89 public IChimpDevice waitForConnection(long timeoutMs, String deviceId) 90 { 91 return this.mBackend.waitForConnection(timeoutMs, deviceId); 92 } 98 public IChimpDevice waitForConnection() 99 { 100 return this.mBackend.waitForConnection(2147483647L, ".*"); 101 }代码8-6-2 ChimpChat - waitForConnection
ChimpChat提供了两个waitForConnection方法,其中一个是不带参数的,相当于用户在脚本直接调用MonkeyDevice.waitForConnection();另外一个是带参数long类型超时和设备序列号的。无论是哪个方法ChimpChat都是很简单只有一行,调用的是mBackend的waitForConnection,只是如果用户没有提供参数的话,ChimpChat会默认初始化超时和设备序列号这两个参数,其中设备序列号会被初始化为一个正则表达式”.*”,代表任意一个首先找到的设备。这里的mBackend就是前面分析“启动AndroidDebugBridge”的过程中实例化的AdbBackend对象,所以我们要定位到该类的waitForConnection方法。
116 public IChimpDevice waitForConnection(long timeoutMs, String deviceIdRegex) 117 { 118 do { 119 IDevice device = findAttachedDevice(deviceIdRegex); 120 121 if ((device != null) && (device.getState() == IDevice.DeviceState.ONLINE)) { 122 IChimpDevice chimpDevice = new AdbChimpDevice(device); 123 this.devices.add(chimpDevice); 124 return chimpDevice; 125 } 126 try 127 { 128 Thread.sleep(200L); 129 } catch (InterruptedException e) { 130 LOG.log(Level.SEVERE, "Error sleeping", e); 131 } 132 timeoutMs -= 200L; 133 } while (timeoutMs > 0L); 134 135 136 return null; 137 }代码8-6-3 AdbBackend - waitForConnection
该方法会做一个while循环一直等待找到一个跟用户提供的或者默认的序列号吻合的设备知道超时,如果找到了该设备就会以该设备作为参数去构造一个AdbChimpDevice的实例对象并返回:
往下我们先重点分析119行findAttachedDevice是如何根据设备序列号找到目标Device对象的,然后再去分析AdbChimpDevice是如何构造起来的。我们先进入到findAttachedDevice方法中:
99 private IDevice findAttachedDevice(String deviceIdRegex) 100 { 101 Pattern pattern = Pattern.compile(deviceIdRegex); 102 for (IDevice device : this.bridge.getDevices()) { 103 String serialNumber = device.getSerialNumber(); 104 if (pattern.matcher(serialNumber).matches()) { 105 return device; 106 } 107 } 108 return null; 109 }代码8-6-4 AdbBackend - findAttachedDevice
在第3节分析“启动AndroidDebugBridge”的过程中我们已经学习了AdbBackend在初始化AndroidDebugBridge对象后会将该实例保存到自身的bridge成员对象里面,所以这个方法做的事情就是通过这个AndroidDebugBridge实例去获得所有最新的Device设备列表(102行),然后比对每个Device设备的序列号和参数提供的时候吻合,如果是的话就返回(103-105行)。
我们先看下是怎么获得设备列表的:
482 public IDevice[] getDevices() 483 { 484 synchronized (sLock) { 485 if (this.mDeviceMonitor != null) { 486 return this.mDeviceMonitor.getDevices(); 487 } 488 } 489 490 return new IDevice[0]; 491 }代码8-6-5 AdbBackend - getDevices
AndroidDebugBridge对象起启动DeviceMonitor设备监控线程对象后会将该对象保存起来到mDeviceMonitor成员变量里面,这些我们在前面都学习过了。这里AndroidDebugBridge的getDevices方法就是去DeviceMonitor对象中调用其getDevices方法来获取最新的设备列表的:
129 Device[] getDevices() 130 { 131 synchronized (this.mDevices) { 132 return (Device[])this.mDevices.toArray(new Device[this.mDevices.size()]); 133 } 134 }代码8-6-6 DeviceMonitor - getDevices
在第4节“启动设备监控线程DeviceMonitor”中我们已经学习了当DeviceMonitor在往ADB服务器发送监控命令“host:track-devices”后,一旦监控到有设备新增加或者移除或者状态变化等,就会将设备的改动更新保存到mDevices这个Device列表里面。以上方法返回的就是这一整个列表。
代码8-6-4 AdbBackend - findAttachedDevice中,findAttachedDevice在获得这个设备列表后,会取出每个设备的序列号来和目标设备序列号进行对比查找直接找到吻合的设备才返回,那么我们看下其对应的获取序列号的方法device.getSerialNumber():
244 public String getSerialNumber() 245 { 246 return this.mSerialNumber; 247 }代码8-6-7 Device - getSerialNumber
直接返回的就是Device设备保存的mSerialNumber变量,这个变量就是代表了对应设备的序列号。大家应该还记得第4节”启动设备监控线程DeviceMonitor”中分析到“processIncomingDeviceData”方法时,一旦ADB服务器将最新的设备列表发送过来的时候就会取出每个设备的序列号和设备状态来初始化Device设备来把该设备的序列号,设备状态保存到该Device设备对应的mSerialNumber和mStat成员变量中。
在获得比对设备序列号后,findAttachedDevice就会跟提供的序列号进行比对,如果吻合就返回给调用者” 代码8-6-3 AdbBackend - waitForConnection”了。而AdbBackend的waitForConnection在获得这个Device实例后就会把它传到AdbChimpDevice的构造函数中来构造AdbChimpDevice的实例对象。我们看看它的构造函数是怎么做的:
68 public AdbChimpDevice(IDevice device) 69 { 70 this.device = device; 71 this.manager = createManager("127.0.0.1", 12345); 72 73 Preconditions.checkNotNull(this.manager); 74 }代码8-6-8 AdbChimpDevice构造函数
如前面一直强调的,AdbChimpDevice是一个很重要的类,它是一个高层抽象的设备对象,它组合了代表通过monkey控制的设备ChimpManager和通过ADB控制的设备Device。这个组合关系就是通过上面这个AdnbChimpDevice构造函数体现出来的了。第70行组合的就是Device设备,71行组合的就是ChimpManager实例。只是Device实例是在启动设备监控线程DeviceMonitor中就已经实例化创建好的,而ChimpManager是在这个时候才进行创建的。创建的时候指定的是本机回环IP地址”127.0.0.1”,端口指定是monkey本地转发端口12345
创建ChimpManager的调用createManager的代码有点长,我们会分两部分来进行分析,其中第一部分是启动Monkey,第二部分是创建ChimpManager。我们先看第一部分:
123 private ChimpManager createManager(String address, int port) { 124 try { 125 this.device.createForward(port, port); 126 } catch (TimeoutException e) { 127 LOG.log(Level.SEVERE, "Timeout creating adb port forwarding", e); 128 return null; 129 } catch (AdbCommandRejectedException e) { 130 LOG.log(Level.SEVERE, "Adb rejected adb port forwarding command: " + e.getMessage(), e); 131 return null; 132 } catch (IOException e) { 133 LOG.log(Level.SEVERE, "Unable to create adb port forwarding: " + e.getMessage(), e); 134 return null; 135 } 136 137 String command = "monkey --port " + port; 138 executeAsyncCommand(command, new LoggingOutputReceiver(LOG, Level.FINE)); 139 140 try 141 { 142 Thread.sleep(1000L); 143 } catch (InterruptedException e) { 144 LOG.log(Level.SEVERE, "Unable to sleep", e); 145 } 146 InetAddress addr; 147 try 148 { 149 addr = InetAddress.getByName(address); 150 } catch (UnknownHostException e) { 151 LOG.log(Level.SEVERE, "Unable to convert address into InetAddress: " + address, e); 152 return null; 153 } ... }代码8-6-9 AdbChimpDevice - createManager之启动monkey
createManager首先做的事情就是去把目标设备端的monkey服务进程给启动起来接收MonkeyRunner测试脚本发送过去的请求。代码流程如下所示:
createManager之启动monkey到了这里就完成了,往下我们继续看第二部分createManager之创建ChimpManager:
123 private ChimpManager createManager(String address, int port) { ... //启动monkey代码略 159 boolean success = false; 160 ChimpManager mm = null; 161 long start = System.currentTimeMillis(); 162 163 while (!success) { 164 long now = System.currentTimeMillis(); 165 long diff = now - start; 166 if (diff > 30000L) { 167 LOG.severe("Timeout while trying to create chimp mananger"); 168 return null; 169 } 170 try 171 { 172 Thread.sleep(1000L); 173 } catch (InterruptedException e) { 174 LOG.log(Level.SEVERE, "Unable to sleep", e); 175 } 176 Socket monkeySocket; 177 try 178 { 179 monkeySocket = new Socket(addr, port); 180 } catch (IOException e) { 181 LOG.log(Level.FINE, "Unable to connect socket", e); 182 success = false; } 183 continue; 184 185 try 186 { 187 mm = new ChimpManager(monkeySocket); 188 } catch (IOException e) { 189 LOG.log(Level.SEVERE, "Unable to open writer and reader to socket"); } 190 continue; 191 192 try 193 { 194 mm.wake(); 195 } catch (IOException e) { 196 LOG.log(Level.FINE, "Unable to wake up device", e); 197 success = false; } 198 continue; 199 200 success = true; 201 } 202 203 return mm; }代码8-6-10 AdbChimpDevice - createManager之创建ChimpManager
其实上面一堆代码无非是在一个循环中做了3个事情:
分析到这里这一小节的目标就已经达到了,我们已经学习到了monkey服务进程是如何在脚本中通过调用MonkeyRunner的waitForConnection方法启动起来的了,同时我们也学习到了AdbChimpDevice和ChimpManager这两个关键类创建的相关知识点。
下一小节我们来尝试把本章学到的内容进行一个总结。
注:更多文章请关注公众号:techgogogo或个人博客http://techgogogo.com。当然,也非常欢迎您直接微信(zhubaitian1)勾搭。本文由天地会珠海分舵原创。转载请自觉,是否投诉维权看心情。