028-86922220

建站动态

根据您的个性需求进行定制 先人一步 抢占小程序红利时代

MonkeyRunner源码分析之与Android设备通讯方式

如前文《谁动了我的截图?--Monkeyrunner takeSnapshot方法源码跟踪分析》所述,本文主要会尝试描述android的自动化测试框架MonkeyRunner究竟是如何和目标设备进行通信的。

成都创新互联公司成立与2013年,先为祁阳等服务建站,祁阳等地企业,进行企业商务咨询服务。为祁阳企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。

在上一篇文章中我们其实已经描述了其中一个方法,就是通过adb协议发送adb服务器请求的方式驱动android设备的adbd守护进程去获取FrameBuffer的数据生成屏幕截图。那么MonkeyRunner还会用其他方式和目标设备进行通信吗?答案是肯定的,且看我们一步步分析道来。


1.概述

MonkeyRunner和目标设备打交道都是通过ChimpChat层进行封装分发但最终是在ddmlib进行处理的,其中囊括的方法大体如下:

以下是MonkeyDevice所有请求对应的与设备通信方式

请求

是否需要和目标设备通信

通信方式

注解

发送adb shell命令

getSystemProperty

是

发送adb shell命令


installPackage

是

发送adb shell命令

传送数据时发送adb协议请求,发送安装命令时使用adb shell命令

startActivity

是

发送adb shell命令


broadcastIntent

是

发送adb shell命令


instrument

是

发送adb shell命令


shell

是

发送adb shell命令

命令为空,所以相当于直接执行”adb shell “

removePackage

是

发送adb shell命令


发送monkey命令

getProperty

是

发送monkey命令  


wake

是

发送monkey命令  


dispose 

是

发送monkey命令   


press

是

发送monkey命令  


type

是

发送monkey命令  


touch

是

发送monkey命令  


drag

是

发送monkey命令  


getViewIdList

是

发送monkey命令  


getView

是

发送monkey命令  


getViews

是

发送monkey命令  


getRootView

是

发送monkey命令  


发送adb协议请求

takeSnapshot

是

发送adb协议请求


reboot

是

发送adb协议命令


installPackage

是

发送adb协议请求

相当于直接发送adb命令行命令’adb push’

分析之前请大家准备好对应的几个库的源码:

MonkeyRunner源码分析之与Android设备通讯方式

2. 发送monkey命令

在剖析如何发送monkey命令之前,我们需要先去了解一个类,因为这个类是处理所有monkey命令的关键,这就是ChimpChat库的ChimpManager类。
我们先查看其构造函数,看它是怎么初始化的:

/*     */   private Socket monkeySocket; /*     */    /*     */   private BufferedWriter monkeyWriter; /*     */    /*     */   private BufferedReader monkeyReader; /*     */    /*     */  /*     */   public ChimpManager(Socket monkeySocket) /*     */     throws IOException /*     */   { /*  62 */     this.monkeySocket = monkeySocket; /*  63 */     this.monkeyWriter = new BufferedWriter(new OutputStreamWriter(monkeySocket.getOutputStream())); /*     */      /*  65 */     this.monkeyReader = new BufferedReader(new InputStreamReader(monkeySocket.getInputStream())); /*     */   }
初始化所做的事情如下

好,那么现在我们返回来看这个类是什么时候实例化的。请定位到AdbChimpDevice的构造函数:

/*     */   private ChimpManager manager; /*     */    /*     */   public AdbChimpDevice(IDevice device) /*     */   { /*  70 */     this.device = device; /*  71 */     this.manager = createManager("127.0.0.1", 12345); /*     */      /*  73 */     Preconditions.checkNotNull(this.manager); /*     */   }
可以看到ChimpManager是在AdbChimpDevice构造的时候已经开始初始化的了,初始化传入的地址是"127.0.0.1"和端口是12345,这个是在下面分析的createManager这个方法中创建socket用的,也就是我们上面提到的monkeySocket.在继续之前这里我们先整理下思路,结合上一篇文章,我们看到几个重要的类的初始化流程是这样的: 根据以上的流程我们就很清晰AdbChimpDevice其实在测试脚本一调用MonkeyRunner.waitForConnection方法的时候就已经会初始化的了,也就是说ChimpManager也在这个时候已经初始化的了。

