阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

Litho 在动态化方案 MTFlexbox 中的实践

  • 2019-10-05
  • 本文字数:5749 字

    阅读完需:约 19 分钟

Litho在动态化方案MTFlexbox中的实践

1. MTFlexbox

MTFlexbox 是美团内部应用的非常成熟的一种跨平台动态化解决方案,它遵循了 CSS3 中提出的Flexbox规范来抹平多平台的差异。MTFlexbox 适用于重展示、轻交互的业务场景,与现有 HTML、React Native、Weex 等跨平台方案相比,MTFlexbox 具备着性能高、渲染速度快、兼容性高、原生功能支持度高等优势。但其缺点在于不支持复杂的交互逻辑,不适合复杂交互的业务场景。目前,MTFlexbox 已经广泛应用在美团首页、搜索、外卖等重要业务场景。本文主要介绍在 MTFlexbox 中使用 Litho 优化性能的实践经验,更多关于 MTFlexbox 的实践内容,可查阅《MTFlexbox自动化埋点探索》。

1.1 MTFlexbox 的原理

MTFlexbox 首先定义一份跨平台统一的 DSL 布局描述文件,前端通过“所见即所得”的编辑器编辑产生布局,客户端下载布局文件后,根据布局中的描述绑定 JSON 数据,并最终完成视图的渲染。MTFlexbox 框架图如下图所示:



图 1 MTFlexbox 的架构


图中分为五层,分别是:


  • 业务应用层:业务使用 MTFlexbox 的编辑器定义符合 Flexbox 规范的 DSL 文件(XML 模版)。

  • 模版下载:负责 XML 模版下载相关的工作,包括模板缓存、预加载和异常监控等。

  • 模版解析:负责模版解析相关的工作,包括标签节点的预处理、数据绑定、标签节点的缓存复用和数据异常监控等。

  • 视图渲染:负责视图渲染相关的工作,包括把标签结点按照 Flexbox 规范解析成 Native 视图,并完成视图属性的设置、点击曝光事件的处理、视图渲染、异常监控等。

  • 自定义标签扩展:提供支持业务扩展自定义标签的能力。


鉴于本篇博客主要涉及渲染相关的内容,下面将着重介绍 MTFlexbox 从模版解析到渲染的过程。如下图所示,MTFlexbox 首先会把 XML 模版解析成 Java 中的标签树,然后和 JSON 数据绑定结合成一颗具有完整数据信息的节点树。至此,模版解析工作就完成了。解析完成的节点树会交给视图引擎进行 Native 视图树的创建和渲染。



图 2 视图模版从解析到渲染

2. MTFlexbox 在美团动态化实践中面临的挑战

随着 MTFlexbox 在美团内部被广泛使用,我们遇到了两个问题:


  • 复杂视图因层级过深,导致滑动卡顿问题。

  • 生成视图耗时过长,导致滑动卡顿问题。

2.1 问题一:视图层级过深

2.1.1 原因分析

MTFlexbox 使用的是 Flexbox 布局,Flexbox 布局可以理解成 Android LinearLayout 布局的一种扩展。Flexbox 在布局过程中使用到大量的布局嵌套,如果布局酷炫复杂,无疑会出现布局层级过深、视图树遍历耗时、绘制耗时等问题,最终引发滑动卡顿。下图是美团正在使用的一个模版的视图层级情况(布局最深处有 8 层):



图 3 模版布局层级效果

2.1.2 影响

布局层级过深在布局的计算和渲染过程中会导致过多的递归调用,影响视图的绘制效率,引发页面滑动 FPS 下降问题,这会直接影响到用户体验。

2.2 问题二:生成视图耗时过长

2.2.1 原因分析

视图生成耗时原因如下图所示:RecyclerView 在使用 MTFlexbox 布局条目时,需要对条目模版进行下载并解析生成节点树,这样会导致生成视图的过程耗时过长。为了提高视图生成速度,我们增加了复用机制,但是滑动过程中,如果遇到新的布局样式仍然需要重新下载和解析。另外,MTFlexbox 绑定的数据是未经解析的 JSON 字符串,所以也要比正常情况下的数据绑定更耗时一些。 正是上面两个原因,导致了 MTFlexbox 生成视图耗时过长的问题,这也会导致滑动时 FPS 出现突然下降的现象,产生卡顿感。



