MonkeyRunner源代码分析Android通信设备
正如前面《谁动了我的截图?--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 |
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 |
分析之前请大家准备好相应的几个库的源代码:
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进行实例化
这里我们会以典型的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守护进程或者服务。
- /* */ 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 |
天地会珠海分舵 |
服务号:TechGoGoGo 扫描码: |
http://blog.csdn.net/zhubaitian |
版权声明:本文博客原创文章。博客,未经同意,不得转载。
MonkeyRunner源代码分析Android通信设备的更多相关文章
- MonkeyRunner源代码分析之启动
在工作中由于要追求完毕目标的效率,所以很多其它是强调实战.注重招式.关注怎么去用各种框架来实现目的.可是假设一味仅仅是注重招式.缺少对原理这个内功的了解,相信自己非常难对各种框架有更深入的理解. 从几 ...
- 结合源代码分析android的消息机制
描写叙述 结合几个问题去看源代码. 1.Handler, MessageQueue, Message, Looper, LocalThread这5者在android的消息传递过程中扮演了什么样的角色? ...
- android源代码分析 android toast使用具体解释 toast自己定义
在安卓开发过程中.toast使我们常常使用的一个类.当我们须要向用户传达一些信息,可是不须要和用户交互时,该方式就是一种十分恰当的途径. 我们习惯了这样使用toast:Toast.makeText(C ...
- Android KLog源代码分析
Android KLog源代码分析 Android KLog源代码分析 代码结构 详细分析 BaseLog FileLog JsonLog XmlLog 核心文件KLogjava分析 遇到的问题 一直 ...
- 从Handler+Message+Looper源代码带你分析Android系统的消息处理机制
PS一句:不得不说CSDN同步做的非常烂.还得我花了近1个小时恢复这篇博客. 引言 [转载请注明出处:http://blog.csdn.net/feiduclear_up CSDN 废墟的树] 作为A ...
- Monkey源代码分析之执行流程
在<MonkeyRunner源代码分析之与Android设备通讯方式>中.我们谈及到MonkeyRunner控制目标android设备有多种方法.当中之中的一个就是在目标机器启动一个mon ...
- android-plugmgr源代码分析
android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束.关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好.在这篇文章中,我 ...
- Android 中View的绘制机制源代码分析 三
到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程.只是在学习layout过程之前.大家有没有发现我换了编辑器,哈哈.最终下定决心从Html编辑器切换为markdown编 ...
- Android系统进程Zygote启动过程的源代码分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6768304 在Android系统中,所有的应用 ...
随机推荐
- 高效搭建Storm全然分布式集群
环境说明 1.硬件说明 使用三台PC机,角色分配例如以下 2.软件说明 约定全部软件都放在/usr/local/路径下 准备工作 1.安装jdk 2.配置SSH Storm集群安装 安装流程图 1.安 ...
- VMware vSphere 服务器虚拟化之二十七桌面虚拟化之View中使用Thinapp软件虚拟化
VMware vSphere 服务器虚拟化之二十七桌面虚拟化之View中使用Thinapp软件虚拟化 VMware ThinApp 应用程序虚拟化软件是无代理解决方案,通过将应用程序隔离并封装为EXE ...
- poj3176--Cow Bowling(dp:数塔问题)
Cow Bowling Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 14028 Accepted: 9302 Desc ...
- Nginx将请求分发到各web应用
介绍了VMWare12虚拟机.Linux(CentOS7)系统安装.部署Nginx1.6.3代理服务做负载均衡.接下来介绍通过Nginx将请求分发到各web应用处理服务. 一.Web应用开发 1.as ...
- maven 项目中使用 jstl标签
在pom.xml文件下面增加如下的依赖包: <dependency> <groupId>javax.servlet</groupId> <artifactId ...
- linux解压多个文件
方法很多种, 根据实际文件类型,位置情况进行变通: 1. for查询:for tar in *.tar.gz; do tar xvf $tar; done2. 列出文件列表,然后xargs 逐一解压: ...
- EJB体系结构
为了适应企业的快速发展.缩短企业信息系统的设计和开发周期.降低构建信息系统的成本,Sun公司制订了Java2 SDK Enterprise Edition(J2EE)规范,定义基于组件的方式设计.开发 ...
- HTML CSS——background的认识(一)
今天回归bug时无意间看到了样式表中background属性,如今总结一下: 1.background-color:设置元素的背景色.其值能够为:color-name.color-rgb.color- ...
- Windows Phone开发(14):数据模板
原文:Windows Phone开发(14):数据模板 数据模板,如果你仅仅听到这个名词,你一定很迷惑,什么来的?用来干什么的?不急,亲,今天,我们一起来探索一下吧. 用白话文说,数据模板就是用来规范 ...
- 从零開始学android<数据存储(1)SharedPreferences属性文件.三十五.>
在android中有五种保存数据的方法.各自是: Shared Preferences Store private primitive data in key-value pairs. 相应属性的键值 ...