好,那么我们继续看AdbChimpDevice里面的方法createManager是如何对ChimpManager进行初始化的:

/*     */   private ChimpManager createManager(String address, int port) { /*     */     try { /* 125 */       this.device.createForward(port, port); /*     */     } catch (TimeoutException e) { /* 127 */       LOG.log(Level.SEVERE, "Timeout creating adb port forwarding", e); /* 128 */       return null; /*     */     } catch (AdbCommandRejectedException e) { /* 130 */       LOG.log(Level.SEVERE, "Adb rejected adb port forwarding command: " + e.getMessage(), e); /* 131 */       return null; /*     */     } catch (IOException e) { /* 133 */       LOG.log(Level.SEVERE, "Unable to create adb port forwarding: " + e.getMessage(), e); /* 134 */       return null; /*     */     } /*     */      /* 137 */     String command = "monkey --port " + port; /* 138 */     executeAsyncCommand(command, new LoggingOutputReceiver(LOG, Level.FINE)); /*     */      /*     */     try /*     */     { /* 142 */       Thread.sleep(1000L); /*     */     } catch (InterruptedException e) { /* 144 */       LOG.log(Level.SEVERE, "Unable to sleep", e); /*     */     } /*     */     InetAddress addr; /*     */     try /*     */     { /* 149 */       addr = InetAddress.getByName(address); /*     */     } catch (UnknownHostException e) { /* 151 */       LOG.log(Level.SEVERE, "Unable to convert address into InetAddress: " + address, e); /* 152 */       return null; /*     */     } /*     */      /*     */  /*     */  /*     */  /*     */  /* 159 */     boolean success = false; /* 160 */     ChimpManager mm = null; /* 161 */     long start = System.currentTimeMillis(); /*     */      /* 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; /*     */       } /*     */       try /*     */       { /* 172 */         Thread.sleep(1000L); /*     */       } catch (InterruptedException e) { /* 174 */         LOG.log(Level.SEVERE, "Unable to sleep", e); /*     */       } /*     */       Socket monkeySocket; /*     */       try /*     */       { /* 179 */         monkeySocket = new Socket(addr, port); /*     */       } catch (IOException e) { /* 181 */         LOG.log(Level.FINE, "Unable to connect socket", e); /* 182 */         success = false; } /* 183 */       continue; /*     */        /*     */       try /*     */       { /* 187 */         mm = new ChimpManager(monkeySocket); /*     */       } catch (IOException e) { /* 189 */         LOG.log(Level.SEVERE, "Unable to open writer and reader to socket"); } /* 190 */       continue; /*     */        /*     */       try /*     */       { /* 194 */         mm.wake(); /*     */       } catch (IOException e) { /* 196 */         LOG.log(Level.FINE, "Unable to wake up device", e); /* 197 */         success = false; } /* 198 */       continue; /*     */        /* 200 */       success = true; /*     */     } /*     */      /* 203 */     return mm; /*     */   }
这个方法比较长,但大体做的事情如下:

