mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-05-13 03:49:52 +08:00
Merging cxxbridge and bridge packages
Reviewed By: javache Differential Revision: D5027875 fbshipit-source-id: 47e081069d4219bdb29f63ce8a78c1f31a590da7
This commit is contained in:
committed by
Facebook Github Bot
parent
31a0b8788f
commit
8b53a2b29b
@@ -6,14 +6,155 @@
|
||||
* 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.reflect.Array;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.bridge.ReadableType;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
public class Arguments {
|
||||
private static Object makeNativeObject(Object object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
} else if (object instanceof Float ||
|
||||
object instanceof Long ||
|
||||
object instanceof Byte ||
|
||||
object instanceof Short) {
|
||||
return new Double(((Number) object).doubleValue());
|
||||
} else if (object.getClass().isArray()) {
|
||||
return makeNativeArray(object);
|
||||
} else if (object instanceof List) {
|
||||
return makeNativeArray((List) object);
|
||||
} else if (object instanceof Map) {
|
||||
return makeNativeMap((Map<String, Object>) object);
|
||||
} else if (object instanceof Bundle) {
|
||||
return makeNativeMap((Bundle) object);
|
||||
} else {
|
||||
// Boolean, Integer, Double, String, WritableNativeArray, WritableNativeMap
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts a List into a NativeArray. The data types supported
|
||||
* are boolean, int, float, double, and String. List, Map, and Bundle
|
||||
* objects, as well as arrays, containing values of the above types and/or
|
||||
* null, or any recursive arrangement of these, are also supported. The best
|
||||
* way to think of this is a way to generate a Java representation of a json
|
||||
* list, from Java types which have a natural representation in json.
|
||||
*/
|
||||
public static WritableNativeArray makeNativeArray(List objects) {
|
||||
WritableNativeArray nativeArray = new WritableNativeArray();
|
||||
if (objects == null) {
|
||||
return nativeArray;
|
||||
}
|
||||
for (Object elem : objects) {
|
||||
elem = makeNativeObject(elem);
|
||||
if (elem == null) {
|
||||
nativeArray.pushNull();
|
||||
} else if (elem instanceof Boolean) {
|
||||
nativeArray.pushBoolean((Boolean) elem);
|
||||
} else if (elem instanceof Integer) {
|
||||
nativeArray.pushInt((Integer) elem);
|
||||
} else if (elem instanceof Double) {
|
||||
nativeArray.pushDouble((Double) elem);
|
||||
} else if (elem instanceof String) {
|
||||
nativeArray.pushString((String) elem);
|
||||
} else if (elem instanceof WritableNativeArray) {
|
||||
nativeArray.pushArray((WritableNativeArray) elem);
|
||||
} else if (elem instanceof WritableNativeMap) {
|
||||
nativeArray.pushMap((WritableNativeMap) elem);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Could not convert " + elem.getClass());
|
||||
}
|
||||
}
|
||||
return nativeArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* This overload is like the above, but uses reflection to operate on any
|
||||
* primitive or object type.
|
||||
*/
|
||||
public static <T> WritableNativeArray makeNativeArray(final Object objects) {
|
||||
if (objects == null) {
|
||||
return new WritableNativeArray();
|
||||
}
|
||||
// No explicit check for objects's type here. If it's not an array, the
|
||||
// Array methods will throw IllegalArgumentException.
|
||||
return makeNativeArray(new AbstractList() {
|
||||
public int size() {
|
||||
return Array.getLength(objects);
|
||||
}
|
||||
|
||||
public Object get(int index) {
|
||||
return Array.get(objects, index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void addEntry(WritableNativeMap nativeMap, String key, Object value) {
|
||||
value = makeNativeObject(value);
|
||||
if (value == null) {
|
||||
nativeMap.putNull(key);
|
||||
} else if (value instanceof Boolean) {
|
||||
nativeMap.putBoolean(key, (Boolean) value);
|
||||
} else if (value instanceof Integer) {
|
||||
nativeMap.putInt(key, (Integer) value);
|
||||
} else if (value instanceof Number) {
|
||||
nativeMap.putDouble(key, ((Number) value).doubleValue());
|
||||
} else if (value instanceof String) {
|
||||
nativeMap.putString(key, (String) value);
|
||||
} else if (value instanceof WritableNativeArray) {
|
||||
nativeMap.putArray(key, (WritableNativeArray) value);
|
||||
} else if (value instanceof WritableNativeMap) {
|
||||
nativeMap.putMap(key, (WritableNativeMap) value);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Could not convert " + value.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts a Map into a NativeMap. Value types are supported as
|
||||
* with makeNativeArray. The best way to think of this is a way to generate
|
||||
* a Java representation of a json object, from Java types which have a
|
||||
* natural representation in json.
|
||||
*/
|
||||
public static WritableNativeMap makeNativeMap(Map<String, Object> objects) {
|
||||
WritableNativeMap nativeMap = new WritableNativeMap();
|
||||
if (objects == null) {
|
||||
return nativeMap;
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : objects.entrySet()) {
|
||||
addEntry(nativeMap, entry.getKey(), entry.getValue());
|
||||
}
|
||||
return nativeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like the above, but takes a Bundle instead of a Map.
|
||||
*/
|
||||
public static WritableNativeMap makeNativeMap(Bundle bundle) {
|
||||
WritableNativeMap nativeMap = new WritableNativeMap();
|
||||
if (bundle == null) {
|
||||
return nativeMap;
|
||||
}
|
||||
for (String key : bundle.keySet()) {
|
||||
addEntry(nativeMap, key, bundle.get(key));
|
||||
}
|
||||
return nativeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be used when you need to stub out creating NativeArrays in unit tests.
|
||||
@@ -64,35 +205,34 @@ public class 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[]}.
|
||||
*
|
||||
* {@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) {
|
||||
for (String v : (String[]) array) {
|
||||
catalystArray.pushString(v);
|
||||
}
|
||||
} else if (array instanceof Bundle[]) {
|
||||
for (Bundle v: (Bundle[]) array) {
|
||||
for (Bundle v : (Bundle[]) array) {
|
||||
catalystArray.pushMap(fromBundle(v));
|
||||
}
|
||||
} else if (array instanceof int[]) {
|
||||
for (int v: (int[]) array) {
|
||||
for (int v : (int[]) array) {
|
||||
catalystArray.pushInt(v);
|
||||
}
|
||||
} else if (array instanceof float[]) {
|
||||
for (float v: (float[]) array) {
|
||||
for (float v : (float[]) array) {
|
||||
catalystArray.pushDouble(v);
|
||||
}
|
||||
} else if (array instanceof double[]) {
|
||||
for (double v: (double[]) array) {
|
||||
for (double v : (double[]) array) {
|
||||
catalystArray.pushDouble(v);
|
||||
}
|
||||
} else if (array instanceof boolean[]) {
|
||||
for (boolean v: (boolean[]) array) {
|
||||
for (boolean v : (boolean[]) array) {
|
||||
catalystArray.pushBoolean(v);
|
||||
}
|
||||
} else {
|
||||
@@ -104,11 +244,11 @@ public class Arguments {
|
||||
/**
|
||||
* Convert a {@link Bundle} to a {@link WritableMap}. Supported key types in the bundle
|
||||
* are:
|
||||
*
|
||||
* <p>
|
||||
* <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>
|
||||
* <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
|
||||
@@ -117,7 +257,7 @@ public class Arguments {
|
||||
*/
|
||||
public static WritableMap fromBundle(Bundle bundle) {
|
||||
WritableMap map = createMap();
|
||||
for (String key: bundle.keySet()) {
|
||||
for (String key : bundle.keySet()) {
|
||||
Object value = bundle.get(key);
|
||||
if (value == null) {
|
||||
map.putNull(key);
|
||||
@@ -144,6 +284,7 @@ public class Arguments {
|
||||
|
||||
/**
|
||||
* Convert a {@link WritableMap} to a {@link Bundle}.
|
||||
*
|
||||
* @param readableMap the {@link WritableMap} to convert.
|
||||
* @return the converted {@link Bundle}.
|
||||
*/
|
||||
|
||||
@@ -33,5 +33,6 @@ android_library(
|
||||
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
|
||||
react_native_dep("third-party/java/jsr-305:jsr-305"),
|
||||
react_native_target("java/com/facebook/react/common:common"),
|
||||
react_native_target("java/com/facebook/react/module/model:model"),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,567 @@
|
||||
/**
|
||||
* 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.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.CatalystInstance;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.JavaScriptModuleRegistry;
|
||||
import com.facebook.react.bridge.MemoryPressure;
|
||||
import com.facebook.react.bridge.NativeArray;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
|
||||
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
|
||||
import com.facebook.react.bridge.queue.MessageQueueThread;
|
||||
import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl;
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.facebook.systrace.Systrace;
|
||||
import com.facebook.systrace.TraceListener;
|
||||
|
||||
/**
|
||||
* This provides an implementation of the public CatalystInstance instance. It is public because
|
||||
* it is built by XReactInstanceManager which is in a different package.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class CatalystInstanceImpl implements CatalystInstance {
|
||||
|
||||
/* package */ static final String REACT_NATIVE_LIB = "reactnativejnifb";
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1);
|
||||
|
||||
private static class PendingJSCall {
|
||||
|
||||
public String mModule;
|
||||
public String mMethod;
|
||||
public NativeArray mArguments;
|
||||
|
||||
public PendingJSCall(
|
||||
String module,
|
||||
String method,
|
||||
NativeArray arguments) {
|
||||
mModule = module;
|
||||
mMethod = method;
|
||||
mArguments = arguments;
|
||||
}
|
||||
}
|
||||
|
||||
// Access from any thread
|
||||
private final ReactQueueConfigurationImpl mReactQueueConfiguration;
|
||||
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;
|
||||
private final TraceListener mTraceListener;
|
||||
private final JavaScriptModuleRegistry mJSModuleRegistry;
|
||||
private final JSBundleLoader mJSBundleLoader;
|
||||
private final ArrayList<PendingJSCall> mJSCallsPendingInit = new ArrayList<PendingJSCall>();
|
||||
private final Object mJSCallsPendingInitLock = new Object();
|
||||
|
||||
private final NativeModuleRegistry mJavaRegistry;
|
||||
private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
|
||||
private final MessageQueueThread mNativeModulesQueueThread;
|
||||
private final @Nullable MessageQueueThread mUIBackgroundQueueThread;
|
||||
private boolean mInitialized = false;
|
||||
private volatile boolean mAcceptCalls = false;
|
||||
|
||||
private boolean mJSBundleHasLoaded;
|
||||
private @Nullable String mSourceURL;
|
||||
|
||||
// C++ parts
|
||||
private final HybridData mHybridData;
|
||||
private native static HybridData initHybrid();
|
||||
|
||||
private CatalystInstanceImpl(
|
||||
final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
|
||||
final JavaScriptExecutor jsExecutor,
|
||||
final NativeModuleRegistry registry,
|
||||
final JavaScriptModuleRegistry jsModuleRegistry,
|
||||
final JSBundleLoader jsBundleLoader,
|
||||
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
|
||||
FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
|
||||
mHybridData = initHybrid();
|
||||
|
||||
mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
|
||||
ReactQueueConfigurationSpec,
|
||||
new NativeExceptionHandler());
|
||||
mBridgeIdleListeners = new CopyOnWriteArrayList<>();
|
||||
mJavaRegistry = registry;
|
||||
mJSModuleRegistry = jsModuleRegistry;
|
||||
mJSBundleLoader = jsBundleLoader;
|
||||
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
|
||||
mNativeModulesQueueThread = mReactQueueConfiguration.getNativeModulesQueueThread();
|
||||
mUIBackgroundQueueThread = mReactQueueConfiguration.getUIBackgroundQueueThread();
|
||||
mTraceListener = new JSProfilerTraceListener(this);
|
||||
|
||||
FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge before initializeBridge");
|
||||
initializeBridge(
|
||||
new BridgeCallback(this),
|
||||
jsExecutor,
|
||||
mReactQueueConfiguration.getJSQueueThread(),
|
||||
mNativeModulesQueueThread,
|
||||
mUIBackgroundQueueThread,
|
||||
mJavaRegistry.getJavaModules(this),
|
||||
mJavaRegistry.getCxxModules());
|
||||
FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge after initializeBridge");
|
||||
}
|
||||
|
||||
private static class BridgeCallback implements ReactCallback {
|
||||
// We do this so the callback doesn't keep the CatalystInstanceImpl alive.
|
||||
// In this case, the callback is held in C++ code, so the GC can't see it
|
||||
// and determine there's an inaccessible cycle.
|
||||
private final WeakReference<CatalystInstanceImpl> mOuter;
|
||||
|
||||
public BridgeCallback(CatalystInstanceImpl outer) {
|
||||
mOuter = new WeakReference<CatalystInstanceImpl>(outer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatchComplete() {
|
||||
CatalystInstanceImpl impl = mOuter.get();
|
||||
if (impl != null) {
|
||||
impl.mJavaRegistry.onBatchComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementPendingJSCalls() {
|
||||
CatalystInstanceImpl impl = mOuter.get();
|
||||
if (impl != null) {
|
||||
impl.incrementPendingJSCalls();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrementPendingJSCalls() {
|
||||
CatalystInstanceImpl impl = mOuter.get();
|
||||
if (impl != null) {
|
||||
impl.decrementPendingJSCalls();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private native void initializeBridge(
|
||||
ReactCallback callback,
|
||||
JavaScriptExecutor jsExecutor,
|
||||
MessageQueueThread jsQueue,
|
||||
MessageQueueThread moduleQueue,
|
||||
MessageQueueThread uiBackgroundQueue,
|
||||
Collection<JavaModuleWrapper> javaModules,
|
||||
Collection<ModuleHolder> cxxModules);
|
||||
|
||||
/**
|
||||
* This API is used in situations where the JS bundle is being executed not on
|
||||
* the device, but on a host machine. In that case, we must provide two source
|
||||
* URLs for the JS bundle: One to be used on the device, and one to be used on
|
||||
* the remote debugging machine.
|
||||
*
|
||||
* @param deviceURL A source URL that is accessible from this device.
|
||||
* @param remoteURL A source URL that is accessible from the remote machine
|
||||
* executing the JS.
|
||||
*/
|
||||
/* package */ void setSourceURLs(String deviceURL, String remoteURL) {
|
||||
mSourceURL = deviceURL;
|
||||
jniSetSourceURL(remoteURL);
|
||||
}
|
||||
|
||||
/* package */ void loadScriptFromAssets(AssetManager assetManager, String assetURL) {
|
||||
mSourceURL = assetURL;
|
||||
jniLoadScriptFromAssets(assetManager, assetURL);
|
||||
}
|
||||
|
||||
/* package */ void loadScriptFromFile(String fileName, String sourceURL) {
|
||||
mSourceURL = sourceURL;
|
||||
jniLoadScriptFromFile(fileName, sourceURL);
|
||||
}
|
||||
|
||||
private native void jniSetSourceURL(String sourceURL);
|
||||
private native void jniLoadScriptFromAssets(AssetManager assetManager, String assetURL);
|
||||
private native void jniLoadScriptFromFile(String fileName, String sourceURL);
|
||||
|
||||
@Override
|
||||
public void runJSBundle() {
|
||||
Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
|
||||
mJSBundleHasLoaded = true;
|
||||
|
||||
// incrementPendingJSCalls();
|
||||
mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
|
||||
|
||||
synchronized (mJSCallsPendingInitLock) {
|
||||
// Loading the bundle is queued on the JS thread, but may not have
|
||||
// run yet. It's safe to set this here, though, since any work it
|
||||
// gates will be queued on the JS thread behind the load.
|
||||
mAcceptCalls = true;
|
||||
|
||||
for (PendingJSCall call : mJSCallsPendingInit) {
|
||||
jniCallJSFunction(call.mModule, call.mMethod, call.mArguments);
|
||||
}
|
||||
mJSCallsPendingInit.clear();
|
||||
}
|
||||
|
||||
// This is registered after JS starts since it makes a JS call
|
||||
Systrace.registerListener(mTraceListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getSourceURL() {
|
||||
return mSourceURL;
|
||||
}
|
||||
|
||||
private native void jniCallJSFunction(
|
||||
String module,
|
||||
String method,
|
||||
NativeArray arguments);
|
||||
|
||||
@Override
|
||||
public void callFunction(
|
||||
final String module,
|
||||
final String method,
|
||||
final NativeArray arguments) {
|
||||
if (mDestroyed) {
|
||||
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
|
||||
return;
|
||||
}
|
||||
if (!mAcceptCalls) {
|
||||
// Most of the time the instance is initialized and we don't need to acquire the lock
|
||||
synchronized (mJSCallsPendingInitLock) {
|
||||
if (!mAcceptCalls) {
|
||||
mJSCallsPendingInit.add(new PendingJSCall(module, method, arguments));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jniCallJSFunction(module, method, arguments);
|
||||
}
|
||||
|
||||
private native void jniCallJSCallback(int callbackID, NativeArray arguments);
|
||||
|
||||
@Override
|
||||
public void invokeCallback(final int callbackID, final NativeArray arguments) {
|
||||
if (mDestroyed) {
|
||||
FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
|
||||
return;
|
||||
}
|
||||
|
||||
jniCallJSCallback(callbackID, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys this catalyst instance, waiting for any other threads in ReactQueueConfiguration
|
||||
* (besides the UI thread) to finish running. Must be called from the UI thread so that we can
|
||||
* fully shut down other threads.
|
||||
*/
|
||||
@Override
|
||||
public void destroy() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
if (mDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: tell all APIs to shut down
|
||||
mDestroyed = true;
|
||||
mNativeModulesQueueThread.runOnQueue(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mJavaRegistry.notifyJSInstanceDestroy();
|
||||
boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
|
||||
if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
|
||||
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
|
||||
listener.onTransitionToBridgeIdle();
|
||||
}
|
||||
}
|
||||
mHybridData.resetNative();
|
||||
}
|
||||
});
|
||||
|
||||
// This is a noop if the listener was not yet registered.
|
||||
Systrace.unregisterListener(mTraceListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyed() {
|
||||
return mDestroyed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all the native modules
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public void initialize() {
|
||||
Assertions.assertCondition(
|
||||
!mInitialized,
|
||||
"This catalyst instance has already been initialized");
|
||||
// We assume that the instance manager blocks on running the JS bundle. If
|
||||
// that changes, then we need to set mAcceptCalls just after posting the
|
||||
// task that will run the js bundle.
|
||||
Assertions.assertCondition(
|
||||
mAcceptCalls,
|
||||
"RunJSBundle hasn't completed.");
|
||||
mInitialized = true;
|
||||
mNativeModulesQueueThread.runOnQueue(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mJavaRegistry.notifyJSInstanceInitialized();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactQueueConfiguration getReactQueueConfiguration() {
|
||||
return mReactQueueConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
|
||||
return mJSModuleRegistry.getJavaScriptModule(this, jsInterface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends NativeModule> boolean hasNativeModule(Class<T> nativeModuleInterface) {
|
||||
return mJavaRegistry.hasModule(nativeModuleInterface);
|
||||
}
|
||||
|
||||
// This is only ever called with UIManagerModule or CurrentViewerModule.
|
||||
@Override
|
||||
public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
|
||||
return mJavaRegistry.getModule(nativeModuleInterface);
|
||||
}
|
||||
|
||||
// This is only used by com.facebook.react.modules.common.ModuleDataCleaner
|
||||
@Override
|
||||
public Collection<NativeModule> getNativeModules() {
|
||||
return mJavaRegistry.getAllModules();
|
||||
}
|
||||
|
||||
private native void handleMemoryPressureUiHidden();
|
||||
private native void handleMemoryPressureModerate();
|
||||
private native void handleMemoryPressureCritical();
|
||||
|
||||
@Override
|
||||
public void handleMemoryPressure(MemoryPressure level) {
|
||||
if (mDestroyed) {
|
||||
return;
|
||||
}
|
||||
switch (level) {
|
||||
case UI_HIDDEN:
|
||||
handleMemoryPressureUiHidden();
|
||||
break;
|
||||
case MODERATE:
|
||||
handleMemoryPressureModerate();
|
||||
break;
|
||||
case CRITICAL:
|
||||
handleMemoryPressureCritical();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* onBatchComplete call. The listener should be purely passive and not affect application logic.
|
||||
*/
|
||||
@Override
|
||||
public void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
|
||||
mBridgeIdleListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a NotThreadSafeBridgeIdleDebugListener previously added with
|
||||
* {@link #addBridgeIdleDebugListener}
|
||||
*/
|
||||
@Override
|
||||
public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
|
||||
mBridgeIdleListeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public native void setGlobalVariable(String propName, String jsonValue);
|
||||
|
||||
@Override
|
||||
public native long getJavaScriptContext();
|
||||
|
||||
// TODO mhorowitz: add mDestroyed checks to the next three methods
|
||||
|
||||
@Override
|
||||
public native boolean supportsProfiling();
|
||||
|
||||
@Override
|
||||
public native void startProfiler(String title);
|
||||
|
||||
@Override
|
||||
public native void stopProfiler(String title, String filename);
|
||||
|
||||
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()) {
|
||||
mNativeModulesQueueThread.runOnQueue(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
|
||||
listener.onTransitionToBridgeBusy();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void decrementPendingJSCalls() {
|
||||
int newPendingCalls = mPendingJSCalls.decrementAndGet();
|
||||
// TODO(9604406): handle case of web workers injecting messages to main thread
|
||||
//Assertions.assertCondition(newPendingCalls >= 0);
|
||||
boolean isNowIdle = newPendingCalls == 0;
|
||||
Systrace.traceCounter(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
mJsPendingCallsTitleForTrace,
|
||||
newPendingCalls);
|
||||
|
||||
if (isNowIdle && !mBridgeIdleListeners.isEmpty()) {
|
||||
mNativeModulesQueueThread.runOnQueue(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
|
||||
listener.onTransitionToBridgeIdle();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void onNativeException(Exception e) {
|
||||
mNativeModuleCallExceptionHandler.handleException(e);
|
||||
mReactQueueConfiguration.getUIQueueThread().runOnQueue(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.
|
||||
onNativeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class JSProfilerTraceListener implements TraceListener {
|
||||
// We do this so the callback doesn't keep the CatalystInstanceImpl alive.
|
||||
// In this case, Systrace will keep the registered listener around forever
|
||||
// if the CatalystInstanceImpl is not explicitly destroyed. These instances
|
||||
// can still leak, but they are at least small.
|
||||
private final WeakReference<CatalystInstanceImpl> mOuter;
|
||||
|
||||
public JSProfilerTraceListener(CatalystInstanceImpl outer) {
|
||||
mOuter = new WeakReference<CatalystInstanceImpl>(outer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTraceStarted() {
|
||||
CatalystInstanceImpl impl = mOuter.get();
|
||||
if (impl != null) {
|
||||
impl.getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTraceStopped() {
|
||||
CatalystInstanceImpl impl = mOuter.get();
|
||||
if (impl != null) {
|
||||
impl.getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private @Nullable ReactQueueConfigurationSpec mReactQueueConfigurationSpec;
|
||||
private @Nullable JSBundleLoader mJSBundleLoader;
|
||||
private @Nullable NativeModuleRegistry mRegistry;
|
||||
private @Nullable JavaScriptModuleRegistry mJSModuleRegistry;
|
||||
private @Nullable JavaScriptExecutor mJSExecutor;
|
||||
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
|
||||
|
||||
public Builder setReactQueueConfigurationSpec(
|
||||
ReactQueueConfigurationSpec ReactQueueConfigurationSpec) {
|
||||
mReactQueueConfigurationSpec = ReactQueueConfigurationSpec;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRegistry(NativeModuleRegistry registry) {
|
||||
mRegistry = registry;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJSModuleRegistry(JavaScriptModuleRegistry jsModuleRegistry) {
|
||||
mJSModuleRegistry = jsModuleRegistry;
|
||||
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 CatalystInstanceImpl build() {
|
||||
return new CatalystInstanceImpl(
|
||||
Assertions.assertNotNull(mReactQueueConfigurationSpec),
|
||||
Assertions.assertNotNull(mJSExecutor),
|
||||
Assertions.assertNotNull(mRegistry),
|
||||
Assertions.assertNotNull(mJSModuleRegistry),
|
||||
Assertions.assertNotNull(mJSBundleLoader),
|
||||
Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.NativeArray;
|
||||
|
||||
import static com.facebook.react.bridge.Arguments.*;
|
||||
|
||||
/**
|
||||
* Callback impl that calls directly into the cxx bridge. Created from C++.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class CxxCallbackImpl implements Callback {
|
||||
@DoNotStrip
|
||||
private final HybridData mHybridData;
|
||||
|
||||
@DoNotStrip
|
||||
private CxxCallbackImpl(HybridData hybridData) {
|
||||
mHybridData = hybridData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(Object... args) {
|
||||
nativeInvoke(fromJavaArgs(args));
|
||||
}
|
||||
|
||||
private native void nativeInvoke(NativeArray arguments);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
/**
|
||||
* This does nothing interesting, except avoid breaking existing code.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class CxxModuleWrapper extends CxxModuleWrapperBase
|
||||
{
|
||||
protected CxxModuleWrapper(HybridData hd) {
|
||||
super(hd);
|
||||
}
|
||||
|
||||
private static native CxxModuleWrapper makeDsoNative(String soPath, String factory);
|
||||
|
||||
public static CxxModuleWrapper makeDso(String library, String factory) {
|
||||
SoLoader.loadLibrary(library);
|
||||
String soPath = SoLoader.unpackLibraryAndDependencies(library).getAbsolutePath();
|
||||
return makeDsoNative(soPath, factory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
/**
|
||||
* A Java Object which represents a cross-platform C++ module
|
||||
*
|
||||
* This module implements the NativeModule interface but will never be invoked from Java,
|
||||
* instead the underlying Cxx module will be extracted by the bridge and called directly.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class CxxModuleWrapperBase implements NativeModule
|
||||
{
|
||||
static {
|
||||
SoLoader.loadLibrary(CatalystInstanceImpl.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
private HybridData mHybridData;
|
||||
|
||||
@Override
|
||||
public native String getName();
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOverrideExistingModule() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCatalystInstanceDestroy() {
|
||||
mHybridData.resetNative();
|
||||
}
|
||||
|
||||
// For creating a wrapper from C++, or from a derived class.
|
||||
protected CxxModuleWrapperBase(HybridData hd) {
|
||||
mHybridData = hd;
|
||||
}
|
||||
|
||||
// Replace the current native module held by this wrapper by a new instance
|
||||
protected void resetModule(HybridData hd) {
|
||||
if (hd != mHybridData) {
|
||||
mHybridData.resetNative();
|
||||
mHybridData = hd;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Stack;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
|
||||
/**
|
||||
* FallbackJSBundleLoader
|
||||
*
|
||||
* An implementation of {@link JSBundleLoader} that will try to load from
|
||||
* multiple sources, falling back from one source to the next at load time
|
||||
* when an exception is thrown for a recoverable error.
|
||||
*/
|
||||
public final class FallbackJSBundleLoader extends JSBundleLoader {
|
||||
|
||||
/* package */ static final String RECOVERABLE = "facebook::react::Recoverable";
|
||||
/* package */ static final String TAG = "FallbackJSBundleLoader";
|
||||
|
||||
// Loaders to delegate to, with the preferred one at the top.
|
||||
private Stack<JSBundleLoader> mLoaders;
|
||||
|
||||
// Reasons why we fell-back on previous loaders, in order of occurrence.
|
||||
private final ArrayList<Exception> mRecoveredErrors = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @param loaders Loaders for the sources to try, in descending order of
|
||||
* preference.
|
||||
*/
|
||||
public FallbackJSBundleLoader(List<JSBundleLoader> loaders) {
|
||||
mLoaders = new Stack();
|
||||
ListIterator<JSBundleLoader> it = loaders.listIterator(loaders.size());
|
||||
while (it.hasPrevious()) {
|
||||
mLoaders.push(it.previous());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This loader delegates to (and so behaves like) the currently preferred
|
||||
* loader. If that loader fails in a recoverable way and we fall back from it,
|
||||
* it is replaced by the next most preferred loader.
|
||||
*/
|
||||
@Override
|
||||
public String loadScript(CatalystInstanceImpl instance) {
|
||||
while (true) {
|
||||
try {
|
||||
return getDelegateLoader().loadScript(instance);
|
||||
} catch (Exception e) {
|
||||
if (!e.getMessage().startsWith(RECOVERABLE)) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
mLoaders.pop();
|
||||
mRecoveredErrors.add(e);
|
||||
FLog.wtf(TAG, "Falling back from recoverable error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JSBundleLoader getDelegateLoader() {
|
||||
if (!mLoaders.empty()) {
|
||||
return mLoaders.peek();
|
||||
}
|
||||
|
||||
RuntimeException fallbackException =
|
||||
new RuntimeException("No fallback options available");
|
||||
|
||||
// Invariant: tail.getCause() == null
|
||||
Throwable tail = fallbackException;
|
||||
for (Exception e : mRecoveredErrors) {
|
||||
tail.initCause(e);
|
||||
while (tail.getCause() != null) {
|
||||
tail = tail.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
throw fallbackException;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import com.facebook.react.devsupport.DebugServerException;
|
||||
|
||||
/**
|
||||
* 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 in native code to save on passing large
|
||||
* strings from java to native memory.
|
||||
*/
|
||||
public static JSBundleLoader createAssetLoader(
|
||||
final Context context,
|
||||
final String assetUrl) {
|
||||
return new JSBundleLoader() {
|
||||
@Override
|
||||
public String loadScript(CatalystInstanceImpl instance) {
|
||||
instance.loadScriptFromAssets(context.getAssets(), assetUrl);
|
||||
return assetUrl;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This loader loads bundle from file system. The bundle will be read in native code to save on
|
||||
* passing large strings from java to native memorory.
|
||||
*/
|
||||
public static JSBundleLoader createFileLoader(final String fileName) {
|
||||
return createFileLoader(fileName, fileName);
|
||||
}
|
||||
|
||||
public static JSBundleLoader createFileLoader(
|
||||
final String fileName,
|
||||
final String assetUrl) {
|
||||
return new JSBundleLoader() {
|
||||
@Override
|
||||
public String loadScript(CatalystInstanceImpl instance) {
|
||||
instance.loadScriptFromFile(fileName, assetUrl);
|
||||
return fileName;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 String loadScript(CatalystInstanceImpl instance) {
|
||||
try {
|
||||
instance.loadScriptFromFile(cachedFileLocation, sourceURL);
|
||||
return sourceURL;
|
||||
} catch (Exception e) {
|
||||
throw DebugServerException.makeGeneric(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 proxySourceURL,
|
||||
final String realSourceURL) {
|
||||
return new JSBundleLoader() {
|
||||
@Override
|
||||
public String loadScript(CatalystInstanceImpl instance) {
|
||||
instance.setSourceURLs(realSourceURL, proxySourceURL);
|
||||
return realSourceURL;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the script, returning the URL of the source it loaded.
|
||||
*/
|
||||
public abstract String loadScript(CatalystInstanceImpl instance);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.react.bridge.ReadableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
@DoNotStrip
|
||||
public class JSCJavaScriptExecutor extends JavaScriptExecutor {
|
||||
public static class Factory implements JavaScriptExecutor.Factory {
|
||||
private ReadableNativeArray mJSCConfig;
|
||||
|
||||
public Factory(WritableNativeMap jscConfig) {
|
||||
// TODO (t10707444): use NativeMap, which requires moving NativeMap out of OnLoad.
|
||||
WritableNativeArray array = new WritableNativeArray();
|
||||
array.pushMap(jscConfig);
|
||||
mJSCConfig = array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaScriptExecutor create() throws Exception {
|
||||
return new JSCJavaScriptExecutor(mJSCConfig);
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(CatalystInstanceImpl.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
public JSCJavaScriptExecutor(ReadableNativeArray jscConfig) {
|
||||
super(initHybrid(jscConfig));
|
||||
}
|
||||
|
||||
private native static HybridData initHybrid(ReadableNativeArray jscConfig);
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
/**
|
||||
* 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.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.BaseJavaModule;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.Dynamic;
|
||||
import com.facebook.react.bridge.DynamicFromArray;
|
||||
import com.facebook.react.bridge.JSInstance;
|
||||
import com.facebook.react.bridge.NativeArgumentsParseException;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.PromiseImpl;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableNativeArray;
|
||||
import com.facebook.react.bridge.UnexpectedNativeTypeException;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.systrace.SystraceMessage;
|
||||
|
||||
import static com.facebook.infer.annotation.Assertions.assertNotNull;
|
||||
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
|
||||
|
||||
public class JavaMethodWrapper implements NativeModule.NativeMethod {
|
||||
|
||||
private static abstract class ArgumentExtractor<T> {
|
||||
public int getJSArgumentsNeeded() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public abstract @Nullable T extractArgument(
|
||||
JSInstance jsInstance, ReadableNativeArray jsArguments, int atIndex);
|
||||
}
|
||||
|
||||
static final private ArgumentExtractor<Boolean> ARGUMENT_EXTRACTOR_BOOLEAN =
|
||||
new ArgumentExtractor<Boolean>() {
|
||||
@Override
|
||||
public Boolean extractArgument(
|
||||
JSInstance jsInstance, ReadableNativeArray jsArguments, int atIndex) {
|
||||
return jsArguments.getBoolean(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
static final private ArgumentExtractor<Double> ARGUMENT_EXTRACTOR_DOUBLE =
|
||||
new ArgumentExtractor<Double>() {
|
||||
@Override
|
||||
public Double extractArgument(
|
||||
JSInstance jsInstance, ReadableNativeArray jsArguments, int atIndex) {
|
||||
return jsArguments.getDouble(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
static final private ArgumentExtractor<Float> ARGUMENT_EXTRACTOR_FLOAT =
|
||||
new ArgumentExtractor<Float>() {
|
||||
@Override
|
||||
public Float extractArgument(
|
||||
JSInstance jsInstance, ReadableNativeArray jsArguments, int atIndex) {
|
||||
return (float) jsArguments.getDouble(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
static final private ArgumentExtractor<Integer> ARGUMENT_EXTRACTOR_INTEGER =
|
||||
new ArgumentExtractor<Integer>() {
|
||||
@Override
|
||||
public Integer extractArgument(
|
||||
JSInstance jsInstance, ReadableNativeArray jsArguments, int atIndex) {
|
||||
return (int) jsArguments.getDouble(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
static final private ArgumentExtractor<String> ARGUMENT_EXTRACTOR_STRING =
|
||||
new ArgumentExtractor<String>() {
|
||||
@Override
|
||||
public String extractArgument(
|
||||
JSInstance jsInstance, ReadableNativeArray jsArguments, int atIndex) {
|
||||
return jsArguments.getString(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
static final private ArgumentExtractor<ReadableNativeArray> ARGUMENT_EXTRACTOR_ARRAY =
|
||||
new ArgumentExtractor<ReadableNativeArray>() {
|
||||
@Override
|
||||
public ReadableNativeArray extractArgument(
|
||||
JSInstance jsInstance, ReadableNativeArray jsArguments, int atIndex) {
|
||||
return jsArguments.getArray(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
static final private ArgumentExtractor<Dynamic> ARGUMENT_EXTRACTOR_DYNAMIC =
|
||||
new ArgumentExtractor<Dynamic>() {
|
||||
@Override
|
||||
public Dynamic extractArgument(
|
||||
JSInstance jsInstance, ReadableNativeArray jsArguments, int atIndex) {
|
||||
return DynamicFromArray.create(jsArguments, atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
static final private ArgumentExtractor<ReadableMap> ARGUMENT_EXTRACTOR_MAP =
|
||||
new ArgumentExtractor<ReadableMap>() {
|
||||
@Override
|
||||
public ReadableMap extractArgument(
|
||||
JSInstance jsInstance, ReadableNativeArray jsArguments, int atIndex) {
|
||||
return jsArguments.getMap(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
static final private ArgumentExtractor<Callback> ARGUMENT_EXTRACTOR_CALLBACK =
|
||||
new ArgumentExtractor<Callback>() {
|
||||
@Override
|
||||
public @Nullable Callback extractArgument(
|
||||
JSInstance jsInstance, ReadableNativeArray jsArguments, int atIndex) {
|
||||
if (jsArguments.isNull(atIndex)) {
|
||||
return null;
|
||||
} else {
|
||||
int id = (int) jsArguments.getDouble(atIndex);
|
||||
return new com.facebook.react.bridge.CallbackImpl(jsInstance, id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static final private ArgumentExtractor<Promise> ARGUMENT_EXTRACTOR_PROMISE =
|
||||
new ArgumentExtractor<Promise>() {
|
||||
@Override
|
||||
public int getJSArgumentsNeeded() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise extractArgument(
|
||||
JSInstance jsInstance, ReadableNativeArray jsArguments, int atIndex) {
|
||||
Callback resolve = ARGUMENT_EXTRACTOR_CALLBACK
|
||||
.extractArgument(jsInstance, jsArguments, atIndex);
|
||||
Callback reject = ARGUMENT_EXTRACTOR_CALLBACK
|
||||
.extractArgument(jsInstance, jsArguments, atIndex + 1);
|
||||
return new PromiseImpl(resolve, reject);
|
||||
}
|
||||
};
|
||||
|
||||
private static char paramTypeToChar(Class paramClass) {
|
||||
char tryCommon = commonTypeToChar(paramClass);
|
||||
if (tryCommon != '\0') {
|
||||
return tryCommon;
|
||||
}
|
||||
if (paramClass == Callback.class) {
|
||||
return 'X';
|
||||
} else if (paramClass == Promise.class) {
|
||||
return 'P';
|
||||
} else if (paramClass == ReadableMap.class) {
|
||||
return 'M';
|
||||
} else if (paramClass == ReadableArray.class) {
|
||||
return 'A';
|
||||
} else if (paramClass == Dynamic.class) {
|
||||
return 'Y';
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Got unknown param class: " + paramClass.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
private static char returnTypeToChar(Class returnClass) {
|
||||
// Keep this in sync with MethodInvoker
|
||||
char tryCommon = commonTypeToChar(returnClass);
|
||||
if (tryCommon != '\0') {
|
||||
return tryCommon;
|
||||
}
|
||||
if (returnClass == void.class) {
|
||||
return 'v';
|
||||
} else if (returnClass == WritableMap.class) {
|
||||
return 'M';
|
||||
} else if (returnClass == WritableArray.class) {
|
||||
return 'A';
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Got unknown return class: " + returnClass.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
private static char commonTypeToChar(Class typeClass) {
|
||||
if (typeClass == boolean.class) {
|
||||
return 'z';
|
||||
} else if (typeClass == Boolean.class) {
|
||||
return 'Z';
|
||||
} else if (typeClass == int.class) {
|
||||
return 'i';
|
||||
} else if (typeClass == Integer.class) {
|
||||
return 'I';
|
||||
} else if (typeClass == double.class) {
|
||||
return 'd';
|
||||
} else if (typeClass == Double.class) {
|
||||
return 'D';
|
||||
} else if (typeClass == float.class) {
|
||||
return 'f';
|
||||
} else if (typeClass == Float.class) {
|
||||
return 'F';
|
||||
} else if (typeClass == String.class) {
|
||||
return 'S';
|
||||
} else {
|
||||
return '\0';
|
||||
}
|
||||
}
|
||||
|
||||
private final Method mMethod;
|
||||
private final Class[] mParameterTypes;
|
||||
private final int mParamLength;
|
||||
private final JavaModuleWrapper mModuleWrapper;
|
||||
private String mType = BaseJavaModule.METHOD_TYPE_ASYNC;
|
||||
private boolean mArgumentsProcessed = false;
|
||||
private @Nullable ArgumentExtractor[] mArgumentExtractors;
|
||||
private @Nullable String mSignature;
|
||||
private @Nullable Object[] mArguments;
|
||||
private @Nullable int mJSArgumentsNeeded;
|
||||
|
||||
public JavaMethodWrapper(JavaModuleWrapper module, Method method, boolean isSync) {
|
||||
mModuleWrapper = module;
|
||||
mMethod = method;
|
||||
mMethod.setAccessible(true);
|
||||
mParameterTypes = mMethod.getParameterTypes();
|
||||
mParamLength = mParameterTypes.length;
|
||||
|
||||
if (isSync) {
|
||||
mType = BaseJavaModule.METHOD_TYPE_SYNC;
|
||||
} else if (mParamLength > 0 && (mParameterTypes[mParamLength - 1] == Promise.class)) {
|
||||
mType = BaseJavaModule.METHOD_TYPE_PROMISE;
|
||||
}
|
||||
}
|
||||
|
||||
private void processArguments() {
|
||||
if (mArgumentsProcessed) {
|
||||
return;
|
||||
}
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "processArguments")
|
||||
.arg("method", mModuleWrapper.getName() + "." + mMethod.getName())
|
||||
.flush();
|
||||
mArgumentsProcessed = true;
|
||||
mArgumentExtractors = buildArgumentExtractors(mParameterTypes);
|
||||
mSignature = buildSignature(mMethod, mParameterTypes, (mType.equals(BaseJavaModule.METHOD_TYPE_SYNC)));
|
||||
// Since native methods are invoked from a message queue executed on a single thread, it is
|
||||
// safe to allocate only one arguments object per method that can be reused across calls
|
||||
mArguments = new Object[mParameterTypes.length];
|
||||
mJSArgumentsNeeded = calculateJSArgumentsNeeded();
|
||||
com.facebook.systrace.Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return mMethod;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
if (!mArgumentsProcessed) {
|
||||
processArguments();
|
||||
}
|
||||
return assertNotNull(mSignature);
|
||||
}
|
||||
|
||||
private String buildSignature(Method method, Class[] paramTypes, boolean isSync) {
|
||||
StringBuilder builder = new StringBuilder(paramTypes.length + 2);
|
||||
|
||||
if (isSync) {
|
||||
builder.append(returnTypeToChar(method.getReturnType()));
|
||||
builder.append('.');
|
||||
} else {
|
||||
builder.append("v.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
Class paramClass = paramTypes[i];
|
||||
if (paramClass == Promise.class) {
|
||||
Assertions.assertCondition(
|
||||
i == paramTypes.length - 1, "Promise must be used as last parameter only");
|
||||
}
|
||||
builder.append(paramTypeToChar(paramClass));
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private ArgumentExtractor[] buildArgumentExtractors(Class[] paramTypes) {
|
||||
ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length];
|
||||
for (int i = 0; i < paramTypes.length; i += argumentExtractors[i].getJSArgumentsNeeded()) {
|
||||
Class argumentClass = paramTypes[i];
|
||||
if (argumentClass == Boolean.class || argumentClass == boolean.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_BOOLEAN;
|
||||
} else if (argumentClass == Integer.class || argumentClass == int.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_INTEGER;
|
||||
} else if (argumentClass == Double.class || argumentClass == double.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_DOUBLE;
|
||||
} else if (argumentClass == Float.class || argumentClass == float.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_FLOAT;
|
||||
} else if (argumentClass == String.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_STRING;
|
||||
} else if (argumentClass == Callback.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_CALLBACK;
|
||||
} else if (argumentClass == Promise.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_PROMISE;
|
||||
Assertions.assertCondition(
|
||||
i == paramTypes.length - 1, "Promise must be used as last parameter only");
|
||||
} else if (argumentClass == ReadableMap.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_MAP;
|
||||
} else if (argumentClass == ReadableArray.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_ARRAY;
|
||||
} else if (argumentClass == Dynamic.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_DYNAMIC;
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Got unknown argument class: " + argumentClass.getSimpleName());
|
||||
}
|
||||
}
|
||||
return argumentExtractors;
|
||||
}
|
||||
|
||||
private int calculateJSArgumentsNeeded() {
|
||||
int n = 0;
|
||||
for (ArgumentExtractor extractor : assertNotNull(mArgumentExtractors)) {
|
||||
n += extractor.getJSArgumentsNeeded();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private String getAffectedRange(int startIndex, int jsArgumentsNeeded) {
|
||||
return jsArgumentsNeeded > 1 ?
|
||||
"" + startIndex + "-" + (startIndex + jsArgumentsNeeded - 1) : "" + startIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(JSInstance jsInstance, ReadableNativeArray parameters) {
|
||||
String traceName = mModuleWrapper.getName() + "." + mMethod.getName();
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "callJavaModuleMethod")
|
||||
.arg("method", traceName)
|
||||
.flush();
|
||||
try {
|
||||
if (!mArgumentsProcessed) {
|
||||
processArguments();
|
||||
}
|
||||
if (mArguments == null || mArgumentExtractors == null) {
|
||||
throw new Error("processArguments failed");
|
||||
}
|
||||
if (mJSArgumentsNeeded != parameters.size()) {
|
||||
throw new NativeArgumentsParseException(
|
||||
traceName + " got " + parameters.size() + " arguments, expected " + mJSArgumentsNeeded);
|
||||
}
|
||||
|
||||
int i = 0, jsArgumentsConsumed = 0;
|
||||
try {
|
||||
for (; i < mArgumentExtractors.length; i++) {
|
||||
mArguments[i] = mArgumentExtractors[i].extractArgument(
|
||||
jsInstance, parameters, jsArgumentsConsumed);
|
||||
jsArgumentsConsumed += mArgumentExtractors[i].getJSArgumentsNeeded();
|
||||
}
|
||||
} catch (UnexpectedNativeTypeException e) {
|
||||
throw new NativeArgumentsParseException(
|
||||
e.getMessage() + " (constructing arguments for " + traceName + " at argument index " +
|
||||
getAffectedRange(jsArgumentsConsumed, mArgumentExtractors[i].getJSArgumentsNeeded()) +
|
||||
")",
|
||||
e);
|
||||
}
|
||||
|
||||
try {
|
||||
mMethod.invoke(mModuleWrapper.getModule(), mArguments);
|
||||
} catch (IllegalArgumentException ie) {
|
||||
throw new RuntimeException("Could not invoke " + traceName, ie);
|
||||
} catch (IllegalAccessException iae) {
|
||||
throw new RuntimeException("Could not invoke " + traceName, 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 " + traceName, ite);
|
||||
}
|
||||
} finally {
|
||||
com.facebook.systrace.Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how the method is exported in JavaScript:
|
||||
* METHOD_TYPE_ASYNC for regular methods
|
||||
* METHOD_TYPE_PROMISE for methods that return a promise object to the caller.
|
||||
* METHOD_TYPE_SYNC for sync methods
|
||||
*/
|
||||
@Override
|
||||
public String getType() {
|
||||
return mType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.BaseJavaModule;
|
||||
import com.facebook.react.bridge.JSInstance;
|
||||
import com.facebook.react.bridge.NativeArray;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactMarker;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.facebook.systrace.Systrace;
|
||||
import com.facebook.systrace.SystraceMessage;
|
||||
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.CONVERT_CONSTANTS_END;
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.CONVERT_CONSTANTS_START;
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.GET_CONSTANTS_END;
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.GET_CONSTANTS_START;
|
||||
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
|
||||
|
||||
/**
|
||||
* This is part of the glue which wraps a java BaseJavaModule in a C++
|
||||
* NativeModule. This could all be in C++, but it's android-specific
|
||||
* initialization code, and writing it this way is easier to read and means
|
||||
* fewer JNI calls.
|
||||
*/
|
||||
|
||||
@DoNotStrip
|
||||
public class JavaModuleWrapper {
|
||||
@DoNotStrip
|
||||
public class MethodDescriptor {
|
||||
@DoNotStrip
|
||||
Method method;
|
||||
@DoNotStrip
|
||||
String signature;
|
||||
@DoNotStrip
|
||||
String name;
|
||||
@DoNotStrip
|
||||
String type;
|
||||
}
|
||||
|
||||
private final JSInstance mJSInstance;
|
||||
private final ModuleHolder mModuleHolder;
|
||||
private final Class<? extends NativeModule> mModuleClass;
|
||||
private final ArrayList<NativeModule.NativeMethod> mMethods;
|
||||
private final ArrayList<MethodDescriptor> mDescs;
|
||||
|
||||
public JavaModuleWrapper(JSInstance jsInstance, Class<? extends NativeModule> moduleClass, ModuleHolder moduleHolder) {
|
||||
mJSInstance = jsInstance;
|
||||
mModuleHolder = moduleHolder;
|
||||
mModuleClass = moduleClass;
|
||||
mMethods = new ArrayList<>();
|
||||
mDescs = new ArrayList();
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public BaseJavaModule getModule() {
|
||||
return (BaseJavaModule) mModuleHolder.getModule();
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public String getName() {
|
||||
return mModuleHolder.getName();
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
private void findMethods() {
|
||||
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "findMethods");
|
||||
Set<String> methodNames = new HashSet<>();
|
||||
|
||||
Method[] targetMethods = mModuleClass.getDeclaredMethods();
|
||||
for (Method targetMethod : targetMethods) {
|
||||
ReactMethod annotation = targetMethod.getAnnotation(ReactMethod.class);
|
||||
if (annotation != null) {
|
||||
String methodName = targetMethod.getName();
|
||||
if (methodNames.contains(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);
|
||||
}
|
||||
MethodDescriptor md = new MethodDescriptor();
|
||||
JavaMethodWrapper method = new JavaMethodWrapper(this, targetMethod, annotation.isBlockingSynchronousMethod());
|
||||
md.name = methodName;
|
||||
md.type = method.getType();
|
||||
if (md.type == BaseJavaModule.METHOD_TYPE_SYNC) {
|
||||
md.signature = method.getSignature();
|
||||
md.method = targetMethod;
|
||||
}
|
||||
mMethods.add(method);
|
||||
mDescs.add(md);
|
||||
}
|
||||
}
|
||||
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public List<MethodDescriptor> getMethodDescriptors() {
|
||||
if (mDescs.isEmpty()) {
|
||||
findMethods();
|
||||
}
|
||||
return mDescs;
|
||||
}
|
||||
|
||||
// TODO mhorowitz: make this return NativeMap, which requires moving
|
||||
// NativeMap out of OnLoad.
|
||||
@DoNotStrip
|
||||
public @Nullable NativeArray getConstants() {
|
||||
if (!mModuleHolder.getHasConstants()) {
|
||||
return null;
|
||||
}
|
||||
BaseJavaModule baseJavaModule = getModule();
|
||||
ReactMarker.logMarker(GET_CONSTANTS_START, getName());
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "Map constants")
|
||||
.arg("moduleName", getName())
|
||||
.flush();
|
||||
Map<String, Object> map = baseJavaModule.getConstants();
|
||||
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "WritableNativeMap constants")
|
||||
.arg("moduleName", getName())
|
||||
.flush();
|
||||
ReactMarker.logMarker(CONVERT_CONSTANTS_START, getName());
|
||||
WritableNativeMap writableNativeMap;
|
||||
try {
|
||||
writableNativeMap = Arguments.makeNativeMap(map);
|
||||
} finally {
|
||||
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
WritableNativeArray array = new WritableNativeArray();
|
||||
array.pushMap(writableNativeMap);
|
||||
ReactMarker.logMarker(CONVERT_CONSTANTS_END);
|
||||
ReactMarker.logMarker(GET_CONSTANTS_END);
|
||||
return array;
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public void invoke(int methodId, ReadableNativeArray parameters) {
|
||||
if (mMethods == null || methodId >= mMethods.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mMethods.get(methodId).invoke(mJSInstance, parameters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@DoNotStrip
|
||||
public abstract class JavaScriptExecutor {
|
||||
public interface Factory {
|
||||
JavaScriptExecutor create() throws Exception;
|
||||
}
|
||||
|
||||
private final HybridData mHybridData;
|
||||
|
||||
protected JavaScriptExecutor(HybridData hybridData) {
|
||||
mHybridData = hybridData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close this executor and cleanup any resources that it was using. No further calls are
|
||||
* expected after this.
|
||||
* TODO mhorowitz: This may no longer be used; check and delete if possible.
|
||||
*/
|
||||
public void close() {
|
||||
mHybridData.resetNative();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactMarker;
|
||||
import com.facebook.react.bridge.ReactMarkerConstants;
|
||||
import com.facebook.react.module.model.ReactModuleInfo;
|
||||
import com.facebook.systrace.Systrace;
|
||||
import com.facebook.systrace.SystraceMessage;
|
||||
|
||||
import static com.facebook.infer.annotation.Assertions.assertNotNull;
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_MODULE_END;
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_MODULE_START;
|
||||
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
|
||||
|
||||
/**
|
||||
* Holder to enable us to lazy create native modules.
|
||||
*
|
||||
* This works by taking a provider instead of an instance, when it is first required we'll create
|
||||
* and initialize it. Initialization currently always happens on the UI thread but this is due to
|
||||
* change for performance reasons.
|
||||
*
|
||||
* Lifecycle events via a {@link LifecycleEventListener} will still always happen on the UI thread.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class ModuleHolder {
|
||||
|
||||
private final String mName;
|
||||
private final boolean mCanOverrideExistingModule;
|
||||
private final boolean mHasConstants;
|
||||
|
||||
private @Nullable Provider<? extends NativeModule> mProvider;
|
||||
private @Nullable NativeModule mModule;
|
||||
private boolean mInitializeNeeded;
|
||||
|
||||
public ModuleHolder(ReactModuleInfo moduleInfo, Provider<? extends NativeModule> provider) {
|
||||
mName = moduleInfo.name();
|
||||
mCanOverrideExistingModule = moduleInfo.canOverrideExistingModule();
|
||||
mHasConstants = moduleInfo.hasConstants();
|
||||
mProvider = provider;
|
||||
if (moduleInfo.needsEagerInit()) {
|
||||
mModule = create();
|
||||
}
|
||||
}
|
||||
|
||||
public ModuleHolder(NativeModule nativeModule) {
|
||||
mName = nativeModule.getName();
|
||||
mCanOverrideExistingModule = nativeModule.canOverrideExistingModule();
|
||||
mHasConstants = true;
|
||||
mModule = nativeModule;
|
||||
}
|
||||
|
||||
public synchronized void initialize() {
|
||||
if (mModule != null) {
|
||||
doInitialize(mModule);
|
||||
} else {
|
||||
mInitializeNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean isInitialized() {
|
||||
return mModule != null;
|
||||
}
|
||||
|
||||
public synchronized void destroy() {
|
||||
if (mModule != null) {
|
||||
mModule.onCatalystInstanceDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public boolean getCanOverrideExistingModule() {
|
||||
return mCanOverrideExistingModule;
|
||||
}
|
||||
|
||||
public boolean getHasConstants() {
|
||||
return mHasConstants;
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public synchronized NativeModule getModule() {
|
||||
if (mModule == null) {
|
||||
mModule = create();
|
||||
}
|
||||
return mModule;
|
||||
}
|
||||
|
||||
private NativeModule create() {
|
||||
SoftAssertions.assertCondition(mModule == null, "Creating an already created module.");
|
||||
ReactMarker.logMarker(CREATE_MODULE_START, mName);
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createModule")
|
||||
.arg("name", mName)
|
||||
.flush();
|
||||
NativeModule module;
|
||||
try {
|
||||
module = assertNotNull(mProvider).get();
|
||||
mProvider = null;
|
||||
if (mInitializeNeeded) {
|
||||
doInitialize(module);
|
||||
mInitializeNeeded = false;
|
||||
}
|
||||
} finally {
|
||||
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
ReactMarker.logMarker(CREATE_MODULE_END);
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
private void doInitialize(NativeModule module) {
|
||||
SystraceMessage.Builder section =
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "initialize");
|
||||
if (module instanceof CxxModuleWrapper) {
|
||||
section.arg("className", module.getClass().getSimpleName());
|
||||
} else {
|
||||
section.arg("name", mName);
|
||||
}
|
||||
section.flush();
|
||||
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_START, mName);
|
||||
try {
|
||||
module.initialize();
|
||||
} finally {
|
||||
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_END);
|
||||
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* 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.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.JSInstance;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.OnBatchCompleteListener;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactMarker;
|
||||
import com.facebook.react.bridge.ReactMarkerConstants;
|
||||
import com.facebook.systrace.Systrace;
|
||||
|
||||
/**
|
||||
* A set of Java APIs to expose to a particular JavaScript instance.
|
||||
*/
|
||||
public class NativeModuleRegistry {
|
||||
|
||||
private final ReactApplicationContext mReactApplicationContext;
|
||||
private final Map<Class<? extends NativeModule>, ModuleHolder> mModules;
|
||||
private final ArrayList<ModuleHolder> mBatchCompleteListenerModules;
|
||||
|
||||
public NativeModuleRegistry(
|
||||
ReactApplicationContext reactApplicationContext,
|
||||
Map<Class<? extends NativeModule>, ModuleHolder> modules,
|
||||
ArrayList<ModuleHolder> batchCompleteListenerModules) {
|
||||
mReactApplicationContext = reactApplicationContext;
|
||||
mModules = modules;
|
||||
mBatchCompleteListenerModules = batchCompleteListenerModules;
|
||||
}
|
||||
|
||||
/* package */ Collection<JavaModuleWrapper> getJavaModules(
|
||||
JSInstance jsInstance) {
|
||||
ArrayList<JavaModuleWrapper> javaModules = new ArrayList<>();
|
||||
for (Map.Entry<Class<? extends NativeModule>, ModuleHolder> entry : mModules.entrySet()) {
|
||||
Class<? extends NativeModule> type = entry.getKey();
|
||||
if (!CxxModuleWrapperBase.class.isAssignableFrom(type)) {
|
||||
javaModules.add(new JavaModuleWrapper(jsInstance, type, entry.getValue()));
|
||||
}
|
||||
}
|
||||
return javaModules;
|
||||
}
|
||||
|
||||
/* package */ Collection<ModuleHolder> getCxxModules() {
|
||||
ArrayList<ModuleHolder> cxxModules = new ArrayList<>();
|
||||
for (Map.Entry<Class<? extends NativeModule>, ModuleHolder> entry : mModules.entrySet()) {
|
||||
Class<?> type = entry.getKey();
|
||||
if (CxxModuleWrapperBase.class.isAssignableFrom(type)) {
|
||||
cxxModules.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
return cxxModules;
|
||||
}
|
||||
|
||||
/* package */ void notifyJSInstanceDestroy() {
|
||||
mReactApplicationContext.assertOnNativeModulesQueueThread();
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
"NativeModuleRegistry_notifyJSInstanceDestroy");
|
||||
try {
|
||||
for (ModuleHolder module : mModules.values()) {
|
||||
module.destroy();
|
||||
}
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void notifyJSInstanceInitialized() {
|
||||
mReactApplicationContext.assertOnNativeModulesQueueThread("From version React Native v0.44, " +
|
||||
"native modules are explicitly not initialized on the UI thread. See " +
|
||||
"https://github.com/facebook/react-native/wiki/Breaking-Changes#d4611211-reactnativeandroidbreaking-move-nativemodule-initialization-off-ui-thread---aaachiuuu " +
|
||||
" for more details.");
|
||||
ReactMarker.logMarker(ReactMarkerConstants.NATIVE_MODULE_INITIALIZE_START);
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
"NativeModuleRegistry_notifyJSInstanceInitialized");
|
||||
try {
|
||||
for (ModuleHolder module : mModules.values()) {
|
||||
module.initialize();
|
||||
}
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
ReactMarker.logMarker(ReactMarkerConstants.NATIVE_MODULE_INITIALIZE_END);
|
||||
}
|
||||
}
|
||||
|
||||
public void onBatchComplete() {
|
||||
for (ModuleHolder moduleHolder : mBatchCompleteListenerModules) {
|
||||
if (moduleHolder.isInitialized()) {
|
||||
((OnBatchCompleteListener) moduleHolder.getModule()).onBatchComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends NativeModule> boolean hasModule(Class<T> moduleInterface) {
|
||||
return mModules.containsKey(moduleInterface);
|
||||
}
|
||||
|
||||
public <T extends NativeModule> T getModule(Class<T> moduleInterface) {
|
||||
return (T) Assertions.assertNotNull(mModules.get(moduleInterface)).getModule();
|
||||
}
|
||||
|
||||
public List<NativeModule> getAllModules() {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
for (ModuleHolder module : mModules.values()) {
|
||||
modules.add(module.getModule());
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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.jni.HybridData;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.JavaJSExecutor;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
public static class Factory implements JavaScriptExecutor.Factory {
|
||||
private final JavaJSExecutor.Factory mJavaJSExecutorFactory;
|
||||
|
||||
public Factory(JavaJSExecutor.Factory javaJSExecutorFactory) {
|
||||
mJavaJSExecutorFactory = javaJSExecutorFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaScriptExecutor create() throws Exception {
|
||||
return new ProxyJavaScriptExecutor(mJavaJSExecutorFactory.create());
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(CatalystInstanceImpl.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
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) {
|
||||
super(initHybrid(executor));
|
||||
mJavaJSExecutor = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (mJavaJSExecutor != null) {
|
||||
mJavaJSExecutor.close();
|
||||
mJavaJSExecutor = null;
|
||||
}
|
||||
}
|
||||
|
||||
private native static HybridData initHybrid(JavaJSExecutor executor);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@DoNotStrip
|
||||
/* package */ interface ReactCallback {
|
||||
@DoNotStrip
|
||||
void onBatchComplete();
|
||||
|
||||
@DoNotStrip
|
||||
void incrementPendingJSCalls();
|
||||
|
||||
@DoNotStrip
|
||||
void decrementPendingJSCalls();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
## Putting this here is kind of a hack. I don't want to modify the OSS bridge.
|
||||
## TODO mhorowitz: add @DoNotStrip to the interface directly.
|
||||
|
||||
-keepclassmembers class com.facebook.react.bridge.queue.MessageQueueThread {
|
||||
public boolean isOnThread();
|
||||
public void assertIsOnThread();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
-keepnames class * extends com.facebook.react.bridge.JavaScriptModule { *; }
|
||||
-keepnames class * extends com.facebook.react.cxxbridge.CxxModuleWrapper {*; }
|
||||
-keepnames class * extends com.facebook.react.bridge.CxxModuleWrapper {*; }
|
||||
-keepclassmembers class * extends com.facebook.react.bridge.NativeModule {
|
||||
@com.facebook.react.bridge.ReactMethod *;
|
||||
public <init>(...);
|
||||
@@ -17,3 +17,11 @@
|
||||
void markerAnnotate(int,int,java.lang.String,java.lang.String);
|
||||
void markerTag(int,int,java.lang.String);
|
||||
}
|
||||
|
||||
## Putting this here is kind of a hack. I don't want to modify the OSS bridge.
|
||||
## TODO mhorowitz: add @DoNotStrip to the interface directly.
|
||||
|
||||
-keepclassmembers class com.facebook.react.bridge.queue.MessageQueueThread {
|
||||
public boolean isOnThread();
|
||||
public void assertIsOnThread();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user