diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index d417e74d1..b120049e9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -55,6 +55,7 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactMarker; import com.facebook.react.bridge.ReactMarkerConstants; import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.bridge.NativeArray; import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec; import com.facebook.react.common.LifecycleState; import com.facebook.react.common.ReactConstants; @@ -105,6 +106,8 @@ import javax.annotation.Nullable; @ThreadSafe public class ReactInstanceManager { + private static final String TAG = ReactInstanceManager.class.getSimpleName(); + /** * Listener interface for react instance events. */ @@ -127,6 +130,7 @@ public class ReactInstanceManager { private final @Nullable JSBundleLoader mBundleLoader; /* path to JS bundle on file system */ private final @Nullable String mJSMainModulePath; /* path to JS bundle root on packager server */ private final List mPackages; + private final List mInitFunctions; private final DevSupportManager mDevSupportManager; private final boolean mUseDeveloperSupport; private final @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener; @@ -232,6 +236,7 @@ public class ReactInstanceManager { mBundleLoader = bundleLoader; mJSMainModulePath = jsMainModulePath; mPackages = new ArrayList<>(); + mInitFunctions = new ArrayList<>(); mUseDeveloperSupport = useDeveloperSupport; mDevSupportManager = DevSupportManagerFactory.create( applicationContext, @@ -351,11 +356,33 @@ public class ReactInstanceManager { } /** - * 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. + * If the JavaScript bundle for this app requires initialization as part of bridge start up, + * register a function using its @param module and @param method and optional arguments. + */ + public void registerInitFunction(String module, String method, @Nullable NativeArray arguments) { + CatalystInstanceImpl.PendingJSCall init = + new CatalystInstanceImpl.PendingJSCall(module, method, arguments); + synchronized (this) { + mInitFunctions.add(init); + } + ReactContext context = getCurrentReactContext(); + CatalystInstance catalystInstance = context == null ? null : context.getCatalystInstance(); + if (catalystInstance == null) { + return; + } else { + // CatalystInstance is only visible after running jsBundle, so these will be put on the native + // JS queue + // TODO T20546472 remove cast when catalystInstance and InstanceImpl are renamed/merged + ((CatalystInstanceImpl) catalystInstance).callFunction(init); + } + } + + /** + * 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. + *

Called from UI thread. */ @ThreadConfined(UI) public void recreateReactContextInBackground() { @@ -995,10 +1022,19 @@ public class ReactInstanceManager { if (Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JSC_CALLS)) { catalystInstance.setGlobalVariable("__RCTProfileIsProfiling", "true"); } + catalystInstance.runJSBundle(); + + // Transitions functions in the minitFunctions list to catalystInstance, to run after the bundle + // TODO T20546472 + if (!mInitFunctions.isEmpty()) { + for (CatalystInstanceImpl.PendingJSCall function : mInitFunctions) { + ((CatalystInstanceImpl) catalystInstance).callFunction(function); + } + } + ReactMarker.logMarker(ReactMarkerConstants.PRE_RUN_JS_BUNDLE_START); reactContext.initializeWithInstance(catalystInstance); - ReactMarker.logMarker(ReactMarkerConstants.PRE_RUN_JS_BUNDLE_START); - catalystInstance.runJSBundle(); + return reactContext; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index cede3abbe..c372d16e3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -43,20 +43,27 @@ public class CatalystInstanceImpl implements CatalystInstance { private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1); - private static class PendingJSCall { + public static class PendingJSCall { public String mModule; public String mMethod; - public NativeArray mArguments; + public @Nullable NativeArray mArguments; - public PendingJSCall( - String module, - String method, - NativeArray arguments) { + public PendingJSCall(String module, String method, @Nullable NativeArray arguments) { mModule = module; mMethod = method; mArguments = arguments; } + + void call(CatalystInstanceImpl catalystInstance) { + NativeArray arguments = mArguments != null ? mArguments : new WritableNativeArray(); + catalystInstance.jniCallJSFunction(mModule, mMethod, arguments); + } + + public String toString() { + return mModule + "." + mMethod + "(" + + (mArguments == null ? "" : mArguments.toString()) + ")"; + } } // Access from any thread @@ -216,7 +223,6 @@ public class CatalystInstanceImpl implements CatalystInstance { public void runJSBundle() { Log.d(ReactConstants.TAG, "CatalystInstanceImpl.runJSBundle()"); Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!"); - // incrementPendingJSCalls(); mJSBundleLoader.loadScript(CatalystInstanceImpl.this); @@ -227,8 +233,8 @@ public class CatalystInstanceImpl implements CatalystInstance { // gates will be queued on the JS thread behind the load. mAcceptCalls = true; - for (PendingJSCall call : mJSCallsPendingInit) { - jniCallJSFunction(call.mModule, call.mMethod, call.mArguments); + for (PendingJSCall function : mJSCallsPendingInit) { + function.call(this); } mJSCallsPendingInit.clear(); mJSBundleHasLoaded = true; @@ -260,8 +266,12 @@ public class CatalystInstanceImpl implements CatalystInstance { final String module, final String method, final NativeArray arguments) { + callFunction(new PendingJSCall(module, method, arguments)); + } + + public void callFunction(PendingJSCall function) { if (mDestroyed) { - final String call = module + "." + method + "(" + arguments.toString() + ")"; + final String call = function.toString(); FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed: " + call); return; } @@ -269,13 +279,12 @@ public class CatalystInstanceImpl implements CatalystInstance { // 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(module, method, arguments)); + mJSCallsPendingInit.add(function); return; } } } - - jniCallJSFunction(module, method, arguments); + function.call(this); } private native void jniCallJSCallback(int callbackID, NativeArray arguments); diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java index c9c4abbb1..f1d03b44d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java @@ -186,14 +186,13 @@ public class StackTraceHelper { Matcher matcher = STACK_FRAME_PATTERN.matcher(stackTrace[i]); if (!matcher.find()) { throw new IllegalArgumentException( - "Unexpected stack frame format: " + stackTrace[i]); + "Unexpected stack frame format: " + stackTrace[i]); } - result[i] = new StackFrameImpl( - matcher.group(2), - matcher.group(1) == null ? "(unknown)" : matcher.group(1), - Integer.parseInt(matcher.group(3)), - Integer.parseInt(matcher.group(4))); + matcher.group(2), + matcher.group(1) == null ? "(unknown)" : matcher.group(1), + Integer.parseInt(matcher.group(3)), + Integer.parseInt(matcher.group(4))); } } return result; diff --git a/ReactAndroid/src/main/java/com/facebook/react/module/processing/ReactModuleSpecProcessor.java b/ReactAndroid/src/main/java/com/facebook/react/module/processing/ReactModuleSpecProcessor.java index 73e315dfa..cc08d9537 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/module/processing/ReactModuleSpecProcessor.java +++ b/ReactAndroid/src/main/java/com/facebook/react/module/processing/ReactModuleSpecProcessor.java @@ -88,6 +88,7 @@ public class ReactModuleSpecProcessor extends AbstractProcessor { TypeElement typeElement = (TypeElement) reactModuleListElement; ReactModuleList reactModuleList = typeElement.getAnnotation(ReactModuleList.class); + if (reactModuleList == null) { continue; }