[Android] Memory leak leading to intermittent crashing
See original GitHub issueIt looks like a memory leak in MainActivity
related to the ScreenFragment onDestroy
function is causing app crashes.
We have a Tab Navigator with a nested Auth Stack Navigator for login flows. We are using react-native-screens
with enableScreens()
at the top of App.tsx
outside of the root component.
When we turn off enableScreens()
the memory leak goes away. But this causes exceptionally poor performance on Android 8 and lower-end devices.
{
"react-native-screens": "^2.11.0",
"react-native": "0.63.3",
"@react-navigation/native": "^5.7.6",
"@react-navigation/stack": "^5.9.3",
}
LeakCanary report:
β¬βββ
β GC Root: Global variable in native code
β
ββ com.facebook.react.bridge.JavaModuleWrapper instance
β Leaking: UNKNOWN
β β JavaModuleWrapper.mModuleHolder
β ~~~~~~~~~~~~~
ββ com.facebook.react.bridge.ModuleHolder instance
β Leaking: UNKNOWN
β β ModuleHolder.mModule
β ~~~~~~~
ββ com.facebook.react.uimanager.UIManagerModule instance
β Leaking: UNKNOWN
β β UIManagerModule.mUIImplementation
β ~~~~~~~~~~~~~~~~~
ββ com.facebook.react.uimanager.UIImplementation instance
β Leaking: UNKNOWN
β β UIImplementation.mOperationsQueue
β ~~~~~~~~~~~~~~~~
ββ com.facebook.react.uimanager.UIViewOperationQueue instance
β Leaking: UNKNOWN
β β UIViewOperationQueue.mNativeViewHierarchyManager
β ~~~~~~~~~~~~~~~~~~~~~~~~~~~
ββ com.facebook.react.uimanager.NativeViewHierarchyManager instance
β Leaking: UNKNOWN
β β NativeViewHierarchyManager.mTagsToViews
β ~~~~~~~~~~~~
ββ android.util.SparseArray instance
β Leaking: UNKNOWN
β β SparseArray.mValues
β ~~~~~~~
ββ java.lang.Object[] array
β Leaking: UNKNOWN
β β Object[].[65]
β ~~~~
ββ com.swmansion.rnscreens.Screen instance
β Leaking: YES (View detached and has parent)
β mContext instance of com.facebook.react.uimanager.ThemedReactContext, wrapping activity com.XXXX.MainActivity with mDestroyed = false
β View#mParent is set
β View#mAttachInfo is null (view detached)
β View.mID = R.id.null
β View.mWindowAttachCount = 1
β β Screen.mFragment
β°β com.swmansion.rnscreens.ScreenFragment instance
β Leaking: YES (ObjectWatcher was watching this because com.swmansion.rnscreens.ScreenFragment received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
β key = 55523ed9-b81c-4d11-a5b7-42155878b7f6
β watchDurationMillis = 7610
β retainedDurationMillis = 2572
METADATA
Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: OnePlus
LeakCanary version: 2.4
App process name: com.XXXX
Analysis duration: 12898 ms
MainTabNavigator.tsx
const Tab = createBottomTabNavigator<MainNavParamList>()
const MainTabNavigator: FC<{}> = () => {
return (
<Tab.Navigator initialRouteName={'Home'} tabBarOptions={tabBarOptions}>
<Tab.Screen
name="Home"
component={HomeStack}
options={{ tabBarLabel: tabBarLabel(I18n.t('Home')), tabBarIcon: tabBarIcon('home-alt') }}
/>
<Tab.Screen
name="Menu"
component={MenuStack}
options={{ tabBarLabel: tabBarLabel(I18n.t('Menu')), tabBarIcon: tabBarIcon('coffee') }}
/>
<Tab.Screen
name="Order"
component={OrderStack}
options={{ tabBarLabel: tabBarLabel(I18n.t('Orders')), tabBarIcon: tabBarIcon('receipt') }}
/>
<Tab.Screen
name="Account"
component={AccountStack}
options={{ tabBarLabel: tabBarLabel(I18n.t('Account')), tabBarIcon: tabBarIcon('user') }}
/>
<Tab.Screen name="Cart" component={CartStackNavigator} options={{ tabBarButton: () => null, tabBarVisible: false }} />
<Tab.Screen name="Auth" component={LoginStack} options={{ tabBarButton: () => null }} />
</Tab.Navigator>
)
}
Auth Stack Navigator
const Stack = createStackNavigator<AuthStackParamList>()
interface Props {
navigation?: StackNavigationProp<MainNavParamList, 'Auth'>
route?: RouteProp<MainNavParamList, 'Auth'>
}
export type LeftAction = ((props: StackHeaderLeftButtonProps) => React.ReactNode) | undefined
const AuthFlowNavigator: React.FC<Props> = (props: Props) => {
let initialRoute: AuthInitialScreen = 'SignIn'
if (props.route && props.route.params) {
initialRoute = props.route.params.screen || initialRoute
}
let goBack: LeftAction
const navigation = props.navigation
if (navigation) {
goBack = (stackProps: StackHeaderLeftButtonProps) => (
<HeaderBackButton
{...stackProps}
onPress={() => navigation.goBack()}
backImage={(imgProps: { tintColor: string }) => <BackIcon tintColor={imgProps.tintColor} />}
/>
)
}
return (
<Stack.Navigator
mode="card"
headerMode="float"
initialRouteName={initialRoute}
screenOptions={stackNavigationOptions}>
<Stack.Screen
name="SignIn"
component={SignIn}
options={{
headerTitle: I18n.t('Sign in to my Account'),
headerLeft: goBack,
}}
/>
<Stack.Screen
name="SignUp"
component={SignUp}
options={{
headerTitle: I18n.t('Sign up'),
headerLeft: initialRoute === 'SignUp' ? goBack : undefined,
}}
/>
<Stack.Screen
name="SignUpWithEmail"
component={SignUpWithEmail}
options={{
headerTitle: I18n.t('Sign up by email'),
}}
/>
<Stack.Screen
name="ConfirmSignUp"
component={ConfirmSignUp}
options={{
headerTitle: I18n.t('Sign up by email'),
}}
/>
<Stack.Screen
name="ForgotPasswordEmail"
component={ForgotPasswordEmail}
options={{ headerTitle: I18n.t('Reset password') }}
/>
<Stack.Screen
name="ForgotPasswordChallenge"
component={ForgotPasswordChallenge}
options={{ headerTitle: I18n.t('Reset password') }}
/>
</Stack.Navigator>
)
}
MainActivity.java
import android.os.Bundle;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import org.devio.rn.splashscreen.SplashScreen;
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "XXXX";
}
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected ReactRootView createRootView() {
return new RNGestureHandlerEnabledRootView(MainActivity.this);
}
};
}
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.show(this, R.style.SplashStatusBarTheme);
super.onCreate(null);
}
}
The memory dump occurs when you navigate to the Auth Stack. App doesnβt seem to crash, however.
Crashes are reported when user is redirected to Auth Stack when trying to place an order while not logged in. These crashes are intermittent and cannot be reliably induced.
Cheers, Jon
EDIT:
App Center Diagnostics shows this stack trace occuring multiple times, for only Android users:
com.swmansion.rnscreens.ScreenFragment.<init> ScreenFragment.java:44
java.lang.reflect.Constructor.newInstance0 Constructor.java
androidx.fragment.app.Fragment.instantiate Fragment.java:548
androidx.fragment.app.FragmentContainer.instantiate FragmentContainer.java:57
androidx.fragment.app.FragmentManager$3.instantiate FragmentManager.java:390
androidx.fragment.app.FragmentStateManager.<init> FragmentStateManager.java:74
androidx.fragment.app.FragmentManager.restoreSaveState FragmentManager.java:2454
androidx.fragment.app.FragmentController.restoreSaveState FragmentController.java:196
androidx.fragment.app.FragmentActivity.onCreate FragmentActivity.java:287
androidx.appcompat.app.AppCompatActivity.onCreate AppCompatActivity.java:106
com.facebook.react.ReactActivity.onCreate ReactActivity.java:44
com.XXXX.MainActivity.onCreate MainActivity.java:37
android.app.Activity.performCreate Activity.java:8000
Issue Analytics
- State:
- Created 3 years ago
- Reactions:3
- Comments:18 (9 by maintainers)
Top GitHub Comments
Any updates on this, I am still facing the same issue
as @d3vhound said
super.onCreate(null)
didnβt fix the issue.Then I think there is no more to be done unfortunately, as mentioned here: https://github.com/software-mansion/react-native-screens/issues/843#issuecomment-832034119.