RN4A运行环境的创建流程
前言
国内近年来对ReactNaitve讨论的火爆程度不言而喻,可能你都已经用了一段时间的RN4A了。不过你是否清楚RN4A是如何初始化一个环境?Js是何时通知Native渲染UI组件?从RN4A的环境初始化到ReactView呈现到UI的时候,RN4A都干了什么?别急,接下来就为你揭晓这些问题的答案。(PS: 本文源码分析基于当前最新ReactNative源码 v0.40.0-rc.4,限于作者水平有限,如果有错误和理解不当之处感谢指出。)
总体流程
为了接下来细节分析的时候你心中有个整体印象,我们先直接说下RN4A的框架初始化的总体流程。(PS:根据项目实际接入RN4A的方式不同,流程可能有所不同,这里只是官方使用的一种初始化流程。)
创建ReactRootView -> 创建ReactInstanceManager -> 创建ReactContext -> RN4A环境初始化完成 -> 通知Js渲染界面。
万物之始ReactRootView
俗话说”擒贼先擒王”,一般分析代码都会从源头走起。如果你查看RN4A的接入文档,就知道RN4A已经为我们封装好了ReactActiviy
类,只要通过继承它,你可以省掉RN4A与Activity之间的绝大部分逻辑交互,包括生命周期回调,以及发送消息通知Js渲染UI的操作等等。通过源码我们可以看到ReactActivity
中所有的逻辑都是由ReactActivityDelegate
类来代理,这是一个不错的设计方式,你可以轻松地把ReactActivityDelegate
集成到你自己的Activity中,自由定制RN4A环境的初始化方案。好的,有点扯远了,现在让我们来看下ReactActivityDelegate
中的关键方法吧。
protected void onCreate(Bundle savedInstanceState) {
//省略判断悬浮窗权限
...
if (mMainComponentName != null && !needsOverlayPermission) {
loadApp(mMainComponentName);
}
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
}
我们知道onCreate
方法就是Activity开始执行的地方,ReactActivityDelegate
的onCreate
方法自然也会在Activity的onCreate
中调用,这里可以看到,代码会判断当前App是否有显示悬浮层的权限,然后开始调用loadApp
方法,注意,这里就是RN4A官方方式加载的入口了。我们接下来继续跟踪下去:
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);
}
这段代码逻辑很简单,createRootView
方法创建了一个RN4A的根View(ReactRootView
),所有RN4A的View都会创建在ReactRootView
里,然后将ReactRootView
当成了Activity的内容布局,一般将ReactRootView
作为Activity的内容布局是比较省事的方式,当然,你也可以将它作为某个ViewGroup
的子View,只不过这种方式你很容易会踩到一些坑,比如你需要处理RN的View和原生View之间的事件冲突。好了,那我们接着看代码,我们看下关键的startReactApplication
方法,这里注意一下它的形参,第一个参数需要一个ReactInstanceManager
的实例,ReactNativeHost
的getReactInstanceManager
这个方法会创建一个ReactInstanceManager
实例,ReactInstanceManager
是RN4A的核心类,我们需要先来看下它是如何被初始化的。
protected ReactInstanceManager createReactInstanceManager() {
ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
.setApplication(mApplication)
.setJSMainModuleName(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setRedBoxHandler(getRedBoxHandler())
.setUIImplementationProvider(getUIImplementationProvider())
.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()));
}
return builder.build();
}
由于ReactInstanceManager
的参数很多,所以RN4A使用了建造者(Builder)模式,我们先简单看一下这些参数的意义:
-
application
– 这个就不说了; -
jsMainModuleName
– 在Js文件中设置的模块名称,通过该名称加载对应的Js组件; -
useDeveloperSupport
– 设置是否使用Dev调试工具; -
redBoxHandler
– 设置红框处理器,Js运行时的异常展示出来的红框; -
UIImplementationProvider
–UIManagerModule
的工人,负责处理从Js过来的跟UI操作相关的消息(View的创建、测量、更新等各种脏活); -
initialLifecycleState
–ReactInstanceManager
实例初始化时候的生命周期; -
reactPackage
– 自定义的ReactNative包: -
jsBundleFile
– 放在手机文件系统中的JsBundle的文件路径; -
bundleAssetName
– 内置在Assets目录下的JsBundle文件,如果设置了则不会走其他的JsBundle加载方式,需要注意在jsBundleFile
有值的情况下不会生效;
看完ReactInstanceManager
的创建,我们再返回到之前loadApp
方法处,继续跟踪ReactRootView
的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();
}
}
这里有两个重要的操作, mReactInstanceManager.createReactContextInBackground
方法完成了RN4A环境的创建和初始化,其中RN4A桥的创建和Js脚本的加载都是在这里面进行的;而另一个attachToReactInstanceManager
则将ReactRootView
实例与ReactInstanceManager
实例绑定起来,并从Native发送runApplication
消息到Js,Js收到消息便会开始执行相应的业务,这里需要注意的是,如果ReactInstanceManager
是第一次创建的话由于它的内部还没有创建好RN4A上下文实例(ReactContext
),Native此时并不会发送runApplication
消息给Js,而是将这个操作放在RN4A所有的环境创建完成之后才被执行,这里只是先提下,下面还会说到。
接着我们看下mReactInstanceManager.createReactContextInBackground
这个方法,由源码可知XReactInstanceManagerImpl
是ReactInstanceManager
的唯一实现类,所以你可以一直跟踪到下面的代码,
private void recreateReactContextInBackgroundInner() {
UiThreadUtil.assertOnUiThread();
if (mUseDeveloperSupport && mJSMainModuleName != null) {
final DeveloperSettings devSettings = mDevSupportManager.getDevSettings();
// If remote JS debugging is enabled, load from dev server.
if (mDevSupportManager.hasUpToDateJSBundleInCache() &&
!devSettings.isRemoteJSDebugEnabled()) {
// If there is a up-to-date bundle downloaded from server,
// with remote JS debugging disabled, always use that.
onJSBundleLoadedFromServer();
} else if (mBundleLoader == null) {
mDevSupportManager.handleReloadJS();
} else {
mDevSupportManager.isPackagerRunning(
new DevServerHelper.PackagerStatusCallback() {
@Override
public void onPackagerStatusFetched(final boolean packagerIsRunning) {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (packagerIsRunning) {
mDevSupportManager.handleReloadJS();
} else {
// If dev server is down, disable the remote JS debugging.
devSettings.setRemoteJSDebugEnabled(false);
recreateReactContextInBackgroundFromBundleLoader();
}
}
});
}
});
}
return;
}
recreateReactContextInBackgroundFromBundleLoader();
}
上面的代码还挺长的,其实只要关注recreateReactContextInBackgroundFromBundleLoader
方法就行了,不过这里还是需要简单说下这一处源码的逻辑。执行过程大概是这样的,如果你启用了RN4A的Dev支持,并且Js模块名(JsModuleName)不是空的,RN4A就会判断你本地是否有最新的JsBundler文件,如果有的话就直接读取本地的JsBundle文件,否则会从你的LocalServer中加载JsBundle文件,这里为了分析源码方便,我们先假设RN4A是处在Release环境中执行的,所以我们就直接走recreateReactContextInBackgroundFromBundleLoader
代码, 在RN4A源码中经过几处跳转之后我们就会看到下面这段逻辑。
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.
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;
}
}
这是RN4A的一段相对重要的逻辑,我们可以看到RN4A使用了Android的异步任务(ReactContextInitAsyncTask)来执行初始化操作,阅读源码可以知道RN4A先判断当前有没有ReactContextInitAsyncTask在进行,如果有的话,RN4A会将本次的初始化参数存放到initParams
全局变量,等ReactContextInitAsyncTask初始化完成之后再去重新执行初始化操作,如果当前没有ReactContextInitAsyncTask任务在执行,则直接新建一个ReactContextInitAsyncTask任务并开始执行初始化操作。我们接着跟进源码:
/*
* Task class responsible for (re)creating react context in the background. These tasks can only
* be executing one at time, see {@link #recreateReactContextInBackground()}.
*/
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 {
mReactContextInitAsyncTask = null;
}
// Handle enqueued request to re-initialize react context.
if (mPendingReactContextInitParams != null) {
recreateReactContextInBackground(
mPendingReactContextInitParams.getJsExecutorFactory(),
mPendingReactContextInitParams.getJsBundleLoader());
mPendingReactContextInitParams = null;
}
}
@Override
protected void onCancelled(Result<ReactApplicationContext> reactApplicationContextResult) {
//省略一些代码
}
}
这也算是RN4A的核心一部分了,通过源码我们可以知道RN4A会在任务开始时候卸载掉旧的RN4A上下文实例(ReactContext
是一个RN4A的上下文环境,持有了UI、Js和Native三线程,并维持了一个和Js通信的桥等),然后在异步任务线程池中RN4A会开始创建一个新的RN4A上下文实例(ReactContext
),并在执行结束之后设置这个RN4A上下文实例(ReactContext
);当RN4A执行完成之后如果发现有initParams
(上文提到的参数),就会重新开始执行ReactContextInitAsyncTask任务。接下来我们去看下createReactContext
方法都在做啥。
/**
* @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
*/
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
mSourceUrl = jsBundleLoader.getSourceUrl();
List<ModuleSpec> moduleSpecs = new ArrayList<>();
Map<Class, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder();
final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
if (mUseDeveloperSupport) {
reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
}
try {
CoreModulesPackage coreModulesPackage =
new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
processPackage(
coreModulesPackage,
reactContext,
moduleSpecs,
reactModuleInfoMap,
jsModulesBuilder);
} finally {
//省略一些代码
}
for (ReactPackage reactPackage : mPackages) {
try {
processPackage(
reactPackage,
reactContext,
moduleSpecs,
reactModuleInfoMap,
jsModulesBuilder);
} finally {
//省略一些代码
}
}
NativeModuleRegistry nativeModuleRegistry;
try {
nativeModuleRegistry = new NativeModuleRegistry(moduleSpecs, reactModuleInfoMap);
} finally {
//省略一些代码
}
NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
? mNativeModuleCallExceptionHandler
: mDevSupportManager;
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
.setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
.setJSExecutor(jsExecutor)
.setRegistry(nativeModuleRegistry)
.setJSModuleRegistry(jsModulesBuilder.build())
.setJSBundleLoader(jsBundleLoader)
.setNativeModuleCallExceptionHandler(exceptionHandler);
final CatalystInstance catalystInstance;
try {
catalystInstance = catalystInstanceBuilder.build();
} finally {
//省略一些代码
}
if (mBridgeIdleDebugListener != null) {
catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
}
reactContext.initializeWithInstance(catalystInstance);
catalystInstance.runJSBundle();
return reactContext;
}
上面的代码看着有点长,不过逻辑也就这么几步:
- 生成一个RN4A上下文实例(
ReactContext
),并在开启Dev模式情况下设置一个Native异常处理器,用于捕获三个线程(UI、Js和Native)中发生的异常; - 处理RN包,包括核心包(CorePackage)以及注册的自定义包(如官方提供的MainPackage),NativeModule信息被存放到
moduleSpecs
和reactModuleInfoMap
中,而JsModule则被放到jsModulesBuilder
中; - 通过桥构造器(
CatalystInstanceImpl.Builder
)构建出一个CatalystInstance
实例,它在RN4A中负责中掌管Js和Native之间的通信; - 将创建好的桥(CatalystInstance)放到RN4A上下文(
ReactContext
)中并进行初始化,这个过程实际上只是让RN4A上下文(ReactContext
)持有三个关键线程管理实例(UI、JS和Native),RN4A的全部工作依赖这三条线程之间的相互协作; - 最后就是运行在桥实例(
CatalystInstance
)中的JsBundle了,这里会从Jni层去调用Js引擎解释执行Js代码,关于RN4A的Jni层的逻辑,限于篇幅留待之后分析。
接下来让我们从ReactContextInitAsyncTask类的onPostExecute
方法接着看,进入setupReactContext
方法。
private void setupReactContext(ReactApplicationContext reactContext) {
UiThreadUtil.assertOnUiThread();
Assertions.assertCondition(mCurrentReactContext == null);
mCurrentReactContext = Assertions.assertNotNull(reactContext);
CatalystInstance catalystInstance =
Assertions.assertNotNull(reactContext.getCatalystInstance());
catalystInstance.initialize();
mDevSupportManager.onNewReactContextCreated(reactContext);
mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
moveReactContextToCurrentLifecycleState();
for (ReactRootView rootView : mAttachedRootViews) {
attachMeasuredRootViewToInstance(rootView, catalystInstance);
}
ReactInstanceEventListener[] listeners =
new ReactInstanceEventListener[mReactInstanceEventListeners.size()];
listeners = mReactInstanceEventListeners.toArray(listeners);
for (ReactInstanceEventListener listener : listeners) {
listener.onReactContextInitialized(reactContext);
}
}
这里RN4A会去执行一次桥CatalystInstance
的初始化逻辑,并把初始化完成的消息发送出去,比如通知注册在桥的Module执行初始化的一些操作,告诉绑定的ReactRootView
可以通过attachMeasuredRootViewToInstance
方法通知Js”真正”执行业务逻辑了,之后Js就会开始通过一系列的消息指挥Native渲染展示UI等等。
结语
至此,RN4A从环境初始化到RN界面展示出来所经过的一个流程我们已经走了一遍,这篇文章只是分析RN4A框架的开篇,Native和Js之间的通信方式,Js Dom的解析渲染等等,会在后续的文章中分析,敬请期待。