近些年,移动端动态化技术可谓是“百花齐放”,其中的渲染性能也是动态化技术一直在探索、研究的课题。美团的开源框架 Graver 也为解决动态化框架的渲染性能问题提供了一种新思路:关于布局,咱们能够采用“画控件”方案替代传统的“拼控件”方式。本文尝试给出一些探索思考与实践经验的分享。css
动态化技术指的是不依赖程序安装包,就能进行动态实时更新页面的技术。特别是对于电商、社交等须要快速迭代、实时调整的强运营类业务来讲,动态化具备很是重要意义。它的优点主要表现为:提升人效、缩短迭代试错周期、解决版本长尾问题、减小包大小等等。html
从2018年开始,移动端设备的增加红利再也不,整个生态增加趋势开始由高走低,与之对应的开发生态在 Native 技术方向也逐渐开始进入低迷阶段,大方向在向跨平台演进,方案上已是“百花齐放”。现有的客户端动态化技术主要能够划分为以基于 Webview 的 Web 页面动态化加载、本地内置多个模板支持动态切换、支持动态 DSL 的布局引擎以及基于虚拟机等四类。前端
动态化方案的渲染引擎多数是基于原生 UI 控件搭建动态化页面。基于 Webview 的 Web 页面动态化,实质是基于浏览器运行网页,页面绘制效率、运行效率相对较低一些。然后三种解决方案,分别经过创建映射表、布局引擎、虚拟机与客户端渲染引擎通信及调用关系,渲染引擎则都是基于原生 UI 控件搭建动态化页面。因为操做系统提供的 UI 控件布局/绘制仅支持主线程访问,大量原生 UI 控件操做致使 CPU/GPU 负担太重,因此在构建复杂的动态化页面上存在效率和性能瓶颈。所以,渲染性能是动态化技术一直在探索、研究的课题。本文尝试给出一些探索思考与实践经验的分享。git
如前言部分的图1所示,MTFlexbox 是美团点评自研的一款跨平台动态布局框架,它遵循了 CSS3 中提出的 Flexbox 规范来抹平多端差别。美团 App 首页、搜索结果页等业务有一个共同点,就是面向的业务方比较多,承载了流量输送变现的能力。在视图层面呈现轻交互、重展现的特征。频繁变更 UI,快速上线是一个刚需,MTFlexbox 正是知足了这样一个刚需。github
因为本文侧重对 MTFlexbox 的渲染性能优化,故仅对 MTFlexbox 作归纳介绍。MTFlexbox 首先定义了一份跨平台统一的 DSL 布局描述文件,前端经过编辑器编辑产生布局文件并上传到云端,客户端下载布局文件而后根据布局中的描述信息绑定业务数据,最后基于原生 UI 控件搭建视图并渲染展现。MTFlexbox 的工做原理以下图所示:浏览器
然而,随着业务的迭代演变,美团 App 首页、搜索结果页等业务视图卡片样式愈来愈多,展现也愈来愈复杂。样式种类多意味着视图复用率低,极端场景下甚至没法进行复用。展现复杂,同时也意味着控件数量多、布局复杂、层级深。若是大量复杂操做都发生在主线程,不免形成渲染卡顿等用户体验方面的问题。缓存
针对上述问题,外卖终端用户研发组、美团终端技术研发组、美团终端业务研发组合做双赢,三方协调资源成立了跨部门、跨事业部的虚拟专项联合项目组,三方精诚合做,在技术上不断追求卓越,力求同时保证稳定性、动态化和高性能。安全
动态布局框架 MTFlexbox 经过系统 UIKit 搭建视图并渲染展现,其测量、布局、绘制过程均发生在主线程。而做为一款 iOS 端高效的 UI 异步渲染框架 Graver,其布局计算、渲染过程彻底异步化,整个过程结束后才通知 UI 线程进行展现。这给咱们解决动态化框架的渲染性能问题打开了新思路:关于布局,咱们能够采用“画控件”方案替代传统的“拼控件”方式。Graver 已经在美团 App 的外卖频道、独立外卖 App 核心业务场景的多个业务中经历了一年多的实践检验。良好的稳定性和出色的渲染性能,也获得了美团外卖内部技术团队的承认和确定。关于 Graver 更多的内容这里再也不赘述,详细介绍请参考另外一篇技术博客:《美团开源 Graver 框架:用“雕刻”诠释 iOS 端 UI 界面的高效渲染》 。性能优化
如何构建基于 Graver 进行异步渲染的动态化框架(MTFlexbox),成为首先须要解决的问题。数据结构
经过对系统 UI 渲染流程分析不难发现:惟一肯定一个视图展现仅须要肯定视图布局信息、内容信息、渲染信息三个要素。含义以下:
Graver 的每一个绘制元素经过 WMMutableAttributedItem 来表达内容信息、渲染信息,CGRect 表达绘制元素的大小和位置。渲染整个过程除画板视图外,彻底没有使用 UIKit 控件,最终产出的结果是一张位图(Bitmap)。若是能经过一棵树形结构组织全部的绘制元素即绘制结点树,便可按照递归遍历的方式“画控件”来转义“拼控件”构建视图。接下来,咱们须要思考如何创建 MTFlexbox 的数据结构与绘制结点树之间的关系,而且保证该转化过程彻底异步化。
如开篇动态布局框架章节 MTFlexbox 的原理所描述:在相继完成模板树构建、数据绑定以后即进行了视图树构建。然而,出于功能划分考虑、兼顾保留 MTFlexbox 的系统 UI 渲染引擎能力以及构建绘制结点树须要的必要信息考虑,须要构建一个中间数据结构:虚拟结点树。它应包含树形结构的层级信息、Flex 属性信息、数据解析处理后的内容信息以及基本的渲染信息。虚拟结点树是既能构建 UI 控件树也能构建绘制结点树的“桥梁”。
经过上述思路分析,肯定了关键数据结构:虚拟结点树、绘制结点树。接下来,咱们须要思考如何构建虚拟结点树到绘制结点树的数据流。
在前端有两个重要的概念:回流、重绘。
参考前端技术思想以及考虑单一职责原则,在虚拟结点树与绘制结点树中间构建 Fat 型渲染结点树和 Thin 型渲染结点树。Fat 型渲染结点树负责保存原始数据以便作逻辑处理,Thin 型渲染结点树负责保存位置、大小和内容信息。当有修改不影响几何尺寸变化的状况下,仅从新生成 Thin 型渲染结点树的内容信息便可。
提升渲染性能的关键,便是全力保证主线程的最小资源开销。所以,须要思考如何保证虚拟结点树到绘制结点树的转换过程是线程安全的。Facebook 开源的跨平台布局引擎 Yoga,提供了经过 UI 视图树中 Flex 属性计算得出每一个 UI 控件的位置和大小。然而,提供给 iOS 平台的插头类是基于 UIView 的,即布局计算过程必须在主线程。须要基于 Yoga 核心逻辑从新封装基于渲染结点树的计算逻辑,以保证布局计算是线程安全的。以下图所示:
有了上述的思路分析,接下来咱们开始着手 Graver 接入 MTFlexbox 的架构设计。Graver 须要做为独立渲染引擎存在,并保留接入多种动态化框架的可能,这是出于架构设计的灵活性和扩展性的考虑。接入层命名为 M-Graver,其上层基于 MTFlexbox 进行扩展但可灵活插拔,下层基于 Graver 渲染引擎,以下图10所示:
M-Graver 是线程安全的,其主要分为解析层、聚合层、布局层和预备层。下面对各层分别作简单的介绍:
按照上述思路分析完成架构设计,但在实施部署的过程当中也遇到了很多的技术难点和问题。如:动态布局框架 MTFlexbox 建立至今已两年有余,因业务的快速发展而产生了一些技术“负债”。为了保证不影响线上原有的业务逻辑,因此在进行 MTFlexbox 的模板树到虚拟结点树,再到 UI 视图树的技术升级改造过程时,尤为须要关注各类“蛛丝马迹”式的细微逻辑。
另外,在将异步渲染引擎 Graver 接入 MTFlexbox 的过程当中也遇到了诸多问题,包括如何构建基于位图的事件处理系统,跨渲染引擎的技术融合,一些极端场景下的绘制效率瓶颈等等。下面将逐一展开阐述。
因为视图最终经过渲染位图来呈现,这就须要创建基于位图的事件处理系统。如前文所述,渲染结点树记录了每一个控件的位置、大小信息以及层级结构,基于此可仿照系统事件处理逻辑进行基于位图的事件处理系统设计。在视图展现期间,画板视图收到事件响应通知后(如点击了画板视图中标号为5的红色按钮),根据位图对应的渲染结点树存储的各控件布局、层级和渲染信息,逐层遍历找到须要响应的渲染结点,若是涉及信息修改则变动其在渲染结点树中的渲染信息,触发再次渲染的同时执行该渲染结点绑定的事件方法。遍历渲染结点树的输入是系统基于画板视图返回的点击位置,其遍历过程与系统 UI 事件查找过程比较类似。事件处理过程以下图11所示:
从美团业务特征出发,图文组合占据多数 UI 场景。然而也存在诸如动效等没法依托 Graver 进行图文渲染的状况。所以,须要考虑跨渲染引擎的渲染融合问题。在 M-Graver 的预备层遍历渲染结点树时,能够根据当前结点是否为原生结点决议树拆分,若是是原生结点,将该结点连同其子树“直系”绘制结点从渲染结点树中拆分下来,以该结点为根结点的子渲染结点树,生成对应的绘制结点树,多个子渲染结点树的根结点,构成了以画板视图为单元的画板视图树。以下图12所示:
为了便于理解,咱们给出如下几个名词的解释说明:
树拆分的过程还涉及到兄弟结点层级颠倒以及布局交叉等问题。兄弟结点层级颠倒问题经过结点变异来解决。布局交叉问题存在于断定渲染结点树的结点是绘制结点或原生结点以前,因为布局缘由存在视图交叉。布局交叉问题经过新建画板视图插入来保证层级正确以及绘制正确。因为篇幅有限,这里再也不赘述。
从产品交互层面看,为了提升屏效每每存在多向滑动的视图组件场景。如横滑 Scroll 组件,其特色是须要经过滑动才能逐渐看到全部的视图内容。经过异步渲染绘制位图来实现的状况下,存在单一并发渲染任务计算逻辑繁重的问题,从用户体验层面看容易形成“白屏”现象。为解决该问题,将视图卡片渲染过程分解,进行增量渲染,采用渐进式的方式减小空白页面等待时间。根据待展现区域在屏幕中相对位置进行区块划分,经过队列集中控制绘制操做。以此进一步提升并发效率,并减小渲染过程的非必要系统资源消耗。
区块划分
区块划分策略的实质是绘制结点树的拆分,将绘制结点树中不存在布局交叉的子结点树进行逐一拆分,每一个拆分下来的绘制结点子树即为一个区块,同时要设置最小块策略,不然拆分粒度过小反而会由于过多的线程并发形成性能瓶颈。
分块绘制
如下图为例说明分块绘制逻辑。在滑动过程当中,若本地缓存有此区域绘制结果,读取缓存并直接通知主线程展现,如例4中 X4'。不然,将该区域加入队列,以块为单元进行并发绘制,绘制完成后更新缓存,再通知主线程展现,如例1中 X1’,例2中 X2‘,例3中 X3’。对划到屏幕外的区域,从队列中清除,终止绘制操做;若此区域已绘制完成,则通知主线程清除此区域的展现,如例2中 X2,例3中 X3,例4中 X4。
在完成“拼控件”到“画控件”的思路探索与技术落地以后,须要发挥其价值,将其部署到线上进行业务实践应用。动态布局框架 MTFlexbox 的跨平台代码复用能力对业务开发效率有了大幅提高。从产品层面看,在原有资源不变的状况下,达到了高效支撑业务迭代的效果。MTFlexbox 动态布局框架在经历了一次联合共建的“洗礼”后渲染性能获得大幅提升,变得越发成熟、完善。在过去的半年多期间,咱们采用异步渲染引擎 Graver 的 MTFlexbox 已前后应用在搜索结果页、美团首页等核心流量区业务。下面列举部分应用案例:
采用异步渲染引擎 Graver 的 MTFlexbox 绝大多数应用场景为列表级应用。如上图所示,全部视图卡片均为采用 M-Graver 实现的动态模板。截止到发稿,覆盖搜索结果页36个动态模板,覆盖首页42个动态模板,业务应用累计覆盖78个动态模板。
以业务应用美团 App 新版首页为例,分类频道卡片如下所有为 MTFlexbox 实现的动态模板视图卡片。因为采用异步渲染引擎 Graver 的 MTFlexbox 具有了在线程安全条件下进行测量、布局、渲染,美团首页接入后滚动 FPS 提高明显,对于上拉加载过程的 FPS 提高更为明显。所以,列表使用体验变更更加顺畅。美团首页的50分位滚动 FPS 接近59,上拉加载 FPS 接近满帧。详细数据以下图所示:
从业务场景做为出发点和原始驱动力,如何改善动态布局框架的渲染性能问题,从本质上讲是解决业务迭代演变时带来的用户体验问题。这里有如下几点经验可供你们参考:
最后,借用朱光潜先生在《艺文杂谈·谈对话体》中提到的一句话做为结尾:“疑难是思想的起点与核心。”
美团外卖长期招聘 Android、iOS、FE 高级/资深工程师和技术专家,Base 北京、上海、成都,欢迎有兴趣的同窗投递简历到duyao04@meituan.com。