分析到这里我们可以看到monkey已经在目标机器起来了,那么我们就需要去分析MonkeyRunner是如何发送monkey命令过去控制设备的了。这里我们会以典型的press这个方法作为例子来进行阐述。
我们先看AdbChimpDevice里面的press方法:
/*     */   public void press(String keyName, TouchPressType type) /*     */   { /*     */     try /*     */     { /* 326 */       switch (3.$SwitchMap$com$android$chimpchat$core$TouchPressType[type.ordinal()]) { /*     */       case 1:  /* 328 */         this.manager.press(keyName); /* 329 */         break; /*     */       case 2:  /* 331 */         this.manager.keyDown(keyName); /* 332 */         break; /*     */       case 3:  /* 334 */         this.manager.keyUp(keyName); /*     */       } /*     */     } /*     */     catch (IOException e) { /* 338 */       LOG.log(Level.SEVERE, "Error sending press event: " + keyName + " " + type, e); /*     */     } /*     */   }
方法很简单,就是根据不同的按下类型来调用ChimpManager中不同的press的方法,我们这里假设用户按下的是 DOWN_AND_UP这个类型,也就是说调用的是ChimpMananer里面的press方法:
/*     */   public boolean press(String name) /*     */     throws IOException /*     */   { /* 135 */     return sendMonkeyEvent("press " + name); /*     */   }
跟着调用sendMonkeyEvent:
/*     */   private boolean sendMonkeyEvent(String command) /*     */     throws IOException /*     */   { /* 234 */     synchronized (this) { /* 235 */       String monkeyResponse = sendMonkeyEventAndGetResponse(command); /* 236 */       return parseResponseForSuccess(monkeyResponse); /*     */     } /*     */   }
跟着调用sendMonkeyEventAndGetResponse方法:
/*     */   private String sendMonkeyEventAndGetResponse(String command) /*     */     throws IOException /*     */   { /* 182 */     command = command.trim(); /* 183 */     LOG.info("Monkey Command: " + command + "."); /*     */      /*     */  /* 186 */     this.monkeyWriter.write(command + "\n"); /* 187 */     this.monkeyWriter.flush(); /* 188 */     return this.monkeyReader.readLine(); /*     */   }
以上这几个方法都是在ChimpManager这个类里面的成员方法。从最后这个sendMonkeyEventAndGetResponse方法我们可以看到它所做的事情就是用我们前面描述的monkeyWritter和monkeyReader这两个成员变量往主机pc这边的终会转发给目标机器monkey那个端口(其实就是上面的monkeySocket)进行读写操作。


3. 发送adb协议请求

请查看《谁动了我的截图?--Monkeyrunner takeSnapshot方法源码跟踪分析》

4. 发送adb shell命令


通过上一篇文章《谁动了我的截图?--Monkeyrunner takeSnapshot方法源码跟踪分析》的分析,我们知道MonkeyRunner分发不同的设备控制信息是在ChimpChat库的AdbChimpDevice这个类里面进行的。所以这里我就不会从头开始分析我们是怎么进入到这个类里面的了,大家不清楚的请先查看上一篇投石问路的文章再返回来看本文。

这里我们尝试以getSystemProperty这个稍微复杂点的方法为例子分析下MonkeyRunner是真么通过adb shell发送命令的,我们首先定位到AdbChimpDevice的该方法:

/*     */   public String getSystemProperty(String key) /*     */   { /* 224 */     return this.device.getProperty(key); /*     */   }

这里的device成员函数指的就是ddmlib库里面的Device这个类(请查看上一篇文章),那么我们进去该类看下getProperty这个方法:

/*      */   public String getProperty(String name) /*      */   { /*  379 */     return (String)this.mProperties.get(name); /*      */   }
该方法直接使用mProperties这个Device类的成员变量的get方法根据property的名字获得返回值,从定义可以看出这是个map:

/*   65 */   private final Map mProperties = new HashMap();
且这个map是在初始化Device实例之前就已经定义好的了,因为其构造函数并没有代码提及,但是我们可以看到Device类里面有一个函数专门往这个map里面添加property:

/*      */   void addProperty(String label, String value) { /*  779 */     this.mProperties.put(label, value); /*      */   }
那么这个addProperty又是在哪里被调用了呢?一番查看后发现是在ddmlib里面的GetPropertyReceiver这个类里面的processNewLines这个方法:

