Add ability to lazy load Native Java Modules

Summary: Utilizes the build time annotation processor ReactModuleSpecProcessor that creates ReactModuleInfos for modules annotated with ReactModule and listed in the ReactModuleList annotation of LazyReactPackages. This way we don't have to instantiate the native modules to get the name, canOverrideExistingModule, and supportsWebWorkers values of the native modules. In the NativeModuleRegistry, we either store these ReactModuleInfos inside of a ModuleHolder or if we can't get the ReactModuleInfo for a specific module we instantiate that module to get the values (as we previously did) to store in a LegacyModuleInfo.

Reviewed By: astreet

Differential Revision: D3796561

fbshipit-source-id: f8fb9b4993f59b51ce595eb2f2c3425129b28ce5
This commit is contained in:
Aaron Chiu 2016-09-23 15:48:54 -07:00 committed by Facebook Github Bot 3
parent 1f9b765f81
commit 797ca6c219
16 changed files with 498 additions and 118 deletions

View File

@ -16,6 +16,7 @@ android_library(
react_native_target('java/com/facebook/react/common:common'),
react_native_target('java/com/facebook/react/cxxbridge:bridge'),
react_native_target('java/com/facebook/react/devsupport:devsupport'),
react_native_target('java/com/facebook/react/module/model:model'),
react_native_target('java/com/facebook/react/modules/core:core'),
react_native_target('java/com/facebook/react/modules/debug:debug'),
react_native_target('java/com/facebook/react/shell:shell'),

View File

@ -22,17 +22,16 @@ import android.view.ViewGroup;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.SoftAssertions;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.ApplicationHolder;
import com.facebook.react.common.futures.SimpleSettableFuture;
import com.facebook.react.devsupport.DevSupportManager;
import com.facebook.react.modules.core.Timing;
import com.facebook.soloader.SoLoader;
import static org.mockito.Mockito.mock;

View File

@ -10,6 +10,9 @@ package com.facebook.react.testing;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import android.app.Instrumentation;
@ -18,30 +21,34 @@ import android.support.test.InstrumentationRegistry;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.EagerModuleProvider;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.cxxbridge.CatalystInstanceImpl;
import com.facebook.react.cxxbridge.JSBundleLoader;
import com.facebook.react.cxxbridge.NativeModuleRegistry;
import com.facebook.react.cxxbridge.JSCJavaScriptExecutor;
import com.facebook.react.cxxbridge.JavaScriptExecutor;
import com.facebook.react.bridge.JavaScriptModuleRegistry;
import com.facebook.react.bridge.ModuleSpec;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
import com.facebook.react.cxxbridge.CatalystInstanceImpl;
import com.facebook.react.cxxbridge.JSBundleLoader;
import com.facebook.react.cxxbridge.JSCJavaScriptExecutor;
import com.facebook.react.cxxbridge.JavaScriptExecutor;
import com.facebook.react.cxxbridge.NativeModuleRegistry;
import com.facebook.react.module.model.ReactModuleInfo;
import com.android.internal.util.Predicate;
public class ReactTestHelper {
private static class DefaultReactTestFactory implements ReactTestFactory {
private static class ReactInstanceEasyBuilderImpl implements ReactInstanceEasyBuilder {
private @Nullable Context mContext;
private final NativeModuleRegistry.Builder mNativeModuleRegistryBuilder =
new NativeModuleRegistry.Builder();
private final List<ModuleSpec> mModuleSpecList = new ArrayList<>();
private final JavaScriptModuleRegistry.Builder mJSModuleRegistryBuilder =
new JavaScriptModuleRegistry.Builder();
private @Nullable Context mContext;
@Override
public ReactInstanceEasyBuilder setContext(Context context) {
mContext = context;
@ -49,8 +56,9 @@ public class ReactTestHelper {
}
@Override
public ReactInstanceEasyBuilder addNativeModule(NativeModule module) {
mNativeModuleRegistryBuilder.add(module);
public ReactInstanceEasyBuilder addNativeModule(NativeModule nativeModule) {
mModuleSpecList.add(
new ModuleSpec(nativeModule.getClass(), new EagerModuleProvider(nativeModule)));
return this;
}
@ -71,7 +79,9 @@ public class ReactTestHelper {
return new CatalystInstanceImpl.Builder()
.setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
.setJSExecutor(executor)
.setRegistry(mNativeModuleRegistryBuilder.build())
.setRegistry(new NativeModuleRegistry(
mModuleSpecList,
Collections.<Class, ReactModuleInfo>emptyMap()))
.setJSModuleRegistry(mJSModuleRegistryBuilder.build())
.setJSBundleLoader(JSBundleLoader.createAssetLoader(
mContext,

View File

@ -0,0 +1,24 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react;
import javax.inject.Provider;
import com.facebook.react.bridge.NativeModule;
/**
* Provider for an already initialized and non-lazy NativeModule.
*/
public class EagerModuleProvider implements Provider<NativeModule> {
private final NativeModule mModule;
public EagerModuleProvider(NativeModule module) {
mModule = module;
}
@Override
public NativeModule get() {
return mModule;
}
}

View File

@ -14,8 +14,10 @@ import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
@ -35,6 +37,7 @@ import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.JavaJSExecutor;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.JavaScriptModuleRegistry;
import com.facebook.react.bridge.ModuleSpec;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
@ -61,6 +64,8 @@ import com.facebook.react.devsupport.DevSupportManager;
import com.facebook.react.devsupport.DevSupportManagerFactory;
import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler;
import com.facebook.react.devsupport.RedBoxHandler;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.debug.DeveloperSettings;
@ -72,6 +77,7 @@ import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper;
import com.facebook.soloader.SoLoader;
import com.facebook.systrace.Systrace;
import com.facebook.systrace.SystraceMessage;
import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_NATIVE_MODULE_REGISTRY_END;
import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_NATIVE_MODULE_REGISTRY_START;
@ -107,6 +113,8 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
*/
/* package */ class XReactInstanceManagerImpl extends ReactInstanceManager {
private static final String TAG = XReactInstanceManagerImpl.class.getSimpleName();
/* should only be accessed from main thread (UI thread) */
private final List<ReactRootView> mAttachedRootViews = new ArrayList<>();
private LifecycleState mLifecycleState;
@ -836,7 +844,8 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
FLog.i(ReactConstants.TAG, "Creating react context.");
ReactMarker.logMarker(CREATE_REACT_CONTEXT_START);
mSourceUrl = jsBundleLoader.getSourceUrl();
NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
List<ModuleSpec> moduleSpecs = new ArrayList<>();
Map<Class, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder();
final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
@ -851,7 +860,12 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
try {
CoreModulesPackage coreModulesPackage =
new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
processPackage(
coreModulesPackage,
reactContext,
moduleSpecs,
reactModuleInfoMap,
jsModulesBuilder);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
@ -862,7 +876,12 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
TRACE_TAG_REACT_JAVA_BRIDGE,
"createAndProcessCustomReactPackage");
try {
processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
processPackage(
reactPackage,
reactContext,
moduleSpecs,
reactModuleInfoMap,
jsModulesBuilder);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
@ -873,7 +892,7 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry");
NativeModuleRegistry nativeModuleRegistry;
try {
nativeModuleRegistry = nativeRegistryBuilder.build();
nativeModuleRegistry = new NativeModuleRegistry(moduleSpecs, reactModuleInfoMap);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
@ -939,14 +958,78 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
private void processPackage(
ReactPackage reactPackage,
ReactApplicationContext reactContext,
NativeModuleRegistry.Builder nativeRegistryBuilder,
List<ModuleSpec> moduleSpecs,
Map<Class, ReactModuleInfo> reactModuleInfoMap,
JavaScriptModuleRegistry.Builder jsModulesBuilder) {
for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) {
nativeRegistryBuilder.add(nativeModule);
SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "processPackage")
.arg("className", reactPackage.getClass().getSimpleName())
.flush();
if (mLazyNativeModulesEnabled && reactPackage instanceof LazyReactPackage) {
LazyReactPackage lazyReactPackage = (LazyReactPackage) reactPackage;
if (addReactModuleInfos(lazyReactPackage, reactContext, moduleSpecs, reactModuleInfoMap)) {
moduleSpecs.addAll(lazyReactPackage.getNativeModules(reactContext));
}
} else {
FLog.d(
ReactConstants.TAG,
reactPackage.getClass().getSimpleName() +
" is not a LazyReactPackage, falling back to old version");
addEagerModuleProviders(reactPackage, reactContext, moduleSpecs);
}
for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()) {
jsModulesBuilder.add(jsModuleClass);
}
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
private boolean addReactModuleInfos(
LazyReactPackage lazyReactPackage,
ReactApplicationContext reactApplicationContext,
List<ModuleSpec> moduleSpecs,
Map<Class, ReactModuleInfo> reactModuleInfoMap) {
Class<?> reactModuleInfoProviderClass = null;
try {
reactModuleInfoProviderClass = Class.forName(
lazyReactPackage.getClass().getCanonicalName() + "$$ReactModuleInfoProvider");
} catch (ClassNotFoundException e) {
FLog.w(
TAG,
"Could not find generated ReactModuleInfoProvider for " + lazyReactPackage.getClass());
// Fallback to non-lazy method.
addEagerModuleProviders(lazyReactPackage, reactApplicationContext, moduleSpecs);
return false;
}
if (reactModuleInfoProviderClass != null) {
ReactModuleInfoProvider instance;
try {
instance = (ReactModuleInfoProvider) reactModuleInfoProviderClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(
"Unable to instantiate ReactModuleInfoProvider for " + lazyReactPackage.getClass(),
e);
} catch (IllegalAccessException e) {
throw new RuntimeException(
"Unable to instantiate ReactModuleInfoProvider for " + lazyReactPackage.getClass(),
e);
}
Map<Class, ReactModuleInfo> map = instance.getReactModuleInfos();
if (!map.isEmpty()) {
reactModuleInfoMap.putAll(map);
}
}
return true;
}
private void addEagerModuleProviders(
ReactPackage reactPackage,
ReactApplicationContext reactApplicationContext,
List<ModuleSpec> moduleSpecs) {
for (NativeModule nativeModule : reactPackage.createNativeModules(reactApplicationContext)) {
moduleSpecs.add(
new ModuleSpec(nativeModule.getClass(), new EagerModuleProvider(nativeModule)));
}
}
private void moveReactContextToCurrentLifecycleState() {

View File

@ -11,9 +11,9 @@ package com.facebook.react.bridge;
import java.util.Collection;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* A higher level API on top of the asynchronous JSC bridge. This provides an

View File

@ -29,8 +29,9 @@ package com.facebook.react.bridge;
public interface LifecycleEventListener {
/**
* Called when host activity receives resume event (e.g. {@link Activity#onResume}. Always called
* for the most current activity.
* Called either when the host activity receives a resume event (e.g. {@link Activity#onResume} or
* if the native module that implements this is initialized while the host activity is already
* resumed. Always called for the most current activity.
*/
void onHostResume();

View File

@ -26,6 +26,11 @@ import android.view.LayoutInflater;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.queue.MessageQueueThread;
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
import com.facebook.react.common.LifecycleState;
import static com.facebook.react.common.LifecycleState.BEFORE_CREATE;
import static com.facebook.react.common.LifecycleState.BEFORE_RESUME;
import static com.facebook.react.common.LifecycleState.RESUMED;
/**
* Abstract ContextWrapper for Android application or activity {@link Context} and
@ -49,6 +54,7 @@ public class ReactContext extends ContextWrapper {
private @Nullable MessageQueueThread mJSMessageQueueThread;
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private @Nullable WeakReference<Activity> mCurrentActivity;
private LifecycleState mLifecycleState = LifecycleState.BEFORE_RESUME;
public ReactContext(Context base) {
super(base);
@ -103,7 +109,9 @@ public class ReactContext extends ContextWrapper {
return mCatalystInstance.getJSModule(jsInterface);
}
public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) {
public <T extends JavaScriptModule> T getJSModule(
ExecutorToken executorToken,
Class<T> jsInterface) {
if (mCatalystInstance == null) {
throw new RuntimeException(EARLY_JS_ACCESS_EXCEPTION_MESSAGE);
}
@ -137,8 +145,25 @@ public class ReactContext extends ContextWrapper {
return mCatalystInstance != null && !mCatalystInstance.isDestroyed();
}
public void addLifecycleEventListener(LifecycleEventListener listener) {
public void addLifecycleEventListener(final LifecycleEventListener listener) {
mLifecycleEventListeners.add(listener);
if (hasActiveCatalystInstance()) {
switch (mLifecycleState) {
case BEFORE_CREATE:
case BEFORE_RESUME:
break;
case RESUMED:
runOnUiQueueThread(new Runnable() {
@Override
public void run() {
listener.onHostResume();
}
});
break;
default:
throw new RuntimeException("Unhandled lifecycle state.");
}
}
}
public void removeLifecycleEventListener(LifecycleEventListener listener) {
@ -173,6 +198,7 @@ public class ReactContext extends ContextWrapper {
public void onHostResume(@Nullable Activity activity) {
UiThreadUtil.assertOnUiThread();
mCurrentActivity = new WeakReference(activity);
mLifecycleState = LifecycleState.RESUMED;
for (LifecycleEventListener listener : mLifecycleEventListeners) {
listener.onHostResume();
}
@ -191,6 +217,7 @@ public class ReactContext extends ContextWrapper {
*/
public void onHostPause() {
UiThreadUtil.assertOnUiThread();
mLifecycleState = LifecycleState.BEFORE_RESUME;
for (LifecycleEventListener listener : mLifecycleEventListeners) {
listener.onHostPause();
}

View File

@ -27,6 +27,7 @@ android_library(
react_native_target('java/com/facebook/react/bridge:bridge'),
react_native_target('java/com/facebook/react/common:common'),
react_native_target('java/com/facebook/react/devsupport:devsupport'),
react_native_target('java/com/facebook/react/module/model:model'),
],
visibility = [
'PUBLIC',

View File

@ -49,23 +49,23 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
}
private final CatalystInstance mCatalystInstance;
private final BaseJavaModule mModule;
private final ModuleHolder mModuleHolder;
private final ArrayList<BaseJavaModule.JavaMethod> mMethods;
public JavaModuleWrapper(CatalystInstance catalystinstance, BaseJavaModule module) {
public JavaModuleWrapper(CatalystInstance catalystinstance, ModuleHolder moduleHolder) {
mCatalystInstance = catalystinstance;
mModule = module;
mMethods = new ArrayList<BaseJavaModule.JavaMethod>();
mModuleHolder = moduleHolder;
mMethods = new ArrayList<>();
}
@DoNotStrip
public BaseJavaModule getModule() {
return mModule;
return (BaseJavaModule) mModuleHolder.getModule();
}
@DoNotStrip
public String getName() {
return mModule.getName();
return mModuleHolder.getInfo().name();
}
@DoNotStrip
@ -73,7 +73,7 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
ArrayList<MethodDescriptor> descs = new ArrayList<>();
for (Map.Entry<String, BaseJavaModule.NativeMethod> entry :
mModule.getMethods().entrySet()) {
getModule().getMethods().entrySet()) {
MethodDescriptor md = new MethodDescriptor();
md.name = entry.getKey();
md.type = entry.getValue().getType();
@ -92,7 +92,7 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
ArrayList<MethodDescriptor> descs = new ArrayList<>();
for (Map.Entry<String, BaseJavaModule.NativeMethod> entry :
mModule.getMethods().entrySet()) {
getModule().getMethods().entrySet()) {
MethodDescriptor md = new MethodDescriptor();
md.name = entry.getKey();
md.type = entry.getValue().getType();
@ -105,7 +105,7 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
}
for (Map.Entry<String, BaseJavaModule.SyncNativeHook> entry :
mModule.getSyncHooks().entrySet()) {
getModule().getSyncHooks().entrySet()) {
MethodDescriptor md = new MethodDescriptor();
md.name = entry.getKey();
md.type = BaseJavaModule.METHOD_TYPE_SYNC;
@ -127,7 +127,7 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "Map constants")
.arg("moduleName", getName())
.flush();
Map<String, Object> map = mModule.getConstants();
Map<String, Object> map = getModule().getConstants();
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "WritableNativeMap constants")
@ -146,7 +146,7 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
@DoNotStrip
public boolean supportsWebWorkers() {
return mModule.supportsWebWorkers();
return getModule().supportsWebWorkers();
}
@DoNotStrip

View File

@ -0,0 +1,159 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.cxxbridge;
import javax.annotation.Nullable;
import javax.inject.Provider;
import java.util.concurrent.ExecutionException;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.common.futures.SimpleSettableFuture;
import com.facebook.react.module.model.Info;
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.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.
*/
public class ModuleHolder {
private final Info mInfo;
private @Nullable Provider<? extends NativeModule> mProvider;
private @Nullable NativeModule mModule;
private boolean mInitializeNeeded;
public ModuleHolder(
Class<? extends NativeModule> clazz,
@Nullable ReactModuleInfo reactModuleInfo,
Provider<? extends NativeModule> provider) {
mInfo = reactModuleInfo == null ? new LegacyModuleInfo(clazz) : reactModuleInfo;
mProvider = provider;
if (mInfo.needsEagerInit()) {
mModule = doCreate();
}
}
public synchronized void initialize() {
if (mModule != null) {
doInitialize(mModule);
} else {
mInitializeNeeded = true;
}
}
public synchronized void destroy() {
if (mModule != null) {
mModule.onCatalystInstanceDestroy();
}
}
public Info getInfo() {
return mInfo;
}
public synchronized NativeModule getModule() {
if (mModule == null) {
mModule = doCreate();
}
return mModule;
}
private NativeModule doCreate() {
NativeModule module = create();
mProvider = null;
return module;
}
private NativeModule create() {
String name = mInfo instanceof LegacyModuleInfo ?
((LegacyModuleInfo) mInfo).mType.getSimpleName() :
mInfo.name();
SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createModule")
.arg("name", name)
.flush();
NativeModule module = assertNotNull(mProvider).get();
if (mInitializeNeeded) {
doInitialize(module);
mInitializeNeeded = false;
}
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
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", mInfo.name());
}
section.flush();
callInitializeOnUiThread(module);
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
// TODO(t11394264): Use the native module thread here after the old bridge is gone
private static void callInitializeOnUiThread(final NativeModule module) {
if (UiThreadUtil.isOnUiThread()) {
module.initialize();
return;
}
final SimpleSettableFuture<Void> future = new SimpleSettableFuture<>();
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "initializeOnUiThread");
try {
module.initialize();
future.set(null);
} catch (Exception e) {
future.setException(e);
}
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
});
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
private class LegacyModuleInfo implements Info {
public final Class<?> mType;
public LegacyModuleInfo(Class<?> type) {
mType = type;
}
public String name() {
return getModule().getName();
}
public boolean canOverrideExistingModule() {
return getModule().canOverrideExistingModule();
}
public boolean supportsWebWorkers() {
return getModule().supportsWebWorkers();
}
public boolean needsEagerInit() {
return true;
}
}
}

View File

@ -10,32 +10,68 @@
package com.facebook.react.cxxbridge;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.util.Pair;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.ModuleSpec;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.OnBatchCompleteListener;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.systrace.Systrace;
/**
* A set of Java APIs to expose to a particular JavaScript instance.
*/
public class NativeModuleRegistry {
private final Map<Class<NativeModule>, NativeModule> mModuleInstances;
private final Map<Class<? extends NativeModule>, ModuleHolder> mModules;
private final ArrayList<OnBatchCompleteListener> mBatchCompleteListenerModules;
private NativeModuleRegistry(Map<Class<NativeModule>, NativeModule> moduleInstances) {
mModuleInstances = moduleInstances;
mBatchCompleteListenerModules = new ArrayList<OnBatchCompleteListener>(mModuleInstances.size());
for (NativeModule module : mModuleInstances.values()) {
if (module instanceof OnBatchCompleteListener) {
mBatchCompleteListenerModules.add((OnBatchCompleteListener) module);
public NativeModuleRegistry(
List<ModuleSpec> moduleSpecList,
Map<Class, ReactModuleInfo> reactModuleInfoMap) {
Map<String, Pair<Class<? extends NativeModule>, ModuleHolder>> namesToSpecs = new HashMap<>();
for (ModuleSpec module : moduleSpecList) {
Class<? extends NativeModule> type = module.getType();
ModuleHolder holder = new ModuleHolder(
type,
reactModuleInfoMap.get(type),
module.getProvider());
String name = holder.getInfo().name();
Class<? extends NativeModule> existing = namesToSpecs.containsKey(name) ?
namesToSpecs.get(name).first :
null;
if (existing != null && !holder.getInfo().canOverrideExistingModule()) {
throw new IllegalStateException("Native module " + type.getSimpleName() +
" tried to override " + existing.getSimpleName() + " for module name " + name +
". If this was your intention, set canOverrideExistingModule=true");
}
namesToSpecs.put(name, new Pair<Class<? extends NativeModule>, ModuleHolder>(type, holder));
}
mModules = new HashMap<>();
for (Pair<Class<? extends NativeModule>, ModuleHolder> pair : namesToSpecs.values()) {
mModules.put(pair.first, pair.second);
}
mBatchCompleteListenerModules = new ArrayList<>();
for (Class<? extends NativeModule> type : mModules.keySet()) {
if (OnBatchCompleteListener.class.isAssignableFrom(type)) {
final ModuleHolder holder = mModules.get(type);
mBatchCompleteListenerModules.add(new OnBatchCompleteListener() {
@Override
public void onBatchComplete() {
OnBatchCompleteListener listener = (OnBatchCompleteListener) holder.getModule();
listener.onBatchComplete();
}
});
}
}
}
@ -44,13 +80,15 @@ public class NativeModuleRegistry {
CatalystInstanceImpl catalystInstanceImpl) {
ArrayList<JavaModuleWrapper> javaModules = new ArrayList<>();
ArrayList<CxxModuleWrapper> cxxModules = new ArrayList<>();
for (NativeModule module : mModuleInstances.values()) {
if (module instanceof BaseJavaModule) {
javaModules.add(new JavaModuleWrapper(catalystInstanceImpl, (BaseJavaModule) module));
} else if (module instanceof CxxModuleWrapper) {
cxxModules.add((CxxModuleWrapper) module);
for (Map.Entry<Class<? extends NativeModule>, ModuleHolder> entry : mModules.entrySet()) {
Class<?> type = entry.getKey();
ModuleHolder moduleHolder = entry.getValue();
if (BaseJavaModule.class.isAssignableFrom(type)) {
javaModules.add(new JavaModuleWrapper(catalystInstanceImpl, moduleHolder));
} else if (CxxModuleWrapper.class.isAssignableFrom(type)) {
cxxModules.add((CxxModuleWrapper) moduleHolder.getModule());
} else {
throw new IllegalArgumentException("Unknown module type " + module.getClass());
throw new IllegalArgumentException("Unknown module type " + type);
}
}
return new ModuleRegistryHolder(catalystInstanceImpl, javaModules, cxxModules);
@ -62,8 +100,8 @@ public class NativeModuleRegistry {
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"NativeModuleRegistry_notifyCatalystInstanceDestroy");
try {
for (NativeModule nativeModule : mModuleInstances.values()) {
nativeModule.onCatalystInstanceDestroy();
for (ModuleHolder module : mModules.values()) {
module.destroy();
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
@ -78,8 +116,8 @@ public class NativeModuleRegistry {
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"NativeModuleRegistry_notifyCatalystInstanceInitialized");
try {
for (NativeModule nativeModule : mModuleInstances.values()) {
nativeModule.initialize();
for (ModuleHolder module : mModules.values()) {
module.initialize();
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
@ -94,38 +132,18 @@ public class NativeModuleRegistry {
}
public <T extends NativeModule> boolean hasModule(Class<T> moduleInterface) {
return mModuleInstances.containsKey(moduleInterface);
return mModules.containsKey(moduleInterface);
}
public <T extends NativeModule> T getModule(Class<T> moduleInterface) {
return (T) Assertions.assertNotNull(mModuleInstances.get(moduleInterface));
return (T) Assertions.assertNotNull(mModules.get(moduleInterface)).getModule();
}
public Collection<NativeModule> getAllModules() {
return mModuleInstances.values();
}
public static class Builder {
private final HashMap<String, NativeModule> mModules = MapBuilder.newHashMap();
public Builder add(NativeModule module) {
NativeModule existing = mModules.get(module.getName());
if (existing != null && !module.canOverrideExistingModule()) {
throw new IllegalStateException("Native module " + module.getClass().getSimpleName() +
" tried to override " + existing.getClass().getSimpleName() + " for module name " +
module.getName() + ". If this was your intention, return true from " +
module.getClass().getSimpleName() + "#canOverrideExistingModule()");
}
mModules.put(module.getName(), module);
return this;
}
public NativeModuleRegistry build() {
Map<Class<NativeModule>, NativeModule> moduleInstances = new HashMap<>();
for (NativeModule module : mModules.values()) {
moduleInstances.put((Class<NativeModule>) module.getClass(), module);
}
return new NativeModuleRegistry(moduleInstances);
public List<NativeModule> getAllModules() {
List<NativeModule> modules = new ArrayList<>();
for (ModuleHolder module : mModules.values()) {
modules.add(module.getModule());
}
return modules;
}
}

View File

@ -0,0 +1,17 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.module.model;
/**
* Interface for static information about native modules.
*/
public interface Info {
String name();
boolean canOverrideExistingModule();
boolean supportsWebWorkers();
boolean needsEagerInit();
}

View File

@ -3,14 +3,15 @@
package com.facebook.react.module.model;
/**
* Data holder class holding native module specifications.
* Data holder class holding native module specifications. {@link ReactModuleSpecProcessor} creates
* these so Java modules don't have to be instantiated at React Native start up.
*/
public class ReactModuleInfo {
public class ReactModuleInfo implements Info {
public final String mName;
public final boolean mCanOverrideExistingModule;
public final boolean mSupportsWebWorkers;
public final boolean mNeedsEagerInit;
private final String mName;
private final boolean mCanOverrideExistingModule;
private final boolean mSupportsWebWorkers;
private final boolean mNeedsEagerInit;
public ReactModuleInfo(
String name,
@ -22,4 +23,24 @@ public class ReactModuleInfo {
mSupportsWebWorkers = supportsWebWorkers;
mNeedsEagerInit = needsEagerInit;
}
@Override
public String name() {
return mName;
}
@Override
public boolean canOverrideExistingModule() {
return mCanOverrideExistingModule;
}
@Override
public boolean supportsWebWorkers() {
return mSupportsWebWorkers;
}
@Override
public boolean needsEagerInit() {
return mNeedsEagerInit;
}
}

View File

@ -0,0 +1,13 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.module.model;
import java.util.Map;
/**
* Interface for auto-generated class by ReactModuleSpecProcessor.
*/
public interface ReactModuleInfoProvider {
Map<Class, ReactModuleInfo> getReactModuleInfos();
}

View File

@ -28,6 +28,7 @@ import com.facebook.infer.annotation.SuppressFieldNotInitialized;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.module.annotations.ReactModuleList;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
@ -97,9 +98,8 @@ public class ReactModuleSpecProcessor extends AbstractProcessor {
MethodSpec getReactModuleInfosMethod;
try {
getReactModuleInfosMethod = MethodSpec.methodBuilder("getReactModuleInfos")
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
// TODO add function to native module interface
// .addAnnotation(Override.class)
.addCode(getCodeBlockForReactModuleInfos(nativeModules))
.returns(MAP_TYPE)
.build();
@ -112,6 +112,7 @@ public class ReactModuleSpecProcessor extends AbstractProcessor {
fileName + "$$ReactModuleInfoProvider")
.addModifiers(Modifier.PUBLIC)
.addMethod(getReactModuleInfosMethod)
.addSuperinterface(ReactModuleInfoProvider.class)
.build();
JavaFile javaFile = JavaFile.builder(packageName, reactModulesInfosTypeSpec)
@ -130,8 +131,11 @@ public class ReactModuleSpecProcessor extends AbstractProcessor {
private CodeBlock getCodeBlockForReactModuleInfos(List<String> nativeModules)
throws ReactModuleSpecException {
CodeBlock.Builder builder = CodeBlock.builder()
.addStatement("$T map = new $T()", MAP_TYPE, INSTANTIATED_MAP_TYPE);
CodeBlock.Builder builder = CodeBlock.builder();
if (nativeModules == null || nativeModules.isEmpty()) {
builder.addStatement("return Collections.emptyMap()");
} else {
builder.addStatement("$T map = new $T()", MAP_TYPE, INSTANTIATED_MAP_TYPE);
for (String nativeModule : nativeModules) {
String keyString = nativeModule + ".class";
@ -139,7 +143,8 @@ public class ReactModuleSpecProcessor extends AbstractProcessor {
TypeElement typeElement = mElements.getTypeElement(nativeModule);
ReactModule reactModule = typeElement.getAnnotation(ReactModule.class);
if (reactModule == null) {
throw new ReactModuleSpecException(keyString + " not found by ReactModuleSpecProcessor. " +
throw new ReactModuleSpecException(
keyString + " not found by ReactModuleSpecProcessor. " +
"Did you forget to add the @ReactModule annotation the the native module?");
}
String valueString = new StringBuilder()
@ -154,6 +159,7 @@ public class ReactModuleSpecProcessor extends AbstractProcessor {
builder.addStatement("map.put(" + keyString + ", " + valueString + ")");
}
builder.addStatement("return map");
}
return builder.build();
}