Web开发有一个经典问题:“浏览器中从输入URL到页面渲染的这个过程中都发生了什么?”。
这个问题历史悠久,但在前端领域一直被讨论,因为它涵盖了广泛的知识点,是性能优化分析的一个经典切入点。今天,我们将借鉴这个问题的分析思路,聚焦于React Native,从启动到页面首次渲染完成,结合源码和新架构(1.0),系统性地分析启动性能的优化路径。
请注意,文章中的源码分析基于React Native 0.64版本,涉及Objective-C、Java、C++和JavaScript四种语言。
React Native启动流程概览
React Native作为混合开发框架,其启动过程可大致分为两个主要部分:Native容器的运行和JavaScript代码的运行。
在现有架构(版本号小于1.0.0)中,Native容器的启动可细分为三个步骤:Native容器的初始化、Native Modules的全量绑定,以及JSEngine的初始化。
容器初始化完成后,JavaScript代码开始加载、解析和执行,随后构建JS组件。最后,JS线程将计算好的布局信息发送到Native端,生成Shadow Tree,最终由UI线程进行布局和渲染。
关于渲染部分的性能优化,可以参考作者之前撰写的《React Native性能优化指南》,本文主要关注启动过程的优化。
1. 升级React Native版本
提升React Native应用性能最直接有效的方法之一就是升级大版本。例如,从0.59升级到0.62后,即使没有进行额外的优化工作,应用的启动时间也可能缩短一半。未来的新架构发布后,启动和渲染速度将得到进一步加强。
当然,RN的版本升级涉及iOS、Android和JavaScript三端,可能会遇到兼容性破坏性更新,需要谨慎规划和测试。
2. Native容器初始化优化
容器的初始化从应用的入口文件开始。下面我们通过关键代码梳理初始化的核心流程。
iOS源码分析
-
AppDelegate.m
这是iOS的入口文件,主要完成三件事:- 初始化一个
RCTBridge来加载jsbundle。 - 利用
RCTBridge初始化一个RCTRootView。 - 将
RCTRootView设置为UIViewController的视图进行挂载。
初始化工作最终都指向RCTRootView。
- 初始化一个
-
RCTRootView
RCTRootView继承自UIView,本质上是一个UI组件。它的初始化方法需要传入一个已初始化的RCTBridge。在初始化时,它会监听JavaScript加载事件,当JS Bundle加载完成后,便调用JavaScript中的AppRegistry.runApplication()来启动RN应用。其核心是事件监听,而非初始化逻辑本身。 -
RCTBridge
RCTBridge的初始化最终会调用setUp方法,该方法会获取默认的bridgeClass(即RCTCxxBridge),然后初始化并启动RTCxxBridge。 -
RTCxxBridge
这是React Native初始化的核心。在其start方法中,大致流程如下:- 初始化专用的JS线程(
_jsThread),后续所有JavaScript代码都在此线程执行。 - 在主线程上注册所有
native modules。 - 准备JavaScript和Native之间的桥接及JavaScript运行环境。
- 在JS线程上创建消息队列(
RCTMessageThread),并初始化底层_reactInstance。 - 在JS线程上加载JS Bundle。
- 待以上步骤全部完成后,执行JavaScript代码。
这是一个涉及多线程(GCD)协作的复杂过程。
- 初始化专用的JS线程(
Android源码分析
-
MainActivity.java & MainApplication.java
MainActivity通常继承自ReactActivity。而MainApplication作为入口,实现了ReactApplication接口,需要创建一个ReactNativeHost对象。ReactNativeHost的主要职责是创建ReactInstanceManager,并配置诸如JavaScript主模块路径、开发模式开关、ReactPackage列表等参数。 -
ReactActivityDelegate
ReactActivity的功能由其委托类ReactActivityDelegate完成。在onCreate方法中,它会实例化一个ReactDelegate,并调用loadApp来加载应用页面,最终通过setContentView将根视图设置到Activity上。 -
ReactDelegate
ReactDelegate在loadApp方法中主要做两件事:创建ReactRootView作为根视图,然后调用getReactNativeHost().getReactInstanceManager()来启动RN应用。
优化建议:RN容器池
对于以Native为主的混合开发APP,一个有效的优化思路是提前初始化RN容器,即构建“RN容器池”。其核心是一个映射表(Map),key为RN页面的componentName,value为已经实例化的RCTRootView(iOS)或ReactRootView(Android)。
APP启动后,在适当的时机提前初始化可能用到的RN容器并放入池中。当需要进入某个RN页面时,首先从池中查找匹配的容器,如果找到则直接使用,避免重新初始化的开销。如果未找到,则执行常规的初始化流程作为兜底。
需要注意的是,每个RCTRootView/ReactRootView都会占用一定内存,因此池的大小、初始化时机以及清理策略都需要结合具体业务进行精细设计和权衡。
3. Native Modules绑定优化
现有架构的问题
在现有架构中,启动时会对所有Native Modules进行全量绑定,无论这些模块在当前页面是否被使用。随着业务迭代和模块增多,这一过程的耗时也会相应增加。
- iOS:在
RCTCxxBridge的start方法中,通过_initializeModules函数,在主线程初始化所有Native Modules。 - Android:在
ReactInstanceManager创建ReactContext时,通过processPackages函数同步遍历所有ReactPackage,将其中的Native Modules全部注册到表中。
优化建议
全量绑定问题在现有架构下难以根治。一个间接的优化思路是减少Native Modules的数量,即参与社区的“Lean Core”计划。该计划旨在精简React Native的核心库,将一些组件(如WebView)移出主工程,交由社区维护,使用时再单独集成。
这样做的好处包括:核心更精简,维护更聚焦;减少不必要的绑定耗时和JS加载体积,有利于启动性能;以及加快迭代速度等。升级到较新的RN版本(如0.62)本身就能享受到“Lean Core”带来的部分启动性能收益。
4. RN新架构前瞻:JSI与TurboModules
React Native新架构(虽然尚未正式发布)旨在从根本上解决一些性能瓶颈。
JSI(JavaScript Interface)
JSI是一个用C++编写的框架,它支持JavaScript直接调用Native方法,取代现有的Bridge异步通信机制。借助JSI,JavaScript可以直接获得C++对象(Host Objects)的引用,从而直接控制UI或调用Native Modules,省去了Bridge异步通信的开销,实现了同步调用。
TurboModules
TurboModules旨在解决Native Modules全量加载的问题。在新架构中,Native Modules将是懒加载的,只有在JavaScript代码真正调用某个模块时,该模块才会被初始化。
其调用路径大致如下:
- 使用JSI创建一个顶层的Native Modules代理(如
global.__turboModuleProxy)。 - 当JavaScript需要访问某个Native Module(例如
SampleTurboModule)时,会先调用TurboModuleRegistry.getEnforcing(),进而调用global.__turboModuleProxy("SampleTurboModule")。 - 这个调用会触发JSI暴露的Native方法,C++层根据传入的模块名找到对应的ObjC/Java实现,并返回一个JSI对象给JavaScript。
- JavaScript随后可以同步调用这个JSI对象上的方法和属性。
通过TurboModules,不仅可以消除启动时全量初始化Native Modules的时间消耗,还能借助JSI实现更高效的同步调用。
总结
本文从Native层面深入分析了React Native现有架构的启动流程,并总结了若干性能优化点,包括使用RN容器池进行预加载、通过升级版本享受“Lean Core”成果等。同时,展望了新架构中JSI和TurboModules如何从底层解决通信效率和模块加载的瓶颈问题。
参考
- React Native性能优化指南
- React Native升级指南(0.59 -> 0.62)
- Chain React 2019 - Performance in React Native
- React Native's new architecture - Glossary of terms
- React Native JSI Challenge
- RFC0002: Turbo Modules ™
- ReactNative与iOS原生通信原理解析-初始化
- React Native iOS 源码解析
- ReactNative源码篇:源码初识
- 如何用React Native预加载方案解决白屏问题