14
头图

There is a classic problem in web development: "What from the URL input to the page rendering in the "

As far as I textual this issue at least, there years history. In the ever-changing front-end circle, this question can be asked all the time because it is a very good question, involving a lot of knowledge points, and usually doing some performance optimization, you can start from this problem and analyze the performance bottleneck. , And then optimize the remedy.

But today we will not talk about the performance optimization of the Web. We just use the analysis of the classic problem just now, from the startup of React Native to the completion of the first rendering of the page, combined with the source code of React Native and the new 1.0 architecture, one by one Analyze the startup performance optimization road of React Native .

If you like my article, I hope to like it 👍 Collection 📁 Comment 💬 Three consecutive support, thank you, this is really important to me!
Reading reminder :
1. The source code content in the article is RN 0.64 version
2. The content of source code analysis involves Objective-C , Java , C++ , JavaScript . I try to speak it in an easy-to-understand manner. If you really don’t understand, you can directly see the conclusion.

0.React Native startup process

React Native, as a web front-end friendly hybrid development framework, can be roughly divided into two parts at startup:

  • Native container operation
  • JavaScript code running

Among them, the Native container is started in the existing architecture (the version number is less than 1.0.0): it can be roughly divided into three parts:

  • Native container initialization
  • Native Modules full binding
  • Initialization of JSEngine

After the container is initialized, the stage is handed over to JavaScript, and the process can be subdivided into 2 parts:

  • Loading, parsing and execution of JavaScript code
  • Construction of JS Component

Finally, JS Thread sends the calculated layout information to the Native side, calculates the Shadow Tree, and finally layout and rendering by UI Thread.

About rendering performance optimization section can see written before me "React Native Performance Tuning Guide" , I'm from rendering , picture , animation , long list and other direction describes common routine RN rendering optimization , Interested readers can check it out, I won’t introduce it here.

For the above steps, I drew a picture. Below I use this picture as a table of contents to introduce the optimization direction of each step from left to right:

Tip : React Native initialization time, there may be multiple tasks parallel execution , so the only figure shows a schematic flow React Native initialization, and does not correspond to the actual code execution timings.

1. Upgrade React Native

If you want to improve the performance of React Native applications, the most once and for all method is to to the large version of RN . After our app was upgraded from 0.59 to 0.62, our app did not do any performance optimization work, and the startup time was directly reduced by 1/2. When the new architecture of React Native is released, the startup speed and rendering speed will be greatly enhanced.

Of course, the RN version upgrade is not easy (across the iOS Android JS three ends, compatible with destructive updates), I wrote an article "React Native Upgrade Guide (0.59 -> 0.62)" , if there is an upgrade The old iron of ideas can read it for reference.

2. Native container initialization

Initialization container is certainly the beginning of the analysis from the entrance APP file, the following I will pick some critical code , comb the initialization process.

iOS source code analysis

1.AppDelegate.m

AppDelegate.m is the entry file of iOS. The code is very streamlined. The main contents are as follows:

// 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;
}

In general, looking at the entry file, three things are done:

  • Initialize a RCTBridge method to load jsbundle
  • Use RCTBridge initialize a RCTRootView
  • Assign RCTRootView to UIViewController to implement UI mounting

From the entry source code, we can find that all the initialization work points to RCTRootView , so let's take a look at what RCTRootView did.

2.RCTRootView

Let's take a look at RCTRootView , we only look at some of the methods we are concerned about:

// RCTRootView.h

@interface RCTRootView : UIView

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

Seen from the header file:

  • RCTRootView inherits from UIView , so it is essentially a UI component;
  • RCTRootView calls initWithBridge initialize, you must pass in an already initialized RCTBridge

In the RCTRootView.m file, initWithBridge will monitor a series of JS loading monitoring functions when it is initialized. After monitoring the loading of the JS Bundle file, it will call AppRegistry.runApplication() in the JS to start the RN application.

Analysis here, we found RCTRootView.m just realized the RCTBridge of events listener, not initialize core , so we have to go to RCTBridge this file up.

3.RCTBridge.m

RCTBridge.m , the initial call path is a bit long, and the full paste source code is a bit longer. In short, the last call is (void)setUp . The core code is as follows:

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

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

We can see, RCTBridge initialization and pointed RTCxxBridge .

