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

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

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

1.概述

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

  • 发送monkey命令:MonkeyRunner先通过adb shell发送命令"monkey -port  12345"在目标机器上启动monkey以监听端口接受连接,然后MonkeyRunner通过连接该端口建立socket并发送monkey命令。所有与界面相关的操作都是通过这种方式发送到目标机器的。
  • 发送adb协议请求:通过发送adb协议请求来与目标设备通信的,详情请查看<<谁动了我的截图?--Monkeyrunner
    takeSnapshot方法源码跟踪分析
    >>和<<adb概览及协议参考>>,其实adb命令行客户端的所有命令最终都是通过发送遵循adb协议的请求来实现的,只是做成命令行方式方便终端用户使用而已
  • 发送adb shell命令:模拟adb命令行工具发送adb shell命令,只是不是真正的直接命令行调用adb工具,而是在每一个命令执行之前先通过上面的“发送adb协议请求“发送“shell:”请求建立一个和adb服务器通信的adb shell的socket连接通道,adb服务器再和目标设备的adb守护进程进行通信

以下是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’

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

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()));
/*     */   }

初始化所做的事情如下

  • 把构造函数传进来的monkeySocket这个socket对象保存起来,往下会分析这个socket是如何创立的
  • 初始化monkeyWriter这个BufferedWriter,今后往monkey的socket发送命令的时候用的就是它
  • 初始化monkeyReader这个BufferedReader,今后从monkey的socket读返回的时候用的就是它

好,那么现在我们返回来看这个类是什么时候实例化的。请定位到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.在继续之前这里我们先整理下思路,结合上一篇文章,我们看到几个重要的类的初始化流程是这样的:

  • MonkeyRunner在启动的时候会先启动MonkeyRunnerStarter这个类,该类的构造函数调用ChimpChat的getInstance方法实例化ChimpChat.
  • ChimpChat的getInstance方法会先实例化AdbBackend这个类,然后构建 ChimpChat自身这个实例
  • 用户调用MonkeyRunner.waitForConnection()方法初始化MonkeyDevice
  • 以上的waitForConnection()又调用的是ChimpChat的waitforConnection()方法
  • ChimpChat的waitForConnection方法调用的是AdbBackend的waitForConnection方法最终会findAttachedDevice找到目标设备然后用该设备初始化AdbChimpDevice

根据以上的流程我们就很清晰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;
/*     */   }

这个方法比较长,但大体做的事情如下:

  • 通过调用ddmlib的device类里面的createForward方法来把主机pc端本地的端口转发给目标机器端的monkey监听端口,这样子做的好处是我们通过直接连接主机pc端的转发端口发送命令就会等同于通过网络连接上目标机器的monkey监听端口来发送monkey命令
  • 调用executeAsyncCommand方法发送异步adb shell命令 “monkey -port"到目标机器开启monkey并监听以上描述的端口
  • 创建连接到主机pc对应目标设备monkey监听端口的monkeySocket
  • 把该monkeySocket传递到本章节开头说的ChimpManager构造函数对ChimpManager进行实例化

分析到这里我们可以看到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<String, String> 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<Device> 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<Device> newList)
/*     */   {
/* 329 */     synchronized ()
/*     */     {
/*     */
/*     */
/* 333 */       ArrayList<Device> 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‘命令来获得设备属性的。

但这里遗留了两个问题

  • 一个是之前提到的GetPropertyReceiver这个类里面的增加property的processNewLines方法是在哪里调用的
  • 一个是executeShellCommand究竟是怎么工作的

各位看官不用着急,且看我们往下分析,很快就会水落石出了。我们继续跟踪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['?'];
/* 408 */       ByteBuffer buf = ByteBuffer.wrap(data);
/* 409 */       long timeToResponseCount = 0L;
/*     */
/*     */       for (;;)
/*     */       {
/* 413 */         if ((rcvr != null) && (rcvr.isCancelled())) {
/* 414 */           Log.v("ddms", "execute: cancelled");
/* 415 */           break;
/*     */         }
/*     */
/* 418 */         int count = adbChan.read(buf);
/* 419 */         if (count < 0)
/*     */         {
/* 421 */           rcvr.flush();
/* 422 */           Log.v("ddms", "execute '" + command + "' on '" + device + "' : EOF hit. Read: " + count);
/*     */
/* 424 */           break; }
/* 425 */         if (count == 0) {
/*     */           try {
/* 427 */             int wait = 25;
/* 428 */             timeToResponseCount += wait;
/* 429 */             if ((maxTimeToOutputMs > 0L) && (timeToResponseCount > maxTimeToOutputMs)) {
/* 430 */               throw new ShellCommandUnresponsiveException();
/*     */             }
/* 432 */             Thread.sleep(wait);
/*     */           }
/*     */           catch (InterruptedException ie) {}
/*     */         }
/*     */         else {
/* 437 */           timeToResponseCount = 0L;
/*     */
/*     */
/* 440 */           if (rcvr != null) {
/* 441 */             rcvr.addOutput(buf.array(), buf.arrayOffset(), buf.position());
/*     */           }
/* 443 */           buf.rewind();
/*     */         }
/*     */       }
/*     */     } finally {
/* 447 */       if (adbChan != null) {
/* 448 */         adbChan.close();
/*     */       }
/* 450 */       Log.v("ddms", "execute: returning");
/*     */     }
/*     */   }

