diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java index fbf8925cba132b..a6ab8fd65bdc04 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java @@ -40,7 +40,6 @@ void callFunction( */ void destroy(); boolean isDestroyed(); - boolean isAcceptingCalls(); /** * Initialize all the native modules diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java index 35ec76084b87fe..c43a23ac9135d0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -40,7 +40,8 @@ public class ReactContext extends ContextWrapper { private static final String EARLY_JS_ACCESS_EXCEPTION_MESSAGE = "Tried to access a JS module before the React instance was fully set up. Calls to " + - "ReactContext#getJSModule should be protected by ReactContext#hasActiveCatalystInstance()."; + "ReactContext#getJSModule should only happen once initialize() has been called on your " + + "native module."; private final CopyOnWriteArraySet mLifecycleEventListeners = new CopyOnWriteArraySet<>(); @@ -143,7 +144,7 @@ public CatalystInstance getCatalystInstance() { } public boolean hasActiveCatalystInstance() { - return mCatalystInstance != null && mCatalystInstance.isAcceptingCalls(); + return mCatalystInstance != null && !mCatalystInstance.isDestroyed(); } public LifecycleState getLifecycleState() { diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java index fad0ff90875382..7150ce21b6351f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java @@ -12,6 +12,7 @@ import javax.annotation.Nullable; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; @@ -57,6 +58,25 @@ public class CatalystInstanceImpl implements CatalystInstance { private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1); + private static class PendingJSCall { + + public ExecutorToken mExecutorToken; + public String mModule; + public String mMethod; + public NativeArray mArguments; + + public PendingJSCall( + ExecutorToken executorToken, + String module, + String method, + NativeArray arguments) { + mExecutorToken = executorToken; + mModule = module; + mMethod = method; + mArguments = arguments; + } + } + // Access from any thread private final ReactQueueConfigurationImpl mReactQueueConfiguration; private final CopyOnWriteArrayList mBridgeIdleListeners; @@ -67,6 +87,8 @@ public class CatalystInstanceImpl implements CatalystInstance { private final TraceListener mTraceListener; private final JavaScriptModuleRegistry mJSModuleRegistry; private final JSBundleLoader mJSBundleLoader; + private final ArrayList mJSCallsPendingInit = new ArrayList(); + private final Object mJSCallsPendingInitLock = new Object(); private ExecutorToken mMainExecutorToken; private final NativeModuleRegistry mJavaRegistry; @@ -168,10 +190,20 @@ public void runJSBundle() { mJSBundleHasLoaded = true; // incrementPendingJSCalls(); mJSBundleLoader.loadScript(CatalystInstanceImpl.this); - // Loading the bundle is queued on the JS thread, but may not have - // run yet. It's save to set this here, though, since any work it - // gates will be queued on the JS thread behind the load. - mAcceptCalls = true; + + synchronized (mJSCallsPendingInitLock) { + // Loading the bundle is queued on the JS thread, but may not have + // run yet. It's save to set this here, though, since any work it + // gates will be queued on the JS thread behind the load. + mAcceptCalls = true; + + for (PendingJSCall call : mJSCallsPendingInit) { + callJSFunction(call.mExecutorToken, call.mModule, call.mMethod, call.mArguments); + } + mJSCallsPendingInit.clear(); + } + + // This is registered after JS starts since it makes a JS call Systrace.registerListener(mTraceListener); } @@ -193,7 +225,13 @@ public void callFunction( return; } if (!mAcceptCalls) { - throw new RuntimeException("Attempt to call JS function before JS bundle is loaded."); + // Most of the time the instance is initialized and we don't need to acquire the lock + synchronized (mJSCallsPendingInitLock) { + if (!mAcceptCalls) { + mJSCallsPendingInit.add(new PendingJSCall(executorToken, module, method, arguments)); + return; + } + } } callJSFunction(executorToken, module, method, arguments); @@ -244,11 +282,6 @@ public boolean isDestroyed() { return mDestroyed; } - @Override - public boolean isAcceptingCalls() { - return !mDestroyed && mAcceptCalls; - } - /** * Initialize all the native modules */