在上文
把复杂的代码讲解清楚通常都不是很容易的事情,为了避免把本文写成流水账,文章将尽可能集中在HierarchyViewer后台代码的主要脉络上,许多细节须要读者本身去阅读,那是必须的。java
MVC模式node
HierarchyViewer采用典型的MVC模式设计。android
当打开HierarchyViewer,进入主界面时,其对应的MVC模式是:HierarchyViewerDirector.java是Controller,DeviceSelectionModel.java是Model,DeviceSelector是View,以下图所示:windows
当双击某个Acitivity,进入浏览层次图界面时,其对应的MVC模式是:HierarchyViewerDirector.java是Controller,TreeViewModel.java是Model,Views是TreeViewController.java、TreeViewOverview.java、PropertyViewer.java、TreeViewer.java、LayoutViewer.java: eclipse
HierachyViewerDirector.java(即Controller)经过DeviceBridge.java来和Android设备通讯,而DeviceBridge.java具体是经过AndroidDebugBridage.java和DeviceConnection.java来和设备通讯。以下图所示:ide
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显示出来了:
读取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树完成了绘制:
加载控件截图
这时,当用户选中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进行更新,因而咱们看到截图在气泡中显示出来:
总结语
咱们试图理清HierarchyViewer后台代码的主要脉络,同时咱们彷佛也“遗漏”了更多内容:咱们没有阅读DeviceBridge.java看它都支持哪些ViewServer命令 -- 咱们已经知道的有LIST、DUMP、CAPTURE;咱们没有深刻阅读AndroidDebugBridge.java是如何工做的(也许不久后我就会写这方面的文章);咱们也没有阅读当设备断开、改变时,当进行刷新等操做时的代码。 我想我不能剥夺你们本身去阅读代码的乐趣。
本系列的最后一篇,咱们将阅读HierarchyViewer的前台代码。
知平软件致力于移动平台自动化测试技术的研究,咱们但愿经过向社区贡献知识和开源项目,来促进行业和自身的发展。