Force react applications to be explicitly unmounted

Summary:
We previously were unmounting the react application unconditionally when the ReactRootView#onDetachedFromWindow. This is nice in that it automatically allows us to reclaim memory, but there are many scenarios where a ReactRootView can be embedded in another piece of UI that detaches its children as part of its normal function (e.g. ListView, RecyclerView, ViewPager, etc).

As such, we will now enforce that the hosting Activity/Fragment/??? explicitly calls unmountReactApplication in the same way it calls startReactApplication. For Applications extending ReactActivity/AbstractReactActivity, this will happen automatically in onDestroy.

Reviewed By: foghina

Differential Revision: D3265161

fb-gh-sync-id: 4d49b0c41256213f00874f57e784aa8741dbf394
fbshipit-source-id: 4d49b0c41256213f00874f57e784aa8741dbf394
This commit is contained in:
Andy Street 2016-05-09 04:08:45 -07:00 committed by Facebook Github Bot 7
parent f7ce0c1c2f
commit 54f7ae1c02
2 changed files with 38 additions and 5 deletions

View File

@ -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();
}

View File

@ -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;