方法中先创建一个面向adb服务器的socket通道,然后通过发送adb协议请求的‘shell:‘命令获得一个adb shell然后再把相应的adb shell命令发送到该socket。从这里可以看到,“发送adb shell命令“其实是基于”发送adb协议请求“的,因为在发送命令之前需要先通过组织基于adb协议的请求”shell:“来获得adb shell。对比上一篇文章《谁动了我的截图?--Monkeyrunner
takeSnapshot方法源码跟踪分析
》我们可以看到“发送adb协议请求”跟“发送adb shell命名”的最大区别就是:

  • 发送adb协议请求:不需要初始化adb shell,直接通过构造基于adb协议的请求把命令发送出去给adb服务器。
  • 发送adb shell命令:每个命令都需要先发送“adb协议请求”的“shell:”来先建立一个adb shell,然后才能够发送命令到adb服务器,再由adb服务器转发到设备端的adb守护进程或者服务。

发送完请求后最终会调用rcvr.addOutput(buf.array(),buf.arrayOffset(), buf.position())这个方法,这里的rcvr就是通过参数传进来的我们上面提到的很重要的那个GetPropertyReceiver,那么我们去看下该类下面的addOutput究竟是怎么处理返回信息的,这里要查看的是GetPropertyReceiver父类MultiLineReceiver类的成员函数addOutPut:

/*     */   public final void addOutput(byte[] data, int offset, int length)
/*     */   {
/*  53 */     if (!isCancelled()) {
/*  54 */       String s = new String(data, offset, length, Charsets.UTF_8);
/*     */
/*     */
/*     */
/*  58 */       if (this.mUnfinishedLine != null) {
/*  59 */         s = this.mUnfinishedLine + s;
/*  60 */         this.mUnfinishedLine = null;
/*     */       }
/*     */
/*     */
/*  64 */       this.mArray.clear();
/*  65 */       int start = 0;
/*     */       for (;;) {
/*  67 */         int index = s.indexOf("\r\n", start);
/*     */
/*     */
/*     */
/*  71 */         if (index == -1) {
/*  72 */           this.mUnfinishedLine = s.substring(start);
/*  73 */           break;
/*     */         }
/*     */
/*     */
/*     */
/*  78 */         String line = s.substring(start, index);
/*  79 */         if (this.mTrimLines) {
/*  80 */           line = line.trim();
/*     */         }
/*  82 */         this.mArray.add(line);
/*     */
/*     */
/*  85 */         start = index + 2;
/*     */       }
/*     */
/*  88 */       if (!this.mArray.isEmpty())
/*     */       {
/*     */
/*  91 */         String[] lines = (String[])this.mArray.toArray(new String[this.mArray.size()]);
/*     */
/*     */
/*  94 */         processNewLines(lines);
/*     */       }
/*     */     }
/*     */   }

这个函数所作的事情就是把‘adb shell getprop‘返回的所有信息一行一行的进行处理,注意最终处理的函数就是processNewLines。还记得这个函数吧?这个就是我们上面提到的GetPropertyReceiver这个类中用来往mProperties这个map增加property的了。迄今为止我们算是把以上留下了两个疑问给解决完了

时间: 2025-01-16 10:52:33

MonkeyRunner和Android设备通讯方式源码分析的相关文章

放大招了!基于Bmob的Android即时通讯应用源码 (一)

今天给大家分享系列干货:基于Bmob的Android即时通讯应用源码.目前Android端已更新到V1.1.1版本.IOS端已推出V1.0.1版本. 以后将持续更新,分享源码!! 脚的好就多多回复 一.已实现功能: 1.支持好友管理功能,包括添加好友.删除好友.获取好友列表,也可以与你已有的用户系统完全解耦: 2.支持的消息类型:纯文本.聊天表情.图片.位置.语音: 3.支持会话的本地化存储: 4.自定义消息处理机制: 5.同一账号多处登陆强制下线: 6.支持消息回执发送:已发送.已阅读两种状态

