React Native性能之谜

日期:2017-04-20 14:10:00    来源:www.gzbifang.com

在一众跨平台开发工具中,React Native能够杀出一条血路,获得目前这么大的影响力,除了React社区生态圈的加持和Facebook的大力推广以外,另外一个最主要的原因就是其在开发效率和应用性能方面取得了一个比较好的平衡。本文将从React Native的性能角度来看看应该如何掌握这个平衡点。

在PhoneGap、RubyMotion、Xamarin、Ionic一众跨平台开发工具中,React Native能够杀出一条血路,获得目前这么大的影响力,除了React社区生态圈的加持和Facebook的大力推广以外,另外一个最主要的原因就是其在开发效率和应用性能方面取得了一个比较好的平衡:

  • 开发效率通过JS工程实践,逻辑跨平台复用得到极大提升
  • 性能则通过全Native的UI层得到满足

不过,虽说框架提供了这个平衡能力,平衡点的选择却掌握在开发者手中,本文将从React Native的性能角度来看看应该如何掌握这个平衡点。

React Native的工作原理

在React Native的应用中,存在着两个不同的技术王国:JS王国和Native王国。应用在启动时会先进行双向注册,搭好桥,让两个王国知道彼此的存在,以及定义好彼此合作的方式:

 

然后,在应用的实际运行过程中,两个技术王国通过搭好的桥,彼此合作完成用户功能:

 

因此,React Native的本质是在两个技术王国之间搭建双向桥梁,让他们可以相互调用和响应。那么就可以把上图简化一下:

React Native的性能瓶颈

经过上面的分析,我们就可以把一个React Native应用分成三个部分:Native王国、Bridge、JS王国。当应用运行时,Native王国和JS王国各自运行在自己独立的线程中:

Native王国:

  • 运行在主线程上(可能会有些独立的后台线程处理运算,当前讨论中可忽略)
  • iOS平台上运行Object-C/Swift代码,Android平台上运行Java/Kotlin代码
  • 负责处理UI的渲染,事件响应。

JS王国:

  • 运行在JS引擎的JS线程上
  • 运行JS代码
  • 负责处理业务逻辑,还包括了应该显示哪个界面,以及如何给页面加样式。

在Native王国中,经过谷歌、苹果公司多年的优化调整,Native代码能够非常快速的运行在设备上。在JS王国中,JS代码作为脚本语言,也能够很快速的运行在JS引擎上,这两边独立来看都不会有性能问题。性能的瓶颈只会出现在从一个王国转入另一个王国时,尤其是频繁的在两个王国之间切换时,两个王国之间不能直接通信,只能通过Bridge做序列化和反序列化,查找模块,调用模块等各种逻辑,最终反应到应用上,就是UI层用户可感知的卡顿。 因此,对React Native的性能控制就主要集中在如何尽量减少Bridge需要处理的逻辑上。

那么,什么情况下会需要Bridge处理逻辑呢?

  1. UI事件响应: 所有的UI事件都发生在Native侧,会以事件的形式传递到JS侧。这个过程非常简单,也不会涉及大量的数据转移。在React Native应用中,业务逻辑,应用状态,数据都在JS侧,所以UI事件只是一个触发器,不会有性能问题。
  2. UI更新:前面已经说过JS负责决定应该展示哪个界面,以及如何样式化界面,因此UI更新的发起方是JS侧,更新时会向Native侧同步大量的UI结构和数据,这类更新经常出现性能问题,尤其是在界面复杂、变动数据大,或者做动画、变动频繁时。
  3. UI事件响应和UI更新同时出现:在UI更新时,结构变化不大,则性能问题不大;但是如果这时又有UI事件触发JS侧逻辑处理,而该逻辑处理又比较复杂,耗时较长,导致JS侧没有时间片处理与Native侧数据同步时,也会发生性能问题。

React Native的性能优化措施

前面已经解释了React Native的性能瓶颈会在什么地方,React Native官方也知道这些,其在React Native中提供了一些性能优化措施帮助开发者克服这些性能问题:

  1. 框架自带的React基于Virtual Dom的Diff算法保证了UI变动时传递的只是变化的UI部分,尽量减少需要同步的数据。
  2. 通过Direct Manipulation的方式直接在底层更新了Native组件的属性,从而避免渲染组件结构和同步太多视图变化所带来的大量开销。这样的确会带来一定的性能提升,同时也会使代码逻辑难以理清,而且并没有解决从JS侧到Native侧的数据同步开销问题。因此这个方式官方都不再推荐,更推荐的做法是合理使用setState()和shouldComponentUpdate()方法解决这类问题。
  3. 在遇到动画性能问题时,可以使用Annimated类的库,一次性把如何变化的声明发送到Native侧,Native侧根据接收到的声明自己负责接下来的UI更新。不需要每帧的UI变化都同步一次数据。
  4. Native和JS混编,把会大量变化的组件做成Native组件,这样UI的变更数据直接在Native侧自己处理了,无需通过Bridge,而不变的内部组件因为没有数据更新需要同步,所以也不会使用到Bridge。框架提供的NavigatorIOS相对于Navigator的性能提升就是这种做法。
  5. 遇到事件响应和UI更新同时发生导致的性能问题时,可以使用Interaction Manager把那些耗时较长的工作安排到所有互动或动画完成之后再进行。

探求性能和效率平衡的套路

在了解了React Native的性能瓶颈和优化措施之后,就可以大概总结一个探寻React Native开发效率和性能平衡点的套路:

第一步: 全JS实现, 从一开始在技术选型上用React Native就是为了保证开发的效率,在没有遇到性能问题之前,最大化效率是团队的一致追求。

第二步: 从JS侧进行性能优化

  • 对于那些明显会涉及Bridge、需大量处理逻辑的场景,比方说动画,复杂的手势操作响应等,尝试使用经过优化过的库(比方说:Animated),一次传递动画或者数据整个数据的描述给Native,Native侧自己会按照声明执行下去。
  • 使用InteractionManager把耗时操作递延到UI响应之后,处理那些存在因为耗时的JS操作导致的UI响应性能问题。

第三步:在真机上实测,检查性能问题点。不要过早优化,找到问题点再一一处理。

第四步:如果经过JS端的优化策略之后,在设备上还是有性能问题,可以把有问题的部分以Native方式实现,这也是为什么要推荐React Native团队中有10%左右的Native Developer。在这个步骤中,需要注意问题的隔离方式,假设一个场景:在移动一个Container时,Container的UI同时发生变化,但是Container内部的内容并没有发生变化,这种情况下,只需要用Native实现Container,Container内部的组件还是以JS实现。

联系

伦经理

10年+互联网IT从业经验,丰富企信息化实战经验