Flutter中ListView复用原理探索

Flutter三棵树之间的关系

众所周知flutter中有三颗非常重要的树形结构,分别为widget树,element树和renderObject树
其中widget树,存放渲染内容,只是一个数据结构。创建和销毁十分轻量,在页面的刷新过程中经常会被重建。就个人而言,可以把widget抽象的理解为一个存放配置信息的map。
element树,同时持有widget和renderObject,存放上下文信息,可以说是widget和renderObject的连接件。
renderObject树,负责layout和paint事件。
显然,相对于widget来说,element和renderObject的创建和销毁会使用相当多的资源,那么在这个整个flutter中,复用的应该是element和renderObject,而复用widget其实是个误区。

widget的创建流程

想要明确复用原理,就要先知道创建原理。举一个最简单的例子,对于app下只有一个text,那么这个text的创建流程是这样的。
根widget调用其build方法
之后会执行updateChild方法中
这里的newWidget就是text,不为空,child由于从未创建过所以为空,直接执行inflateWidget方法。
这个方法中实际上就是调用了该widget的createElement方法,创建了对应的element对象,之后通过mount方法将该element对象挂在到element 树的对应位置上。
之后假如该widget还有child的话,就会调用该widget的performRebuild方法,重复整个流程直到遍历完整棵树。
简单来说,就是Framework 调用Widget.createElement 创建一个Element实例,记为element
再调用 element.mount,mount方法中首先调用element所对应Widget的createRenderObject方法创建与element相关联的RenderObject对象,然后调用element.attachRenderObject方法将element.renderObject添加到渲染树中插槽指定的位置。
当父Widget发生变化,此时就需要重新构建对应的Element树。为了进行Element复用,在Element重新构建前会先尝试是否可以复用旧树上相同位置的element,element节点在更新前都会调用其对应Widget的canUpdate方法,如果返回true,则复用旧Element,旧的Element会使用新Widget配置数据更新,反之则会创建一个新的Element。在canUpdate方法中通过对比runtimeType和key来确定是否可以复用。
此外如果变化的widget完全相同,但处于不同的位置,将不进行销毁重建,而是直接更改其位置。

ListView复用

有了以上的基础,理解listview的复用应该会简单一些,举个例子。
在这里插入图片描述这是一颗listview结构对应的树,假设左边的item为A,右边的为B,此时滑动listview,A将要滑出屏幕,假设下一个进入屏幕的item与B一样,为了进行复用,就会检查旧树上相同位置的element,按照图中的例子来说,column、text、row、row、image、text、text都可以进行复用,而新创建的只有第一个row下的text,这样对比下来,flutter中listview的复用在布局相似时资源的占用确实比较小。

一个小建议

在创建widget时,尽量保证布局的最大相似性,也即通过判断数据的有无来判断子widget是宽高为0的container还是实际的布局,将其写到同一个方法中,也就能保证最大程度的布局相似。
在这里插入图片描述比如这两个item,就可以在方法中判断图片链接是否为空,为空则图片位置为宽高为0的container,否则为image,其余布局完全相同。
而对于比较极端的情况:在这里插入图片描述上面的item明显是一个column,而下面的既可以是column也可以是row,那么为了资源的节省,推荐一并写成column。