4.RTCxxBridge.mm

RTCxxBridge can be said to be the core initialized by React Native. I checked some information and it seems that RTCxxBridge used to RCTBatchedBridge , so these two categories can be roughly regarded as the same thing.

Because the start RCTBridge is called in RTCxxBridge , let's start method to see what has been done.

// 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) {
      }
  ];
  
  // 6.等待 native moudle 和 JS 代码加载完毕后就执行 JS
  dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
    RCTCxxBridge *strongSelf = weakSelf;
    if (sourceCode && strongSelf.loading) {
      [strongSelf executeSourceCode:sourceCode sync:NO];
    }
  });
}

The above code is relatively long, and it uses GCD multi-thread . The process described in words is roughly as follows:

  1. Initialize js thread _jsThread
  2. native modules on the main thread
  3. Prepare the bridge and js runtime environment between js and Native
  4. Create a message queue RCTMessageThread on the JS thread, initialize _reactInstance
  5. Load the JS Bundle on the JS thread
  6. After all the above things are done, execute the JS code

In fact, the above six points can be dig deep, but the source code content involved in this section is enough. Interested readers can combine the reference materials I gave at the end and the React Native source code to dig deeper and explore.

Android source code analysis

1.MainActivity.java & MainApplication.java

Like iOS, we start with the entry file to analyze the startup process. Let’s look at MainActivity.java first:

MainActivity inherited from ReactActivity , ReactActivity inherited from AppCompatActivity :

// MainActivity.java

public class MainActivity extends ReactActivity {
  // 返回组件名,和 js 入口注册名字一致
  @Override
  protected String getMainComponentName() {
    return "rn_performance_demo";
  }
}

Let's start the analysis from the Android entry file MainApplication.java

// MainApplication.java

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost =
      new ReactNativeHost(this) {
        // 返回 app 需要的 ReactPackage,添加需要加载的模块,
        // 这个地方就是我们在项目中添加依赖包时需要添加第三方 package 的地方
        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          return packages;
        }

        // js bundle 入口文件,设置为 index.js
        @Override
        protected String getJSMainModuleName() {
          return "index";
        }
      };

  @Override
  public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    // SoLoader:加载C++底层库
    SoLoader.init(this, /* native exopackage */ false);
  }
}

ReactApplication interface is very simple, requiring us to create a ReactNativeHost object:

public interface ReactApplication {
  ReactNativeHost getReactNativeHost();
}

From the above analysis, we can see that everything points to the ReactNativeHost , let's take a look at it below.

2.ReactNativeHost.java

ReactNativeHost main job of ReactInstanceManager is to create 06076b623b584a:

public abstract class ReactNativeHost {
  protected ReactInstanceManager createReactInstanceManager() {
    ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_START);
    ReactInstanceManagerBuilder builder =
        ReactInstanceManager.builder()
            // 应用上下文
            .setApplication(mApplication)
            // JSMainModulePath 相当于应用首页的 js Bundle,可以传递 url 从服务器拉取 js Bundle
            // 当然这个只在 dev 模式下可以使用
            .setJSMainModulePath(getJSMainModuleName())
            // 是否开启 dev 模式
            .setUseDeveloperSupport(getUseDeveloperSupport())
            // 红盒的回调
            .setRedBoxHandler(getRedBoxHandler())
            .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
            .setUIImplementationProvider(getUIImplementationProvider())
            .setJSIModulesPackage(getJSIModulePackage())
            .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);

    // 添加 ReactPackage
    for (ReactPackage reactPackage : getPackages()) {
      builder.addPackage(reactPackage);
    }
    
    // 获取 js Bundle 的加载路径
    String jsBundleFile = getJSBundleFile();
    if (jsBundleFile != null) {
      builder.setJSBundleFile(jsBundleFile);
    } else {
      builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
    }
    ReactInstanceManager reactInstanceManager = builder.build();
    return reactInstanceManager;
  }
}

3.ReactActivityDelegate.java

Let's go back to ReactActivity . It doesn't do anything on its own. All functions are ReactActivityDelegate , so we directly look at ReactActivityDelegate is implemented:

