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

在上几节的描述中,我们把HierarchyViewer初始化好,也把ViewServer给装备好了。那现在距离获得一个控件去操作它是万事具备只欠东风了,欠了那一股春风了?欠了的是建立控件树这个东风,因为HierarchyViewer根据ID去获取一个控件之前是需要先建立好控件树,然后从该控件树上根据ID去查找到目标控件的。

那么这一小节我们就先去看下HierarchyViewer是如何去ViewServer获取控件列表,然后如何把每个控件的信息解析出来,最后组成一个由根控件开始的一颗控件树的。

其实在上一章我们已经自己编写代码去驱动ViewServer把指定Activity的所有控件给列出来了,那么HierarchyViewer又是怎么做的呢?其实做法都是类似的,只是上一章的实例是通过指定一个Activity的哈希值来DUMP所有控件,而HierarchyViewer是通过指定Activity的哈希值为-1来DUMP屏幕最前面的Activity窗口的所有控件。

我们先跳到HierarchyViewer获取一个控件的API,事情就是从这里开始发生的:

63public ViewNode findViewById(String id) { 64ViewNode rootNode = DeviceBridge.loadWindowData( 65new Window(new ViewServerDevice(mDevice), "", 0xffffffff)); 66if (rootNode == null) { 67throw new RuntimeException("Could not dump view"); 68} 69return findViewById(id, rootNode); 70}代码14-8-1 HierarchyViewer – findViewById

关键代码虽然只有64行这一行,但一行里面做了多个嵌套:

首先是通过传入ddmlib的Device实例来初始化ViewServerDevice这个对象。ViewServerDevice这个类对我们其实并不是很重要,重要的是它持有了Device这个实例,因为和ADB交互靠的就是它

然后又用ViewServerDevice这个对象,一个空标题和-1做为哈希值来初始化一个Window对象(Window构造函数请参考“代码9-1-3 Window-构造函数”)。这里要注意的是代表这个Window的哈希值-1,这个值最终是会做为”DUMP”命令的参数传送给ViewServer来获取控件列表的。我们在第11章第4节“获得控件列表“一开始就又描述过,-1这个哈希值比较特殊,指定它来DUMP一个Activity窗口的控件的话默认用的会是屏幕最前面的那个Activity,也就是当前获得焦点的Activity。

最后最外层的一个嵌套就是指定这个哈希值为-1的Window来调用DeviceBridge.loadWindowData这个方法了,这个才是重点!

我们进入loadWindowData这个方法:

388public static ViewNode loadWindowData(Window window) {389DeviceConnection connection = null;390try {391connection = new DeviceConnection(window.getDevice());392connection.sendCommand("DUMP " + window.encode()); //$NON-NLS-1$393BufferedReader in = connection.getInputStream();394ViewNode currentNode = parseViewHierarchy(in, window);395ViewServerInfo serverInfo = getViewServerInfo(window.getDevice());396if (serverInfo != null) {397currentNode.protocolVersion = serverInfo.protocolVersion;398}399return currentNode;400} catch (Exception e) {401Log.e(TAG, "Unable to load window data for window " + window.getTitle() + " on device "402+ window.getDevice());403Log.e(TAG, e.getMessage());404} finally {405if (connection != null) {406connection.close();407}408}409return null;410}代码14-8-2 HierarchyViewer – loadWindowData

这个方法非常重要,重点做了两个事情:

重点1:392行处通过向ViewServer发送”DUMP”命令来获得控件列表,获得谁的控件列表呢?注意”DUMP”命令所带的参数,调用的是刚才哈希值为-1的那个Window的encode方法,而这个方法所做的事情其实就是将-1转换成16进制,请看代码14-8-3。所以这里其实获得的就是屏幕最前面的Activity窗口的所有控件

public String encode() {return Integer.toHexString(this.mHashCode); }代码14-8-3 Window – encode

重点2: 在获得所有控件列表之后,394行处就会调用parseViewHierarchy这个方法来解析这个ViewServer返回来的一大串控件列表信息,并且把这些解析出来的控件组建成我们最终的控件树

411public static ViewNode parseViewHierarchy(BufferedReader in, Window window) {412ViewNode currentNode = null;413int currentDepth = -1;414String line;415try {416while ((line = in.readLine()) != null) {417if ("DONE.".equalsIgnoreCase(line)) {418break;419}420int depth = 0;421while (line.charAt(depth) == ‘ ‘) {422depth++;423}424while (depth <= currentDepth) {425if (currentNode != null) {426currentNode = currentNode.parent;427}428currentDepth–;429}430currentNode = new ViewNode(window, currentNode, line.substring(depth));431currentDepth = depth;432}433} catch (IOException e) {434Log.e(TAG, "Error reading view hierarchy stream: " + e.getMessage());435return null;436}437if (currentNode == null) {438return null;439}440while (currentNode.parent != null) {441currentNode = currentNode.parent;442}443return currentNode;444}代码14-8-4 BridgeDevice – parseViewHierarchy

整个dump返回的文件可以看成一棵由控件组成的多叉树,每一行代表一个控件,每一行(一个控件)开始前的空格数代表该控件在这棵树的层次,如没有空格代表的就是根节点,也就是我们常说的窗口顶端的DecorView.

以上方法的算法理解我们首先要弄清楚用到的几个变量的意义:

depth: 代表当前在分析的一行控件信息处于控件树的第几层,也就是这一行信息前面空格的个数了

currentDepth:最后创建的ViewNode控件节点在控件树的层次

currentNode:最后创建的ViewNode控件节点,默认会是当前行控件的父节点,但会根据实际情况进行调整

至于ViewNode控件是怎么一回事我们往下会分析到,现在就需要知道整个控件树就是由它组成的且它的构造函数 ViewNode(Window window, ViewNode parent, String data)接受的三个参数分别是:

代表屏幕最上层的获得焦点的Activity窗口的Window实例

该节点的父节点

ViewServer返回的一行控件信息(下面会看到其实是去掉了前面空格的)

世上再美的风景,都不及回家的那段路。

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

相关文章:

你感兴趣的文章:

标签云: