写点什么

如何打造稳定、好用的 Android LayoutInspector?

  • 2021-07-02
  • 本文字数:2779 字

    阅读完需:约 9 分钟

如何打造稳定、好用的 Android LayoutInspector?

一、背景

Android 开发者在日常的开发中,经常需要用到查看视图的功能,Android Studio 开发团队为我们提供了 LayoutInspector 插件。在较新的版本提供了 LiveLayoutInspector,支持 3D,但是不管是 LayoutInspector 还是 LiveLayoutInspector 都非常难用。比如:


  • 速度极慢,遇到复杂的布局经常超时

  • 某些情况无法选中指定的 View


本文将围绕 LayoutInspector 的痛点,分析问题并修复,最终将 LayoutInspector 变成一个稳定、好用的插件。

二、加速 Dump View Hierarchy

2.1 问题描述


开发复杂业务的同学在使用 LayoutInspector 时都遇到过上图所示的错误:由于 View 树结构复杂超时。网上也有其他相关的解决办法,原理就是修改 timeout 的值,目前默认值是 20s,所以改成 1min,大概率是可以的了。


为了更好的解决这个问题,比如是否能加速?我们看一下整个 LayoutInspector 抓取的流程。梳理流程之前,我们需要找到功能的入口。

2.2 问题分析

2.2.1 Dump 总流程

平常开发者使用 LayoutInspector 的流程一般如下:


  1. 和 Attach debugger 类似,先获取要 LayoutInspector 的进程

  2. 如果进程中不止一个 ViewRootImpl,还需要选择 window



在 IDEA Plugin 框架体系中,大多数插件的功能入口都依赖 Action,上图 LayoutInspector 的功能入口对应的 Action 如何找到呢?最快速、准确的办法就是 Debug,在我们点击功能入口之前,在 AnAction#actionPerformed 加上断点。



从 AndroidRunLayoutInspectorAction 出发,我们找到了真正的任务:

LayoutInspectorCaptureTask。


抓取 View 视图的关键方法如下:



我们可以看到这里先构造了一个 Options,Opentions 中有个参数:ProtocolVersion,目前我们能使用的是 ProtocolVersion.Version1,Goolge 内可以通过 StudioFlags 打开 ProtocolVersion.Version2。



capture view 的流程会比较长,涉及到 adb 通信原理,我们先简单了解一下 adb 通信架构。

  • adb server: 运行在我们的 PC 开发机上,监听 5037 端口

  • adb daemon: 运行在 Android 设备上

  • adb server 通过 USB/tcp 和 adbd 通信


了解了基本的 adb 通信基础之后,我们再来看整个 captureview 的原理:

  1. 通过 ClientWindow 发起 loadWindowData 的请求(在这里可以看到默认超时时间是 20s)

  2. ClinetImpl 收到请求,让 HandleViewDebug 将本次请求封装成 JDWP,然后准备发送

  3. ClientImpl 将数据先发送给本 PC 上的 adb server

  4. adb server 将数据通过 usb/tcp 透传给 Android 设备上的 adbd

  5. Android 设备上的 adbd 根据之前选择的进程信息,将信息再透传给指定的 jdwp 线程

  6. jdwp 通过 native 调用 DDMServer 方法

  7. DdmHandleViewDebug 收到请求开始处理

  8. 处理完请求后,再通过 socket 返回,LayoutInspector 收到结果解析后展示




参考:debugger.cc

  • https://android.googlesource.com/platform/art/+/android-cts-5.0_r9/runtime/debugger.cc#3778

2.2.2 dump v1 原理

在上图的流程中可以看到在最后的调用中,有 dump 和 dumpv2 两个方法,而且 dump 方法已经废弃了。

源码 ViewDebug.java:

看源码我们知道 v1 dump 是获取被 @ExportedProperty 注解作用的 filed 和 method,然后将这些数据写入 ByteArrayOutputStream。比如 View 的 padding 属性:

当然也有 method:

上面两图中的 category: padding 和 focus 体现在 LayoutInspector 的属性面板中:



上面看源码的结论:v1 是通过反射遍历所有的 Filed 和 Method。


在我的手机 One Plus7 Android 10 上,View 的 filed 有 487 个,method 有 915 个。写一段简单的代码展示一下仅遍历耗时:

输出:

D/View#dump: 10705ms and 692 views
复制代码

可以看到我们还没有添加逻辑,仅仅遍历耗时都达到了 10s。

2.2.3 dump v2 原理

看 ViewDebug#dumpv2:

调用到了 View#encode:

相比 v1,v2 就很克制了,只返回有限的数据,需要什么数据就获取什么数据,但不支持自定义的属性,相当于牺牲了一定的灵活性,加快了 dump 的速度。在灵活性、速度两个方面,Google 将 v1 和 v2 都保留了,并通过 StudioFlags 提供了开关。

2.3 解决方案

对比完 v1 和 v2 之后,基本可以确定 v2 的速度会快很多了。我们通过自定义 Action,并替换掉原生的 LayoutInspectorCaptureTask,关键是替换下面这个方法:

2.4 效果 &收益

v2 相比 v1 速度快了非常多,下面贴一下抖音直播间的 Dump 数据,设备:One Plus 7 Android 10.


LayoutInspector V1: 18803ms

LayoutInspector V2: 328ms


本章节介绍了如何使用 v2 dump 协议来加速,下面介绍第二个痛点:某些情况无法选中指定的 View。

三、精确获取点击的 View

3.1 问题描述

LayoutInspector 还有一个不尽人意的地方——无法选中指定的 View。举个例子:

上图蓝框其实是一个空白的没有内容的 View,这个蓝框盖在了「收礼」这个红圈上。在我们点击这个红圈的时候,却是选中的蓝框。

3.2 问题分析

我们首先分析一下 LayoutInspector 的 swing 组件组成:

LayoutInspector 中间图片的预览就是上图中的 myPreview。为了解决这个问题,我们看一下这个点击选中的逻辑。IDEA 自定义插件中使用的 GUI 框架是 Java Swing,组件的鼠标点击、鼠标移入、鼠标退出等事件都可以通过 MouseAdapter 来监听。ViewNodeActiveDisplay 的 MouseAdapter 如下:

查找指定的 View 逻辑:

代码反映出,LayoutInspector 为了满足点击事件消费的顺序,是从后往前遍历的,Z 轴值较大的 View 优先消费事件。但是在很多情况,我们更需要通过比较 View 的面积大小,来选中指定的 View。

3.3 解决方案

其实代码好修复,但是比较麻烦的是,如何替换 ViewNodeActiveDisplay 中 getNode 和 updateSelection 相关逻辑呢,我注意到调用 getNode 的地方都是 click/mouseEnter 等事件,所以我们可以替换掉 MosueAdapter,然后重写 getNode 和 updateSelection。


四、手把手教你搭建 IDEA Plugin 开发环境

修复上述两个痛点需要新建一个 IDEA Plugin,和一般插件开发环境略有不同的是,我们需要依赖 android plugin。

然后在 build.gradle 中添加如下配置:

// See https://github.com/JetBrains/gradle-intellij-plugin/intellij {    localPath = "/Users/xx/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-1/202.7231092/Android Studio.app"    plugins = ['android']    updateSinceUntilBuild false}
复制代码
  • localPath 填写你本地的 Android Studio app 路径。

  • 前面我们提到 LayoutInspector 是 android 插件的一部分,所以这里我们声明 plugins = ['android']

五、总结

本文围绕原生 LayoutInspector 的两个痛点,介绍了 LayoutInspector 的工作原理,并提出了解决方案,使得原生 LayoutInspector 稳定、好用。在文章最后也介绍了如何搭建插件工程,方便未接触过插件的新人能进入插件的新世界。


本文转载自:字节跳动技术团队(ID:toutiaotechblog)

