ReactNative源码分析-加载JSBundle

2018/01/09 react-native, android

之前我们分析了加载ReactNativeActivitiy的启动,在启动过程中会调用到CatalysInstanceImpl的runJSBundle,此篇我们来看react-native是如何加载JSBundle,我们就从CastalysInstanceImpl中的runJSBundle开始看起。

  @Override
  public void runJSBundle() {
   ... 
   mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
   ...
  }

runJSBundle中直接调用mJSBundleLoader的loadScript方法, mJSBundleLoader变量是构造CatalysInstanceImpl时传入的,上篇我们分析过,mJSBundleLoader又是构建ReactInstanceManager创建的,我们直接来看创建的代码
ReactInstanceManagerBuilder.java

new ReactInstanceManager(
        mApplication,
        mCurrentActivity,
        mDefaultHardwareBackBtnHandler,
        mJavaScriptExecutorFactory == null
            ? new JSCJavaScriptExecutorFactory(appName, deviceName)
            : mJavaScriptExecutorFactory,
        (mJSBundleLoader == null && mJSBundleAssetUrl != null)
            ? JSBundleLoader.createAssetLoader(
                mApplication, mJSBundleAssetUrl, false /*Asynchronous*/)
            : mJSBundleLoader,
        mJSMainModulePath,
        mPackages,
        mUseDeveloperSupport,
        mBridgeIdleDebugListener,
        Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
        mUIImplementationProvider,
        mNativeModuleCallExceptionHandler,
        mRedBoxHandler,
        mLazyNativeModulesEnabled,
        mLazyViewManagersEnabled,
        mDelayViewManagerClassLoadsEnabled,
        mDevBundleDownloadListener,
        mMinNumShakes,
        mMinTimeLeftInFrameForNonBatchedOperationMs);

可以看到如果用户没有注入一个JSBundleLoader的话, 默认的Loader是AssetLoader, 而在AssertLoader中则又唤起了CatalysInstanceIml的loadScriptFromAssets方法(同级别的方法还有loadScriptFromFile)

  public static JSBundleLoader createAssetLoader(
      final Context context,
      final String assetUrl,
      final boolean loadSynchronously) {
    return new JSBundleLoader() {
      @Override
      public String loadScript(CatalystInstanceImpl instance) {
        instance.loadScriptFromAssets(context.getAssets(), assetUrl, loadSynchronously);
        return assetUrl;
      }
    };
  }

CatalysIntanceImpl.java

/* package */ void loadScriptFromAssets(AssetManager assetManager, String assetURL, boolean loadSynchronously) {
	mSourceURL = assetURL;
	jniLoadScriptFromAssets(assetManager, assetURL, loadSynchronously);
}

  private native void jniLoadScriptFromAssets(AssetManager assetManager, String assetURL, boolean loadSynchronously);

下面直调用了jni的loadScriptFromAssets方法,我们来看c++部分的实现:

react/jni/CatalystInstanceImpl.cpp

void CatalystInstanceImpl::jniLoadScriptFromAssets(
    jni::alias_ref<JAssetManager::javaobject> assetManager,
    const std::string& assetURL,
    bool loadSynchronously) {
  const int kAssetsLength = 9;  // strlen("assets://");
  auto sourceURL = assetURL.substr(kAssetsLength);

  auto manager = extractAssetManager(assetManager);
  //从asset中读取字符串到内存
  auto script = loadScriptFromAssets(manager, sourceURL);
  if (JniJSModulesUnbundle::isUnbundle(manager, sourceURL)) {
    auto bundle = JniJSModulesUnbundle::fromEntryFile(manager, sourceURL);
    auto registry = RAMBundleRegistry::singleBundleRegistry(std::move(bundle));
    instance_->loadRAMBundle(
      std::move(registry),
      std::move(script),
      sourceURL,
      loadSynchronously);
    return;
  } else {
    instance_->loadScriptFromString(std::move(script), sourceURL, loadSynchronously);
  }
}i

其中loadSynchronously为false, 可以回溯到ReactInstanceManagerBuilder重穿件JSBundleLoader的时候。 从assets中加载完JSBundle后调用jniJSModulesUnBundle的isUnbundle方法判断是否是JSBundle资源,具体判断方法见:
react/jni/JniJSModulesUnbundle.cpp

bool JniJSModulesUnbundle::isUnbundle(
    AAssetManager *assetManager,
    const std::string& assetName) {
  if (!assetManager) {
    return false;
  }

  auto magicFileName = jsModulesDir(assetName) + MAGIC_FILE_NAME;
  auto asset = openAsset(assetManager, magicFileName.c_str());
  if (asset == nullptr) {
    return false;
  }

  magic_number_t fileHeader = 0;
  AAsset_read(asset.get(), &fileHeader, sizeof(fileHeader));
  return fileHeader == htole32(MAGIC_FILE_HEADER);
} 

可以看到系统会找到文件,然后读取文件头, 判断文件头是否是MAGEC_FILE_HEADER,来判断是否是JSBundle文件,回到loadScriptFromAssets方法中, 如果此时isUnBundle方法返回true, 则调用instance_的loadRAMBundle方法, 否则调用instance的loadScriptFromString方法。
instance_ 是Instance对象的实例,可以在CatalystInstanceImpl.h中查找到声明:

  std::shared_ptr<Instance> instance_; 

