性能综述
使用React Native开发App的一个主要优势是能够实现每秒60帧的流畅性能,从而提供类似原生应用的外观和手感。框架本身已经进行了大量优化,使开发者可以专注于业务逻辑,而无需过度担心性能问题。然而,在某些情况下,React Native仍无法自动处理所有优化,因此人工干预仍然是必要的。
本文旨在帮助你掌握排查React Native性能问题的基本知识,探讨常见问题的根源,并提供实用的解决方法。
关于“帧”你所需要知道的
视频和用户界面的流畅感实际上是由一系列静态图片快速切换产生的幻觉,其中每一张图片称为一帧。iOS设备通常以每秒60帧的速率运行,这意味着每一帧的渲染时间大约为16.67毫秒。如果在这个时间内未能完成所有工作,就会导致丢帧,从而影响界面的流畅度。
在React Native应用中,你可以通过开发菜单中的Show FPS Monitor来监控帧率,并会注意到有两个不同的帧率指标。
JS帧率(JavaScript线程)
大多数React Native应用的业务逻辑运行在JavaScript线程上,这是处理React组件、API调用和触摸事件的核心线程。数据更新到原生视图是批量进行的,通常在每帧结束前完成。如果JavaScript线程未能及时响应,就会发生丢帧。例如,在复杂组件中调用this.setState可能触发大规模重绘,耗时长达200毫秒,导致12帧丢失,使动画卡顿。用户通常对超过100毫秒的卡顿有明显感知。
这种情况在旧的Navigator导航器切换时尤为常见:推送新路由时,JavaScript需要绘制新场景的所有组件,并发送命令到原生端创建视图,这个过程可能占用多帧时间,引起卡顿。此外,componentDidMount中的额外操作可能进一步延长卡顿时间。
另一个例子是触摸事件响应:当JavaScript线程忙于处理多帧任务时,TouchableOpacity的透明度调整可能被延迟,因为原始触摸事件无法及时处理。
UI帧率(主线程)
与纯JavaScript实现的Navigator相比,NavigatorIOS的性能更佳,因为其切换动画完全在主线程执行,不受JavaScript线程掉帧影响。同样,当JavaScript线程卡顿时,ScrollView的滚动仍然流畅,因为它运行在主线程上,尽管滚动事件会分发到JS线程。
性能问题的常见原因
开发模式(dev=true)
在开发模式下,JavaScript线程的性能通常较差,这是因为框架需要执行额外的检查,如属性类型验证和警告提示。因此,务必在发布模式下测试应用性能,以获得准确结果。
console.log语句
在发布版本中,大量的控制台输出语句可能拖累JavaScript线程。确保移除所有调试库(如redux-logger)中的console调用。可以使用Babel插件transform-remove-console在构建时自动移除这些语句。安装插件后,在.babelrc文件中配置生产环境即可。
ListView首次渲染缓慢或滑动卡顿
建议使用新的FlatList或SectionList组件替代旧的ListView。这些新组件在性能上有显著提升,内存使用为常数级,无论列表长度如何。如果FlatList渲染仍慢,请实现getItemLayout属性来跳过布局计算,从而优化渲染速度。
重绘变化不大的页面时JS帧率下降
通过实现shouldComponentUpdate函数或使用PureComponent,可以避免不必要的重绘。纯组件的界面完全由props和state决定,结合不可变数据结构,可以快速进行深度比较,减少重绘开销。
JavaScript线程同时处理大量工作导致JS帧率下降
“导航切换缓慢”是这种情况的常见表现,但其他场景也可能发生。使用InteractionManager可以延迟任务执行,但如果动画期间延迟影响用户体验,可以考虑LayoutAnimation。AnimatedAPI默认在JavaScript线程计算关键帧,除非设置useNativeDriver: true,而LayoutAnimation利用Core Animation,不受JS线程和主线程掉帧影响。例如,在初始化网络请求并渲染模态框内容时,使用LayoutAnimation可以保持动画流畅。但注意,LayoutAnimation仅适用于“即发即弃”的静态动画,如需可中断动画,仍需使用Animated。
移动视图时UI线程掉帧
当具有透明背景的文本覆盖在图片上,或需要透明合成的视图频繁重绘时,容易导致UI线程掉帧。设置shouldRasterizeIOS(iOS)或renderToHardwareTextureAndroid(Android)属性可以改善性能,但需谨慎使用,避免内存激增。如果视图不需要移动,应关闭这些属性。
动画改变图片尺寸时UI线程掉帧
在iOS上,调整Image组件的宽度或高度会触发原始图片的重新裁剪和缩放,开销较大。建议使用transform: [{scale}]样式属性来缩放图片,而不是直接修改尺寸。例如,全屏放大图片时,这种方法更高效。
Touchable系列组件响应延迟
如果onPress处理函数中包含耗时操作(如setState触发大量计算),可能导致触摸反馈延迟。解决方法是将这些操作封装到requestAnimationFrame中,确保它们在新帧中执行,从而避免阻塞当前帧的渲染。例如:
handleOnPress() {
requestAnimationFrame(() => {
this.doExpensiveAction();
});
}
通过以上优化措施,可以有效提升React Native应用的性能,确保流畅的用户体验。