【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

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

评论

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

工赋开发者社区 | 事件驱动架构要避开的 5 个陷阱

工赋开发者社区

【网络安全】记一次红队渗透实战项目

网络安全学海

网络安全 信息安全 计算机 渗透测试 漏洞挖掘

在spring boot3中使用native image

程序那些事

spring 程序那些事 spring boot3

云原生数据交付平台Kuda在AI场景下的模型分发实践

Geek_c4ea78

机器学习 开源 Kubernetes 容器 云原生

架构实战营4.5 常见存储系统随堂练习

西山薄凉

「架构实战营」

易观千帆 | 2022年12月银行APP月活跃用户规模盘点

易观分析

金融 手机银行 用户

元宇宙成本高吗?构建元宇宙需要多少钱?

3DCAT实时渲染

元宇宙 元宇宙开发 元宇宙成本

软件测试/测试开发 | Appium环境安装与架构介绍

测试人

软件测试 自动化测试 测试开发 appium

SimMIM:更简单的掩码图像建模

Zilliz

组合多个动画效果 —— Flutter 交错动画(Staggered Animation)简介

岛上码农

flutter ios 前端 动画 安卓开发

NLP知识图谱项目合集(信息抽取、文本分类、图神经网络、性能优化等)

汀丶人工智能

人工智能 自然语言处理 深度学习 知识图谱

性能提升 40 倍!我们用 Rust 重写了自己的项目

Peefy

rust 编程语言 #go Python.

技术管理者如何发掘写代码之外的能力?

石云升

极客时间 1月月更 技术领导力实战笔记

【必看知识】PCB设计中焊盘的设计标准

华秋PCB

工具 PCB PCB设计

Tokio 中 hang 死所有 worker 的方法

Databend

镭速-跨国车企数据高速、安全跨境传输解决方案

镭速

云上的米开朗基罗:在不确定时代,寻找建筑般的确定性

华为云开发者联盟

云计算 后端 华为云 企业号 1 月 PK 榜

API 网关策略二三事

API7.ai 技术团队

APISIX 网关设计

直播预告 | 2月2日,腾讯Light与你一同用AI,让美好变更好

腾源会

公益

电商秒杀系统架构设计

π

架构实战营

佛萨奇2.0上链DApp开发|佛萨奇链上开发技术

薇電13242772558

dapp

国内类似禅道的项目管理软件有哪些

PingCode

项目管理 软件 项目管理工具

可以手动磨皮的p图软件portraiture2023

茶色酒

Portraiture2023

接口测试这件小事

石臻臻的杂货铺

接口测试

Mac免费实用的读写软件Tuxera NTFS2023

茶色酒

Tuxera NTFS2023\ Tuxera NTFS2023

基于 SLO 告警(Part 1):基础概念

Grafana 爱好者

可观测性 SRE SLO

工赋开发者社区 | 工业5.0为何是下一个10年的制造业关键性变革方向?

工赋开发者社区

《鹅鸭杀》爆火,一文带你了解如何实现顶流社交游戏

声网

游戏开发 RTC 应用架构

Gartner:流程挖掘市场发展空间巨大,2022年预计市场规模将超10亿美元

九科Ninetech

软件测试/测试开发 | Appium 用例录制

测试人

软件测试 自动化测试 测试开发 测试用例 appium

架构实战营4.6 千万学生管理系统存储设计

西山薄凉

「架构实战营」

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