本文作者:ivweb 朱灵子
React-Native通用化建设与性能优化
本文主要介绍react-native通用化建设以及对react-native项目进行性能优化的方案,总体来讲主要围绕以下几个方面展开:
- React Native通用化建设
- React Native bundle本地分包
- React Native项目线上性能分析
- React Native项目首屏加速和性能优化
React Native通用化建设
React Native通用化建设主要做了一下几个方面的事情:
- 通用化入口: 为React-Native项1目设置通用化入口,实现React-Native项目上线的弱客户端依赖;(更新离线包即可,同时后台tnow串下发url字段,这里如果稳定,客户端也可以一律写死,不根据url来下发字段)
- 版本自由切换: 通过后台tnow串下发实现任何项目(全屏+半屏)React Native版本与H5版本之间的自由切换
- 离线包机制优化:离线包拉取、解析与线上发布流程优化
- Bundle本地分包:实现react-native基础包和业务包的拆分
- 底层监控能力支持:为线上项目CPU/内存/FPS/crash率/渲染时间等各方面的数据获取提供通用化的接口
接下来重点介绍react-native线上离线包优化机制以及react-native项目bundle本地分包方案
react-native线上离线包优化机制
为了实现React-Native线上项目react-native版本与h5版本的自由切换,同时合理地管理好不同项目不同版本的react-native离线包与h5离线包,我们的方案是将h5离线包和react-native bundle文件打在同一个离线包中(放在同一个bid号的离线包中)。
若后台url地址下发中携带md=rn字段,同时离线包中可以检测到react-native bundle文件并且app版本号符合react-native离线包中所配置的离线包生效所要求的app版本范围,则优先加载项目react-native版本。若不满足上诉几点要求,我们则优先加载react-native bundle本地文件或直接走项目h5线上资源。
如下图所示为离线包优化整体流程图
React Native bundle本地分包方案
下图为faceBook推出的react-native消息流页面加载耗时分布图
从这张图中我们可以看出,RN加载速度最大的瓶颈其实在于图中绿色的区块JS init+Require,这块时间也就是JSBundle的执行时间;同时若多个项目同时上线,多个业务却不共用基础模块,jsbundle文件会越来越大,app的离线包文件负荷也会越来越大
基于以上提出的两个问题,我们的解决方案是:react-native bundle本地分包策略
一般基础包压缩以后有150k左右,而较复杂的业务所分离出的业务包体积最多也不到100k。React-native打包方案是一套类似 CommonJS的轻量require/define模块系统,保持轻量和对RN特性关注也是RN不使用webpack和broswerify而是自己实现打包的原因。要实现react-native bundle本地分包,我们要做到依赖引用(业务包去 require 基础包中的模块),因此我们需要把基础包中包含的模块列表导出来给业务包打包时使用。
以下为已实现的react-native bundle本地分包方案的主要思路:
用户在访问react-native view时,客户端检索到离线包中的业务包bundle文件以后后与基础包文件进行简单的合并,然后注入Jscontext&&runApplication, 最后展示React-Native view
使用这一打包思路实现bundle本地分包的优势
- 减少离线包体积,降低离线包热更新流量损耗;
- 降低客户端工作量,不用引入复杂的 Diff算法来分离业务包与基础包的重复部分;
以上打包方法确实解决了app中react-native bundle打包以后文件包体积过大的问题,但是却没有解决react-native bundle加载以及执行时间过长的问题,基于这一点我们提出的方案是
预加载基础包,再运行业务包,而且多个react-native业务切换的时候可以直接复用基础包
该方案的可行性分析:
- 按照之前的方案打出的基础包可以独立运行,所以我们可以凸显加载基础包,基础包加载以后业务后也可以正常运行;
- 预加载基础包的时机可以是runJSInContext部分,也可以直接提前到整个app launch以后,这样可以很大程度上减少react-native庞大的基础包的加载时间
- 这一优化功能的实现我们需要修改react-native IOS部分的源代码,经过调研,react-native源码中有对应接口,可以实现runJSInContext 和 runApplication 的分离
React Native项目线上性能分析
以下为短视频react-native项目的线上数据,主要从首屏时间、cpu、内存以及crash率等方面和h5项目进行对比
通过对比可以发现,react-native项目和h5相比在首屏时间以及fps等方面存在较大的优势,但是其在内存方面存在劣势,而且在首屏时间方面的优势还存在较大的提升空间
React Native性能优化方案
接下来我们从首屏加速、性能优化这两个方面进行分析,主要的优化策略如下图所示:
首屏时间方面的优化主要有
- 文章第一部分详细讲述的react-native Bundle本地分包方案,以及后面提出的先加载基础包后加载业务包的优化
- 前端数据缓存优化以及cgi图片预加载,客户端提前加载cgi的预加载优化
- 针对安卓端提出的安卓端react-native上下文预加载优化
接下来具体介绍针对安卓端提出的安卓端react-native上下文预加载优化
使用React Native开发混合应用的过程中,我们第一次进入页面(React Activity)会有一个短暂的白屏过程(在真机上近 1秒,在模拟器上比较快,在 200毫秒左右),而且在完全退出后再进入,仍然会有这个白屏。
安卓端打点后可以发现在ReactActivity的onCreate方法中,耗时最多的是 createRootView()和startReactApplication()这两个操作 对于安卓白屏的问题我们的优化方案是:提前创建ReactRootView进行render,在runApplication之后直接将创建好的rootView挂载在React-Native view上去
这里是安卓react-native源码时序图
具体来讲就是将oncreate方法中的createRootView()和startReactApplication()这两个耗时比较多的方法提前到上一个activity中进行处理或者在整个app启动以后进行处理
这一点我们可以借鉴qq空间团队的思路,主要优化思路为:客户单预初始化上下文与cgi预加载的结合,主要流程图如下图所示:
在app打开以后自动预初始化客户端React上下文 在点击react-native入口以后直接复用客户端初始化好的rootView,与此同时客户端发起cgi请求,预加载cgi数据并缓存,前端直接读取缓存数据 【注:由于react-native不存在渲染html文件,所以我们通用的preload在rn这里不太适用】
性能优化方案
- react-native js端以及客户端版本一起进行版本升级,内存优化:
最新版rn源码已改为模块按需加载的模式,升级react-native客户端与js端的源码至最新版,可以很大程度上降低react-native项目运行的内存损耗,同时还可以降低app运行的 crash率
- 项目开发过程中减少View层的嵌套,cpu优化
减少绘制,优化CPU
- listView性能优化,内存优化
我们在测量短视频项目启动时的内存变化量时发现了一个有趣的现象:每次测量时是否杀掉进程重新开启app来进行测量和不杀进程进行多次测量的内存变化量相差较大
为什么会存在这个问题呢?因为短视频项目使用的是listView组件ListView 首次加载时都默认最多加载 initialListSize 个子项,所以能保证启动速度,但是在滑动的过程中会逐渐向 ListView 中添加子项,新出现的子项都是通过创建新的 View,而完全没有复用的过程。所以若应用中ListView 的子项数量特别多,ListView 滑动过程中内存会逐渐上涨,离开react-native-view后内存也不会快速释放,所以就是出现之前内存测量的奇怪问题
而listView是rn最常用的组件之一,优化ListView势在必行,这里我们提出两种方案:
- 版本升级之前可以使用能够进行内存自动回收的第三方组件RN-RecyclerView
- react-native最新0.43版本推出了可以直接进行内存回收的原生组件FlatList
感谢您的阅读,欢迎提出问题以及修改建议。