Android工具HierarchyViewer 代碼導讀(3) -- 後臺代碼

在上文 中,咱們講解了如何把HierarchyViewer的項目導入到Eclipse中,以便更高效閱讀代碼。本文將講解HierarchyViewer的後臺代碼,建議你們能夠先閱讀< Android工具HierarchyViewer代碼導讀(1) -- 功能實現演示>一文, 其中的代碼演示了HierarchyViewer的主要功能。而本文就是講解HierarchyViewer是如何實現功能的。 html

 

把複雜的代碼講解清楚通常都不是很容易的事情,爲了避免把本文寫成流水賬,文章將盡可能集中在HierarchyViewer後臺代碼的主要脈絡上,許多細節須要讀者本身去閱讀,那是必須的。java

 

MVC模式node

HierarchyViewer採用典型的MVC模式設計。android

當打開HierarchyViewer,進入主界面時,其對應的MVC模式是:HierarchyViewerDirector.java是Controller,DeviceSelectionModel.java是Model,DeviceSelector是View,以下圖所示:windows

image

 

當雙擊某個Acitivity,進入瀏覽層次圖界面時,其對應的MVC模式是:HierarchyViewerDirector.java是Controller,TreeViewModel.java是Model,Views是TreeViewController.java、TreeViewOverview.java、PropertyViewer.java、TreeViewer.java、LayoutViewer.java: eclipse

image

 

HierachyViewerDirector.java(即Controller)經過DeviceBridge.java來和Android設備通訊,而DeviceBridge.java具體是經過AndroidDebugBridage.java和DeviceConnection.java來和設備通訊。以下圖所示:ide

image

 

AndroidDebugBridge.java : AndroidDebugBridge.java是ADB API,位於ddmlib項目中。 它實現了命令行版adb同樣的功能,在HierarchyViewer中主要用到其鏈接設備,forward端口,啓動ViewServer等操做。函數

DeviceConnection.java: 負責和ViewServer通訊,向ViewServer發送命令並接受其返回的信息。從而獲取Activity列表、控件層次結構圖、截圖等。工具

 

入口點測試

後臺代碼的入口點在HierarchyViewerApplication.java的createContents method中:

@Override
    protected Control createContents(Composite parent) {
        // create this only once the window is opened to please SWT on Mac
        mDirector = HierarchyViewerApplicationDirector.createDirector();
        mDirector.initDebugBridge();
        mDirector.startListenForDevices();
        mDirector.populateDeviceSelectionModel();
       //... ...
    }

以上代碼作了以下工做:

1,HierarchyViewerApplicationDirector.createDirector() -- 建立一個HierarchyViewerDirector對象

2,mDirector.initDebugBridge() -- 初始化AndroidDebugBridge

3,mDirector.startListenForDevices() -- 把mDirctor註冊爲AndroidDebugBridge的監聽者(HierarchyViewerDirector繼承了IDeviceChangeListener接口),當有設備鏈接、斷開、改變時,mDirctor將接收到事件。

4,mDirector.populateDeviceSelectionModel() -- 獲取當前已經鏈接的設備列表,處理並顯示它們。

 

閱讀populateDeviceSelectionModel()函數你會發現, 其中獲取到當前已經鏈接的全部設備列表後,是經過deviceConnected函數來「處理」這些設備;當有新設備鏈接觸發設備鏈接事件時,也是經過deviceConnected函數來「處理」它。

 

啓動並鏈接設備的ViewServer,獲取Activities並顯示列表

HierarchyViewerDirector的deviceConnected 方法,是對IDeviceChangeListener接口方法的實現,咱們來看它是如何「處理」一臺和adb創建鏈接的設備的:

public void deviceConnected(final IDevice device) {
        executeInBackground("Connecting device", new Runnable() {
            public void run() {
                if (DeviceSelectionModel.getModel().containsDevice(device)) {
                    windowsChanged(device);
                } else if (device.isOnline()) {
                    DeviceBridge.setupDeviceForward(device);
                    if (!DeviceBridge.isViewServerRunning(device)) {
                        if (!DeviceBridge.startViewServer(device)) {
                            // Let's do something interesting here... Try again
                            // in 2 seconds.
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException e) {
                            }
                            if (!DeviceBridge.startViewServer(device)) {
                                Log.e(TAG, "Unable to debug device " + device);
                                DeviceBridge.removeDeviceForward(device);
                            } else {
                                loadViewServerInfoAndWindows(device);
                            }
                            return;
                        }
                    }
                    loadViewServerInfoAndWindows(device);
                }
            }
        });
    }

在這個方法中作了以下事情:

1)DeviceBridge.setupDeviceForward(device) -- 把該設備的4939端口映射到本地端口。 HierarchyViewer維護一個列表 --sDevicePortMap,它記錄哪一個設備被映射到了哪一個本地端口。

2)DeviceBridge.isViewServerRunning(device) -- 判斷該設備的ViewServer是否打開。

3)DeviceBridge.startViewServer(device) -- 打開ViewServer。

4)loadViewServerInfoAndWindows(device) -- 1)獲取該設備ViewServer信息,好比版本信息等 2)獲取該設備其全部活動的Activities(在HierarchyView源代碼中,Activities老是被命名爲Windows)。

(若是讀者不明白以上函數的意義,再次建議閱讀<功能實現演示>)

 

讓咱們"Step Into」,來看看loadViewServerInfoAndWindows方法:

private void loadViewServerInfoAndWindows(final IDevice device) {

        ViewServerInfo viewServerInfo = DeviceBridge.loadViewServerInfo(device);
        if (viewServerInfo == null) {
            return;
        }
        Window[] windows = DeviceBridge.loadWindows(device);
        DeviceSelectionModel.getModel().addDevice(device, windows, viewServerInfo);
        if (viewServerInfo.protocolVersion >= 3) {
            WindowUpdater.startListenForWindowChanges(HierarchyViewerDirector.this, device);
            focusChanged(device);
        }

    }

 

1,DeviceBridge.loadViewServerInfo(device) -- 讀取ViewServer信息。

2,DeviceBridge.loadWindows(device) -- 發送 「LIST」命令給ViewServer,讀取設備全部活動的Activities。

3,DeviceSelectionModel.getModel().addDevice(device, windows, viewServerInfo) -- 更新DeviceSelectionModel數據,而後該Model將經過事件通知Views來更新顯示。

 

咱們到哪了?

在以上代碼完成後,HierarchyViewer完成了主界面的加載,已經鏈接的設備及其活動的Activities顯示出來了:

20120729215028656

讀取Activity的控件層次圖

這時,當用戶雙擊上圖中設備的某個Activity,但願查看其控件層次圖時,事件(DeviceSelector.java中的widgetDefaultSelected事件)將調用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);
                }
            }
        });
    }

 

1,DeviceBridge.loadWindowData(window) -- 讀取Activity的全部控件信息,並把每一個控件的信息構形成一個ViewNode對象,全部的ViewNode組成一個樹,該函數的返回值是樹的根節點。

2,DeviceBridge.loadProfileData(window, viewNode) -- 遍歷整個ViewNode樹,爲樹中的每一個節點向ViewServer讀取ProfileData。遺憾的是,目前爲止我也沒有搞明白ProfileData的做用。

3,viewNode.setViewCount() -- 遍歷整個ViewNode樹,計算每一個子樹所包含的節點數量,保存在ViewNode的viewCount字段中。

4,TreeViewModel.getModel().setData(window, viewNode) -- 更新TreeViewModel的數據源,該Modell將通知全部監聽者 -- TreeViewController.java、TreeViewOverview.java、PropertyViewer.java、TreeViewer.java、LayoutViewer.java來更新視圖。

 

讀者能夠「Step into」 loadWindowData方法,能夠看到它是經過向ViewServer發送」DUMP」命令來獲取整個控件樹信息的。

正如咱們在《功能實現演示》中講到的,ViewServer返回給咱們的控件樹信息是一個內容巨大的文本,HierarchyViewer怎麼把這個文本解析成ViewNode樹的,而TreeViewer.java,LayoutViewer.java等視圖又是如何根據ViewNode來進行繪製的,咱們將是下文《前臺代碼》中講解。

 

咱們到哪了?

如今,咱們獲取到了該Activity的控件樹,而且各個Views – TreeViewer.java、LayoutViewer.java等根據ViewNode樹完成了繪製:

20120729215029821

 

加載控件截圖

這時,當用戶選中hierarchy view(TreeView.java)上的某個節點時,HierarchyViewer將向ViewServer請求該控件的截圖,並顯示在該節點上面的氣泡中,這是怎麼作到的呢?

當點擊hierarchy view上的節點時,TreeView.java上的selectionChanged方法(override ITreeChangeListener接口)被觸發(該事件的觸發過程可能要到下文<前臺代碼>中才能說清楚), 它將調用HierarchyViewerDirector.java的loadCaptureInBackground方法:

public void loadCaptureInBackground(final ViewNode viewNode) {
        executeInBackground("Capturing node", new Runnable() {
            public void run() {
                loadCapture(viewNode);
            }
        });
    }

 

讓咱們「Step into」 loadCapture方法:

public Image loadCapture(ViewNode viewNode) {
        final Image image = DeviceBridge.loadCapture(viewNode.window, viewNode);
        if (image != null) {
            viewNode.image = image;

            // Force the layout viewer to redraw.
            TreeViewModel.getModel().notifySelectionChanged();
        }
        return image;
    }

 

DeviceBridge.loadCapture(viewNode.window, viewNode) -- DeviceConnection.java向ViewServer發送"CAPTURE」命令來獲取控件截圖

viewNode.image = image --把截圖保存在viewNode中,下次再次選中節點時,就不用再向ViewServer請求了

TreeViewModel.getModel().notifySelectionChanged() -- 強制TreeViewModel向監聽者發送SelectionChanged事件。

 

咱們到哪了?

獲取到控件截圖後,TreeViewModel通知hierarchy view進行更新,因而咱們看到截圖在氣泡中顯示出來:

201207292150324989

 

總結語

咱們試圖理清HierarchyViewer後臺代碼的主要脈絡,同時咱們彷佛也「遺漏」了更多內容:咱們沒有閱讀DeviceBridge.java看它都支持哪些ViewServer命令 -- 咱們已經知道的有LIST、DUMP、CAPTURE;咱們沒有深刻閱讀AndroidDebugBridge.java是如何工做的(也許不久後我就會寫這方面的文章);咱們也沒有閱讀當設備斷開、改變時,當進行刷新等操做時的代碼。 我想我不能剝奪你們本身去閱讀代碼的樂趣。

 

本系列的最後一篇,咱們將閱讀HierarchyViewer的前臺代碼。

 

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

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

相關文章
相關標籤/搜索