diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 5ab2f725a..4678fd9f4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -91,6 +91,7 @@ public class ReactInstanceManager { private @Nullable DefaultHardwareBackBtnHandler mDefaultBackButtonImpl; private String mSourceUrl; private @Nullable Activity mCurrentActivity; + private volatile boolean mHasStartedCreatingInitialContext = false; private final ReactInstanceDevCommandsHandler mDevInterface = new ReactInstanceDevCommandsHandler() { @@ -235,11 +236,41 @@ public class ReactInstanceManager { /** * Trigger react context initialization asynchronously in a background async task. This enables * applications to pre-load the application JS, and execute global code before - * {@link ReactRootView} is available and measured. + * {@link ReactRootView} is available and measured. This should only be called the first time the + * application is set up, which is enforced to keep developers from accidentally creating their + * application multiple times without realizing it. * * Called from UI thread. */ public void createReactContextInBackground() { + Assertions.assertCondition( + !mHasStartedCreatingInitialContext, + "createReactContextInBackground should only be called when creating the react " + + "application for the first time. When reloading JS, e.g. from a new file, explicitly" + + "use recreateReactContextInBackground"); + + mHasStartedCreatingInitialContext = true; + recreateReactContextInBackgroundInner(); + } + + /** + * Recreate the react application and context. This should be called if configuration has + * changed or the developer has requested the app to be reloaded. It should only be called after + * an initial call to createReactContextInBackground. + * + * Called from UI thread. + */ + public void recreateReactContextInBackground() { + Assertions.assertCondition( + mHasStartedCreatingInitialContext, + "recreateReactContextInBackground should only be called after the initial " + + "createReactContextInBackground call."); + recreateReactContextInBackgroundInner(); + } + + private void recreateReactContextInBackgroundInner() { + UiThreadUtil.assertOnUiThread(); + if (mUseDeveloperSupport && mJSMainModuleName != null) { if (mDevSupportManager.hasUpToDateJSBundleInCache()) { // If there is a up-to-date bundle downloaded from server, always use that @@ -277,6 +308,14 @@ public class ReactInstanceManager { JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile)); } + /** + * @return whether createReactContextInBackground has been called. Will return false after + * onDestroy until a new initial context has been created. + */ + public boolean hasStartedCreatingInitialContext() { + return mHasStartedCreatingInitialContext; + } + /** * This method will give JS the opportunity to consume the back button event. If JS does not * consume the event, mDefaultBackButtonImpl will be invoked at the end of the round trip to JS. @@ -362,6 +401,8 @@ public class ReactInstanceManager { if (mCurrentReactContext != null) { mCurrentReactContext.onDestroy(); + mCurrentReactContext = null; + mHasStartedCreatingInitialContext = false; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 5e21647df..0a4eadc00 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -295,6 +295,8 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView 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 @@ -307,6 +309,10 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView 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 // mAttachScheduled flag, which will make this view startReactApplication itself to instance // manager once onMeasure is called.