FlatList的性能高在哪里?html
☞阅读原文git
考考你
-
问:在数据项高度不肯定状况下,js侧不具有直接计算组件大小的能力,是怎么知道首屏展现几个数据项?
答:js侧首屏几个不是直接计算出来的,而是先经过设置的属性估算出几个数据项,同时设置数据项和列表的布局监听回调onLayout,回调中修正数据项个数(若是还有数据项而且屏幕还有空间,则继续添加数据项)。
-
问:FlatList只展现屏幕中的个数,那怎么还能快速滚动,Native底层对应的但是普通的滚动容器ScrollView?
答:FlatList中数据项个数不止屏幕中显示的个数,会有超出屏幕外的多屏数据项(经过windowSize属性设置)。假如实际数据项100,FlatList一屏能展现10个,此时FlatList中的个数多是30个,若是你快速滑动,只能滑出30个就到底了,须要过一会才发现还能接着滑动。
-
问:FlatList中空白项和实际未显示的数据项个数对应么,空白项高度怎么计算?
答:空白项和实际未显示个数不对应,最多只有两个空白项(顶部空白项和底部空白项)。若是未显示数据项高度已经计算过(以前在界面显示过),则直接累加便可算出,若是位置,则经过估算(已知数据项高度的平均值)。
-
问:FlatList滑动起来一点都不卡,必要的计算仍是有的,这是怎么作到的?
答:除少数状况下(用户滑动到的页面是空白),计算工做大部分在InteractionManager.runAfterInteractions(将一些耗时较长的工做安排到全部互动或动画完成以后再进行)执行。有效的避免了和用户交互抢占CPU,并且是用户交互停下来了才触发,避免了跟随用户动做频繁计算。不过弊端就是上面的少数状况下会卡顿(预设的缓冲用完了,只能强制在交互中同步计算)或者看起来像彩蛋(快速滑动尚未显示完全部数据就到底滑不动了、快速滑动下白屏)。
-
问:网上说FlatList的原理是“不在屏幕中的组件会被移除,经过空白替代”,和没说同样?
答:
1. 长列表最大的硬伤是随着列表不断滑动,数据项愈来愈多,内存愈来愈大,而后就OOM了。经过将屏幕外组件移除是解决该硬伤的核心思想(其实核心思想就这么几种,你们都能想到,困惑的实际上是怎么作到的)。
2. FlatList首先会预缓冲不少屏数据,这样不会影响正常显示和滑动功能。其次就是在互动或动画结束后再刷新缓冲区域,这样不会卡。再次经过key保证了缓冲区是增量刷新,而且限制增量大小,确保不会卡。
-
问:getItemLayout真能提升性能么?
答:getItemLayout能直接获取数据项控件位置和大小,无需借助onLayout回调,能够提升位置计算效率。
-
问:多级吸顶怎么作的?
答:实际使用的是ScrollView.js中自带的吸顶功能(经过位移动画实现)。FlatList虽然声明的属性没有说支持吸顶,但经过设置隐藏属性stickyHeaderIndices(这个属性在VirtualizedList.js里面用到,可是没有显示声明,FlatList会将自身全部属性直接赋值给VirtualizedList)能支持吸顶。
怎么看
- 直接看源码,功力不够,也没有这个耐心,这和看英文文章同样,看着看着就(~﹃~)~zZ。
- 直接打断点一步步Debug,随便一个操做会让你Debug到停不下来,直到怀疑人生。
- 首先网上搜一下相关文章,熟悉一下大概。其次从核心入口render方法大概看看,找到一些关键函数。再次就是直接打日志(js代码就是这个好,依赖文件直接加日志就能够跑),串一下思路。再再次就是日志串不起来再再回头断点看看,来来回回你就懂了。
剖析
- 整个Demo
export default class App extends Component<Props> {
renderItem = (item) => {
var txt = '第' + item.index + '个' + ' title=' + item.item.title;
var bgColor = item.index % 2 == 0 ? 'red' : 'blue';
return <Text style={[{flex: 1, height: 100, backgroundColor: bgColor}, styles.txt]}>{txt}</Text>
}
render() {
var data = [];
for (var i = 0; i < 1000; i++) {
data.push({key: i, title: i + ''});
}
return (
<View style={{flex: 1}}>
<View style={{flex: 1}}>
<FlatList
initialNumToRender={1}
windowSize={2}
renderItem={this.renderItem}
data={data}>
</FlatList>
</View>
</View>
);
}
}
复制代码
- 长这样
- 加点日志
- VirtualizedList.js
- console.log('SSU', '\n\nVirtualizedList#render(){列表开始渲染}\n\n', this.state);
- console.log('SSU', 'VirtualizedList#render()$lead_spacer{列表添加头部空白块}',{lastInitialIndex, initBlock, first, firstBlock}, {[spacerKey]: firstSpace});
- console.log('SSU', 'VirtualizedList#render()$tail_spacer{列表添加尾部空白块}', {last, lastFrame, end, endFrame},{[spacerKey]: tailSpacerLength})
- console.log('SSU', 'VirtualizedList#_pushCells(){填充列表项}',{first, last}, cells);
- console.log('SSU', 'VirtualizedList#_onCellLayout(){开始列表项布局回调}', cellKey, index);
- console.log('SSU', 'VirtualizedList#_onCellLayout(){列表项布局回调结束}', next, this._frames);
- console.log('SSU', 'VirtualizedList#_onLayout(){列表布局回调}', e.nativeEvent.layout);
- console.log('SSU', 'VirtualizedList#_onLayout(){列表布局回调修正滚动参数}this._scrollMetrics.visibleLength=', this._scrollMetrics.visibleLength);
- console.log('SSU', 'VirtualizedList#_onContentSizeChange(){列表内容请大小变化回调}', width, height, this._scrollMetrics);
- console.log('SSU', 'VirtualizedList#_onScroll(){滚动开始}', e.nativeEvent.layoutMeasurement, e.nativeEvent.contentSize, e.nativeEvent.contentOffset);
- console.log('SSU', 'VirtualizedList#_onScroll(){滚动修正滚动参数}', this._scrollMetrics);
- console.log('SSU', 'VirtualizedList#_onScroll(){滚动结束}');
- console.log('SSU', 'VirtualizedList#_scheduleCellsToRenderUpdate(){安排列表项更新优先级}', {first, last}, {offset, visibleLength, velocity}, itemCount);
- console.log('SSU', 'VirtualizedList#_scheduleCellsToRenderUpdate(){优先级高,直接更新列表项}', {hiPri, _averageCellLength: this._averageCellLength, _hiPriInProgress: this._hiPriInProgress});
- console.log('SSU', 'VirtualizedList#_scheduleCellsToRenderUpdate(){优先级低,等空闲再更新列表项}', {hiPri, _averageCellLength: this._averageCellLength, _hiPriInProgress: this._hiPriInProgress});
- Batchinator.js
- console.log('SSU', 'Batchinator#schedule(){安排列表项更新}');
- console.log('SSU', 'Batchinator#schedule()InteractionManager.runAfterInteractions(){开始执行列表项更新}');
- console.log('SSU', 'Batchinator#schedule()InteractionManager.runAfterInteractions(){结束执行列表项更新}');
- VirtualizeUtils.js
- console.log('SSU', 'VirtualizeUtils#computeWindowedRenderLimits(){开始计算屏幕渲染列表项区域}', {maxToRenderPerBatch, windowSize}, prev, scrollMetrics);
- console.log('SSU', 'VirtualizeUtils#computeWindowedRenderLimits(){计算屏幕渲染列表项区域}', {visibleBegin, visibleEnd}, {overscanLength, fillPreference, overscanBegin, overscanEnd});
- console.log('SSU', 'VirtualizeUtils#computeWindowedRenderLimits(){完成计算屏幕渲染列表项区域}', prev, {first, last});
- console.log('SSU', 'VirtualizeUtils#computeWindowedRenderLimits(){完成计算屏幕渲染列表项区域}', prev, {first, last}, {overscanFirst, overscanLast, newCellCount});
- 初始化显示
- 根据设置参数预估显示数据项区[0,1](不用担忧是否显示一屏,上述Demo初始数据项个数为1)
- 数据项布局变化、列表布局变化、列表内容区大小变化均会安排列表项更新(显示数据项个数)优先级
- 根据数据项返回高度、列表高度、列表内容区大小计算出显示不满一屏,直接更新列表项
- 计算出当前状态下计算出显示列表项区间[0,8],经过setState触发从新render
- render出9个数据项和1个尾部空白区,接着走2,由于此时一屏能够显示下,优先级低,因此等待空闲触发更新列表项
- 计算出当前状态下不须要继续添加数据项,setState没有变化,更新中止,页面状态稳定
- 滚动显示(用力向下滑动,发现滑动到“第8个 title=8”就再也划不动了,过一会又能够接着滑)
- 滚动回调(_onScroll)会安排列表项更新(显示数据项个数)优先级,优先级低,因此等待空闲触发更新列表项
- 等到滑动到最后一个列表项时,滑不动了,此时空闲,触发更新列表项
- 根据当前状态,计算出显示列表项区间[0,11]
- render出12个数据项和1个尾部空白区,等待空闲更新列表项
- 列表项计算区间没有变化,更新中止,页面状态稳定
- 不断向下滑动,找到一个中间状态(好比第一项显示“第62个 title=62”发现有顶部空白和底部空白)
- 快速向上滑动,发现会有空白块闪烁一下后再显示出对应内容,滑动流畅
- 尝试设置getItemLayout属性,发现再也不有数据项的布局回调,并且空白区的高度准确(不会出现滑动到必定位置就滑不动的彩蛋)。这么看好像性能也没有提升多少。
-
原理
- 整个过程就是多render几回,而后就达到平衡态了。
- 整个过程有个窗口的概念,经过一通计算,获得当前状态一个合适的窗口大小。
- 各个文件的依赖关系。总体看一下基本就拼接成如今的功能,核心在VirtualizedList。