1。后台Android开发者在日常开发中经常需要用到查看视图的功能。AndroidStudio开发团队为我们提供了LayoutInspector插件。新版本提供了LiveLayoutInspector,支持3D,但是LayoutInspector和LiveLayoutInspector都很难用。例如:速度极慢,遇到复杂布局经常超时某些情况下无法选中指定的View2.AccelerateDumpViewHierarchy2.1问题描述开发复杂业务的同学遇到过如上图所示的错误使用LayoutInspector时:由于View树结构的复杂性而超时。网上也有其他相关的解决方案。原理是修改timeout的值。当前默认值为20秒,因此可以将其更改为1分钟。为了更好的解决这个问题,比如说能不能加速?我们来看看整个LayoutInspector的抓包过程。在梳理流程之前,我们需要找到函数的入口。2.2问题分析2.2.1dump整体流程开发者使用LayoutInspector的一般流程一般如下:与Attachdebugger类似,先获取需要LayoutInspector的流程。如果进程中有多个ViewRootImpl,还需要选择窗口。在IDEAPlugin框架体系中,大部分插件的功能入口都依赖于Action。如何找到上图中LayoutInspector的函数入口对应的Action呢?最快最准确的方法是调试。在我们点击函数入口之前,在AnAction#actionPerformed中添加一个断点。从AndroidRunLayoutInspectorAction开始,我们找到了真正的任务:LayoutInspectorCaptureTask。抓取View视图的关键方法如下:我们可以看到首先构造了一个Options,Opentions中有一个参数:ProtocolVersion。目前,我们可以使用ProtocolVersion.Version1,我们可以通过Google中的StudioFlags打开ProtocolVersion.Version2。抓取视图过程会比较长,涉及到adb通信的原理,我们先简单了解一下adb通信架构。adbserver:运行在我们PC开发机上,监听5037端口adbdaemon:运行在Android设备上adbserver通过USB/tcp与adbd通信了解了adb通信的基础知识之后,我们来看一下整个captureview的原理:通过ClientWindow发起一个loadWindowData请求(这里可以看到默认超时时间是20s)ClinetImpl收到请求,让HandleViewDebug把这个请求封装成JDWP,然后准备发送ClientImpl把数据发送到这台PC上的adbserver。adbserver通过usb/tcp将数据Transmit发送到Android设备上的adbd。Android设备上的adbd根据之前选择的进程信息,将信息透传给指定的jdwp线程。jdwp通过native调用DDMServer方法DdmHandleViewDebug。收到请求后,开始处理请求,然后通过socket返回,LayoutInspector收到解析后会显示结果参考:debugger.cchttps://android.googlesource.com/platform/art/+/android-cts-5.0_r9/runtime/debugger。cc#37782.2.2dumpv1的原理上图的过程可以看出,在上次调用的时候,有dump和dumpv2两个方法,dump方法已经被丢弃了。源码ViewDebug.java:看源码我们知道,v1dump是获取@ExportedProperty注解的filed和method,然后将这些数据写入ByteArrayOutputStream。比如View的padding属性:当然也有方法:上两图中的category:padding和focus在LayoutInspector的属性面板中都有体现:看上面源码的结论:v1遍历所有通过反思归档和方法。我手机一加7Android10,View有487个filed,method有915个。写个简单的代码,只显示遍历的耗时:Output:D/View#dump:10705msand692views可以看到我们还没有添加逻辑,光是遍历的耗时就达到了10s。2.2.3dumpv2的原理看ViewDebug#dumpv2:CalledtoView#encode:v2相比v1非常克制,只返回有限的数据,需要什么数据都可以得到,但是不支持自定义属性,这相当是因为牺牲了一定的灵活性,加快了倾销的速度。在灵活性和速度方面,谷歌同时保留了v1和v2,并通过StudioFlags提供开关。2.3解决方案经过对比v1和v2,基本可以肯定v2的速度会快很多。我们自定义Action,替换原来的LayoutInspectorCaptureTask。关键是更换下面的方法:2.3Effects&Benefitsv2比v1快很多。下面放上抖音直播间的dump数据。设备:一加7Android10.LayoutInspectorV1:18803msLayoutInspectorV2:328ms本章介绍如何使用v2dump协议加速。下面介绍第二个痛点:在某些情况下,无法选中指定的View。3、准确获取被点击的View3.1问题描述LayoutInspector还有一个不尽如人意的地方——无法选中指定的View。例如:上图中的蓝框其实是一个没有内容的空白View,而这个蓝框是叠加在“收礼物”的红圈上的。当我们点击红色圆圈时,就是选中的蓝色框。3.2问题分析我们先来分析一下LayoutInspector的swing组件的构成:LayoutInspector中间图片的预览就是上图中的myPreview。为了解决这个问题,我们来看看这个点击选择的逻辑。IDEA自定义插件使用的GUI框架是JavaSwing,通过MouseAdapter可以监听组件的鼠标点击、鼠标移入、鼠标退出等事件。ViewNodeActiveDisplay的MouseAdapter如下:查找指定的View逻辑:代码反映了LayoutInspector从后往前遍历以满足点击事件消费的顺序,Z轴值较大的View先消费事件。但是很多时候,我们需要通过比较View的大小来选择指定的View。3.3解决方案其实代码很容易修复,但是比较麻烦的是如何替换ViewNodeActiveDisplay中getNode和updateSelection的相关逻辑。我注意到调用getNode的地方是click/mouseEnter等事件,所以我们可以替换MosueAdapter,然后重写getNode和updateSelection。4.手把手教你搭建IDEAPlugin开发环境。要解决以上两个痛点,您需要创建一个新的IDEAPlugin。与一般插件开发环境不同的是,我们需要依赖android插件。然后在build.gradle中添加如下配置://参见https://github.com/JetBrains/gradle-intellij-plugin/intellij{localPath="/Users/xx/Library/ApplicationSupport/JetBrains/Toolbox/apps/AndroidStudio/ch-1/202.7231092/AndroidStudio.app"plugins=['android']updateSinceUntilBuildfalse}localPath填写你本地的AndroidStudioapp路径。前面我们提到LayoutInspector是androidplugin的一部分,所以这里声明plugins=['android']5.本文总结了原生LayoutInspector的两个痛点,介绍了LayoutInspector的工作原理,并提出解决方案使本机LayoutInspector稳定且易于使用。文末还介绍了如何搭建插件项目,让从未接触过插件的新手也能进入插件的新世界。
