安卓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协议请求

 

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的了。迄今为止我们算是把以上留下了两个疑问给解决完了
 
作者自主博客微信服务号及扫描码CSDN
天地会珠海分舵http://techgogogo.com

服务号:TechGoGoGo扫描码:

qrcode_for_gh_0388b3c825f5_430

http://blog.csdn.net/zhubaitian

 

转载于:https://www.cnblogs.com/techgogogo/p/4284817.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值