⚡️ React Native 启动速度优化——Native 篇(内含源码分析)

本文深入分析了React Native的启动流程,包括Native容器初始化、Native Modules绑定和新架构下的优化策略,如JSI和TurboModules。通过对iOS和Android源码的详细解读,提出性能优化建议,如Native模块的懒加载,以提高启动速度。
摘要由CSDN通过智能技术生成

Web 开发有一个经典问题:「浏览器中从输入 URL 到页面渲染的这个过程中都发生了什么?

据我考据这个问题起码有十年历史了。在日新月异学不动的前端圈子里,这个问题能一直被问,就是因为因为它是个非常好的问题,涉及非常多的知识点,平时做一些性能优化,都可以从这个问题出发,分析性能瓶颈,然后对症下药进行优化。

不过今天我们不谈 Web 的性能优化,只是借助刚刚的那个那个经典问题的分析思路,从 React Native 的启动到页面的第一次渲染完成,结合 React Native 的源码和 1.0 的新架构,一一分析 React Native 的启动性能优化之路

如果你喜欢我的文章,希望点赞👍 收藏 📁 评论 💬 三连支持一下,谢谢你,这对我真的很重要!

阅读提醒

1.文章中的源码内容为 RN 0.64 版本

2.源码分析内容涉及 Objective-CJavaC++JavaScript 四门语言,我尽量讲得通俗易懂一些,若实在不理解可以直接看结论

0.React Native 启动流程

React Native 作为一个 Web 前端友好的混合开发框架,启动时可以大致分为两个部分:

  • Native 容器的运行
  • JavaScript 代码的运行

其中 Native 容器启动在现有架构(版本号小于 1.0.0)里:大致可以分为 3 个部分:

  • Native 容器初始化
  • Native Modules 的全量绑定
  • JSEngine 的初始化

容器初始化后,舞台就交给了 JavaScript,流程可以细分为 2 个部分:

  • JavaScript 代码的加载、解析和执行
  • JS Component 的构建

最后 JS Thread 把计算好的布局信息发送到 Native 端,计算 Shadow Tree,最后由 UI Thread 进行布局和渲染。

关于渲染部分的性能优化可以见我之前写的《React Native 性能优化指南》,我从渲染图片动画长列表等方向介绍了 RN 渲染优化的常见套路,感兴趣的读者可以前往查看,我这里就不多介绍了。

上面的几个步骤,我画了一张图,下面我以这张图为目录,从左向右介绍各个步骤的优化方向:

提示:React Native 初始化时,有可能多个任务并行执行,所以上图只能表示 React Native 初始化的大致流程,并不和实际代码的执行时序一一对应。

1.升级 React Native

想提升 React Native 应用的性能,最一劳永逸的方法就是升级 RN 的大版本了。我们的应用从 0.59 升级到 0.62 之后,我们的 APP 没有做任何的性能优化工作,启动时间直接缩短了 1/2。当 React Native 的新架构发布后,启动速度和渲染速度都会大大加强。

当然,RN 的版本升级并不容易(横跨 iOS Android JS 三端,兼容破坏性更新),我之前写过一篇《React Native 升级指南(0.59 -> 0.62)》的文章,如果有升级想法的老铁可以阅读参考一下。

2.Native 容器初始化

容器的初始化肯定是从 APP 的入口文件开始分析,下面我会挑选一些关键代码,梳理一下初始化的流程。

iOS 源码分析

1.AppDelegate.m

AppDelegate.m 是 iOS 的入口文件,代码非常精简,主要内容如下所示:

// AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   
  // 1.初始化一个 RCTBridge 实现加载 jsbundle 的方法
  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];

  // 2.利用 RCTBridge 初始化一个 RCTRootView
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"RN64"
                                            initialProperties:nil];

  // 3.初始化 UIViewController
  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  
  // 4.将 RCTRootView 赋值给 UIViewController 的 view
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  return YES;
}

总的来看入口文件就做了三件事:

  • 初始化一个 RCTBridge 实现加载 jsbundle 的方法
  • 利用 RCTBridge 初始化一个 RCTRootView
  • RCTRootView 赋值给 UIViewController 的 view 实现 UI 的挂载

从入口源码我们可以发现,所有的初始化工作都指向 RCTRootView,所以接下来我们看看 RCTRootView 干了些啥。

2.RCTRootView

我们先看一下 RCTRootView 的头文件,删繁就简,我们只看我们关注的一些方法:

// RCTRootView.h

@interface RCTRootView : UIView

// AppDelegate.m 中用到的初始化方法
- (instancetype)initWithBridge:(RCTBridge *)bridge
                    moduleName:(NSString *)moduleName
             initialProperties:(nullable NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;

从头文件看出:

  • RCTRootView 继承自 UIView,所以它本质上就是一个 UI 组件;
  • RCTRootView 调用 initWithBridge 初始化时要传入一个已经初始化的 RCTBridge

RCTRootView.m 文件里,initWithBridge 初始化时会监听一系列的 JS 加载监听函数,监听到 JS Bundle 文件加载结束后,就会调用 JS 里的 AppRegistry.runApplication(),启动 RN 应用。

分析到这里,我们发现 RCTRootView.m 只是实现了对 RCTBridge 的的各种事件监听,并不是初始化的核心,所以我们就又要转到 RCTBridge 这个文件上去。

3.RCTBridge.m

RCTBridge.m 里,初始化的调用路径有些长,全贴源码有些长,总之最后调用的是 (void)setUp,核心代码如下:

- (Class)bridgeClass
{
   
  return [RCTCxxBridge class];
}

- (void)setUp {
   
  // 获取bridgeClass 默认是 RCTCxxBridge
  Class bridgeClass = self.bridgeClass;
  // 初始化 RTCxxBridge
  self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
  // 启动 RTCxxBridge
  [self.batchedBridge start];
}

我们可以看到,RCTBridge 的初始化又指向了 RTCxxBridge

4.RTCxxBridge.mm

RTCxxBridge 可以说是 React Native 初始化的核心,我查阅了一些资料,貌似 RTCxxBridge 曾用名为 RCTBatchedBridge,所以可以粗暴的把这两个类当成一回事儿。

因为在 RCTBridge 里调用了 RTCxxBridgestart 方法,我们就从 start 方法来看看做了些什么。

// RTCxxBridge.mm

- (void)start {
   
  // 1.初始化 JSThread,后续所有的 js 代码都在这个线程里面执行
  _jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil];
  [_jsThread start];
  
  // 创建并行队列
  dispatch_group_t prepareBridge = dispatch_group_create();
  
  // 2.注册所有的 native modules
  [self registerExtraModules];
  (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
  
  // 3.初始化 JSExecutorFactory 实例
  std::shared_ptr<JSExecutorFactory> executorFactory;
  
  // 4.初始化底层 Instance 实例,也就是 _reactInstance
  dispatch_group_enter(prepareBridge);
  [self ensureOnJavaScriptThread:^{
   
    [weakSelf _initializeBridge:executorFactory];
    dispatch_group_leave(prepareBridge);
  }];
  
  // 5.加载 js 代码
  dispatch_group_enter(prepareBridge);
  __block NSData *sourceCode;
  [self
      loadSource:^(NSError *error, RCTSource *source) {
   
        if (error) {
   
          [weakSelf handleError:error];
        }

        sourceCode = source.data;
        dispatch_group_leave(prepareBridge);
      }
      onProgress:^(RCTLoadingProgress *progressData) {
   
      }
  ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值