这里获取的版本号有两个,一个是ViewServer自身的版本号,一个是ViewServer当前使用协议的版本号。
我们这里为什么需要获取ViewServer版本以及其协议版本呢?其实主要原因是ViewServer有些功能在老版本上是不支持的,比如HierarchyViewer在列出当前所有Activity窗口的时候,针对获取焦点的窗口会根据不同的ViewServer协议版本而作不同处理,请看源码示例:
316 public static Window[] loadWindows(IHvDevice hvDevice, IDevice device) { 317 ArrayList<Window> windows = new ArrayList<Window>(); 318 DeviceConnection connection = null; 319 ViewServerInfo serverInfo = getViewServerInfo(device); 320 try { 321 connection = new DeviceConnection(device); 322 connection.sendCommand("LIST"); //$NON-NLS-1$ 323 BufferedReader in = connection.getInputStream(); 324 String line; 325 while ((line = in.readLine()) != null) { ... Window w = new Window(hvDevice, line.substring(index + 1), id); windows.add(w); } 342 // Automatic refreshing of windows was added in protocol version 3. 343 // Before, the user needed to specify explicitly that he wants to 344 // get the focused window, which was done using a special type of 345 // window with hash code -1. 346 if (serverInfo.protocolVersion < 3) { 347 windows.add(Window.getFocusedWindow(hvDevice)); 348 } } catch (Exception e) { ... } }
代码14-7-1 协议版本作用示例DeviceBridge-loadWindows
代码先是发送”LIST”命令到ViewServer列出所有的打开的窗口,然后把每个窗口都保存起来。342行起按照源码的注释解析就是说:从协议版本3以后开始加入了窗口自动更新的功能,但是在此之前,如果用户想要获得一个获得焦点的窗口的话,需要通过显式的创建一个特殊的哈希值为-1的Window实例来完成。怎么知道它的哈希值是-1呢?请看Window类的getfocusedWindow方法:
public static Window getFocusedWindow(IHvDevice device) { return new Window(device, "<Focused Window>", -1); }
代码14-7-2 Window-getFocusedWindow方法
然后再看其调用的Window的构造函数和对应的传入参数:
public Window(IHvDevice device, String title, int hashCode) { this.mHvDevice = device; this.mTitle = title; this.mHashCode = hashCode; this.mClient = null; }
代码14-7-3 Window-构造函数
最终创建的就是一个标题是”<Focused Window>”,哈希值是-1的Window实例。
通过以上的示例,主要是想说明,ViewServer的版本会影响到代码的不同处理方式,所以我们还是很有必要去看下这些版本信息是如何获得和保存起来的。
好,那么我们继续分析HierarchyViewer在装备ViewServer时的方法setupViewServer最后一个调用方法DeviceBridge.loadViewServerInfo,这个方法有点长,我们对它分开来分析,先看第1部分:
260 public static ViewServerInfo loadViewServerInfo(IDevice device) { 261 int server = -1; 262 int protocol = -1; 263 DeviceConnection connection = null; 264 try { 265 connection = new DeviceConnection(device); 266 connection.sendCommand("SERVER"); //$NON-NLS-1$ 267 String line = connection.getInputStream().readLine(); 268 if (line != null) { 269 server = Integer.parseInt(line); 270 } 271 } catch (Exception e) { 272 Log.e(TAG, "Unable to get view server version from device " + device); 273 } finally { 274 if (connection != null) { 275 connection.close(); 276 } 277 } ... }
代码14-7-4 DeviceBridge - loadViewServerInfo获取ViewServer版本
265行显示的代码的第一个重要的部分是去建立一个DeviceConnection的连接,传入的参数依然是ddmlib的Device类的实例:
36 public DeviceConnection(IDevice device) throws IOException { 37 mSocketChannel = SocketChannel.open(); 38 int port = DeviceBridge.getDeviceLocalPort(device); 39 if (port == -1) { 40 throw new IOException(); 41 } 42 mSocketChannel.connect(new InetSocketAddress("127.0.0.1", port)); //$NON-NLS-1$ 43 mSocketChannel.socket().setSoTimeout(40000); 44 }
代码14-7-5 DeviceConnection - 构造函数
整个代码所做的事情很清晰明了:
- 创建一个SocketChannel
- 根据Device实例获得对应的ViewServer本地转发端口号
- 把SocketChannel连接上本地的ViewServer转发端口
这里值得提一提的倒是如何根据Device实例获得ViewServer本地转发端口号这个事情。大家还记得第4小节我们说端口转发的时候,最终Device实例和对应的本地转发端口号是保存在DeviceBridge的一个名叫sDevicePortMap的静态成员HashMap里面的。所以这里所做的事情就是去到这个HashMap里面以Device实例为键把端口号这个值取出来而已:
155 public static int getDeviceLocalPort(IDevice device) { 156 synchronized (sDevicePortMap) { 157 Integer port = sDevicePortMap.get(device); 158 if (port != null) { 159 return port; 160 } 161 Log.e(TAG, "Missing forwarded port for " + device.getSerialNumber()); 162 return -1; 163 } 164 }
代码14-7-6 DeviceBridge - getDeviceLocalPort
那么现在我们已经获得ViewServer对应本地的转发端口号了,ViewServer也已经在实例化DeviceConnection的时候给连接好了,剩下的就差发命令了。我们继续看下”代码14-7-4 DeviceBridge - loadViewServerInfo获取ViewServer版本”的第2个重要部分:
connection.sendCommand("SERVER");
很明显就是往刚才DeviceConnection建立好的连接到ViewServer的SocketChannel发送”SERVER”这个命令了:
62 public void sendCommand(String command) throws IOException { 63 BufferedWriter out = getOutputStream(); 64 out.write(command); 65 out.newLine(); 66 out.flush(); 67 }
代码14-7-7 DeviceConnection - sendCommand
到此为止我们就分析完如何发送”SERVER”命令到ViewServer以获得其版本号的,完了吗?还没有了,别忘了我们除了要获得ViewServer的版本号外,还需要获得ViewServer当前使用的协议版本了,”代码14-7-4 DeviceBridge - loadViewServerInfo获取ViewServer版本”只是loadViewServerInfo的一部分而已。现在我们往下分析第2部分,但它是怎么去获得协议版本的:
260 public static ViewServerInfo loadViewServerInfo(IDevice device) { 261 int server = -1; 262 int protocol = -1; ... //获得ViewServer版本号部分略 278 connection = null; 279 try { 280 connection = new DeviceConnection(device); 281 connection.sendCommand("PROTOCOL"); //$NON-NLS-1$ 282 String line = connection.getInputStream().readLine(); 283 if (line != null) { 284 protocol = Integer.parseInt(line); 285 } 286 } ... }
代码14-7-8 DeviceBridge - loadViewServerInfo获取协议版本
可以看到,其实流程跟代码14-7-4一模一样,只是最终发送给ViewServer的命令是代表获取协议的”PROTOCOL”而非”SERVER”而已。
最后我们看在获得这些版本号之后loadViewServerInfo是如何处理的:
260 public static ViewServerInfo loadViewServerInfo(IDevice device) { 261 int server = -1; 262 int protocol = -1; ... 296 ViewServerInfo returnValue = new ViewServerInfo(server, protocol); 297 synchronized (sViewServerInfo) { 298 sViewServerInfo.put(device, returnValue); 299 } 300 return returnValue; 301 }
代码14-7-9 DeviceBridge - loadViewServerInfo收尾
最后收尾阶段做两个事情:
- 第一件:根据获得协议创建ViewServerInfo实例
- 第二件:把这个实例保存起来到sViewServerInfo这个HashMap里面,跟上面提到的sDevicePortMap一样,还是用代表设备的Device类的实例做为键,而值是刚创建的这个ViewServerInfo实例
这里我们先看看ViewServerInfo这个类,其实它是DeviceBridge的内部类:
59 public static class ViewServerInfo { 60 public final int protocolVersion; 61 public final int serverVersion; 62 ViewServerInfo(int serverVersion, int protocolVersion) { 63 this.protocolVersion = protocolVersion; 64 this.serverVersion = serverVersion; 65 } 66 }
代码14-7-10 DeviceBridge - ViewServerInfo
里面就两个成员变量一个构造函数完了,两个成员类变量分别保存的是我们刚才获得的ViewServer版本号和ViewServer协议版本号。
注:更多文章请关注公众号:techgogogo或个人博客http://techgogogo.com。当然,也非常欢迎您直接微信(zhubaitian1)勾搭。本文由天地会珠海分舵原创。转载请自觉,是否投诉维权看心情。