前两篇文章和大家分享了RN中的触摸事件机制。接下来的内容将结合Android原生层来分析拆包解决方案。如果你具有Android原生开发经验,并且在实践 Android + RN 的混合模式开发,那么这次连载的文章内容你一定喜欢。拆包解决方案的系列内容目录大致如下:
(2)以RN最新版本为例,结合 Metro,分析如何实现拆包
(3)封装原生层 JSBundle 加载逻辑,实现拆包解决方案
要实现RN的拆包,首选需要我们从RN的加载执行流程切入,了解系统是如何完成JS代码、RN视图加载显示的。网上对于启动流程的分析博客很多,这里我们会跟踪代码执行流程,帮大家梳理清楚执行流程即可。在分析启动加载模块之前,大家先想个问题。
一、为什么要拆包?
RN作为非常优秀的移动端跨平台开发框架,在近几年得到众多开发者的认可。国内各大厂采用在当前原生应用内集成RN的方式,使得App应用的灵活性得到了很大的提升。例如:去哪儿、JD、搜车等等,都采用了这种混合开发模式。顾名思义,在原生应用内嵌入RN,就是需要在原生应用内加载RN模块(1个或多个JSBundle),并得以显示。JSBundle中包含了当前RN模块的js代码。如果存在多个RN模块需要被加载时,就需要分别打出多个JSBundle,并且多个JSBundle包含了很多重复的代码(例如:第三方依赖)。拆包的方式,就是将其中重复不变的代码打成基础包,动态变化的打成业务包。那么就做到了JSBundle的拆分。JSBundle的拆分,对降低内存的占用,减少加载时间,减少热更新时流量带宽等,在优化方面起到了非常大的作用。实现拆包的过程涉及的方面比较多,需要我们对RN模块加载,打包过程有非常深刻的理解,所以本篇内容就先从RN的启动加载流程开始。
二、从源码角度解析 RN 应用启动、视图加载原理
源码基于当前最新RN版本:0.57
"dependencies": {
"react": "16.6.0-alpha.8af6728",
"react-native": "0.57.3"
}
1. JS端启动流程
创建一个RN项目需要用到 react-native init projectName 命令,当执行完成后,系统会自动生成所需文件,例如 android、ios、index.js等等。index.js 作为应用的默认入口,需要将当前APP注册到AppRegistry
组件中:
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
我们来看在 node_modules/react-native/Libraries/ReactNative 目录下的 AppRegistery.js 文件:
/**
* Registers an app's root component.
*
* See http://facebook.github.io/react-native/docs/appregistry.html#registercomponent
*/
registerComponent(
appKey: string,
componentProvider: ComponentProvider,
section?: boolean,
): string {
runnables[appKey] = {
componentProvider,
run: appParameters => {
renderApplication(
componentProviderInstrumentationHook(componentProvider),
appParameters.initialProps,
appParameters.rootTag,
wrapperComponentProvider && wrapperComponentProvider(appParameters),
appParameters.fabric,
);
},
};
if (section) {
sections[appKey] = runnables[appKey];
}
return appKey;
},
registerComponent 方法中传入了 appKey,ComponentProvider 参数并调用了 renderApplication 方法。可以发现在方法中,执行了runnables,并拿到了 appParameters,该参数即从原生层在应用加载初始化时传递给RN层的参数。在分析Native层源码时,我们再仔细看。
renderApplication 和AppRegistry 文件在同一目录下。renderApplication核心代码如下:
function renderApplication<Props: Object>(
RootComponent: React.ComponentType<Props>,
initialProps: Props,
rootTag: any,
WrapperComponent?: ?React.ComponentType<*>,
fabric?: boolean,
showFabricIndicator?: boolean,
) {
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
let renderable = (
<AppContainer rootTag={rootTag} WrapperComponent={WrapperComponent}>
<RootComponent {...initialProps} rootTag={rootTag} />
{fabric === true && showFabricIndicator === true ? (
<ReactFabricIndicator />
) : null}
</AppContainer>
);
...
}
renderApplication中调用了AppContainer组件来封装当前rootVIew组件。最终通过调用AppRegistry.runApplication实际运行应用程序。
/**
* Loads the JavaScript bundle and runs the app.
*
*/
runApplication(appKey: string, appParameters: any): void {
const msg =
'Running application "' +
appKey +
'" with appParams: ' +
JSON.stringify(appParameters) +
'. ' +
'__DEV__ === ' +
String(__DEV__) +
', development-level warning are ' +
(__DEV__ ? 'ON' : 'OFF') +
', performance optimizations are ' +
(__DEV__ ? 'OFF' : 'ON');
infoLog(msg);
BugReporting.addSource(
'AppRegistry.runApplication' + runCount++,
() => msg,
);
invariant(
runnables[appKey] && runnables[appKey].run,
'Application ' +
appKey +
' has not been registered.\n\n' +
"Hint: This error often happens when you're running the packager " +
'(local dev server) from a wrong folder. For example you have ' +
'multiple apps and the packager is still running for the app you ' +
'were working on before.\nIf this is the case, simply kill the old ' +
'packager instance (e.g. close the packager terminal window) ' +
'and start the packager in the correct app folder (e.g. cd into app ' +
"folder and run 'npm start').\n\n" +
'This error can also happen due to a require() error during ' +
'initialization or failure to call AppRegistry.registerComponent.\n\n',
);
SceneTracker.setActiveScene({name: appKey});
runnables[appKey].run(appParameters);
},
在 runApplication 方法中,通过 runnables[appKey] && runnables[appKey].run 来检查是否可以找到appKey对应的module组件,如果没有,则会抛出异常。
视图是如何被加载显示呢?就需要我们从Native层一探究竟。
2. Native端启动流程
当RN项目创建完成后,系统会生成android、ios平台的源码。打开android目录,可以看到在原生代码中会生成 MainActivity
和 MainApplication
两个Java类。很明显没,MainActivity 即为原生层应用程序的入口文件。MainApplication作为整体应用程序的初始化入口文件。我们先来看 MainActivity.java 文件:
public class MainActivity extends ReactActivity {
/**
* 返回你在rn index.js 注册的名称,
* 即 AppRegistry.registerComponent()传入的名称,用来渲染组件。
*/
@Override
protected String getMainComponentName() {
return "splitBundle";
}
}
很简单,继承 ReactActivity 并实现 getMainComponentName 方法,返回与 AppRegistry.registerComponent 的 appKey 相同名称即可。继续来看 MainApplication.java 文件:
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
// 加载C++层渲染代码
SoLoader.init(this, /* native exopackage */ false);
}
}
MainApplication 中主要完成了三件事:
(1)实现 ReactApplication 接口,重写 getReactNativeHost 方法,返回ReactNativeHost实例。
(2)定义并初始化 ReactNativeHost,实现 getUseDeveloperSupport、getPackages、getJSMainModuleName 方法,完成初始化设置。
(3)在 onCreate 生命周期方法中,调用SoLoader的init方法,启动C++层逻辑代码的初始化加载。
了解完两个文件,我们先从 MainActivity 开始分析。
ReactActivity
MainActivity 继承 ReactActivity
类,重写了getMainComponentName
方法,并且方法的返回值需要和我们在JS端的值保持一致。通过上面我们分析的 AppRegistry.js 中的 runApplication 方法发现,如果getMainComponentName中返回的名称与 RN 层AppRegistry.registerComponent注册名称
不一致,会出现 Application XXX appKey has not been registered 异常。跟进 ReactActivity 类,看下核心代码:
package com.facebook.react;
/**
* Base Activity for React Native applications.
*/
public abstract class ReactActivity extends Activity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
private final ReactActivityDelegate mDelegate;
protected ReactActivity() {
mDelegate = createReactActivityDelegate();
}
/**
* 返回从JavaScript注册的主要组件的名称,用于安排组件的渲染。
* e.g. "MoviesApp"
*/
protected @Nullable String getMainComponentName() {
return null;
}
/**
* 在构造时调用,如果您有自定义委托实现,则覆盖.
*/
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDelegate.onCreate(savedInstanceState);
}
... 中间省略生命周期、返回事件、权限申请函数
/**
* 获取 ReactNativeHost 实例
*/
protected final ReactNativeHost getReactNativeHost() {
return mDelegate.getReactNativeHost();
}
/**
* 获取 ReactInstanceManager 实例
*/
protected final ReactInstanceManager getReactInstanceManager() {
return mDelegate.getReactInstanceManager();
}
/**
* 加载 JSBundle
*/
protected final void loadApp(String appKey) {
mDelegate.loadApp(appKey);
}
}
ReactActivity类中主要定义如下:
(1)继承 Activity,实现 DefaultHardwareBackBtnHandler、PermissionAwareActivity 两个接口。重写其中的返回事件,及请求权限的方法。
(2)构造函数中调用 createReactActivityDelegate 方法,传入this、和 getMainComponentName 方法返回值,创建 ReactActivityDelegate实例。
(3)重写 Activity 生命周期方法,调用 delegate 实例的对应生命周期方法。
(4)定义获取 ReactNativeHost、ReactInstanceManager 实例方法。
(5)定义 loadApp方法。
很明显,ReactActivity 中采用了委托的方式,将所有行为全权交给了 ReactActivityDelegate 去处理。好处也很明显,降低代码耦合,提升了可扩展能力。我们接着来看 ReactActivityDelegate 类中核心代码是如何定义的。
ReactActivityDelegate
package com.facebook.react;
/**
* Delegate class for {@link ReactActivity} and {@link ReactFragmentActivity}. You can subclass this
* to provide custom implementations for e.g. {@link #getReactNativeHost()}, if your Application
* class doesn't implement {@link ReactApplication}.
*/
public class ReactActivityDelegate {
private final @Nullable Activity mActivity;
private final @Nullable FragmentActivity mFragmentActivity;
private final @Nullable String mMainComponentName;
private @Nullable ReactRootView mReactRootView;
private @Nullable DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
...
public ReactActivityDelegate(Activity activity, @Nullable String mainComponentName) {
mActivity = activity;
mMainComponentName = mainComponentName;
mFragmentActivity = null;
}
public ReactActivityDelegate(
FragmentActivity fragmentActivity,
@Nullable String mainComponentName) {
mFragmentActivity = fragmentActivity;
mMainComponentName = mainComponentName;
mActivity = null;
}
protected @Nullable Bundle getLaunchOptions() {
return null;
}
protected ReactRootView createRootView() {
return new ReactRootView(getContext());
}
/**
* Get the {@link ReactNativeHost} used by this app. By default, assumes
* {@link Activity#getApplication()} is an instance of {@link ReactApplication} and calls
* {@link ReactApplication#getReactNativeHost()}. Override this method if your application class
* does not implement {@code ReactApplication} or you simply have a different mechanism for
* storing a {@code ReactNativeHost}, e.g. as a static field somewhere.
*/
protected ReactNativeHost getReactNativeHost() {
return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
}
public ReactInstanceManager getReactInstanceManager() {
return getReactNativeHost().getReactInstanceManager();
}
protected void onCreate(Bundle savedInstanceState) {
if (mMainComponentName != null) {
loadApp(mMainComponentName);
}
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
}
protected void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
getPlainActivity().setContentView(mReactRootView);
}
... 中间省略生命周期、返回事件、权限请求的方法
private Context getContext() {
if (mActivity != null) {
return mActivity;
}
return Assertions.assertNotNull(mFragmentActivity);
}
private Activity getPlainActivity() {
return ((Activity) getContext());
}
}
ReactActivityDelegate 类中主要定义如下:
(1)定义 getReactNativeHost、getReactInstanceManager 方法,获取对应实例。可以看到此处调用的是MainApplication中定义的ReactNativeHost实例。这里也就明白,为什么MainApplication需要实现ReactApplication接口,并重写 getReactNativeHost方法了。如果不实现 ReactApplication,可以把ReactNativeHost定义成全局静态常量即可。
(2)定义生命周期方法、返回事件方法、权限请求方法。并在方法中调用 ReactInstanceManager 中对应的方法。
(3)定义 createRootView 方法,在该方法中,通过 new ReactRootView,创建 ReactRootView 实例。
(4)在 onCreate 生命周期中,判断 mMainComponentName,如果不为 null,则执行 loadApp 方法。所以我们重点来看 loadApp 方法。
(5)在 loadApp 方法中主要做了三件事:
1. 创建 RootView
2. 调用 RootView 实例的 startReactApplication 方法,将 ReactInstanceManager 实例、appKey、启动时初始化参数作为参数传递过去
3. 将 ReactRootView 设置为 MainActivity 布局视图
ReactNativeHost
从 ReactActivityDelegate 方法中,我们了解到很多方法都交给了 ReactInstanceManager 实例去处理,ReactInstanceManager实例时通过 MainApplication 类中初始化的 ReactNativeHost 实例获取,继续跟进 ReactNativeHost 源码
package com.facebook.react;
/**
* 包含 ReactInstanceManager 实例的简单类, 在 MainApplication 中定义 或 定义成静态字段使用。
*/
public abstract class ReactNativeHost {
private final Application mApplication;
private @Nullable ReactInstanceManager mReactInstanceManager;
protected ReactNativeHost(Application application) {
mApplication = application;
}
/**
* 获取 或 创建 ReactInstanceManager 实例
*/
public ReactInstanceManager getReactInstanceManager() {
if (mReactInstanceManager == null) {
mReactInstanceManager = createReactInstanceManager();
}
return mReactInstanceManager;
}
/**
* 获取此持有者是否包含{@link ReactInstanceManager}实例
*/
public boolean hasInstance() {
return mReactInstanceManager != null;
}
/**
* 销毁当前实例并释放对其的内部引用,允许它进行GCed
*/
public void clear() {
if (mReactInstanceManager != null) {
mReactInstanceManager.destroy();
mReactInstanceManager = null;
}
}
/**
* 创建 ReactInstanceManager 实例
*/
protected ReactInstanceManager createReactInstanceManager() {
ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
.setApplication(mApplication)
.setJSMainModulePath(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setRedBoxHandler(getRedBoxHandler())
.setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
.setJSIModulesPackage(getJSIModulePackage())
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
ReactInstanceManager reactInstanceManager = builder.build();
return reactInstanceManager;
}
protected final Application getApplication() {
return mApplication;
}
protected @Nullable
JSIModulePackage getJSIModulePackage() {
return null;
}
/**
* 返回 JSBundle 主模块的名称。 确定用于获取JS包的URL来自打包服务器。
* 它仅在启用dev支持时使用。
* 这是创建 ReactInstanceManager 后要执行的第一个文件。
* 默认 index.android”
*/
protected String getJSMainModuleName() {
return "index.android";
}
/**
* 返回捆绑文件的自定义路径。 这是在应该加载bundle的情况下使用的自定义路径。例如“文件://sdcard/myapp_cache/index.android.bundle”
* 默认情况下,它是从指定路径的Android assets目录下加载
*/
protected @Nullable String getJSBundleFile() {
return null;
}
/**
* 返回资产中包的名称。 如果为null,则不指定文件路径捆绑。
* 该方法只能与 getUseDeveloperSupport 一起使用并且将会总是尝试从打包服务器加载JS包。
* 默认为 index.android.bundle
*/
protected @Nullable String getBundleAssetName() {
return "index.android.bundle";
}
/**
* 返回是否应启用dev模式
*/
public abstract boolean getUseDeveloperSupport();
/**
* 返回应用程序使用的 ReactPackage 列表,至少返回 MainReactPackage。
* 如果您的应用使用除默认视图或模块之外的其他视图或模块,您需要在此处添加更多套餐。
*/
protected abstract List<ReactPackage> getPackages();
}
在创建 ReactInstanceManager 过程中,设置了JSMainModuleName、是否开发者模式、初始化的 Packages、JSBundleFile,并且系统为我们提供了更多的选择来自定义行为方式,例如JSBundle的加载路径,JSBundle文件名称等等。此时再回去看MainApplication中定义ReactNativeHost的代码,相信你已经恍然大悟,其实就是在为创建ReactInstanceManager 做热身准备。
ReactRootView
ReactRootView是一个自定义的View,其父类是FrameLayout。在 ReactActivityDelegate 类的 loadApp方法中,调用了ReactRootView实例的 startReactApplication 方法 ,我们来看 startReactApplication 方法中做了什么。
/**
* 使用提供的{@param reactInstanceManager}通过(@ {param
* moduleName})加载并呈现的react组件的渲染,同时附加到该管理器的JS上下文。
* 额外参数{@param launchOptions}可用于传递react组件的initialproperties。
*/
public void startReactApplication(
ReactInstanceManager reactInstanceManager,
String moduleName,
@Nullable Bundle initialProperties) {
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "startReactApplication");
try {
UiThreadUtil.assertOnUiThread();
Assertions.assertCondition(
mReactInstanceManager == null,
"This root view has already been attached to a catalyst instance manager");
mReactInstanceManager = reactInstanceManager;
mJSModuleName = moduleName;
mAppProperties = initialProperties;
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
mReactInstanceManager.createReactContextInBackground();
}
attachToReactInstanceManager();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
在 startReactApplication 方法中调用了 ReactInstanceManager 实例的 createReactContextInBackground 方法。
/**
* 后台异步任务中异步响应上下文初始化。 这使应用程序能够预加载应用程序JS,并在ReactRootView可用和测量之前执行全局代码。
* 仅在应用程序第一次设置时从UI线程调用
* 只在创建反应时调用 createReactContextInBackground。当重新加载JS时,例如从新文件中,应使用 recreateReactContextInBackground
*/
@ThreadConfined(UI)
public void createReactContextInBackground() {
mHasStartedCreatingInitialContext = true;
recreateReactContextInBackgroundInner();
}
该方法只会在 Application 中执行一次,JSBundle 重载时,会走 recreateReactContextInBackground, 这两个方法最终都会调用recreateReactContextInBackgroundInner 方法。
@ThreadConfined(UI)
private void recreateReactContextInBackgroundInner() {
if (mUseDeveloperSupport
&& mJSMainModulePath != null
&& !Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
final DeveloperSettings devSettings = mDevSupportManager.getDevSettings();
// 如果启用了远程JS调试,从dev服务器加载。
if (mDevSupportManager.hasUpToDateJSBundleInCache() &&
!devSettings.isRemoteJSDebugEnabled()) {
// 如果从服务器下载了最新的捆绑包,禁用远程JS调试,始终使用它。
onJSBundleLoadedFromServer(null);
} else if (mBundleLoader == null) {
mDevSupportManager.handleReloadJS();
} else {
mDevSupportManager.isPackagerRunning(
new PackagerStatusCallback() {
@Override
public void onPackagerStatusFetched(final boolean packagerIsRunning) {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (packagerIsRunning) {
mDevSupportManager.handleReloadJS();
} else {
//如果dev服务器关闭,请禁用远程JS调试。
devSettings.setRemoteJSDebugEnabled(false);
recreateReactContextInBackgroundFromBundleLoader();
}
}
});
}
});
}
return;
}
// 从 本地路径 加载 jsBundle
recreateReactContextInBackgroundFromBundleLoader();
}
@ThreadConfined(UI)
private void recreateReactContextInBackgroundFromBundleLoader() {
// 从BundleLoader加载
recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader);
}
在 recreateReactContextInBackgroundInner 方法中,首先判断当前环境是否为开发者模式,在开发者模式下会执行 onJSBundleLoadedFromServer 方法从服务器加载 jsBundle文件。否则执行 recreateReactContextInBackgroundFromBundleLoader 方法从本地目录加载。在 recreateReactContextInBackgroundFromBundleLoader 方法中调用了 recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader) 方法。jsExecutorFactory 为 C++ 和 JS 双向通信的中转站。jsBundleLoader 为 bundle 加载器,根据 ReactNativeHost 中的配置决定从哪里加载bundle文件。
@ThreadConfined(UI)
private void recreateReactContextInBackground(JavaScriptExecutorFactory jsExecutorFactory, JSBundleLoader jsBundleLoader) {
// 创建 ReactContextInitParams 对象
final ReactContextInitParams initParams = new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
if (mCreateReactContextThread == null) {
// 开启一个新的线程创建 ReactContext
runCreateReactContextOnNewThread(initParams);
} else {
mPendingReactContextInitParams = initParams;
}
}
接着看 runCreateReactContextOnNewThread 方法:
@ThreadConfined(UI)
private void runCreateReactContextOnNewThread(final ReactContextInitParams initParams) {
...
mCreateReactContextThread =
new Thread(
new Runnable() {
@Override
public void run() {
....
//由于 destroy() 可能已经运行并将其设置为false,因此在创建之前确保它为true
mHasStartedCreatingInitialContext = true;
try {
// 标准显示系统优先级,主要是改善UI的刷新
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
// 创建 ReactApplicationContext 实例
final ReactApplicationContext reactApplicationContext =
createReactContext(
initParams.getJsExecutorFactory().create(),
initParams.getJsBundleLoader());
mCreateReactContextThread = null;
final Runnable maybeRecreateReactContextRunnable =
new Runnable() {
@Override
public void run() {
if (mPendingReactContextInitParams != null) {
runCreateReactContextOnNewThread(mPendingReactContextInitParams);
mPendingReactContextInitParams = null;
}
}
};
Runnable setupReactContextRunnable =
new Runnable() {
@Override
public void run() {
try {
setupReactContext(reactApplicationContext);
} catch (Exception e) {
mDevSupportManager.handleException(e);
}
}
};
// 开启线程执行
reactApplicationContext.runOnNativeModulesQueueThread(setupReactContextRunnable);
UiThreadUtil.runOnUiThread(maybeRecreateReactContextRunnable);
} catch (Exception e) {
mDevSupportManager.handleException(e);
}
}
});
// // 开启线程执行
mCreateReactContextThread.start();
}
在 runCreateReactContextOnNewThread 方法中,首先创建调用 createReactContext 创建 ReactApplicationContext,然后开启线程执行。重点来看 createReactContext 方法
CatalystInstance
/**
* @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
*/
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
// 创建 ReactApplicationContext 实例
final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
...
// 把各自的Module添加到对应的注册表中,processPackages方法通过遍历方式将在MainApplication 中 重写的ReactNativeHost的getPackages方法中的packages加入到注册表中
NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
// 构建CatalystInstanceImpl实例
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
.setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
// JS 执行通信类
.setJSExecutor(jsExecutor)
// 注册 Java 模块
.setRegistry(nativeModuleRegistry)
// 设置JSBundle 加载方式
.setJSBundleLoader(jsBundleLoader)
// 设置异常处理器
.setNativeModuleCallExceptionHandler(exceptionHandler);
final CatalystInstance catalystInstance;
// 创建 CatalystInstance 实例
try {
catalystInstance = catalystInstanceBuilder.build();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
if (mJSIModulePackage != null) {
catalystInstance.addJSIModules(mJSIModulePackage
.getJSIModules(reactContext, catalystInstance.getJavaScriptContextHolder()));
}
if (mBridgeIdleDebugListener != null) {
catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
}
// 调用 C++ 层代码,把 Java Registry 转换为Json,再由 C++ 层传送到 JS 层
if (Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
catalystInstance.setGlobalVariable("__RCTProfileIsProfiling", "true");
}
// 开始加载JSBundle
catalystInstance.runJSBundle();
// 关联 ReactContext 与 CatalystInstance
reactContext.initializeWithInstance(catalystInstance);
return reactContext;
}
createReactContext 方法比较长,主要做了3件事:
(1)构建 ReactApplicationContext
(2)注册 Packages 原生模块
(3)构建 CatalystInstance 实例
(4)通过 CatalystInstance 实例调用C++层代码逻辑
(5)调用 CatalystInstance 实例的 runJSBundle 方法加载 JSBundle
可以看到最终是通过 CatalystInstance 来加载 JSBundle 文件。继续来看 runJSBundle方法:
@Override
public void runJSBundle() {
Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
// 通过 JSBundleLoader 去执行加载,不同的加载方式 JSBundleLoader 实现方式不同
mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
synchronized (mJSCallsPendingInitLock) {
// 在 JS 线程上排队加载 bundle,此时可能还没有运行。 在这里设置它是安全的,因为它所关联的任何工作都将在加载完成之后的JS线程上排队执行。
mAcceptCalls = true;
for (PendingJSCall function : mJSCallsPendingInit) {
function.call(this);
}
mJSCallsPendingInit.clear();
mJSBundleHasLoaded = true;
}
// 这是在 JS 启动后注册的,因为它进行了 JS 调用
Systrace.registerListener(mTraceListener);
}
在 runJSBundle 方法中通过JSBundleLoader的 loadScript 方法去加载JSBundle。不同的加载方式 JSBundleLoader 实现方式不同。
JSBundleLoader
package com.facebook.react.bridge;
import android.content.Context;
import com.facebook.react.bridge.NativeDeltaClient;
import com.facebook.react.common.DebugServerException;
/**
* 一个存储 JS 包信息的类,允许 CatalystInstance 通过 ReactBridge 加载正确的包。
*/
public abstract class JSBundleLoader {
/**
* 建议将此加载程序用于应用程序的发布版本。 在这种情况下,应该使用本地JS执行程序。 将从本机代码中的资源读取JS包,以节省将大型字符串从java传递到本机内存。
*/
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;
}
};
}
/**
* 此加载程序从文件系统加载包。 将使用本机代码读取该包,以节省将大型字符串从java传递到本机内存。
*/
public static JSBundleLoader createFileLoader(final String fileName) {
return createFileLoader(fileName, fileName, false);
}
public static JSBundleLoader createFileLoader(
final String fileName,
final String assetUrl,
final boolean loadSynchronously) {
return new JSBundleLoader() {
@Override
public String loadScript(CatalystInstanceImpl instance) {
instance.loadScriptFromFile(fileName, assetUrl, loadSynchronously);
return fileName;
}
};
}
/**
* 从dev服务器重新加载bundle时使用此加载器。 在这种情况下,加载器期望预取JS包并存储在本地文件中。
* 我们这样做是为了避免在java和本机代码之间传递大字符串,并避免在java中分配内存以适应整个JS包。
* 为了使JS堆栈跟踪能够正常工作并允许源映射正确地对其进行符号化,需要提供正确的下载bundle的sourceURL。
*/
public static JSBundleLoader createCachedBundleFromNetworkLoader(
final String sourceURL,
final String cachedFileLocation) {
return new JSBundleLoader() {
@Override
public String loadScript(CatalystInstanceImpl instance) {
try {
instance.loadScriptFromFile(cachedFileLocation, sourceURL, false);
return sourceURL;
} catch (Exception e) {
throw DebugServerException.makeGeneric(e.getMessage(), e);
}
}
};
}
/**
* 此加载程序用于从开发服务器加载增量包。 我们将每个delta消息传递给加载器并在C ++中处理它。
* 将其作为字符串传递会由于内存副本而导致效率低下,这必须在后续处理中解决。
*/
public static JSBundleLoader createDeltaFromNetworkLoader(
final String sourceURL,
final NativeDeltaClient nativeDeltaClient) {
return new JSBundleLoader() {
@Override
public String loadScript(CatalystInstanceImpl instance) {
try {
instance.loadScriptFromDeltaBundle(sourceURL, nativeDeltaClient, false);
return sourceURL;
} catch (Exception e) {
throw DebugServerException.makeGeneric(e.getMessage(), e);
}
}
};
}
/**
* 启用代理调试时使用此加载程序。 在这种情况下,从设备获取捆绑包是没有意义的,因为远程执行器无论如何都必须这样做。
*/
public static JSBundleLoader createRemoteDebuggerBundleLoader(
final String proxySourceURL,
final String realSourceURL) {
return new JSBundleLoader() {
@Override
public String loadScript(CatalystInstanceImpl instance) {
instance.setSourceURLs(realSourceURL, proxySourceURL);
return realSourceURL;
}
};
}
/**
* 加载脚本,返回其加载的源的URL。
*/
public abstract String loadScript(CatalystInstanceImpl instance);
}
JSBundleLoader 类中提供了很多种 JSBundle 文件的加载方式。