放大招了!基于Bmob的Android即时通讯应用源码[升级版]

此帖为  放大招了!基于Bmob的Android即时通讯应用源码 (一) 续篇,主要是更新的功能. 一.更新更能: 1.新增支持陌生人聊天,允许发送给非好友用户: 2.新增支持自定义消息发送,便于开发者扩展: 3.新版本大幅度增加稳定性和及时性,保证百分百到达: 4.排除web后端推送对客户端的影响. 二.更新讲解: 1.如果你希望能和所有人(不局限于好友关系)进行聊天,那么这一版本就解决了这个问题. 如果你想看到效果,可以点击附近的人,你找到一个非好友用户,再查看他的资料,这一页面下方有个"发

Android 上千实例源码分析以及开源分析

Android 上千实例源码分析以及开源分析(百度云分享) 要下载的直接翻到最后吧,项目实例有点多. 首先 介绍几本书籍(下载包中)吧. 01_Android系统概述 02_Android系统的开发综述 03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 06_Android的GUI系统 07_Android的Audio系统 08_Android的Video 输入输出系统 09_Android的多媒体系统 10_

Android消息处理机制(源码分析)

前言 虽然一直在做应用层开发,但是我们组是核心系统BSP,了解底层了解Android的运行机制还是很有必要的.就应用程序而言,Android系统中的Java应用程序和其他系统上相同,都是靠消息驱动来工作的,它们大致的工作原理如下: 1. 有一个消息队列,可以往这个消息队列中投递消息. 2. 有一个消息循环,不断从消息队列中取出消息,然后处理 . 为了更深入的理解Android的消息处理机制,这几天空闲时间,我结合<深入理解Android系统>看了Handler.Looper.Message这几

深度理解Android InstantRun原理以及源码分析

深度理解Android InstantRun原理以及源码分析 @Author 莫川 Instant Run官方介绍 简单介绍一下Instant Run,它是Android Studio2.0以后新增的一个运行机制,能够显著减少你第二次及以后的构建和部署时间.简单通俗的解释就是,当你在Android Studio中改了你的代码,Instant Run可以很快的让你看到你修改的效果.而在没有Instant Run之前,你的一个小小的修改,都肯能需要几十秒甚至更长的等待才能看到修改后的效果. 传统的代

Android异步消息传递机制源码分析&amp;&amp;相关知识常被问的面试题

1.Android异步消息传递机制有以下两个方式:(异步消息传递来解决线程通信问题) handler 和 AsyncTask 2.handler官方解释的用途: 1).定时任务:通过handler.postDelay(Runnable r, time)来在指定时间执行msg. 2).线程间通信:在执行较为耗时操作的时候,在子线程中执行耗时任务,然后handler(主线程的)把执行的结果通过sendmessage的方式发送给UI线程去执行用于更新UI. 3.handler源码分析 一.在Activ

Android之Volley框架源码分析

临近毕业,各种事情各种忙.我也没有认真专注写过博客,最近仔细看了Volley框架的使用及其源码,思前想后,想挑战一下自己,还是写一篇博客来分享,如有错误,欢迎吐槽. Volley简介 网络请求是一个App很重要的一部分,android系统只是提供了一个平台,而android应用则是基于这个平台上进行展示数据,起到与用户进行交互的作用,数据来源于服务端,而二者之间必须通过互联网进行传输数据,在Android系统发布初期,很多开发者都是在Apache协会的Http协议的基础上进行网络请求方法的封装,

Android 热修复 Tinker 源码分析之DexDiff / DexPatch

在上一篇文章中,我们介绍了Android 热修复 Tinker接入及源码浅析,里面包含了热修的一些背景知识,从tinker对dex文件的处理来看,源码大体上可以分为3部分阅读: 在应用中对patch的合并与加载,已经在上篇文章中详细介绍过了Android 热修复 Tinker接入及源码浅析 详细的dex patch,dex diff算法 tinker gradle plugin相关知识 tinker有个非常大的亮点就是自研发了一套dex diff.patch相关算法.本篇文章主要目的就是分析该算

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

如前文<谁动了我的截图?--Monkeyrunner takeSnapshot方法源码跟踪分析>所述,本文主要会尝试描述android的自动化测试框架MonkeyRunner究竟是如何和目标设备进行通信的. 在上一篇文章中我们其实已经描述了其中一个方法,就是通过adb协议发送adb服务器请求的方式驱动android设备的adbd守护进程去获取FrameBuffer的数据生成屏幕截图.那么MonkeyRunner还会用其他方式和目标设备进行通信吗?答案是肯定的,且看我们一步步分析道来. 1.概述