共计 5073 个字符,预计需要花费 13 分钟才能阅读完成。
Graver 是一款高效的 UI 渲染框架,它以更低的资源消耗来构建十分流畅的 UI 界面。Graver 独创性的采用了基于绘制的视觉元素分解方式来构建界面,得益于此,该框架能让 UI 渲染过程变得更加简单、灵活。目前,该框架已经在美团 App 的外卖频道、独立外卖 App 核心业务场景的大多数业务中进行了应用,同时也得到美团外卖内部技术团队的认可和肯定。
App 渲染性能优化是一个普遍存在的问题,为了惠及更多的前端开发同学,美团外卖 iOS 开发团队将其进行开源,Github 项目地址与使用文档详见:https://github.com/Meituan-Di…。我们希望该框架能够应用到更广阔的业务场景。当然,我们也知道该框架尚有待完善之处,也希望能与更多技术同行一起交流、探讨、共建。
前言
我们为什么需要关注界面的渲染性能?App 使用体验主要包含产品功能、交互视觉、前端性能,而使用体验的好与坏,直接影响用户持续使用还是转而使用其他 App,所以我们非常关注 App 的渲染性能。而且在互联网产品流量竞争愈发激烈的大背景下,优质的使用体验可以为现有用户提供更好的服务,进而提高用户转化和留存,这也意味着创收、盈利。
<center><font color=gray size = 2> 图 1 使用体验与转化、留存 </font></center>
背景
美团外卖 App 从 2013 年成立至今,已经走过了五个春秋,在技术层面先后经历了快速验证、模块化、精细化和平台化四个阶段,产品形态上也日趋成熟。在此期间,我们构建并完善了监控、报警、容灾、备份等各项基础设施,Metrics 即是其中的性能监控系统。
曾经一段时间,我们以外卖 App 首页商家卡片列表为例,通过 Metrics 性能监控系统发现其在 FPS、CPU、Memory 等方面的各项指标并不理想。于是,通过 Xcode 自带的 TimeProfile 等性能检测工具,然后结合代码分析等手段找到了现存性能瓶颈。与此同时,我们梳理其近半年的迭代版本需求发现,UI 往往需要根据不同场景甚至不同用户展示不同的内容。为了不断迎合用户的需求,快速应对市场变化,这种特征还会持续存在。然而,它会带来以下问题:
视图层级愈加复杂、视图数量愈加众多,从版本长期迭代来看是潜在的性能瓶颈点。
如何快速、高效支撑 UI 变化,同时保证不会二次引入性能瓶颈。
<center><font color=gray size = 2> 图 2 影响渲染性能、研发效率的瓶颈点 </font></center>
Graver 介绍
为了解决现存的性能瓶颈以及后续潜在的性能瓶颈,我们期望构建一套解决方案,该方案能在充分满足外卖业务特征的前提下,以标准化、一站式的方式解决 iOS 端 App 的渲染性能问题,并且对研发效率有一定提升,Graver(雕工)框架应运而生。
因为 Graver 独创性地采用了全新的视觉元素分解思路,所以该框架使用起来十分灵活、简单。我们先来看一下 Graver 的主要特点:
性能表现优异
以外卖 App 首页商家列表为例,应用 Graver 之后 5 分位滚动帧率从满帧的 <font color=red size = 2>84%</font> 提升至 <font color=red size = 2>96%</font>,50 分位几乎满帧;CPU 占用率下降了近 <font color=red size = 2>6 个百分点 </font>,有效提升了空闲 CPU 的资源利用率,降低了峰值 CPU 的占用率。如图 3 所示:
<center><font color=gray size = 2> 图 3 优化前后技术指标对比 </font></center>
“一站式”异步化
Graver 从文本计算、样式排版渲染、图片解码,再到绘制,实现了全程异步化,并且是线程安全的。使用 Graver 可以一站式获得全部性能优化点,可以让我们:
不再担心散点式的“遇见一处改一处”的麻烦。
不再担心离屏渲染等各种可能导致性能瓶颈的问题,以及令人头痛的解决办法。
不再担心优化会有遗漏、优化不到位。
不再担心未来变化可能带来的任何性能瓶颈。
性能消耗的“边际成本”几乎为零
Graver 渲染整个过程除画板视图外完全没有使用 UIKit 控件,最终产出的结果是一张位图(Bitmap),视图层级、数量大幅降低。以外卖 App 首页铂金展位视图为例,原有方案由 58 个控件、12 层级拼接而成;而应用 Graver 后仅需 1 个视图、1 级层级绘制而成。伴随着需求迭代、视觉元素变化,性能消耗恒属常数级。如图 4 所示:
<center><font color=gray size = 2> 图 4 外卖 App 铂金展位应用 Graver 前后对比 </font></center>
渲染速度快
Graver 并发进行多个画板视图的渲染、显示工作。得益于图文混排技术的应用,达到了内存占用低,渲染速度快的效果。由于排版数据是不变的,所以内部会进行缓存、复用,这又进一步促进了整体渲染效率。Graver 既做到了高效渲染,又保证了低时延页面加载。
<center><font color=gray size = 2> 图 5 渲染效率说明 </font></center>
以“少”胜“繁”
Graver 重新抽象封装 CoreText、CoreGraphic 等系统基础能力,通过少量系统标准图形绘制接口即可实现复杂界面展示。
基于位图(Bitmap)的轻量事件交互系统
如上述所说,界面展示从传统的视图树转变为一张位图,而位图不能响应、区分内部具体位置的点击事件。Graver 提供了基于位图的轻量事件交互系统,可以准确识别点击位置发生在位图的哪一块“绘制单元”内。该“绘制单元”可以理解为与我们一贯使用的某个具体 UI 控件相对应的视觉展示。使用 Graver 为某一视觉展示添加事件如同使用系统 UIButton 添加事件一样简单。
全新的视觉元素分解思路
Graver 一改界面编程思路,与传统的通过控件“拼接”、“添加”,视图排列组合方式构建界面不同,它提供了灵活、便捷的接口让我们以“视觉所见”的方式构建界面。这一特点在下文 Graver 使用中详细阐述,正是因为该特点实现了研发效率的提升。
Graver 使用
Graver 引入了全新的视觉元素分解的思路。借助该思路可以实现通过一种对象来表达任一视觉元素、甚至是任一视觉元素的组合,从而消除界面布局的复杂性。
我们先来回顾下传统界面的构建方式,以外卖 App 商家卡片其中一种样式为例,如图 6 所示:
<center><font color=gray size = 2> 图 6 外卖 App 商家卡片 </font></center>
在实现商家卡片的界面样式时,通常会根据视觉上的识别、交互要求来建立界面展示与系统提供的 UI 控件间的映射关系。以标号②位置的样式为例,在考虑复用的情况下通常这部分会使用三个系统控件来完成,分别是左侧蓝底的“预订”使用 UILabel 控件、右侧的蓝色边框“2.26.21:30 起送”使用 UILabel 控件、把左右两侧 UILabel 控件装起来的 UIView 控件;在确定好采用的 UI 控件之后,需要针对展示样式分门别类的设置各个控件的渲染属性来实现图示 UI 效果,渲染属性通常一部分预设,一部分根据业务数据的不同再进行二次设置;其次,设置各个控件的内容属性实现业务数据内容的展示,展示的内容一般是网络业务数据经逻辑处理、加工后的数据。如果涉及到点击事件,还需要添加手势或者更换成 UIButton 控件。接下来,需要根据视觉要求实现排版逻辑,以标号⑧、⑨为例,当标号⑧位置的数据没有的情况下,需要上提标号⑨位置的“美团专送”到图示标号⑧位置。诸如类似的排版逻辑随处可见。对于图示任一位置的展示内容都存在上述的循环思考、编写工作。随着界面元素的增加、变化,问题会变得更加复杂。
传统的界面构建方式其实是在 UI 控件的维度去分解视觉元素,具体是做以下四方面的编写工作:
控件选择:根据展示内容、样式、交互要求确定采用哪种系统控件。
布局信息:UI 控件的大小、位置,即 Frame。
内容信息:UI 控件展示出来的业务数据,如标号①位置的“星巴克咖啡店”。
渲染信息:UI 控件展示出来的效果,如字体、字号、透明度、边框、颜色等。
最后,将各个控件以排列组合方式合成为一棵视图树。
Graver 框架提供了以画板视图为基础,通过对更底层的 CoreText、CoreGraphic 框架封装,以更贴近“视觉所见”的角度定义了全新视觉元素分解、界面展示构建的过程。
通常“视觉所见”可划分为两部分:静态展示、动态展示。静态展示包含图片、文本;动态展示包含视频、动画等。在视觉展示全部为静态内容的时候,一个 Cell 即是一个画布,除此以外没有任何 UI 控件;否则,可以按需灵活的进行画布拆分来满足动画、视频等需要。
<center><font color=gray size = 2> 图 7 画板和传统视图树 </font></center>
以图 6 商家卡片中标号②、⑧为例,新实现方式的伪代码是这样的:
WMMutableAttributedItem *item = [[WMMutableAttributedItem alloc] init];
[[[[item appendImage:[[UIImage wmg_imageWithColor:”blue”] wmg_drawText:” 预订 ”]]
appendImage:[[UIImage wmg_imageWithColor:”clear” borderWidth:1 borderColor:”blue”] wmg_drawText:”2.26.21:30 起送 ”]
appendWhiteSpaceWithWidth:”width”]// 总体宽度减去②和⑧的宽度总和剩余部分
apendText:”50 分钟 |2.5km”];
上述实现方式即是把标号②、⑧部分作为一个整体来实现,任何单一系统控件都无法做到这一点。
Graver 渲染原理
<center><font color=gray size = 2> 图 8 Graver 工作时序 </font></center>
如图 8 所示,Graver 涉及多个队列间的交互,以外卖 App 商家列表为例,整体流程如下:
主线程构建请求参数,创建请求任务并放入网络线程队列中,发起网络请求。
网络线程向后端服务发起请求,获得对应的业务模型数据(如包含了店铺名称,商家头图,评分,配送时长,客单价,优惠活动等店铺属性的商家卡片列表)。
网络线程创建包含业务模型数据(如商家卡片列表)的排版任务,提交到预排版线程处理,进入预排版流程。预排版队列取出排版任务,交由布局引擎计算 UI 布局,将业务模型解析成可被渲染引擎直接处理的,包含布局、层级、渲染信息的排版模型。解析结束后,通知主线程排版完成。
主线程获取排版模型后,随即触发内容显示。根据相对屏幕位置及出现的先后顺序,创建包含将需要显示区域信息的绘制任务,放入异步绘制线程队列中,发起绘制流程。
异步绘制线程队列取出绘制任务,进行图文绘制,最终输出一张包含了图文内容(如商家卡片)的图片。绘制任务结束后,通知主线程队绘制完成,主线程随后展示绘制区域。
整体按照队列间串行、队列内并行的方式执行。
业务应用
Graver 在外卖内部发布之后,我们也将其推广到更多的业务线,并希望 Graver 能够成为对业务开展有重要保障的一项基础服务。经过半年多的内部试用,Graver 的可靠性、渲染性能、业务适应能力也受到外卖内部的肯定和认可。截止发稿时,Graver 已经基本覆盖了美团 App 的外卖频道、独立外卖 App 核心业务场景的大多数业务。下面列举 Graver 在外卖业务的部分应用案例:
经验总结
总结一下,对于界面渲染性能优化而言,要站在一个更高角度来思考问题的解决方案。横向上,从普适性角度解决性能瓶颈点,避免其他人遇到类似问题的重复工作;纵向上,从长远考虑问题做到防微杜渐,一次优化,长期受益。基于此,我们提出一站式、标准化的渲染性能解决方案。诚然,这会遇到很多难点。面对界面样式构建的问题,系统 UIKit 框架着实为我们提供了便利,然而有时候我们需要跳出固有思维,尝试建立一套全新界面构建、视觉元素分解的思路。
参考资料
前端感官性能的衡量和优化实践
作者简介
洋洋,美团高级工程师。2018 年加入美团,目前负责【美团外卖】和【美团外卖频道】的 iOS 客户端首页业务,以及支撑首页业务的技术架构、工具和系统的开发和维护工作。
招聘
美团外卖长期招聘 Android、iOS、FE 高级 / 资深工程师和技术专家,Base 北京、上海、成都,欢迎有兴趣的同学投递简历到 chenhang03#meituan.com。