第14章4节《MonkeyRunner源代码剖析》 HierarchyViewer实现原理-装备ViewServer-port转发

在初始化HierarchyViewer的实例过程中,HierarchyViewer会调用自己的成员方法setupViewServer来把ViewServer装备好,那么我们这里先看下这种方法:

 39     private void setupViewServer() {
 40         DeviceBridge.setupDeviceForward(mDevice);
 41         if (!DeviceBridge.isViewServerRunning(mDevice)) {
 42             if (!DeviceBridge.startViewServer(mDevice)) {
 43                 // TODO: Get rid of this delay.
 44                 try {
 45                     Thread.sleep(2000);
 46                 } catch (InterruptedException e) {
 47                 }
 48                 if (!DeviceBridge.startViewServer(mDevice)) {
 49                     Log.e(TAG, "Unable to debug device " + mDevice);
 50                     throw new RuntimeException("Could not connect to the view server");
 51                 }
 52                 return;
 53             }
 54         }
 55         DeviceBridge.loadViewServerInfo(mDevice);
 56     }

代码14-4-1 HierarchyViewer-setupViewServer

从以上代码中我们能够看到该方法去装备ViewServer主要做的事情有例如以下几点:

  • 40行:设置本地port到目标机器端ViewServer监听port的port转发
  • 41-54行:确定ViewServer线程是否已经启动,没有的话就启动它。
  • 55行:获取ViewServer的版本号以及其支持的协议版本号

本小节我们先描写叙述第一点,看HierarchyViewer是怎样设置本地port到目标机器端ViewServer监听port的port转发的。在第13章第2小节我们也手动做过这个事情,当时发送的命令是:

adb forward tcp:4939 tcp:4939

那么HierarchyViewer是不是也是通过代码做同样的事情呢?那么我们带着这个疑问来进入深入的代码分析。我们进入setupDeviceForward这种方法:

110     /**
111      * Sets up a just-connected device to work with the view server.
112      * <p/>
113      * This starts a port forwarding between a local port and a port on the
114      * device.
115      *
116      * @param device
117      */
118     public static void setupDeviceForward(IDevice device) {
119         synchronized (sDevicePortMap) {
120             if (device.getState() == IDevice.DeviceState.ONLINE) {
121                 int localPort = sNextLocalPort++;
122                 try {
123                     device.createForward(localPort, DEFAULT_SERVER_PORT);
124                     sDevicePortMap.put(device, localPort);
125                 } catch (TimeoutException e) {
126                     Log.e(TAG, "Timeout setting up port forwarding for " + device);
127                 } catch (AdbCommandRejectedException e) {
128                     Log.e(TAG, String.format("Adb rejected forward command for device %1$s: %2$s",
129                             device, e.getMessage()));
130                 } catch (IOException e) {
131                     Log.e(TAG, String.format("Failed to create forward for device %1$s: %2$s",
132                             device, e.getMessage()));
133                 }
134             }
135         }
136     }

代码14-4-2 DeviceBridge - setupDeviceForward

这个处理port转发的方法主要分3步走:

  • 第1步:获得本地ViewServer转发port号
  • 第2步:通过Device类发送adb命令创建本地到ViewServerport转发
  • 第3步:把本地port号和相应的设备序列号保存起来以便查找

我们先看第1步,就是121行,这里要注意”sNextLocalPort”这个变量。事实上它是个静态变量:

private static int sNextLocalPort = 4939;

代码14-4-3 DeviceBridge - sNextLocalPort

所以代码14-4-2中121行所代表的意思是:

  • 第一个建立的ViewServerport转发的本地port是4939
  • 下一个建立的ViewServerport转发的本地port是在4939的基础自添加1

注意这里自添加的写法是”sNextLocalPort ++”。假设反过来写成”++sNextLocalPort”, 那么第一个本地port就会变成4940了。这些都是Java的基本的语法了,这里以防我们做測试的没有太多编程经验,所以指出来。

好我们继续分析第2步port转发相应代码, 这种方法传入的參数就是HierarchyViewer的成员变量mDevice,依据本章第3小节的描写叙述,这个变量是ddmlib中的Device类的一个实例,所以以上调用”device.createForward”方法实际上调用的就是Device的createForward方法:

 644     @Override
 645     public void createForward(int localPort, int remotePort)
 646             throws TimeoutException, AdbCommandRejectedException, IOException {
 647         AdbHelper.createForward(AndroidDebugBridge.getSocketAddress(), this,
 648                 String.format("tcp:%d", localPort),     //$NON-NLS-1$
 649                 String.format("tcp:%d", remotePort));   //$NON-NLS-1$
 650     }