/*    */   public void processNewLines(String[] lines) /*    */   { /* 49 */     for (String line : lines) { /* 50 */       if ((!line.isEmpty()) && (!line.startsWith("#"))) /*    */       { /*    */  /*    */  /* 54 */         Matcher m = GETPROP_PATTERN.matcher(line); /* 55 */         if (m.matches()) { /* 56 */           String label = m.group(1); /* 57 */           String value = m.group(2); /*    */            /* 59 */           if (!label.isEmpty()) { /* 60 */             this.mDevice.addProperty(label, value); /*    */           } /*    */         } /*    */       } /*    */     } /*    */   }
给这个map增加所有property的地方是知道了,但是问题是什么时候增加呢?这里我们先卖个关子。

继续之前我们先要了解下ddmlib这个库里面的DeviceMonitor这个类,这个类会启动一个线程来监控所有连接到主机的设备的状态。

/*      */   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; /*      */   } 
线程的启动是在我们之前见过的AdbDebugBridge里面,一旦adb启动,就会去调用构造函数去初始化DeviceMonitor实例,并调用实例的上面这个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; /*      */   }
该线程会进行一个无限循环来检测设备的变动。

private void deviceMonitorLoop() /*     */   { /*     */     do /*     */     { /*     */       try /*     */       { /* 161 */         if (this.mMainAdbConnection == null) { /* 162 */           Log.d("DeviceMonitor", "Opening adb connection"); /* 163 */           this.mMainAdbConnection = openAdbConnection(); /* 164 */           if (this.mMainAdbConnection == null) { /* 165 */             this.mConnectionAttempt += 1; /* 166 */             Log.e("DeviceMonitor", "Connection attempts: " + this.mConnectionAttempt); /* 167 */             if (this.mConnectionAttempt > 10) { /* 168 */               if (!this.mServer.startAdb()) { /* 169 */                 this.mRestartAttemptCount += 1; /* 170 */                 Log.e("DeviceMonitor", "adb restart attempts: " + this.mRestartAttemptCount); /*     */               } /*     */               else { /* 173 */                 this.mRestartAttemptCount = 0; /*     */               } /*     */             } /* 176 */             waitABit(); /*     */           } else { /* 178 */             Log.d("DeviceMonitor", "Connected to adb for device monitoring"); /* 179 */             this.mConnectionAttempt = 0; /*     */           } /*     */         } /*     */          /* 183 */         if ((this.mMainAdbConnection != null) && (!this.mMonitoring)) { /* 184 */           this.mMonitoring = sendDeviceListMonitoringRequest(); /*     */         } /*     */          /* 187 */         if (this.mMonitoring) /*     */         { /* 189 */           int length = readLength(this.mMainAdbConnection, this.mLengthBuffer); /*     */            /* 191 */           if (length >= 0) /*     */           { /* 193 */             processIncomingDeviceData(length); /*     */              /*     */  /* 196 */             this.mInitialDeviceListDone = true; /*     */           } /*     */         } /*     */       } /*     */       catch (AsynchronousCloseException ace) {}catch (TimeoutException ioe) /*     */       { /* 202 */         handleExpectionInMonitorLoop(ioe); /*     */       } catch (IOException ioe) { /* 204 */         handleExpectionInMonitorLoop(ioe); /*     */       } /* 206 */     } while (!this.mQuit); /*     */   } 
一旦发现设备有变动,该循环会立刻调用processIncomingDeviceData这个方法来更新设备信息

