Intercepting all redboxes in Android Ads Manager

Summary:
Implement a handler to allow intercepting all RN redboxes in Android, including exceptions in both JS and Java.

The handler is not open sourced, so there is only an open-source interface called **RedBoxHandler** in //fbandroid/java/com/facebook/catalyst/js/react-native-github/ReactAndroid/src/main/java/com/facebook/react/devsupport//, meantime there is an internal class called **FBRedBoxHandler**, which implements **RedBoxHandler** and is located in //fbandroid/java/com/facebook/fbreact/redboxhandler//, actually handles the exception information.

The code structure is as follows:
  - **AdsManagerActivity** has a member variable of **FBRedBoxHandler**.
  - **AdsManagerActivity** passes this handler all the way down to the **DevSupportManagerImpl**, through** ReactInstanceManager**, **ReactInstanceManagerImpl**, **DevSupportManagerFactory**.
  - **DevSupportManagerImpl** intercepts the exceptions just before showing the redboxes, like this:

              mRedBoxDialog.setExceptionDetails(message, stack);
              mRedBoxDialog.setErrorCookie(errorCookie);
              if (mRedBoxHandler != null) {
                mRedBoxHandler.handleRedbox(message, stack);
              }
              mRedBoxDialog.show();

By now, the internal class just prints information for each redbox to logcat, including exception message and stack trace.

Reviewed By: mkonicek

Differential Revision: D3369064

fbshipit-source-id: 199012c4b6ecf4b3d3aff51a26c9c9901847b6fc
This commit is contained in:
Siqi Liu 2016-06-03 17:11:51 -07:00 committed by Facebook Github Bot 5
parent 5961764668
commit 5aa0e098b4
8 changed files with 177 additions and 34 deletions

View File