原文链接:如何打造稳定、好用的 Android LayoutInspector?

2021-07-02 07:001990

评论

发布
暂无评论
发现更多内容

http server源码解析

爱好编程进阶

Java 面试 后端开发

一文看懂“低代码,零代码,APAAS”是什么?怎么选?

优秀

低代码 零代码 aPaaS

如何调节鼠标的灵敏度?

InfoQ IT百科

【等保】二级等保常见问题解答汇总

行云管家

网络安全 等保 等保2.0 二级等保

Google 出品的 Java 编码规范,权威又科学,强烈推荐

爱好编程进阶

Java 面试 后端开发

电脑分区C盘格式化会怎样?

InfoQ IT百科

破浪人丨国内首位 Envoy Maintainer!王佰平独家讲述四年开源之路

网易数帆

开源 云原生 Service Mesh 服务网格 envoy

云小课|教你如何使用RDS for PostgreSQL插件

华为云开发者联盟

postgresql 插件 开源数据库 RDS for PostgreSQL

电脑内存越大处理速度就越快吗?

InfoQ IT百科

OneFlow学习笔记:从Python到C++调用过程分析

OneFlow

c++ Python Relu 调用过程分析

Java并发关键字-volatile

爱好编程进阶

Java 面试 后端开发

纯 JS 实现 WebRTC 视频通话

杨成功

音视频 WebRTC

Java 集合容器篇面试题(上)-王者笔记

爱好编程进阶

Java 面试 后端开发

跟我读CVPR 2022论文:基于场景文字知识挖掘的细粒度图像识别算法

华为云开发者联盟

图像识别 推理 视觉 文字检测 语义信息

java程序员的AI之路-大数据篇 hadoop安装

爱好编程进阶

Java 面试 后端开发

所以,我们依然要读书!

博文视点Broadview

库存管理与资产管理的区别是什么?

低代码小观

低代码 企业管理 资产管理 资产安全 WMS仓库管理

【网络安全】8个网络安全名词解释看这里!

行云管家

网络安全 防火墙 数据安全 堡垒机

通过 SingleFlight 模式学习 Go 并发编程

万俊峰Kevin

微服务 并发编程 web开发 go-zero Go 语言

焱融 YRCloudFile 跨云多源数据管理,直击自动驾驶 “割裂式存储”痛点

焱融科技

自动驾驶 云计算 分布式 高性能 文件存储

怎么样判断显卡性能好坏?

InfoQ IT百科

前端食堂技术周刊第 34 期:Node.js v18 、Nuxt 3 RC1、Parcel v2.5.0、计算机程序的构造和解释、Linux 命令行世界生存指南

童欧巴

JavaScript 前端 技术周刊

一文深入Java浅拷贝和深拷贝

芝士味的椒盐

Java 大数据 Java 开发 深拷贝 浅拷贝

抖音春晚活动背后的 Service Mesh 流量治理技术

火山引擎开发者社区

微服务 后端 后端技术

Kubernetes家族容器小管家Pod在线答疑?

囧么肥事

Kubernetes 云原生 k8s #Kubernetes# 容器服务

Windows操作系统升级后会出现与CPU不兼容情况吗?

InfoQ IT百科

DevSecOps软件安全开发实践

华为云开发者联盟

开源 DevSecOps 安全开发 华为云DevCloud 软件研发

java 使用Html2Image将html转图片

爱好编程进阶

Java 面试 后端开发

Java中return和finally到底哪个先执行

爱好编程进阶

Java 面试 后端开发

Java中使用Spring-security(一)

爱好编程进阶

Java 面试 后端开发

聊一聊龙蜥硬件兼容性 SIG 那些事儿 | 龙蜥 SIG

OpenAnolis小助手

开源 sig 硬件兼容 龙蜥操作系统

如何打造稳定、好用的 Android LayoutInspector?_语言 & 开发_字节跳动技术团队_InfoQ精选文章