diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java index d708e7dc2..b6bdfb0b5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java @@ -27,6 +27,7 @@ public abstract class ReactActivity extends Activity implements DefaultHardwareB "Overlay permissions needs to be granted in order for react native apps to run in dev mode"; private @Nullable ReactInstanceManager mReactInstanceManager; + private @Nullable ReactRootView mReactRootView; private LifecycleState mLifecycleState = LifecycleState.BEFORE_RESUME; private boolean mDoRefresh = false; @@ -138,7 +139,7 @@ public abstract class ReactActivity extends Activity implements DefaultHardwareB } mReactInstanceManager = createReactInstanceManager(); - ReactRootView mReactRootView = createRootView(); + mReactRootView = createRootView(); mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(), getLaunchOptions()); setContentView(mReactRootView); } @@ -169,6 +170,9 @@ public abstract class ReactActivity extends Activity implements DefaultHardwareB protected void onDestroy() { super.onDestroy(); + mReactRootView.unmountReactApplication(); + mReactRootView = null; + if (mReactInstanceManager != null) { mReactInstanceManager.destroy(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 2c4802e24..50f7f4077 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -165,13 +165,18 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView // No-op since UIManagerModule handles actually laying out children. } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mIsAttachedToInstance) { + getViewTreeObserver().addOnGlobalLayoutListener(getKeyboardListener()); + } + } + @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - - if (mReactInstanceManager != null && mIsAttachedToInstance) { - mReactInstanceManager.detachRootView(this); - mIsAttachedToInstance = false; + if (mIsAttachedToInstance) { getViewTreeObserver().removeOnGlobalLayoutListener(getKeyboardListener()); } } @@ -217,6 +222,19 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView } } + /** + * Unmount the react application at this root view, reclaiming any JS memory associated with that + * application. If {@link #startReactApplication} is called, this method must be called before the + * ReactRootView is garbage collected (typically in your Activity's onDestroy, or in your Fragment's + * onDestroyView). + */ + public void unmountReactApplication() { + if (mReactInstanceManager != null && mIsAttachedToInstance) { + mReactInstanceManager.detachRootView(this); + mIsAttachedToInstance = false; + } + } + /* package */ String getJSModuleName() { return Assertions.assertNotNull(mJSModuleName); } @@ -252,6 +270,17 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView getViewTreeObserver().addOnGlobalLayoutListener(getKeyboardListener()); } + @Override + protected void finalize() throws Throwable { + super.finalize(); + Assertions.assertCondition( + !mIsAttachedToInstance, + "The application this ReactRootView was rendering was not unmounted before the ReactRootView " + + "was garbage collected. This usually means that your application is leaking large amounts of " + + "memory. To solve this, make sure to call ReactRootView#unmountReactApplication in the onDestroy() " + + "of your hosting Activity or in the onDestroyView() of your hosting Fragment."); + } + private class KeyboardListener implements ViewTreeObserver.OnGlobalLayoutListener { private final Rect mVisibleViewArea; private final int mMinKeyboardHeightDetected;