可以查看Instance中的具体方法, Instance.cpp文件存在于 ReactCommon/cxxreact目录下:

void Instance::loadRAMBundle(std::unique_ptr<RAMBundleRegistry> bundleRegistry,
                             std::unique_ptr<const JSBigString> startupScript,
                             std::string startupScriptSourceURL,
                             bool loadSynchronously) {
  if (loadSynchronously) {
    loadApplicationSync(std::move(bundleRegistry), std::move(startupScript),
                        std::move(startupScriptSourceURL));
  } else {
    loadApplication(std::move(bundleRegistry), std::move(startupScript),
                    std::move(startupScriptSourceURL));
  }
}

最终走到上述方法, 因为loadSynchronously是false,则走入到loadAplication中(异步加载)

void Instance::loadApplication(std::unique_ptr<RAMBundleRegistry> bundleRegistry,
                               std::unique_ptr<const JSBigString> string,
                               std::string sourceURL) {
  callback_->incrementPendingJSCalls();
  SystraceSection s("Instance::loadApplication", "sourceURL",
                    sourceURL);
  nativeToJsBridge_->loadApplication(std::move(bundleRegistry), std::move(string),
                                     std::move(sourceURL));
}

我们来看加载Module的过程,是通过fromEntryFile加载的是一个JniJsModulesUnbunle, fromEntryFile方法中只是获取了文件路径,并设置了assetManager.然后在CatalysInstanceImpl.cpp中又通过singleBundleRegistry获取了一个RAMBundleRegistry对象,我们来看一下它的具体结构

std::unique_ptr<RAMBundleRegistry> RAMBundleRegistry::singleBundleRegistry(std::unique_ptr<JSModulesUnbundle> mainBundle) {
  RAMBundleRegistry *registry = new RAMBundleRegistry(std::move(mainBundle));
  return std::unique_ptr<RAMBundleRegistry>(registry);
}

这里直接传入了之前获得的JNIJSModulesUnbundle到RAmBundleRegistry对象中,记录在unique_ram_bundle字段。

之后最终调用了NativeToJsBridge的loadApplication方法,和CatalystInstnaceImpl.cpp loadScruptFromString的调用最终路径一致,区别在于RanBundleRegistry的参数后者为空. 上一篇我们已经分析了Native和JS之间的通信机制,可以看到这里也是通过NativeToJsBridge来执行js文件的,我们来看loadApplication方法:

  runOnExecutorQueue(
      [bundleRegistryWrap=folly::makeMoveWrapper(std::move(bundleRegistry)),
       startupScript=folly::makeMoveWrapper(std::move(startupScript)),
       startupScriptSourceURL=std::move(startupScriptSourceURL)]
        (JSExecutor* executor) mutable {
    auto bundleRegistry = bundleRegistryWrap.move();
    if (bundleRegistry) {
      executor->setBundleRegistry(std::move(bundleRegistry));
    }
    executor->loadApplicationScript(std::move(*startupScript),
                                    std::move(startupScriptSourceURL));
  });

和js通信机制一样在js的线程中调用了JSCExecutor的loadApplicationScript,区别在于含有RamBundleRegistry的额外调用了JSCExecutor的setBundleRegistry方法,我们先看setBundleRegistry方法:

 void JSCExecutor::setBundleRegistry(std::unique_ptr<RAMBundleRegistry> bundleRegistry) {
      if (!m_bundleRegistry) {
        installNativeHook<&JSCExecutor::nativeRequire>("nativeRequire");
      }
      m_bundleRegistry = std::move(bundleRegistry);
    }

第一次本地m_bundleRegistry不存在的时候,添加了Hook,至于具体内容不是我们关心的不做套路,然后记录下了RAMBundleRegistry.之后如果出发到registerBundle的时候都回记录在这个BundleRegistry中。

我们接着看loadloadApplicationScript,此方法中最终掉到了ReactCommon/jschelpers/JSCHelpers.cpp 中的evaluateScript方法:

JSValueRef evaluateScript(JSContextRef context, JSStringRef script, JSStringRef sourceURL) {
  JSValueRef exn, result;
  result = JSC_JSEvaluateScript(context, script, NULL, sourceURL, 0, &exn);
  if (result == nullptr) {
    throw JSException(context, exn, sourceURL);
  }
  return result;
}

JSC_JSEJSC_JSEvaluateScript 定义在JavaScriptCore中。。。实际上是调用的webkitcore来加载js的。。。可以在ReactAndroid下的libs下的BUCK中找到远程的文件

remote_file(
    name = "android-jsc-aar",
    sha1 = "880cedd93f43e0fc841f01f2fa185a63d9230f85",
    url = "mvn:org.webkit:android-jsc:aar:r174650",

在maven上可以找到此包,组组有3.9m大,,不难看出为什么RN比较大了。


欢迎关注我的微信公众号

璐豪笔记

CoderHouse

Search

    Post Directory