共计 13157 个字符,预计需要花费 33 分钟才能阅读完成。
如何进行 ReactNative For Android 框架启动核心路径剖析,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
前面给大家分析过 ReactNative For Android (RN4A) 的通信机制,这次我们从源码出发,分析下 RN4A 的启动过程。启动过程基于通信机制,涉及通信机制原理大家可以查看前一篇文章。
上面是 2016 React.js Conf FB 工程师分享的 RN 启动时序图,整个过程比较清晰,先启动终端运行时,随后由终端上下文去启动 JS 的运行时,进而布局,最后再由终端进行渲染,最后将 View 添加到 RootView 上。那接下来,我们先理解几个概念,方便后续我们对整个启动过程的理解。
模块:
模块即暴露给调用方的 API 集合,在 RN4A 存在两种模块。
一种是 Native 层暴露给 Js 层的 API 集合模块,即 NativeModule,如 ToastModule,DialogModule, 或是创建 View 的 UIManagerModule。业务方可以通过实现 NativeModule 自定义模块,通过重写 getName 将模块名暴露给 Js 层,通过注解的方式将 API 暴露给 Js 层调用。
另一种是 Js 层暴露给 Java 层的 API 集合模块,即 JavascriptModule,如 DeviceEventEmitter,AppRegistry 等。业务方可以通过继承 JavaScriptModule 接口自定义接口模块,申明与 Js 层相应的方法即可。
无论是 NativeModule 还是 JavascriptModule,在 Js 层存在与之相互映射同名的 Module,Js 层通过 require 引用 Module。
模块注册表:
各模块信息统一收集到模块注册表。同样,在 RN4A 中存在两种模块注册表,一是由集合所有 Java 层模块接口信息的 NativeModuleRegistry,另一种是集合所有 Js 层模块接口信息的 JavascriptModuleRegistry。在启动 RN4A 后,终端将注册表信息存入与前端互通的全局变量__fbBatchedBridgeConfig 中,使得 Js 层与 Java 层存在同样的模块注册表。
正如上面 FB 攻城狮提出的时序图,从终端启动,入口是 ReactRootView.startReactApplication,在构造 JavaScriptExecutor JSBundleLoader 后,进而通过 ReactContextInitAsycnTask 去创建 ReactContext,这部分主要创建了 NativeModules,JavaScriptModule 及其对的注册表,负责 Js 与 Java 通信的高层接口 CatalystInstance 等。在创建完 ReactContext 后,通过 CatalystInstance 获取 AppRegistry 并调用其 runApplication 启动 Js Application。整体流程如下:
接下来进入正题,从源码来分析 RN4A 的启动(为阅读方便,源码适当裁剪)
ReactInstanceManager createReactContextInBackground,通过 AysncTask 初始化 ReactNative 上下文。mJSModuleName 是与前端约定好所要启动的 JS Application Name。mLauncahOptions 是终端启动前端 Application 可选的传入的参数。
/**
* ReactRootView.java
*/
public void startReactApplication(
ReactInstanceManager reactInstanceManager,
String moduleName,
@Nullable Bundle launchOptions) { UiThreadUtil.assertOnUiThread();
mReactInstanceManager = reactInstanceManager;
mJSModuleName = moduleName;
mLaunchOptions = launchOptions;
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) { mReactInstanceManager.createReactContextInBackground();
}
if (mWasMeasured mIsAttachedToWindow) { mReactInstanceManager.attachMeasuredRootView(this);
mIsAttachedToInstance = true;
getViewTreeObserver().addOnGlobalLayoutListener(getKeyboardListener());
} else {
mAttachScheduled = true;
}
`
createReactContextInBackground 最终调用到 recreateReactContextInBackgroundFromBundleFile。这里会创建两个 Key Obj : JSCJavaScriptExecutor JSBundleLoader。
JSCJavaScriptExecutor 继承自 JavaScriptExecutor,在 JSCJavaScriptExecutor.class 加载会加载 ReactNative 的 SO,并且,在初始 JSCJavaScriptExecutor 时会调用 initialze 去初始 C ++ 层 ReactNative 与 JSC 的通信框架等。
JSBundleLoader 缓存了 JsBundle 的信息,封装了上层加载 JsBundle 相关接口,CatalystInstance 通过其间接调用 ReactBridge 去加载文件。
/**
* ReactInstanceManagerImpl.java
*/
private void recreateReactContextInBackgroundFromBundleFile() {
recreateReactContextInBackground( new JSCJavaScriptExecutor.Factory(),
JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile));
}
创建完 JSCJavaScriptExecutor JSBundleLoader 后,execute ReactContextInitAsyncTask 继续初始化 ReactContext。
/**
* ReactInstanceManagerImpl.java
*/
private void recreateReactContextInBackground(
JavaScriptExecutor.Factory jsExecutorFactory,
JSBundleLoader jsBundleLoader) {
ReactContextInitParams initParams =
new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
if (!mIsContextInitAsyncTaskRunning) { ReactContextInitAsyncTask initTask = new ReactContextInitAsyncTask();
initTask.execute(initParams);
mIsContextInitAsyncTaskRunning = true;
} else {
mPendingReactContextInitParams = initParams;
}
}
ReactContextInitAsyncTask 为创建 ReactContext 的核心类,在执行初始化前会销毁先前的上下文,保证只存在一个上下文。随后,调用 createReactContext 进一步创建 ReactContext。在创建完 React Context 后会调用 setUpReactContext,进而通知 DevSupportManager 更新上下文,更新生命周期,将 ReactRootView 做为 Root View 传递给 UIManagerModule,调用 AppRegistry 的 runApplication 去启动 Js Application 等。
/**
* ReactInstanceManagerImpl$ReactContextInitAsynTask.java
*/
private final class ReactContextInitAsyncTask extends
AsyncTask ReactContextInitParams, Void, Result ReactApplicationContext {
@Override
protected void onPreExecute() { if (mCurrentReactContext != null) { tearDownReactContext(mCurrentReactContext);
mCurrentReactContext = null;
}
}
@Override
protected Result ReactApplicationContext doInBackground(ReactContextInitParams... params) { Assertions.assertCondition(params != null params.length 0 params[0] != null);
try { JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create();
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);
}
}
@Override
protected void onPostExecute(Result ReactApplicationContext result) {
try { setupReactContext(result.get());
} catch (Exception e) { mDevSupportManager.handleException(e);
} finally {
mIsContextInitAsyncTaskRunning = false;
}
// Handle enqueued request to re-initialize react context.
if (mPendingReactContextInitParams != null) {
recreateReactContextInBackground( mPendingReactContextInitParams.getJsExecutorFactory(),
mPendingReactContextInitParams.getJsBundleLoader());
mPendingReactContextInitParams = null;
}
}
}
在 CreateReactContext 中,主要有以下 5 个 key path:
通过 Builder 构建上文概念讲过的 NativeModuleRegistry 及 JavaScriptModuleConfig;
创建 ReactApplicationContext。ReactApplicationContext 继承自 ContextWrapper,主要缓存了 Application Context,Activity Context,ReactNative 处理消息的三个 thread(下篇讲述),还有就是全局控制 JS 调用导致 Native Module Crash 的 NativeModuleCallExceptionHandler,在初始化 ReactInstanceManager 的时候传入,并且要关闭 DeveloperSupport 后才可以启用,假如不传,则默认交由 DevSupportManger 去处理;
创建 ReactPackage。ReactPackage 主要通过 createNativeModules、createJSModules 和 createViewManagers 等 API 去创建本地模块,JS 模块及视图组件等。ReactPackage 分为 framework 的 CoreModulesPackage 和业务方可选的基础 MainReactPackage,CoreModulesPackage 封装了大部分通信,调试核心类,如 UIManagerModule,这个负责控制 Js 层 Dom 到 Native View 的核心类;
创建 CatalystInstance。CatalystInstance 并不直接面向开发者,开发者通 ReactInstanceManger 间接操作 CatalystInstance。CatalystInstance 持有对 ReactBridge 的引用,主要通过 ReactBridge 这个 JNI 类去实现 Java 层与 Js 层的通信,ReactBridge 由 CatalystInstance 的 Constructor 创建。同时初始化的时候调用了 ReactQueueConfigurationSpec.createDefault 创建了 ReactNative 通信的两个线程 JsQueueThread NativeModulesQueueThread;
调用 reactContext.initializeWithInstance 进一步将创建完的 CatalystInstance 及线程等缓存在 ReactContext 中;
调用 catalystInstance.runJSBundle 加载解析 Jsbundle;
/**
* ReactInstanceManagerImpl.java
*
* @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
*/
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) { 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);
}
CoreModulesPackage coreModulesPackage =
new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
for (ReactPackage reactPackage : mPackages) { processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
}
nativeModuleRegistry = nativeRegistryBuilder.build();
javaScriptModulesConfig = jsModulesBuilder.build();
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);
CatalystInstance catalystInstance= catalystInstanceBuilder.build();
if (mBridgeIdleDebugListener != null) { catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
}
reactContext.initializeWithInstance(catalystInstance);
catalystInstance.runJSBundle();
return reactContext;
}
ReactBridge 由 CatalystInstance 的 Constructor 创建。
/**
* CatalystInstanceImpl.java
*/
private CatalystInstanceImpl(
final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
final JavaScriptExecutor jsExecutor,
final NativeModuleRegistry registry,
final JavaScriptModulesConfig jsModulesConfig,
final JSBundleLoader jsBundleLoader,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
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 Callable ReactBridge () {
@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);
}
}
ReactBridge 将注册表信息存入与前端互通的全局变量 __fbBatchedBridgeConfig 中,使得 Js 层与 Java 层存在同样的模块注册表。
/**
* CatalystInstanceImpl.java
*/
private ReactBridge initializeBridge(
JavaScriptExecutor jsExecutor,
JavaScriptModulesConfig jsModulesConfig) { ReactBridge bridge = new ReactBridge(jsExecutor, new NativeModulesReactCallback(),
mReactQueueConfiguration.getNativeModulesQueueThread());
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, setBatchedBridgeConfig
bridge.setGlobalVariable(
__fbBatchedBridgeConfig ,
buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
bridge.setGlobalVariable(
__RCTProfileIsProfiling ,
return bridge;
}
调用 catalystInstance.runJSBundle 加载解析 Jsbundle。假如在解析过程中出现 Exception,统一交给 NativeModuleCallExceptionHandler 处理,建议开发者设置自己的 NativeModuleCallExceptionHandler,可以归避部分 Crash(SyntaxError: Unexpected token‘‘或 SyntaxError: Unexpected end of script)。
/**
* CatalystInstanceImpl.java
*/
public void runJSBundle() {
try { mJSBundleHasLoaded = mReactQueueConfiguration.getJSQueueThread().callOnQueue( new Callable Boolean () {
@Override
public Boolean call() throws Exception { incrementPendingJSCalls();
try { mJSBundleLoader.loadScript(mBridge);
Systrace.registerListener(mTraceListener);
} catch (JSExecutionException e) { mNativeModuleCallExceptionHandler.handleException(e);
} finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
return true;
}
}).get();
} catch (Exception t) { throw new RuntimeException(t);
}
}
在创建完 React Context 后会执行 ReactContextInitAsyncTask 的 onPostExecute,从而调用 setUpReactContext,会将 ReactRootView 做为 Root View 传递给 UIManagerModule,此后 Js 通过 UIManager 创建的 View 都会 add 到该 View 上。
/**
* ReactInstanceManagerImpl.java
*/
@Override
public void attachMeasuredRootView(ReactRootView rootView) { UiThreadUtil.assertOnUiThread();
if(mIsNeedDetachView){
Log.d(ReactConstants.QZONE_REACT_SRC_TAG, attachMeasuredRootView do add
mAttachedRootViews.add(rootView);
// If react context is being created in the background, JS application will be started
// automatically when creation completes, as root view is part of the attached root view list.
if (!mIsContextInitAsyncTaskRunning mCurrentReactContext != null) { attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance());
}
}else{
Log.d(ReactConstants.QZONE_REACT_SRC_TAG, attachMeasuredRootView do nothing
}
}
在绑定完 RootView 后,通过 CatalystInstance 获取 AppRegistry 这个 JSModule 后,进一步调用 runApplication 启动 Js Application。
/**
* ReactInstanceManagerImpl.java
*/
private void attachMeasuredRootViewToInstance(
ReactRootView rootView,
CatalystInstance catalystInstance) { 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);
}
ReactNative 中 Java 与 Js 通信不再赘述。至此,启动 Js 层 AppRegistry 的 runApplication 启动 Js Application。
/**
* AppRegistry.js
runApplication: function(appKey: string, appParameters: any): void {
console.log(
Running application + appKey + with appParams: +
JSON.stringify(appParameters) + . +
__DEV__ === + String(__DEV__) +
, development-level warning are + (__DEV__ ? ON : OFF) +
, performance optimizations are + (__DEV__ ? OFF : ON)
);
invariant( runnables[appKey] runnables[appKey].run,
Application + appKey + has not been registered. This +
is either due to a require() error during initialization +
or failure to call AppRegistry.registerComponent.
);
runnables[appKey].run(appParameters);
},
看完上述内容,你们掌握如何进行 ReactNative For Android 框架启动核心路径剖析的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注丸趣 TV 行业资讯频道,感谢各位的阅读!