图 4 视图生成耗时原因分析

2.2.2 影响

由于视图的创建会阻塞主线程,创建视图耗时过长会导致 RecyclerView 列表滑动时卡顿感明显,也严重影响到了用户体验。

3. Litho

3.1 Litho 原理

Litho 是一套声明式 UI 框架,或者说是一个渲染引擎,它主要优化复杂 RecyclerView 列表的滑动性能问题。Litho 实现了视图的细粒度复用、异步计算布局和扁平化视图,可以显著提升滑动性能,减少 RecyclerView 滑动时的内存占用。详细介绍可以参考美团技术团队之前发布的另一篇博客:Litho的使用及原理剖析

3.2 Litho 的优势

通过对 Litho 原理的了解,我们可以看到 Litho 主要针对 RecyclerView 复杂滑动列表做了以下几点优化:


  • 视图的细粒度复用,可以减少一定程度的内存占用。

  • 异步计算布局,把测量和布局放到异步线程进行。

  • 扁平化视图,把复杂的布局拍成极致的扁平效果,优化复杂列表滑动时由布局计算导致的卡顿问题。


扁平化视图刚好可以优化 MTFlexbox 遇到的视图层级过深的问题。异步计算布局虽然不能直接解决 MTFlexbox 生成视图耗时过长问题,但是给问题的解决提供了新的思路——异步提前完成视图创建。而且使用 Litho 还能带来一定程度的内存优化。所以如何将 Litho 应用到 MTFlexbox 中,进而来解决 MTFlexbox 现存的问题,是我们解下来要讨论的重点。

4. Litho + MTFlexbox 是怎么解决上述两个问题的?

4.1 解决问题一:视图层级过深问题

Litho 实现了布局的扁平化,所以最直接的方式就是使用 Litho 来替换 MTFlexbox 现有的视图引擎。视图引擎最主要的作用,是把 XML 文件解析出来的节点树变成 Litho 可以展示的视图,所以视图引擎替换的主要工作是把节点树转换成 Litho 能展示的视图。如下图所示。由于 Litho 使用的是组件化思想,需要先把节点转化成组件,再把组件树设置给 LithoView,而 LithoView 是 Litho 用于兼容原生 View 的容器,它负责把 Litho 和系统视图引擎桥接起来。



图 5 Litho 视图引擎从节点到视图的转换


不过视图引擎的替换并不是一帆风顺的,我们在替换过程中也遇到了 4 个比较大的挑战。


难点一:复用视图无法更新数据问题


问题描述: 完成了节点树到组件树的转化以后,我们发现了一个严重的问题——复用的视图无法应用新的数据。


问题分析: 当数据发生变化后,MTFlexbox 的节点树会对比新旧数据的变更,确定哪些结点需要更新并通知到具体的视图节点,然后更新显示内容(例如:新数据相比旧数据改变了 Text,那么只有 Text 对应的节点会通知对应的视图去更新内容)。Litho 组件的 Prop 属性是不允许更改的,而 Litho 组件中绝大多数属性都是 Prop 属性。


解决方案


方案一:使用 State 属性全局替换所有组件的 Prop 属性。这种方式的优点在于替换方式相对简单直接,缺点是侵入性强,替换工作量巨大且不符合 Litho 的思想(尽可能少的去改变组件的状态)。这种方案不是最优解,我们要降低侵入,简单快捷地实现数据更新,于是就产生了方案二,具体如下图所示。


方案二:封装一套 Updater 组件,用于创建真正展示的组件。Updater 组通过 State 属性监听对应节点的数据变更,当节点数据变化时,可以触发对应节点的更新。



图 6 数据更新问题初版解决方案


但在后来的实践过程中,我们发现 Litho 整个组件树中只要有一个组件有状态更新,便会重新计算整个布局,而每次数据更新少说也会有几十个节点发生变化。频繁的重复计算反而导致性能变得很差。在经过了多种尝试以后,我们找到了最优的解决方案:



图 7 数据更新问题最终解决方案


如上图所示,状态更新控制器负责整个视图所有节点的更新操作。在所有数据都更新完成以后,统一交由状态更新控制器触发一遍组件更新。


难点二:Litho 不支持层叠布局问题


MTFlexbox 并没有完全严格的使用 Flexbox 布局规范,为了简单实现层叠效果,MTFlexbox 自定义了一种新布局规范——Layer 布局。Layer 布局具有以下两个特点:


  • 特点一:Layer 的子视图在 z 轴上依次层叠展示。

  • 特点二:Layer 的子视图默认且只能充满父布局。


原因分析: 由于 Litho 严格遵守 Flexbox 布局规范,所以没有现成的 Layer 组件。


解决方案: 自己实现 Layer 组件,满足第一个特点很容易,Flexbox 本身就支持层叠展示,只需要把子视图设为绝对布局就可以了。但是让子视图默认充满父布局就没有那么简单了,Flexbox 布局中没有任何一个属性可以达到这个效果。在经过了若干次组合多个属性的尝试以后,还是没能找到解决方案。既然 Layer 并不是 Flexbox 布局的规范,那么我们局限在 Flexbox 的束缚下,怕是很难找到完美的解决方案。那么,能不能在 Litho 中绕过 Flexbox 的约束,自己实现 Layer 效果呢?想在 Litho 中突破 Flexbox 布局的束缚,就需要了解 Litho 是如何使用 Flexbox 的。



图 8 Litho 的布局计算原理


如上图,Litho 的 Flexbox 布局是由 Yoga 负责布局计算的。每一个 Litho 组件都会对应一个 Yoga 节点。但 Yoga 的布局计算过程是由根节点去统一触发的,子节点没有办法知道自己对应的 Yoga 节点是何时开始计算,及何时计算结束。这样以来,我们就没有时机去感知到 Layer 组件的布局是否计算完成,也就没有办法在 Layer 组件计算完成后去控制 Layer 子节点的计算。为了解决这个问题,我们做了两件事:


  • 添加布局计算完成的回调,在布局计算完成后由根节点逐层通知子节点计算完成的消息。

  • 拆分 Yoga 节点树,由 Layer 自己来控制子节点的计算。



图 9 Layer 布局的实现原理


如上图所示,把 Layer 组件伪造成叶子节点,不把 Layer 组件的子节点设置给 Yoga,这样一个 Yoga 中的布局树就被 Layer 组件切割开了。当根节点计算完成以后,通知到 Layer 组件,Layer 组件再依次去设置子节点的宽高和位置属性,并触发子节点去完成各自子节点的布局计算。这样就完美地实现了 Layer 的布局效果。


难点三:Litho 图片组件不支持使用网络图片问题


原因分析: Litho 的组件是一个属性的集合,Litho 期望我们在组件创建时便确定了所有属性的值,所以 Litho 不支持网络图的展示。如果要支持从网络下载图片,就意味着图片组件用来展示的内容会发生变化。所以 Litho 自带的图片组件并不支持使用网络图片。


解决方案


方案一:用 State 属性解决网络图片下载带来的展示内容变化问题。我们在实践中发现,State 属性的更新会导致整个布局重新计算,其实替换图片资源不会导致图片组件的大小位置发生变化,根本不需要重新计算布局。为了减少使用 State 属性导致布局计算频繁的问题,就摒弃了这种方案。


方案二:Litho 官方额外提供的异步下载图片组件FrescoImage中使用的是图片代理方式。FrescoImage 使用 DraweeDrawable 来绘制视图,而 DraweeDrawable 实际上并不具备图片渲染的能力,只是在内部保存了一个真正的 Drawable 来负责渲染。所以,DraweeDrawable 本质上是对真正要展示的图片做了一层代理,当从网络上下载下来真正要展示的图片后,只需要通过替换代理图片就可以完成视图的更新。美团下载图片使用的是 Glide,只需要按照这个思路实现自己的 GlideDrawable 就好了。


难点四:自定义标签扩展的接口不兼容问题