代码14-4-3 Device - createForward

像第10章《MonkeyDevice实现原理基础》所描写叙述的那样。Device终于直接调用AdbHelper静态类的createForward方法来设置port转发:

549     public static void createForward(InetSocketAddress adbSockAddr, Device device,
550             String localPortSpec, String remotePortSpec)
551                     throws TimeoutException, AdbCommandRejectedException, IOException {
552
553         SocketChannel adbChan = null;
554         try {
555             adbChan = SocketChannel.open(adbSockAddr);
556             adbChan.configureBlocking(false);
557
558             byte[] request = formAdbRequest(String.format(
559                     "host-serial:%1$s:forward:%2$s;%3$s", //$NON-NLS-1$
560                     device.getSerialNumber(), localPortSpec, remotePortSpec));
561
562             write(adbChan, request);
563
564             AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
565             if (!resp.okay) {
566                 Log.w("create-forward", "Error creating forward: " + resp.message);
567                 throw new AdbCommandRejectedException(resp.message);
568             }
569         } finally {
570             if (adbChan != null) {
571                 adbChan.close();
572             }
573         }
574     }

代码14-4-4 AdbHelper - createForward

formAdbRequest我们在之前已经分析过。做的事情就是组建好ADB协议的命令以待发送给ADB服务器,在我们558行中终于组建好的ADB协议命令将会例如以下:

“host-serial:xxx:forward:localPortSpec;remotePortSpec”

当中xxx就是代表目标设备的序列号,能够通过”adb devices -l”获得:

图14-4-1获取设备序列号

所以在终于这个ADB协议命令字串将会变成:

“host-serial:HT21ATD05099:foward:4939;4939”

而參照ADB协议,实际上就相当于ADB命令行client命令的:

“adb -s HT21ATD05099 forward tcp:4939 tcp:4939”

这事实上跟第13章第2小节手动发送ViewServerport转发命令是一样的,仅仅是这里多了个-s參数来指定要转发的port属于哪个设备上的ViewServer而已。

到如今为止我们已经完毕了port转发的第2步了,那么我们往下看第3步,做的事情就是把代表目标设备的Device实例和本地ViewServer的转发port做为键值对给保存起来到sDevicePortMap这个成员变量里面:

sDevicePortMap.put(device, Integer.valueOf(localPort));
sDevicePortMap这个成员变量是个HashMap:
55	private static final HashMap<IDevice, Integer> sDevicePortMap = new HashMap();

代码14-4-5 DeviceBridge - sDevicePortmap

注意这个变量是非常重要的,由于HierarchyViewer连接相应的设备的socket就是靠它来提供相应的本地ViewServer转发port号的。

注:很多其它文章请关注公众号:techgogogo或个人博客http://techgogogo.com

当然,也非常欢迎您直接微信(zhubaitian1)勾搭。

本文由天地会珠海分舵原创。转载请自觉,是否投诉维权看心情。

时间: 2024-10-06 19:44:35

第14章4节《MonkeyRunner源代码剖析》 HierarchyViewer实现原理-装备ViewServer-port转发的相关文章

第6章1节《MonkeyRunner源代码剖析》Monkey原理分析-事件源-事件源概览

在上一章中我们有简要的介绍了事件源是怎么一回事.可是并没有进行详细的描写叙述.那么往下的这几个小节我们就须要把这方面的知识给补充完整. 这一节我们先主要环绕MonkeySourceNetwork这个事件源来学习事件源的框架结构.首先,要理解事件源,必须先搞清楚几个问题: 事件从哪里来? Monkey的事件来源有多个方面,可是作为MonkeyRunner框架的一部分,它的事件来源主要是来自MonkeyRunner通过网络Socket(USB/TCP协议)发送过来的命令字串.MonkeySource

第14章5节《MonkeyRunner源代码剖析》 HierarchyViewer实现原理-装备ViewServer-查询ViewServer执行状态

