mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-09 22:43:10 +08:00
Add ability to expose sync hooks from Java to JS
Summary:
This only works for the new cxx bridge (hopefully open sourcing soon!).
This diff allows Java native modules to expose synchronous hooks to JS via the ReactSyncHook annotation. The methods will appear in JS on the native module object (e.g. you would do `require('UIManager').mySyncHook('foo');`) which allows us to enforce that required native modules are installed at build time. In order to support remote debugging, both the args and return type must be JSON serializable (so that we can go back across to the device to resolve synchronous hooks).
Follow ups will be integration tests, adding support for return types besides void, and adding support for remote debugging.
Reviewed By: mhorowitz
Differential Revision: D3218794
fb-gh-sync-id: 7e3366a8254276f5a55eb806287419287ca9182b
fbshipit-source-id: 7e3366a8254276f5a55eb806287419287ca9182b
This commit is contained in:
committed by
Facebook Github Bot 2
parent
171c7230b1
commit
50d8d46733
@@ -49,6 +49,7 @@ public abstract class BaseJavaModule implements NativeModule {
|
||||
// taken from Libraries/Utilities/MessageQueue.js
|
||||
static final public String METHOD_TYPE_REMOTE = "remote";
|
||||
static final public String METHOD_TYPE_REMOTE_ASYNC = "remoteAsync";
|
||||
static final public String METHOD_TYPE_SYNC_HOOK = "syncHook";
|
||||
|
||||
private static abstract class ArgumentExtractor<T> {
|
||||
public int getJSArgumentsNeeded() {
|
||||
@@ -186,56 +187,28 @@ public abstract class BaseJavaModule implements NativeModule {
|
||||
|
||||
private String buildSignature(Class[] paramTypes) {
|
||||
StringBuilder builder = new StringBuilder(paramTypes.length);
|
||||
builder.append("v.");
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
Class argumentClass = paramTypes[i];
|
||||
if (argumentClass == ExecutorToken.class) {
|
||||
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.");
|
||||
}
|
||||
|
||||
builder.append('T');
|
||||
} else if (argumentClass == boolean.class) {
|
||||
builder.append('z');
|
||||
} else if (argumentClass == Boolean.class) {
|
||||
builder.append('Z');
|
||||
} else if (argumentClass == int.class) {
|
||||
builder.append('i');
|
||||
} else if (argumentClass == Integer.class) {
|
||||
builder.append('I');
|
||||
} else if (argumentClass == double.class) {
|
||||
builder.append('d');
|
||||
} else if (argumentClass == Double.class) {
|
||||
builder.append('D');
|
||||
} else if (argumentClass == float.class) {
|
||||
builder.append('f');
|
||||
} else if (argumentClass == Float.class) {
|
||||
builder.append('F');
|
||||
} else if (argumentClass == String.class) {
|
||||
builder.append('S');
|
||||
} else if (argumentClass == Callback.class) {
|
||||
builder.append('X');
|
||||
} else if (argumentClass == Promise.class) {
|
||||
builder.append('P');
|
||||
} else if (paramClass == Promise.class) {
|
||||
Assertions.assertCondition(
|
||||
i == paramTypes.length - 1, "Promise must be used as last parameter only");
|
||||
mType = METHOD_TYPE_REMOTE_ASYNC;
|
||||
} else if (argumentClass == ReadableMap.class) {
|
||||
builder.append('M');
|
||||
} else if (argumentClass == ReadableArray.class) {
|
||||
builder.append('A');
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Got unknown argument class: " + argumentClass.getSimpleName());
|
||||
}
|
||||
builder.append(paramTypeToChar(paramClass));
|
||||
}
|
||||
|
||||
// Modules that support web workers are expected to take an ExecutorToken as the first
|
||||
// parameter to all their @ReactMethod-annotated methods.
|
||||
if (BaseJavaModule.this.supportsWebWorkers()) {
|
||||
if (builder.charAt(0) != 'T') {
|
||||
if (builder.charAt(2) != 'T') {
|
||||
throw new RuntimeException(
|
||||
"Module " + BaseJavaModule.this + " supports web workers, but " + mMethod.getName() +
|
||||
"does not take an ExecutorToken as its first parameter.");
|
||||
@@ -372,24 +345,84 @@ public abstract class BaseJavaModule implements NativeModule {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Map<String, NativeMethod> getMethods() {
|
||||
Map<String, NativeMethod> methods = new HashMap<String, NativeMethod>();
|
||||
public class SyncJavaHook implements SyncNativeHook {
|
||||
|
||||
private Method mMethod;
|
||||
private final String mSignature;
|
||||
|
||||
public SyncJavaHook(Method method) {
|
||||
mMethod = method;
|
||||
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 final Map<String, NativeMethod> mMethods = new HashMap<>();
|
||||
private final Map<String, SyncNativeHook> mHooks = new HashMap<>();
|
||||
|
||||
public BaseJavaModule() {
|
||||
Method[] targetMethods = getClass().getDeclaredMethods();
|
||||
for (int i = 0; i < targetMethods.length; i++) {
|
||||
Method targetMethod = targetMethods[i];
|
||||
if (targetMethod.getAnnotation(ReactMethod.class) != null) {
|
||||
String methodName = targetMethod.getName();
|
||||
if (methods.containsKey(methodName)) {
|
||||
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() + " method name already registered: " + methodName);
|
||||
}
|
||||
methods.put(methodName, new JavaMethod(targetMethod));
|
||||
"Java Module " + getName() + " sync method name already registered: " + methodName);
|
||||
}
|
||||
mMethods.put(methodName, new JavaMethod(targetMethod));
|
||||
}
|
||||
if (targetMethod.getAnnotation(ReactSyncHook.class) != null) {
|
||||
String methodName = targetMethod.getName();
|
||||
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));
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Map<String, NativeMethod> getMethods() {
|
||||
return mMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,6 +432,10 @@ public abstract class BaseJavaModule implements NativeModule {
|
||||
return null;
|
||||
}
|
||||
|
||||
public final Map<String, SyncNativeHook> getSyncHooks() {
|
||||
return mHooks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void writeConstantsField(JsonWriter writer, String fieldName) throws IOException {
|
||||
Map<String, Object> constants = getConstants();
|
||||
@@ -438,4 +475,66 @@ public abstract class BaseJavaModule implements NativeModule {
|
||||
public boolean supportsWebWorkers() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static char paramTypeToChar(Class paramClass) {
|
||||
char tryCommon = commonTypeToChar(paramClass);
|
||||
if (tryCommon != '\0') {
|
||||
return tryCommon;
|
||||
}
|
||||
if (paramClass == ExecutorToken.class) {
|
||||
return 'T';
|
||||
} else 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 {
|
||||
throw new RuntimeException(
|
||||
"Got unknown param class: " + paramClass.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
private static char returnTypeToChar(Class returnClass) {
|
||||
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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user