從java層看react-naive通信機制

-2,前言

最近我們公司突然開始涉足react-native開發,老實說,我內心是拒絕的。其一,是因為目前我對于原生開發還不夠精通,不想突然轉向,其二是因為react-native目前還沒有1.0的正式版本,仍然處于探索期不穩定。其三,我特么開發react-native用的是windows啊,人家facebook的工程師開發這玩兒用的都是mac。看在工資的份兒上,我開始探索react-native。

-1,解釋說明

  • 說實話,一開始就上這么難的話題,我也是沒辦法。關于環境集成,跑demo等等遇到的這些坑,雖然積攢了一點經驗,但是很多時候,我是懵逼的,所以總結這些經驗而不說出問題的根源,不去探究為什么,這不是我的風格。而通信機制則是一整套嚴肅的設計,知根知底,而且隨著對于原理的深究,以后對于問題的根源就會更加明了(不過windows環境下還是不要立這種flag比較好)。
  • 其次,作為react-native的菜鳥,我駕馭這個問題是有一些難度的,但是,這也正好是另一種優勢,那就是,我能以一個react-native的菜鳥的角度,去解釋Java與js的通信機制,應該是對于剛接觸react-native的原生開發的工程師更加友好。
  • 最后,我在探究這個問題的過程中,找到了很多很棒的博文,比如其實沒那么復雜!探究react-native通信機制等,對于這些前輩,我只有獻上自己的膝蓋。 我目前用的RN的最新版本,而他們的版本應該是有點老,因此代碼其實跟上面的作者分析有出入。

0,鋪墊

首先我們要明白以下幾點:

  • 在react-native中的通信,主要是Java與JavaScript之間的通信,而實際上,Java與Js之間是根本沒辦法直接對話的,別看他們看起來好像是親戚,實際上他們的關系就相當于雷鋒和雷峰塔的關系
  • 那么Java和Js之間想要能聽懂對方的話,有兩個必備條件:
    • 雙方的信息要能夠傳達到對方那里去,就是,先不管聽不聽的懂 ,你首先要把話傳過去
    • 信息傳達前需要經過翻譯,才能被接受方正確理解。
  • 第一個條件的解決方案是通過C++來做這個傳話筒,Java通過JNI來call到c++層,然后c++層再把信息傳到js,反之亦然;第二個條件的解決方案就是通過在初始化的時候構造兩本“詞典”,約定好以后說話只說對方的“詞典”上的單詞。

所以我們的問題其實只有兩點:那就是集中精力觀察“詞典”是怎么傳遞到雙方手里的,以及兩方是怎么傳遞數據的

1,開篇

1.1,Java傳遞“詞典”

首先,對于詞典還是正確解釋一下,它是某種config,某種配置文件,每次Java層收到js層傳來的的信息,都會讀取這個文件,然后才能理解Java層的意思。Java層也是一樣。他們對應RN的代碼的類分別是:NativeModuleRegistry和JavaScriptModuleRegistry

初始化的開端源自ReactActivity,這是react-native中的類,它的onCreate()方法中是這么做的:

  @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();
      }
    }

    mReactRootView = createRootView();
    //這是最重要的一步
    mReactRootView.startReactApplication(
      getReactNativeHost().getReactInstanceManager(),
      getMainComponentName(),
      getLaunchOptions());
    setContentView(mReactRootView);
    mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
  }

mRootView是一個layout,繼承自FrameLayout,一切的js渲染從這個Layout上開始,它的startReactApplication()方法如下:

  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 which
    // will make this view startReactApplication itself to instance manager once onMeasure is called.
    if (mWasMeasured) {
      attachToReactInstanceManager();
    }
  }

這里有一個ReactInstanceManager,它的作用就是管理CatalystInstance的實例,CatalystInstance是什么?這是一個上層抽象的調用接口),Java和Js都可以通過這個去調用對方,當然,那兩個類都是抽象的,實際上都是通過它們的XXXXImpl類來實現具體的功能。

那么我們接著往下,注意我們的目的:了解初始化時如何傳遞那兩本“詞典”的,mReactInstanceManager.createReactContextInBackground();這個方法就直接調用到了它的實現類:XReactInstanceManagerImpl中的createReactContextInBackground然后接下來的流程就是:

createReactContextInBackground()------> recreateReactContextInBackgroundInner(); -------> recreateReactContextInBackgroundFromBundleLoader(); ---------> recreateReactContextInBackground() ;

到了recreateReactContextInBackground()這個方法大概是這樣的:


  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.
      //執行了一個AsyncTask.......
      mReactContextInitAsyncTask = new ReactContextInitAsyncTask();
      mReactContextInitAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, initParams);
    } else {
      // Background task is currently running, queue up most recent init params to recreate context
      // once task completes.
      mPendingReactContextInitParams = initParams;
    }
  }
  

好我們接下來看這個線程內部的細節,重點看doInBackground()這個方法:

 @Override
    protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) {
      // TODO(t11687218): Look over all threading
      // Default priority is Process.THREAD_PRIORITY_BACKGROUND which means we'll be put in a cgroup
      // that only has access to a small fraction of CPU time. The priority will be reset after
      // this task finishes: https://android.googlesource.com/platform/frameworks/base/+/
      d630f105e8bc0021541aacb4dc6498a49048ecea/core/java/android/os/AsyncTask.java#256
      Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);

      Assertions.assertCondition(params != null && params.length > 0 && params[0] != null);
      try {
        JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create();
        
        //createReactContext()這個方法被執行
        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);
      }
    }

接下去看createReactContext(jsExecutor, params[0].getJsBundleLoader())這個方法:

/**
   * @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
   */
  private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader) {
    FLog.i(ReactConstants.TAG, "Creating react context.");
    ReactMarker.logMarker(CREATE_REACT_CONTEXT_START);
    mSourceUrl = jsBundleLoader.getSourceUrl();
    
    //你瞧,之前提到的兩本“詞典”,他們的Builder已經露面了。
    NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
    JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder();

    final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
    if (mUseDeveloperSupport) {
      reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
    }

    ReactMarker.logMarker(PROCESS_PACKAGES_START);
    Systrace.beginSection(
        TRACE_TAG_REACT_JAVA_BRIDGE,
        "createAndProcessCoreModulesPackage");
    try {
    
    //CoreModulesPackage里面定義了RN框架核心的一些Java和JS的module
    //通過processPackage()方法寫入到兩本“詞典”的Builder中
      CoreModulesPackage coreModulesPackage =
          new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
          
      processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    }

    // TODO(6818138): Solve use-case of native/js modules overriding
    //這里是開發者自己定義或封裝的一些組件或者事件的package
    for (ReactPackage reactPackage : mPackages) {
      Systrace.beginSection(
          TRACE_TAG_REACT_JAVA_BRIDGE,
          "createAndProcessCustomReactPackage");
      try {
        processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
      } finally {
        Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      }
    }
    ReactMarker.logMarker(PROCESS_PACKAGES_END);

    ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_START);
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry");
    NativeModuleRegistry nativeModuleRegistry;
    try {
        //好了,創建了用于翻譯Java端的“詞典”
       nativeModuleRegistry = nativeRegistryBuilder.build();
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
    }

    NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
        ? mNativeModuleCallExceptionHandler
        : mDevSupportManager;
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
        .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
        .setJSExecutor(jsExecutor)
        .setRegistry(nativeModuleRegistry)
        //創建了用于翻譯JS端的“詞典”
        .setJSModuleRegistry(jsModulesBuilder.build())
        .setJSBundleLoader(jsBundleLoader)
        .setNativeModuleCallExceptionHandler(exceptionHandler);
    
    //到目前為止。兩本“詞典”都已經創建完畢,而且全部都在CatalystInstance這個類的**實現類的Builder中**,此時你可以回憶一下整個過程,理清一下思路。
    
    ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
    // CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
    final CatalystInstance catalystInstance;
    try {
      //這個Build()很關鍵,它用實現類的Builder創建了一個CatalystInstance類。
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
    }

    if (mBridgeIdleDebugListener != null) {
      catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
    }

    ReactMarker.logMarker(RUN_JS_BUNDLE_START);
    try {
      catalystInstance.getReactQueueConfiguration().getJSQueueThread().callOnQueue(
        new Callable<Void>() {
          @Override
          public Void call() throws Exception {
            reactContext.initializeWithInstance(catalystInstance);

            Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");
            try {
                //在這里就運行js代碼說明至少在這個方法之前,“詞典”應該傳過去了
                //于是我們刻印回去聚焦到catalystInstance = catalystInstanceBuilder.build();這段代碼
              catalystInstance.runJSBundle();
            } finally {
              Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
              ReactMarker.logMarker(RUN_JS_BUNDLE_END);
            }
            return null;
          }
        }).get();
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    } catch (ExecutionException e) {
      if (e.getCause() instanceof RuntimeException) {
        throw (RuntimeException) e.getCause();
      } else {
        throw new RuntimeException(e);
      }
    }

    return reactContext;
  }
  
  //將package中的關于Java和js的東西分別添加到兩本“詞典”的builder中
   private void processPackage(
      ReactPackage reactPackage,
      ReactApplicationContext reactContext,
      NativeModuleRegistry.Builder nativeRegistryBuilder,
      JavaScriptModuleRegistry.Builder jsModulesBuilder) {
    for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) {
      nativeRegistryBuilder.add(nativeModule);
    }
    for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()) {
      jsModulesBuilder.add(jsModuleClass);
    }
  }

catalystInstanceBuilder.build()這段代碼具體實現如下:

    public CatalystInstanceImpl build() {
      return new CatalystInstanceImpl(
          Assertions.assertNotNull(mReactQueueConfigurationSpec),
          Assertions.assertNotNull(mJSExecutor),
          Assertions.assertNotNull(mRegistry),
          Assertions.assertNotNull(mJSModuleRegistry),
          Assertions.assertNotNull(mJSBundleLoader),
          Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
    }

這個方法的作用就是直接new了一個CatalystInstanceImpl類,那么我們接下去看CatalystInstanceImpl類的構造方法:

 private CatalystInstanceImpl(
      final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry registry,
      final JavaScriptModuleRegistry jsModuleRegistry,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
    FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
    mHybridData = initHybrid();

    mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
        ReactQueueConfigurationSpec,
        new NativeExceptionHandler());
    mBridgeIdleListeners = new CopyOnWriteArrayList<>();
    //這個就是Java層要傳遞給Js層的“詞典”
    mJavaRegistry = registry;
    mJSModuleRegistry = jsModuleRegistry;
    mJSBundleLoader = jsBundleLoader;
    mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
    mTraceListener = new JSProfilerTraceListener(this);
    
    //在這個方法里,就把Java暴露給Js的詞典傳了進去
    initializeBridge(
      new BridgeCallback(this),
      jsExecutor,
      mReactQueueConfiguration.getJSQueueThread(),
      mReactQueueConfiguration.getNativeModulesQueueThread(),
      //getModuleRegistryHolder()這個方法不過是一種holder,對“詞典”做了一些封裝。
      mJavaRegistry.getModuleRegistryHolder(this));
    mMainExecutorToken = getMainExecutorToken();
  }
  
  //你瞧,native方法,直接call到C++層,接下來,由C++層通過各種折騰,然后生成某種配置文件,轉發到Js端
   private native void initializeBridge(ReactCallback callback,
                                       JavaScriptExecutor jsExecutor,
                                       MessageQueueThread jsQueue,
                                       MessageQueueThread moduleQueue,
                                       ModuleRegistryHolder registryHolder);

好了,關于“詞典”是如何傳遞過去的,就解釋到這里,雖然C++層可能有更多操作,但是目的就是一個,把Java類轉化成一個JS讀得懂的格式的文件。
那么我們接下來看看C++是如何做好傳聲筒的

1.2,數據傳遞過程

初始化完成之后,Java端和Js端都有了“詞典”,就可把自己的意圖翻譯成對方能聽得懂的話了,這個時候交流就會暢通了。

1.2.1,Java --> Js

這個問題首先Java層應該做的是找到那本Js的詞典,所以我們應該尋找Java層是在哪里調用到了JSModuleRegistry這個類的,

讓我們退回到ReactContextInitAsyncTask的doInBackground方法中,在詞典傳遞完畢之后,這個方法基本執行完畢,接下來是

  @Override
    protected void onPostExecute(Result<ReactApplicationContext> 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;
      }
    }

setupReactContext(result.get())這個方法,然后這個方法又會調用attachMeasuredRootViewToInstance()方法:

 private void attachMeasuredRootViewToInstance(
      ReactRootView rootView,
      CatalystInstance catalystInstance) {
    Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachMeasuredRootViewToInstance");
    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);
    rootView.setRootViewTag(rootTag);
    @Nullable Bundle launchOptions = rootView.getLaunchOptions();
    WritableMap initialProps = Arguments.makeNativeMap(launchOptions);
    String jsAppModuleName = rootView.getJSModuleName();

    WritableNativeMap appParams = new WritableNativeMap();
    appParams.putDouble("rootTag", rootTag);
    appParams.putMap("initialProps", initialProps);
    
    //在這里,Java找到了那本Js的“詞典”,然后runApplication
    catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
    Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
  }

其實catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams)這段代碼比較復雜,首先getJSModule方法具體實現在catalystInstanceImpl中,然后會調用到mJSModuleRegistry.getJavaScriptModule(this, executorToken, jsInterface)中,代碼如下:

  public synchronized <T extends JavaScriptModule> T getJavaScriptModule(
    CatalystInstance instance,
    ExecutorToken executorToken,
    Class<T> moduleInterface) {
    HashMap<Class<? 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, instance, registration));
    instancesForContext.put(moduleInterface, interfaceProxy);
    return (T) interfaceProxy;
  }

什么動態代理不用理他,看 new JavaScriptModuleInvocationHandler(executorToken, instance, registration)這個類:

  private static class JavaScriptModuleInvocationHandler implements InvocationHandler {

    private final WeakReference<ExecutorToken> mExecutorToken;
    private final CatalystInstance mCatalystInstance;
    private final JavaScriptModuleRegistration mModuleRegistration;

    public JavaScriptModuleInvocationHandler(
        ExecutorToken executorToken,
        CatalystInstance catalystInstance,
        JavaScriptModuleRegistration moduleRegistration) {
      mExecutorToken = new WeakReference<>(executorToken);
      mCatalystInstance = catalystInstance;
      mModuleRegistration = moduleRegistration;
    }

    //關鍵
    @Override
    public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
      ExecutorToken executorToken = mExecutorToken.get();
      if (executorToken == null) {
        FLog.w(ReactConstants.TAG, "Dropping JS call, ExecutorToken went away...");
        return null;
      }
      NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray();
      mCatalystInstance.callFunction(
        executorToken,
        mModuleRegistration.getName(),
        method.getName(),
        jsArgs
      );
      return null;
    }
  }
}

invoke方法通過mCatalystInstance調用了callFunction()方法;不用多想,我們直接在實現類中去找這個方法:

@Override
  public void callFunction(
      ExecutorToken executorToken,
      final String module,
      final String method,
      final NativeArray arguments) {
    if (mDestroyed) {
      FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
      return;
    }
    if (!mAcceptCalls) {
      throw new RuntimeException("Attempt to call JS function before JS bundle is loaded.");
    }

    callJSFunction(executorToken, module, method, arguments);
  }
  
  
    private native void callJSFunction(ExecutorToken token,String module, String method,NativeArray arguments);

callFunction()方法里面調用了callJSFunction()這個本地方法,然后由C++做轉發,這個本地方法傳遞的參數有token,包名,方法名,和參數,

至此,從Java端調用到Js端的過程,到這里可以宣告結束了。

1.2.2,js-->Java

待續。

2,矯正

  • 暫無

3,總結

  • 首先這里沒有分析js端到java端的過程,是因為我目前對于js還不是十分熟悉,目前還沒有把我能用自己的話吧這件事說清楚,所以先挖坑,到時候一定填上。
  • 從開發的角度來看整個react-native兩端通信,其實我們最終是避不開這些框架層的東西的,架構在原生Android的系統之上的js無論如何都是需要和Java溝通的,雖然react-native封裝了一些事件和組件,但是假如業務需要而框架沒有封裝,那你就不得不直面這種通信了。
  • 還是原生開發好,windows說。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容