这一部分的代码逻辑关系是这样的:

  • 344行: 一个外部循环每次从上次保存下来的设备列表获得一个设备Device实例
  • 350行: 再在一个内部循环从最新的设备列表中获得一个设备Device实例
  • 353行:然后分别比较两个设备的序列号是否相等,相等则代表这个设备没有被移除。
  • 357行: 如果设备没有被移除的话,那么判断这个设备是否是状态变化了?
  • 358-373行: 变化了就需要把设备的状态改变过来
    • 363-368行: 特别是在设备变成offline变成online状态后,需要去对该设备里面的可调试进程进行监控
    • 372-373行: 并把该设备标识成还没有获取基本信息的状态,因为每个已经连接的online设备都应该保存有基本的build等信息
  • 379行: 如果分析完一个设备发现只是状态变化了的话,最后把它从新设备列表中删除掉,因为已经分析过了,下面不需要再使用它了
  • 384-388行: 如果设备已经被移除了的话(在新设备列表里面通过序列号找不到了), 那么需要把该设备移除出监控范围

以上代码是设备被移除和设备状态有更新时候的处理,那么新设备该怎么处理呢?毕竟一开始设备都是新的,这个才是关键点。

326     private void updateDevices(ArrayList<Device> newList) {

...

395                 // at this point we should still have some new devices in newList, so we

396                 // process them.

397                 for (Device newDevice : newList) {

398                     // add them to the list

399                     mDevices.add(newDevice);

400                     mServer.deviceConnected(newDevice);

401

402                     // start monitoring them.

403                     if (AndroidDebugBridge.getClientSupport()) {

404                         if (newDevice.isOnline()) {

405                             startMonitoringDevice(newDevice);

406                         }

407                     }

408

409                     // look for their build info.

410                     if (newDevice.isOnline()) {

411                         devicesToQuery.add(newDevice);

412                     }

413                 }

414             }

415

416             // query the new devices for info.

417             for (Device d : devicesToQuery) {

418                 queryNewDeviceForInfo(d);

419             }

420         }

421         newList.clear();

422     }

代码8-4-6 DeviceMonitor - updateDevices处理新增加设备

  • 397行: 对所有剩余没有处理的新设备做一个循环
  • 399行: 把该设备Device对象保留起来,下次有更新的时候需要用来跟新的设备列表做对比,正如代码5-4-5所做的事情
  • 404-405行: 开始对新设备进行监控,其实说白了就是对新设备里面的每个可调式进程的vm建立一个客户端进行监控
  • 418行: 获得新设备的基本信息,这个我们最后来分析

这里我们先重点看405行startMonitoringDevice:

509     private boolean startMonitoringDevice(Device device) {

510         SocketChannel socketChannel = openAdbConnection();

511

512         if (socketChannel != null) {

513             try {

514                 boolean result = sendDeviceMonitoringRequest(

socketChannel, device);

515                 if (result) {

516

517                     if (mSelector == null) {

518                         startDeviceMonitorThread();

519                     }

520

521                     device.setClientMonitoringSocket(socketChannel);

522

523                     synchronized (mDevices) {

524                         // always wakeup before doing the register. The synchronized block

525                         // ensure that the selector won't select() before the end of this block.

526                         // @see deviceClientMonitorLoop

527                         mSelector.wakeup();

528

529                         socketChannel.configureBlocking(false);

530                         socketChannel.register(mSelector, SelectionKey.OP_READ, device);

531                     }

532

533                     return true;

534                 }

535             }

... //省略错误处理代码

}

代码8-4-7 DeviceMonitor - startMonitoringDevice

514行首先给ADB服务器发送监听请求获得所有可调试的应用进程PID列表:

674     private boolean sendDeviceMonitoringRequest(SocketChannel socket, Device device)

675             throws TimeoutException, AdbCommandRejectedException, IOException {

676

677         try {

678             AdbHelper.setDevice(socket, device);

679

680             byte[] request = AdbHelper.formAdbRequest("track-jdwp"); //$NON-NLS-1$

681

682             AdbHelper.write(socket, request);

683

684             AdbResponse resp = AdbHelper.readAdbResponse(socket, false /* readDiagString */);

685

686             if (!resp.okay) {

687                 // request was refused by adb!

688                 Log.e("DeviceMonitor", "adb refused request: " + resp.message);

689             }

690

691             return resp.okay;

692         }

...//省略错误处理代码

}

代码8-4-8 DeviceMonitor - sendDeviceMonitoringRequest

其实这段代码和上面的“代码8-4-5 DeviceMonitor - sendDeviceListMonitoringRequest”是类似的,只是发送是要在678行先把连接切换到目标监控设备(AdbHelper.setDevice方法将在下一章进行想尽描述)以及最后发送的命令变成是”track-jdwp”命令而已。最终这个命令其实等同于你在命令调用ADB命令行客户端发送命令”adb jdwp”,返回来的就是所有可调式应用进程的PID,请看以下输出结果示例,其与上图8-4-1中DDMS的Devices模块打印的进程PID是一致的:

图 8-4-2 adb jdwp 命令输出

获取到设备里面运行的可调试进程PID列表后,大家应该也可以想到下一步动作就是为每一个PID,也就是为每一个进程的vm虚拟机创建一个客户端线程来通过JDWP协议监控调试了,这也就是为什么DDMS能够动态获得每个进程的动态信息的原因了。

进程VM虚拟机监控代码分析到这里在本书中应该就算差不多了,如果再往下分析的话就需要去分析DDMS更多的知识以及JDWP协议相关的东西了,毕竟这不是我们这本书的重点,所以分析到这里让大家对DDMS工作原理有个基本认知就好了,再往下分析一大堆不相关代码就有走题和凑字数的嫌疑了。

这里我们根据上面承诺的,还是要看看“代码8-4-6 DeviceMonitor - updateDevices处理新增加设备”中481行对新增加的设备是如何通过调用“queryNewDeviceForInfo”这个方法来获取基本信息的,获取的又是什么信息:

442     private void queryNewDeviceForInfo(Device device) {

443         // TODO: do this in a separate thread.

444         try {

445             // first get the list of properties.

446             device.executeShellCommand(

“getprop”,

447                     new GetPropReceiver(device));

448

449             queryNewDeviceForMountingPoint(device,

“EXTERNAL_STORAGE”);

450             queryNewDeviceForMountingPoint(device,

“ANDROID_DATA”);

451             queryNewDeviceForMountingPoint(device,

“ANDROID_ROOT”);

452

453             // now get the emulator Virtual Device name (if applicable).

454             if (device.isEmulator()) {

455                 EmulatorConsole console = EmulatorConsole.getConsole(device);

456                 if (console != null) {

457                     device.setAvdName(console.getAvdName());

458                     console.close();

459                 }

460             }

461         }

...//省略错误处理部分代码

}

代码8-4-9 DeviceMonitor - queryNewDeviceForInfo

这个方法所做的事情就是:

  • 446行: 首先通过Device类的executeShellCommand方法发送类似”adb shell getprop”的命令去获得所有支持的系统属性,这个方法最终调用的是AdbHelper类的executeShellCommand方法,它会接收一个专门用来对指定shell命令如getprop的返回值进行处理的接收类实例。AdbHelper的工作原理以及处理”adb shell getprop”返回结果的接收类GetPropReceiver我们在下一章”第7章 MonkeyDevice实现原理”中会进行详细阐述。
  • 449-451行: 获得文件系统几个重要的挂载点,相信这也是给DDMS的File Explorer功能用的

获取完系统属性后,我们就要看下新设备的文件系统的那几个挂载点是怎么获得的了,我们进入到对应方法:

483   private void queryNewDeviceForMountingPoint(final Device device, final String name)

484     throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException

485   {

486     device.executeShellCommand("echo $" + name,

new MultiLineReceiver()

487     {

488       public boolean isCancelled() {

489         return false;

490       }

491

492       public void processNewLines(String[] lines)

493       {

494         for (String line : lines) {

495           if (!line.isEmpty())

496           {

497             device.setMountingPoint(name, line);

498           }

499         }

500       }

501     });

502   }

代码8-4-10 DeviceMonitor - queryNewDeviceForMountingPoint

这个跟上面的发送getprop命令有类似的地方,只是命令换了”adb shell $name”和返回值处理类是重新实现的而已,但原理都一样。这里$name换成上面调用方法形参对应的”EXTERNAL_STORAGE”,”ANDROID_DATA”和“ANDROID_ROOT”就行了,以下就是本人通过命令行执行的效果:

图8-4-4 挂载点

最后把这个几个挂载点保存起来到Device实例的mMountpoints这个映射表里面:

67   private final Map<String, String> mMountPoints = new HashMap();

...

783   void setMountingPoint(String name, String value) {

784     this.mMountPoints.put(name, value);

785   }

代码8-4-11 Device - setMountingPoint

老李推荐: 第8章4节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-启动AndroidDebugBridge 4的更多相关文章

  1. 老李推荐:第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles

    老李推荐:第5章7节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles   poptest是国内唯一一家培养测试开 ...

  2. 老李推荐:第5章6节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 初始化事件源

    老李推荐:第5章6节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 初始化事件源   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试 ...

  3. 老李推荐:第5章3节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动脚本

    老李推荐:第5章3节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动脚本   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性 ...

  4. 老李推荐:第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用

    老李推荐:第5章5节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 获取系统服务引用   上一节我们描述了monkey的命令处理入口函数run是如何调用optionP ...

  5. 老李推荐:第5章2节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动流程概览

    老李推荐:第5章2节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动流程概览   每个应用都会有一个入口方法来供操作系统调用执行,Monkey这个应用的入口方法就 ...

  6. 老李推荐:第5章1节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 官方简介

    老李推荐:第5章1节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 官方简介   在MonkeyRunner的框架中,Monkey是作为一个服务来接受来自Monkey ...

  7. 第5章1节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 官方简介(原创)

    天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写<深入理解 MonkeyRunner>书籍“.但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在 ...

  8. 老李推荐:第14章9节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-遍历控件树查找控件

    老李推荐:第14章9节<MonkeyRunner源码剖析> HierarchyViewer实现原理-遍历控件树查找控件   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员 ...

  9. 老李推荐:第14章5节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-装备ViewServer-查询ViewServer运行状态

    老李推荐:第14章5节<MonkeyRunner源码剖析> HierarchyViewer实现原理-装备ViewServer-查询ViewServer运行状态   poptest是国内唯一 ...

  10. 老李推荐:第14章6节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-装备ViewServer-启动ViewServer

    老李推荐:第14章6节<MonkeyRunner源码剖析> HierarchyViewer实现原理-装备ViewServer-启动ViewServer   poptest是国内唯一一家培养 ...

随机推荐

  1. Loadrunner 在controller中运行socket脚本时报错:Abnormal termination, caused by mdrv process termination 的原因和解决方法

    原因: 网上给出的可能的原因大致有两个: 1.  压力负载机器的资源不足(CPU,内存) 2.  分配内存和释放内存的语句不匹配. 并给出了一些解决方案,最开始我以为是加了IP地址的原因,不断尝试增加 ...

  2. Python学习一:Python简介

    Python简介: Python是目前广泛使用的一门动态语言,类似Java,源代码必须首先由编译器转换成字节码(byte code),然后再由解释器来执行字节码.与Java不同的是,Python的编译 ...

  3. struts2接收参数的5种方法

    以下形式中最常用的是前两种 1. 使用Action的属性: 在action 里面定义要接收的参数,并提供相应的setter,getter,和提交参数的名称一致, 并不用做数据类型的转换相应提交方式可以 ...

  4. show_you_my_codes 001

    program 001 第 0001 题:做为 Apple Store App 独立开发者,你要搞限时促销,为你的应用生成激活码(或者优惠券), 使用 Python 如何生成 200 个激活码(或者优 ...

  5. mysql 安装及卸载 主从配置

    1.查询rpm -qa | grep mysql* 组件 出现类似安装包 mysql-server-5.1.71-1.el6.x86_64 mysql-libs-5.1.71-1.el6.x86_64 ...

  6. 每天一个linux命令(37)--iostat命令

    Linux 系统中的iostat是I/O statistics (输入/输出统计)的缩写,iostat工具将对系统的磁盘操作活动进行监视.它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况. ...

  7. BFC原理

    一.BFC是什么? 在解释 BFC 是什么之前,需要先介绍 Box.Formatting Context的概念. Box: CSS布局的基本单位 Box 是 CSS 布局的对象和基本单位, 直观点来说 ...

  8. 使用Microsoft.ExceptionMessageBox.dll捕获WinForm程序中异常信息并弹窗显示

    WinForm程序开发中,在开发模式下对于异常的处理一般都是通过调试的方式来查找异常发生的未知与原因. 下面以“除数为0”的情况来具体说明. Button按钮事件如下: private void bu ...

  9. Jdom读取XML文件

    学习Spring时,我们经常看到很多xml配置文件,Spring通过在配置文件中的配置,使用IOC(控制反转),从而实现代码的灵活性,本篇我就为大家介绍一种解析xml方式--Jdom 首先我们到Jdo ...

  10. RabbitMQ-从基础到实战(5)— 消息的交换(下)

    转载请注明出处 0.目录 RabbitMQ-从基础到实战(1)- Hello RabbitMQ RabbitMQ-从基础到实战(2)- 防止消息丢失 RabbitMQ-从基础到实战(3)- 消息的交换 ...