正如前面《谁动了我的截图?--Monkeyrunner
takeSnapshot方法源代码跟踪分析
》所述,本文主要会尝试描写叙述android的自己主动化測试框架MonkeyRunner到底是怎样和目标设备进行通信的。

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

1.概述

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

  • 发送monkey命令:MonkeyRunner先通过adb shell发送命令"monkey -port  12345"在目标机器上启动monkey以监听port接受连接,然后MonkeyRunner通过连接该port建立socket并发送monkey命令。全部与界面相关的操作都是通过这样的方式发送到目标机器的。

  • 发送adb协议请求:通过发送adb协议请求来与目标设备通信的。详情请查看<<谁动了我的截图?--Monkeyrunner
    takeSnapshot方法源代码跟踪分析
    >>和<<adb概览及协议參考>>。事实上adb命令行client的全部命令终于都是通过发送遵循adb协议的请求来实现的,仅仅是做成命令行方式方便终端用户使用而已
  • 发送adb shell命令:模拟adb命令行工具发送adb shell命令,仅仅是不是真正的直接命令行调用adb工具。而是在每个命令运行之前先通过上面的“发送adb协议请求“发送“shell:”请求建立一个和adbserver通信的adb shell的socket连接通道,adbserver再和目标设备的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"和port是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端本地的port转发给目标机器端的monkey监听port。这样子做的优点是我们通过直接连接主机pc端的转发port发送命令就会等同于通过网络连接上目标机器的monkey监听port来发送monkey命令
  • 调用executeAsyncCommand方法发送异步adb shell命令 “monkey -port"到目标机器开启monkey并监听以上描写叙述的port
  • 创建连接到主机pc相应目标设备monkey监听port的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);
/* */ }
/* */ }

方法非常easy,就是依据不同的按下类型来调用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那个port(事实上就是上面的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");
/* */ }
/* */ }

方法中先创建一个面向adbserver的socket通道。然后通过发送adb协议请求的'shell:'命令获得一个adb shell然后再把对应的adb shell命令发送到该socket。从这里能够看到。“发送adb shell命令“事实上是基于”发送adb协议请求“的,由于在发送命令之前须要先通过组织基于adb协议的请求”shell:“来获得adb shell。对照上一篇文章《谁动了我的截图?--Monkeyrunner
takeSnapshot方法源代码跟踪分析
》我们能够看到“发送adb协议请求”跟“发送adb shell命名”的最大差别就是:

  • 发送adb协议请求:不须要初始化adb shell,直接通过构造基于adb协议的请求把命令发送出去给adbserver。
  • 发送adb shell命令:每一个命令都须要先发送“adb协议请求”的“shell:”来先建立一个adb shell,然后才可以发送命令到adbserver,再由adbserver转发到设备端的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

扫描码:

http://blog.csdn.net/zhubaitian



版权声明:本文博客原创文章。博客,未经同意,不得转载。

MonkeyRunner源代码分析Android通信设备的更多相关文章

  1. MonkeyRunner源代码分析之启动

    在工作中由于要追求完毕目标的效率,所以很多其它是强调实战.注重招式.关注怎么去用各种框架来实现目的.可是假设一味仅仅是注重招式.缺少对原理这个内功的了解,相信自己非常难对各种框架有更深入的理解. 从几 ...

  2. 结合源代码分析android的消息机制

    描写叙述 结合几个问题去看源代码. 1.Handler, MessageQueue, Message, Looper, LocalThread这5者在android的消息传递过程中扮演了什么样的角色? ...

  3. android源代码分析 android toast使用具体解释 toast自己定义

    在安卓开发过程中.toast使我们常常使用的一个类.当我们须要向用户传达一些信息,可是不须要和用户交互时,该方式就是一种十分恰当的途径. 我们习惯了这样使用toast:Toast.makeText(C ...

  4. Android KLog源代码分析

    Android KLog源代码分析 Android KLog源代码分析 代码结构 详细分析 BaseLog FileLog JsonLog XmlLog 核心文件KLogjava分析 遇到的问题 一直 ...

  5. 从Handler+Message+Looper源代码带你分析Android系统的消息处理机制

    PS一句:不得不说CSDN同步做的非常烂.还得我花了近1个小时恢复这篇博客. 引言 [转载请注明出处:http://blog.csdn.net/feiduclear_up CSDN 废墟的树] 作为A ...

  6. Monkey源代码分析之执行流程

    在<MonkeyRunner源代码分析之与Android设备通讯方式>中.我们谈及到MonkeyRunner控制目标android设备有多种方法.当中之中的一个就是在目标机器启动一个mon ...

  7. android-plugmgr源代码分析

    android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束.关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好.在这篇文章中,我 ...

  8. Android 中View的绘制机制源代码分析 三

    到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程.只是在学习layout过程之前.大家有没有发现我换了编辑器,哈哈.最终下定决心从Html编辑器切换为markdown编 ...

  9. Android系统进程Zygote启动过程的源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6768304 在Android系统中,所有的应用 ...

随机推荐

  1. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

  2. hdu3001(状压dp)

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=3001 题意:n 个城市已经 m 条路 以及对应路费 c,要求遍历所有城市最少的路费,每个城市不能超过2 ...

  3. EF 分离实体

    具体步骤: 新建测试项目:如图 其中Respository和Model层均为类库项目, 1.在Respository层添加ADO.NET实体数据模型, 2.复制Model.tt文件到Model层,这是 ...

  4. java 通过sftp服务器上传下载删除文件

    最近做了一个sftp服务器文件下载的功能,mark一下: 首先是一个SftpClientUtil 类,封装了对sftp服务器文件上传.下载.删除的方法 import java.io.File; imp ...

  5. 搭建ganglia集群而且监视hadoop CDH4.6

    前言 近期在研究云监控的相关工具,感觉ganglia颇有亮点,能从一个集群总体的角度来展现数据. 但是安装过程稍过复杂,相关依赖稍多,故写此文章与大家分享下. 本文不解说相关原理,若想了解请參考其它资 ...

  6. linux下使用vi操作

    ESC : 进入命令模式 linux下使用vi后,怎样跳转到文件结尾 pagedown键连续按 虽然我也这么用,但还是太笨了.问了高手,方法是按shift+g,另外,到文件开头是gg.   linux ...

  7. 谁的用户在世界上是&#160;&#160;明基决心保时捷设计标准

        谈到保时捷.相信非常多人都非常了解,世界名车啊,仅仅有高富帅才玩儿得起.只是,假设由保时捷的设计师来设计一款显示器,水准一流.质地厚道,且价格亲民,你怎么看?     如近期京东上热销的明基G ...

  8. hadoop的一些名词解释

    在网上收集了一些mapreduce中常用的一些名词的解释,分享一下: Shuffle(洗牌):当第一个map任务完成后,节点可能还要继续执行更多的map 任务,但这时候也开始把map任务的中间输出交换 ...

  9. Java进阶 创建和销毁对象

    最近准备写点Javase的东西,希望可以帮助大家写出更好的代码. 1.给不可实例化的类提供私有构造器 比如:每个项目中都有很多工具类,提供了很多static类型的方法供大家使用,谁也不希望看到下面的代 ...

  10. Java 过滤器的作用

    Servlet API 非常久曾经就已成为企业应用开发的基石,而 Servlet 过滤器则是对 J2EE 家族的相对较新的补充.在 J2EE 探索者 系列文章的最后一篇中,作者 Kyle Gabhar ...