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工程師的代碼的確很簡潔優秀。

 

本文由知平軟件劉斌華原創,轉載請注明出處。

知平軟件致力於移動平台自動化測試技術的研究,我們希望通過向社區貢獻知識和開源項目,來促進行業和自身的發展。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM