React Native is one of the most popular frameworks on Android these days. I’m not going to comment on it because only people who have used it will have a deep understanding of it. But I have a personal habit of looking at the source code before using an open source library to understand, if not 100%, how it works, so I looked at the source code before using RN. Don’t see don’t know, a look startled, it is one of the most core part – Java and JS communication is really written is very subtle, the whole process to understand after I benefit from infinite.

Here an aside, Ali’s WEEX is also about to open source, my small partner around me has been put into the source code, when it is open source I will also go to see its source code, to understand the difference between it and RN in the end is what.

Without further ado, let’s take a closer look at RN’s communication mechanism.

preface

Before reading this article, you should make sure you have some background in RN development. If you have no background in RN, I suggest you check out this series of articles, which are very clear on how to use RN for development. Of course, if you are not satisfied with just [understanding], you can also go to the Internet to look up some information about RN, in fact, some things are very worth looking at, such as virtual DOM, diff mechanism and so on.

Communication mode

By communication, we mean the communication between Java and JS in RN, that is, how JSX code in the JS part is translated into real Views and events in a Java layer, and how the Java layer calls JS to find the views and events it needs.

To put it simply, the two ends of RN communicate with each other by a configuration table. The Java end and JS end hold the same table, and the communication is carried out by the correspondence of each item in this table.

Basically, just like the above picture, both ends hold the same Config, and there are some registered modules in the config. The communication between the two ends is realized by transmitting such “A”, “B” or “C”. The code for this config to RN is NativeModuleRegistry and JavaScriptModuleRegistry. If you can’t imagine it, I can give you an analogy. The communication between Java terminal and JS terminal is just like a Chinese and an American in a dialogue, and the Config, that is, the registry is just like two translators, with the translation of two languages can normal communication. So how did these two tables come up? Let’s look at the code for the answer.

First we know that when we use RN, our corresponding activity inherits from ReactActivity and overrides a method called getPackages.


   @Override
   protected ListReactPackage 
   getPackages
   () {

   return Arrays.ReactPackageasList(

   new MainReactPackage()
       );
       }Copy the code

And you can see that the MainReactPackage in there is RN and that helped us generate it.

public class MainReactPackage implements ReactPackage { @Override public ListNativeModule createNativeModules (ReactApplicationContext reactContext) { return Arrays.NativeModuleasList( new AppStateModule(reactContext), new AsyncStorageModule(reactContext), new CameraRollManager(reactContext), new ClipboardModule(reactContext), new DatePickerDialogModule(reactContext), new DialogModule(reactContext), new FrescoModule(reactContext), new ImageEditingManager(reactContext), new ImageStoreManager(reactContext), new IntentModule(reactContext), new LocationModule(reactContext), new NetworkingModule(reactContext), new NetInfoModule(reactContext), new StatusBarModule(reactContext), new TimePickerDialogModule(reactContext), new ToastModule(reactContext), new VibrationModule(reactContext), new WebSocketModule(reactContext) ); } @Override public ListClass? extends JavaScriptModule createJSModules() { return Collections.emptyList(); } @Override public ListViewManager createViewManagers (ReactApplicationContext reactContext) { return Arrays.ViewManagerasList( ARTRenderableViewManager.createARTGroupViewManager(), ARTRenderableViewManager.createARTShapeViewManager(), ARTRenderableViewManager.createARTTextViewManager(), new ARTSurfaceViewManager(), new ReactDialogPickerManager(), new ReactDrawerLayoutManager(), new ReactDropdownPickerManager(), new ReactHorizontalScrollViewManager(), new ReactImageManager(), new ReactModalHostManager(), new ReactProgressBarViewManager(), new ReactRawTextManager(), new ReactScrollViewManager(), new ReactSliderManager(), new ReactSwitchManager(), new FrescoBasedReactTextInlineImageViewManager(), new ReactTextInputManager(), new ReactTextViewManager(), new ReactToolbarManager(), new ReactViewManager(), new ReactViewPagerManager(), new ReactVirtualTextViewManager(), new ReactWebViewManager(), new RecyclerViewBackedScrollViewManager(), new SwipeRefreshLayoutManager()); }}Copy the code

You can see that there are some basic components and events defined, but I don’t want to go into details. If you want to customize some components or events, you have to write a package yourself. As for how to do this, you can see the series of articles I mentioned earlier. NativeModuleRegistry and JavaScriptModuleRegistry are generated from such a package as we’ll see below.

Since our activity inherits from ReactActivity, let’s take a look at what’s going on inside the ReactActivity. The first thing to look at, of course, is the onCreate function.

@Override protected void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getUseDeveloperSupport() Build.VERSION.SDK_INT = 23) { // Get permission to show redbox in dev builds. if (! Settings.canDrawOverlays( this)) { Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); startActivity(serviceIntent); FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE); Toast.makeText( this, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show(); } } mReactInstanceManager = createReactInstanceManager(); ReactRootView mReactRootView = createRootView(); mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(), getLaunchOptions()); setContentView(mReactRootView); }Copy the code

You can see that we’ve created a ReactInstanceManager, and let’s see how that works.

protected ReactInstanceManager createReactInstanceManager () { ReactInstanceManager.Builder builder = ReactInstanceManager.builder() .setApplication(getApplication()) .setJSMainModuleName(getJSMainModuleName()) .setUseDeveloperSupport(getUseDeveloperSupport()) .setInitialLifecycleState(mLifecycleState); for (ReactPackage reactPackage : getPackages()) { builder.addPackage(reactPackage); } String jsBundleFile = getJSBundleFile(); if (jsBundleFile ! = null) { builder.setJSBundleFile(jsBundleFile); } else { builder.setBundleAssetName(getBundleAssetName()); } return builder.build(); }Copy the code

There’s a code in the middle that looks like this


   for (ReactPackage reactPackage : getPackages()) {
     builder.addPackage(reactPackage);
     }Copy the code

Inject our package into the Builder using builder mode and finally create an instance of ReactInstanceManagerImpl by calling the Build method.

public ReactInstanceManager build () { ....... Return new ReactInstanceManagerImpl(Assertions. AssertNotNull (mApplication, Application property has not been set with this builder), mJSBundleFile, mJSMainModuleName, mPackages, mUseDeveloperSupport, mBridgeIdleDebugListener, Assertions.assertNotNull(mInitialLifecycleState, Initial lifecycle state was not set), mUIImplementationProvider, mNativeModuleCallExceptionHandler, mJSCConfig); }Copy the code

Where our package is passed as an argument to the ReactInstanceManagerImpl constructor.

Let’s go back to the onCreate function, and after that we’re going to create a ReactRootView as our root view, and we’re going to call its startReactApplication function, and as you can tell from the name of the function, it’s going to be very important, so from here, It kind of started our RN program.

public void startReactApplication ( ReactInstanceManager reactInstanceManager, String moduleName, @Nullable Bundle launchOptions) { UiThreadUtil.assertOnUiThread(); // TODO(6788889): Use POJO instead of bundle here, apparently we can't just use WritableMap // here as it may be deallocated in native after passing via JNI bridge, but we want to reuse // it in the case of re-creating the catalyst instance Assertions.assertCondition( mReactInstanceManager == null, This root view has already + been attached to a catalyst instance manager); mReactInstanceManager = reactInstanceManager; mJSModuleName = moduleName; mLaunchOptions = launchOptions; if (! mReactInstanceManager.hasStartedCreatingInitialContext()) { mReactInstanceManager.createReactContextInBackground(); } // We need to wait for the initial onMeasure, if this view has not yet been measured, we set // mAttachScheduled flag, which will make this view startReactApplication itself to instance // manager once onMeasure is called. if (mWasMeasured  mIsAttachedToWindow) { mReactInstanceManager.attachMeasuredRootView( this); mIsAttachedToInstance = true; getViewTreeObserver().addOnGlobalLayoutListener(getKeyboardListener()); } else { mAttachScheduled = true; }}Copy the code

Which we call the ReactInstanceManager createReactContextInBackground () method to construct belong to an RN program context.

@Override public void createReactContextInBackground () { Assertions.assertCondition( ! mHasStartedCreatingInitialContext, createReactContextInBackground should only be called when creating the react + application for the first time. When reloading JS, e.g. from a new file, explicitly + use recreateReactContextInBackground); mHasStartedCreatingInitialContext = true; recreateReactContextInBackgroundInner(); } private void recreateReactContextInBackgroundInner () { UiThreadUtil.assertOnUiThread(); if (mUseDeveloperSupport mJSMainModuleName ! = null) { if (mDevSupportManager.hasUpToDateJSBundleInCache()) { // If there is a up-to-date bundle downloaded from server, always use that onJSBundleLoadedFromServer(); } else if (mJSBundleFile == null) { mDevSupportManager.handleReloadJS(); } else { mDevSupportManager.isPackagerRunning( new DevServerHelper.PackagerStatusCallback() { @Override public void onPackagerStatusFetched ( final boolean packagerIsRunning) { UiThreadUtil.runOnUiThread( new Runnable() { @Override public void run () { if (packagerIsRunning) { mDevSupportManager.handleReloadJS(); } else { recreateReactContextInBackgroundFromBundleFile(); }}}); }}); } return; } recreateReactContextInBackgroundFromBundleFile(); }Copy the code

The logic is if you are a development mode, is called onJSBundleLoadedFromServer () this function. Otherwise go directly recreateReactContextInBackgroundFromBundleFile (). We’ll focus on the former here, but there’s not much difference between the two. The main difference is how jsBundle is fetched.

private void onJSBundleLoadedFromServer () { recreateReactContextInBackground( new JSCJavaScriptExecutor.Factory(), JSBundleLoader.createCachedBundleFromNetworkLoader( mDevSupportManager.getSourceUrl(), mDevSupportManager.getDownloadedJSBundleFile())); } private void recreateReactContextInBackground ( JavaScriptExecutor.Factory jsExecutorFactory, JSBundleLoader jsBundleLoader) { UiThreadUtil.assertOnUiThread(); ReactContextInitParams initParams = new ReactContextInitParams(jsExecutorFactory, jsBundleLoader); if (mReactContextInitAsyncTask == null) { // No background task to create react context is currently running, create and execute one. mReactContextInitAsyncTask = new ReactContextInitAsyncTask(); mReactContextInitAsyncTask.execute(initParams); } else { // Background task is currently running, queue up most recent init params to recreate context // once task completes. mPendingReactContextInitParams = initParams; }}Copy the code

We call JSBundleLoader createCachedBundleFromNetworkLoader method in onJSBundleLoadedFromServer method to create a JSBundleLoader.


   public 
   static JSBundleLoader 
   createCachedBundleFromNetworkLoader
   (

   final String sourceURL,

   final String cachedFileLocation) {

   return 
   new JSBundleLoader() {

   @Override

   public 
   void 
   loadScript
   (ReactBridge bridge) {
         bridge.loadScriptFromFile(cachedFileLocation, sourceURL);
       }


       @Override

   public String 
   getSourceUrl
   () {

   return sourceURL;
       }
     };
     }Copy the code

Its main purpose is to load the jsBundle. If you look at the JSBundleLoader class, there are two other ways to create a loader, and if we’re going to release it, we’ll call createFileLoader, In the release case we need to generate a jsBundler using Gradle and then place it in assets or a file.

Let’s see after recreateReactContextInBackground method.

It is called a called mReactContextInitAsyncTask AsyncTask asynchronous tasks to perform.

@Override protected ResultReactApplicationContext doInBackground (ReactContextInitParams... params) { Assertions.assertCondition(params ! = null params.length 0 params[ 0] ! = null); try { JavaScriptExecutor jsExecutor = params[ 0].getJsExecutorFactory().create( mJSCConfig == null ? new WritableNativeMap() : mJSCConfig.getConfigMap()); return Result.of(createReactContext(jsExecutor, params[ 0].getJsBundleLoader())); } catch (Exception e) { // Pass exception to onPostExecute() so it can be handled on the main thread return Result.of(e); }}Copy the code

We can see that its doInBackground method calls the createReactContext() method to create the context.

private ReactApplicationContext createReactContext ( JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) { FLog.i(ReactConstants.TAG, Creating react context.); ReactMarker.logMarker(CREATE_REACT_CONTEXT_START); mSourceUrl = jsBundleLoader.getSourceUrl(); NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder(); JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder(); ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext); if (mUseDeveloperSupport) { reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager); } ReactMarker.logMarker(PROCESS_PACKAGES_START); Systrace.beginSection( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, createAndProcessCoreModulesPackage); try { CoreModulesPackage coreModulesPackage = new CoreModulesPackage( this, mBackBtnHandler, mUIImplementationProvider);  processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } // TODO(6818138): Solve use-case of native/js modules overriding for (ReactPackage reactPackage : mPackages) { Systrace.beginSection( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, createAndProcessCustomReactPackage); try { processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } } ReactMarker.logMarker(PROCESS_PACKAGES_END); ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_START); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, buildNativeModuleRegistry); NativeModuleRegistry nativeModuleRegistry; try { nativeModuleRegistry = nativeRegistryBuilder.build(); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END); } ReactMarker.logMarker(BUILD_JS_MODULE_CONFIG_START); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, buildJSModuleConfig); JavaScriptModulesConfig javaScriptModulesConfig; try { javaScriptModulesConfig = jsModulesBuilder.build(); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); ReactMarker.logMarker(BUILD_JS_MODULE_CONFIG_END); } NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler ! = null ? mNativeModuleCallExceptionHandler : mDevSupportManager; CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder() .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault()) .setJSExecutor(jsExecutor) .setRegistry(nativeModuleRegistry) .setJSModulesConfig(javaScriptModulesConfig) .setJSBundleLoader(jsBundleLoader) .setNativeModuleCallExceptionHandler(exceptionHandler); ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START); // CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, createCatalystInstance); CatalystInstance catalystInstance; try { catalystInstance = catalystInstanceBuilder.build(); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END); } if (mBridgeIdleDebugListener ! = null) { catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener); } reactContext.initializeWithInstance(catalystInstance); ReactMarker.logMarker(RUN_JS_BUNDLE_START); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, runJSBundle); try { catalystInstance.runJSBundle(); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); ReactMarker.logMarker(RUN_JS_BUNDLE_END); } return reactContext; }Copy the code

This method has more code, but let’s just focus on what we care about for now. You remember our focus, right? How two registries NativeModuleRegistry and JavaScriptModuleRegistry are generated. Here’s the answer.


   NativeModuleRegistry.Builder nativeRegistryBuilder = 
   new NativeModuleRegistry.Builder();
   JavaScriptModulesConfig.Builder jsModulesBuilder = 
   new JavaScriptModulesConfig.Builder();Copy the code

Start by creating two Builders.

try { CoreModulesPackage coreModulesPackage = new CoreModulesPackage( this, mBackBtnHandler, mUIImplementationProvider);  processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); }Copy the code

And then go back and new a CoreModulesPackage and use the processPackage method to process it, what’s that?

/* package */ class CoreModulesPackage implements ReactPackage { private final ReactInstanceManager mReactInstanceManager; private final DefaultHardwareBackBtnHandler mHardwareBackBtnHandler; private final UIImplementationProvider mUIImplementationProvider; CoreModulesPackage( ReactInstanceManager reactInstanceManager, DefaultHardwareBackBtnHandler hardwareBackBtnHandler, UIImplementationProvider uiImplementationProvider) { mReactInstanceManager = reactInstanceManager; mHardwareBackBtnHandler = hardwareBackBtnHandler; mUIImplementationProvider = uiImplementationProvider; } @Override public ListNativeModule createNativeModules ( ReactApplicationContext catalystApplicationContext) { Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, createUIManagerModule); UIManagerModule uiManagerModule; try { ListViewManager viewManagersList = mReactInstanceManager.createAllViewManagers( catalystApplicationContext); uiManagerModule = new UIManagerModule( catalystApplicationContext, viewManagersList, mUIImplementationProvider.createUIImplementation( catalystApplicationContext, viewManagersList)); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } return Arrays.NativeModuleasList( new AnimationsDebugModule( catalystApplicationContext, mReactInstanceManager.getDevSupportManager().getDevSettings()), new AndroidInfoModule(), new DeviceEventManagerModule(catalystApplicationContext, mHardwareBackBtnHandler), new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager()), new Timing(catalystApplicationContext), new SourceCodeModule( mReactInstanceManager.getSourceUrl(), mReactInstanceManager.getDevSupportManager().getSourceMapUrl()), uiManagerModule, new DebugComponentOwnershipModule(catalystApplicationContext)); } @Override public ListClass? extends JavaScriptModule createJSModules() { return Arrays.asList( DeviceEventManagerModule.RCTDeviceEventEmitter.class,  JSTimersExecution.class, RCTEventEmitter.class, RCTNativeAppEventEmitter.class, AppRegistry.class, com.facebook.react.bridge.Systrace.class, HMRClient.class, DebugComponentOwnershipModule.RCTDebugComponentOwnership.class); } @Override public ListViewManager createViewManagers (ReactApplicationContext reactContext) { return new ArrayList( 0);  }}Copy the code

As you can see, there are plenty of JSModules and NativeModules, and these modules are the core modules required to run RN programs. Let’s look at the processPackage method.

private void processPackage ( ReactPackage reactPackage, ReactApplicationContext reactContext, NativeModuleRegistry.Builder nativeRegistryBuilder, JavaScriptModulesConfig.Builder jsModulesBuilder) { for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) { nativeRegistryBuilder.add(nativeModule); } for (Class? extends JavaScriptModule jsModuleClass : reactPackage.createJSModules()) { jsModulesBuilder.add(jsModuleClass); }}Copy the code

Simply take the specific Native and JS modules and add them to the corresponding Builder.

After processing the CoreModulesPackage, the program will process the packages we injected into the activity.

for (ReactPackage reactPackage : mPackages) { Systrace.beginSection( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, createAndProcessCustomReactPackage); try { processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); }}Copy the code

The next step is to generate the registry.


   try {
      nativeModuleRegistry = nativeRegistryBuilder.build();
      } 
      finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
      ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
      }

      try {
      javaScriptModulesConfig = jsModulesBuilder.build();
      } 
      finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
      ReactMarker.logMarker(BUILD_JS_MODULE_CONFIG_END);
      }Copy the code

At this point, we have written all packages, including the CoreModulesPackage of RN core and modules from our activity’s own injected package, into the builder corresponding to Registry.

Now these two registries exist on the Java side, so how to transfer to the JS side? Let’s keep going.

After the registry is created, ReactInstanceManagerImpl creates an instance of CatalystInstanceImpl using Builder mode.


   CatalystInstance catalystInstance;
   try {
     catalystInstance = catalystInstanceBuilder.build();
     } 
     finally {
     Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
     ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
     }Copy the code

Let’s take a look at what the CatalystInstanceImpl constructor does.

private final ReactBridge mBridge; private CatalystInstanceImpl ( final ReactQueueConfigurationSpec ReactQueueConfigurationSpec, final JavaScriptExecutor jsExecutor, final NativeModuleRegistry registry, final JavaScriptModulesConfig jsModulesConfig, final JSBundleLoader jsBundleLoader, NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) { FLog.d(ReactConstants.TAG, Initializing React Bridge.); mReactQueueConfiguration = ReactQueueConfigurationImpl.create( ReactQueueConfigurationSpec, new NativeExceptionHandler()); mBridgeIdleListeners = new CopyOnWriteArrayList(); mJavaRegistry = registry; mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl. this, jsModulesConfig); mJSBundleLoader = jsBundleLoader; mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler; mTraceListener = new JSProfilerTraceListener(); try { mBridge = mReactQueueConfiguration.getJSQueueThread().callOnQueue( new CallableReactBridge() { @Override public ReactBridge call () throws Exception { Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, initializeBridge); try { return initializeBridge(jsExecutor, jsModulesConfig); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } } }).get(); } catch (Exception t) { throw new RuntimeException( Failed to initialize bridge, t); }}Copy the code

It’s a lot of code, but all you need to know is that it creates a ReactBridge. InitializeBridge (jsExecutor, jsModulesConfig) is called; This method.


   private ReactBridge 
   initializeBridge
   (
       JavaScriptExecutor jsExecutor,
       JavaScriptModulesConfig jsModulesConfig) {
     mReactQueueConfiguration.getJSQueueThread().assertIsOnThread();
     Assertions.assertCondition(mBridge == 
     null, 
     initializeBridge should be called once);

     Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, 
     ReactBridgeCtor);
     ReactBridge bridge;

     try {
       bridge = 
       new ReactBridge(
           jsExecutor,

           new NativeModulesReactCallback(),
           mReactQueueConfiguration.getNativeModulesQueueThread());
       mMainExecutorToken = bridge.getMainExecutorToken();
     } 
     finally {
       Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
     }

     Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, 
     setBatchedBridgeConfig);

     try {
       bridge.setGlobalVariable(

       __fbBatchedBridgeConfig,
           buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
       bridge.setGlobalVariable(

       __RCTProfileIsProfiling,
           Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ? 
           true : 
           false);
     } 
     finally {
       Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
     }

     mJavaRegistry.notifyReactBridgeInitialized(bridge);

     return bridge;
     }Copy the code

There was this passage:


   bridge.setGlobalVariable(

   __fbBatchedBridgeConfig,
       buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));Copy the code

Call bridge’s setGlobalVariable method, which is a native method.


   public 
   native 
   void 
   setGlobalVariable
   (String propertyName, String jsonEncodedArgument);Copy the code

This method formats the JavaRegistry into JSON and calls the C-layer code to pass to the JS side. Due to my C layer of learning skills is not fine, can understand a probably, afraid of details will be wrong, do not take you to analyze.

conclusion

Here, the truth comes out, let’s sum up. At startup, in the onCreate function of the ReactActivity, we’re going to create a ReactInstanceManagerImpl object, And open the entire RN world through ReactRootView’s Star Act Application method. In this method, we will create the ReactContext via an AsyncTask. So in creating the ReactContext, We inject modules from our own (MainReactPackage) and system-generated (CoreModulesPackage) packages into the corresponding Registry using the processPackage method. Finally, the Java registry is transferred to the js layer via jni via ReactBridge in CatalystInstanceImpl. In this way, the JS layer gets all the interfaces and methods of the Java layer, equivalent to an American having a Chinese translation at his side. The JS layer registry is originally generated by the Java layer, so it is equivalent to a Chinese side with an English translation, from then on they can communicate happily.

Java – js

We talked about how the two sides communicate and how the registry is sent from the Java side to the JS side. Now let’s talk about how Java calls JS methods once this preparation is complete.

The JavaRegistry transfer from Java to JS is a process in which Java communicates with JS. This process was described in the last section. Before everything is done in in ReactInstanceManagerImpl ReactContextInitAsyncTask doInBackground method, this method completes and did what?


   @Override
   protected 
   void 
   onPostExecute
   (ResultReactApplicationContext result) {

   try {
       setupReactContext(result.get());
     } 
     catch (Exception e) {
       mDevSupportManager.handleException(e);
     } 
     finally {
       mReactContextInitAsyncTask = 
       null;
     }


     // Handle enqueued request to re-initialize react context.

   if (mPendingReactContextInitParams != 
   null) {
       recreateReactContextInBackground(
           mPendingReactContextInitParams.getJsExecutorFactory(),
           mPendingReactContextInitParams.getJsBundleLoader());
       mPendingReactContextInitParams = 
       null;
     }
     }Copy the code

So you can see that in the onPstEcexute method we’re calling setupReactContext and passing in the Context that we created.

private void setupReactContext (ReactApplicationContext reactContext) { UiThreadUtil.assertOnUiThread(); Assertions.assertCondition(mCurrentReactContext == null); mCurrentReactContext = Assertions.assertNotNull(reactContext); CatalystInstance catalystInstance = Assertions.assertNotNull(reactContext.getCatalystInstance()); catalystInstance.initialize(); mDevSupportManager.onNewReactContextCreated(reactContext); mMemoryPressureRouter.addMemoryPressureListener(catalystInstance); moveReactContextToCurrentLifecycleState(); for (ReactRootView rootView : mAttachedRootViews) { attachMeasuredRootViewToInstance(rootView, catalystInstance); } ReactInstanceEventListener[] listeners = new ReactInstanceEventListener[mReactInstanceEventListeners.size()]; listeners = mReactInstanceEventListeners.toArray(listeners); for (ReactInstanceEventListener listener : listeners) { listener.onReactContextInitialized(reactContext); }}Copy the code

