背景:
最近在阅读相关技术文章的时候,有看到讨论RN长列表性能优化相关的问题,想起之前在备份宝(Backuper)项目中遇到的文件上传、下载列表轮询更新时,当文件到达一定数量时,引起操作页面卡顿,影响用户操作体验的问题,因此想着记录下来算是对这个项目的一次回顾总结吧。。。
问题:
文件上传/下载模块,由于页面需要实时更新文件传输速率以及实时传输进度,因此需2秒更新一次文件传输列表。页面UI效果图如下(UI被评价“一言难尽”,,,),就是这个页面的频繁刷新导致用户停留在当前页面时,存在明显的操作延迟。
原因及解决方案:
原因:分析得知,原因主要在两个地方:一是传输列表的缩略未进行缓存处理,导致不管是上传文件(缩略图android从本地读取,ios从沙盒读取),还是下载文件(缩略图从服务端读取),都需要频繁的重新渲染;二是每两秒更新一次传输列表,其实真正需要更新的只是当前正处于传输的文件(即文件状态为:文件合并中、上传中、下载中),其他列表项并不需要每次都去更新渲染。这里的长列表使用的React Native官方提供的SectionList
组件,SectionList
本质上还是由最初的列表组件VirtualizedList
转换得来,底层使用的是VirtualizedSectionList
,支持将二维数据转换一维数据,我的理解其实就是相比于FlatList
多了一个标签分组的功能。缩略图部分使用的是RN提供的Image
组件,由于各个平台(android/ios)针对Image组件属性的支持度不统一,导致类似缓存策略的实施并不能达到令人满意的效果,这也是导致重复渲染的凶手之一。既然原因已经找到,那接下来就是需要如何解决的问题啦。
解决方案:
一:针对列表渲染项的处理
首先,RN官方文档给出了一些优化建议,如设置initialNumToRender
属性,即设置首屏渲染列表数量,可由屏幕高度减去顶部Header的高度得出首屏渲染列表项数。官方定义:指定一开始渲染的元素数量,最好刚刚够填满一个屏幕,这样保证了用最短的时间给用户呈现可见的内容。注意这第一批次渲染的元素不会在滑动过程中被卸载,这样是为了保证用户执行返回顶部的操作时,不需要重新渲染首批元素
;设置keyExtractor
用于提供给单个item列表项,可以减少重新渲染的开销;类似这些(比如还有getItemLayout
等)都是官方给出的一些减少重复渲染的方法。此外,还可以利用页面组件的生命周期函数shouldComponentUpdate()
在渲染前进行过滤,避免不必要的重复渲染。考虑到速率以及文件传输进度的实时变化,这个其实效果可以忽略不计。那就是能是对当前页面的组件进行进一步的优化,考虑组件的单一职责,将单个列表项抽取为单独的列表组件,通过在该组件中的生命周期函数shouldComponentUpdate()
对来自父组件的列表项对象进行判断,同时结合当前文件的传输状态,仅在文件处于传输中或文件合并中以及文件状态切换时才主动更新渲染单个列表项。通过以上处理,可以有效的避免传输列表中单个列表项不必要的重复渲染。
二:针对缩略图的处理
考虑到RN提供的Image
组件对图片的管控能力一般,且部分属性在不同平台上的兼容性。因此采用社区认可度比较高的第三方图片处理库react-native-fast-image
,相关配置官方文档上都有详细的说明,可参考下方链接。相比Image
组件,react-native-fast-image
在图片处理上能更好的针对不同的平台做处理,根据官方文档了解,组件的底层实现在不同平台上分别使用了SDWebImage (iOS)
和Glide (Android)
进行处理,涉及到原生开发的相关概念。
番外:在查找相关资料的时候,看到有文章指出图片来源不同(如网络图片和本地图片),也会导致图片加载速度不一致,最终导致页面渲染变慢,这有待进一步探究。。。相关文章链接:图片渲染
参考链接:
1.SectionList官方文档
2.react-native-fast-image
3.《RN性能优化指南》