MTFlexbox 支持自定义标签的扩展,所以我们在完成基本视图标签的 Litho 实现以后,还需要支持自定义 Tag 的扩展,才算完成视图引擎的替换工作。


原因分析:MTFlexbox 在设计自定义标签接口时,只提供了允许使用 View 完成视图扩展的接口,但是 Litho 实现的视图引擎是使用组件作为视图单元和 MTFlexbox 对接的,所以接口不能兼容。


解决方案


方案一:重新提供使用 Litho 组件完成视图扩展的接口。其缺点是,需要 MTFlexbox 的使用方重新实现已经支持了的自定义标签,工作量较大,所以这种方案被抛弃了。


方案二:Litho 中使用业务方已经扩展好的 View。其优点是使用方对视图引擎的替换无感知。那么,怎样才能在 Litho 中使用业务方已经扩展好的 View 呢?可以先看下面这张图。



图 10 Litho 对 View 功能的拆分


我们可以简单的理解成 Litho 对 Android 的 View 做了一个功能拆分,把属性和布局计算的能力放在了组件里面,每一种组件对应一个绘制单元来专门负责绘制。那么对于使用方扩展的标签,我们可以定义一个通用组件来统一承接。在挂载绘制单元时,再去调用使用方扩展的视图去绘制。


优化效果


至此,视图引擎的替换就完成了,整个视图引擎的替换做到了使用方无感知。完美解决了 MTFlexbox 视图层级深的问题,顺带还优化了部分性能。下面是布局层级优化效果的对比,可以看到相同样式下,使用 Litho 引擎实现的视图比使用 MTFlexbox 原生引擎的视图层级要浅很多。



除此之外,还有我们的内存优化成果。下图是美团首页使用 MTFlexbox 时,内存占用随滑动页数(一页为 20 条数据)增加而变化的趋势图。可以看到,使用 Litho 引擎实现的 MTFlexbox 比使用原生引擎的 MTFlexbox 在内存占用上能有 30M 以上的优化。


4.2 解决问题二:生成视图耗时过长

上文提到导致生成视图耗时过长的有两个原因:


a. MTFlexbox 对布局模版的下载和解析耗时。


b. MTFlexbox 绑定时解析数据的耗时。


上文“自定义标签扩展的接口不兼容问题”中介绍过 Litho 的组件能够独立完成布局计算。另外,Litho 组件是轻量级的,所以我们直接把 Litho 组件作为 RecyclerView 适配器的数据源。这样就需要在数据解析时提前完成组件的创建,而组件的创建需要用到 MTFlexbox 的整个解析过程,也就是说,我们把 MTFlexbox 导致视图生成耗时过长的过程提前在数据层异步完成了。这样就不需要等到视图要展示时再去解析,从而规避了视图生成耗时过长的问题。具体的原理,可以参见Litho的使用及原理剖析一文中的 3.2 节“异步布局”。



如上图所示,在异步线程中提前完成 MTFlexbox 布局到 Litho 组件的转换。当视图真正要展示时,只需要把组件设置给 LithoView 就可以了。


优化效果


使用 Litho 引擎实现的滑动列表,在连续滑动过程中不会出现 FPS 波动问题,而使用 MTFlexbox 原生引擎实现的滑动列表则波动明显。(数据采集自魅蓝 2 手机,中低端手机优化效果明显。)


5. 总结

经过一段时间的实践,Litho + MTFlexbox 给美团 App 在性能指标上带来了较大的提升。但是还有很多问题待完善,我们后续还会针对以下几点进一步提升效果:


  • 利用 Litho 组件属性不可变的特点,将提前异步布局进一步扩展为提前渲染出位图,在绘制时直接展示位图,可以进一步提升绘制效率。

  • 优化 RecyclerView 相关的 API,降低侵入性。

  • 解决有点击事件、埋点事件等属性的视图需要降级成 View 才能使功能生效的问题,进一步优化视图层级。

6.参考资料

Litho官网


Flexbox规范


作者介绍


少宽腾飞叶梓,美团终端业务研发团队开发工程师。


本文转载自公众号美团技术团队(ID:meituantech)


原文链接


