zoukankan      html  css  js  c++  java
  • Android工具HierarchyViewer代码导读(4) 前台代码

    在前文<Android工具HierarchyViewer 代码导读(3) -- 后台代码>中,我们讲解了HierarchyViewe的后台代码,指的是HierarchyViewer如何通过ADB和ViewServer这两个信道和Android设备进行通信,获取Acitivities信息、控件信息和控件截图等信息。本文将讲解HierarchyViewer的前台代码,指的是在后台获取到数据后,HierarchyViewer是如何显示他们的;当用户对视图进行操作时,如选中、放大缩小等,视图是如何响应的。

    MVC模式

    前文中我们提到,HierarchyViewer代码采用的是典型的MVC构架,我们把上文中使用的MVC模式图再拿出来(这里只讨论控件层次图界面相关的代码结构):

    image

    其中,在TreeViewModel.java文件中定义了ITreeChangeListener接口

        public static interface ITreeChangeListener {
            public void treeChanged();
    
            public void selectionChanged();
    
            public void viewportChanged();
    
            public void zoomChanged();
        }

    所有的Views – LayoutViewer, TreeViewer, PropertyViewer, TreeViewOverview, TreeViewControllers都实现了该接口。 TreeViewModel维护了一个ITreeChangeListener的ArrayList:

        private final ArrayList<ITreeChangeListener> mTreeChangeListeners =
                new ArrayList<ITreeChangeListener>();

    当Views构造时,都会把自己加到mTreeChangeListeners中,当TreeViewModel中的数据改变时,TreeViewModel通过事件通知所有注册到mTreeCHangeListeners中的Views。

    这些事件包括:

    treeChanged -- 整个TreeView改变时触发

    selectionChanged -- 选中的节点改变时触发

    viewportChanged -- 当前视见区改变时触发

    zoomChanged -- 当前放大缩小比例改变时触发

    TreeViewModel中保存了四个数据:

        private DrawableViewNode mTree; //整个控件树
    
        private DrawableViewNode mSelectedNode; //当前选中的控件树
    
        private Rectangle mViewport; //视见区
    
        private double mZoom;  //放大缩小比例

    Views通过读取4个数据进绘制或显示。

    TreeView加载

    当用户在主界面双击某个Activity,或者在查看控件树界面点击刷新时,整个TreeView将重新加载。双击或者刷新操作将最终调用HierarchyViewerDirector.java的loadViewTreeData方法:

        public void loadViewTreeData(final Window window) {
            executeInBackground("Loading view hierarchy", new Runnable() {
                public void run() {
    
                    mFilterText = ""; //$NON-NLS-1$
    
                    ViewNode viewNode = DeviceBridge.loadWindowData(window);
                    if (viewNode != null) {
                        DeviceBridge.loadProfileData(window, viewNode);
                        viewNode.setViewCount();
                        TreeViewModel.getModel().setData(window, viewNode);
                    }
                }
            });
        }

    这个函数我们在上文中已经提到过,本文主要关心其中2个函数:

    DeviceBridge.loadWindowData(window) -- 这个函数做了两件事情:1)向ViewServer发送DUMP命令,来获取Acitivity所有控件的信息。 2)获取到的控件树信息是文本的形式返回的,如下是其中一个控件的文本信息:

    android.widget.FrameLayout@44edba90 mForeground=52,android.graphics.drawable.NinePatchDrawable@44edc1e0 mForegroundInPadding=5,false mForegroundPaddingBottom=1,0 mForegroundPaddingLeft=1,0 mForegroundPaddingRight=1,0 mForegroundPaddingTop=1,0 mMeasureAllChildren=5,false mForegroundGravity=2,55 getDescendantFocusability()=24,FOCUS_BEFORE_DESCENDANTS getPersistentDrawingCache()=9,SCROLLING isAlwaysDrawnWithCacheEnabled()=4,true isAnimationCacheEnabled()=4,true isChildrenDrawingOrderEnabled()=5,false isChildrenDrawnWithCacheEnabled()=5,false mMinWidth=1,0 mPaddingBottom=1,0 mPaddingLeft=1,0 mPaddingRight=1,0 mPaddingTop=2,38 mMinHeight=1,0 mMeasuredWidth=3,480 mMeasuredHeight=3,800 mLeft=1,0 mPrivateFlags_DRAWING_CACHE_INVALID=3,0x0 mPrivateFlags_DRAWN=4,0x20 mPrivateFlags=8,16911408 mID=10,id/content mRight=3,480 mScrollX=1,0 mScrollY=1,0 mTop=1,0 mBottom=3,800 mUserPaddingBottom=1,0 mUserPaddingRight=1,0 mViewFlags=9,402653186 getBaseline()=2,-1 getHeight()=3,800 layout_bottomMargin=1,0 layout_leftMargin=1,0 layout_rightMargin=1,0 layout_topMargin=1,0 layout_height=12,MATCH_PARENT layout_width=12,MATCH_PARENT getTag()=4,null getVisibility()=7,VISIBLE getWidth()=3,480 hasFocus()=5,false isClickable()=5,false isDrawingCacheEnabled()=5,false isEnabled()=4,true isFocusable()=5,false isFocusableInTouchMode()=5,false isFocused()=5,false isHapticFeedbackEnabled()=4,true isInTouchMode()=4,true isOpaque()=5,false isSelected()=5,false isSoundEffectsEnabled()=4,true willNotCacheDrawing()=5,false willNotDraw()=5,false 

    该文本将被解析,所有信息将保存在ViewNode对象中。文本中所有的属性都同时保存在ViewNode的List<Property> properties和Map<String, Property> namedProperties中,一些和绘制视图相关的属性,如top,paddingLeft,marginBottom等等,除了保存在properties和namedProperties中,还将直接保存在ViewNode的成员变量中。

    ViewNode是一个树,每个ViewNode节点中保存了它的父节点和子节点。文本解析的时候,是如何确定ViewNode父节点的呢?原来每行文本信息前面都有若干个空格,空格的数量决定了这个节点的深度,如5个空格表示这个节点在第6层,它的父节点就是最近收到的,有4个空格的节点。具体解析过程大家可以深入阅读loadWindowData函数。

    TreeViewModel.getModel().setData(window, viewNode) -- 更新TreeViewModel的TreeView

    让我们step into TreeViewModel.getModel().setData(window, viewNode)函数:

        public void setData(Window window, ViewNode viewNode) {
            synchronized (this) {
                if (mTree != null) {
                    mTree.viewNode.dispose();
                }
                this.mWindow = window;
                if (viewNode == null) {
                    mTree = null;
                } else {
                    mTree = new DrawableViewNode(viewNode);
                    mTree.setLeft();
                    mTree.placeRoot();
                }
                mViewport = null;
                mZoom = 1;
                mSelectedNode = null;
            }
            notifyTreeChanged();
        }

    以上函数中:

    mTree = new DrawableViewNode(viewNode) –通过ViewNode树来构造DrawableViewNode树。为什么已经有了ViewNode结构还要再构造一个DrawableViewNode结构呢? 它们的功能是不同的,ViewNode是面向数据的,它对应的是Acitivity中每个控件节点的信息; 而DrawableViewNode面向的是图形绘制,它通过计算ViewNode中提供的数据,确定如何在Hierarchy view中进行绘制。读者深入阅读该构造函数,它的作用是根据ViewNode来递归地构造整个DrawableViewNode控件树,并根据每个子树的size确定每个子树在Hierarchy view绘制时中占据的高度。

    mTree.setLeft()  -- 计算树中每个节点在Hierarchy view绘制时的left值。

    mTree.placeRoot() -- 计算树中每个节点在Hierarchy view绘制时的top值。

    mViewport = null,mZoom = 1,mSelectedNode = null -- 初始化视见区,放大缩小比例和当前选中节点。

    notifyTreeChanged() -- 触发treeChanged事件。

    最后,TreeViewOverview.java, LayoutViewer, TreeViewer都是通过响应treeChanged事件,并最终调用PaintListener事件,根据TreeViewModel中的mTree,mViewport,mZoom,mSelectedNode的数据来绘制图形的(这3个类都是继承Canvas类)。

    这3个类中的PaintListener事件中图形绘制的代码都很值得一读,但本文限于篇幅不能详细介绍了。

    用户事件响应

    当用户在一个View中进行操作,其他View也会响应这个操作。如在TreeView中滚动滚轮,TreeViewOverview也会跟着放大缩小;在LayoutViewer中选中某个节点,TreeView和TreeViewOverview中也会跟着选中,这一切是怎么发生的呢?

    通过上一节,其实我们很容易理解HierarchyViewer是怎么做的了,这还是一个经典的MVC模式的例子:TreeViewModel提供了如下公开方法(加上上节中的setData方法,一共4个方法)来改变TreeViewModel中的数据:

     public void setSelection(DrawableViewNode selectedNode)
     public void setViewport(Rectangle viewport)
     public void setZoom(double newZoom)

    当在某View中选中节点时,移动视见区,放大缩小时,View将调用对应的方法来修改TreeViewModel中的数据,然后对应的事件 -- selectionChanged,viewportChanged和zoomChanged将被触发,Views通过响应这些事件,在PaintListener中重绘图形。这是一个用户操作View,View调用Model,Model触发事件,Views响应事件的过程。

    Note:

    1)不是所有的Views都关心所有的事件。如LayoutViewer不关心zoomChanged和viewportChanged事件;PropertyViewer只关心selectionChanged事件。

    2)用户选中一个节点时,需要进行坐标转换,遍历所有的点才能找到选中的节点;在LayoutViewer中,需要找到的是符合条件的,层次低的节点。

    本系列到此结束。我相信阅读HierarchyViewer和其他一些sdk工具的源代码,对于理解Android的机制是有帮助的。同时,对于学习MVC也会助益不少,google工程师的代码的确很简洁优秀。

    本文由知平软件刘斌华原创,转载请注明出处。

    知平软件致力于移动平台自动化测试技术的研究,我们希望通过向社区贡献知识和开源项目,来促进行业和自身的发展。

  • 相关阅读:
    HDU 6143 Killer Names【dp递推】【好题】【思维题】【阅读题】
    HDU 6143 Killer Names【dp递推】【好题】【思维题】【阅读题】
    POJ 3974 Palindrome【manacher】【模板题】【模板】
    POJ 3974 Palindrome【manacher】【模板题】【模板】
    HDU 6127 Hard challenge【计算机几何】【思维题】
    HDU 6127 Hard challenge【计算机几何】【思维题】
    HDU 6129 Just do it【杨辉三角】【思维题】【好题】
    HDU 6129 Just do it【杨辉三角】【思维题】【好题】
    HDU 3037 Saving Beans【Lucas定理】【模板题】【模板】【组合数取余】
    8.Math 对象
  • 原文地址:https://www.cnblogs.com/vowei/p/2650722.html
Copyright © 2011-2022 走看看