public class ReactActivityDelegate {
  protected void onCreate(Bundle savedInstanceState) {
    String mainComponentName = getMainComponentName();
    mReactDelegate =
        new ReactDelegate(
            getPlainActivity(), getReactNativeHost(), mainComponentName, getLaunchOptions()) {
          @Override
          protected ReactRootView createRootView() {
            return ReactActivityDelegate.this.createRootView();
          }
        };
    if (mMainComponentName != null) {
      // 载入 app 页面
      loadApp(mainComponentName);
    }
  }
  
  protected void loadApp(String appKey) {
    mReactDelegate.loadApp(appKey);
    // Activity 的 setContentView() 方法
    getPlainActivity().setContentView(mReactDelegate.getReactRootView());
  }
}

onCreate() when they instantiates a ReactDelegate , we look at its implementation.

4.ReactDelegate.java

In ReactDelegate.java , I did not see it do two things:

  • Create ReactRootView as the root view
  • Call getReactNativeHost().getReactInstanceManager() start the RN application
public class ReactDelegate {
  public void loadApp(String appKey) {
    if (mReactRootView != null) {
      throw new IllegalStateException("Cannot loadApp while app is already running.");
    }
    // 创建 ReactRootView 作为根视图
    mReactRootView = createRootView();
    // 启动 RN 应用
    mReactRootView.startReactApplication(
        getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);
  }
}

Basic startup process The source code content involved in this section is enough. Interested readers can combine the reference materials I gave at the end and dig deeper and explore the React Native source code.

Optimization suggestion

For applications with React Native as the mainstay, the RN container must be initialized immediately after the APP is started. Basically, there is no optimization idea; but the Native-based hybrid development APP has tricks:

Since initialization takes the longest time, why not initialize in advance before we officially enter the React Native container?

This method is very common, because many H5 containers also do this. Before entering the WebView webpage formally, first make a WebView container pool, initialize the WebView in advance, and directly load the data rendering after entering the H5 container to achieve the effect that the webpage opens in seconds.

RN container pool looks very mysterious, in fact it is a Map , key is componentName (that is, AppRegistry.registerComponent(appName, Component) passed in appName ), value is an already instantiated RCTRootView/ReactRootView .

After the APP is started, find a trigger time to initialize in advance, read the container pool before entering the RN container, if there is a matching container, just use it directly, and reinitialize if there is no match.

To write two very simple cases, iOS can build an RN container pool as shown in the figure below:

@property (nonatomic, strong) NSMutableDictionary<NSString *, RCTRootView *> *rootViewRool;

// 容器池
-(NSMutableDictionary<NSString *, RCTRootView *> *)rootViewRool {
  if (!_rootViewRool) {
    _rootViewRool = @{}.mutableCopy;
  }
  
  return _rootViewRool;
}


// 缓存 RCTRootView
-(void)cacheRootView:(NSString *)componentName path:(NSString *)path props:(NSDictionary *)props bridge:(RCTBridge *)bridge {
  // 初始化
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:componentName
                                            initialProperties:props];
  // 实例化后要加载到屏幕的最下面,否则不能触发视图渲染
  [[UIApplication sharedApplication].keyWindow.rootViewController.view insertSubview:rootView atIndex:0];
  rootView.frame = [UIScreen mainScreen].bounds;
  
  // 把缓存好的 RCTRootView 放到容器池中
  NSString *key = [NSString stringWithFormat:@"%@_%@", componentName, path];
  self.rootViewRool[key] = rootView;
}


// 读取容器
-(RCTRootView *)getRootView:(NSString *)componentName path:(NSString *)path props:(NSDictionary *)props bridge:(RCTBridge *)bridge {
  NSString *key = [NSString stringWithFormat:@"%@_%@", componentName, path];
  RCTRootView *rootView = self.rootViewRool[key];
  if (rootView) {
    return rootView;
  }
  
  // 兜底逻辑
  return [[RCTRootView alloc] initWithBridge:bridge moduleName:componentName initialProperties:props];
}

Android builds the RN container pool as follows:

private HashMap<String, ReactRootView> rootViewPool = new HashMap<>();

// 创建容器
private ReactRootView createRootView(String componentName, String path, Bundle props, Context context) {
    ReactInstanceManager bridgeInstance = ((ReactApplication) application).getReactNativeHost().getReactInstanceManager();
    ReactRootView rootView = new ReactRootView(context);

    if(props == null) {
        props = new Bundle();
    }
    props.putString("path", path);

    rootView.startReactApplication(bridgeInstance, componentName, props);

    return rootView;
}

// 缓存容器
public void cahceRootView(String componentName, String path, Bundle props, Context context) {
    ReactRootView rootView = createRootView(componentName, path, props, context);
    String key = componentName + "_" + path;

    // 把缓存好的 RCTRootView 放到容器池中
    rootViewPool.put(key, rootView);
}

// 读取容器
public ReactRootView getRootView(String componentName, String path, Bundle props, Context context) {
    String key = componentName + "_" + path;
    ReactRootView rootView = rootViewPool.get(key);

    if (rootView != null) {
        rootView.setAppProperties(newProps);
        rootViewPool.remove(key);
        return rootView;
    }

    // 兜底逻辑
    return createRootView(componentName, path, props, context);
}

Of course, because RCTRootView/ReactRootView takes up a certain amount of memory each time, when to instantiate, instantiate several containers, the size limit of the pool, and when to clear the container, it is necessary to practice and explore in combination with the business.

3.Native Modules binding

iOS source code analysis

The Native Modules of iOS has 3 pieces of content, the _initializeModules function 06076b623b5b0c in the middle:

// RCTCxxBridge.mm

- (void)start {
  // 初始化 RCTBridge 时调用 initWithBundleURL_moduleProvider_launchOptions 中的 moduleProvider 返回的 native modules
  [self registerExtraModules];
  
  // 注册所有的自定义 Native Module
  (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
  
  // 初始化所有懒加载的 native module,只有用 Chrome debug 时才会调用
  [self registerExtraLazyModules];
}

Let's see what the _initializeModules function does:

// RCTCxxBridge.mm

- (NSArray<RCTModuleData *> *)_initializeModules:(NSArray<Class> *)modules
                               withDispatchGroup:(dispatch_group_t)dispatchGroup
                                lazilyDiscovered:(BOOL)lazilyDiscovered
{
    for (RCTModuleData *moduleData in _moduleDataByID) {
      if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
        // Modules that were pre-initialized should ideally be set up before
        // bridge init has finished, otherwise the caller may try to access the
        // module directly rather than via `[bridge moduleForClass:]`, which won't
        // trigger the lazy initialization process. If the module cannot safely be
        // set up on the current thread, it will instead be async dispatched
        // to the main thread to be set up in _prepareModulesWithDispatchGroup:.
        (void)[moduleData instance];
      }
    }
    _moduleSetupComplete = YES;
    [self _prepareModulesWithDispatchGroup:dispatchGroup];
}

According to _initializeModules and _prepareModulesWithDispatchGroup , it can be seen that iOS is in the process of loading JS Bundle (in JSThead thread ), and at the same time in main thread initializes all Native Modules.

Combined with the previous source code analysis, we can see that when the React Native iOS container is initialized, will initialize all Native Modules . If Native Modules more 06076b623b5b9d, it will affect the startup time of the Android RN container.

Android source code analysis

Regarding the registration of Native Modules, clues have been given MainApplication.java

// MainApplication.java

protected List<ReactPackage> getPackages() {
  @SuppressWarnings("UnnecessaryLocalVariable")
  List<ReactPackage> packages = new PackageList(this).getPackages();
  // Packages that cannot be autolinked yet can be added manually here, for example:
  // packages.add(new MyReactNativePackage());
  return packages;
}

Since React Native has enabled auto link after 0.60, the installed third-party Native Modules are all in PackageList , so we can get the modules of auto link getPackages()

Source, in the ReactInstanceManager.java this file, run createReactContext() create ReactContext , and there are step is to register nativeModules registry:

// ReactInstanceManager.java

private ReactApplicationContext createReactContext(
  JavaScriptExecutor jsExecutor, 
  JSBundleLoader jsBundleLoader) {
  
  // 注册 nativeModules 注册表
  NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
}

According to the function call, we trace to processPackages() , and use a for loop to add all the Native Modules in mPackages to the registry:

// ReactInstanceManager.java

private NativeModuleRegistry processPackages(
    ReactApplicationContext reactContext,
    List<ReactPackage> packages,
    boolean checkAndUpdatePackageMembership) {
  // 创建 JavaModule 注册表 Builder,用来创建 JavaModule 注册表,
  // JavaModule 注册表将所有的 JavaModule 注册到 CatalystInstance 中
  NativeModuleRegistryBuilder nativeModuleRegistryBuilder =
      new NativeModuleRegistryBuilder(reactContext, this);

  // 给 mPackages 加锁
  // mPackages 类型为 List<ReactPackage>,与 MainApplication.java 里的 packages 对应
  synchronized (mPackages) {
    for (ReactPackage reactPackage : packages) {
      try {
        // 循环处理我们在 Application 里注入的 ReactPackage,处理的过程就是把各自的 Module 添加到对应的注册表中
        processPackage(reactPackage, nativeModuleRegistryBuilder);
      } finally {
        Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      }
    }
  }

  NativeModuleRegistry nativeModuleRegistry;
  try {
    // 生成 Java Module 注册表
    nativeModuleRegistry = nativeModuleRegistryBuilder.build();
  } finally {
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
  }

  return nativeModuleRegistry;
}

Finally, call processPackage() for real registration:

// ReactInstanceManager.java

private void processPackage(
    ReactPackage reactPackage,
    NativeModuleRegistryBuilder nativeModuleRegistryBuilder
) {
  nativeModuleRegistryBuilder.processPackage(reactPackage);
}

It can be seen from the above process that when Android registers Native Modules , is registered with in full synchronization. If Native Modules more 06076b623b5cd8, it will affect the startup time of the Android RN container.

Optimization suggestion

To be honest, Native Modules are completely bound in the existing architecture and there is no solution. : Regardless of whether you use this Native Methods or not, initialize all of them when the container starts. In the new RN architecture, TurboModules will solve this problem (described in the next section of this article).

If you have to talk about optimization, there is actually another idea. Don’t you initialize it at all, can I reduce the number of Native Modules? There is a step in the new architecture called Lean Core , which is to streamline the React Native core and remove some functions/components from the main RN project (for example, the WebView component), hand it over to the community for maintenance, and download it separately when you want to use it. integrated.

The main advantages of this are as follows:

  • The core is more streamlined, and RN maintainers have more energy to maintain the main functions
  • Reduce the binding time of Native Modules and redundant JS loading time, reduce the package size, and be more friendly to the initialization performance (we upgraded the RN version to 0.62 and doubled the initialization speed, which is basically due to Lean Core)
  • Speed up the iteration speed, optimize the development experience, etc.

Now Lean Core has been basically completed. For more discussion, official issues forum . As long as we upgrade the React Native version simultaneously, we can enjoy the results of Lean Core

4. How the new RN architecture optimizes startup performance

The new React Native architecture has been bounced for nearly two years. Every time I ask about the progress, the official reply is "Don't rush, don't rush, do it, do it".

I personally looked forward to a whole year last year, but I didn't wait for anything, so when RN will be updated to version 1.0.0, I don't care anymore. Although the official RN has always been a pigeon, I have to say that there is something about their new architecture. I have basically read the articles and videos about the new architecture of RN on the market, so I still have an overall understanding of the new architecture. .

Because the new architecture has not been officially released, there must be some differences in the specific details. The specific implementation details still have to wait for the official React Native.

JSI

The full name of JSI is JavaScript Interface , a framework written in C++, and its function is that supports JS to directly call Native method instead of asynchronous communication through Bridge.

How does JS directly call Native? Let's take the simplest example. setTimeout document.getElementById on the browser, the Native Code is actually called directly on the JS side. We can verify it in the browser console:

For example, I executed a command:

let el = document.createElement('div')

The variable el holds not a JS object, but an object instantiated in C++. For the object held by el, we set the relevant properties:

el.setAttribute('width', 100)

At this time, it is actually JS synchronously calling setWidth C++ to change the width of this element.

The JSI in React Native's new architecture mainly plays this role. With JSI, we can use JS to directly obtain references to C++ objects ( Host Objects ), and then directly control the UI, directly call the Native Modules method, and save the bridge The overhead of asynchronous communication.

Let's take a small example to see how Java/OC uses JSI to expose synchronous call methods to JS.

#pragma once

#include <string>
#include <unordered_map>

#include <jsi/jsi.h>

// SampleJSIObject 继承自 HostObject,表示这个一个暴露给 JS 的对象
// 对于 JS 来说,JS 可以直接同步调用这个对象上的属性和方法
class JSI_EXPORT SampleJSIObject : public facebook::jsi::HostObject {

public: 

// 第一步
// 将 window.__SampleJSIObject 暴露给JavaScript
// 这是一个静态函数,一般在应用初始化时从 ObjC/Java 中调用
static void SampleJSIObject::install(jsi::Runtime &runtime) {
  runtime.global().setProperty(
      runtime,
      "__sampleJSIObject",
      jsi::Function::createFromHostFunction(
          runtime,
          jsi::PropNameID::forAscii(runtime, "__SampleJSIObject"),
          1,
          [binding](jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count) {
            // 返回调用 window.__SampleJSIObject 时得到的内容
            return std::make_shared<SampleJSIObject>();
          }));
}

// 类似于 getter,每次 JS 访问这个对象的时候,都要经过这个方法,作用类似于一个包装器
// 比如说我们调用 window.__sampleJSIObject.method1(),这个方法就会被调用
jsi::Value TurboModule::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) {
  // 调用方法名
  // 比如说调用 window.__sampleJSIObject.method1() 时,propNameUtf8 就是 method1
  std::string propNameUtf8 = propName.utf8(runtime);

  return jsi::Function::createFromHostFunction(
    runtime,
    propName,
    argCount,
    [](facebook::jsi::Runtime &rt, const facebook::jsi::Value &thisVal, const facebook::jsi::Value *args, size_t count) {
      if (propNameUtf8 == 'method1') {
        // 调用 method1 时,相关的函数处理逻辑
      }
    });
}
  
std::vector<PropNameID> getPropertyNames(Runtime& rt){
}
  
}

The above example is relatively short. If you want to learn more about JSI, you can read the "React Native JSI Challenge" or read the source code directly.

TurboModules

Through the previous source code analysis, we can know that in the existing architecture, will fully load the native modules when Native is initialized. With the iteration of the business, there will only be more and more native modules, and the time-consuming here will be more and more. long.

TurboModules can solve this problem all at once. In the new architecture, the native modules are lazily loaded , which means that the loading will be initialized only when you call the corresponding native modules. This solves the problem of long initial full loading.

The calling path of TurboModules is roughly like this:

  1. First use JSI to create a top-level "Native Modules Proxy", call it global.__turboModuleProxy
  2. To access a Native Modules, for example, to access SampleTurboModule , we first execute require('NativeSampleTurboModule')
  3. In the NativeSampleTurboModule.js file, we first call TurboModuleRegistry.getEnforcing() and then global.__turboModuleProxy("SampleTurboModule")
  4. When global.__turboModuleProxy is called, the Native method exposed by JSI in the first step is called. At this time, the C++ layer finds the implementation of ObjC/Java through the incoming string "SampleTurboModule", and finally returns a corresponding JSI object
  5. Now that we get the SampleTurboModule , we can use JavaScript to synchronously call the properties and methods on the JSI object

Through the above steps, we can see that with TurboModules, Native Modules will only be loaded when calls fully loads Native Modules when the React Native container is initialized; at the same time, we can use JSI to achieve Synchronous calls between JS and Native are less time-consuming and more efficient.

to sum up

This article from Native point of view, the analysis React Native from source existing infrastructure start the process, summed up the performance optimization point of several Native layer; and finally a brief introduction about React Native of new architecture . In the next article, I will explain how to start with JavaScript optimize the startup speed of React Native.


If you like my article, I hope to like it 👍 Collection 📁 Comment 💬 Three consecutive support, thank you, this is really important to me!

Welcome everyone to pay attention to my WeChat public account: Halogen Lab, currently focusing on front-end technology, and also doing some small research on graphics.

Original link 👉 ⚡️ React Native startup speed optimization-Native article (including source code analysis) : Updates are more timely, and the reading experience is better

reference

React Native Performance Optimization Guide

React Native Upgrade Guide (0.59 -> 0.62)

Chain React 2019 - Ram Narasimhan - Performance in React Native

React Native's new architecture - Glossary of terms

React Native JSI Challenge

RFC0002: Turbo Modules ™

ReactNative and iOS native communication principle analysis-initialization

React Native iOS source code analysis

ReactNative source code articles:

How to use the React Native preloading solution to solve the white screen problem



卤代烃
1.6k 声望5.9k 粉丝