From 530ee3eeac40d43c6517cc47dfa8745222cf5956 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Wed, 18 Nov 2015 11:19:31 -0800 Subject: [PATCH] Initialize JS executor and context at loadApp time Summary: Automatically starts loading the RN instance as soon as loadApp is called instead of waiting for the UI to be constructed and attached to the window. This saves ~75ms of cold start time on GN 2011 phones. Also, updates the contract for createReactContextInBackground such that it is only called once (so that people don't accidentally initialize more than once). See http://imgur.com/a/d7qVm for systrace visualization. public Reviewed By: kmagiera Differential Revision: D2652279 fb-gh-sync-id: 6e7b1fa5dea091af0d568834e11ed23eb1a7860e --- .../facebook/react/ReactInstanceManager.java | 43 ++++++++++++++++++- .../com/facebook/react/ReactRootView.java | 6 +++ 2 files changed, 48 insertions(+), 1 deletion(-) 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.