https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651750679&idx=2&sn=b9b3e95b9c35869081b5f76d542dc7ff&chksm=bd12585a8a65d14c738fbb83cacaebf53fb735ae1a745b15dfca2b5fe386af60ca06233a4af4&scene=27#wechat_redirect


2019-10-05 08:001844

评论

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

3D建模设计 Vectorworks 2022 SP5激活版 for Mac 下载安装教程

南屿

3D建模软件 Vectorworks 2022下载 破解软件 Vectorworks 2022注册码

快麦ERP退货借助APPlink快速同步CRM

RestCloud

零代码 CRM ERP APPlink

LED屏租赁需求激增,为何LED显示屏租赁如此受欢迎?

Dylan

综艺节目 应用 LED显示屏 户外LED显示屏 led显示屏厂家

万界星空科技云MES,助力客户快速构建数字工厂

万界星空科技

mes 万界星空科技 低代码云MES

深入理解TF-IDF、BM25算法与BM25变种:揭秘信息检索的核心原理与应用

汀丶人工智能

nlp 搜索系统 BM25算法 关键词检索

【http服务】使用命令来查看和停止端口教程。

百度搜索:蓝易云

Linux 运维 云服务器 ECS

矢量图形转换工具vector magic for Mac 兼容macos14系统

Rose

mac软件下载 Vector Magic破解版 矢量图像转换工具

Rhinoceros 6 for Mac(犀牛6) 6.31.20315完美激活版

mac

苹果mac Windows软件 Rhinoceros 3D设计软件 犀牛

有关SCADA系统的所有信息:什么是SCADA?

2D3D前端可视化开发

物联网 组态软件 工业自动化 SCADA HMI

2024年,苟住求活,才是长期主义

老张

第二曲线 职场发展 互联网裁员

做CAE分析用哪个显卡?CAE咨询

思茂信息

仿真 CAE CAE软件

TikTok直播专线的优势及应用价值

Ogcloud

直播 直播优化 TikTok

去年最火的 JS 开源项目「GitHub 热点速览」

EquatorCoco

GitHub 开源 js 工具库

首个云原生、分布式、全栈国产化银行核心业务系统投产上线丨TiDB × 杭州银行

编程猫

【年后跳槽必看篇-非广告】Spring Bean的生命周期

派大星

spring 跳槽季 Java 面试题

如何查看崩溃日志

程序员都必须知道的Vue 开发技巧

不在线第一只蜗牛

vue.js Vue 开发技巧

NFTScan | 01.01~01.07 NFT 市场热点汇总

NFT Research

NFT NFT\ NFTScan

Java执行Python代码的两种方法(Jython与ProcessBuilder)

百度搜索:蓝易云

Java Python Linux Jython ProcessBuilder

超自动化助力企业财务转型升级

智达方通

超自动化 企业财务转型

草料荣获第五届中国工业互联网大赛新锐组二等奖

草料二维码

二维码 草料二维码

Mac电脑前端代码编辑神器:Sublime Text 4 Dev 激活码中文

mac大玩家j

代码编辑器 Mac软件 前端代码编辑器

5分钟使用Hologres实时湖仓加速分析挑战赛来袭

阿里云大数据AI技术

AudFree Tidable Music Converter for Mac(Tidal音乐转换器)

Rose

Tidal音乐转换器 苹果电脑音频转换器 AudFree Tidal Music

APP加固技术及其应用

雪奈椰子

Linux常用命令—findfs命令

百度搜索:蓝易云

云计算 Linux 云服务器 uuid findfs

左耳听风-我的三观「读书打卡 day 01」

Java 工程师蔡姬

程序员 读书 读书感悟 左耳朵耗子

软件开发

Geek_8da502

F5:伴随生成式AI的发展,2024年将出现十大网络安全风险威胁

科技热闻

强大的原型设计:Kite Compositor最新激活版

胖墩儿不胖y

Mac软件 原型设计工具 Mac动画软件

macos强大的界面设计和原型制作工具:Kite

Rose

Mac动画原型设计 Kite for Mac破解 Kite Mac下载 苹果软件下载站

Litho在动态化方案MTFlexbox中的实践_语言 & 开发_叶梓_InfoQ精选文章