mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-05-11 19:01:43 +08:00
Release React Native for Android
This is an early release and there are several things that are known not to work if you're porting your iOS app to Android. See the Known Issues guide on the website. We will work with the community to reach platform parity with iOS.
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
public class Arguments {
|
||||
|
||||
/**
|
||||
* This method should be used when you need to stub out creating NativeArrays in unit tests.
|
||||
*/
|
||||
public static WritableArray createArray() {
|
||||
return new WritableNativeArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be used when you need to stub out creating NativeMaps in unit tests.
|
||||
*/
|
||||
public static WritableMap createMap() {
|
||||
return new WritableNativeMap();
|
||||
}
|
||||
|
||||
public static WritableNativeArray fromJavaArgs(Object[] args) {
|
||||
WritableNativeArray arguments = new WritableNativeArray();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
Object argument = args[i];
|
||||
if (argument == null) {
|
||||
arguments.pushNull();
|
||||
continue;
|
||||
}
|
||||
|
||||
Class argumentClass = argument.getClass();
|
||||
if (argumentClass == Boolean.class) {
|
||||
arguments.pushBoolean(((Boolean) argument).booleanValue());
|
||||
} else if (argumentClass == Integer.class) {
|
||||
arguments.pushDouble(((Integer) argument).doubleValue());
|
||||
} else if (argumentClass == Double.class) {
|
||||
arguments.pushDouble(((Double) argument).doubleValue());
|
||||
} else if (argumentClass == Float.class) {
|
||||
arguments.pushDouble(((Float) argument).doubleValue());
|
||||
} else if (argumentClass == String.class) {
|
||||
arguments.pushString(argument.toString());
|
||||
} else if (argumentClass == WritableNativeMap.class) {
|
||||
arguments.pushMap((WritableNativeMap) argument);
|
||||
} else if (argumentClass == WritableNativeArray.class) {
|
||||
arguments.pushArray((WritableNativeArray) argument);
|
||||
} else {
|
||||
throw new RuntimeException("Cannot convert argument of type " + argumentClass);
|
||||
}
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array to a {@link WritableArray}.
|
||||
*
|
||||
* @param array the array to convert. Supported types are: {@code String[]}, {@code Bundle[]},
|
||||
* {@code int[]}, {@code float[]}, {@code double[]}, {@code boolean[]}.
|
||||
*
|
||||
* @return the converted {@link WritableArray}
|
||||
* @throws IllegalArgumentException if the passed object is none of the above types
|
||||
*/
|
||||
public static WritableArray fromArray(Object array) {
|
||||
WritableArray catalystArray = createArray();
|
||||
if (array instanceof String[]) {
|
||||
for (String v: (String[]) array) {
|
||||
catalystArray.pushString(v);
|
||||
}
|
||||
} else if (array instanceof Bundle[]) {
|
||||
for (Bundle v: (Bundle[]) array) {
|
||||
catalystArray.pushMap(fromBundle(v));
|
||||
}
|
||||
} else if (array instanceof int[]) {
|
||||
for (int v: (int[]) array) {
|
||||
catalystArray.pushInt(v);
|
||||
}
|
||||
} else if (array instanceof float[]) {
|
||||
for (float v: (float[]) array) {
|
||||
catalystArray.pushDouble(v);
|
||||
}
|
||||
} else if (array instanceof double[]) {
|
||||
for (double v: (double[]) array) {
|
||||
catalystArray.pushDouble(v);
|
||||
}
|
||||
} else if (array instanceof boolean[]) {
|
||||
for (boolean v: (boolean[]) array) {
|
||||
catalystArray.pushBoolean(v);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown array type " + array.getClass());
|
||||
}
|
||||
return catalystArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a {@link Bundle} to a {@link WritableMap}. Supported key types in the bundle
|
||||
* are:
|
||||
*
|
||||
* <ul>
|
||||
* <li>primitive types: int, float, double, boolean</li>
|
||||
* <li>arrays supported by {@link #fromArray(Object)}</li>
|
||||
* <li>{@link Bundle} objects that are recursively converted to maps</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param bundle the {@link Bundle} to convert
|
||||
* @return the converted {@link WritableMap}
|
||||
* @throws IllegalArgumentException if there are keys of unsupported types
|
||||
*/
|
||||
public static WritableMap fromBundle(Bundle bundle) {
|
||||
WritableMap map = createMap();
|
||||
for (String key: bundle.keySet()) {
|
||||
Object value = bundle.get(key);
|
||||
if (value == null) {
|
||||
map.putNull(key);
|
||||
} else if (value.getClass().isArray()) {
|
||||
map.putArray(key, fromArray(value));
|
||||
} else if (value instanceof String) {
|
||||
map.putString(key, (String) value);
|
||||
} else if (value instanceof Number) {
|
||||
if (value instanceof Integer) {
|
||||
map.putInt(key, (Integer) value);
|
||||
} else {
|
||||
map.putDouble(key, ((Number) value).doubleValue());
|
||||
}
|
||||
} else if (value instanceof Boolean) {
|
||||
map.putBoolean(key, (Boolean) value);
|
||||
} else if (value instanceof Bundle) {
|
||||
map.putMap(key, fromBundle((Bundle) value));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Could not convert " + value.getClass());
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* Like {@link AssertionError} but extends RuntimeException so that it may be caught by a
|
||||
* {@link NativeModuleCallExceptionHandler}. See that class for more details. Used in
|
||||
* conjunction with {@link SoftAssertions}.
|
||||
*/
|
||||
public class AssertionException extends RuntimeException {
|
||||
|
||||
public AssertionException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
||||
import com.facebook.systrace.Systrace;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base class for Catalyst native modules whose implementations are written in Java. Default
|
||||
* implementations for {@link #initialize} and {@link #onCatalystInstanceDestroy} are provided for
|
||||
* convenience. Subclasses which override these don't need to call {@code super} in case of
|
||||
* overriding those methods as implementation of those methods is empty.
|
||||
*
|
||||
* BaseJavaModules can be linked to Fragments' lifecycle events, {@link CatalystInstance} creation
|
||||
* and destruction, by being called on the appropriate method when a life cycle event occurs.
|
||||
*
|
||||
* Native methods can be exposed to JS with {@link ReactMethod} annotation. Those methods may
|
||||
* only use limited number of types for their arguments:
|
||||
* 1/ primitives (boolean, int, float, double
|
||||
* 2/ {@link String} mapped from JS string
|
||||
* 3/ {@link ReadableArray} mapped from JS Array
|
||||
* 4/ {@link ReadableMap} mapped from JS Object
|
||||
* 5/ {@link Callback} mapped from js function and can be used only as a last parameter or in the
|
||||
* case when it express success & error callback pair as two last arguments respecively.
|
||||
*
|
||||
* All methods exposed as native to JS with {@link ReactMethod} annotation must return
|
||||
* {@code void}.
|
||||
*
|
||||
* Please note that it is not allowed to have multiple methods annotated with {@link ReactMethod}
|
||||
* with the same name.
|
||||
*/
|
||||
public abstract class BaseJavaModule implements NativeModule {
|
||||
private class JavaMethod implements NativeMethod {
|
||||
private Method method;
|
||||
|
||||
public JavaMethod(Method method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters) {
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "callJavaModuleMethod");
|
||||
try {
|
||||
Class[] types = method.getParameterTypes();
|
||||
if (types.length != parameters.size()) {
|
||||
throw new NativeArgumentsParseException(
|
||||
BaseJavaModule.this.getName() + "." + method.getName() + " got " + parameters.size() +
|
||||
" arguments, expected " + types.length);
|
||||
}
|
||||
Object[] arguments = new Object[types.length];
|
||||
|
||||
int i = 0;
|
||||
try {
|
||||
for (; i < types.length; i++) {
|
||||
Class argumentClass = types[i];
|
||||
if (argumentClass == Boolean.class || argumentClass == boolean.class) {
|
||||
arguments[i] = Boolean.valueOf(parameters.getBoolean(i));
|
||||
} else if (argumentClass == Integer.class || argumentClass == int.class) {
|
||||
arguments[i] = Integer.valueOf((int) parameters.getDouble(i));
|
||||
} else if (argumentClass == Double.class || argumentClass == double.class) {
|
||||
arguments[i] = Double.valueOf(parameters.getDouble(i));
|
||||
} else if (argumentClass == Float.class || argumentClass == float.class) {
|
||||
arguments[i] = Float.valueOf((float) parameters.getDouble(i));
|
||||
} else if (argumentClass == String.class) {
|
||||
arguments[i] = parameters.getString(i);
|
||||
} else if (argumentClass == Callback.class) {
|
||||
if (parameters.isNull(i)) {
|
||||
arguments[i] = null;
|
||||
} else {
|
||||
int id = (int) parameters.getDouble(i);
|
||||
arguments[i] = new CallbackImpl(catalystInstance, id);
|
||||
}
|
||||
} else if (argumentClass == ReadableMap.class) {
|
||||
arguments[i] = parameters.getMap(i);
|
||||
} else if (argumentClass == ReadableArray.class) {
|
||||
arguments[i] = parameters.getArray(i);
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Got unknown argument class: " + argumentClass.getSimpleName());
|
||||
}
|
||||
}
|
||||
} catch (UnexpectedNativeTypeException e) {
|
||||
throw new NativeArgumentsParseException(
|
||||
e.getMessage() + " (constructing arguments for " + BaseJavaModule.this.getName() +
|
||||
"." + method.getName() + " at argument index " + i + ")",
|
||||
e);
|
||||
}
|
||||
|
||||
try {
|
||||
method.invoke(BaseJavaModule.this, arguments);
|
||||
} catch (IllegalArgumentException ie) {
|
||||
throw new RuntimeException(
|
||||
"Could not invoke " + BaseJavaModule.this.getName() + "." + method.getName(), ie);
|
||||
} catch (IllegalAccessException iae) {
|
||||
throw new RuntimeException(
|
||||
"Could not invoke " + BaseJavaModule.this.getName() + "." + method.getName(), iae);
|
||||
} catch (InvocationTargetException ite) {
|
||||
// Exceptions thrown from native module calls end up wrapped in InvocationTargetException
|
||||
// which just make traces harder to read and bump out useful information
|
||||
if (ite.getCause() instanceof RuntimeException) {
|
||||
throw (RuntimeException) ite.getCause();
|
||||
}
|
||||
throw new RuntimeException(
|
||||
"Could not invoke " + BaseJavaModule.this.getName() + "." + method.getName(), ite);
|
||||
}
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Map<String, NativeMethod> getMethods() {
|
||||
Map<String, NativeMethod> methods = new HashMap<String, NativeMethod>();
|
||||
Method[] targetMethods = getClass().getDeclaredMethods();
|
||||
for (int i = 0; i < targetMethods.length; i++) {
|
||||
Method targetMethod = targetMethods[i];
|
||||
if (targetMethod.getAnnotation(ReactMethod.class) != null) {
|
||||
String methodName = targetMethod.getName();
|
||||
if (methods.containsKey(methodName)) {
|
||||
// We do not support method overloading since js sees a function as an object regardless
|
||||
// of number of params.
|
||||
throw new IllegalArgumentException(
|
||||
"Java Module " + getName() + " method name already registered: " + methodName);
|
||||
}
|
||||
methods.put(methodName, new JavaMethod(targetMethod));
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a map of constants this module exports to JS. Supports JSON types.
|
||||
*/
|
||||
public @Nullable Map<String, Object> getConstants() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void writeConstantsField(JsonGenerator jg, String fieldName) throws IOException {
|
||||
Map<String, Object> constants = getConstants();
|
||||
if (constants == null || constants.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
jg.writeObjectFieldStart(fieldName);
|
||||
for (Map.Entry<String, Object> constant : constants.entrySet()) {
|
||||
JsonGeneratorHelper.writeObjectField(
|
||||
jg,
|
||||
constant.getKey(),
|
||||
constant.getValue());
|
||||
}
|
||||
jg.writeEndObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCatalystInstanceDestroy() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* Interface that represent javascript callback function which can be passed to the native module
|
||||
* as a method parameter.
|
||||
*/
|
||||
public interface Callback {
|
||||
|
||||
/**
|
||||
* Schedule javascript function execution represented by this {@link Callback} instance
|
||||
*
|
||||
* @param args arguments passed to javascript callback method via bridge
|
||||
*/
|
||||
public void invoke(Object... args);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* Implementation of javascript callback function that use Bridge to schedule method execution
|
||||
*/
|
||||
public final class CallbackImpl implements Callback {
|
||||
|
||||
private final CatalystInstance mCatalystInstance;
|
||||
private final int mCallbackId;
|
||||
|
||||
public CallbackImpl(CatalystInstance bridge, int callbackId) {
|
||||
mCatalystInstance = bridge;
|
||||
mCallbackId = callbackId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(Object... args) {
|
||||
mCatalystInstance.invokeCallback(mCallbackId, Arguments.fromJavaArgs(args));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.bridge.queue.CatalystQueueConfiguration;
|
||||
import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec;
|
||||
import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.systrace.Systrace;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
||||
/**
|
||||
* A higher level API on top of the asynchronous JSC bridge. This provides an
|
||||
* environment allowing the invocation of JavaScript methods and lets a set of
|
||||
* Java APIs be invokable from JavaScript as well.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class CatalystInstance {
|
||||
|
||||
private static final int BRIDGE_SETUP_TIMEOUT_MS = 15000;
|
||||
|
||||
private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1);
|
||||
|
||||
// Access from any thread
|
||||
private final CatalystQueueConfiguration mCatalystQueueConfiguration;
|
||||
private final CopyOnWriteArrayList<NotThreadSafeBridgeIdleDebugListener> mBridgeIdleListeners;
|
||||
private final AtomicInteger mPendingJSCalls = new AtomicInteger(0);
|
||||
private final String mJsPendingCallsTitleForTrace =
|
||||
"pending_js_calls_instance" + sNextInstanceIdForTrace.getAndIncrement();
|
||||
private volatile boolean mDestroyed = false;
|
||||
|
||||
// Access from native modules thread
|
||||
private final NativeModuleRegistry mJavaRegistry;
|
||||
private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
|
||||
private boolean mInitialized = false;
|
||||
|
||||
// Access from JS thread
|
||||
private @Nullable ReactBridge mBridge;
|
||||
private @Nullable JavaScriptModuleRegistry mJSModuleRegistry;
|
||||
|
||||
private CatalystInstance(
|
||||
final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec,
|
||||
final JavaScriptExecutor jsExecutor,
|
||||
final NativeModuleRegistry registry,
|
||||
final JavaScriptModulesConfig jsModulesConfig,
|
||||
final JSBundleLoader jsBundleLoader,
|
||||
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
|
||||
mCatalystQueueConfiguration = CatalystQueueConfiguration.create(
|
||||
catalystQueueConfigurationSpec,
|
||||
new NativeExceptionHandler());
|
||||
mBridgeIdleListeners = new CopyOnWriteArrayList<NotThreadSafeBridgeIdleDebugListener>();
|
||||
mJavaRegistry = registry;
|
||||
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
|
||||
|
||||
final CountDownLatch initLatch = new CountDownLatch(1);
|
||||
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
initializeBridge(jsExecutor, registry, jsModulesConfig, jsBundleLoader);
|
||||
mJSModuleRegistry =
|
||||
new JavaScriptModuleRegistry(CatalystInstance.this, jsModulesConfig);
|
||||
|
||||
initLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
Assertions.assertCondition(
|
||||
initLatch.await(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS),
|
||||
"Timed out waiting for bridge to initialize!");
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeBridge(
|
||||
JavaScriptExecutor jsExecutor,
|
||||
NativeModuleRegistry registry,
|
||||
JavaScriptModulesConfig jsModulesConfig,
|
||||
JSBundleLoader jsBundleLoader) {
|
||||
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
|
||||
Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
|
||||
mBridge = new ReactBridge(
|
||||
jsExecutor,
|
||||
new NativeModulesReactCallback(),
|
||||
mCatalystQueueConfiguration.getNativeModulesQueueThread());
|
||||
mBridge.setGlobalVariable(
|
||||
"__fbBatchedBridgeConfig",
|
||||
buildModulesConfigJSONProperty(registry, jsModulesConfig));
|
||||
jsBundleLoader.loadScript(mBridge);
|
||||
}
|
||||
|
||||
/* package */ void callFunction(
|
||||
final int moduleId,
|
||||
final int methodId,
|
||||
final NativeArray arguments,
|
||||
final String tracingName) {
|
||||
if (mDestroyed) {
|
||||
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
|
||||
return;
|
||||
}
|
||||
|
||||
incrementPendingJSCalls();
|
||||
|
||||
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
|
||||
|
||||
if (mDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, tracingName);
|
||||
try {
|
||||
Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId, arguments);
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This is called from java code, so it won't be stripped anyway, but proguard will rename it,
|
||||
// which this prevents.
|
||||
@DoNotStrip
|
||||
/* package */ void invokeCallback(final int callbackID, final NativeArray arguments) {
|
||||
if (mDestroyed) {
|
||||
FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
|
||||
return;
|
||||
}
|
||||
|
||||
incrementPendingJSCalls();
|
||||
|
||||
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
|
||||
|
||||
if (mDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "<callback>");
|
||||
try {
|
||||
Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments);
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys this catalyst instance, waiting for any other threads in CatalystQueueConfiguration
|
||||
* (besides the UI thread) to finish running. Must be called from the UI thread so that we can
|
||||
* fully shut down other threads.
|
||||
*/
|
||||
/* package */ void destroy() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
if (mDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: tell all APIs to shut down
|
||||
mDestroyed = true;
|
||||
mJavaRegistry.notifyCatalystInstanceDestroy();
|
||||
mCatalystQueueConfiguration.destroy();
|
||||
boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
|
||||
if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
|
||||
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
|
||||
listener.onTransitionToBridgeIdle();
|
||||
}
|
||||
}
|
||||
|
||||
// We can access the Bridge from any thread now because we know either we are on the JS thread
|
||||
// or the JS thread has finished via CatalystQueueConfiguration#destroy()
|
||||
Assertions.assertNotNull(mBridge).dispose();
|
||||
}
|
||||
|
||||
public boolean isDestroyed() {
|
||||
return mDestroyed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all the native modules
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void initialize() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
Assertions.assertCondition(
|
||||
!mInitialized,
|
||||
"This catalyst instance has already been initialized");
|
||||
mInitialized = true;
|
||||
mJavaRegistry.notifyCatalystInstanceInitialized();
|
||||
}
|
||||
|
||||
public CatalystQueueConfiguration getCatalystQueueConfiguration() {
|
||||
return mCatalystQueueConfiguration;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public @Nullable
|
||||
ReactBridge getBridge() {
|
||||
return mBridge;
|
||||
}
|
||||
|
||||
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
|
||||
return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(jsInterface);
|
||||
}
|
||||
|
||||
public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
|
||||
return mJavaRegistry.getModule(nativeModuleInterface);
|
||||
}
|
||||
|
||||
public Collection<NativeModule> getNativeModules() {
|
||||
return mJavaRegistry.getAllModules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a idle listener for this Catalyst instance. The listener will receive notifications
|
||||
* whenever the bridge transitions from idle to busy and vice-versa, where the busy state is
|
||||
* defined as there being some non-zero number of calls to JS that haven't resolved via a
|
||||
* onBatchCompleted call. The listener should be purely passive and not affect application logic.
|
||||
*/
|
||||
public void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
|
||||
mBridgeIdleListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a NotThreadSafeBridgeIdleDebugListener previously added with
|
||||
* {@link #addBridgeIdleDebugListener}
|
||||
*/
|
||||
public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
|
||||
mBridgeIdleListeners.remove(listener);
|
||||
}
|
||||
|
||||
private String buildModulesConfigJSONProperty(
|
||||
NativeModuleRegistry nativeModuleRegistry,
|
||||
JavaScriptModulesConfig jsModulesConfig) {
|
||||
// TODO(5300733): Serialize config using single json generator
|
||||
JsonFactory jsonFactory = new JsonFactory();
|
||||
StringWriter writer = new StringWriter();
|
||||
try {
|
||||
JsonGenerator jg = jsonFactory.createGenerator(writer);
|
||||
jg.writeStartObject();
|
||||
jg.writeFieldName("remoteModuleConfig");
|
||||
jg.writeRawValue(nativeModuleRegistry.moduleDescriptions());
|
||||
jg.writeFieldName("localModulesConfig");
|
||||
jg.writeRawValue(jsModulesConfig.moduleDescriptions());
|
||||
jg.writeEndObject();
|
||||
jg.close();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
|
||||
}
|
||||
return writer.getBuffer().toString();
|
||||
}
|
||||
|
||||
private void incrementPendingJSCalls() {
|
||||
int oldPendingCalls = mPendingJSCalls.getAndIncrement();
|
||||
boolean wasIdle = oldPendingCalls == 0;
|
||||
Systrace.traceCounter(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
mJsPendingCallsTitleForTrace,
|
||||
oldPendingCalls + 1);
|
||||
if (wasIdle && !mBridgeIdleListeners.isEmpty()) {
|
||||
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
|
||||
listener.onTransitionToBridgeBusy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void decrementPendingJSCalls() {
|
||||
int newPendingCalls = mPendingJSCalls.decrementAndGet();
|
||||
boolean isNowIdle = newPendingCalls == 0;
|
||||
Systrace.traceCounter(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
mJsPendingCallsTitleForTrace,
|
||||
newPendingCalls);
|
||||
|
||||
if (isNowIdle && !mBridgeIdleListeners.isEmpty()) {
|
||||
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
|
||||
listener.onTransitionToBridgeIdle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NativeModulesReactCallback implements ReactCallback {
|
||||
|
||||
@Override
|
||||
public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
|
||||
mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
|
||||
|
||||
// Suppress any callbacks if destroyed - will only lead to sadness.
|
||||
if (mDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
mJavaRegistry.call(CatalystInstance.this, moduleId, methodId, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatchComplete() {
|
||||
mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
|
||||
|
||||
// The bridge may have been destroyed due to an exception during the batch. In that case
|
||||
// native modules could be in a bad state so we don't want to call anything on them. We
|
||||
// still want to trigger the debug listener since it allows instrumentation tests to end and
|
||||
// check their assertions without waiting for a timeout.
|
||||
if (!mDestroyed) {
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchComplete");
|
||||
try {
|
||||
mJavaRegistry.onBatchComplete();
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
}
|
||||
|
||||
decrementPendingJSCalls();
|
||||
}
|
||||
}
|
||||
|
||||
private class NativeExceptionHandler implements QueueThreadExceptionHandler {
|
||||
|
||||
@Override
|
||||
public void handleException(Exception e) {
|
||||
// Any Exception caught here is because of something in JS. Even if it's a bug in the
|
||||
// framework/native code, it was triggered by JS and theoretically since we were able
|
||||
// to set up the bridge, JS could change its logic, reload, and not trigger that crash.
|
||||
mNativeModuleCallExceptionHandler.handleException(e);
|
||||
mCatalystQueueConfiguration.getUIQueueThread().runOnQueue(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private @Nullable CatalystQueueConfigurationSpec mCatalystQueueConfigurationSpec;
|
||||
private @Nullable JSBundleLoader mJSBundleLoader;
|
||||
private @Nullable NativeModuleRegistry mRegistry;
|
||||
private @Nullable JavaScriptModulesConfig mJSModulesConfig;
|
||||
private @Nullable JavaScriptExecutor mJSExecutor;
|
||||
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
|
||||
|
||||
public Builder setCatalystQueueConfigurationSpec(
|
||||
CatalystQueueConfigurationSpec catalystQueueConfigurationSpec) {
|
||||
mCatalystQueueConfigurationSpec = catalystQueueConfigurationSpec;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRegistry(NativeModuleRegistry registry) {
|
||||
mRegistry = registry;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJSModulesConfig(JavaScriptModulesConfig jsModulesConfig) {
|
||||
mJSModulesConfig = jsModulesConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJSBundleLoader(JSBundleLoader jsBundleLoader) {
|
||||
mJSBundleLoader = jsBundleLoader;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJSExecutor(JavaScriptExecutor jsExecutor) {
|
||||
mJSExecutor = jsExecutor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNativeModuleCallExceptionHandler(
|
||||
NativeModuleCallExceptionHandler handler) {
|
||||
mNativeModuleCallExceptionHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CatalystInstance build() {
|
||||
return new CatalystInstance(
|
||||
Assertions.assertNotNull(mCatalystQueueConfigurationSpec),
|
||||
Assertions.assertNotNull(mJSExecutor),
|
||||
Assertions.assertNotNull(mRegistry),
|
||||
Assertions.assertNotNull(mJSModulesConfig),
|
||||
Assertions.assertNotNull(mJSBundleLoader),
|
||||
Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
/**
|
||||
* Abstract base for a AsyncTask that should have any RuntimeExceptions it throws
|
||||
* handled by the {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} registered if
|
||||
* the app is in dev mode.
|
||||
*
|
||||
* This class doesn't allow doInBackground to return a results. This is mostly because when this
|
||||
* class was written that functionality wasn't used and it would require some extra code to make
|
||||
* work correctly with caught exceptions. Don't let that stop you from adding it if you need it :)
|
||||
*/
|
||||
public abstract class GuardedAsyncTask<Params, Progress>
|
||||
extends AsyncTask<Params, Progress, Void> {
|
||||
|
||||
private final ReactContext mReactContext;
|
||||
|
||||
protected GuardedAsyncTask(ReactContext reactContext) {
|
||||
mReactContext = reactContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Void doInBackground(Params... params) {
|
||||
try {
|
||||
doInBackgroundGuarded(params);
|
||||
} catch (RuntimeException e) {
|
||||
mReactContext.handleException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract void doInBackgroundGuarded(Params... params);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
/**
|
||||
* Exception thrown by {@link ReadableMapKeySeyIterator#nextKey()} when the iterator tries
|
||||
* to iterate over elements after the end of the key set.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class InvalidIteratorException extends RuntimeException {
|
||||
|
||||
@DoNotStrip
|
||||
public InvalidIteratorException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A special RuntimeException that should be thrown by native code if it has reached an exceptional
|
||||
* state due to a, or a sequence of, bad commands.
|
||||
*
|
||||
* A good rule of thumb for whether a native Exception should extend this interface is 1) Can a
|
||||
* developer make a change or correction in JS to keep this Exception from being thrown? 2) Is the
|
||||
* app outside of this catalyst instance still in a good state to allow reloading and restarting
|
||||
* this catalyst instance?
|
||||
*
|
||||
* Examples where this class is appropriate to throw:
|
||||
* - JS tries to update a view with a tag that hasn't been created yet
|
||||
* - JS tries to show a static image that isn't in resources
|
||||
* - JS tries to use an unsupported view class
|
||||
*
|
||||
* Examples where this class **isn't** appropriate to throw:
|
||||
* - Failed to write to localStorage because disk is full
|
||||
* - Assertions about internal state (e.g. that child.getParent().indexOf(child) != -1)
|
||||
*/
|
||||
public class JSApplicationCausedNativeException extends RuntimeException {
|
||||
|
||||
public JSApplicationCausedNativeException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public JSApplicationCausedNativeException(
|
||||
@Nullable String detailMessage,
|
||||
@Nullable Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* An illegal argument Exception caused by an argument passed from JS.
|
||||
*/
|
||||
public class JSApplicationIllegalArgumentException extends JSApplicationCausedNativeException {
|
||||
|
||||
public JSApplicationIllegalArgumentException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
/**
|
||||
* A class that stores JS bundle information and allows {@link CatalystInstance} to load a correct
|
||||
* bundle through {@link ReactBridge}.
|
||||
*/
|
||||
public abstract class JSBundleLoader {
|
||||
|
||||
/**
|
||||
* This loader is recommended one for release version of your app. In that case local JS executor
|
||||
* should be used. JS bundle will be read from assets directory in native code to save on passing
|
||||
* large strings from java to native memory.
|
||||
*/
|
||||
public static JSBundleLoader createAssetLoader(
|
||||
final AssetManager assetManager,
|
||||
final String assetFileName) {
|
||||
return new JSBundleLoader() {
|
||||
@Override
|
||||
public void loadScript(ReactBridge bridge) {
|
||||
bridge.loadScriptFromAssets(assetManager, assetFileName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This loader is used when bundle gets reloaded from dev server. In that case loader expect JS
|
||||
* bundle to be prefetched and stored in local file. We do that to avoid passing large strings
|
||||
* between java and native code and avoid allocating memory in java to fit whole JS bundle in it.
|
||||
* Providing correct {@param sourceURL} of downloaded bundle is required for JS stacktraces to
|
||||
* work correctly and allows for source maps to correctly symbolize those.
|
||||
*/
|
||||
public static JSBundleLoader createCachedBundleFromNetworkLoader(
|
||||
final String sourceURL,
|
||||
final String cachedFileLocation) {
|
||||
return new JSBundleLoader() {
|
||||
@Override
|
||||
public void loadScript(ReactBridge bridge) {
|
||||
bridge.loadScriptFromNetworkCached(sourceURL, cachedFileLocation);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This loader is used when proxy debugging is enabled. In that case there is no point in fetching
|
||||
* the bundle from device as remote executor will have to do it anyway.
|
||||
*/
|
||||
public static JSBundleLoader createRemoteDebuggerBundleLoader(
|
||||
final String sourceURL) {
|
||||
return new JSBundleLoader() {
|
||||
@Override
|
||||
public void loadScript(ReactBridge bridge) {
|
||||
bridge.loadScriptFromNetworkCached(sourceURL, null);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public abstract void loadScript(ReactBridge bridge);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
@DoNotStrip
|
||||
public class JSCJavaScriptExecutor extends JavaScriptExecutor {
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
public JSCJavaScriptExecutor() {
|
||||
initialize();
|
||||
}
|
||||
|
||||
private native void initialize();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.Request;
|
||||
import com.squareup.okhttp.Response;
|
||||
import com.squareup.okhttp.ws.WebSocket;
|
||||
import com.squareup.okhttp.ws.WebSocketCall;
|
||||
import com.squareup.okhttp.ws.WebSocketListener;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
|
||||
/**
|
||||
* A wrapper around WebSocketClient that recognizes RN debugging message format.
|
||||
*/
|
||||
public class JSDebuggerWebSocketClient implements WebSocketListener {
|
||||
|
||||
private static final String TAG = "JSDebuggerWebSocketClient";
|
||||
private static final JsonFactory mJsonFactory = new JsonFactory();
|
||||
|
||||
public interface JSDebuggerCallback {
|
||||
void onSuccess(@Nullable String response);
|
||||
void onFailure(Throwable cause);
|
||||
}
|
||||
|
||||
private @Nullable WebSocket mWebSocket;
|
||||
private @Nullable OkHttpClient mHttpClient;
|
||||
private @Nullable JSDebuggerCallback mConnectCallback;
|
||||
private final AtomicInteger mRequestID = new AtomicInteger();
|
||||
private final ConcurrentHashMap<Integer, JSDebuggerCallback> mCallbacks =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
public void connect(String url, JSDebuggerCallback callback) {
|
||||
if (mHttpClient != null) {
|
||||
throw new IllegalStateException("JSDebuggerWebSocketClient is already initialized.");
|
||||
}
|
||||
mConnectCallback = callback;
|
||||
mHttpClient = new OkHttpClient();
|
||||
mHttpClient.setConnectTimeout(10, TimeUnit.SECONDS);
|
||||
mHttpClient.setWriteTimeout(10, TimeUnit.SECONDS);
|
||||
// Disable timeouts for read
|
||||
mHttpClient.setReadTimeout(0, TimeUnit.MINUTES);
|
||||
|
||||
Request request = new Request.Builder().url(url).build();
|
||||
WebSocketCall call = WebSocketCall.create(mHttpClient, request);
|
||||
call.enqueue(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the next JSON message to send to remote JS executor, with request ID pre-filled in.
|
||||
*/
|
||||
private JsonGenerator startMessageObject(int requestID) throws IOException {
|
||||
JsonGenerator jg = mJsonFactory.createGenerator(new StringWriter());
|
||||
jg.writeStartObject();
|
||||
jg.writeNumberField("id", requestID);
|
||||
return jg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in a JsonGenerator created by {@link #startMessageObject} and returns the stringified
|
||||
* JSON
|
||||
*/
|
||||
private String endMessageObject(JsonGenerator jg) throws IOException {
|
||||
jg.writeEndObject();
|
||||
jg.flush();
|
||||
return ((StringWriter) jg.getOutputTarget()).getBuffer().toString();
|
||||
}
|
||||
|
||||
public void prepareJSRuntime(JSDebuggerCallback callback) {
|
||||
int requestID = mRequestID.getAndIncrement();
|
||||
mCallbacks.put(requestID, callback);
|
||||
|
||||
try {
|
||||
JsonGenerator jg = startMessageObject(requestID);
|
||||
jg.writeStringField("method", "prepareJSRuntime");
|
||||
sendMessage(requestID, endMessageObject(jg));
|
||||
} catch (IOException e) {
|
||||
triggerRequestFailure(requestID, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void executeApplicationScript(
|
||||
String sourceURL,
|
||||
HashMap<String, String> injectedObjects,
|
||||
JSDebuggerCallback callback) {
|
||||
int requestID = mRequestID.getAndIncrement();
|
||||
mCallbacks.put(requestID, callback);
|
||||
|
||||
try {
|
||||
JsonGenerator jg = startMessageObject(requestID);
|
||||
jg.writeStringField("method", "executeApplicationScript");
|
||||
jg.writeStringField("url", sourceURL);
|
||||
jg.writeObjectFieldStart("inject");
|
||||
for (String key : injectedObjects.keySet()) {
|
||||
jg.writeObjectField(key, injectedObjects.get(key));
|
||||
}
|
||||
jg.writeEndObject();
|
||||
sendMessage(requestID, endMessageObject(jg));
|
||||
} catch (IOException e) {
|
||||
triggerRequestFailure(requestID, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void executeJSCall(
|
||||
String moduleName,
|
||||
String methodName,
|
||||
String jsonArgsArray,
|
||||
JSDebuggerCallback callback) {
|
||||
|
||||
int requestID = mRequestID.getAndIncrement();
|
||||
mCallbacks.put(requestID, callback);
|
||||
|
||||
try {
|
||||
JsonGenerator jg = startMessageObject(requestID);
|
||||
jg.writeStringField("method","executeJSCall");
|
||||
jg.writeStringField("moduleName", moduleName);
|
||||
jg.writeStringField("moduleMethod", methodName);
|
||||
jg.writeFieldName("arguments");
|
||||
jg.writeRawValue(jsonArgsArray);
|
||||
sendMessage(requestID, endMessageObject(jg));
|
||||
} catch (IOException e) {
|
||||
triggerRequestFailure(requestID, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void closeQuietly() {
|
||||
if (mWebSocket != null) {
|
||||
try {
|
||||
mWebSocket.close(1000, "End of session");
|
||||
} catch (IOException e) {
|
||||
// swallow, no need to handle it here
|
||||
}
|
||||
mWebSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(int requestID, String message) {
|
||||
if (mWebSocket == null) {
|
||||
triggerRequestFailure(
|
||||
requestID,
|
||||
new IllegalStateException("WebSocket connection no longer valid"));
|
||||
return;
|
||||
}
|
||||
Buffer messageBuffer = new Buffer();
|
||||
messageBuffer.writeUtf8(message);
|
||||
try {
|
||||
mWebSocket.sendMessage(WebSocket.PayloadType.TEXT, messageBuffer);
|
||||
} catch (IOException e) {
|
||||
triggerRequestFailure(requestID, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerRequestFailure(int requestID, Throwable cause) {
|
||||
JSDebuggerCallback callback = mCallbacks.get(requestID);
|
||||
if (callback != null) {
|
||||
mCallbacks.remove(requestID);
|
||||
callback.onFailure(cause);
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerRequestSuccess(int requestID, @Nullable String response) {
|
||||
JSDebuggerCallback callback = mCallbacks.get(requestID);
|
||||
if (callback != null) {
|
||||
mCallbacks.remove(requestID);
|
||||
callback.onSuccess(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(BufferedSource payload, WebSocket.PayloadType type) throws IOException {
|
||||
if (type != WebSocket.PayloadType.TEXT) {
|
||||
FLog.w(TAG, "Websocket received unexpected message with payload of type " + type);
|
||||
return;
|
||||
}
|
||||
|
||||
String message = null;
|
||||
try {
|
||||
message = payload.readUtf8();
|
||||
} finally {
|
||||
payload.close();
|
||||
}
|
||||
Integer replyID = null;
|
||||
|
||||
try {
|
||||
JsonParser parser = new JsonFactory().createParser(message);
|
||||
String result = null;
|
||||
while (parser.nextToken() != JsonToken.END_OBJECT) {
|
||||
String field = parser.getCurrentName();
|
||||
if ("replyID".equals(field)) {
|
||||
parser.nextToken();
|
||||
replyID = parser.getIntValue();
|
||||
} else if ("result".equals(field)) {
|
||||
parser.nextToken();
|
||||
result = parser.getText();
|
||||
}
|
||||
}
|
||||
if (replyID != null) {
|
||||
triggerRequestSuccess(replyID, result);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (replyID != null) {
|
||||
triggerRequestFailure(replyID, e);
|
||||
} else {
|
||||
abort("Parsing response message from websocket failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(IOException e, Response response) {
|
||||
abort("Websocket exception", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
mWebSocket = webSocket;
|
||||
Assertions.assertNotNull(mConnectCallback).onSuccess(null);
|
||||
mConnectCallback = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int code, String reason) {
|
||||
mWebSocket = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPong(Buffer payload) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
private void abort(String message, Throwable cause) {
|
||||
FLog.e(TAG, "Error occurred, shutting down websocket connection: " + message, cause);
|
||||
closeQuietly();
|
||||
|
||||
// Trigger failure callbacks
|
||||
if (mConnectCallback != null) {
|
||||
mConnectCallback.onFailure(cause);
|
||||
mConnectCallback = null;
|
||||
}
|
||||
for (JSDebuggerCallback callback : mCallbacks.values()) {
|
||||
callback.onFailure(cause);
|
||||
}
|
||||
mCallbacks.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.jni.Countable;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
@DoNotStrip
|
||||
public abstract class JavaScriptExecutor extends Countable {
|
||||
|
||||
/**
|
||||
* Close this executor and cleanup any resources that it was using. No further calls are
|
||||
* expected after this.
|
||||
*/
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
/**
|
||||
* Interface denoting that a class is the interface to a module with the same name in JS. Calling
|
||||
* functions on this interface will result in corresponding methods in JS being called.
|
||||
*
|
||||
* When extending JavaScriptModule and registering it with a CatalystInstance, all public methods
|
||||
* are assumed to be implemented on a JS module with the same name as this class. Calling methods
|
||||
* on the object returned from {@link ReactContext#getJSModule} or
|
||||
* {@link CatalystInstance#getJSModule} will result in the methods with those names exported by
|
||||
* that module being called in JS.
|
||||
*
|
||||
* NB: JavaScriptModule does not allow method name overloading because JS does not allow method name
|
||||
* overloading.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public interface JavaScriptModule {
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
|
||||
/**
|
||||
* Registration info for a {@link JavaScriptModule}. Maps its methods to method ids.
|
||||
*/
|
||||
@Immutable
|
||||
class JavaScriptModuleRegistration {
|
||||
|
||||
private final int mModuleId;
|
||||
private final Class<? extends JavaScriptModule> mModuleInterface;
|
||||
private final Map<Method, Integer> mMethodsToIds;
|
||||
private final Map<Method, String> mMethodsToTracingNames;
|
||||
|
||||
JavaScriptModuleRegistration(int moduleId, Class<? extends JavaScriptModule> moduleInterface) {
|
||||
mModuleId = moduleId;
|
||||
mModuleInterface = moduleInterface;
|
||||
|
||||
mMethodsToIds = MapBuilder.newHashMap();
|
||||
mMethodsToTracingNames = MapBuilder.newHashMap();
|
||||
final Method[] declaredMethods = mModuleInterface.getDeclaredMethods();
|
||||
Arrays.sort(declaredMethods, new Comparator<Method>() {
|
||||
@Override
|
||||
public int compare(Method lhs, Method rhs) {
|
||||
return lhs.getName().compareTo(rhs.getName());
|
||||
}
|
||||
});
|
||||
|
||||
// Methods are sorted by name so we can dupe check and have obvious ordering
|
||||
String previousName = null;
|
||||
for (int i = 0; i < declaredMethods.length; i++) {
|
||||
Method method = declaredMethods[i];
|
||||
String name = method.getName();
|
||||
Assertions.assertCondition(
|
||||
!name.equals(previousName),
|
||||
"Method overloading is unsupported: " + mModuleInterface.getName() + "#" + name);
|
||||
previousName = name;
|
||||
|
||||
mMethodsToIds.put(method, i);
|
||||
mMethodsToTracingNames.put(method, "JSCall__" + getName() + "_" + method.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public int getModuleId() {
|
||||
return mModuleId;
|
||||
}
|
||||
|
||||
public int getMethodId(Method method) {
|
||||
final Integer id = mMethodsToIds.get(method);
|
||||
Assertions.assertNotNull(id, "Unknown method: " + method.getName());
|
||||
return id.intValue();
|
||||
}
|
||||
|
||||
public String getTracingName(Method method) {
|
||||
return Assertions.assertNotNull(mMethodsToTracingNames.get(method));
|
||||
}
|
||||
|
||||
public Class<? extends JavaScriptModule> getModuleInterface() {
|
||||
return mModuleInterface;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
// With proguard obfuscation turned on, proguard apparently (poorly) emulates inner classes or
|
||||
// something because Class#getSimpleName() no longer strips the outer class name. We manually
|
||||
// strip it here if necessary.
|
||||
String name = mModuleInterface.getSimpleName();
|
||||
int dollarSignIndex = name.lastIndexOf('$');
|
||||
if (dollarSignIndex != -1) {
|
||||
name = name.substring(dollarSignIndex + 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public Set<Method> getMethods() {
|
||||
return mMethodsToIds.keySet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
|
||||
/**
|
||||
* Class responsible for holding all the {@link JavaScriptModule}s registered to this
|
||||
* {@link CatalystInstance}. Uses Java proxy objects to dispatch method calls on JavaScriptModules
|
||||
* to the bridge using the corresponding module and method ids so the proper function is executed in
|
||||
* JavaScript.
|
||||
*/
|
||||
/*package*/ class JavaScriptModuleRegistry {
|
||||
|
||||
private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;
|
||||
|
||||
public JavaScriptModuleRegistry(
|
||||
CatalystInstance instance,
|
||||
JavaScriptModulesConfig config) {
|
||||
mModuleInstances = 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);
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends JavaScriptModule> T getJavaScriptModule(Class<T> moduleInterface) {
|
||||
return (T) Assertions.assertNotNull(
|
||||
mModuleInstances.get(moduleInterface),
|
||||
"JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
|
||||
}
|
||||
|
||||
private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final CatalystInstance mCatalystInstance;
|
||||
private final JavaScriptModuleRegistration mModuleRegistration;
|
||||
|
||||
public JavaScriptModuleInvocationHandler(
|
||||
CatalystInstance catalystInstance,
|
||||
JavaScriptModuleRegistration moduleRegistration) {
|
||||
mCatalystInstance = catalystInstance;
|
||||
mModuleRegistration = moduleRegistration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
String tracingName = mModuleRegistration.getTracingName(method);
|
||||
mCatalystInstance.callFunction(
|
||||
mModuleRegistration.getModuleId(),
|
||||
mModuleRegistration.getMethodId(method),
|
||||
Arguments.fromJavaArgs(args),
|
||||
tracingName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
||||
/**
|
||||
* Class stores configuration of javascript modules that can be used across the bridge
|
||||
*/
|
||||
public class JavaScriptModulesConfig {
|
||||
|
||||
private final List<JavaScriptModuleRegistration> mModules;
|
||||
|
||||
private JavaScriptModulesConfig(List<JavaScriptModuleRegistration> modules) {
|
||||
mModules = modules;
|
||||
}
|
||||
|
||||
/*package*/ List<JavaScriptModuleRegistration> getModuleDefinitions() {
|
||||
return mModules;
|
||||
}
|
||||
|
||||
/*package*/ String moduleDescriptions() {
|
||||
JsonFactory jsonFactory = new JsonFactory();
|
||||
StringWriter writer = new StringWriter();
|
||||
try {
|
||||
JsonGenerator jg = jsonFactory.createGenerator(writer);
|
||||
jg.writeStartObject();
|
||||
for (JavaScriptModuleRegistration registration : mModules) {
|
||||
jg.writeObjectFieldStart(registration.getName());
|
||||
appendJSModuleToJSONObject(jg, registration);
|
||||
jg.writeEndObject();
|
||||
}
|
||||
jg.writeEndObject();
|
||||
jg.close();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
|
||||
}
|
||||
return writer.getBuffer().toString();
|
||||
}
|
||||
|
||||
private void appendJSModuleToJSONObject(
|
||||
JsonGenerator jg,
|
||||
JavaScriptModuleRegistration registration) throws IOException {
|
||||
jg.writeObjectField("moduleID", registration.getModuleId());
|
||||
jg.writeObjectFieldStart("methods");
|
||||
for (Method method : registration.getMethods()) {
|
||||
jg.writeObjectFieldStart(method.getName());
|
||||
jg.writeObjectField("methodID", registration.getMethodId(method));
|
||||
jg.writeEndObject();
|
||||
}
|
||||
jg.writeEndObject();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private int mLastJSModuleId = 0;
|
||||
private List<JavaScriptModuleRegistration> mModules =
|
||||
new ArrayList<JavaScriptModuleRegistration>();
|
||||
|
||||
public Builder add(Class<? extends JavaScriptModule> moduleInterfaceClass) {
|
||||
int moduleId = mLastJSModuleId++;
|
||||
mModules.add(new JavaScriptModuleRegistration(moduleId, moduleInterfaceClass));
|
||||
return this;
|
||||
}
|
||||
|
||||
public JavaScriptModulesConfig build() {
|
||||
return new JavaScriptModulesConfig(mModules);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
||||
/**
|
||||
* Helper for generating JSON for lists and maps.
|
||||
*/
|
||||
public class JsonGeneratorHelper {
|
||||
|
||||
/**
|
||||
* Like {@link JsonGenerator#writeObjectField(String, Object)} but supports Maps and Lists.
|
||||
*/
|
||||
public static void writeObjectField(JsonGenerator jg, String name, Object object)
|
||||
throws IOException {
|
||||
if (object instanceof Map) {
|
||||
writeMap(jg, name, (Map) object);
|
||||
} else if (object instanceof List) {
|
||||
writeList(jg, name, (List) object);
|
||||
} else {
|
||||
jg.writeObjectField(name, object);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeMap(JsonGenerator jg, String name, Map map) throws IOException {
|
||||
jg.writeObjectFieldStart(name);
|
||||
Set<Map.Entry> entries = map.entrySet();
|
||||
for (Map.Entry entry : entries) {
|
||||
writeObjectField(jg, entry.getKey().toString(), entry.getValue());
|
||||
}
|
||||
jg.writeEndObject();
|
||||
}
|
||||
|
||||
private static void writeList(JsonGenerator jg, String name, List list) throws IOException {
|
||||
jg.writeArrayFieldStart(name);
|
||||
for (Object item : list) {
|
||||
jg.writeObject(item);
|
||||
}
|
||||
jg.writeEndArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* Listener for receiving activity/service lifecycle events.
|
||||
*/
|
||||
public interface LifecycleEventListener {
|
||||
|
||||
/**
|
||||
* Called when host (activity/service) receives resume event (e.g. {@link Activity#onResume}
|
||||
*/
|
||||
void onHostResume();
|
||||
|
||||
/**
|
||||
* Called when host (activity/service) receives pause event (e.g. {@link Activity#onPause}
|
||||
*/
|
||||
void onHostPause();
|
||||
|
||||
/**
|
||||
* Called when host (activity/service) receives destroy event (e.g. {@link Activity#onDestroy}
|
||||
*/
|
||||
void onHostDestroy();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Exception thrown when a native module method call receives unexpected arguments from JS.
|
||||
*/
|
||||
public class NativeArgumentsParseException extends JSApplicationCausedNativeException {
|
||||
|
||||
public NativeArgumentsParseException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public NativeArgumentsParseException(@Nullable String detailMessage, @Nullable Throwable t) {
|
||||
super(detailMessage, t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
/**
|
||||
* Base class for an array whose members are stored in native code (C++).
|
||||
*/
|
||||
@DoNotStrip
|
||||
public abstract class NativeArray {
|
||||
static {
|
||||
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
public NativeArray() {
|
||||
mHybridData = initHybrid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public native String toString();
|
||||
|
||||
private native HybridData initHybrid();
|
||||
|
||||
@DoNotStrip
|
||||
private HybridData mHybridData;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.jni.Countable;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
/**
|
||||
* Base class for a Map whose keys and values are stored in native code (C++).
|
||||
*/
|
||||
@DoNotStrip
|
||||
public abstract class NativeMap extends Countable {
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
public NativeMap() {
|
||||
initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public native String toString();
|
||||
|
||||
private native void initialize();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
||||
/**
|
||||
* A native module whose API can be provided to JS catalyst instances. {@link NativeModule}s whose
|
||||
* implementation is written in Java should extend {@link BaseJavaModule} or {@link
|
||||
* ReactContextBaseJavaModule}. {@link NativeModule}s whose implementation is written in C++
|
||||
* must not provide any Java code (so they can be reused on other platforms), and instead should
|
||||
* register themselves using {@link CxxModuleWrapper}.
|
||||
*/
|
||||
public interface NativeModule {
|
||||
public static interface NativeMethod {
|
||||
void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of this module. This will be the name used to {@code require()} this module
|
||||
* from javascript.
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* @return methods callable from JS on this module
|
||||
*/
|
||||
public Map<String, NativeMethod> getMethods();
|
||||
|
||||
/**
|
||||
* Append a field which represents the constants this module exports
|
||||
* to JS. If no constants are exported this should do nothing.
|
||||
*/
|
||||
public void writeConstantsField(JsonGenerator jg, String fieldName) throws IOException;
|
||||
|
||||
/**
|
||||
* This is called at the end of {@link CatalystApplicationFragment#createCatalystInstance()}
|
||||
* after the CatalystInstance has been created, in order to initialize NativeModules that require
|
||||
* the CatalystInstance or JS modules.
|
||||
*/
|
||||
public void initialize();
|
||||
|
||||
/**
|
||||
* Called before {CatalystInstance#onHostDestroy}
|
||||
*/
|
||||
public void onCatalystInstanceDestroy();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* Interface for a class that knows how to handle an Exception thrown by a native module invoked
|
||||
* from JS. Since these Exceptions are triggered by JS calls (and can be fixed in JS), a
|
||||
* common way to handle one is to show a error dialog and allow the developer to change and reload
|
||||
* JS.
|
||||
*
|
||||
* We should also note that we have a unique stance on what 'caused' means: even if there's a bug in
|
||||
* the framework/native code, it was triggered by JS and theoretically since we were able to set up
|
||||
* the bridge, JS could change its logic, reload, and not trigger that crash.
|
||||
*/
|
||||
public interface NativeModuleCallExceptionHandler {
|
||||
|
||||
/**
|
||||
* Do something to display or log the exception.
|
||||
*/
|
||||
void handleException(Exception e);
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.common.SetBuilder;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.systrace.Systrace;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
||||
/**
|
||||
* A set of Java APIs to expose to a particular JavaScript instance.
|
||||
*/
|
||||
public class NativeModuleRegistry {
|
||||
|
||||
private final ArrayList<ModuleDefinition> mModuleTable;
|
||||
private final Map<Class<NativeModule>, NativeModule> mModuleInstances;
|
||||
private final String mModuleDescriptions;
|
||||
private final ArrayList<OnBatchCompleteListener> mBatchCompleteListenerModules;
|
||||
|
||||
private NativeModuleRegistry(
|
||||
ArrayList<ModuleDefinition> moduleTable,
|
||||
Map<Class<NativeModule>, NativeModule> moduleInstances,
|
||||
String moduleDescriptions) {
|
||||
mModuleTable = moduleTable;
|
||||
mModuleInstances = moduleInstances;
|
||||
mModuleDescriptions = moduleDescriptions;
|
||||
|
||||
mBatchCompleteListenerModules = new ArrayList<OnBatchCompleteListener>(mModuleTable.size());
|
||||
for (int i = 0; i < mModuleTable.size(); i++) {
|
||||
ModuleDefinition definition = mModuleTable.get(i);
|
||||
if (definition.target instanceof OnBatchCompleteListener) {
|
||||
mBatchCompleteListenerModules.add((OnBatchCompleteListener) definition.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void call(
|
||||
CatalystInstance catalystInstance,
|
||||
int moduleId,
|
||||
int methodId,
|
||||
ReadableNativeArray parameters) {
|
||||
ModuleDefinition definition = mModuleTable.get(moduleId);
|
||||
if (definition == null) {
|
||||
throw new RuntimeException("Call to unknown module: " + moduleId);
|
||||
}
|
||||
definition.call(catalystInstance, methodId, parameters);
|
||||
}
|
||||
|
||||
/* package */ String moduleDescriptions() {
|
||||
return mModuleDescriptions;
|
||||
}
|
||||
|
||||
/* package */ void notifyCatalystInstanceDestroy() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
for (NativeModule nativeModule : mModuleInstances.values()) {
|
||||
nativeModule.onCatalystInstanceDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void notifyCatalystInstanceInitialized() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
for (NativeModule nativeModule : mModuleInstances.values()) {
|
||||
nativeModule.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
public void onBatchComplete() {
|
||||
for (int i = 0; i < mBatchCompleteListenerModules.size(); i++) {
|
||||
mBatchCompleteListenerModules.get(i).onBatchComplete();
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends NativeModule> T getModule(Class<T> moduleInterface) {
|
||||
return (T) Assertions.assertNotNull(mModuleInstances.get(moduleInterface));
|
||||
}
|
||||
|
||||
public Collection<NativeModule> getAllModules() {
|
||||
return mModuleInstances.values();
|
||||
}
|
||||
|
||||
private static class ModuleDefinition {
|
||||
public final int id;
|
||||
public final String name;
|
||||
public final NativeModule target;
|
||||
public final ArrayList<MethodRegistration> methods;
|
||||
|
||||
public ModuleDefinition(int id, String name, NativeModule target) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.target = target;
|
||||
this.methods = new ArrayList<MethodRegistration>();
|
||||
|
||||
for (Map.Entry<String, NativeModule.NativeMethod> entry : target.getMethods().entrySet()) {
|
||||
this.methods.add(
|
||||
new MethodRegistration(
|
||||
entry.getKey(), "NativeCall__" + target.getName() + "_" + entry.getKey(),
|
||||
entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
public void call(
|
||||
CatalystInstance catalystInstance,
|
||||
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);
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class MethodRegistration {
|
||||
public MethodRegistration(String name, String tracingName, NativeModule.NativeMethod method) {
|
||||
this.name = name;
|
||||
this.tracingName = tracingName;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public String name;
|
||||
public String tracingName;
|
||||
public NativeModule.NativeMethod method;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private ArrayList<ModuleDefinition> mModuleDefinitions;
|
||||
private Map<Class<NativeModule>, NativeModule> mModuleInstances;
|
||||
private Set<String> mSeenModuleNames;
|
||||
|
||||
public Builder() {
|
||||
mModuleDefinitions = new ArrayList<ModuleDefinition>();
|
||||
mModuleInstances = MapBuilder.newHashMap();
|
||||
mSeenModuleNames = SetBuilder.newHashSet();
|
||||
}
|
||||
|
||||
public Builder add(NativeModule module) {
|
||||
ModuleDefinition registration = new ModuleDefinition(
|
||||
mModuleDefinitions.size(),
|
||||
module.getName(),
|
||||
module);
|
||||
Assertions.assertCondition(
|
||||
!mSeenModuleNames.contains(module.getName()),
|
||||
"Module " + module.getName() + " was already registered!");
|
||||
mSeenModuleNames.add(module.getName());
|
||||
mModuleDefinitions.add(registration);
|
||||
mModuleInstances.put((Class<NativeModule>) module.getClass(), module);
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeModuleRegistry build() {
|
||||
JsonFactory jsonFactory = new JsonFactory();
|
||||
StringWriter writer = new StringWriter();
|
||||
try {
|
||||
JsonGenerator jg = jsonFactory.createGenerator(writer);
|
||||
jg.writeStartObject();
|
||||
for (ModuleDefinition module : mModuleDefinitions) {
|
||||
jg.writeObjectFieldStart(module.name);
|
||||
jg.writeNumberField("moduleID", module.id);
|
||||
jg.writeObjectFieldStart("methods");
|
||||
for (int i = 0; i < module.methods.size(); i++) {
|
||||
MethodRegistration method = module.methods.get(i);
|
||||
jg.writeObjectFieldStart(method.name);
|
||||
jg.writeNumberField("methodID", i);
|
||||
jg.writeEndObject();
|
||||
}
|
||||
jg.writeEndObject();
|
||||
module.target.writeConstantsField(jg, "constants");
|
||||
jg.writeEndObject();
|
||||
}
|
||||
jg.writeEndObject();
|
||||
jg.close();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("Unable to serialize Java module configuration", ioe);
|
||||
}
|
||||
String moduleDefinitionJson = writer.getBuffer().toString();
|
||||
return new NativeModuleRegistry(mModuleDefinitions, mModuleInstances, moduleDefinitionJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
/**
|
||||
* Exception thrown by {@link ReadableNativeMap} when a key that does not exist is requested.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class NoSuchKeyException extends RuntimeException {
|
||||
|
||||
@DoNotStrip
|
||||
public NoSuchKeyException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* Interface for receiving notification for bridge idle/busy events. Should not affect application
|
||||
* logic and should only be used for debug/monitoring/testing purposes. Call
|
||||
* {@link CatalystInstance#addBridgeIdleDebugListener} to start monitoring.
|
||||
*
|
||||
* NB: onTransitionToBridgeIdle and onTransitionToBridgeBusy may be called from different threads,
|
||||
* and those threads may not be the same thread on which the listener was originally registered.
|
||||
*/
|
||||
public interface NotThreadSafeBridgeIdleDebugListener {
|
||||
|
||||
/**
|
||||
* Called once all pending JS calls have resolved via an onBatchComplete call in the bridge and
|
||||
* the requested native module calls have also run. The bridge will not become busy again until
|
||||
* a timer, touch event, etc. causes a Java->JS call to be enqueued again.
|
||||
*/
|
||||
void onTransitionToBridgeIdle();
|
||||
|
||||
/**
|
||||
* Called when the bridge was in an idle state and executes a JS call or callback.
|
||||
*/
|
||||
void onTransitionToBridgeBusy();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
/**
|
||||
* Exception thrown when a caller attempts to modify or use a {@link WritableNativeArray} or
|
||||
* {@link WritableNativeMap} after it has already been added to a parent array or map. This is
|
||||
* unsafe since we reuse the native memory so the underlying array/map is no longer valid.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class ObjectAlreadyConsumedException extends RuntimeException {
|
||||
|
||||
@DoNotStrip
|
||||
public ObjectAlreadyConsumedException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* Interface for a module that will be notified when a batch of JS->Java calls has finished.
|
||||
*/
|
||||
public interface OnBatchCompleteListener {
|
||||
|
||||
void onBatchComplete();
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
/**
|
||||
* JavaScript executor that delegates JS calls processed by native code back to a java version
|
||||
* of the native executor interface.
|
||||
*
|
||||
* When set as a executor with {@link CatalystInstance.Builder}, catalyst native code will delegate
|
||||
* low level javascript calls to the implementation of {@link JavaJSExecutor} interface provided
|
||||
* with the constructor of this class.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class ProxyJavaScriptExecutor extends JavaScriptExecutor {
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
public static class ProxyExecutorException extends Exception {
|
||||
public ProxyExecutorException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is class represents java version of native js executor interface. When set through
|
||||
* {@link ProxyJavaScriptExecutor} as a {@link CatalystInstance} executor, native code will
|
||||
* delegate js calls to the given implementation of this interface.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public interface JavaJSExecutor {
|
||||
/**
|
||||
* Close this executor and cleanup any resources that it was using. No further calls are
|
||||
* expected after this.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Load javascript into the js context
|
||||
* @param script script contet to be executed
|
||||
* @param sourceURL url or file location from which script content was loaded
|
||||
*/
|
||||
@DoNotStrip
|
||||
void executeApplicationScript(String script, String sourceURL) throws ProxyExecutorException;
|
||||
|
||||
/**
|
||||
* Execute javascript method within js context
|
||||
* @param modulename name of the common-js like module to execute the method from
|
||||
* @param methodName name of the method to be executed
|
||||
* @param jsonArgsArray json encoded array of arguments provided for the method call
|
||||
* @return json encoded value returned from the method call
|
||||
*/
|
||||
@DoNotStrip
|
||||
String executeJSCall(String modulename, String methodName, String jsonArgsArray)
|
||||
throws ProxyExecutorException;
|
||||
|
||||
@DoNotStrip
|
||||
void setGlobalVariable(String propertyName, String jsonEncodedValue);
|
||||
}
|
||||
|
||||
private @Nullable JavaJSExecutor mJavaJSExecutor;
|
||||
|
||||
/**
|
||||
* Create {@link ProxyJavaScriptExecutor} instance
|
||||
* @param executor implementation of {@link JavaJSExecutor} which will be responsible for handling
|
||||
* javascript calls
|
||||
*/
|
||||
public ProxyJavaScriptExecutor(JavaJSExecutor executor) {
|
||||
mJavaJSExecutor = executor;
|
||||
initialize(executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (mJavaJSExecutor != null) {
|
||||
mJavaJSExecutor.close();
|
||||
mJavaJSExecutor = null;
|
||||
}
|
||||
}
|
||||
|
||||
private native void initialize(JavaJSExecutor executor);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* A context wrapper that always wraps Android Application {@link Context} and
|
||||
* {@link CatalystInstance} by extending {@link ReactContext}
|
||||
*/
|
||||
public class ReactApplicationContext extends ReactContext {
|
||||
// We want to wrap ApplicationContext, since there is no easy way to verify that application
|
||||
// context is passed as a param, we use {@link Context#getApplicationContext} to ensure that
|
||||
// the context we're wrapping is in fact an application context.
|
||||
public ReactApplicationContext(Context context) {
|
||||
super(context.getApplicationContext());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import com.facebook.react.bridge.queue.MessageQueueThread;
|
||||
import com.facebook.jni.Countable;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
/**
|
||||
* Interface to the JS execution environment and means of transport for messages Java<->JS.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class ReactBridge extends Countable {
|
||||
|
||||
/* package */ static final String REACT_NATIVE_LIB = "reactnativejni";
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
private final ReactCallback mCallback;
|
||||
private final JavaScriptExecutor mJSExecutor;
|
||||
private final MessageQueueThread mNativeModulesQueueThread;
|
||||
|
||||
/**
|
||||
* @param jsExecutor the JS executor to use to run JS
|
||||
* @param callback the callback class used to invoke native modules
|
||||
* @param nativeModulesQueueThread the MessageQueueThread the callbacks should be invoked on
|
||||
*/
|
||||
public ReactBridge(
|
||||
JavaScriptExecutor jsExecutor,
|
||||
ReactCallback callback,
|
||||
MessageQueueThread nativeModulesQueueThread) {
|
||||
mJSExecutor = jsExecutor;
|
||||
mCallback = callback;
|
||||
mNativeModulesQueueThread = nativeModulesQueueThread;
|
||||
initialize(jsExecutor, callback, mNativeModulesQueueThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
mJSExecutor.close();
|
||||
mJSExecutor.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private native void initialize(
|
||||
JavaScriptExecutor jsExecutor,
|
||||
ReactCallback callback,
|
||||
MessageQueueThread nativeModulesQueueThread);
|
||||
public native void loadScriptFromAssets(AssetManager assetManager, String assetName);
|
||||
public native void loadScriptFromNetworkCached(String sourceURL, @Nullable String tempFileName);
|
||||
public native void callFunction(int moduleId, int methodId, NativeArray arguments);
|
||||
public native void invokeCallback(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);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
@DoNotStrip
|
||||
public interface ReactCallback {
|
||||
|
||||
@DoNotStrip
|
||||
void call(int moduleId, int methodId, ReadableNativeArray parameters);
|
||||
|
||||
@DoNotStrip
|
||||
void onBatchComplete();
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import com.facebook.react.bridge.queue.CatalystQueueConfiguration;
|
||||
import com.facebook.react.bridge.queue.MessageQueueThread;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
|
||||
/**
|
||||
* Abstract ContextWrapper for Android applicaiton or activity {@link Context} and
|
||||
* {@link CatalystInstance}
|
||||
*/
|
||||
public class ReactContext extends ContextWrapper {
|
||||
|
||||
private final CopyOnWriteArraySet<LifecycleEventListener> mLifecycleEventListeners =
|
||||
new CopyOnWriteArraySet<>();
|
||||
|
||||
private @Nullable CatalystInstance mCatalystInstance;
|
||||
private @Nullable LayoutInflater mInflater;
|
||||
private @Nullable MessageQueueThread mUiMessageQueueThread;
|
||||
private @Nullable MessageQueueThread mNativeModulesMessageQueueThread;
|
||||
private @Nullable MessageQueueThread mJSMessageQueueThread;
|
||||
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
|
||||
|
||||
public ReactContext(Context base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set and initialize CatalystInstance for this Context. This should be called exactly once.
|
||||
*/
|
||||
public void initializeWithInstance(CatalystInstance catalystInstance) {
|
||||
if (catalystInstance == null) {
|
||||
throw new IllegalArgumentException("CatalystInstance cannot be null.");
|
||||
}
|
||||
if (mCatalystInstance != null) {
|
||||
throw new IllegalStateException("ReactContext has been already initialized");
|
||||
}
|
||||
|
||||
mCatalystInstance = catalystInstance;
|
||||
|
||||
CatalystQueueConfiguration queueConfig = catalystInstance.getCatalystQueueConfiguration();
|
||||
mUiMessageQueueThread = queueConfig.getUIQueueThread();
|
||||
mNativeModulesMessageQueueThread = queueConfig.getNativeModulesQueueThread();
|
||||
mJSMessageQueueThread = queueConfig.getJSQueueThread();
|
||||
}
|
||||
|
||||
public void setNativeModuleCallExceptionHandler(
|
||||
@Nullable NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
|
||||
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
|
||||
}
|
||||
|
||||
// We override the following method so that views inflated with the inflater obtained from this
|
||||
// context return the ReactContext in #getContext(). The default implementation uses the base
|
||||
// context instead, so it couldn't be cast to ReactContext.
|
||||
// TODO: T7538796 Check requirement for Override of getSystemService ReactContext
|
||||
@Override
|
||||
public Object getSystemService(String name) {
|
||||
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
|
||||
if (mInflater == null) {
|
||||
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
|
||||
}
|
||||
return mInflater;
|
||||
}
|
||||
return getBaseContext().getSystemService(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return handle to the specified JS module for the CatalystInstance associated with this Context
|
||||
*/
|
||||
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
|
||||
if (mCatalystInstance == null) {
|
||||
throw new RuntimeException("Trying to invoke JS before CatalystInstance has been set!");
|
||||
}
|
||||
return mCatalystInstance.getJSModule(jsInterface);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the instance of the specified module interface associated with this ReactContext.
|
||||
*/
|
||||
public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
|
||||
if (mCatalystInstance == null) {
|
||||
throw new RuntimeException("Trying to invoke JS before CatalystInstance has been set!");
|
||||
}
|
||||
return mCatalystInstance.getNativeModule(nativeModuleInterface);
|
||||
}
|
||||
|
||||
public CatalystInstance getCatalystInstance() {
|
||||
return Assertions.assertNotNull(mCatalystInstance);
|
||||
}
|
||||
|
||||
public boolean hasActiveCatalystInstance() {
|
||||
return mCatalystInstance != null && !mCatalystInstance.isDestroyed();
|
||||
}
|
||||
|
||||
public void addLifecycleEventListener(LifecycleEventListener listener) {
|
||||
mLifecycleEventListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeLifecycleEventListener(LifecycleEventListener listener) {
|
||||
mLifecycleEventListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called by the hosting Fragment in {@link Fragment#onResume}
|
||||
*/
|
||||
public void onResume() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
for (LifecycleEventListener listener : mLifecycleEventListeners) {
|
||||
listener.onHostResume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called by the hosting Fragment in {@link Fragment#onPause}
|
||||
*/
|
||||
public void onPause() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
for (LifecycleEventListener listener : mLifecycleEventListeners) {
|
||||
listener.onHostPause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called by the hosting Fragment in {@link Fragment#onDestroy}
|
||||
*/
|
||||
public void onDestroy() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
for (LifecycleEventListener listener : mLifecycleEventListeners) {
|
||||
listener.onHostDestroy();
|
||||
}
|
||||
if (mCatalystInstance != null) {
|
||||
mCatalystInstance.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public void assertOnUiQueueThread() {
|
||||
Assertions.assertNotNull(mUiMessageQueueThread).assertIsOnThread();
|
||||
}
|
||||
|
||||
public boolean isOnUiQueueThread() {
|
||||
return Assertions.assertNotNull(mUiMessageQueueThread).isOnThread();
|
||||
}
|
||||
|
||||
public void runOnUiQueueThread(Runnable runnable) {
|
||||
Assertions.assertNotNull(mUiMessageQueueThread).runOnQueue(runnable);
|
||||
}
|
||||
|
||||
public void assertOnNativeModulesQueueThread() {
|
||||
Assertions.assertNotNull(mNativeModulesMessageQueueThread).assertIsOnThread();
|
||||
}
|
||||
|
||||
public boolean isOnNativeModulesQueueThread() {
|
||||
return Assertions.assertNotNull(mNativeModulesMessageQueueThread).isOnThread();
|
||||
}
|
||||
|
||||
public void runOnNativeModulesQueueThread(Runnable runnable) {
|
||||
Assertions.assertNotNull(mNativeModulesMessageQueueThread).runOnQueue(runnable);
|
||||
}
|
||||
|
||||
public void assertOnJSQueueThread() {
|
||||
Assertions.assertNotNull(mJSMessageQueueThread).assertIsOnThread();
|
||||
}
|
||||
|
||||
public boolean isOnJSQueueThread() {
|
||||
return Assertions.assertNotNull(mJSMessageQueueThread).isOnThread();
|
||||
}
|
||||
|
||||
public void runOnJSQueueThread(Runnable runnable) {
|
||||
Assertions.assertNotNull(mJSMessageQueueThread).runOnQueue(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the given exception to the current
|
||||
* {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} if one exists, rethrowing
|
||||
* otherwise.
|
||||
*/
|
||||
public void handleException(RuntimeException e) {
|
||||
if (mCatalystInstance != null &&
|
||||
!mCatalystInstance.isDestroyed() &&
|
||||
mNativeModuleCallExceptionHandler != null) {
|
||||
mNativeModuleCallExceptionHandler.handleException(e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* Base class for Catalyst native modules that require access to the {@link ReactContext}
|
||||
* instance.
|
||||
*/
|
||||
public abstract class ReactContextBaseJavaModule extends BaseJavaModule {
|
||||
|
||||
private final ReactApplicationContext mReactApplicationContext;
|
||||
|
||||
public ReactContextBaseJavaModule(ReactApplicationContext reactContext) {
|
||||
mReactApplicationContext = reactContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can use this method to access catalyst context passed as a constructor
|
||||
*/
|
||||
protected final ReactApplicationContext getReactApplicationContext() {
|
||||
return mReactApplicationContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Annotation which is used to mark methods that are exposed to
|
||||
* Catalyst. This applies to derived classes of {@link
|
||||
* BaseJavaModule}, which will generate a list of exported methods by
|
||||
* searching for those which are annotated with this annotation and
|
||||
* adding a JS callback for each.
|
||||
*/
|
||||
@Retention(RUNTIME)
|
||||
public @interface ReactMethod {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* Interface for an array that allows typed access to its members. Used to pass parameters from JS
|
||||
* to Java.
|
||||
*/
|
||||
public interface ReadableArray {
|
||||
|
||||
int size();
|
||||
boolean isNull(int index);
|
||||
boolean getBoolean(int index);
|
||||
double getDouble(int index);
|
||||
int getInt(int index);
|
||||
String getString(int index);
|
||||
ReadableArray getArray(int index);
|
||||
ReadableMap getMap(int index);
|
||||
ReadableType getType(int index);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* Interface for a map that allows typed access to its members. Used to pass parameters from JS to
|
||||
* Java.
|
||||
*/
|
||||
public interface ReadableMap {
|
||||
|
||||
boolean hasKey(String name);
|
||||
boolean isNull(String name);
|
||||
boolean getBoolean(String name);
|
||||
double getDouble(String name);
|
||||
int getInt(String name);
|
||||
String getString(String name);
|
||||
ReadableArray getArray(String name);
|
||||
ReadableMap getMap(String name);
|
||||
ReadableType getType(String name);
|
||||
ReadableMapKeySeyIterator keySetIterator();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
/**
|
||||
* Interface of a iterator for a {@link NativeMap}'s key set.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public interface ReadableMapKeySeyIterator {
|
||||
|
||||
boolean hasNextKey();
|
||||
String nextKey();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
/**
|
||||
* Implementation of a NativeArray that allows read-only access to its members. This will generally
|
||||
* be constructed and filled in native code so you shouldn't construct one yourself.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class ReadableNativeArray extends NativeArray implements ReadableArray {
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public native int size();
|
||||
@Override
|
||||
public native boolean isNull(int index);
|
||||
@Override
|
||||
public native boolean getBoolean(int index);
|
||||
@Override
|
||||
public native double getDouble(int index);
|
||||
@Override
|
||||
public native String getString(int index);
|
||||
@Override
|
||||
public native ReadableNativeArray getArray(int index);
|
||||
@Override
|
||||
public native ReadableNativeMap getMap(int index);
|
||||
@Override
|
||||
public native ReadableType getType(int index);
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
return (int) getDouble(index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.jni.Countable;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
/**
|
||||
* Implementation of a read-only map in native memory. This will generally be constructed and filled
|
||||
* in native code so you shouldn't construct one yourself.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class ReadableNativeMap extends NativeMap implements ReadableMap {
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public native boolean hasKey(String name);
|
||||
@Override
|
||||
public native boolean isNull(String name);
|
||||
@Override
|
||||
public native boolean getBoolean(String name);
|
||||
@Override
|
||||
public native double getDouble(String name);
|
||||
@Override
|
||||
public native String getString(String name);
|
||||
@Override
|
||||
public native ReadableNativeArray getArray(String name);
|
||||
@Override
|
||||
public native ReadableNativeMap getMap(String name);
|
||||
@Override
|
||||
public native ReadableType getType(String name);
|
||||
|
||||
@Override
|
||||
public ReadableMapKeySeyIterator keySetIterator() {
|
||||
return new ReadableNativeMapKeySeyIterator(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String name) {
|
||||
return (int) getDouble(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of a {@link ReadableNativeMap} iterator in native memory.
|
||||
*/
|
||||
@DoNotStrip
|
||||
private static class ReadableNativeMapKeySeyIterator extends Countable
|
||||
implements ReadableMapKeySeyIterator {
|
||||
|
||||
private final ReadableNativeMap mReadableNativeMap;
|
||||
|
||||
public ReadableNativeMapKeySeyIterator(ReadableNativeMap readableNativeMap) {
|
||||
mReadableNativeMap = readableNativeMap;
|
||||
initialize(mReadableNativeMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public native boolean hasNextKey();
|
||||
@Override
|
||||
public native String nextKey();
|
||||
|
||||
private native void initialize(ReadableNativeMap readableNativeMap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
/**
|
||||
* Defines the type of an object stored in a {@link ReadableArray} or
|
||||
* {@link ReadableMap}.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public enum ReadableType {
|
||||
Null,
|
||||
Boolean,
|
||||
Number,
|
||||
String,
|
||||
Map,
|
||||
Array,
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Utility class to make assertions that should not hard-crash the app but instead be handled by the
|
||||
* Catalyst app {@link NativeModuleCallExceptionHandler}. See the javadoc on that class for
|
||||
* more information about our opinion on when these assertions should be used as opposed to
|
||||
* assertions that might throw AssertionError Throwables that will cause the app to hard crash.
|
||||
*/
|
||||
public class SoftAssertions {
|
||||
|
||||
/**
|
||||
* Asserts the given condition, throwing an {@link AssertionException} if the condition doesn't
|
||||
* hold.
|
||||
*/
|
||||
public static void assertCondition(boolean condition, String message) {
|
||||
if (!condition) {
|
||||
throw new AssertionException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given Object isn't null, throwing an {@link AssertionException} if it was.
|
||||
*/
|
||||
public static <T> T assertNotNull(@Nullable T instance) {
|
||||
if (instance == null) {
|
||||
throw new AssertionException("Expected object to not be null!");
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
/**
|
||||
* Utility for interacting with the UI thread.
|
||||
*/
|
||||
public class UiThreadUtil {
|
||||
|
||||
@Nullable private static Handler sMainHandler;
|
||||
|
||||
/**
|
||||
* @return whether the current thread is the UI thread.
|
||||
*/
|
||||
public static boolean isOnUiThread() {
|
||||
return Looper.getMainLooper().getThread() == Thread.currentThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link AssertionException} if the current thread is not the UI thread.
|
||||
*/
|
||||
public static void assertOnUiThread() {
|
||||
SoftAssertions.assertCondition(isOnUiThread(), "Expected to run on UI thread!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link AssertionException} if the current thread is the UI thread.
|
||||
*/
|
||||
public static void assertNotOnUiThread() {
|
||||
SoftAssertions.assertCondition(!isOnUiThread(), "Expected not to run on UI thread!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given Runnable on the UI thread.
|
||||
*/
|
||||
public static void runOnUiThread(Runnable runnable) {
|
||||
synchronized (UiThreadUtil.class) {
|
||||
if (sMainHandler == null) {
|
||||
sMainHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
}
|
||||
sMainHandler.post(runnable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
/**
|
||||
* Exception thrown from native code when a type retrieved from a map or array (e.g. via
|
||||
* {@link NativeArrayParameter#getString(int)}) does not match the expected type.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class UnexpectedNativeTypeException extends RuntimeException {
|
||||
|
||||
@DoNotStrip
|
||||
public UnexpectedNativeTypeException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
|
||||
/**
|
||||
* Executes JS remotely via the react nodejs server as a proxy to a browser on the host machine.
|
||||
*/
|
||||
public class WebsocketJavaScriptExecutor implements ProxyJavaScriptExecutor.JavaJSExecutor {
|
||||
|
||||
private static final long CONNECT_TIMEOUT_MS = 5000;
|
||||
private static final int CONNECT_RETRY_COUNT = 3;
|
||||
|
||||
public interface JSExecutorConnectCallback {
|
||||
void onSuccess();
|
||||
void onFailure(Throwable cause);
|
||||
}
|
||||
|
||||
public static class WebsocketExecutorTimeoutException extends Exception {
|
||||
public WebsocketExecutorTimeoutException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static class JSExecutorCallbackFuture implements
|
||||
JSDebuggerWebSocketClient.JSDebuggerCallback {
|
||||
|
||||
private final Semaphore mSemaphore = new Semaphore(0);
|
||||
private @Nullable Throwable mCause;
|
||||
private @Nullable String mResponse;
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable String response) {
|
||||
mResponse = response;
|
||||
mSemaphore.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable cause) {
|
||||
mCause = cause;
|
||||
mSemaphore.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call only once per object instance!
|
||||
*/
|
||||
public @Nullable String get() throws Throwable {
|
||||
mSemaphore.acquire();
|
||||
if (mCause != null) {
|
||||
throw mCause;
|
||||
}
|
||||
return mResponse;
|
||||
}
|
||||
}
|
||||
|
||||
final private HashMap<String, String> mInjectedObjects = new HashMap<>();
|
||||
private @Nullable JSDebuggerWebSocketClient mWebSocketClient;
|
||||
|
||||
public void connect(final String webSocketServerUrl, final JSExecutorConnectCallback callback) {
|
||||
final AtomicInteger retryCount = new AtomicInteger(CONNECT_RETRY_COUNT);
|
||||
final JSExecutorConnectCallback retryProxyCallback = new JSExecutorConnectCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable cause) {
|
||||
if (retryCount.decrementAndGet() <= 0) {
|
||||
callback.onFailure(cause);
|
||||
} else {
|
||||
connectInternal(webSocketServerUrl, this);
|
||||
}
|
||||
}
|
||||
};
|
||||
connectInternal(webSocketServerUrl, retryProxyCallback);
|
||||
}
|
||||
|
||||
private void connectInternal(
|
||||
String webSocketServerUrl,
|
||||
final JSExecutorConnectCallback callback) {
|
||||
final JSDebuggerWebSocketClient client = new JSDebuggerWebSocketClient();
|
||||
final Handler timeoutHandler = new Handler();
|
||||
client.connect(
|
||||
webSocketServerUrl, new JSDebuggerWebSocketClient.JSDebuggerCallback() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable String response) {
|
||||
client.prepareJSRuntime(
|
||||
new JSDebuggerWebSocketClient.JSDebuggerCallback() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable String response) {
|
||||
timeoutHandler.removeCallbacksAndMessages(null);
|
||||
mWebSocketClient = client;
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable cause) {
|
||||
timeoutHandler.removeCallbacksAndMessages(null);
|
||||
callback.onFailure(cause);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable cause) {
|
||||
callback.onFailure(cause);
|
||||
}
|
||||
});
|
||||
timeoutHandler.postDelayed(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
client.closeQuietly();
|
||||
callback.onFailure(
|
||||
new WebsocketExecutorTimeoutException(
|
||||
"Timeout while connecting to remote debugger"));
|
||||
}
|
||||
},
|
||||
CONNECT_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (mWebSocketClient != null) {
|
||||
mWebSocketClient.closeQuietly();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeApplicationScript(String script, String sourceURL)
|
||||
throws ProxyJavaScriptExecutor.ProxyExecutorException {
|
||||
JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture();
|
||||
Assertions.assertNotNull(mWebSocketClient).executeApplicationScript(
|
||||
sourceURL,
|
||||
mInjectedObjects,
|
||||
callback);
|
||||
try {
|
||||
callback.get();
|
||||
} catch (Throwable cause) {
|
||||
throw new ProxyJavaScriptExecutor.ProxyExecutorException(cause);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String executeJSCall(String moduleName, String methodName, String jsonArgsArray)
|
||||
throws ProxyJavaScriptExecutor.ProxyExecutorException {
|
||||
JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture();
|
||||
Assertions.assertNotNull(mWebSocketClient).executeJSCall(
|
||||
moduleName,
|
||||
methodName,
|
||||
jsonArgsArray,
|
||||
callback);
|
||||
try {
|
||||
return callback.get();
|
||||
} catch (Throwable cause) {
|
||||
throw new ProxyJavaScriptExecutor.ProxyExecutorException(cause);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGlobalVariable(String propertyName, String jsonEncodedValue) {
|
||||
// Store and use in the next executeApplicationScript() call.
|
||||
mInjectedObjects.put(propertyName, jsonEncodedValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* Interface for a mutable array. Used to pass arguments from Java to JS.
|
||||
*/
|
||||
public interface WritableArray extends ReadableArray {
|
||||
|
||||
void pushNull();
|
||||
void pushBoolean(boolean value);
|
||||
void pushDouble(double value);
|
||||
void pushInt(int value);
|
||||
void pushString(String value);
|
||||
void pushArray(WritableArray array);
|
||||
void pushMap(WritableMap map);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
/**
|
||||
* Interface for a mutable map. Used to pass arguments from Java to JS.
|
||||
*/
|
||||
public interface WritableMap extends ReadableMap {
|
||||
|
||||
void putNull(String key);
|
||||
void putBoolean(String key, boolean value);
|
||||
void putDouble(String key, double value);
|
||||
void putInt(String key, int value);
|
||||
void putString(String key, String value);
|
||||
void putArray(String key, WritableArray value);
|
||||
void putMap(String key, WritableMap value);
|
||||
|
||||
void merge(ReadableMap source);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
/**
|
||||
* Implementation of a write-only array stored in native memory. Use
|
||||
* {@link Arguments#createArray()} if you need to stub out creating this class in a test.
|
||||
* TODO(5815532): Check if consumed on read
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class WritableNativeArray extends ReadableNativeArray implements WritableArray {
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public native void pushNull();
|
||||
@Override
|
||||
public native void pushBoolean(boolean value);
|
||||
@Override
|
||||
public native void pushDouble(double value);
|
||||
@Override
|
||||
public native void pushString(String value);
|
||||
|
||||
@Override
|
||||
public void pushInt(int value) {
|
||||
pushDouble(value);
|
||||
}
|
||||
|
||||
// Note: this consumes the map so do not reuse it.
|
||||
@Override
|
||||
public void pushArray(WritableArray array) {
|
||||
Assertions.assertCondition(
|
||||
array == null || array instanceof WritableNativeArray, "Illegal type provided");
|
||||
pushNativeArray((WritableNativeArray) array);
|
||||
}
|
||||
|
||||
// Note: this consumes the map so do not reuse it.
|
||||
@Override
|
||||
public void pushMap(WritableMap map) {
|
||||
Assertions.assertCondition(
|
||||
map == null || map instanceof WritableNativeMap, "Illegal type provided");
|
||||
pushNativeMap((WritableNativeMap) map);
|
||||
}
|
||||
|
||||
private native void pushNativeArray(WritableNativeArray array);
|
||||
private native void pushNativeMap(WritableNativeMap map);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
/**
|
||||
* Implementation of a write-only map stored in native memory. Use
|
||||
* {@link Arguments#createMap()} if you need to stub out creating this class in a test.
|
||||
* TODO(5815532): Check if consumed on read
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class WritableNativeMap extends ReadableNativeMap implements WritableMap {
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public native void putBoolean(String key, boolean value);
|
||||
@Override
|
||||
public native void putDouble(String key, double value);
|
||||
@Override
|
||||
public native void putString(String key, String value);
|
||||
@Override
|
||||
public native void putNull(String key);
|
||||
|
||||
@Override
|
||||
public void putInt(String key, int value) {
|
||||
putDouble(key, value);
|
||||
}
|
||||
|
||||
// Note: this consumes the map so do not reuse it.
|
||||
@Override
|
||||
public void putMap(String key, WritableMap value) {
|
||||
Assertions.assertCondition(
|
||||
value == null || value instanceof WritableNativeMap, "Illegal type provided");
|
||||
putNativeMap(key, (WritableNativeMap) value);
|
||||
}
|
||||
|
||||
// Note: this consumes the map so do not reuse it.
|
||||
@Override
|
||||
public void putArray(String key, WritableArray value) {
|
||||
Assertions.assertCondition(
|
||||
value == null || value instanceof WritableNativeArray, "Illegal type provided");
|
||||
putNativeArray(key, (WritableNativeArray) value);
|
||||
}
|
||||
|
||||
// Note: this **DOES NOT** consume the source map
|
||||
@Override
|
||||
public void merge(ReadableMap source) {
|
||||
Assertions.assertCondition(source instanceof ReadableNativeMap, "Illegal type provided");
|
||||
mergeNativeMap((ReadableNativeMap) source);
|
||||
}
|
||||
|
||||
private native void putNativeMap(String key, WritableNativeMap value);
|
||||
private native void putNativeArray(String key, WritableNativeArray value);
|
||||
private native void mergeNativeMap(ReadableNativeMap source);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
srcs = sys.argv[1:]
|
||||
|
||||
with zipfile.ZipFile(sys.stdout, 'w') as jar:
|
||||
for src in srcs:
|
||||
archive_name = os.path.join('assets/', os.path.basename(src))
|
||||
jar.write(src, archive_name, zipfile.ZIP_DEFLATED)
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.queue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import android.os.Looper;
|
||||
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
|
||||
/**
|
||||
* Specifies which {@link MessageQueueThread}s must be used to run the various contexts of
|
||||
* execution within catalyst (Main UI thread, native modules, and JS). Some of these queues *may* be
|
||||
* the same but should be coded against as if they are different.
|
||||
*
|
||||
* UI Queue Thread: The standard Android main UI thread and Looper. Not configurable.
|
||||
* Native Modules Queue Thread: The thread and Looper that native modules are invoked on.
|
||||
* JS Queue Thread: The thread and Looper that JS is executed on.
|
||||
*/
|
||||
public class CatalystQueueConfiguration {
|
||||
|
||||
private final MessageQueueThread mUIQueueThread;
|
||||
private final MessageQueueThread mNativeModulesQueueThread;
|
||||
private final MessageQueueThread mJSQueueThread;
|
||||
|
||||
private CatalystQueueConfiguration(
|
||||
MessageQueueThread uiQueueThread,
|
||||
MessageQueueThread nativeModulesQueueThread,
|
||||
MessageQueueThread jsQueueThread) {
|
||||
mUIQueueThread = uiQueueThread;
|
||||
mNativeModulesQueueThread = nativeModulesQueueThread;
|
||||
mJSQueueThread = jsQueueThread;
|
||||
}
|
||||
|
||||
public MessageQueueThread getUIQueueThread() {
|
||||
return mUIQueueThread;
|
||||
}
|
||||
|
||||
public MessageQueueThread getNativeModulesQueueThread() {
|
||||
return mNativeModulesQueueThread;
|
||||
}
|
||||
|
||||
public MessageQueueThread getJSQueueThread() {
|
||||
return mJSQueueThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when the corresponding {@link com.facebook.react.bridge.CatalystInstance}
|
||||
* is destroyed so that we shut down the proper queue threads.
|
||||
*/
|
||||
public void destroy() {
|
||||
if (mNativeModulesQueueThread.getLooper() != Looper.getMainLooper()) {
|
||||
mNativeModulesQueueThread.quitSynchronous();
|
||||
}
|
||||
if (mJSQueueThread.getLooper() != Looper.getMainLooper()) {
|
||||
mJSQueueThread.quitSynchronous();
|
||||
}
|
||||
}
|
||||
|
||||
public static CatalystQueueConfiguration create(
|
||||
CatalystQueueConfigurationSpec spec,
|
||||
QueueThreadExceptionHandler exceptionHandler) {
|
||||
Map<MessageQueueThreadSpec, MessageQueueThread> specsToThreads = MapBuilder.newHashMap();
|
||||
|
||||
MessageQueueThreadSpec uiThreadSpec = MessageQueueThreadSpec.mainThreadSpec();
|
||||
MessageQueueThread uiThread = MessageQueueThread.create( uiThreadSpec, exceptionHandler);
|
||||
specsToThreads.put(uiThreadSpec, uiThread);
|
||||
|
||||
MessageQueueThread jsThread = specsToThreads.get(spec.getJSQueueThreadSpec());
|
||||
if (jsThread == null) {
|
||||
jsThread = MessageQueueThread.create(spec.getJSQueueThreadSpec(), exceptionHandler);
|
||||
}
|
||||
|
||||
MessageQueueThread nativeModulesThread =
|
||||
specsToThreads.get(spec.getNativeModulesQueueThreadSpec());
|
||||
if (nativeModulesThread == null) {
|
||||
nativeModulesThread =
|
||||
MessageQueueThread.create(spec.getNativeModulesQueueThreadSpec(), exceptionHandler);
|
||||
}
|
||||
|
||||
return new CatalystQueueConfiguration(uiThread, nativeModulesThread, jsThread);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.queue;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
|
||||
/**
|
||||
* Spec for creating a CatalystQueueConfiguration. This exists so that CatalystInstance is able to
|
||||
* set Exception handlers on the MessageQueueThreads it uses and it would not be super clean if the
|
||||
* threads were configured, then passed to CatalystInstance where they are configured more. These
|
||||
* specs allows the Threads to be created fully configured.
|
||||
*/
|
||||
public class CatalystQueueConfigurationSpec {
|
||||
|
||||
private final MessageQueueThreadSpec mNativeModulesQueueThreadSpec;
|
||||
private final MessageQueueThreadSpec mJSQueueThreadSpec;
|
||||
|
||||
private CatalystQueueConfigurationSpec(
|
||||
MessageQueueThreadSpec nativeModulesQueueThreadSpec,
|
||||
MessageQueueThreadSpec jsQueueThreadSpec) {
|
||||
mNativeModulesQueueThreadSpec = nativeModulesQueueThreadSpec;
|
||||
mJSQueueThreadSpec = jsQueueThreadSpec;
|
||||
}
|
||||
|
||||
public MessageQueueThreadSpec getNativeModulesQueueThreadSpec() {
|
||||
return mNativeModulesQueueThreadSpec;
|
||||
}
|
||||
|
||||
public MessageQueueThreadSpec getJSQueueThreadSpec() {
|
||||
return mJSQueueThreadSpec;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static CatalystQueueConfigurationSpec createDefault() {
|
||||
return builder()
|
||||
.setJSQueueThreadSpec(MessageQueueThreadSpec.newBackgroundThreadSpec("js"))
|
||||
.setNativeModulesQueueThreadSpec(
|
||||
MessageQueueThreadSpec.newBackgroundThreadSpec("native_modules"))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private @Nullable MessageQueueThreadSpec mNativeModulesQueueSpec;
|
||||
private @Nullable MessageQueueThreadSpec mJSQueueSpec;
|
||||
|
||||
public Builder setNativeModulesQueueThreadSpec(MessageQueueThreadSpec spec) {
|
||||
Assertions.assertCondition(
|
||||
mNativeModulesQueueSpec == null,
|
||||
"Setting native modules queue spec multiple times!");
|
||||
mNativeModulesQueueSpec = spec;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJSQueueThreadSpec(MessageQueueThreadSpec spec) {
|
||||
Assertions.assertCondition(mJSQueueSpec == null, "Setting JS queue multiple times!");
|
||||
mJSQueueSpec = spec;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CatalystQueueConfigurationSpec build() {
|
||||
return new CatalystQueueConfigurationSpec(
|
||||
Assertions.assertNotNull(mNativeModulesQueueSpec),
|
||||
Assertions.assertNotNull(mJSQueueSpec));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.queue;
|
||||
|
||||
import android.os.Looper;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.AssertionException;
|
||||
import com.facebook.react.bridge.SoftAssertions;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.futures.SimpleSettableFuture;
|
||||
|
||||
/**
|
||||
* Encapsulates a Thread that has a {@link Looper} running on it that can accept Runnables.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class MessageQueueThread {
|
||||
|
||||
private final String mName;
|
||||
private final Looper mLooper;
|
||||
private final MessageQueueThreadHandler mHandler;
|
||||
private final String mAssertionErrorMessage;
|
||||
private volatile boolean mIsFinished = false;
|
||||
|
||||
private MessageQueueThread(
|
||||
String name,
|
||||
Looper looper,
|
||||
QueueThreadExceptionHandler exceptionHandler) {
|
||||
mName = name;
|
||||
mLooper = looper;
|
||||
mHandler = new MessageQueueThreadHandler(looper, exceptionHandler);
|
||||
mAssertionErrorMessage = "Expected to be called from the '" + getName() + "' thread!";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given Runnable on this Thread. It will be submitted to the end of the event queue even
|
||||
* if it is being submitted from the same queue Thread.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public void runOnQueue(Runnable runnable) {
|
||||
if (mIsFinished) {
|
||||
FLog.w(
|
||||
ReactConstants.TAG,
|
||||
"Tried to enqueue runnable on already finished thread: '" + getName() +
|
||||
"... dropping Runnable.");
|
||||
}
|
||||
mHandler.post(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the current Thread is also the Thread associated with this MessageQueueThread.
|
||||
*/
|
||||
public boolean isOnThread() {
|
||||
return mLooper.getThread() == Thread.currentThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an
|
||||
* {@link AssertionError}) if the assertion fails.
|
||||
*/
|
||||
public void assertIsOnThread() {
|
||||
SoftAssertions.assertCondition(isOnThread(), mAssertionErrorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quits this queue's Looper. If that Looper was running on a different Thread than the current
|
||||
* Thread, also waits for the last message being processed to finish and the Thread to die.
|
||||
*/
|
||||
public void quitSynchronous() {
|
||||
mIsFinished = true;
|
||||
mLooper.quit();
|
||||
if (mLooper.getThread() != Thread.currentThread()) {
|
||||
try {
|
||||
mLooper.getThread().join();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Got interrupted waiting to join thread " + mName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Looper getLooper() {
|
||||
return mLooper;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public static MessageQueueThread create(
|
||||
MessageQueueThreadSpec spec,
|
||||
QueueThreadExceptionHandler exceptionHandler) {
|
||||
switch (spec.getThreadType()) {
|
||||
case MAIN_UI:
|
||||
return createForMainThread(spec.getName(), exceptionHandler);
|
||||
case NEW_BACKGROUND:
|
||||
return startNewBackgroundThread(spec.getName(), exceptionHandler);
|
||||
default:
|
||||
throw new RuntimeException("Unknown thread type: " + spec.getThreadType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a MessageQueueThread corresponding to Android's main UI thread.
|
||||
*/
|
||||
private static MessageQueueThread createForMainThread(
|
||||
String name,
|
||||
QueueThreadExceptionHandler exceptionHandler) {
|
||||
Looper mainLooper = Looper.getMainLooper();
|
||||
return new MessageQueueThread(name, mainLooper, exceptionHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a new MessageQueueThread encapsulating a new Thread with a new Looper
|
||||
* running on it. Give it a name for easier debugging. When this method exits, the new
|
||||
* MessageQueueThread is ready to receive events.
|
||||
*/
|
||||
private static MessageQueueThread startNewBackgroundThread(
|
||||
String name,
|
||||
QueueThreadExceptionHandler exceptionHandler) {
|
||||
final SimpleSettableFuture<Looper> simpleSettableFuture = new SimpleSettableFuture<>();
|
||||
Thread bgThread = new Thread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
|
||||
simpleSettableFuture.set(Looper.myLooper());
|
||||
|
||||
Looper.loop();
|
||||
}
|
||||
}, "mqt_" + name);
|
||||
bgThread.start();
|
||||
|
||||
return new MessageQueueThread(name, simpleSettableFuture.get(5000), exceptionHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.queue;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
/**
|
||||
* Handler that can catch and dispatch Exceptions to an Exception handler.
|
||||
*/
|
||||
public class MessageQueueThreadHandler extends Handler {
|
||||
|
||||
private final QueueThreadExceptionHandler mExceptionHandler;
|
||||
|
||||
public MessageQueueThreadHandler(Looper looper, QueueThreadExceptionHandler exceptionHandler) {
|
||||
super(looper);
|
||||
mExceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchMessage(Message msg) {
|
||||
try {
|
||||
super.dispatchMessage(msg);
|
||||
} catch (Exception e) {
|
||||
mExceptionHandler.handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.queue;
|
||||
|
||||
/**
|
||||
* Spec for creating a MessageQueueThread.
|
||||
*/
|
||||
public class MessageQueueThreadSpec {
|
||||
|
||||
private static final MessageQueueThreadSpec MAIN_UI_SPEC =
|
||||
new MessageQueueThreadSpec(ThreadType.MAIN_UI, "main_ui");
|
||||
|
||||
protected static enum ThreadType {
|
||||
MAIN_UI,
|
||||
NEW_BACKGROUND,
|
||||
}
|
||||
|
||||
public static MessageQueueThreadSpec newBackgroundThreadSpec(String name) {
|
||||
return new MessageQueueThreadSpec(ThreadType.NEW_BACKGROUND, name);
|
||||
}
|
||||
|
||||
public static MessageQueueThreadSpec mainThreadSpec() {
|
||||
return MAIN_UI_SPEC;
|
||||
}
|
||||
|
||||
private final ThreadType mThreadType;
|
||||
private final String mName;
|
||||
|
||||
private MessageQueueThreadSpec(ThreadType threadType, String name) {
|
||||
mThreadType = threadType;
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public ThreadType getThreadType() {
|
||||
return mThreadType;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.queue;
|
||||
|
||||
import com.facebook.jni.Countable;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
/**
|
||||
* A Runnable that has a native run implementation.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class NativeRunnable extends Countable implements Runnable {
|
||||
|
||||
/**
|
||||
* Should only be instantiated via native (JNI) code.
|
||||
*/
|
||||
@DoNotStrip
|
||||
private NativeRunnable() {
|
||||
}
|
||||
|
||||
public native void run();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.queue;
|
||||
|
||||
/**
|
||||
* Interface for a class that knows how to handle an Exception thrown while executing a Runnable
|
||||
* submitted via {@link MessageQueueThread#runOnQueue}.
|
||||
*/
|
||||
public interface QueueThreadExceptionHandler {
|
||||
|
||||
void handleException(Exception e);
|
||||
}
|
||||
Reference in New Issue
Block a user