上一小节我们描写叙述了HierarchyViewer是怎样组建ADB协议命令来实现ViewServer的port转发的.在port转发设置好后,下一个要做的事情就是去检測目标设备端ViewServer线程是否已经启动起来了.我们进入setupViewServer调用的DeviceBridge的isViewServerRunning方法: 165 public static boolean isViewServerRunning(IDevice device) { 166 final boolea

第14章1节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-面向控件编程VS面向坐标编程

到此为止我们描述的MonkeyRunner对应用的点击拖放等操作都是直接通过指定坐标点来实现的,比如下面触摸一个坐标点为(60,90)的按钮的脚本例子: 1 device.touch(60,900,MonkeyDevice.DOWN_AND_UP) 代码14-1-1 直接使用坐标点操作应用 这样子做的话代码会存在以下几大缺点: 缺乏易用性:要操作某个控件之前需要先想办法如通过工具来定位该控件的坐标点 可扩展性差:当屏幕分辨率改变的时候需要另外写一个通用算法来处理坐标点的变化 可读性差:代码到处都

第8章2节《MonkeyRunner源代码剖析》MonkeyRunner启动执行过程-解析处理命令行參数

MonkeyRunnerStarter是MonkeyRunner启动时的入口类,由于它里面包括了main方法.它的整个启动过程主要做了以下几件事情: 解析用户启动MonkeyRunner时从命令行传输进来的參数: 由于MonkeyRunner须要依据指定的參数才干做事情,比方输入的一个须要执行的脚本. 假设确实不知道不论什么參数的话它就会进入MonkeyRunner的交互模式,事实上就是Jythong的交互模式,让用户能够边写代码边执行 启动AndroidDebugBridge: 事实上就是启动

老李推荐:第14章8节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-获取控件列表并建立控件树 6

这里如果你对java熟悉的话其实很简单,就是根据控件属性的名字对properties列表进行一次排序而已.如果你对java不熟悉的话,那就要先去查下Collections.sort这个方法是怎么回事了.顾名思义它提供的是对一个集合List的排序功能,但是根据什么来排序呢?这里就涉及到两个概念了: Comparator接口:提供的是一个接口,用户应该去实现该接口来提供列表中两个元素的对比功能 另外一个是匿名类:上面的new Comparator的写法就是建立一个实现了Comparator接口的匿名

老李推荐:第14章7节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-装备ViewServer-获取版本号 2

代码先是发送”LIST”命令到ViewServer列出所有的打开的窗口,然后把每个窗口都保存起来.342行起按照源码的注释解析就是说:从协议版本3以后开始加入了窗口自动更新的功能,但是在此之前,如果用户想要获得一个获得焦点的窗口的话,需要通过显式的创建一个特殊的哈希值为-1的Window实例来完成.怎么知道它的哈希值是-1呢?请看Window类的getfocusedWindow方法: return new Window(device, "<Focused Window>",

第14章7节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-装备ViewServer-获取版本号

这里获取的版本号有两个,一个是ViewServer自身的版本号,一个是ViewServer当前使用协议的版本号. 我们这里为什么需要获取ViewServer版本以及其协议版本呢?其实主要原因是ViewServer有些功能在老版本上是不支持的,比如HierarchyViewer在列出当前所有Activity窗口的时候,针对获取焦点的窗口会根据不同的ViewServer协议版本而作不同处理,请看源码示例: 316 public static Window[] loadWindows(IHvDevic

老李推荐: 第8章4节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-启动AndroidDebugBridge 1

老李推荐: 第8章4节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-启动AndroidDebugBridge 上一节我们看到在启动AndroidDebugBridge的过程中会调用其start方法,而该方法会做2个主要的事情: 715行startAdb:开启AndroidDebugBridge 722-723行:初始化android设备监控并启动DeviceMonitor设备监控线程. 其中第一点我们上一小节已经做了详尽分析了,那么我们往下就去分析下第2点. Dev

老李推荐: 第1章1节《MonkeyRunner源码剖析》概述:前言

老李推荐: 第1章1节<MonkeyRunner源码剖析>概述:前言 前言 相信大家做过安卓移动平台UI自动化开发的必然会用过,至少听过MonkeyRunner这个名字.MonkeyRunner是一个针对安卓平台的UI自动化测试框架,这个框架的其中一个但绝不是唯一的优点是支持用当今非常流行和高效的Python语言来进行脚本开发.同时,它相比Instrumentation框架或者基于Instrumentation的自动化测试框架最大的优点之一就是可以跨应用测试. 这本书不会有什么序言或者致谢什么