2019-01-31
JS Bundle 在 React Native 中是如何加载运行的呢?在本文中,我们通过 JSBundleLoader 类作为切入点,对这个过程一探究竟。
JSBundleLoader 类的作用是:它存储 JS 包信息,并允许通过 ReactBridge 加载包。
这个类本身很简单,是一个抽象类:
public abstract class JSBundleLoader { public abstract String loadScript(CatalystInstanceImpl instance); }
其中:
在 JSBundleLoader 中通过静态方法的形式对外暴露了几种 Loader 的创建方法,有如下几类:
在本节中,我们主要来看 AssetLoader。
在上一节,ReactInstanceManagerBuilder 中的 build 一步,会创建 AssetLoader:
public ReactInstanceManager build() { ... return new ReactInstanceManager( mApplication, mCurrentActivity, mDefaultHardwareBackBtnHandler, mJavaScriptExecutorFactory == null ? new JSCJavaScriptExecutorFactory(appName, deviceName) : mJavaScriptExecutorFactory, (mJSBundleLoader == null && mJSBundleAssetUrl != null) ? JSBundleLoader.createAssetLoader( mApplication, mJSBundleAssetUrl, false /*Asynchronous*/) : mJSBundleLoader, mJSMainModulePath,
其中,createAssetLoader 的签名为:
public static JSBundleLoader createAssetLoader( final Context context, final String assetUrl, final boolean loadSynchronously) {
其中:
我们来看 createAssetLoader 的内部实现:
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; } }; }
其中:
我们进入 CatalystInstanceImpl.loadScriptFromAssets,实际加载的逻辑都在这里面。这个方法的定义如下:
/* package */ void loadScriptFromAssets(AssetManager assetManager, String assetURL, boolean loadSynchronously) { mSourceURL = assetURL; jniLoadScriptFromAssets(assetManager, assetURL, loadSynchronously); }
由此可以看出,它首先把 URL 保存下来,之后实际加载是调用 JNI 方法,进入 Native 层来实现的。
我们进入到 Native 层方法,位于 ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp。首先看方法签名:
void CatalystInstanceImpl::jniLoadScriptFromAssets( jni::alias_ref<JAssetManager::javaobject> assetManager, const std::string& assetURL, bool loadSynchronously) {
其中:
jni::alias_ref<JAssetManager::javaobject>
是 fbjni 提供的一种 Java 类映射方法首先我们通过下面代码,拿到 AssetManager 在 C++ 中的实例:
auto manager = extractAssetManager(assetManager);
通过下面方法读取 Bundle 的内容:
auto script = loadScriptFromAssets(manager, sourceURL);
其中,loadScriptFromAssets 方法并不复杂,我们略过。
现在我们读取的包还是字符串,下面要对它进行解析:
instance_->loadScriptFromString(std::move(script), sourceURL, loadSynchronously);
其中:
instance_
是 Native 层 CatalystInstanceImpl 类的成员,类型为 Instance(位于 ReactCommon/cxxreact/Instance.h)。这个方法的签名如下:
void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string, std::string sourceURL, bool loadSynchronously) {
其中:
这会调用:
loadApplicationSync(nullptr, std::move(string), std::move(sourceURL));
我们进入 loadApplicationSync:
nativeToJsBridge_->loadApplicationSync(std::move(bundleRegistry), std::move(string), std::move(sourceURL));
其中:
nativeToJsBridge_
是 Instance 类的成员,其类型为 NativeToJsBridge。方法签名为:
void NativeToJsBridge::loadApplicationSync( std::unique_ptr<RAMBundleRegistry> bundleRegistry, std::unique_ptr<const JSBigString> startupScript, std::string startupScriptSourceURL) {
其中:
方法内部调用了:
m_executor->loadApplicationScript(std::move(startupScript), std::move(startupScriptSourceURL));
其中:
m_executor
是 NativeToJsBridge 类的成员,类型为 JSExecutorNative 层的 JSExecutor 位于 ReactCommon/cxxreact/JSExecutor.h,需要注意的是它是一个抽象类。
它的实现类是:JSCExecutor:位于 ReactCommon/cxxreact/JSCExecutor.h
这里设计 JSExecutor 这样一个隔离层的意思是 JS 引擎可隔离,React Native 选用的 JS 引擎是 JavaScriptCore,但是通过隔离层,我们完全可以换用其他的引擎。
这里我们来看 JSCExecutor 的 loadApplicationScript 方法,方法签名:
void JSCExecutor::loadApplicationScript( std::unique_ptr<const JSBigString> script, std::string sourceURL) {
其中两个参数:包的内容以及包的路径。
核心的方法是这一句:
evaluateScript(m_context, jsScript, jsSourceURL);
这个方法的定义位于 ReactCommon/jschelpers/JSCHelpers.cpp:
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; }
到这里,我们已经进入了 JavaScriptCore 的世界中了。
JSC_JSEvaluateScript_
下面我们进入 JavaScriptCore 的源码。JSC_JSEvaluateScript_
定义在 JavaScriptCore.h 中,它是一个宏:
#define JSC_JSEvaluateScript(...) __jsc_wrapper(JSEvaluateScript, __VA_ARGS__)
__jsc_wrapper
也是一个宏:
#define __jsc_wrapper(method, ctx, ...) method(ctx, ## __VA_ARGS__)
最终来到了 JSBase.h 中的:
JS_EXPORT JSValueRef JSEvaluateScript( JSContextRef ctx, // JavaScript Runtime Context JSStringRef script, // JS Bundle JSObjectRef thisObject, // 用作 this 的对象,这里是 NULL JSStringRef sourceURL, // 路径,也就是我们的 bundle path int startingLineNumber, // 如果报错了,抛出异常的行号 JSValueRef* exception); // 用于抛异常的指针
这个方法的作用就是执行 JS 脚本。
至此,我们就一路从 Java 层的 BundleLoader,到了 Native 层加载 Bundle 代码,再从 React Native 的代码进入 JavaScriptCore 的代码。完整地走了一趟 JS Bundle 从加载到执行的全过程。
前面我们对 JSBundleLoader 内部完成了一探究竟。在本节中,我们把 JSBundleLoader 当做一个黑盒,看看外面是如何使用它的。
还记得 CataLystInstanceBuilder 创建了 AssetLoader 的实例吗?
JSBundleLoader 是作为 CatalystInstance 的成员进行持有的。我们回到 Java 世界中的 CatalystInstanceImpl.java。
JSBundleLoader 在其中作为一个成员:
private final JSBundleLoader mJSBundleLoader;
它在 CatalystInstanceImpl 中是如何使用的呢?
位于 runJSBundle 方法中:
@Override public void runJSBundle() { mJSBundleLoader.loadScript(CatalystInstanceImpl.this); ... }
下面我们由内而外地去梳理。
这个方法在哪里被调用了呢?
我们找到了在 ReactInstanceManager.createReactContext 中。这与我们系列中的第一篇已经呼应上了。
这个方法在哪里被调用了呢?
答案是在 ReactInstanceManager.runCreateReactContextOnNewThread 中。
我们再往上跟:
这样,我们又反向地把第一篇中的路径反走了一遍,一正一反,对整个过程有了更深的认识了。
在本文中,我们从 JSBundleLoader 入手,将 JS Bundle 的加载、执行流程完整地分析了一遍。