Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Memory leak leading to intermittent crashing #687

Closed
jmkmay opened this issue Oct 29, 2020 · 18 comments
Closed

[Android] Memory leak leading to intermittent crashing #687

jmkmay opened this issue Oct 29, 2020 · 18 comments
Labels
Missing info The user didn't precise the problem enough Platform: Android This issue is specific to Android

Comments

@jmkmay
Copy link

jmkmay commented Oct 29, 2020

It 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
@jmkmay jmkmay changed the title Memory leak leading to intermittent crashing [Android] Memory leak leading to intermittent crashing Oct 29, 2020
@WoLewicki
Copy link
Member

At the end of your comment, you placed a stack trace that shows going into androidx.fragment.app.FragmentController.restoreSaveState FragmentController.java:196, but is only achievable if there is no null in MainActivity.java's super.onCreate(null); (see the picture below). Are you sure there is a null there? Am I missing something?

image

@WoLewicki WoLewicki added Platform: Android This issue is specific to Android Missing info The user didn't precise the problem enough labels Oct 30, 2020
@jmkmay
Copy link
Author

jmkmay commented Oct 30, 2020

Yea I was confused about that as well. The MainActivity.java there is copy pasted from our app...

Could it be that its getting overloaded from somewhere else?

@WoLewicki
Copy link
Member

I am not sure, maybe you changed it not so long ago and these logs are from the older version of your app?

@WoLewicki
Copy link
Member

Did you manage to spot out what was the issue @jmkmay? Also, can I help you any more with this, and if not, can I close this issue then?

@AjayShivanagol
Copy link

is it resolved?

@WoLewicki
Copy link
Member

@1Ajay most probably it was caused by not specifying super.onCreate(null); in MainActivity.java, at least it is what the stack trace suggests.

@WoLewicki
Copy link
Member

I will close this issue since it seems there is nothing I can help with more. Feel free to comment here if you have any questions.

@d3vhound
Copy link

I will leave this comment for future readers.

Currently experiencing the same issue. Multiple crashes in Appcenter with the same stack trace. The super.onCreate(null) doesn't seem to have any affect whatsoever.

@jmkmay
Copy link
Author

jmkmay commented Feb 23, 2021

Apologies for not replying to earlier comments. This was never resolved and we ended up just deploying app without enableScreens(). It sucks, but we basically copied the example MainActivity.java line for line and the issue persisted.

@WoLewicki
Copy link
Member

We would need to have a reproduction to be able to find a solution for this problem. These stack traces are not enough since the behavior they show should be avoided by applying super.onCreate(null);

@d3vhound
Copy link

d3vhound commented Feb 24, 2021

Using this tool on a project with a basic react-navigation set-up should show the leaks. I don't think me or @jmkmay are doing anything out of the ordinary. A auth stack and main stack is what we both have in common. But unfortunately applying super.onCreate(null) doesn't stop the leaks.

Screen Shot 2021-02-23 at 3 12 24 PM

@Ahmed-Imam
Copy link

Ahmed-Imam commented Apr 22, 2021

Any updates on this, I am still facing the same issue

"react-native": "0.63.4",
"react-navigation": "^4.0.0",   
"react-navigation-material-bottom-tabs": "^1.0.0",   
"react-navigation-stack": "^2.10.4",   
"react-navigation-tabs": "^2.3.0",

as @d3vhound said super.onCreate(null) didn't fix the issue.

Screen Shot 2021-04-22 at 4 32 13 PM

@UserSty
Copy link

UserSty commented Sep 22, 2021

Any news about fix?

@WoLewicki
Copy link
Member

I believe this has been discussed here: #843

@UserSty
Copy link

UserSty commented Sep 22, 2021

I believe this has been discussed here: #843

Unfortunately it doesn't have solution

@WoLewicki
Copy link
Member

What is your exact concern about this behavior? Does it lead to crash in your case too @UserSty ?

@UserSty
Copy link

UserSty commented Sep 22, 2021

What is your exact concern about this behavior? Does it lead to crash in your case too @UserSty ?

Just leak

@WoLewicki
Copy link
Member

Then I think there is no more to be done unfortunately, as mentioned here: #843 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Missing info The user didn't precise the problem enough Platform: Android This issue is specific to Android
Projects
None yet
Development

No branches or pull requests

6 participants