背景
最近小组在尝试使用集团DinamicX的DSL,通过动态模板下发,实现Flutter端的动态化模板渲染;本来以为只是DSL到Widget的简单映射和数据绑定,但实际跑起来的效果出乎意料的差,列表卡顿严重,帧率丢失严重。这就让我们不得不深入Flutter的Framework层,去了解Widget的创建、布局以及渲染的过程。
为什么Native可行方案在Flutter表现差?
在iOS和Android开发中,DSL到Native的方案其实并不陌生;Android中,我们就是通过编写XML文件来描述页面布局。Native的这种映射的方案,为什么在Flutter上,效果变得如此糟糕呢?
先通过一个简单的示例来看一下DinamicX_DSL的定义:
可以看到DSL的设计与Android中的XML很相似,在我们的DSL中,每个节点的width和height属性,可以赋值两种特殊意义的值:match_parent
和 match_content
。
在Flutter中,并没有 match_parent
和 match_content
的概念。最初我们的想法很简单,在Widget的build方法中,会递归计算。
表面上看,做好每个节点的宽高计算的缓存,虽然达不到一次性线性布局,这样的开销也并不是很大。但我们忽略掉了一个很重要的问题:Widget是immutable的,在Flutter中,Widget会被不断的创建销毁,这会导致布局计算非常的频繁。
要解决这些问题,单单处理Widget是不够的,需要Element以及RenderObject上做更多的处理,这也就是我们为什么要考虑自定义Widget的原因。
接下来通过源码来了解Flutter中Widget的build、layout以及paint相关的逻辑。
认识三棵树
我们通过一个简单的Widget—— Opacity
来了解一下 Widget
、 Element
、 RenderObject
。
Widget
在Flutter中,万物皆是Widget,Widget是immutable的,只是包含了视图的配置信息的描述,是非常轻量级的,创建和销毁的开销比较小。
Opacity
继承自 RenderObjectWidget
,其定义了两个比较关键的函数:
RenderObjectElement createElement();
RenderObject createRenderObject(BuildContext context);
这正是我们要找的Element和RenderObject!这里只是定义了创建的逻辑,具体调用的时机我们继续往下看。
Element
在SingleChildRenderObjectWidget可以看到创建了 SingleChildRenderObjectElement
对象。
Element是Widget的抽象,在Widget初始化的时候,调用Widget.createElement创建,Element持有Widget和RenderObject;BuildOwne