mirror of
https://github.com/status-im/react-native.git
synced 2025-02-25 07:35:25 +00:00
Merge ReactMethod and ReactSyncHook
Reviewed By: astreet Differential Revision: D4409726 fbshipit-source-id: 7a0091da754b114680772aa9c0a898b1aa721ba5
This commit is contained in:
parent
412acd237a
commit
59226f022c
@ -176,12 +176,18 @@ public abstract class BaseJavaModule implements NativeModule {
|
|||||||
private final int mJSArgumentsNeeded;
|
private final int mJSArgumentsNeeded;
|
||||||
private final String mTraceName;
|
private final String mTraceName;
|
||||||
|
|
||||||
public JavaMethod(Method method) {
|
public JavaMethod(Method method, boolean isSync) {
|
||||||
mMethod = method;
|
mMethod = method;
|
||||||
mMethod.setAccessible(true);
|
mMethod.setAccessible(true);
|
||||||
|
|
||||||
|
if (isSync) {
|
||||||
|
mType = METHOD_TYPE_SYNC;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: create these lazily
|
||||||
Class[] parameterTypes = method.getParameterTypes();
|
Class[] parameterTypes = method.getParameterTypes();
|
||||||
mArgumentExtractors = buildArgumentExtractors(parameterTypes);
|
mArgumentExtractors = buildArgumentExtractors(parameterTypes);
|
||||||
mSignature = buildSignature(parameterTypes);
|
mSignature = buildSignature(mMethod, parameterTypes, isSync);
|
||||||
// Since native methods are invoked from a message queue executed on a single thread, it is
|
// Since native methods are invoked from a message queue executed on a single thread, it is
|
||||||
// save to allocate only one arguments object per method that can be reused across calls
|
// save to allocate only one arguments object per method that can be reused across calls
|
||||||
mArguments = new Object[parameterTypes.length];
|
mArguments = new Object[parameterTypes.length];
|
||||||
@ -197,9 +203,16 @@ public abstract class BaseJavaModule implements NativeModule {
|
|||||||
return mSignature;
|
return mSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildSignature(Class[] paramTypes) {
|
private String buildSignature(Method method, Class[] paramTypes, boolean isSync) {
|
||||||
StringBuilder builder = new StringBuilder(paramTypes.length);
|
StringBuilder builder = new StringBuilder(paramTypes.length + 2);
|
||||||
builder.append("v.");
|
|
||||||
|
if (isSync) {
|
||||||
|
builder.append(returnTypeToChar(method.getReturnType()));
|
||||||
|
builder.append('.');
|
||||||
|
} else {
|
||||||
|
builder.append("v.");
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < paramTypes.length; i++) {
|
for (int i = 0; i < paramTypes.length; i++) {
|
||||||
Class paramClass = paramTypes[i];
|
Class paramClass = paramTypes[i];
|
||||||
if (paramClass == ExecutorToken.class) {
|
if (paramClass == ExecutorToken.class) {
|
||||||
@ -212,7 +225,9 @@ public abstract class BaseJavaModule implements NativeModule {
|
|||||||
} else if (paramClass == Promise.class) {
|
} else if (paramClass == Promise.class) {
|
||||||
Assertions.assertCondition(
|
Assertions.assertCondition(
|
||||||
i == paramTypes.length - 1, "Promise must be used as last parameter only");
|
i == paramTypes.length - 1, "Promise must be used as last parameter only");
|
||||||
mType = METHOD_TYPE_PROMISE;
|
if (!isSync) {
|
||||||
|
mType = METHOD_TYPE_PROMISE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
builder.append(paramTypeToChar(paramClass));
|
builder.append(paramTypeToChar(paramClass));
|
||||||
}
|
}
|
||||||
@ -352,6 +367,7 @@ public abstract class BaseJavaModule implements NativeModule {
|
|||||||
* Determines how the method is exported in JavaScript:
|
* Determines how the method is exported in JavaScript:
|
||||||
* METHOD_TYPE_ASYNC for regular methods
|
* METHOD_TYPE_ASYNC for regular methods
|
||||||
* METHOD_TYPE_PROMISE for methods that return a promise object to the caller.
|
* METHOD_TYPE_PROMISE for methods that return a promise object to the caller.
|
||||||
|
* METHOD_TYPE_SYNC for sync methods
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getType() {
|
public String getType() {
|
||||||
@ -359,82 +375,28 @@ public abstract class BaseJavaModule implements NativeModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SyncJavaHook implements SyncNativeHook {
|
|
||||||
|
|
||||||
private Method mMethod;
|
|
||||||
private final String mSignature;
|
|
||||||
|
|
||||||
public SyncJavaHook(Method method) {
|
|
||||||
mMethod = method;
|
|
||||||
mMethod.setAccessible(true);
|
|
||||||
mSignature = buildSignature(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Method getMethod() {
|
|
||||||
return mMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSignature() {
|
|
||||||
return mSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildSignature(Method method) {
|
|
||||||
Class[] paramTypes = method.getParameterTypes();
|
|
||||||
StringBuilder builder = new StringBuilder(paramTypes.length + 2);
|
|
||||||
|
|
||||||
builder.append(returnTypeToChar(method.getReturnType()));
|
|
||||||
builder.append('.');
|
|
||||||
|
|
||||||
for (int i = 0; i < paramTypes.length; i++) {
|
|
||||||
Class paramClass = paramTypes[i];
|
|
||||||
if (paramClass == ExecutorToken.class) {
|
|
||||||
if (!BaseJavaModule.this.supportsWebWorkers()) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Module " + BaseJavaModule.this + " doesn't support web workers, but " +
|
|
||||||
mMethod.getName() +
|
|
||||||
" takes an ExecutorToken.");
|
|
||||||
}
|
|
||||||
} else 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 @Nullable Map<String, NativeMethod> mMethods;
|
private @Nullable Map<String, NativeMethod> mMethods;
|
||||||
private @Nullable Map<String, SyncNativeHook> mHooks;
|
|
||||||
|
|
||||||
private void findMethods() {
|
private void findMethods() {
|
||||||
if (mMethods == null) {
|
if (mMethods == null) {
|
||||||
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "findMethods");
|
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "findMethods");
|
||||||
mMethods = new HashMap<>();
|
mMethods = new HashMap<>();
|
||||||
mHooks = new HashMap<>();
|
|
||||||
|
|
||||||
Method[] targetMethods = getClass().getDeclaredMethods();
|
Method[] targetMethods = getClass().getDeclaredMethods();
|
||||||
for (Method targetMethod : targetMethods) {
|
for (Method targetMethod : targetMethods) {
|
||||||
if (targetMethod.getAnnotation(ReactMethod.class) != null) {
|
ReactMethod annotation = targetMethod.getAnnotation(ReactMethod.class);
|
||||||
|
if (annotation != null) {
|
||||||
String methodName = targetMethod.getName();
|
String methodName = targetMethod.getName();
|
||||||
if (mHooks.containsKey(methodName) || mMethods.containsKey(methodName)) {
|
if (mMethods.containsKey(methodName)) {
|
||||||
// We do not support method overloading since js sees a function as an object regardless
|
// We do not support method overloading since js sees a function as an object regardless
|
||||||
// of number of params.
|
// of number of params.
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Java Module " + getName() + " sync method name already registered: " + methodName);
|
"Java Module " + getName() + " method name already registered: " + methodName);
|
||||||
}
|
}
|
||||||
mMethods.put(methodName, new JavaMethod(targetMethod));
|
mMethods.put(
|
||||||
}
|
methodName,
|
||||||
if (targetMethod.getAnnotation(ReactSyncHook.class) != null) {
|
new JavaMethod(targetMethod,
|
||||||
String methodName = targetMethod.getName();
|
annotation.isBlockingSynchronousMethod()));
|
||||||
if (mHooks.containsKey(methodName) || mMethods.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() + " sync method name already registered: " + methodName);
|
|
||||||
}
|
|
||||||
mHooks.put(methodName, new SyncJavaHook(targetMethod));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
|
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||||
@ -454,11 +416,6 @@ public abstract class BaseJavaModule implements NativeModule {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Map<String, SyncNativeHook> getSyncHooks() {
|
|
||||||
findMethods();
|
|
||||||
return assertNotNull(mHooks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@ -24,13 +24,6 @@ public interface NativeModule {
|
|||||||
String getType();
|
String getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A method that can be called from JS synchronously on the JS thread and return a result.
|
|
||||||
* @see ReactSyncHook
|
|
||||||
*/
|
|
||||||
interface SyncNativeHook {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the name of this module. This will be the name used to {@code require()} this module
|
* @return the name of this module. This will be the name used to {@code require()} this module
|
||||||
* from javascript.
|
* from javascript.
|
||||||
|
@ -14,13 +14,28 @@ import java.lang.annotation.Retention;
|
|||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation which is used to mark methods that are exposed to
|
* Annotation which is used to mark methods that are exposed to React Native.
|
||||||
* Catalyst. This applies to derived classes of {@link
|
*
|
||||||
* BaseJavaModule}, which will generate a list of exported methods by
|
* This applies to derived classes of {@link BaseJavaModule}, which will generate a list
|
||||||
* searching for those which are annotated with this annotation and
|
* of exported methods by searching for those which are annotated with this annotation
|
||||||
* adding a JS callback for each.
|
* and adding a JS callback for each.
|
||||||
*/
|
*/
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
public @interface ReactMethod {
|
public @interface ReactMethod {
|
||||||
|
/**
|
||||||
|
* Whether the method can be called from JS synchronously **on the JS thread**,
|
||||||
|
* possibly returning a result.
|
||||||
|
*
|
||||||
|
* WARNING: in the vast majority of cases, you should leave this to false which allows
|
||||||
|
* your native module methods to be called asynchronously: calling methods
|
||||||
|
* synchronously can have strong performance penalties and introduce threading-related
|
||||||
|
* bugs to your native modules.
|
||||||
|
*
|
||||||
|
* In order to support remote debugging, both the method args and return type must be
|
||||||
|
* serializable to JSON: this means that we only support the same args as
|
||||||
|
* {@link ReactMethod}, and the hook can only be void or return JSON values (e.g. bool,
|
||||||
|
* number, String, {@link WritableMap}, or {@link WritableArray}). Calling these
|
||||||
|
* methods when running under the websocket executor is currently not supported.
|
||||||
|
*/
|
||||||
|
boolean isBlockingSynchronousMethod() default false;
|
||||||
}
|
}
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 for a method in a {@link NativeModule} that can be called from JS synchronously **on
|
|
||||||
* the JS thread**, possibly returning a result.
|
|
||||||
*
|
|
||||||
* In order to support remote debugging, both the method args and return type must be serializable
|
|
||||||
* to JSON: this means that we only support the same args as {@link ReactMethod}, and the hook can
|
|
||||||
* only be void or return JSON values (e.g. bool, number, String, {@link WritableMap}, or
|
|
||||||
* {@link WritableArray}).
|
|
||||||
*
|
|
||||||
* In the vast majority of cases, you should use {@link ReactMethod} which allows your native module
|
|
||||||
* methods to be called asynchronously: calling methods synchronously can have strong performance
|
|
||||||
* penalties and introduce threading-related bugs to your native modules.
|
|
||||||
*/
|
|
||||||
@Retention(RUNTIME)
|
|
||||||
public @interface ReactSyncHook {
|
|
||||||
}
|
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
package com.facebook.react.cxxbridge;
|
package com.facebook.react.cxxbridge;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -19,6 +20,7 @@ import com.facebook.react.bridge.CatalystInstance;
|
|||||||
import com.facebook.react.bridge.ExecutorToken;
|
import com.facebook.react.bridge.ExecutorToken;
|
||||||
import com.facebook.react.bridge.NativeArray;
|
import com.facebook.react.bridge.NativeArray;
|
||||||
import com.facebook.react.bridge.NativeModuleLogger;
|
import com.facebook.react.bridge.NativeModuleLogger;
|
||||||
|
import com.facebook.react.bridge.NativeModule;
|
||||||
import com.facebook.react.bridge.ReadableNativeArray;
|
import com.facebook.react.bridge.ReadableNativeArray;
|
||||||
import com.facebook.react.bridge.WritableNativeArray;
|
import com.facebook.react.bridge.WritableNativeArray;
|
||||||
import com.facebook.react.bridge.WritableNativeMap;
|
import com.facebook.react.bridge.WritableNativeMap;
|
||||||
@ -38,6 +40,10 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
|
|||||||
/* package */ class JavaModuleWrapper {
|
/* package */ class JavaModuleWrapper {
|
||||||
@DoNotStrip
|
@DoNotStrip
|
||||||
public class MethodDescriptor {
|
public class MethodDescriptor {
|
||||||
|
@DoNotStrip
|
||||||
|
Method method;
|
||||||
|
@DoNotStrip
|
||||||
|
String signature;
|
||||||
@DoNotStrip
|
@DoNotStrip
|
||||||
String name;
|
String name;
|
||||||
@DoNotStrip
|
@DoNotStrip
|
||||||
@ -46,7 +52,7 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
|
|||||||
|
|
||||||
private final CatalystInstance mCatalystInstance;
|
private final CatalystInstance mCatalystInstance;
|
||||||
private final ModuleHolder mModuleHolder;
|
private final ModuleHolder mModuleHolder;
|
||||||
private final ArrayList<BaseJavaModule.JavaMethod> mMethods;
|
private final ArrayList<NativeModule.NativeMethod> mMethods;
|
||||||
|
|
||||||
public JavaModuleWrapper(CatalystInstance catalystinstance, ModuleHolder moduleHolder) {
|
public JavaModuleWrapper(CatalystInstance catalystinstance, ModuleHolder moduleHolder) {
|
||||||
mCatalystInstance = catalystinstance;
|
mCatalystInstance = catalystinstance;
|
||||||
@ -67,19 +73,21 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
|
|||||||
@DoNotStrip
|
@DoNotStrip
|
||||||
public List<MethodDescriptor> getMethodDescriptors() {
|
public List<MethodDescriptor> getMethodDescriptors() {
|
||||||
ArrayList<MethodDescriptor> descs = new ArrayList<>();
|
ArrayList<MethodDescriptor> descs = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, NativeModule.NativeMethod> entry :
|
||||||
for (Map.Entry<String, BaseJavaModule.NativeMethod> entry :
|
getModule().getMethods().entrySet()) {
|
||||||
getModule().getMethods().entrySet()) {
|
|
||||||
MethodDescriptor md = new MethodDescriptor();
|
MethodDescriptor md = new MethodDescriptor();
|
||||||
md.name = entry.getKey();
|
md.name = entry.getKey();
|
||||||
md.type = entry.getValue().getType();
|
md.type = entry.getValue().getType();
|
||||||
|
|
||||||
BaseJavaModule.JavaMethod method = (BaseJavaModule.JavaMethod) entry.getValue();
|
BaseJavaModule.JavaMethod method = (BaseJavaModule.JavaMethod) entry.getValue();
|
||||||
|
if (md.type == BaseJavaModule.METHOD_TYPE_SYNC) {
|
||||||
|
md.signature = method.getSignature();
|
||||||
|
md.method = method.getMethod();
|
||||||
|
}
|
||||||
mMethods.add(method);
|
mMethods.add(method);
|
||||||
|
|
||||||
descs.add(md);
|
descs.add(md);
|
||||||
}
|
}
|
||||||
|
|
||||||
return descs;
|
return descs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cxxreact/ModuleRegistry.h>
|
||||||
#include <fb/fbjni.h>
|
#include <fb/fbjni.h>
|
||||||
|
|
||||||
#include <cxxreact/ModuleRegistry.h>
|
|
||||||
#include "CxxModuleWrapper.h"
|
#include "CxxModuleWrapper.h"
|
||||||
|
|
||||||
namespace facebook {
|
namespace facebook {
|
||||||
|
@ -11,7 +11,6 @@ package com.facebook.react.bridge;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.facebook.react.bridge.Arguments;
|
|
||||||
import com.facebook.react.bridge.ReadableNativeArray;
|
import com.facebook.react.bridge.ReadableNativeArray;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@ -55,20 +54,28 @@ public class BaseJavaModuleTest {
|
|||||||
regularMethod.invoke(null, null, mArguments);
|
regularMethod.invoke(null, null, mArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = NativeArgumentsParseException.class)
|
@Test
|
||||||
public void testCallAsyncMethodWithoutEnoughArgs() throws Exception {
|
public void testCallMethodWithEnoughArgs() {
|
||||||
BaseJavaModule.NativeMethod asyncMethod = mMethods.get("asyncMethod");
|
BaseJavaModule.NativeMethod regularMethod = mMethods.get("regularMethod");
|
||||||
Mockito.stub(mArguments.size()).toReturn(2);
|
Mockito.stub(mArguments.size()).toReturn(2);
|
||||||
asyncMethod.invoke(null, null, mArguments);
|
regularMethod.invoke(null, null, mArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test()
|
@Test
|
||||||
public void testCallAsyncMethodWithEnoughArgs() throws Exception {
|
public void testCallAsyncMethodWithEnoughArgs() {
|
||||||
|
// Promise block evaluates to 2 args needing to be passed from JS
|
||||||
BaseJavaModule.NativeMethod asyncMethod = mMethods.get("asyncMethod");
|
BaseJavaModule.NativeMethod asyncMethod = mMethods.get("asyncMethod");
|
||||||
Mockito.stub(mArguments.size()).toReturn(3);
|
Mockito.stub(mArguments.size()).toReturn(3);
|
||||||
asyncMethod.invoke(null, null, mArguments);
|
asyncMethod.invoke(null, null, mArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCallSyncMethod() {
|
||||||
|
BaseJavaModule.NativeMethod syncMethod = mMethods.get("syncMethod");
|
||||||
|
Mockito.stub(mArguments.size()).toReturn(2);
|
||||||
|
syncMethod.invoke(null, null, mArguments);
|
||||||
|
}
|
||||||
|
|
||||||
private static class MethodsModule extends BaseJavaModule {
|
private static class MethodsModule extends BaseJavaModule {
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@ -76,11 +83,14 @@ public class BaseJavaModuleTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void regularMethod(String a, int b) {
|
public void regularMethod(String a, int b) {}
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void asyncMethod(int a, Promise p) {
|
public void asyncMethod(int a, Promise p) {}
|
||||||
|
|
||||||
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||||
|
public int syncMethod(int a, int b) {
|
||||||
|
return a + b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user