react native 性能优化基础 - 官方文档

来源:互联网 发布:用友软件销售公司 编辑:程序博客网 时间:2024/05/05 23:37

使用React Native的一个重要原因就是达到60FPS的刷新,这看起来跟本地APP是一样的。在可能的情况下,我们尽量完善ReactNative的性能,使你只关注APP的逻辑,而可以不用管性能的优化。但是有的地方,我们还没有关注到。同样,跟本地代码(Object c)一样,我们不能确定哪种方式是最好的,所以还需要你手动干预。

这个指南的目的是教会你一些基础知识,以帮助您解决性能的问题,并且,讨论常见的一些性能问题以及解决的方法。

你需要了解什么是帧

在你的祖父辈,它们一般把视频称为”移动的图片”的一个原因是:视频中逼真的动作是以固定的速度,快速改变静态的图片创造出来一个错觉。这里的每一个图片就一个是帧。每一秒中图片的数量(帧),它直接影响了视频的真时性(或者app中的用户界面). iOS的设备是每秒60帧。它也就给了你以及UI系统大约16.67毫秒的时间,来完成生成一张静态图片(帧)的所有工作。如果你不能在16.67ms内完成,则你将会丢失一个帧,UI会出现无响应状态。

在你的应用程序中,打开Deveroper菜单,并切换到fps监视器,你会发现有两种不同的帧速率。

Javascript 帧速率

许多的React Native应用,你的业务逻辑都是运行在Javascript线程上的。比如React 应用的生命周期(mount, update), API调用,触摸事件的处理等。并且在每一帧失效前, 更新原生支持的视图(Views)是被成批处理的,并且在每一次事件循环遍历结束后,发送给本地端。如果Javascript线程对于一个帧没有响应,则被认为删除这个帧。举例来说,在一个复杂的应用的root 组件中,你调用了this.setState. 它会导致重新宣染子组件,这个过程很消耗资源。可以假设为它将花费200ms, 那么导致12帧丢弃(200ms / 16.67ms)。通过Javascript控制的作何动画,在这个过程会被冻结。如果作何计算超过100ms, 用户就可以感受到。

这通常发生在导航的切换中: 当你添加一个新的路由,Javascript线程需要读取这个场景所需要的所有组件,然后通过适当的命令发送给本地端,创建视图。这个过程会花费多个帧,引起卡顿,这是因为transition是由Javascript控制的。有此组件会在componentDidMount中做额外的计算,这可能会导航在transition卡顿的第二个原因.

另一个例子是响应触摸:如果你要做的任务在Javascript线程上跨越多个帧,你可能会注意到TouchableOpacity的延迟。这就是在Javascript 线程在忙的时候,不能处理从主线程发送过来的触摸事件的原因,所以会出现,native view调整了透明度,而又不对触摸事件做出响应。

主线程帧速率

许多人可能注意到,使用NavigationIOS的性能要比Navigator要好。这个原因是,transition(转场动画)的完成是在main thread中完成的。

同样,当Javascript被锁上时,你依然可以通过ScrollView向上和向下滚动,这是因为ScrollView存在于主线程中(虽然scroll event会向JS触发,但是对于滚动的发生是没有必要的).

常见的性能问题来源

Console.log 语句

当运行一个打包好的app, Console.log语句会引起很大的瓶颈。它会包含调试库redux-logger, 所以在打包前,确保删除了Console.log语句.

Development mode(dev=true)

在dev模式中,会影响到Javascript线程的性能. 比如在运行时,向你提供警告和错误信息,检验propTypes。

Slow navigator transitions

上面提供过,Navigator动画是由Javascript线程控制。假设一个从右到左的场景转换, 既添加一个新页面:新的场景scene,是从右到左移动(-320 到 0),在这个转换过程的每一帧,Javascript thread需要将新的x位置发送给主线程。如果javascript 线程被冻结。它就不能做这些,那么这些帧就不会被更新,动画就变得断断续续。

一劳永逸的解决方案是将基于 Javascript的动画转变为基于main thread的动画。在上面的例子中,我们可能需要计算transition的每一个x偏移位置,然后发送给主线程,以一个优化的方式来执行。现在Javascript不需要在负责这个。

