Permalink
Browse files

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
  • Loading branch information...
1 parent 1f9b765 commit 797ca6c219b2a44f88f10c61d91e8cc21e2f306e @AaaChiuuu AaaChiuuu committed with Facebook Github Bot 3 Sep 23, 2016
@@ -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'),
@@ -22,17 +22,16 @@
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;
@@ -10,6 +10,9 @@
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,39 +21,44 @@
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;
return this;
}
@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 CatalystInstance build() {
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,
@@ -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;
+ }
+}
@@ -14,8 +14,10 @@
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.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.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.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 @@
*/
/* 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 @@ private ReactApplicationContext createReactContext(
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 @@ private ReactApplicationContext createReactContext(
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 @@ private ReactApplicationContext createReactContext(
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 @@ private ReactApplicationContext createReactContext(
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 @@ public Void call() throws Exception {
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() {
@@ -11,9 +11,9 @@
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
@@ -29,8 +29,9 @@
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();
@@ -26,6 +26,11 @@
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 @@
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 Object getSystemService(String name) {
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 boolean hasActiveCatalystInstance() {
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();
@silhouettes
silhouettes Nov 12, 2016

I'm not sure I agree with this change, because it seems inconsistent with how the onResume() method of Android Activities work.

But even if we want to do it this way, isn't it inconsistent that onHostPause() is not also called immediately when added while the app's state is BEFORE_RESUME? Note also that without this addition, it seems like there's no way for a consumer to detect specifically the first time an app transitions from BEFORE_RESUME to RESUMED, as opposed to the first time an app is in the RESUMED state.

+ }
+ });
+ break;
+ default:
+ throw new RuntimeException("Unhandled lifecycle state.");
+ }
+ }
}
public void removeLifecycleEventListener(LifecycleEventListener listener) {
@@ -173,6 +198,7 @@ public void removeActivityEventListener(ActivityEventListener listener) {
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 void onNewIntent(@Nullable Activity activity, Intent intent) {
*/
public void onHostPause() {
UiThreadUtil.assertOnUiThread();
+ mLifecycleState = LifecycleState.BEFORE_RESUME;
for (LifecycleEventListener listener : mLifecycleEventListeners) {
listener.onHostPause();
}
Oops, something went wrong.

0 comments on commit 797ca6c

Please sign in to comment.