在上文中,我们讲解了如何把HierarchyViewer的项目导入到Eclipse中,以便更高效阅读代码。本文将讲解HierarchyViewer的后台代码,建议大家可以先阅读<Android工具HierarchyViewer代码导读(1) -- 功能实现演示>一文, 其中的代码演示了HierarchyViewer的主要功能。而本文就是讲解HierarchyViewer是如何实现功能的。

把复杂的代码讲解清楚一般都不是很容易的事情,为了不把本文写成流水帐,文章将尽量集中在HierarchyViewer后台代码的主要脉络上,许多细节需要读者自己去阅读,那是必须的。

MVC模式

HierarchyViewer采用典型的MVC模式设计。

当打开HierarchyViewer,进入主界面时,其对应的MVC模式是:HierarchyViewerDirector.java是Controller,DeviceSelectionModel.java是Model,DeviceSelector是View,如下图所示:

当双击某个Acitivity,进入浏览层次图界面时,其对应的MVC模式是:HierarchyViewerDirector.java是Controller,TreeViewModel.java是Model,Views是TreeViewController.java、TreeViewOverview.java、PropertyViewer.java、TreeViewer.java、LayoutViewer.java:

HierachyViewerDirector.java(即Controller)通过DeviceBridge.java来和Android设备通信,而DeviceBridge.java具体是通过AndroidDebugBridage.java和DeviceConnection.java来和设备通信。如下图所示:

AndroidDebugBridge.java : AndroidDebugBridge.java是ADB API,位于ddmlib项目中。 它实现了命令行版adb一样的功能,在HierarchyViewer中主要用到其连接设备,forward端口,启动ViewServer等操作。

DeviceConnection.java: 负责和ViewServer通信,向ViewServer发送命令并接受其返回的信息。从而获取Activity列表、控件层次结构图、截图等。

入口点

后台代码的入口点在HierarchyViewerApplication.java的createContents method中:

@Overrideprotected Control createContents(Composite parent) {// create this only once the window is opened to please SWT on MacmDirector = 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的前台代码。

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

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

转载于:https://www.cnblogs.com/vowei/archive/2012/08/08/2627614.html

Android工具HierarchyViewer 代码导读(3) -- 后台代码相关推荐

  1. 网页服务器后台代码,菜谱网站后台代码

    菜谱网站后台代码 内容精选 换一换 购买并编辑云速建站后,您可以通过以下方式,查看网站编辑效果.如果您的网站尚未绑定自己的域名,使用测试域名访问网站,具体请参考本章节.如果您的网站已绑定自己的域名,使 ...

  2. Android工具修复属性,Android 热修复介绍之代码修复

    什么是Android热修复技术 简单来说就是不重新安装apk的情况下,通过补丁,修复bug 正常开发流程 热修复开发流程 目前主流的热修复技术框架 阿里系的: Andfix.Hotfix.Sophix ...

  3. android 后台截屏代码,Android实现截图和分享功能的代码

    先给大家展示下效果图吧 直接上代码: xml的布局: android:id="@+id/btn_jp" android:layout_marginTop="10dip&q ...

  4. 【Android NDK 开发】NDK C/C++ 代码崩溃调试 - Tombstone 报错信息日志文件分析 ( 使用 addr2line 命令行工具查找动态库中的报错代码位置 )

    文章目录 一.从 Tombstone 报错日志中查找报错动态库 二.addr2line 命令行工具使用 64 位动态库使用的 aarch64-linux-android-addr2line.exe 工 ...

  5. c java 开发android_java代码与纯C代码混编完成android应用的开发

    在我们这个java与C语言的调用中,分为两个部分, 第一部分是java语言调用C语言的C库,也就是java调C; 第二部分是C语言调用java语言. 这里我主要讲解一下第一种java语言调用C库 这里 ...

  6. Caffe代码导读(4):数据集准备

    转载自: Caffe代码导读(4):数据集准备 - 卜居 - 博客频道 - CSDN.NET http://blog.csdn.net/kkk584520/article/details/416492 ...

  7. Caffe代码导读(1):Protobuf例子

    转载自: Caffe代码导读(1):Protobuf例子 - 卜居 - 博客频道 - CSDN.NET http://blog.csdn.net/kkk584520/article/details/4 ...

  8. Caffe代码导读(0):路线图

    转载自: Caffe代码导读(0):路线图 - 卜居 - 博客频道 - CSDN.NET http://blog.csdn.net/kkk584520/article/details/41681085 ...

  9. 8/人天,小记一次 JAVA(APP后台) 项目改造 .NET 过程(后台代码已完整开源于 Github)...

    Github: https://github.com/iccb1013/Jade.Net 我们只消耗了8/人天的时间,完成了全部工作,基于我们 Jade.Net 的开源后台代码,任何小规模的后台管理系 ...

最新文章

  1. 再见了,收费的 Navicat!
  2. ubuntu 各版本的区别
  3. node.js入门 - 12.api:进程(process)
  4. 使SSH不用输入密码
  5. Web开发-Django初识及实战
  6. vue中传值和传引用_vue prop属性传值与传引用示例
  7. 矩阵的Cholesky分解
  8. 【重难点】【Redis 01】为什么使用 Redis、Redis 的线程模型、Redis 的数据类型及其底层数据结构
  9. Android 功耗(8)---如何找到阻止进入deep idle / SODI的元凶
  10. python万年历实验报告_Python实现的简单万年历例子分享
  11. 修复群集无法切换磁盘问题
  12. layui的tree实现 struts 2+layui+jsp
  13. Spring Boot 2.3.3 正式发布!2.4.0 正式版即将发布!
  14. 利用WireShark破解网站密码
  15. Podfile文件用法详解
  16. 初中英语听力计算机考试反馈,【初中英语】英语听说机考,怎样才能有效提高英语听力和口语水平?...
  17. 【胡学长 带你学 Global Mapper 】Global Mapper Pro 23.1 -x64安装教程(附*英*软件包下载)
  18. cad沿线插入块 lisp_我有一组数有十几万个坐标点,如何利用lisp程序快速导入CAD中,并可以快速处理!...
  19. java 跨年 周计算公式_Java关于周跨年的周数计算
  20. ArcGIS Pro数据加载学习总结

热门文章

  1. QT 手动创建信号函数 与 槽函数
  2. python api接口生成_Django 自动生成api接口文档教程
  3. linux 打包排除多个目录,tar打包整个目录(可排除子目录)几种方法
  4. struct类型重定义 不同的基类型_C++构造数据类型
  5. 【渝粤题库】陕西师范大学210023 学前儿童社会教育 作业(专升本)
  6. 国家开放大学2021春1378管理英语3题目
  7. 擦地机器人修理_自带眼睛还有嘴,喷水式擦地机器人效果实测
  8. 数字信号处理基础知识之DFT、DTFT、DFS、FFT基本概念扫盲
  9. html5贝塞尔,使用HTML5画布绘制贝塞尔曲线
  10. es6 遍历数组对象获取所有的id_ES6对象遍历Object.keys()方法