React Native性能优化总结
React Native开源已经接近2年时间,京东、携程、58同城等互联网公司都在使用,我们公司于今年也开始采用,并推广到各个新项目。本文重点分享我们遇到的一些问题以及优化方案。
一、为什么会引入React Native?
- APP大小占用:公司数字钱包项目功能不断增加,App体积达到近30MB。使用React Native开发,Size远小于Native开发,有助于App可持续发展。
- 用户体验佳:RN通过JavascriptCore解析JavaScript模块,转换成原生组件渲染,相比H5页面性能提升,用户体验媲美Native。
- 相对成熟:Android和iOS的RN都已开源,组件和API丰富,跨平台一致,接口稳定,适合业务开发。
- 支持动态更新:iOS系统限制不能动态执行远端Native代码,而RN完全满足该需求,支持代码热更新。
- 跨平台:RN提供的API和组件大多跨平台,对少数不支持的进行二次封装,实现一份代码运行在iOS和Android上,提高开发效率,降低维护成本。
二、如何引入?
基于RN 0.56版本,我们开发了快速开发框架,主要从工具、控件、稳定性性能优化和发布等方面着手。工具包括CLI工程创建和打包工具;控件扩展了跨平台API和业务相关组件;优化聚焦页面加载提速和稳定性提升;发布支持统一管理和差分增量更新。此外,还提供文档和技术支持,形成一个完整的产品开发框架。
三、遇到的问题和优化
做React Native开发的团队,常面临四个问题:打包出来的JSBundle过大、首次进入RN页面加载缓慢、稳定性不够导致大量crash、大数据量时ListView加载卡顿。接下来针对这些问题一一探讨。
分析性能瓶颈显示,最大的瓶颈在JS初始化和模块加载阶段,这段耗时占页面加载的大部分。为了提升速度,必须优化JSBundle的执行时间。
JSBundle文件过大与页面加载慢
一个HelloWorld应用使用官方打包命令生成的JSBundle文件大小约531KB,其中RN框架JavaScript占530KB,压缩后也有148KB。对于多业务场景,每个业务包含完整框架代码不可接受。因此,需改造打包脚本,将框架代码拆分出来供所有业务共享。
背景知识:RN应用代码结构分为头部依赖引用、中间模块定义和尾部入口注册。打包后JSBundle包含全局定义、模块定义和引擎初始化。模块ID由打包过程解析依赖生成,如果所有业务入口文件首先require react/react-native,则相关模块ID固定。
拆分方案一
将JSBundle拆分为框架部分和业务部分,加载时合并。但拆分后不能分别执行,因为依赖问题会导致模块找不到。此方案适用于纯RNApp,native为壳,所有业务为RN开发,实现简单、无侵入。
拆分方案二
框架部分文件大,执行时间长。设计一个空白页面的FakeApp,后台预加载框架JS引擎,进入业务页面时使用预加载引擎渲染业务模块,可大幅提升加载速度。但业务JS代码膨胀后,执行时间瓶颈会再次出现。
拆分方案三
受Facebook的Unbundle方案启发,将所有JS模块拆分成独立文件。打包时生成entry.js、UNBUNDLE标志文件和模块文件(如12.js),模块ID对应文件名。加载时按需读取文件。Android支持unbundle包,iOS因文件IO效率问题使用prepack模式,将模块打包成二进制文件。
我们修改打包工具,开发了CRNUnbundle,优化文件合并:将公共JS文件合并为common_ios(android).js,并添加_crn_config记录入口模块ID和模块路径。测试显示,iOS和Android的加载时间比官方方式减少50%。
页面加载优化
实现拆包后,页面加载流程缓存common.js的JS执行引擎(iOS RCTBridge, Android ReactInstanceManager),页面加载提速。对使用过的引擎定义生命周期状态:loading、ready、dirty,并在dirty状态达到条件时回收,从缓存模块数组中删除业务代码,还原引擎状态。
错误处理
RN上线初期,因RN导致的crash较多,常见错误包括iOS的RCTFatalException和Android的多种异常。
iOS crash处理:主要来自RCTFatalException,通过设置自定义Error Handler处理RCTFatal错误,避免生产环境直接crash。
Android crash处理:出现在bundle加载的RuntimeException、JS执行中的NativeExceptionsManagerModule、native模块执行的NativeModuleCallExceptionHandler,以及so库加载失败的UnsatisfiedLinkError。解决方案包括捕获ReactInstanceManagerImp的createReactContext异常,给System.load添加try-catch补偿。
ListView性能问题
RN官方ListView未实现cell重用,超出屏幕的条目仍被渲染,导致大数据量时卡顿。我们开发了可重用cell的QRNListView,iOS借鉴ReactNativeTableView,Android采用RecyclerView实现,接口与官方一致,并扩展下拉刷新、加载更多等功能。测试显示,数据量少时性能相当,数据量大时优势明显。
四、下一阶段的规划
- QRN-Web开发:通过工具将QRN代码转换到H5平台运行,实现一套代码三端(iOS、Android、Web)运行,降低开发维护成本。
- 单JS执行引擎实现:RN内存耗用大,多引擎创建占用大量内存。通过业务隔离,在单个引擎中运行多个业务JS代码,减少内存占用,正在进行相关尝试,预计未来1-2个版本完成线上验证。