android自动化测试中hierarchyviewer和uiautomatorviewer获取控件信息的方式比对



android自动化测试中hierarchyviewer和uiautomatorviewer获取控件信息的方式比对(1)
adroid的目录tools下有2个自带的工具hierarchyviewer和uiautomatorviewer,打开后,如下所示:

 

 

 

       

        分别来介绍它们怎么使用的:

 

   UiAutoMatorViewer

 

      

 点击左上角的手机图样的小图标,出现弹出框,此时正在获取信息:

 

      

         完成获取后得到了当前的手机界面:

 

        

        然后你可以点击主面板中的图片,右面的属性面板会显示你所点击的区域的控件属性。(需要注意到是,你的手机设备或模拟器的api要在16以上,也就是android版本得是4.2以上,因为这个工具是google在4.2以后推出来的,只适用于4.2以后的版本)。

 

      

        到这一步,我了解了如何使用这个工具,但是并不是我要研究的东西。我要知道他是如何和手机设备通信,然后获取控件信息的,所以我反编译了uiautomatorviewer.jar,以便从源码了解它的原理。

        在tools/lib下找到uiautomatorviewer.jar。反编译后项目结构如下所示:

 

       

        首先查看UiAutomatorViewer.class,打开后明显看出这是一个java.swt的主界面类,那我们只需要找到工具栏中的按钮的定义。我们就能追踪到点击它是怎么得到设备控件信息的。

 

      

        找到这个按钮的定义后,我们按ctrl键 点击鼠标跟踪进去,发现该类中有一个进度条对话框,这正是我们先前点击按钮出现的对话框,这里面肯定定义信息获取的方式。

       

        查看上面的代码分析得到,UiAutoMatorHelper的子类UiAutoMatorResult和对象result,它得到了UiAutomatorHelper方法和takeSnapShot返回的结果,那么我就去这个方法一探究竟。进入UiAutomatorHelper中,找到takeSnapshot方法。

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public static UiAutomatorResult takeSnapshot(IDevice device, IProgressMonitor monitor) throws com.android.uiautomator.UiAutomatorHelper.UiAutomatorException  
  2.   {  
  3.     UiAutomatorModel model;  
  4.     String msg;  
  5.     RawImage rawImage;  
  6.     if (monitor == null) {  
  7.       monitor = new NullProgressMonitor();  
  8.     }  
  9.   
  10.     monitor.subTask("Checking if device support UI Automator");  
  11.     if (!(supportsUiAutomator(device))) {  
  12.       String msg = "UI Automator requires a device with API Level 16";  
  13.   
  14.       throw new com.android.uiautomator.UiAutomatorHelper.UiAutomatorException(msg, null);  
  15.     }  
  16.   
  17.     monitor.subTask("Creating temporary files for uiautomator results.");  
  18.     File tmpDir = null;  
  19.     File xmlDumpFile = null;  
  20.     File screenshotFile = null;  
  21.     try {  
  22.       tmpDir = File.createTempFile("uiautomatorviewer_""");  
  23.       tmpDir.delete();  
  24.       if (!(tmpDir.mkdirs()))  
  25.         throw new IOException("Failed to mkdir");  
  26.       xmlDumpFile = File.createTempFile("dump_"".uix", tmpDir);  
  27.       screenshotFile = File.createTempFile("screenshot_"".png", tmpDir);  
  28.     } catch (Exception e) {  
  29.       msg = "Error while creating temporary file to save snapshot: " + e.getMessage();  
  30.   
  31.       throw new com.android.uiautomator.UiAutomatorHelper.UiAutomatorException(msg, e);  
  32.     }  
  33.   
  34.     tmpDir.deleteOnExit();  
  35.     xmlDumpFile.deleteOnExit();  
  36.     screenshotFile.deleteOnExit();  
  37.   
  38.     monitor.subTask("Obtaining UI hierarchy");  
  39.     try {  
  40.       <span style="color:#ff0000;">getUiHierarchyFile(device, xmlDumpFile, monitor);  
  41. </span>    } catch (Exception e) {  
  42.       msg = "Error while obtaining UI hierarchy XML file: " + e.getMessage();  
  43.       throw new com.android.uiautomator.UiAutomatorHelper.UiAutomatorException(msg, e);  
  44.     }  


        刚开始的时候没明白为什么有好多定义文件的代码,然后就没管,往下看的时候发现了一个getUiHierarchyFile方法,既然在这个方法里没找到获取控件信息的方式,那么我就试着去getUiherarchyFile方法中看看(其实我之前研究hierarchyviewer的时候,这个工具也用过这个getHierarchy方法,只不过叫parseHierarchy,它就是获取控件树的,所以我心中一喜,有戏) 。让我们进入getUiHerarchyFile中看看,看能否发现我们真正需要的。

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. private static void getUiHierarchyFile(IDevice device, File dst, IProgressMonitor monitor) {  
  2.     if (monitor == null) {  
  3.       monitor = new NullProgressMonitor();  
  4.     }  
  5.   
  6.     monitor.subTask("Deleting old UI XML snapshot ...");  
  7.     String command = "rm /data/local/tmp/uidump.xml";  
  8.     try  
  9.     {  
  10.       commandCompleteLatch = new CountDownLatch(1);  
  11.       device.executeShellCommand(command, new CollectingOutputReceiver(commandCompleteLatch));  
  12.   
  13.       commandCompleteLatch.await(5L, TimeUnit.SECONDS);  
  14.     }  
  15.     catch (Exception e1)  
  16.     {  
  17.     }  
  18.     monitor.subTask("Taking UI XML snapshot...");  
  19.     command = String.format("%s %s %s"new Object[] { "/system/bin/uiautomator""dump""/data/local/tmp/uidump.xml" });  
  20.   
  21.     CountDownLatch commandCompleteLatch = new CountDownLatch(1);  
  22.     try  
  23.     {  
  24.       device.executeShellCommand(command, new CollectingOutputReceiver(commandCompleteLatch), 40000);  
  25.   
  26.       commandCompleteLatch.await(40L, TimeUnit.SECONDS);  
  27.   
  28.       monitor.subTask("Pull UI XML snapshot from device...");  
  29.       device.getSyncService().pullFile("/data/local/tmp/uidump.xml", dst.getAbsolutePath(), SyncService.getNullProgressMonitor());  
  30.     }  
  31.     catch (Exception e) {  
  32.       throw new RuntimeException(e);  
  33.     }  
  34.   }  
[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. </pre><pre code_snippet_id="250249" snippet_file_name="blog_20140322_3_2829642" class="html" name="code">  

       

        我原本以为这里面也应该有个socke啥的,跟手机端通信获取数据的。进来这里面没发现socket,但是仔细一分析,原来UiAutomatorviewer并不是通过socket来获取信息的。它是发送dump命令,让存放在手机设备中/system/bin/uiautomator下的脚本执行,获得一个uidump.xml的文件,然后将这个文件抓到本地。本地读取xml文件就可以了。这才恍然大悟为什么之前takeSnapshot方法中有定义文件的操作,原来它是通过获取xml文件存放在本地临时文件里,太鬼了。

        那剩下的就是读取xml文件喽。到这,我的uiautomatorviewer的了解就结束了,还算有点收获。下面接着hierarchyviewer的使用。

 

   HierarchyViewer

 

        说到hierarchyviewer都是眼泪,花了3个礼拜研究,由于自己的死心眼,非要通过它实现自动化,非要用java写。然后一直研究到它可以遍历settings的所有界面;然后才发现被坑了,代码太长了;if/else写了一大堆,又是dumpsys命令获取window信息和activity信息,又是图片比对确定点击跳转的图片,然后满心欢喜的拿给经理看,经理直接给否决了---效率太低;hierarchyviewer获取数据确实慢,但是总算有的基础版本的嘛;其实我发现经理心里算盘打着好着呢,她只是让我们一步一步的了解,她知道哪种方式最适合,就是还不告诉我们,就让我们自己研究,而我呢,刚来又着急展示一下,根基没打稳就像往大的方向走,都开始实现开啦;然后被经理给拽回来, 开始研究哪些实现获取控件的方式,以及优缺点,这才有了上面的uiautomator的研究;哎,不着急,一步一步来吧。

        说到hierarchyviewer,研究起来真的是小孩学步啊。经理只丢了一个命题:多语自动化测试,你们研究吧。我ca类,我还傻不垃圾的问了一句:什么是多语。旁边的测试人员给我回答了,多国语言测试。汗!好吧,我低端。。。。。。开始吧。

        首先选择是手机端直接测试,还是连上PC端测试?然后发现了monkey,再到monkeyrunner,然后在monkeyrunner里有touch方法点击,然后却不知道一个按钮的坐标怎么确定,在一个犄角旮旯的地方发现有人说通过hierarchyviewer可以获取坐标。然后就开始一个坐标一个坐标的找啊,编写脚本啊。总算实现了一点:唤醒--解锁--点--点--点;然后开讨论会的时候,让经理否决了,说是这么多控件,要一个一个找,得多长时间啊。要做到连上手机,不管哪个画面,它自己获得,然后点击。确实高端大气上档次!but你倒是告诉我从那块搞起啊。哎,在百般纠结于无奈中,柳暗花明啦,hierarchyviewer上的东西不就是人家从客户端获取的么。得到这个讯息后,我找到了一个知平软件写的,然后根据他的研究,一步一步了解了hierarchyviewer,在此感谢这个前辈,放出连接,新手同学可以研究下。

        第一篇:http://www.cnblogs.com/vowei/archive/2012/07/30/2614353.html

        第二篇:http://www.cnblogs.com/vowei/archive/2012/08/03/2618753.html

        第三篇:http://www.cnblogs.com/vowei/archive/2012/08/08/2627614.html

        第四篇:http://www.cnblogs.com/vowei/archive/2012/08/22/2650722.html

        通过读这四篇文章,我对hierarchyviewer有了一定了解,对于这位前辈没给出的一些疑惑我做了一些深入的研究和总结。

        总结:

        1.hierarchyviewer是通过socket连接android设备的ViewServer,通过4939端口建立通信。

        2.通过adb  -s <device>  forward  tcp:localpott  tcp:4939将端口映射到本地端口上

        3.通过该本地端口,客户端启动socket连接ViewServer,发送"dump -1"命令获取控件信息。这些信息一行代表一个控件,然后存放在ViewNode中。

        以上通过上面四篇文章你都能了解到。下面是自己的疑问:

        1.信息是如何从一行一行的字符串转变为viewnode对象的。

        2.是怎么深度遍历树的。

       

       带着这些疑问我又暴力了,反编译hierarchyviewer2lib.jar文件,寻找到了DeviceBridge的parseHierarchy方法:

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.  public static ViewNode parseViewHierarchy(BufferedReader in, Window window) {  
  2.     ViewNode currentNode = null;  
  3.     int currentDepth = -1;  
  4.     try  
  5.     {  
  6.       while ((line = in.readLine()) != null) {  
  7.         String line;  
  8.         if ("DONE.".equalsIgnoreCase(line))  
  9.           break;  
  10.   
  11.         int depth = 0;  
  12.         while (line.charAt(depth) == ' ')  
  13.           ++depth;  
  14.   
  15.         while (depth <= currentDepth) {  
  16.           if (currentNode != null)  
  17.             currentNode = currentNode.parent;  
  18.   
  19.           --currentDepth;  
  20.         }  
  21.         <span style="color:#ff6666;">currentNode = new ViewNode(window, currentNode, line.substring(depth));  
  22. </span>        currentDepth = depth;  
  23.       }  
  24.     } catch (IOException e) {  
  25.       Log.e("hierarchyviewer""Error reading view hierarchy stream: " + e.getMessage());  
  26.       return null;  
  27.     }  
  28.     if (currentNode == null)  
  29.       return null;  
  30.   
  31.     while (currentNode.parent != null) {  
  32.       currentNode = currentNode.parent;  
  33.     }  
  34.   
  35.     return currentNode;  
  36.   }  

 

        通过标红的代码可知,创建ViewNode对象的时候,传入三个参数:window,currentNode,读取的行数据去掉空格后的line.找到ViewNode类。

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.android.hierarchyviewerlib.models;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Collections;  
  5. import java.util.Comparator;  
  6. import java.util.HashMap;  
  7. import java.util.Iterator;  
  8. import java.util.List;  
  9. import java.util.Map;  
  10. import java.util.Set;  
  11. import java.util.TreeSet;  
  12. import org.eclipse.swt.graphics.Image;  
  13.   
  14. public class ViewNode  
  15. {  
  16.   private static final double RED_THRESHOLD = 0.80000000000000004D;  
  17.   private static final double YELLOW_THRESHOLD = 0.5D;  
  18.   public static final String MISCELLANIOUS = "miscellaneous";  
  19.   public String id;  
  20.   public String name;  
  21.   public String hashCode;  
  22.   public List<Property> properties = new ArrayList();  
  23.   public Map<String, Property> namedProperties = new HashMap();  
  24.   public ViewNode parent;  
  25.   public List<ViewNode> children = new ArrayList();  
  26.   public int left;  
  27.   public int top;  
  28.   public int width;  
  29.   public int height;  
  30.   public int scrollX;  
  31.   public int scrollY;  
  32.   public int paddingLeft;  
  33.   public int paddingRight;  
  34.   public int paddingTop;  
  35.   public int paddingBottom;  
  36.   public int marginLeft;  
  37.   public int marginRight;  
  38.   public int marginTop;  
  39.   public int marginBottom;  
  40.   public int baseline;  
  41.   public boolean willNotDraw;  
  42.   public boolean hasMargins;  
  43.   public boolean hasFocus;  
  44.   public int index;  
  45.   public double measureTime;  
  46.   public double layoutTime;  
  47.   public double drawTime;  
  48.   public ProfileRating measureRating = ProfileRating.NONE;  
  49.   public ProfileRating layoutRating = ProfileRating.NONE;  
  50.   public ProfileRating drawRating = ProfileRating.NONE;  
  51.   public Set<String> categories = new TreeSet();  
  52.   public Window window;  
  53.   public Image image;  
  54.   public int imageReferences = 1;  
  55.   public int viewCount;  
  56.   public boolean filtered;  
  57.   public int protocolVersion;  
  58.   
  59.   public ViewNode(Window window, ViewNode parent, String data)  
  60.   {  
  61.     <span style="color:#ff6666;">this.window = window;  
  62.     this.parent = parent;  
  63.     this.index = ((this.parent == null) ? 0 : this.parent.children.size());  
  64.     if (this.parent != null)  
  65.       this.parent.children.add(this);  
  66.   
  67.     int delimIndex = data.indexOf(64);  
  68.     if (delimIndex < 0)  
  69.       throw new IllegalArgumentException("Invalid format for ViewNode, missing @: " + data);  
  70.   
  71.     this.name = data.substring(0, delimIndex);  
  72.     data = data.substring(delimIndex + 1);  
  73.     delimIndex = data.indexOf(32);  
  74.     this.hashCode = data.substring(0, delimIndex);  
  75.   
  76.     if (data.length() > delimIndex + 1) {  
  77.       loadProperties(data.substring(delimIndex + 1).trim());  
  78.     }  
  79.     else {  
  80.       this.id = "unknown";  
  81.       this.width = (this.height = 10);  
  82.     }  
  83.   
  84. </span>    this.measureTime = -1.0D;  
  85.     this.layoutTime = -1.0D;  
  86.     this.drawTime = -1.0D;  
  87.   }  
  88.   
  89.   public void dispose() {  
  90.     int N = this.children.size();  
  91.     for (int i = 0; i < N; ++i)  
  92.       ((ViewNode)this.children.get(i)).dispose();  
  93.   
  94.     dereferenceImage();  
  95.   }  
  96.   
  97.   public void referenceImage() {  
  98.     this.imageReferences += 1;  
  99.   }  
  100.   
  101.   public void dereferenceImage() {  
  102.     this.imageReferences -= 1;  
  103.     if ((this.image != null) && (this.imageReferences == 0))  
  104.       this.image.dispose();  
  105.   }  
  106.   
  107.   private void loadProperties(String data)  
  108.   {  
  109.     boolean stop;  
  110.     int start = 0;  
  111.     do  
  112.     {  
  113.       int index = data.indexOf(61, start);  
  114.       Property property = new Property();  
  115.       property.name = data.substring(start, index);  
  116.   
  117.       int index2 = data.indexOf(44, index + 1);  
  118.       int length = Integer.parseInt(data.substring(index + 1, index2));  
  119.       start = index2 + 1 + length;  
  120.       property.value = data.substring(index2 + 1, index2 + 1 + length);  
  121.   
  122.       this.properties.add(property);  
  123.       this.namedProperties.put(property.name, property);  
  124.   
  125.       stop = start >= data.length();  
  126.       if (!(stop))  
  127.         ++start;  
  128.     }  
  129.     while (!(stop));  
  130.   
  131.     Collections.sort(this.properties, new Comparator(this)  
  132.     {  
  133.       public int compare(, ViewNode.Property destination) {  
  134.         return source.name.compareTo(destination.name);  
  135.       }  
  136.   
  137.     });  
  138.     this.id = ((Property)this.namedProperties.get("mID")).value;  
  139.   
  140.     this.left = ((this.namedProperties.containsKey("mLeft")) ? getInt("mLeft"0) : getInt("layout:mLeft"0));  
  141.   
  142.     this.top = ((this.namedProperties.containsKey("mTop")) ? getInt("mTop"0) : getInt("layout:mTop"0));  
  143.     this.width = ((this.namedProperties.containsKey("getWidth()")) ? getInt("getWidth()"0) : getInt("layout:getWidth()"0));  
  144.   
  145.     this.height = ((this.namedProperties.containsKey("getHeight()")) ? getInt("getHeight()"0) : getInt("layout:getHeight()"0));  
  146.   
  147.     this.scrollX = ((this.namedProperties.containsKey("mScrollX")) ? getInt("mScrollX"0) : getInt("scrolling:mScrollX"0));  
  148.   
  149.     this.scrollY = ((this.namedProperties.containsKey("mScrollY")) ? getInt("mScrollY"0) : getInt("scrolling:mScrollY"0));  
  150.   
  151.     this.paddingLeft = ((this.namedProperties.containsKey("mPaddingLeft")) ? getInt("mPaddingLeft"0) : getInt("padding:mPaddingLeft"0));  
  152.   
  153.     this.paddingRight = ((this.namedProperties.containsKey("mPaddingRight")) ? getInt("mPaddingRight"0) : getInt("padding:mPaddingRight"0));  
  154.   
  155.     this.paddingTop = ((this.namedProperties.containsKey("mPaddingTop")) ? getInt("mPaddingTop"0) : getInt("padding:mPaddingTop"0));  
  156.   
  157.     this.paddingBottom = ((this.namedProperties.containsKey("mPaddingBottom")) ? getInt("mPaddingBottom"0) : getInt("padding:mPaddingBottom"0));  
  158.   
  159.     this.marginLeft = ((this.namedProperties.containsKey("layout_leftMargin")) ? getInt("layout_leftMargin", -2147483648) : getInt("layout:layout_leftMargin", -2147483648));  
  160.   
  161.     this.marginRight = ((this.namedProperties.containsKey("layout_rightMargin")) ? getInt("layout_rightMargin", -2147483648) : getInt("layout:layout_rightMargin", -2147483648));  
  162.   
  163.     this.marginTop = ((this.namedProperties.containsKey("layout_topMargin")) ? getInt("layout_topMargin", -2147483648) : getInt("layout:layout_topMargin", -2147483648));  
  164.   
  165.     this.marginBottom = ((this.namedProperties.containsKey("layout_bottomMargin")) ? getInt("layout_bottomMargin", -2147483648) : getInt("layout:layout_bottomMargin", -2147483648));  
  166.   
  167.     this.baseline = ((this.namedProperties.containsKey("getBaseline()")) ? getInt("getBaseline()"0) : getInt("layout:getBaseline()"0));  
  168.   
  169.     this.willNotDraw = ((this.namedProperties.containsKey("willNotDraw()")) ? getBoolean("willNotDraw()"false) : getBoolean("drawing:willNotDraw()"false));  
  170.   
  171.     this.hasFocus = ((this.namedProperties.containsKey("hasFocus()")) ? getBoolean("hasFocus()"false) : getBoolean("focus:hasFocus()"false));  
  172.   
  173.     this.hasMargins = ((this.marginLeft != -2147483648) && (this.marginRight != -2147483648) && (this.marginTop != -2147483648) && (this.marginBottom != -2147483648));  
  174.   
  175.     for (Iterator i$ = this.namedProperties.keySet().iterator(); i$.hasNext(); ) { String name = (String)i$.next();  
  176.       int index = name.indexOf(58);  
  177.       if (index != -1)  
  178.         this.categories.add(name.substring(0, index));  
  179.     }  
  180.   
  181.     if (this.categories.size() != 0)  
  182.       this.categories.add("miscellaneous");  
  183.   }  
  184.   
  185.   public void setProfileRatings()  
  186.   {  
  187.     int N = this.children.size();  
  188.     if (N > 1) {  
  189.       ViewNode child;  
  190.       double totalMeasure = 0D;  
  191.       double totalLayout = 0D;  
  192.       double totalDraw = 0D;  
  193.       for (int i = 0; i < N; ++i) {  
  194.         child = (ViewNode)this.children.get(i);  
  195.         totalMeasure += child.measureTime;  
  196.         totalLayout += child.layoutTime;  
  197.         totalDraw += child.drawTime;  
  198.       }  
  199.       for (i = 0; i < N; ++i) {  
  200.         child = (ViewNode)this.children.get(i);  
  201.         if (child.measureTime / totalMeasure >= 0.80000000000000004D)  
  202.           child.measureRating = ProfileRating.RED;  
  203.         else if (child.measureTime / totalMeasure >= 0.5D)  
  204.           child.measureRating = ProfileRating.YELLOW;  
  205.         else  
  206.           child.measureRating = ProfileRating.GREEN;  
  207.   
  208.         if (child.layoutTime / totalLayout >= 0.80000000000000004D)  
  209.           child.layoutRating = ProfileRating.RED;  
  210.         else if (child.layoutTime / totalLayout >= 0.5D)  
  211.           child.layoutRating = ProfileRating.YELLOW;  
  212.         else  
  213.           child.layoutRating = ProfileRating.GREEN;  
  214.   
  215.         if (child.drawTime / totalDraw >= 0.80000000000000004D)  
  216.           child.drawRating = ProfileRating.RED;  
  217.         else if (child.drawTime / totalDraw >= 0.5D)  
  218.           child.drawRating = ProfileRating.YELLOW;  
  219.         else  
  220.           child.drawRating = ProfileRating.GREEN;  
  221.       }  
  222.     }  
  223.   
  224.     for (int i = 0; i < N; ++i)  
  225.       ((ViewNode)this.children.get(i)).setProfileRatings();  
  226.   }  
  227.   
  228.   public void setViewCount()  
  229.   {  
  230.     this.viewCount = 1;  
  231.     int N = this.children.size();  
  232.     for (int i = 0; i < N; ++i) {  
  233.       ViewNode child = (ViewNode)this.children.get(i);  
  234.       child.setViewCount();  
  235.       this.viewCount += child.viewCount;  
  236.     }  
  237.   }  
  238.   
  239.   public void filter(String text) {  
  240.     int dotIndex = this.name.lastIndexOf(46);  
  241.     String shortName = (dotIndex == -1) ? this.name : this.name.substring(dotIndex + 1);  
  242.     this.filtered = ((!(text.equals(""))) && (((shortName.toLowerCase().contains(text.toLowerCase())) || ((!(this.id.equals("NO_ID"))) && (this.id.toLowerCase().contains(text.toLowerCase()))))));  
  243.   
  244.     int N = this.children.size();  
  245.     for (int i = 0; i < N; ++i)  
  246.       ((ViewNode)this.children.get(i)).filter(text);  
  247.   }  
  248.   
  249.   private boolean getBoolean(String name, boolean defaultValue)  
  250.   {  
  251.     Property p = (Property)this.namedProperties.get(name);  
  252.     if (p != null)  
  253.       try {  
  254.         return Boolean.parseBoolean(p.value);  
  255.       } catch (NumberFormatException e) {  
  256.         return defaultValue;  
  257.       }  
  258.   
  259.     return defaultValue;  
  260.   }  
  261.   
  262.   private int getInt(String name, int defaultValue) {  
  263.     Property p = (Property)this.namedProperties.get(name);  
  264.     if (p != null)  
  265.       try {  
  266.         return Integer.parseInt(p.value);  
  267.       } catch (NumberFormatException e) {  
  268.         return defaultValue;  
  269.       }  
  270.   
  271.     return defaultValue;  
  272.   }  
  273.   
  274.   public String toString()  
  275.   {  
  276.     return this.name + "@" + this.hashCode;  
  277.   }  
  278.   
  279.   public static class Property  
  280.   {  
  281.     public String name;  
  282.     public String value;  
  283.   
  284.     public String toString()  
  285.     {  
  286.       return this.name + '=' + this.value;  
  287.     }  
  288.   }  
  289.   
  290.   public static enum ProfileRating  
  291.   {  
  292.     RED, YELLOW, GREEN, NONE;  
  293.   }  
  294. }  


        在构造方法中,我了解到了它把传递过来的ViewNode对象作为当前对象的父对象,又把当前对象作为父节点的子节点。形成一个链表结构,这样我通过最上层的根节点就可以获得所有节点(佩服佩服,后悔自己数据结构和算法没学好啊!)。一行currentNode = new ViewNode(window, currentNode, line.substring(depth));解决了所有烦恼,就是理解的时候要花费一点时间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值