/*     */   private void processIncomingDeviceData(int length) throws IOException /*     */   { /* 298 */     ArrayList list = new ArrayList(); /*     */      /* 300 */     if (length > 0) { /* 301 */       byte[] buffer = new byte[length]; /* 302 */       String result = read(this.mMainAdbConnection, buffer); /*     */        /* 304 */       String[] devices = result.split("\n"); /*     */        /* 306 */       for (String d : devices) { /* 307 */         String[] param = d.split("\t"); /* 308 */         if (param.length == 2) /*     */         { /* 310 */           Device device = new Device(this, param[0], IDevice.DeviceState.getState(param[1])); /*     */            /*     */  /*     */  /* 314 */           list.add(device); /*     */         } /*     */       } /*     */     } /*     */      /*     */  /* 320 */     updateDevices(list); /*     */   }
该方法首先会取得所有的device列表(类似"adb devices -l"命令获得所有device列表),然后调用updateDevices这个方法来对所有设备信息进行一次更新:

 private void updateDevices(ArrayList newList) /*     */   { /* 329 */     synchronized () /*     */     { /*     */  /*     */  /* 333 */       ArrayList devicesToQuery = new ArrayList(); /* 334 */       synchronized (this.mDevices) /*     */       { /*     */  /*     */  /*     */  /*     */  /*     */  /*     */  /*     */  /*     */  /* 344 */         for (int d = 0; d < this.mDevices.size();) { /* 345 */           Device device = (Device)this.mDevices.get(d); /*     */            /*     */  /* 348 */           int count = newList.size(); /* 349 */           boolean foundMatch = false; /* 350 */           for (int dd = 0; dd < count; dd++) { /* 351 */             Device newDevice = (Device)newList.get(dd); /*     */              /* 353 */             if (newDevice.getSerialNumber().equals(device.getSerialNumber())) { /* 354 */               foundMatch = true; /*     */                /*     */  /* 357 */               if (device.getState() != newDevice.getState()) { /* 358 */                 device.setState(newDevice.getState()); /* 359 */                 device.update(1); /*     */                  /*     */  /*     */  /* 363 */                 if (device.isOnline()) { /* 364 */                   if ((AndroidDebugBridge.getClientSupport()) &&  /* 365 */                     (!startMonitoringDevice(device))) { /* 366 */                     Log.e("DeviceMonitor", "Failed to start monitoring " + device.getSerialNumber()); /*     */                   } /*     */                    /*     */  /*     */  /*     */  /* 372 */                   if (device.getPropertyCount() == 0) { /* 373 */                     devicesToQuery.add(device); /*     */                   } /*     */                 } /*     */               } /*     */                /*     */  /* 379 */               newList.remove(dd); /* 380 */               break; /*     */             } /*     */           } /*     */            /* 384 */           if (!foundMatch) /*     */           { /*     */  /* 387 */             removeDevice(device); /* 388 */             this.mServer.deviceDisconnected(device); /*     */           } /*     */           else { /* 391 */             d++; /*     */           } /*     */         } /*     */          /*     */  /*     */  /* 397 */         for (Device newDevice : newList) /*     */         { /* 399 */           this.mDevices.add(newDevice); /* 400 */           this.mServer.deviceConnected(newDevice); /*     */            /*     */  /* 403 */           if ((AndroidDebugBridge.getClientSupport()) &&  /* 404 */             (newDevice.isOnline())) { /* 405 */             startMonitoringDevice(newDevice); /*     */           } /*     */            /*     */  /*     */  /* 410 */           if (newDevice.isOnline()) { /* 411 */             devicesToQuery.add(newDevice); /*     */           } /*     */         } /*     */       } /*     */        /*     */  /* 417 */       for (Device d : devicesToQuery) { /* 418 */         queryNewDeviceForInfo(d); /*     */       } /*     */     } /* 421 */     newList.clear(); /*     */   } 
该方法我们关注的是最后面它会循环每个设备,然后调用queryNewDeviceForInfo这个方法去更新每个设备所有的porperty信息。

/*     */   private void queryNewDeviceForInfo(Device device) /*     */   { /*     */     try /*     */     { /* 446 */       device.executeShellCommand("getprop", new GetPropReceiver(device)); /*     */        /*     */  /* 449 */       queryNewDeviceForMountingPoint(device, "EXTERNAL_STORAGE"); /* 450 */       queryNewDeviceForMountingPoint(device, "ANDROID_DATA"); /* 451 */       queryNewDeviceForMountingPoint(device, "ANDROID_ROOT"); /*     */        /*     */  /* 454 */       if (device.isEmulator()) { /* 455 */         EmulatorConsole console = EmulatorConsole.getConsole(device); /* 456 */         if (console != null) { /* 457 */           device.setAvdName(console.getAvdName()); /* 458 */           console.close(); /*     */         } /*     */       } /*     */     } catch (TimeoutException e) { /* 462 */       Log.w("DeviceMonitor", String.format("Connection timeout getting info for device %s", new Object[] { device.getSerialNumber() })); /*     */  /*     */     } /*     */     catch (AdbCommandRejectedException e) /*     */     { /* 467 */       Log.w("DeviceMonitor", String.format("Adb rejected command to get  device %1$s info: %2$s", new Object[] { device.getSerialNumber(), e.getMessage() })); /*     */  /*     */     } /*     */     catch (ShellCommandUnresponsiveException e) /*     */     { /* 472 */       Log.w("DeviceMonitor", String.format("Adb shell command took too long returning info for device %s", new Object[] { device.getSerialNumber() })); /*     */  /*     */     } /*     */     catch (IOException e) /*     */     { /* 477 */       Log.w("DeviceMonitor", String.format("IO Error getting info for device %s", new Object[] { device.getSerialNumber() })); /*     */     } /*     */   }
到了这里我们终于看到了该方法调用了一个ddmlib库的device类里面的executeShellCommand方法来执行‘getprop'这个命令。到目前位置我们达到的目的是知道了getSystemProperty这个MonkeyDevice的api最终确实是通过发送'adb shell getporp‘命令来获得设备属性的。

但这里遗留了两个问题

各位看官不用着急,且看我们往下分析,很快就会水落石出了。我们继续跟踪executeShellCommand这个方法,在我们的例子中其以命令'getprop'和new的GetPropertyReceiver对象实例为参数,最终会调用到Device这个类里面的executeShellCommand这个方法。注意这个GetPropertyReceiver很重要,我们往后会看到。

/*      */   public void executeShellCommand(String command, IShellOutputReceiver receiver, int maxTimeToOutputResponse) /*      */     throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException /*      */   { /*  618 */     AdbHelper.executeRemoteCommand(AndroidDebugBridge.getSocketAddress(), command, this, receiver, maxTimeToOutputResponse); /*      */   }
方法中继续把调用直接抛给AdbHelper这个工具类,

/*     */   static void executeRemoteCommand(InetSocketAddress adbSockAddr, String command, IDevice device, IShellOutputReceiver rcvr, long maxTimeToOutputResponse, TimeUnit maxTimeUnits) /*     */     throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException /*     */   { /* 378 */     long maxTimeToOutputMs = 0L; /* 379 */     if (maxTimeToOutputResponse > 0L) { /* 380 */       if (maxTimeUnits == null) { /* 381 */         throw new NullPointerException("Time unit must not be null for non-zero max."); /*     */       } /* 383 */       maxTimeToOutputMs = maxTimeUnits.toMillis(maxTimeToOutputResponse); /*     */     } /*     */      /* 386 */     Log.v("ddms", "execute: running " + command); /*     */      /* 388 */     SocketChannel adbChan = null; /*     */     try { /* 390 */       adbChan = SocketChannel.open(adbSockAddr); /* 391 */       adbChan.configureBlocking(false); /*     */        /*     */  /*     */  /*     */  /* 396 */       setDevice(adbChan, device); /*     */        /* 398 */       byte[] request = formAdbRequest("shell:" + command); /* 399 */       write(adbChan, request); /*     */        /* 401 */       AdbResponse resp = readAdbResponse(adbChan, false); /* 402 */       if (!resp.okay) { /* 403 */         Log.e("ddms", "ADB rejected shell command (" + command + "): " + resp.message); /* 404 */         throw new AdbCommandRejectedException(resp.message); /*     */       } /*     */        /* 407 */       byte[] data = new byte['            
            
                    
文章标题:MonkeyRunner源码分析之与Android设备通讯方式
文章出自:http://www.tsicrk.com/article/ggoceo.html

其他资讯

让你的专属顾问为你服务

0.7699s