WebWorkers: Add ExecutorToken to route native module calls to/from workers

Summary:To support native modules in web workers, native modules need to have an notion of the JS executor/thread that called into them in order to respond via Callback or JS module call to the right executor on the right thread.

ExecutorToken is an object that only serves as a token that can be used to identify an executor/message queue thread in the bridge. It doesn't expose any methods. Native modules Callback objects automatically have this ExecutionContext attached -- JSModule calls for modules that support workers will need to supply an appropriate ExecutorToken when retrieving the JSModule implementation from the ReactContext.

Reviewed By: mhorowitz

Differential Revision: D2965458

fb-gh-sync-id: 6e354d4df8536d40b12d02bd055f6d06b4ca595d
shipit-source-id: 6e354d4df8536d40b12d02bd055f6d06b4ca595d
This commit is contained in:
Andy Street
2016-03-01 07:57:11 -08:00
committed by Facebook Github Bot 4
parent f67fa82008
commit bd95b22844
26 changed files with 614 additions and 116 deletions

View File

@@ -57,14 +57,14 @@ public abstract class BaseJavaModule implements NativeModule {
}
public abstract @Nullable T extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex);
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex);
}
static final private ArgumentExtractor<Boolean> ARGUMENT_EXTRACTOR_BOOLEAN =
new ArgumentExtractor<Boolean>() {
@Override
public Boolean extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return jsArguments.getBoolean(atIndex);
}
};
@@ -73,7 +73,7 @@ public abstract class BaseJavaModule implements NativeModule {
new ArgumentExtractor<Double>() {
@Override
public Double extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return jsArguments.getDouble(atIndex);
}
};
@@ -82,7 +82,7 @@ public abstract class BaseJavaModule implements NativeModule {
new ArgumentExtractor<Float>() {
@Override
public Float extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return (float) jsArguments.getDouble(atIndex);
}
};
@@ -91,7 +91,7 @@ public abstract class BaseJavaModule implements NativeModule {
new ArgumentExtractor<Integer>() {
@Override
public Integer extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return (int) jsArguments.getDouble(atIndex);
}
};
@@ -100,7 +100,7 @@ public abstract class BaseJavaModule implements NativeModule {
new ArgumentExtractor<String>() {
@Override
public String extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return jsArguments.getString(atIndex);
}
};
@@ -109,7 +109,7 @@ public abstract class BaseJavaModule implements NativeModule {
new ArgumentExtractor<ReadableNativeArray>() {
@Override
public ReadableNativeArray extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return jsArguments.getArray(atIndex);
}
};
@@ -118,7 +118,7 @@ public abstract class BaseJavaModule implements NativeModule {
new ArgumentExtractor<ReadableMap>() {
@Override
public ReadableMap extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return jsArguments.getMap(atIndex);
}
};
@@ -127,12 +127,12 @@ public abstract class BaseJavaModule implements NativeModule {
new ArgumentExtractor<Callback>() {
@Override
public @Nullable Callback extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
if (jsArguments.isNull(atIndex)) {
return null;
} else {
int id = (int) jsArguments.getDouble(atIndex);
return new CallbackImpl(catalystInstance, id);
return new CallbackImpl(catalystInstance, executorToken, id);
}
}
};
@@ -146,11 +146,11 @@ public abstract class BaseJavaModule implements NativeModule {
@Override
public Promise extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
Callback resolve = ARGUMENT_EXTRACTOR_CALLBACK
.extractArgument(catalystInstance, jsArguments, atIndex);
.extractArgument(catalystInstance, executorToken, jsArguments, atIndex);
Callback reject = ARGUMENT_EXTRACTOR_CALLBACK
.extractArgument(catalystInstance, jsArguments, atIndex + 1);
.extractArgument(catalystInstance, executorToken, jsArguments, atIndex + 1);
return new PromiseImpl(resolve, reject);
}
};
@@ -174,9 +174,21 @@ public abstract class BaseJavaModule implements NativeModule {
}
private ArgumentExtractor[] buildArgumentExtractors(Class[] paramTypes) {
ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length];
for (int i = 0; i < paramTypes.length; i += argumentExtractors[i].getJSArgumentsNeeded()) {
Class argumentClass = paramTypes[i];
// Modules that support web workers are expected to take an ExecutorToken as the first
// parameter to all their @ReactMethod-annotated methods. We compensate for that here.
int executorTokenOffset = 0;
if (BaseJavaModule.this.supportsWebWorkers()) {
if (paramTypes[0] != ExecutorToken.class) {
throw new RuntimeException(
"Module " + BaseJavaModule.this + " supports web workers, but " + mMethod.getName() +
"does not take an ExecutorToken as its first parameter.");
}
executorTokenOffset = 1;
}
ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length - executorTokenOffset];
for (int i = 0; i < paramTypes.length - executorTokenOffset; i += argumentExtractors[i].getJSArgumentsNeeded()) {
Class argumentClass = paramTypes[i + executorTokenOffset];
if (argumentClass == Boolean.class || argumentClass == boolean.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_BOOLEAN;
} else if (argumentClass == Integer.class || argumentClass == int.class) {
@@ -220,7 +232,7 @@ public abstract class BaseJavaModule implements NativeModule {
}
@Override
public void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters) {
public void invoke(CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray parameters) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "callJavaModuleMethod");
try {
if (mJSArgumentsNeeded != parameters.size()) {
@@ -229,11 +241,18 @@ public abstract class BaseJavaModule implements NativeModule {
parameters.size() + " arguments, expected " + mJSArgumentsNeeded);
}
// Modules that support web workers are expected to take an ExecutorToken as the first
// parameter to all their @ReactMethod-annotated methods. We compensate for that here.
int i = 0, jsArgumentsConsumed = 0;
int executorTokenOffset = 0;
if (BaseJavaModule.this.supportsWebWorkers()) {
mArguments[0] = executorToken;
executorTokenOffset = 1;
}
try {
for (; i < mArgumentExtractors.length; i++) {
mArguments[i] = mArgumentExtractors[i].extractArgument(
catalystInstance, parameters, jsArgumentsConsumed);
mArguments[i + executorTokenOffset] = mArgumentExtractors[i].extractArgument(
catalystInstance, executorToken, parameters, jsArgumentsConsumed);
jsArgumentsConsumed += mArgumentExtractors[i].getJSArgumentsNeeded();
}
} catch (UnexpectedNativeTypeException e) {
@@ -341,7 +360,7 @@ public abstract class BaseJavaModule implements NativeModule {
public void onCatalystInstanceDestroy() {
// do nothing
}
@Override
public boolean supportsWebWorkers() {
return false;

View File

@@ -15,15 +15,17 @@ package com.facebook.react.bridge;
public final class CallbackImpl implements Callback {
private final CatalystInstance mCatalystInstance;
private final ExecutorToken mExecutorToken;
private final int mCallbackId;
public CallbackImpl(CatalystInstance bridge, int callbackId) {
public CallbackImpl(CatalystInstance bridge, ExecutorToken executorToken, int callbackId) {
mCatalystInstance = bridge;
mExecutorToken = executorToken;
mCallbackId = callbackId;
}
@Override
public void invoke(Object... args) {
mCatalystInstance.invokeCallback(mCallbackId, Arguments.fromJavaArgs(args));
mCatalystInstance.invokeCallback(mExecutorToken, mCallbackId, Arguments.fromJavaArgs(args));
}
}

View File

@@ -14,7 +14,6 @@ import java.util.Collection;
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.common.annotations.VisibleForTesting;
/**
* A higher level API on top of the asynchronous JSC bridge. This provides an
@@ -27,7 +26,7 @@ public interface CatalystInstance {
// This is called from java code, so it won't be stripped anyway, but proguard will rename it,
// which this prevents.
@DoNotStrip
void invokeCallback(final int callbackID, final NativeArray arguments);
void invokeCallback(ExecutorToken executorToken, final int callbackID, final NativeArray arguments);
/**
* Destroys this catalyst instance, waiting for any other threads in ReactQueueConfiguration
* (besides the UI thread) to finish running. Must be called from the UI thread so that we can
@@ -45,6 +44,7 @@ public interface CatalystInstance {
ReactQueueConfiguration getReactQueueConfiguration();
<T extends JavaScriptModule> T getJSModule(Class<T> jsInterface);
<T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface);
<T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface);
Collection<NativeModule> getNativeModules();

View File

@@ -54,6 +54,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
private final JavaScriptModuleRegistry mJSModuleRegistry;
private final JSBundleLoader mJSBundleLoader;
private final Object mTeardownLock = new Object();
private @Nullable ExecutorToken mMainExecutorToken;
// Access from native modules thread
private final NativeModuleRegistry mJavaRegistry;
@@ -113,6 +114,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
jsExecutor,
new NativeModulesReactCallback(),
mReactQueueConfiguration.getNativeModulesQueueThread());
mMainExecutorToken = bridge.getMainExecutorToken();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
@@ -165,10 +167,11 @@ public class CatalystInstanceImpl implements CatalystInstance {
}
/* package */ void callFunction(
final int moduleId,
final int methodId,
final NativeArray arguments,
final String tracingName) {
ExecutorToken executorToken,
int moduleId,
int methodId,
NativeArray arguments,
String tracingName) {
synchronized (mTeardownLock) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
@@ -177,7 +180,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
incrementPendingJSCalls();
Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId, arguments, tracingName);
Assertions.assertNotNull(mBridge).callFunction(executorToken, moduleId, methodId, arguments, tracingName);
}
}
@@ -185,7 +188,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
// which this prevents.
@DoNotStrip
@Override
public void invokeCallback(final int callbackID, final NativeArray arguments) {
public void invokeCallback(ExecutorToken executorToken, int callbackID, NativeArray arguments) {
synchronized (mTeardownLock) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
@@ -194,7 +197,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
incrementPendingJSCalls();
Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments);
Assertions.assertNotNull(mBridge).invokeCallback(executorToken, callbackID, arguments);
}
}
@@ -269,7 +272,12 @@ public class CatalystInstanceImpl implements CatalystInstance {
@Override
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(jsInterface);
return getJSModule(Assertions.assertNotNull(mMainExecutorToken), jsInterface);
}
@Override
public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) {
return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(executorToken, jsInterface);
}
@Override
@@ -388,7 +396,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
private class NativeModulesReactCallback implements ReactCallback {
@Override
public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
public void call(ExecutorToken executorToken, int moduleId, int methodId, ReadableNativeArray parameters) {
mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// Suppress any callbacks if destroyed - will only lead to sadness.
@@ -396,7 +404,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
return;
}
mJavaRegistry.call(CatalystInstanceImpl.this, moduleId, methodId, parameters);
mJavaRegistry.call(CatalystInstanceImpl.this, executorToken, moduleId, methodId, parameters);
}
@Override
@@ -441,12 +449,13 @@ public class CatalystInstanceImpl implements CatalystInstance {
private class JSProfilerTraceListener implements TraceListener {
@Override
public void onTraceStarted() {
getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(true);
getJSModule(Assertions.assertNotNull(mMainExecutorToken), com.facebook.react.bridge.Systrace.class).setEnabled(
true);
}
@Override
public void onTraceStopped() {
getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(false);
getJSModule(Assertions.assertNotNull(mMainExecutorToken), com.facebook.react.bridge.Systrace.class).setEnabled(false);
}
}

View File

@@ -0,0 +1,24 @@
package com.facebook.react.bridge;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* Class corresponding to a JS VM that can call into native modules. In Java, this should
* just be treated as a black box to be used to help the framework route native->JS calls back to
* the proper JS VM. See {@link ReactContext#getJSModule(ExecutorToken, Class)} and
* {@link BaseJavaModule#supportsWebWorkers()}.
*
* Note: If your application doesn't use web workers, it will only have a single ExecutorToken
* per instance of React Native.
*/
@DoNotStrip
public class ExecutorToken {
private final HybridData mHybridData;
@DoNotStrip
private ExecutorToken(HybridData hybridData) {
mHybridData = hybridData;
}
}

View File

@@ -12,12 +12,16 @@ package com.facebook.react.bridge;
import javax.annotation.Nullable;
import java.lang.Class;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.WeakHashMap;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.common.ReactConstants;
/**
* Class responsible for holding all the {@link JavaScriptModule}s registered to this
@@ -27,45 +31,71 @@ import com.facebook.infer.annotation.Assertions;
*/
/*package*/ class JavaScriptModuleRegistry {
private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;
private final CatalystInstanceImpl mCatalystInstance;
private final WeakHashMap<ExecutorToken, HashMap<Class<? extends JavaScriptModule>, JavaScriptModule>> mModuleInstances;
private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModuleRegistration> mModuleRegistrations;
public JavaScriptModuleRegistry(
CatalystInstanceImpl instance,
JavaScriptModulesConfig config) {
mModuleInstances = new HashMap<>();
mCatalystInstance = instance;
mModuleInstances = new WeakHashMap<>();
mModuleRegistrations = new HashMap<>();
for (JavaScriptModuleRegistration registration : config.getModuleDefinitions()) {
Class<? extends JavaScriptModule> moduleInterface = registration.getModuleInterface();
JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
moduleInterface.getClassLoader(),
new Class[]{moduleInterface},
new JavaScriptModuleInvocationHandler(instance, registration));
mModuleInstances.put(moduleInterface, interfaceProxy);
mModuleRegistrations.put(registration.getModuleInterface(), registration);
}
}
public <T extends JavaScriptModule> T getJavaScriptModule(Class<T> moduleInterface) {
return (T) Assertions.assertNotNull(
mModuleInstances.get(moduleInterface),
"JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
public synchronized <T extends JavaScriptModule> T getJavaScriptModule(ExecutorToken executorToken, Class<T> moduleInterface) {
HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> instancesForContext =
mModuleInstances.get(executorToken);
if (instancesForContext == null) {
instancesForContext = new HashMap<>();
mModuleInstances.put(executorToken, instancesForContext);
}
JavaScriptModule module = instancesForContext.get(moduleInterface);
if (module != null) {
return (T) module;
}
JavaScriptModuleRegistration registration =
Assertions.assertNotNull(
mModuleRegistrations.get(moduleInterface),
"JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
moduleInterface.getClassLoader(),
new Class[]{moduleInterface},
new JavaScriptModuleInvocationHandler(executorToken, mCatalystInstance, registration));
instancesForContext.put(moduleInterface, interfaceProxy);
return (T) interfaceProxy;
}
private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
private final WeakReference<ExecutorToken> mExecutorToken;
private final CatalystInstanceImpl mCatalystInstance;
private final JavaScriptModuleRegistration mModuleRegistration;
public JavaScriptModuleInvocationHandler(
ExecutorToken executorToken,
CatalystInstanceImpl catalystInstance,
JavaScriptModuleRegistration moduleRegistration) {
mExecutorToken = new WeakReference<>(executorToken);
mCatalystInstance = catalystInstance;
mModuleRegistration = moduleRegistration;
}
@Override
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
ExecutorToken executorToken = mExecutorToken.get();
if (executorToken == null) {
FLog.w(ReactConstants.TAG, "Dropping JS call, ExecutorToken went away...");
return null;
}
String tracingName = mModuleRegistration.getTracingName(method);
mCatalystInstance.callFunction(
executorToken,
mModuleRegistration.getModuleId(),
mModuleRegistration.getMethodId(method),
Arguments.fromJavaArgs(args),

View File

@@ -23,7 +23,7 @@ import com.fasterxml.jackson.core.JsonGenerator;
*/
public interface NativeModule {
interface NativeMethod {
void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters);
void invoke(CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray parameters);
String getType();
}
@@ -74,7 +74,25 @@ public interface NativeModule {
void onCatalystInstanceDestroy();
/**
* Whether this native module supports calls from web workers. Ignored for now.
* In order to support web workers, a module must be aware that it can be invoked from multiple
* different JS VMs. Supporting web workers means recognizing things like:
*
* 1) ids (e.g. timer ids, request ids, etc.) may only unique on a per-VM basis
* 2) the module needs to make sure to enqueue callbacks and JS module calls to the correct VM
*
* In order to facilitate this, modules that support web workers will have all their @ReactMethod-
* annotated methods passed a {@link ExecutorToken} as the first parameter before any arguments
* from JS. This ExecutorToken internally maps to a specific JS VM and can be used by the
* framework to route calls appropriately. In order to make JS module calls correctly, start using
* the version of {@link ReactContext#getJSModule(ExecutorToken, Class)} that takes an
* ExecutorToken. It will ensure that any calls you dispatch to the returned object will go to
* the right VM. For Callbacks, you don't have to do anything special -- the framework
* automatically tags them with the correct ExecutorToken when the are created.
*
* Note: even though calls can come from multiple JS VMs on multiple threads, calls to this module
* will still only occur on a single thread.
*
* @return whether this module supports web workers.
*/
boolean supportsWebWorkers();
}

View File

@@ -48,6 +48,7 @@ public class NativeModuleRegistry {
/* package */ void call(
CatalystInstance catalystInstance,
ExecutorToken executorToken,
int moduleId,
int methodId,
ReadableNativeArray parameters) {
@@ -55,7 +56,7 @@ public class NativeModuleRegistry {
if (definition == null) {
throw new RuntimeException("Call to unknown module: " + moduleId);
}
definition.call(catalystInstance, methodId, parameters);
definition.call(catalystInstance, executorToken, methodId, parameters);
}
/* package */ void writeModuleDescriptions(JsonGenerator jg) throws IOException {
@@ -164,12 +165,13 @@ public class NativeModuleRegistry {
public void call(
CatalystInstance catalystInstance,
ExecutorToken executorToken,
int methodId,
ReadableNativeArray parameters) {
MethodRegistration method = this.methods.get(methodId);
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, method.tracingName);
try {
this.methods.get(methodId).method.invoke(catalystInstance, parameters);
this.methods.get(methodId).method.invoke(catalystInstance, executorToken, parameters);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}

View File

@@ -79,12 +79,13 @@ public class ReactBridge extends Countable {
*/
public native void loadScriptFromAssets(AssetManager assetManager, String assetName);
public native void loadScriptFromFile(@Nullable String fileName, @Nullable String sourceURL);
public native void callFunction(int moduleId, int methodId, NativeArray arguments, String tracingName);
public native void invokeCallback(int callbackID, NativeArray arguments);
public native void callFunction(ExecutorToken executorToken, int moduleId, int methodId, NativeArray arguments, String tracingName);
public native void invokeCallback(ExecutorToken executorToken, int callbackID, NativeArray arguments);
public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
public native boolean supportsProfiling();
public native void startProfiler(String title);
public native void stopProfiler(String title, String filename);
public native ExecutorToken getMainExecutorToken();
private native void handleMemoryPressureModerate();
private native void handleMemoryPressureCritical();
public native void destroy();

View File

@@ -15,7 +15,7 @@ import com.facebook.proguard.annotations.DoNotStrip;
public interface ReactCallback {
@DoNotStrip
void call(int moduleId, int methodId, ReadableNativeArray parameters);
void call(ExecutorToken executorToken, int moduleId, int methodId, ReadableNativeArray parameters);
@DoNotStrip
void onBatchComplete();

View File

@@ -96,6 +96,13 @@ public class ReactContext extends ContextWrapper {
return mCatalystInstance.getJSModule(jsInterface);
}
public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) {
if (mCatalystInstance == null) {
throw new RuntimeException("Trying to invoke JS before CatalystInstance has been set!");
}
return mCatalystInstance.getJSModule(executorToken, jsInterface);
}
/**
* @return the instance of the specified module interface associated with this ReactContext.
*/