产品战略专家梁宁确认出席AICon北京站,分享AI时代下的商业逻辑与产品需求 了解详情
写点什么

如何打造稳定、好用的 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:001618

评论

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

精彩抢先看!OceanBase在「数据技术嘉年华 2023」现场等你

OceanBase 数据库

数据库 oceanbase

用户画像设计:揭秘成功产品背后的关键环节

L3C老司机

产品设计 数字化转型 产品管理 用户画像 用户画像分析

BroadcastReceiver牛刀小试

梦笔生花

广播 broadcast broadcastreceiver

一个 OpenTiny,Vue2 Vue3 都支持!

Kagol

typescript 开源 前端 Vue3 UI组件库

月光宝盒(vivo流量录制回放平台)正式对外开源

vivo互联网技术

自动化测试 流量回放 流量录制

移动端动态更新背后的原理及技术原理

没有用户名丶

互联网坊间鄙视链,现在反过来了?

博文视点Broadview

BSN季度版本2023年3月31日迭代更新

BSN研习社

神奇,前端不用redis也能实现消息队列——indexedDB

吴脑的键客

前端 js indexedDB

华为云开源项目OpenTiny的TinyNG组件库应该如何使用?

英勇无比的消炎药

开源 前端 OpenTiny UI组件库

京东LBS推荐算法实践

京东科技开发者

算法 推荐算法 企业号 4 月 PK 榜 排序模型 LBS推荐算法

最佳实践:马来西亚第一大电子钱包通过阿里云EMAS测试提效6倍

云布道师

阿里云

天翼混合云一体机iStack解决方案直播

天翼云开发者社区

BSN-DDC基础网络详解(七):BSN-DDC官方合约市场

BSN研习社

Redis集群介绍及测试思路

京东科技开发者

redis redis集群 主从复制 redis哨兵集群 企业号 4 月 PK 榜

“成年人”的数据库,既要又要也要!

OceanBase 数据库

数据库 oceanbase

产品思维:微信是如何一步步打磨出完美产品的

石云升

读书感悟 产品思维

触手可及的 GPT —— LLaMA

Zilliz

Towhee ChatGPT LLM llama

〖产品思维训练白宝书 - 认知篇③〗- 产品思维 VS 技术思维

哈哥撩编程

#产品思维

【3.31-4.7】写作社区优秀技术博文一览

InfoQ写作社区官方

热门活动 优质创作周报

Salesforce遇挑战:Marc Benioff的管理、代码老化、Slack后遗症

B Impact

RunnerGo(开源)Docker版部署文档(macOS)

爱研究代码的极客人

Docker 开源 软件测试 部署 runnergo

Django笔记二之连接数据库、执行migrate数据结构更改操作

Hunter熊

Python django

平均互信息与条件熵

timerring

信息论

聊聊业务高可用和应用高可用

老张

高可用 高可用架构 稳定性保障

AI日课@20230406:一次毫不费力的ChatGPT内部分享

无人之路

ChatGPT

IO流中「线程」模型总结

Java 架构

请您关注我们! 开源免费独立站BeikeShop——新一代跨境电商系统

BeikeShop

开源 跨境电商 电商系统 独立站 自建网站

艾瑞巴蒂看过来!OSSChat 上线:融合 CVP,试用通道已开放

Zilliz

开源 向量数据库 ChatGPT osschat

共享资源的保护:使用RWMutex的正确姿势

Jack

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