@ -25,6 +25,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContext;
import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.devsupport.DevSupportManager; import com.facebook.react.devsupport.DevSupportManager;
import com.facebook.react.devsupport.RedBoxHandler;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.uimanager.UIImplementationProvider; import com.facebook.react.uimanager.UIImplementationProvider;
import com.facebook.react.uimanager.ViewManager; import com.facebook.react.uimanager.ViewManager;
@ -188,6 +189,7 @@ public abstract class ReactInstanceManager {
protected @Nullable JSCConfig mJSCConfig; protected @Nullable JSCConfig mJSCConfig;
protected @Nullable Activity mCurrentActivity; protected @Nullable Activity mCurrentActivity;
protected @Nullable DefaultHardwareBackBtnHandler mDefaultHardwareBackBtnHandler; protected @Nullable DefaultHardwareBackBtnHandler mDefaultHardwareBackBtnHandler;
protected @Nullable RedBoxHandler mRedBoxHandler;
protected Builder() { protected Builder() {
} }
@ -297,6 +299,11 @@ public abstract class ReactInstanceManager {
return this; return this;
} }
public Builder setRedBoxHandler(RedBoxHandler redBoxHandler) {
mRedBoxHandler = redBoxHandler;
return this;
}
/** /**
* Instantiates a new {@link ReactInstanceManagerImpl}. * Instantiates a new {@link ReactInstanceManagerImpl}.
* Before calling {@code build}, the following must be called: * Before calling {@code build}, the following must be called:
@ -335,7 +342,8 @@ public abstract class ReactInstanceManager {
Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"), Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
mUIImplementationProvider, mUIImplementationProvider,
mNativeModuleCallExceptionHandler, mNativeModuleCallExceptionHandler,
mJSCConfig); mJSCConfig,
mRedBoxHandler);
} }
} }
} }

View File

@ -57,6 +57,7 @@ import com.facebook.react.devsupport.DevServerHelper;
import com.facebook.react.devsupport.DevSupportManager; import com.facebook.react.devsupport.DevSupportManager;
import com.facebook.react.devsupport.DevSupportManagerFactory; import com.facebook.react.devsupport.DevSupportManagerFactory;
import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler; import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler;
import com.facebook.react.devsupport.RedBoxHandler;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.AppRegistry; import com.facebook.react.uimanager.AppRegistry;
@ -122,6 +123,7 @@ import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_START
private final MemoryPressureRouter mMemoryPressureRouter; private final MemoryPressureRouter mMemoryPressureRouter;
private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private final @Nullable JSCConfig mJSCConfig; private final @Nullable JSCConfig mJSCConfig;
private @Nullable RedBoxHandler mRedBoxHandler;
private final ReactInstanceDevCommandsHandler mDevInterface = private final ReactInstanceDevCommandsHandler mDevInterface =
new ReactInstanceDevCommandsHandler() { new ReactInstanceDevCommandsHandler() {
@ -262,6 +264,35 @@ import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_START
} }
} }
/* package */ ReactInstanceManagerImpl(
Context applicationContext,
@Nullable Activity currentActivity,
@Nullable DefaultHardwareBackBtnHandler defaultHardwareBackBtnHandler,
@Nullable String jsBundleFile,
@Nullable String jsMainModuleName,
List<ReactPackage> packages,
boolean useDeveloperSupport,
@Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
LifecycleState initialLifecycleState,
UIImplementationProvider uiImplementationProvider,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler,
@Nullable JSCConfig jscConfig) {
this(applicationContext,
currentActivity,
defaultHardwareBackBtnHandler,
jsBundleFile,
jsMainModuleName,
packages,
useDeveloperSupport,
bridgeIdleDebugListener,
initialLifecycleState,
uiImplementationProvider,
nativeModuleCallExceptionHandler,
jscConfig,
null);
}
/* package */ ReactInstanceManagerImpl( /* package */ ReactInstanceManagerImpl(
Context applicationContext, Context applicationContext,
@Nullable Activity currentActivity, @Nullable Activity currentActivity,
@ -274,7 +305,8 @@ import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_START
LifecycleState initialLifecycleState, LifecycleState initialLifecycleState,
UIImplementationProvider uiImplementationProvider, UIImplementationProvider uiImplementationProvider,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler, NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler,
@Nullable JSCConfig jscConfig) { @Nullable JSCConfig jscConfig,
@Nullable RedBoxHandler redBoxHandler) {
initializeSoLoaderIfNecessary(applicationContext); initializeSoLoaderIfNecessary(applicationContext);
// TODO(9577825): remove this // TODO(9577825): remove this
@ -288,11 +320,13 @@ import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_START
mJSMainModuleName = jsMainModuleName; mJSMainModuleName = jsMainModuleName;
mPackages = packages; mPackages = packages;
mUseDeveloperSupport = useDeveloperSupport; mUseDeveloperSupport = useDeveloperSupport;
mRedBoxHandler = redBoxHandler;
mDevSupportManager = DevSupportManagerFactory.create( mDevSupportManager = DevSupportManagerFactory.create(
applicationContext, applicationContext,
mDevInterface, mDevInterface,
mJSMainModuleName, mJSMainModuleName,
useDeveloperSupport); useDeveloperSupport,
mRedBoxHandler);
mBridgeIdleDebugListener = bridgeIdleDebugListener; mBridgeIdleDebugListener = bridgeIdleDebugListener;
mLifecycleState = initialLifecycleState; mLifecycleState = initialLifecycleState;
mUIImplementationProvider = uiImplementationProvider; mUIImplementationProvider = uiImplementationProvider;

View File

@ -62,7 +62,8 @@ public abstract class XReactInstanceManager {
Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"), Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
mUIImplementationProvider, mUIImplementationProvider,
mNativeModuleCallExceptionHandler, mNativeModuleCallExceptionHandler,
mJSCConfig); mJSCConfig,
mRedBoxHandler);
} }
} }
} }

View File

