第8章6节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-启动Monkey

大家可能会觉得奇怪,为什么启动目标设备端的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这个设备。这里大家可能迷糊了,这又是什么设备啊?这里其实有几个抽象设备类(抽象设备类在这里的意思不是说这些类是抽象的,而是说这些类代表的设备是真是设备在主机端的一个虚拟抽象),如果我做如下的解析大家应该就很好理解了:

  • Device(ddmlib):代表的是通过ADB进行控制的设备
  • ChimpManager(chimpchat): 代表的是通过money进行控制的设备
  • AdbChimpDevice(chimpchat): 是一个更高层次的设备概念的抽象,它包含了以上的通过ADB控制的Device设备,同时也包含了上面的通过monkey进行控制的设备,当调用过来的时候AdbChimpDevice会根据究竟是需要发送命令到ADB服务器还是发送到monkey来决定使用的是哪个设备
  • MonkeyDevice(monkeyrunner): 可以被测试脚本直接使用的一个高层抽象设备,它所有的API基本上都是分发到AdbChimpDevice来执行的

大家看下下面的类图中AdbChimpDevice和Device以及ChimpManager的关系就会更清楚了:AdbChimpDevice拥有两个关键的成员变量,一个是IDevice类型,其实就是Device的父类的一个实例;一个是ChimpManager类型的一个实例。在下一章分析MonkeyDevice的实现原理的时候我们就会看到MonkeyDevice过来的一个调用,比如press,最终是被分发到AdbChimpDevice,AdbChimpDevice再来决定是通过ChimpManager来往monkey发送命令还是通过Device往ADB服务器发送命令。


图8-6-1 启动Monkey过程涉及的关键类

我们先分析下类图中每个类的关系以及简要描述下MonkeyRunner是怎么启动 monkey的,然后再去分析它们相关的实现代码。

  • MonkeyRunner: 每个测试脚本相信都会首先用这个类的waitForConnection方法来获得一个MonkeyDevice对象来去实现点击等操作,但此后在脚本中这个类基本上就不见踪影了。它拥有一个ChimpChat的实例变量chimpchat,上面我们已经分析过它是在MonkeyRunnerStarter实例化的时候被设置的。waitForConnection方法最终会通过chimpchat这个实例调用ChimpChat类的waitForConnection方法
  • MonkeyDevice: 可以说是脚本使用最频繁的一个类,基本所有和目标设备交互的操作都是通过调用它来完成的。它拥有大量的操作目标设备的方法,比如press,takeSnapShot,type等。注意它拥有了一个iChimpDevice类型的impl实例,通过下面的分析我们会看到它其实就是AdbChimpDevice的一个实例,因为AdbChimpDevice也是实现了IChimpDevice接口的。根据上面的描述AdbChimpDevice是一个高层抽象出来的设备,它拥有ChimpManager的实例可以向monkey发送命令,也拥有Device实例可以向ADB服务器发送adb命令
  • ChimpChat: 这个类我们在上面的启动流程中已经分析过,它是在MonkeyRunnerStarter实例化的过程中被实例化的,而它实例化的过程中又会启动AndroidDebugBridge和DeviceMonitor。实例化完后,MonkeyRunnerStarter构造函数最后会调用MonkeyRunner的setChimpChat静态方法把它设置到MonkeyRunner的chimpchat这个成员变量里面。ChimpChat拥有一个IChimpBackend类型的mBackend对象,其实就是一个AdbBackend对象了,因为根据前面启动流程的分析,当ChimpChat构造函数在实例化完AdbBackend对象后会将该对象保存下来到mBackend这个成员对象里面,ChimpChat的waitForConnection方法就是通过这个对象来调用AdbBackend的waitForConnection方法的
  • AdbBackend:这个类同样在上面的启动流程中已经分析过,它是在实例化ChimpChat的过程中被实例化的,而它实例化的过程中又会去实例化AndroidDebugBridge,所以它持有一个AndroidDebugBridge的实例。通过这个实例AdbBackend就能调用AndroidDebugBridge的getDevices方法来获得所有已经连接上来的Device设备列表,并根据waitForConnection传进来的设备序列号来找到目标Device设备,再来实例化AdbChimpDevice这个高层抽象设备并返回该设备给上层
  • AdbChimpDevice: 这个就是前面一直强调的高层设备,拥有代表ADB设备的device实例,同时也拥有代表monkey设备的manager实例。MonkeyRunner类的waitForConnection方法最终获得的就是这个类的实例,并以这个实例为参数构造MonkeyDevice对象的,构造过程中MonkeyDevice会把这个实例保存到MonkeyDevice对象的impl这个成员变量里面,所以上面描述的MonkeyDevice类的press,type,takeSnapshot等API调用就可以通过impl这个对象来调用其对应的press,type,takeSnapshot等方法,并将这些方法分发到monkey设备ChimpManager或者ADB设备Device去执行
  • ChimpManager: 代表了一个monkey设备,也就是说所有monkey相关的请求都是发送到ChimpManager来进行处理的。它拥有几个专门和monkey通信的成员变量,比如monkeSocket是用来和monkey进行连接的;monkeyWriter是用来往该socket写数据的,也就是发送请求的;monkeyReader是用来往该socket读数据的,也就是读取请求结果的。同时它拥有很多monkey相关的调用,比如touch等
  • AndroidDebugBridge: 这个类同样在前面的MonkeyRunner启动流程中已经分析过它是怎么启动起来的,前面描述的它的主要功能是将ADB服务器和DeviceMonitor给启动起来。在这一节它的主要功能将会是因为它维护了mDeviceMonitor这个DeviceMonitor的实例,所以能通过它来获得DeviceMonitor维护的最新的Device列表。为什么要获得这个列表,请看上面AdbBackend类的描述
  • DeviceMonitor: 这个类在这里的主要功能就是提供了getDevices这个方法来获得它维护的最新的Device设备列表

