From 369b28ce014f76b4b47c4cf7632ac66f39a53fa2 Mon Sep 17 00:00:00 2001 From: Kudo Chien Date: Wed, 22 Sep 2021 10:31:50 -0700 Subject: [PATCH] Support custom DevSupportManager (#31841) Summary: to open possibilities for some DX enhancement, the pr introduces `DevSupportManagerFactory` customization. applications could implement a different DevSupportManager in ReactNativeHost. ## Changelog [Internal] [Added] - Support custom DevSupportManager Pull Request resolved: https://github.com/facebook/react-native/pull/31841 Test Plan: this pr just introduces some new interfaces and should not break existing functionalities. Reviewed By: RSNara Differential Revision: D30878134 Pulled By: yungsters fbshipit-source-id: ccdf798caa322b07a876da8312b97002da057388 --- .../facebook/react/ReactInstanceManager.java | 6 +- .../react/ReactInstanceManagerBuilder.java | 16 +++ .../com/facebook/react/ReactNativeHost.java | 7 ++ .../DefaultDevSupportManagerFactory.java | 97 +++++++++++++++++++ .../devsupport/DevSupportManagerFactory.java | 75 +------------- 5 files changed, 127 insertions(+), 74 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevSupportManagerFactory.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 72f0e379654..5ca75bdab9c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -219,6 +219,7 @@ public class ReactInstanceManager { @Nullable String jsMainModulePath, List packages, boolean useDeveloperSupport, + DevSupportManagerFactory devSupportManagerFactory, boolean requireActivity, @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener, LifecycleState initialLifecycleState, @@ -250,7 +251,7 @@ public class ReactInstanceManager { Systrace.beginSection( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactInstanceManager.initDevSupportManager"); mDevSupportManager = - DevSupportManagerFactory.create( + devSupportManagerFactory.create( applicationContext, createDevHelperInterface(), mJSMainModulePath, @@ -1225,7 +1226,8 @@ public class ReactInstanceManager { // If we can't get a UIManager something has probably gone horribly wrong if (uiManager == null) { throw new IllegalStateException( - "Unable to attach a rootView to ReactInstance when UIManager is not properly initialized."); + "Unable to attach a rootView to ReactInstance when UIManager is not properly" + + " initialized."); } @Nullable Bundle initialProperties = reactRoot.getAppProperties(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java index 36f700e4976..0064ca021c8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java @@ -22,6 +22,8 @@ import com.facebook.react.bridge.JavaScriptExecutorFactory; import com.facebook.react.bridge.NativeModuleCallExceptionHandler; import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; import com.facebook.react.common.LifecycleState; +import com.facebook.react.devsupport.DefaultDevSupportManagerFactory; +import com.facebook.react.devsupport.DevSupportManagerFactory; import com.facebook.react.devsupport.RedBoxHandler; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import com.facebook.react.devsupport.interfaces.DevSupportManager; @@ -45,6 +47,7 @@ public class ReactInstanceManagerBuilder { private @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener; private @Nullable Application mApplication; private boolean mUseDeveloperSupport; + private @Nullable DevSupportManagerFactory mDevSupportManagerFactory; private boolean mRequireActivity; private @Nullable LifecycleState mInitialLifecycleState; private @Nullable UIImplementationProvider mUIImplementationProvider; @@ -172,6 +175,16 @@ public class ReactInstanceManagerBuilder { return this; } + /** + * Set the custom {@link DevSupportManagerFactory}. If not set, will use {@link + * DefaultDevSupportManagerFactory}. + */ + public ReactInstanceManagerBuilder setDevSupportManagerFactory( + final DevSupportManagerFactory devSupportManagerFactory) { + mDevSupportManagerFactory = devSupportManagerFactory; + return this; + } + /** * When {@code false}, indicates that correct usage of React Native will NOT involve an Activity. * For the vast majority of Android apps in the ecosystem, this will not need to change. Unless @@ -294,6 +307,9 @@ public class ReactInstanceManagerBuilder { mJSMainModulePath, mPackages, mUseDeveloperSupport, + mDevSupportManagerFactory == null + ? new DefaultDevSupportManagerFactory() + : mDevSupportManagerFactory, mRequireActivity, mBridgeIdleDebugListener, Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java index dc313d33bc0..68b1f3e367f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java @@ -15,6 +15,7 @@ import com.facebook.react.bridge.JavaScriptExecutorFactory; import com.facebook.react.bridge.ReactMarker; import com.facebook.react.bridge.ReactMarkerConstants; import com.facebook.react.common.LifecycleState; +import com.facebook.react.devsupport.DevSupportManagerFactory; import com.facebook.react.devsupport.RedBoxHandler; import com.facebook.react.uimanager.UIImplementationProvider; import java.util.List; @@ -68,6 +69,7 @@ public abstract class ReactNativeHost { .setApplication(mApplication) .setJSMainModulePath(getJSMainModuleName()) .setUseDeveloperSupport(getUseDeveloperSupport()) + .setDevSupportManagerFactory(getDevSupportManagerFactory()) .setRequireActivity(getShouldRequireActivity()) .setRedBoxHandler(getRedBoxHandler()) .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory()) @@ -160,6 +162,11 @@ public abstract class ReactNativeHost { /** Returns whether dev mode should be enabled. This enables e.g. the dev menu. */ public abstract boolean getUseDeveloperSupport(); + /** Get the {@link DevSupportManagerFactory}. Override this to use a custom dev support manager */ + protected @Nullable DevSupportManagerFactory getDevSupportManagerFactory() { + return null; + } + /** * Returns a list of {@link ReactPackage} used by the app. You'll most likely want to return at * least the {@code MainReactPackage}. If your app uses additional views or modules besides the diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevSupportManagerFactory.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevSupportManagerFactory.java new file mode 100644 index 00000000000..3b0233ace20 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevSupportManagerFactory.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.devsupport; + +import android.content.Context; +import androidx.annotation.Nullable; +import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; +import com.facebook.react.devsupport.interfaces.DevSupportManager; +import com.facebook.react.packagerconnection.RequestHandler; +import java.lang.reflect.Constructor; +import java.util.Map; + +/** + * A simple factory that creates instances of {@link DevSupportManager} implementations. Uses + * reflection to create BridgeDevSupportManager if it exists. This allows ProGuard to strip that + * class and its dependencies in release builds. If the class isn't found, {@link + * DisabledDevSupportManager} is returned instead. + */ +public class DefaultDevSupportManagerFactory implements DevSupportManagerFactory { + + private static final String DEVSUPPORT_IMPL_PACKAGE = "com.facebook.react.devsupport"; + private static final String DEVSUPPORT_IMPL_CLASS = "BridgeDevSupportManager"; + + public DevSupportManager create( + Context applicationContext, + ReactInstanceDevHelper reactInstanceDevHelper, + @Nullable String packagerPathForJSBundleName, + boolean enableOnCreate, + int minNumShakes) { + + return create( + applicationContext, + reactInstanceDevHelper, + packagerPathForJSBundleName, + enableOnCreate, + null, + null, + minNumShakes, + null); + } + + @Override + public DevSupportManager create( + Context applicationContext, + ReactInstanceDevHelper reactInstanceManagerHelper, + @Nullable String packagerPathForJSBundleName, + boolean enableOnCreate, + @Nullable RedBoxHandler redBoxHandler, + @Nullable DevBundleDownloadListener devBundleDownloadListener, + int minNumShakes, + @Nullable Map customPackagerCommandHandlers) { + if (!enableOnCreate) { + return new DisabledDevSupportManager(); + } + try { + // ProGuard is surprisingly smart in this case and will keep a class if it detects a call to + // Class.forName() with a static string. So instead we generate a quasi-dynamic string to + // confuse it. + String className = + new StringBuilder(DEVSUPPORT_IMPL_PACKAGE) + .append(".") + .append(DEVSUPPORT_IMPL_CLASS) + .toString(); + Class devSupportManagerClass = Class.forName(className); + Constructor constructor = + devSupportManagerClass.getConstructor( + Context.class, + ReactInstanceDevHelper.class, + String.class, + boolean.class, + RedBoxHandler.class, + DevBundleDownloadListener.class, + int.class, + Map.class); + return (DevSupportManager) + constructor.newInstance( + applicationContext, + reactInstanceManagerHelper, + packagerPathForJSBundleName, + true, + redBoxHandler, + devBundleDownloadListener, + minNumShakes, + customPackagerCommandHandlers); + } catch (Exception e) { + throw new RuntimeException( + "Requested enabled DevSupportManager, but BridgeDevSupportManager class was not found" + + " or could not be created", + e); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java index 24ef6b3d3bb..e279b6b047e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java @@ -12,39 +12,10 @@ import androidx.annotation.Nullable; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.packagerconnection.RequestHandler; -import java.lang.reflect.Constructor; import java.util.Map; -/** - * A simple factory that creates instances of {@link DevSupportManager} implementations. Uses - * reflection to create BridgeDevSupportManager if it exists. This allows ProGuard to strip that - * class and its dependencies in release builds. If the class isn't found, {@link - * DisabledDevSupportManager} is returned instead. - */ -public class DevSupportManagerFactory { - - private static final String DEVSUPPORT_IMPL_PACKAGE = "com.facebook.react.devsupport"; - private static final String DEVSUPPORT_IMPL_CLASS = "BridgeDevSupportManager"; - - public static DevSupportManager create( - Context applicationContext, - ReactInstanceDevHelper reactInstanceDevHelper, - @Nullable String packagerPathForJSBundleName, - boolean enableOnCreate, - int minNumShakes) { - - return create( - applicationContext, - reactInstanceDevHelper, - packagerPathForJSBundleName, - enableOnCreate, - null, - null, - minNumShakes, - null); - } - - public static DevSupportManager create( +public interface DevSupportManagerFactory { + DevSupportManager create( Context applicationContext, ReactInstanceDevHelper reactInstanceManagerHelper, @Nullable String packagerPathForJSBundleName, @@ -52,45 +23,5 @@ public class DevSupportManagerFactory { @Nullable RedBoxHandler redBoxHandler, @Nullable DevBundleDownloadListener devBundleDownloadListener, int minNumShakes, - @Nullable Map customPackagerCommandHandlers) { - if (!enableOnCreate) { - return new DisabledDevSupportManager(); - } - try { - // ProGuard is surprisingly smart in this case and will keep a class if it detects a call to - // Class.forName() with a static string. So instead we generate a quasi-dynamic string to - // confuse it. - String className = - new StringBuilder(DEVSUPPORT_IMPL_PACKAGE) - .append(".") - .append(DEVSUPPORT_IMPL_CLASS) - .toString(); - Class devSupportManagerClass = Class.forName(className); - Constructor constructor = - devSupportManagerClass.getConstructor( - Context.class, - ReactInstanceDevHelper.class, - String.class, - boolean.class, - RedBoxHandler.class, - DevBundleDownloadListener.class, - int.class, - Map.class); - return (DevSupportManager) - constructor.newInstance( - applicationContext, - reactInstanceManagerHelper, - packagerPathForJSBundleName, - true, - redBoxHandler, - devBundleDownloadListener, - minNumShakes, - customPackagerCommandHandlers); - } catch (Exception e) { - throw new RuntimeException( - "Requested enabled DevSupportManager, but BridgeDevSupportManager class was not found" - + " or could not be created", - e); - } - } + @Nullable Map customPackagerCommandHandlers); }