可是这中方案还没有实现,所以在这段期间,我们应该使用InteractionManager,为新的scene选择最少的内容数量以及动画过程。“`InteractionManager.runAfterInteractions接受一个唯一的回调函数作为参数。这个回调函数在导航动画完成后触发。

你的scene组件应该如下

class ExpensiveScene extends React.Component {  constructor(props, context) {    super(props, context);    this.state = {renderPlaceholderOnly: true};  }  componentDidMount() {    InteractionManager.runAfterInteractions(() => {      this.setState({renderPlaceholderOnly: false});    });  }  render() {    if (this.state.renderPlaceholderOnly) {      return this._renderPlaceholderView();    }    return (      <View>        <Text>Your full view goes here</Text>      </View>    );  }  _renderPlaceholderView() {    return (      <View>        <Text>Loading...</Text>      </View>    );  }};

在这,你可以不局限于加载器,也可以是读取部分的类容。比如,当你加载Facebook appp时,你会看到新闻评论的占位符是一个灰色的矩形.

ListView初始化时或者读取一个大的列表时很慢

我们可以通过以下的几个方面,改善部分性能
initialListSize
这个属性用来指定我们第一次渲染时,要读取的行数。如果我们想尽可能的快,我们可以设置它为1, 然后可以在后续的帧中,填弃其它的行。每一次读取的行数,由pageSize决定.

**pageSize**pageSize
在使用了initialListSize之后,ListView根据pageSize来决定每一帧读取的行数,默认值为1, 但如果你的的views 非常的小,并且读取时占的资源很少, 你可以调整这个值,在找到适合你的值。

scrollRenderAheadDistance
“以像素为单位,如何预读取要加载的行?”
如果我们的列表有2000个项,而让它一次性读取,它会导致内存和计算资源的耗尽。所以scrollRenderAhead distance可以指定,超出当前视口多省,继续宣染。

removeClippedSubviews
“当它设置为true时,当本地端的superview为offscreen时 ,不在屏幕上显示的子视图offscreen(它的overflow的值为hidden) 会被删除。它可以改善长列表的滚动的性能,默认值为true.

这对于大的ListViews来说是一个非常重要。在Android, overflow的值通常为hidden. 所以我们并不需要担心它的设置,但是对于iOS来说,你需要设置row container的样式为overflow: hidden

我的组件读取时非常慢,并且不需要立即读取所有的内容
对于这个问题,我们首先可能不会想到使用ListView. 但使用ListView得当,往往是实现移定性能的关键,如我们上面讨论的,ListView向我们提供了不同的工具,可以让你视图分隔为多个帧中读取,满足特定的需求。请记住ListView也可以是水平读取。

JS FPS plunges when re-rendering a view that hardly changes

如果你使用了ListView, 你必须提供一个rowHasChanged函数,它可以确定是否需要重新渲染一行。如果你使用的是不变的数据结构,它可以跟引用类型的相等比较一样简单。

相似的,你也可以实现shouldComponentUpdate,以确定需要重新渲染时的条件。如果你写了一个纯组件(render函数完成依赖props和state), 你可以利用PureRenderMixin来做这个事情,再一次,不可变的数据结构非常有用,可以保持它的快速性,如果你要深入的比较一个大的对像列表,使得重新渲染整个组件会更快,而且只要更少的代码.

因为在相同的时间里,Javascript线程上做了大量的工作,导致帧被删除

“慢的导航转场”是这个问题的最常见的表现,但也有其它的情形。使用InteractionManager是一个最好的方法。但如果在动画期间,有大量的延迟类的工作,则可以考虑LayoutAnimation.

Animated api当前计算每一帧都是基于Javascript线程,而LayoutAnimation利用了核心动画,不会受到JS线程和主线程丢帧的影响。

常见的一种情况是弹出对话框的动画(从上向下滑动,并且淡入的动画), 而初始化和接收多个网络请求的响应,渲染对话框的的内容,并且当打开对话框时,更新视图。可以查看Animations 指南了解更多使用LayoutAnimation的信息.

警告 - LayoutAnimtion仅适用于fire-and-forget动画(“静态”动画) - 如果动画需要被中断,则你需要使用Animated.

在一个sceen里,移动一个View(Scrolling, translating, rotating), UI线程掉帧

这种情况常见于,带透明背景的文本,在一个图片之上。或者它的alpha混合的情况。可以使用shouldRasterizeIOS和renderToHardwareTextureAndroid来改进性能。

但最好不要浪用它,或者你的内存会被用完,在使用这个属性时,最好监控性能和内存的使用。如果你不计划移动一个View, 则把这些属性关闭。

改变一个图片大小的动画,UI线程掉帧

在iOS, 每一次你调整一个图片组件的宽和高,它都是从原始图片中re-croped and scaled。这个过程非常昂贵,特别是对于大图来说。代替的, 我们可以使用transform: [{scale}]样式属性动画的改变大小,比如轻触一个图片,然后变为全屏。

My TouchableX view isn’t very responsive

有时,如果我们在相同的帧里改变透明度和颜色,以响应触摸事情。我们可能在onPress返回之后看不到作何的响应。如果onPress里有一个setState, 它引发大量的工作,并且有一些帧被删除掉,这时就会出现这种情况。一种解决方案是,将作何的动画包装在requestAnimationFrame处理器中。

handleOnPress() {  // Always use TimerMixin with requestAnimationFrame, setTimeout and  // setInterval  this.requestAnimationFrame(() => {    this.doExpensiveAction();  });}

Profilling

使用内置的分析器,获得更多关于Javascript 线程和主线程的信息。
在iOS中,instruments是一个非常好的工具,对于Android, 则需要学会使用systrace.

0 0
原创粉丝点击