Stop requiring registration of callable JS modules

Reviewed By: AaaChiuuu

Differential Revision: D5229073

fbshipit-source-id: d6d1967982ae379733a7e9667515ca9f074aadd4
This commit is contained in:
Pieter De Baets 2017-06-13 05:44:10 -07:00 committed by Facebook Github Bot
parent 1d30f833a6
commit 53d5504f40
3 changed files with 58 additions and 124 deletions

View File

@ -75,7 +75,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
private final ArrayList<PendingJSCall> mJSCallsPendingInit = new ArrayList<PendingJSCall>(); private final ArrayList<PendingJSCall> mJSCallsPendingInit = new ArrayList<PendingJSCall>();
private final Object mJSCallsPendingInitLock = new Object(); private final Object mJSCallsPendingInitLock = new Object();
private final NativeModuleRegistry mJavaRegistry; private final NativeModuleRegistry mNativeModuleRegistry;
private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private final MessageQueueThread mNativeModulesQueueThread; private final MessageQueueThread mNativeModulesQueueThread;
private final @Nullable MessageQueueThread mUIBackgroundQueueThread; private final @Nullable MessageQueueThread mUIBackgroundQueueThread;
@ -92,8 +92,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
private CatalystInstanceImpl( private CatalystInstanceImpl(
final ReactQueueConfigurationSpec reactQueueConfigurationSpec, final ReactQueueConfigurationSpec reactQueueConfigurationSpec,
final JavaScriptExecutor jsExecutor, final JavaScriptExecutor jsExecutor,
final NativeModuleRegistry registry, final NativeModuleRegistry nativeModuleRegistry,
final JavaScriptModuleRegistry jsModuleRegistry,
final JSBundleLoader jsBundleLoader, final JSBundleLoader jsBundleLoader,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) { NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
Log.d(ReactConstants.TAG, "Initializing React Xplat Bridge."); Log.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
@ -103,8 +102,8 @@ public class CatalystInstanceImpl implements CatalystInstance {
reactQueueConfigurationSpec, reactQueueConfigurationSpec,
new NativeExceptionHandler()); new NativeExceptionHandler());
mBridgeIdleListeners = new CopyOnWriteArrayList<>(); mBridgeIdleListeners = new CopyOnWriteArrayList<>();
mJavaRegistry = registry; mNativeModuleRegistry = nativeModuleRegistry;
mJSModuleRegistry = jsModuleRegistry; mJSModuleRegistry = new JavaScriptModuleRegistry();
mJSBundleLoader = jsBundleLoader; mJSBundleLoader = jsBundleLoader;
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler; mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
mNativeModulesQueueThread = mReactQueueConfiguration.getNativeModulesQueueThread(); mNativeModulesQueueThread = mReactQueueConfiguration.getNativeModulesQueueThread();
@ -118,8 +117,8 @@ public class CatalystInstanceImpl implements CatalystInstance {
mReactQueueConfiguration.getJSQueueThread(), mReactQueueConfiguration.getJSQueueThread(),
mNativeModulesQueueThread, mNativeModulesQueueThread,
mUIBackgroundQueueThread, mUIBackgroundQueueThread,
mJavaRegistry.getJavaModules(this), mNativeModuleRegistry.getJavaModules(this),
mJavaRegistry.getCxxModules()); mNativeModuleRegistry.getCxxModules());
Log.d(ReactConstants.TAG, "Initializing React Xplat Bridge after initializeBridge"); Log.d(ReactConstants.TAG, "Initializing React Xplat Bridge after initializeBridge");
} }
@ -137,7 +136,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
public void onBatchComplete() { public void onBatchComplete() {
CatalystInstanceImpl impl = mOuter.get(); CatalystInstanceImpl impl = mOuter.get();
if (impl != null) { if (impl != null) {
impl.mJavaRegistry.onBatchComplete(); impl.mNativeModuleRegistry.onBatchComplete();
} }
} }
@ -294,7 +293,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
mNativeModulesQueueThread.runOnQueue(new Runnable() { mNativeModulesQueueThread.runOnQueue(new Runnable() {
@Override @Override
public void run() { public void run() {
mJavaRegistry.notifyJSInstanceDestroy(); mNativeModuleRegistry.notifyJSInstanceDestroy();
boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0); boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
if (!wasIdle && !mBridgeIdleListeners.isEmpty()) { if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) { for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
@ -342,7 +341,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
mNativeModulesQueueThread.runOnQueue(new Runnable() { mNativeModulesQueueThread.runOnQueue(new Runnable() {
@Override @Override
public void run() { public void run() {
mJavaRegistry.notifyJSInstanceInitialized(); mNativeModuleRegistry.notifyJSInstanceInitialized();
} }
}); });
} }
@ -359,19 +358,19 @@ public class CatalystInstanceImpl implements CatalystInstance {
@Override @Override
public <T extends NativeModule> boolean hasNativeModule(Class<T> nativeModuleInterface) { public <T extends NativeModule> boolean hasNativeModule(Class<T> nativeModuleInterface) {
return mJavaRegistry.hasModule(nativeModuleInterface); return mNativeModuleRegistry.hasModule(nativeModuleInterface);
} }
// This is only ever called with UIManagerModule or CurrentViewerModule. // This is only ever called with UIManagerModule or CurrentViewerModule.
@Override @Override
public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) { public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
return mJavaRegistry.getModule(nativeModuleInterface); return mNativeModuleRegistry.getModule(nativeModuleInterface);
} }
// This is only used by com.facebook.react.modules.common.ModuleDataCleaner // This is only used by com.facebook.react.modules.common.ModuleDataCleaner
@Override @Override
public Collection<NativeModule> getNativeModules() { public Collection<NativeModule> getNativeModules() {
return mJavaRegistry.getAllModules(); return mNativeModuleRegistry.getAllModules();
} }
private native void handleMemoryPressureUiHidden(); private native void handleMemoryPressureUiHidden();
@ -528,7 +527,6 @@ public class CatalystInstanceImpl implements CatalystInstance {
private @Nullable ReactQueueConfigurationSpec mReactQueueConfigurationSpec; private @Nullable ReactQueueConfigurationSpec mReactQueueConfigurationSpec;
private @Nullable JSBundleLoader mJSBundleLoader; private @Nullable JSBundleLoader mJSBundleLoader;
private @Nullable NativeModuleRegistry mRegistry; private @Nullable NativeModuleRegistry mRegistry;
private @Nullable JavaScriptModuleRegistry mJSModuleRegistry;
private @Nullable JavaScriptExecutor mJSExecutor; private @Nullable JavaScriptExecutor mJSExecutor;
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
@ -544,7 +542,6 @@ public class CatalystInstanceImpl implements CatalystInstance {
} }
public Builder setJSModuleRegistry(JavaScriptModuleRegistry jsModuleRegistry) { public Builder setJSModuleRegistry(JavaScriptModuleRegistry jsModuleRegistry) {
mJSModuleRegistry = jsModuleRegistry;
return this; return this;
} }
@ -569,7 +566,6 @@ public class CatalystInstanceImpl implements CatalystInstance {
Assertions.assertNotNull(mReactQueueConfigurationSpec), Assertions.assertNotNull(mReactQueueConfigurationSpec),
Assertions.assertNotNull(mJSExecutor), Assertions.assertNotNull(mJSExecutor),
Assertions.assertNotNull(mRegistry), Assertions.assertNotNull(mRegistry),
Assertions.assertNotNull(mJSModuleRegistry),
Assertions.assertNotNull(mJSBundleLoader), Assertions.assertNotNull(mJSBundleLoader),
Assertions.assertNotNull(mNativeModuleCallExceptionHandler)); Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
} }

View File

@ -1,71 +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 javax.annotation.concurrent.Immutable;
import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import com.facebook.react.common.build.ReactBuildConfig;
/**
* Registration info for a {@link JavaScriptModule}. Maps its methods to method ids.
*/
@Immutable
public class JavaScriptModuleRegistration {
private final Class<? extends JavaScriptModule> mModuleInterface;
private @Nullable String mName;
public JavaScriptModuleRegistration(Class<? extends JavaScriptModule> moduleInterface) {
mModuleInterface = moduleInterface;
if (ReactBuildConfig.DEBUG) {
Set<String> methodNames = new LinkedHashSet<>();
for (Method method : mModuleInterface.getDeclaredMethods()) {
if (!methodNames.add(method.getName())) {
throw new AssertionError(
"Method overloading is unsupported: " + mModuleInterface.getName() + "#" +
method.getName());
}
}
}
}
public Class<? extends JavaScriptModule> getModuleInterface() {
return mModuleInterface;
}
public String getName() {
if (mName == null) {
// With proguard obfuscation turned on, proguard apparently (poorly) emulates inner classes or
// something because Class#getSimpleName() no longer strips the outer class name. We manually
// strip it here if necessary.
String name = mModuleInterface.getSimpleName();
int dollarSignIndex = name.lastIndexOf('$');
if (dollarSignIndex != -1) {
name = name.substring(dollarSignIndex + 1);
}
// getting the class name every call is expensive, so cache it
mName = name;
}
return mName;
}
public List<Method> getMethods() {
return Arrays.asList(mModuleInterface.getDeclaredMethods());
}
}

View File

@ -11,35 +11,25 @@ package com.facebook.react.bridge;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.HashSet;
import java.util.WeakHashMap; import java.util.Set;
import com.facebook.common.logging.FLog; import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.common.ReactConstants;
/** /**
* Class responsible for holding all the {@link JavaScriptModule}s registered to this * Class responsible for holding all the {@link JavaScriptModule}s. Uses Java proxy objects
* {@link CatalystInstance}. Uses Java proxy objects to dispatch method calls on JavaScriptModules * to dispatch method calls on JavaScriptModules to the bridge using the corresponding
* to the bridge using the corresponding module and method ids so the proper function is executed in * module and method ids so the proper function is executed in JavaScript.
* JavaScript.
*/ */
public class JavaScriptModuleRegistry { public final class JavaScriptModuleRegistry {
private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances; private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;
private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModuleRegistration> mModuleRegistrations;
public JavaScriptModuleRegistry(List<JavaScriptModuleRegistration> config) { public JavaScriptModuleRegistry() {
mModuleInstances = new HashMap<>(); mModuleInstances = new HashMap<>();
mModuleRegistrations = new HashMap<>();
for (JavaScriptModuleRegistration registration : config) {
mModuleRegistrations.put(registration.getModuleInterface(), registration);
}
} }
public synchronized <T extends JavaScriptModule> T getJavaScriptModule( public synchronized <T extends JavaScriptModule> T getJavaScriptModule(
@ -50,51 +40,70 @@ public class JavaScriptModuleRegistry {
return (T) module; return (T) module;
} }
JavaScriptModuleRegistration registration =
Assertions.assertNotNull(
mModuleRegistrations.get(moduleInterface),
"JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance( JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
moduleInterface.getClassLoader(), moduleInterface.getClassLoader(),
new Class[]{moduleInterface}, new Class[]{moduleInterface},
new JavaScriptModuleInvocationHandler(instance, registration)); new JavaScriptModuleInvocationHandler(instance, moduleInterface));
mModuleInstances.put(moduleInterface, interfaceProxy); mModuleInstances.put(moduleInterface, interfaceProxy);
return (T) interfaceProxy; return (T) interfaceProxy;
} }
public static class Builder { public static class Builder {
private List<JavaScriptModuleRegistration> mModules =
new ArrayList<JavaScriptModuleRegistration>();
public Builder add(Class<? extends JavaScriptModule> moduleInterfaceClass) { public Builder add(Class<? extends JavaScriptModule> moduleInterfaceClass) {
mModules.add(new JavaScriptModuleRegistration(moduleInterfaceClass));
return this; return this;
} }
public JavaScriptModuleRegistry build() { public JavaScriptModuleRegistry build() {
return new JavaScriptModuleRegistry(mModules); return new JavaScriptModuleRegistry();
} }
} }
private static class JavaScriptModuleInvocationHandler implements InvocationHandler { private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
private final CatalystInstance mCatalystInstance; private final CatalystInstance mCatalystInstance;
private final JavaScriptModuleRegistration mModuleRegistration; private final Class<? extends JavaScriptModule> mModuleInterface;
private @Nullable String mName;
public JavaScriptModuleInvocationHandler( public JavaScriptModuleInvocationHandler(
CatalystInstance catalystInstance, CatalystInstance catalystInstance,
JavaScriptModuleRegistration moduleRegistration) { Class<? extends JavaScriptModule> moduleInterface) {
mCatalystInstance = catalystInstance; mCatalystInstance = catalystInstance;
mModuleRegistration = moduleRegistration; mModuleInterface = moduleInterface;
if (ReactBuildConfig.DEBUG) {
Set<String> methodNames = new HashSet<>();
for (Method method : mModuleInterface.getDeclaredMethods()) {
if (!methodNames.add(method.getName())) {
throw new AssertionError(
"Method overloading is unsupported: " + mModuleInterface.getName() +
"#" + method.getName());
}
}
}
}
private String getJSModuleName() {
if (mName == null) {
// With proguard obfuscation turned on, proguard apparently (poorly) emulates inner
// classes or something because Class#getSimpleName() no longer strips the outer
// class name. We manually strip it here if necessary.
String name = mModuleInterface.getSimpleName();
int dollarSignIndex = name.lastIndexOf('$');
if (dollarSignIndex != -1) {
name = name.substring(dollarSignIndex + 1);
}
// getting the class name every call is expensive, so cache it
mName = name;
}
return mName;
} }
@Override @Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray(); NativeArray jsArgs = args != null
mCatalystInstance.callFunction( ? Arguments.fromJavaArgs(args)
mModuleRegistration.getName(), : new WritableNativeArray();
method.getName(), mCatalystInstance.callFunction(getJSModuleName(), method.getName(), jsArgs);
jsArgs
);
return null; return null;
} }
} }