In this method, there is an important statement:


   attachMeasuredRootViewToInstance(rootView, catalystInstance);Copy the code

See what it did

private void attachMeasuredRootViewToInstance ( ReactRootView rootView, CatalystInstance catalystInstance) { UiThreadUtil.assertOnUiThread(); // Reset view content as it's going to be populated by the application content from JS rootView.removeAllViews(); rootView.setId(View.NO_ID); UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class); int rootTag = uiManagerModule.addMeasuredRootView(rootView); @Nullable Bundle launchOptions = rootView.getLaunchOptions(); WritableMap initialProps = launchOptions ! = null ? Arguments.fromBundle(launchOptions) : Arguments.createMap(); String jsAppModuleName = rootView.getJSModuleName(); WritableNativeMap appParams = new WritableNativeMap(); appParams.putDouble( rootTag, rootTag); appParams.putMap( initialProps, initialProps); catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams); }Copy the code

The method ends up using our getJSModule method of CatalystInstanceImpl to get the specific JSModule and call its methods. Let’s take a look at what getJSModule does.


   @Override
   public T extends JavaScriptModule 
   T 
   getJSModule
   (ClassT jsInterface) {

   return getJSModule(Assertions.assertNotNull(mMainExecutorToken), jsInterface);
   }

   @Override
   public T extends JavaScriptModule 
   T 
   getJSModule
   (ExecutorToken executorToken, ClassT jsInterface) {

   return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(executorToken, jsInterface);
   }Copy the code

It goes to JavaScriptModuleRegistry to get the corresponding JavaScriptModule, that is, from the registry.

public synchronized T extends JavaScriptModule T getJavaScriptModule (ExecutorToken executorToken, ClassT moduleInterface) { HashMapClass? extends JavaScriptModule, JavaScriptModule instancesForContext = mModuleInstances.get(executorToken); if (instancesForContext == null) { instancesForContext = new HashMap(); mModuleInstances.put(executorToken, instancesForContext); } JavaScriptModule module = instancesForContext.get(moduleInterface); if (module ! = null) { return (T) module; } JavaScriptModuleRegistration registration = Assertions.assertNotNull( mModuleRegistrations.get(moduleInterface), JS module + moduleInterface.getSimpleName() + hasn't been registered!) ; JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance( moduleInterface.getClassLoader(), new Class[]{moduleInterface}, new JavaScriptModuleInvocationHandler(executorToken, mCatalystInstance, registration)); instancesForContext.put(moduleInterface, interfaceProxy); return (T) interfaceProxy; }Copy the code

This method is quite magical, first go to the cache to find, if found return, do not find to create, how to create, with a dynamic proxy!


   JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
       moduleInterface.getClassLoader(),

       new Class[]{moduleInterface},

       new JavaScriptModuleInvocationHandler(executorToken, mCatalystInstance, registration));Copy the code

Here you must have some knowledge of dynamic proxy, you can find the relevant knowledge. Let’s take a look at JavaScriptModuleInvocationHandler.

@Override public @Nullable Object invoke (Object proxy, Method method, Object[] args) throws Throwable { ExecutorToken executorToken = mExecutorToken.get(); if (executorToken == null) { FLog.w(ReactConstants.TAG, Dropping JS call, ExecutorToken went away...) ; return null; } String tracingName = mModuleRegistration.getTracingName(method); mCatalystInstance.callFunction( executorToken, mModuleRegistration.getModuleId(), mModuleRegistration.getMethodId(method), Arguments.fromJavaArgs(args), tracingName); return null; }Copy the code

Let’s look at its core invoke method. Getmoduleid, methodId, and parameters for the method called, and then call CatalystInstanceImpl callFunction to execute.

@Override public void callFunction ( ExecutorToken executorToken, int moduleId, int methodId, NativeArray arguments, String tracingName) { synchronized (mJavaToJSCallsTeardownLock) { if (mDestroyed) { FLog.w(ReactConstants.TAG, Calling JS function after bridge has been destroyed.); return; } incrementPendingJSCalls(); Assertions.assertNotNull(mBridge).callFunction(executorToken, moduleId, methodId, arguments, tracingName); }}Copy the code

The ReactBridge function of the same name is called directly.


   public 
   native 
   void 
   callFunction
   (ExecutorToken executorToken, 
   int moduleId, 
   int methodId, NativeArray arguments, String tracingName);Copy the code

ModuleId, methodId and arguments corresponding to the method you want to call are passed to the JS side via JNI for call. On the other hand, we call AppRegistry’s runApplication method, which on the JS side starts running the entire JS program, thus making our RN program actually run.

conclusion

First of all, for our Java side to call the JS side of the class and method, we must register in the JS registry, this process has been covered in the last part of the analysis. When the call is actually made, the getJSModule method of the CatalystInstanceImpl class is used to get the corresponding JSModule, and then the dynamic proxy is used to get the parameters of the method, which are passed through the ReactBridge to the C layer and then to the JS layer.

It should make a lot of sense with this diagram and the code.

Js – Java

The process of RN js calling Java is the most novel thing for me. Specifically, JS does not call Java methods directly through the registration interface, but pushes the corresponding parameters (moduleId and methodId) into a messageQueue and waits for events from the Java layer to drive it. When the Java layer event is passed in, the JS layer returns all the data in messageQueue to the Java layer and invokes the method via the registry JavaRegistry.

First, we said that the JS layer pushes the corresponding parameters into messageQueue. The specific method is messagequeue. js __nativeCall method.


   __nativeCall(
   module, method, params, onFail, onSucc) {
   if (onFail || onSucc) {

   // eventually delete old debug info
     (
     this._callbackID  (
     1  
     5))
       (
       this._debugInfo[
       this._callbackID  
   5] = 
   null);


   this._debugInfo[
   this._callbackID  
   1] = [
   module, method];
     onFail params.push(
     this._callbackID);

     this._callbacks[
     this._callbackID++] = onFail;
     onSucc params.push(
     this._callbackID);

     this._callbacks[
     this._callbackID++] = onSucc;
   }

   global.nativeTraceBeginAsyncFlow
     global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, 
     'native', 
     this._callID);
     this._callID++;

   this._queue[MODULE_IDS].push(
   module);
   this._queue[METHOD_IDS].push(method);
   this._queue[PARAMS].push(params);

   var now = 
   new 
   Date().getTime();
   if (global.nativeFlushQueueImmediate
       now - 
       this._lastFlush = MIN_TIME_BETWEEN_FLUSHES_MS) {
     global.nativeFlushQueueImmediate(
     this._queue);

     this._queue = [[], [], [], 
     this._callID];

   this._lastFlush = now;
   }
   Systrace.counterEvent(
   'pending_js_to_native_queue', 
   this._queue[
   0].length);
   if (__DEV__ SPY_MODE 
   isFinite(
   module)) {

   console.log(
   'JS-N : ' + 
   this._remoteModuleTable[
   module] + 
   '.' +

   this._remoteMethodTable[
   module][method] + 
   '(' + 
   JSON.stringify(params) + 
   ')');
     }
     }Copy the code

Among them


   this._queue[MODULE_IDS].push(
   module);
   this._queue[METHOD_IDS].push(method);
   this._queue[PARAMS].push(params);Copy the code

The corresponding Module, Method, and Params are pushed into the queue and wait for the Java layer event driver.

Java layer event-driven can also be seen as Java layer to JS layer communication, Will eventually go the MessageQueue. Js callFunctionReturnFlushedQueue method and invokeCallbackAndReturnFlushedQueue method.


   callFunctionReturnFlushedQueue(
   module, method, args) {
   guard(() = {

   this.__callFunction(
   module, method, args);

   this.__callImmediates();
   });

   return 
   this.flushedQueue();
   }Copy the code

The flushedQueue is called to send all the data in MessageQueue through the C layer to the Java layer.

The Java layer after the callback to NativeModulesReactCallback class execution call method.

private class NativeModulesReactCallback implements ReactCallback { @Override public void call (ExecutorToken executorToken, int moduleId, int methodId, ReadableNativeArray parameters) { mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread(); synchronized (mJSToJavaCallsTeardownLock) { // Suppress any callbacks if destroyed - will only lead to sadness. if (mDestroyed) { return; } mJavaRegistry.call(CatalystInstanceImpl. this, executorToken, moduleId, methodId, parameters); }}... }Copy the code

You can see that JavaRegistry calls its call method.


   /* package */ 
   void 
   call
   (
       CatalystInstance catalystInstance,
       ExecutorToken executorToken,

       int moduleId,

       int methodId,
       ReadableNativeArray parameters) {
     ModuleDefinition definition = mModuleTable.get(moduleId);

     if (definition == 
     null) {

   throw 
   new RuntimeException(
   Call to unknown module:  + moduleId);
     }
     definition.call(catalystInstance, executorToken, methodId, parameters);
     }Copy the code

Get the corresponding Module and call the call method.

public void call ( CatalystInstance catalystInstance, ExecutorToken executorToken, int methodId, ReadableNativeArray parameters) { MethodRegistration method = this.methods.get(methodId); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, method.tracingName); try { this.methods.get(methodId).method.invoke(catalystInstance, executorToken, parameters); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); }}}Copy the code

Invoke (methodId); invoke (methodId);

The result is the BaseJavaModule invoke method.

@Override public void invoke (CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray parameters) { ......... mMethod.invoke(BaseJavaModule. this, mArguments); . }Copy the code

Call the final method through reflection.

conclusion

What we should focus on here is that the communication between JS and Java relies on the event-driven mode, that is, JS pushes the corresponding parameters of the method into MessageQueue, and then transfers all the data to The Java layer through THE C layer after the event is delivered from the Java side. We then fetch the corresponding Module and Method through the Java registry JavaRegistry, and execute the method through reflection.

The old rule: use diagrams and code to understand the flow.

The role of important classes

Let’s conclude by summarizing the roles of several important classes mentioned earlier.

ReactInstanceManager: It creates ReactContext, CatalystInstance and other classes, parses the package to generate the registry, and works with ReactRootView to manage View creation, lifecycle and other functions.

ReactRootView: The root view of an RN program. The startReactApplication method opens the world of RN.

CatalystInstance: Java-side communication management class, provides the communication environment, methods, and callback methods, internal communication through the ReactBridge.

ReactBridge: A core class for communication via JNI.

NativeModuleRegistry: Registry of a Java interface.

JavascriptModuleRegistry: Registry of THE JS interface.

CoreModulePackage: THE PACKAGE of RN’s core framework, including the Java and JS interfaces, and CLICK and click is registered here.

MainReactPackage: Some generic Java components and events that RN helps us encapsulate.

JsBundleLoader: Class used to load jsbundles, where different loaders are created depending on the application.

JSBundle: Contains the JS core logic, which is created in the release environment by using the Gradle task and placed in the corresponding directory.