有了以下的基本认知之后,我们就可以通过分析代码来阐述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框架的理解。好,我们继续对上面代码的分析:

  • 75行: 调用chimpchat对象的waitForConnection方法来获得一个AdbChimpDevice的高层抽象设备实例
  • 79-78行: 将上面的AdbChimpDevice实例作为参数传入到MonkeyDevice来构造一个MonkeDevice对象并返回给测试代码,这样测试代码就可以通过操作该MonkeyDevice实例来控制目标设备了

我们重点往下分析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的实例对象并返回:

  • 133行: 进入while循环,如果在超时时间内还没有连接上目标设备的话就一直循环下去
  • 128和132行: 每次循环如果没有连接上目标设备的话就睡眠0.2秒再进行下次循环
  • 119行: 根据提供的设备序列号(正则表达式)查找目标Device设备,去哪里查找呢?其实就是去DeviceMonitor监控线程对象维护的最新Device设备队列中查找
  • 122行: 找到目标Device设备后,将该设备对象传进去到AdbChimpDevice的构造函数中去实例化AndroidChimpDevice对象并返回。我们往下会看到AdbChimpDevice在实例化的过程中会去实现ChimpManager对象来启动monkey进行通信

往下我们先重点分析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测试脚本发送过去的请求。代码流程如下所示:

  • 125行: 设置本机到目标机器monkey进程监听端口的端口转发,调用的是Device的createForward的方法,这个方法我们在下一章描述Device类详解的时候会进行分析。这里只需要它基本可以看作是在命令行发送”adb forward 12345 12345“来完成从本机12345端口到远程monkey监控的12345端口的转发就好了。设置好端口转发后往下的代码就能直接连接本机的12345端口,这就等同于连上的是远端目标设备中monkey监听的12345端口了
  • 139-138行: 设置好monkey端口转发后,createManager方法就会往ADB服务器发送shell命令”monkey --port 12345”来启动monkey去监听端口12345。发送adb shell命令使用的方法是createAsyncCommand方法,其实该方法没有什么好分析的,因为它把发送命令请求直接转发给Device类的executeShellCommand而已,而executeShellCommand这个方法我们也是在下一章会进行分析
  • 149行: 将本机监听地址“127.0.0.1”转换成InetAddress对象格式,这样往下创建Socket连接的时候才能直接使用

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个事情:

  • 179行:  创建连接到本机monkey转发端口的Socket对象
  • 187行:  根据该Socket对象构造ChimpManager实例,ChimpManager的详细分析会放到下一章的描述ChimpManager类详解的时候进行分析
  • 194行:  往monkey发送命令去唤醒休眠屏幕,如果屏幕是在休眠状态的话。wake的原理也会在下一章进行分析

分析到这里这一小节的目标就已经达到了,我们已经学习到了monkey服务进程是如何在脚本中通过调用MonkeyRunner的waitForConnection方法启动起来的了,同时我们也学习到了AdbChimpDevice和ChimpManager这两个关键类创建的相关知识点。

下一小节我们来尝试把本章学到的内容进行一个总结。

注:更多文章请关注公众号:techgogogo或个人博客http://techgogogo.com。当然,也非常欢迎您直接微信(zhubaitian1)勾搭。本文由天地会珠海分舵原创。转载请自觉,是否投诉维权看心情。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值