@ -67,6 +67,7 @@ import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager; import com.facebook.react.uimanager.ViewManager;
import com.facebook.soloader.SoLoader; import com.facebook.soloader.SoLoader;
import com.facebook.systrace.Systrace; import com.facebook.systrace.Systrace;
import com.facebook.react.devsupport.RedBoxHandler;
import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_JS_MODULE_CONFIG_END; import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_JS_MODULE_CONFIG_END;
import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_JS_MODULE_CONFIG_START; import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_JS_MODULE_CONFIG_START;
@ -125,6 +126,7 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
private final MemoryPressureRouter mMemoryPressureRouter; private final MemoryPressureRouter mMemoryPressureRouter;
private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private final @Nullable JSCConfig mJSCConfig; private final @Nullable JSCConfig mJSCConfig;
private @Nullable RedBoxHandler mRedBoxHandler;
private final ReactInstanceDevCommandsHandler mDevInterface = private final ReactInstanceDevCommandsHandler mDevInterface =
new ReactInstanceDevCommandsHandler() { new ReactInstanceDevCommandsHandler() {
@ -276,6 +278,37 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
UIImplementationProvider uiImplementationProvider, UIImplementationProvider uiImplementationProvider,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler, NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler,
@Nullable JSCConfig jscConfig) { @Nullable JSCConfig jscConfig) {
this(applicationContext,
currentActivity,
defaultHardwareBackBtnHandler,
jsBundleFile,
jsMainModuleName,
packages,
useDeveloperSupport,
bridgeIdleDebugListener,
initialLifecycleState,
uiImplementationProvider,
nativeModuleCallExceptionHandler,
jscConfig,
null);
}
/* package */ XReactInstanceManagerImpl(
Context applicationContext,
@Nullable Activity currentActivity,
@Nullable DefaultHardwareBackBtnHandler defaultHardwareBackBtnHandler,
@Nullable String jsBundleFile,
@Nullable String jsMainModuleName,
List<ReactPackage> packages,
boolean useDeveloperSupport,
@Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
LifecycleState initialLifecycleState,
UIImplementationProvider uiImplementationProvider,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler,
@Nullable JSCConfig jscConfig,
@Nullable RedBoxHandler redBoxHandler) {
initializeSoLoaderIfNecessary(applicationContext); initializeSoLoaderIfNecessary(applicationContext);
// TODO(9577825): remove this // TODO(9577825): remove this
@ -289,11 +322,13 @@ import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
mJSMainModuleName = jsMainModuleName; mJSMainModuleName = jsMainModuleName;
mPackages = packages; mPackages = packages;
mUseDeveloperSupport = useDeveloperSupport; mUseDeveloperSupport = useDeveloperSupport;
mRedBoxHandler = redBoxHandler;
mDevSupportManager = DevSupportManagerFactory.create( mDevSupportManager = DevSupportManagerFactory.create(
applicationContext, applicationContext,
mDevInterface, mDevInterface,
mJSMainModuleName, mJSMainModuleName,
useDeveloperSupport); useDeveloperSupport,
mRedBoxHandler);
mBridgeIdleDebugListener = bridgeIdleDebugListener; mBridgeIdleDebugListener = bridgeIdleDebugListener;
mLifecycleState = initialLifecycleState; mLifecycleState = initialLifecycleState;
mUIImplementationProvider = uiImplementationProvider; mUIImplementationProvider = uiImplementationProvider;

View File

@ -1,3 +1,12 @@
/**
* 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.devsupport; package com.facebook.react.devsupport;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -5,10 +14,6 @@ import javax.annotation.Nullable;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import android.content.Context; import android.content.Context;
import android.util.Log;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.build.ReactBuildConfig;
/** /**
* A simple factory that creates instances of {@link DevSupportManager} implementations. Uses * A simple factory that creates instances of {@link DevSupportManager} implementations. Uses
@ -26,6 +31,21 @@ public class DevSupportManagerFactory {
ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, ReactInstanceDevCommandsHandler reactInstanceCommandsHandler,
@Nullable String packagerPathForJSBundleName, @Nullable String packagerPathForJSBundleName,
boolean enableOnCreate) { boolean enableOnCreate) {
return create(
applicationContext,
reactInstanceCommandsHandler,
packagerPathForJSBundleName,
enableOnCreate,
null);
}
public static DevSupportManager create(
Context applicationContext,
ReactInstanceDevCommandsHandler reactInstanceCommandsHandler,
@Nullable String packagerPathForJSBundleName,
boolean enableOnCreate,
@Nullable RedBoxHandler redBoxHandler) {
if (!enableOnCreate) { if (!enableOnCreate) {
return new DisabledDevSupportManager(); return new DisabledDevSupportManager();
} }
@ -34,28 +54,30 @@ public class DevSupportManagerFactory {
// Class.forName() with a static string. So instead we generate a quasi-dynamic string to // Class.forName() with a static string. So instead we generate a quasi-dynamic string to
// confuse it. // confuse it.
String className = String className =
new StringBuilder(DEVSUPPORT_IMPL_PACKAGE) new StringBuilder(DEVSUPPORT_IMPL_PACKAGE)
.append(".") .append(".")
.append(DEVSUPPORT_IMPL_CLASS) .append(DEVSUPPORT_IMPL_CLASS)
.toString(); .toString();
Class<?> devSupportManagerClass = Class<?> devSupportManagerClass =
Class.forName(className); Class.forName(className);
Constructor constructor = Constructor constructor =
devSupportManagerClass.getConstructor( devSupportManagerClass.getConstructor(
Context.class, Context.class,
ReactInstanceDevCommandsHandler.class, ReactInstanceDevCommandsHandler.class,
String.class, String.class,
boolean.class); boolean.class,
RedBoxHandler.class);
return (DevSupportManager) constructor.newInstance( return (DevSupportManager) constructor.newInstance(
applicationContext, applicationContext,
reactInstanceCommandsHandler, reactInstanceCommandsHandler,
packagerPathForJSBundleName, packagerPathForJSBundleName,
true); true,
redBoxHandler);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException( throw new RuntimeException(
"Requested enabled DevSupportManager, but DevSupportManagerImpl class was not found" + "Requested enabled DevSupportManager, but DevSupportManagerImpl class was not found" +
" or could not be created", " or could not be created",
e); e);
} }
} }

View File

@ -111,12 +111,13 @@ public class DevSupportManagerImpl implements DevSupportManager {
private boolean mIsDevSupportEnabled = false; private boolean mIsDevSupportEnabled = false;
private boolean mIsCurrentlyProfiling = false; private boolean mIsCurrentlyProfiling = false;
private int mProfileIndex = 0; private int mProfileIndex = 0;
private @Nullable RedBoxHandler mRedBoxHandler;
public DevSupportManagerImpl( public DevSupportManagerImpl(
Context applicationContext, Context applicationContext,
ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, ReactInstanceDevCommandsHandler reactInstanceCommandsHandler,
@Nullable String packagerPathForJSBundleName, @Nullable String packagerPathForJSBundleName,
boolean enableOnCreate) { boolean enableOnCreate) {
mReactInstanceCommandsHandler = reactInstanceCommandsHandler; mReactInstanceCommandsHandler = reactInstanceCommandsHandler;
mApplicationContext = applicationContext; mApplicationContext = applicationContext;
mJSAppBundleName = packagerPathForJSBundleName; mJSAppBundleName = packagerPathForJSBundleName;
@ -160,6 +161,21 @@ public class DevSupportManagerImpl implements DevSupportManager {
setDevSupportEnabled(enableOnCreate); setDevSupportEnabled(enableOnCreate);
} }
public DevSupportManagerImpl(
Context applicationContext,
ReactInstanceDevCommandsHandler reactInstanceCommandsHandler,
@Nullable String packagerPathForJSBundleName,
boolean enableOnCreate,
@Nullable RedBoxHandler redBoxHandler) {
this(applicationContext,
reactInstanceCommandsHandler,
packagerPathForJSBundleName,
enableOnCreate);
mRedBoxHandler = redBoxHandler;
}
@Override @Override
public void handleException(Exception e) { public void handleException(Exception e) {
if (mIsDevSupportEnabled) { if (mIsDevSupportEnabled) {
@ -209,9 +225,12 @@ public class DevSupportManagerImpl implements DevSupportManager {
errorCookie != mRedBoxDialog.getErrorCookie()) { errorCookie != mRedBoxDialog.getErrorCookie()) {
return; return;
} }
mRedBoxDialog.setExceptionDetails( StackFrame[] stack = StackTraceHelper.convertJsStackTrace(details);
message, mRedBoxDialog.setExceptionDetails(message, stack);
StackTraceHelper.convertJsStackTrace(details)); mRedBoxDialog.setErrorCookie(errorCookie);
if (mRedBoxHandler != null) {
mRedBoxHandler.handleRedbox(message, stack);
}
mRedBoxDialog.show(); mRedBoxDialog.show();
} }
}); });
@ -244,6 +263,9 @@ public class DevSupportManagerImpl implements DevSupportManager {
} }
mRedBoxDialog.setExceptionDetails(message, stack); mRedBoxDialog.setExceptionDetails(message, stack);
mRedBoxDialog.setErrorCookie(errorCookie); mRedBoxDialog.setErrorCookie(errorCookie);
if (mRedBoxHandler != null) {
mRedBoxHandler.handleRedbox(message, stack);
}
mRedBoxDialog.show(); mRedBoxDialog.show();
} }
}); });

View File

@ -0,0 +1,21 @@
/**
* 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.devsupport;
import com.facebook.react.devsupport.StackTraceHelper.StackFrame;
/**
* Interface used by {@link DevSupportManagerImpl} to allow interception on any redboxes
* during development and handling the information from the redbox.
* The implementation should be passed by {@link #setRedBoxHandler} in {@link ReactInstanceManager}.
*/
public interface RedBoxHandler {
void handleRedbox(String title, StackFrame[] stack);
}

View File

@ -17,7 +17,7 @@ import com.facebook.react.bridge.ReadableMap;
/** /**
* Helper class converting JS and Java stack traces into arrays of {@link StackFrame} objects. * Helper class converting JS and Java stack traces into arrays of {@link StackFrame} objects.
*/ */
/* package */ class StackTraceHelper { public class StackTraceHelper {
/** /**
* Represents a generic entry in a stack trace, be it originally from JS or Java. * Represents a generic entry in a stack trace, be it originally from JS or Java.