Android 上的 React Navigation 内存泄漏

Ahm*_*mam 14 android react-native react-navigation react-native-reanimated react-native-screens

我们遇到了一个问题,即我们的 React Native Android 应用程序由于内存泄漏而在某些设备上崩溃。虽然它在大多数设备上都能完美运行,但大约 25% 的用户报告了此崩溃。该问题已通过 Crashlytics 进行跟踪,并使用 LeakCanary 进行进一步调查,发现在屏幕(底部选项卡或堆栈导航)之间导航时会发生内存泄漏。

\n

演示问题的存储库 Github 链接

\n

下面是导航结构:

\n
// main navigation\n<NavigationContainer>\n  <Stack.Navigator>\n    <Stack.Screen name="Auth" component={Auth} />\n    <Stack.Screen name="App" component={Drawer} />\n  </Stack.Navigator>\n</NavigationContainer>\n\n// drawer\n<Drawer.Navigator drawerContent={(props) => <DrawerContent {...props} />}>\n    <Drawer.Screen name="Main" component={BottomTabs} />\n</Drawer.Navigator>\n\n\n// Bottom Tabs\n<BottomTab.Navigator>\n  <BottomTab.Screen name="Tab1" component={Stack1} />\n  <BottomTab.Screen name="Tab2" component={Stack2} />\n  <BottomTab.Screen name="Tab3" component={Stack3} />\n  <BottomTab.Screen name="Tab4" component={Stack4} />\n</BottomTab.Navigator>\n\n// Stack 1 \n<Stack.Navigator>\n  <Stack.Screen name="Main" component={Screen} />\n  <Stack.Screen name="Screen2" component={Screen2} />\n  <Stack.Screen name="Screen3" component={Screen3} />\n  <Stack.Screen name="Screen4" component={Screen4} />\n  <Stack.Screen name="Screen5" component={Screen5} />\n</Stack.Navigator>\n
Run Code Online (Sandbox Code Playgroud)\n

问题Logcat

\n
\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n\xe2\x94\x82 GC Root: Thread object\n\xe2\x94\x82\n\xe2\x94\x9c\xe2\x94\x80 android.net.ConnectivityThread instance\n\xe2\x94\x82    Leaking: NO (PathClassLoader\xe2\x86\x93 is not leaking)\n\xe2\x94\x82    Thread name: \'ConnectivityThread\'\n\xe2\x94\x82    \xe2\x86\x93 Thread.contextClassLoader\n\xe2\x94\x9c\xe2\x94\x80 dalvik.system.PathClassLoader instance\n\xe2\x94\x82    Leaking: NO (InternalLeakCanary\xe2\x86\x93 is not leaking and A ClassLoader is never\n\xe2\x94\x82    leaking)\n\xe2\x94\x82    \xe2\x86\x93 ClassLoader.runtimeInternalObjects\n\xe2\x94\x9c\xe2\x94\x80 java.lang.Object[] array\n\xe2\x94\x82    Leaking: NO (InternalLeakCanary\xe2\x86\x93 is not leaking)\n\xe2\x94\x82    \xe2\x86\x93 Object[1048]\n\xe2\x94\x9c\xe2\x94\x80 leakcanary.internal.InternalLeakCanary class\n\xe2\x94\x82    Leaking: NO (MainActivity\xe2\x86\x93 is not leaking and a class is never leaking)\n\xe2\x94\x82    \xe2\x86\x93 static InternalLeakCanary.resumedActivity\n\xe2\x94\x9c\xe2\x94\x80 com.appname.MainActivity instance\n\xe2\x94\x82    Leaking: NO (Activity#mDestroyed is false)\n\xe2\x94\x82    mApplication instance of com.appname.MainApplication\n\xe2\x94\x82    mBase instance of androidx.appcompat.view.ContextThemeWrapper\n\xe2\x94\x82    \xe2\x86\x93 AppCompatActivity.mDelegate\n\xe2\x94\x82                        ~~~~~~~~~\n\xe2\x94\x9c\xe2\x94\x80 androidx.appcompat.app.AppCompatDelegateImpl instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 1.1 kB in 16 objects\n\xe2\x94\x82    mAppCompatCallback instance of com.appname.MainActivity with\n\xe2\x94\x82    mDestroyed = false\n\xe2\x94\x82    mContext instance of com.appname.MainActivity with mDestroyed = false\n\xe2\x94\x82    mHost instance of com.appname.MainActivity with mDestroyed = false\n\xe2\x94\x82    \xe2\x86\x93 AppCompatDelegateImpl.mActionBar\n\xe2\x94\x82                            ~~~~~~~~~~\n\xe2\x94\x9c\xe2\x94\x80 androidx.appcompat.app.ToolbarActionBar instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 5.7 MB in 12165 objects\n\xe2\x94\x82    \xe2\x86\x93 ToolbarActionBar.mDecorToolbar\n\xe2\x94\x82                       ~~~~~~~~~~~~~\n\xe2\x94\x9c\xe2\x94\x80 androidx.appcompat.widget.ToolbarWidgetWrapper instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 5.7 MB in 12161 objects\n\xe2\x94\x82    \xe2\x86\x93 ToolbarWidgetWrapper.mToolbar\n\xe2\x94\x82                           ~~~~~~~~\n\xe2\x94\x9c\xe2\x94\x80 com.swmansion.rnscreens.ScreenStackHeaderConfig$DebugMenuToolbar instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 5.7 MB in 12148 objects\n\xe2\x94\x82    View not part of a window view hierarchy\n\xe2\x94\x82    View.mAttachInfo is null (view detached)\n\xe2\x94\x82    View.mWindowAttachCount = 2\n\xe2\x94\x82    mPopupContext instance of com.facebook.react.uimanager.ThemedReactContext,\n\xe2\x94\x82    wrapping activity com.appname.MainActivity with mDestroyed = false\n\xe2\x94\x82    mContext instance of com.facebook.react.uimanager.ThemedReactContext,\n\xe2\x94\x82    wrapping activity com.appname.MainActivity with mDestroyed = false\n\xe2\x94\x82    \xe2\x86\x93 View.mParent\n\xe2\x94\x82           ~~~~~~~\n\xe2\x94\x9c\xe2\x94\x80 com.google.android.material.appbar.AppBarLayout instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 3.3 kB in 80 objects\n\xe2\x94\x82    View not part of a window view hierarchy\n\xe2\x94\x82    View.mAttachInfo is null (view detached)\n\xe2\x94\x82    View.mWindowAttachCount = 1\n\xe2\x94\x82    mContext instance of com.appname.MainActivity with mDestroyed = false\n\xe2\x94\x82    \xe2\x86\x93 View.mParent\n\xe2\x94\x82           ~~~~~~~\n\xe2\x95\xb0\xe2\x86\x92 com.swmansion.rnscreens.ScreenStackFragment$ScreensCoordinatorLayout instance\n\xe2\x80\x8b     Leaking: YES (ObjectWatcher was watching this because com.swmansion.\n\xe2\x80\x8b     rnscreens.ScreenStackFragment received Fragment#onDestroyView() callback\n\xe2\x80\x8b     (references to its views should be cleared to prevent leaks))\n\xe2\x80\x8b     Retaining 2.8 kB in 74 objects\n\xe2\x80\x8b     key = edfb8295-6373-4ec3-b16b-565e1448a34d\n\xe2\x80\x8b     watchDurationMillis = 6377\n\xe2\x80\x8b     retainedDurationMillis = 1377\n\xe2\x80\x8b     View not part of a window view hierarchy\n\xe2\x80\x8b     View.mAttachInfo is null (view detached)\n\xe2\x80\x8b     View.mWindowAttachCount = 1\n\xe2\x80\x8b     mContext instance of com.appname.MainActivity with mDestroyed = false\n\nMETADATA\n\nBuild.VERSION.SDK_INT: 33\nBuild.MANUFACTURER: Google\nLeakCanary version: 2.11\nApp process name: com.appname\nClass count: 27700\nInstance count: 268061\nPrimitive array count: 148295\nObject array count: 41071\nThread count: 56\nHeap total bytes: 32668238\nBitmap count: 83\nBitmap total bytes: 24208979\nLarge bitmap count: 0\nLarge bitmap total bytes: 0\nDb 1: open /data/user/0/com.appnamw/databases/com.\ngoogle.android.datatransport.events\nDb 2: open /data/user/0/com.\nappname/databases/RKStorage\nDb 3: open /data/user/0/com.appname/no_backup/androidx.work.workdb\nStats: LruCache[maxSize=3000,hits=162815,misses=288528,hitRate=36%]\nRandomAccess[bytes=14810283,reads=288528,travel=86887776715,range=38641306,size=\n47641520]\nAnalysis duration: 13551 ms\n\xe2\x94\x82 GC Root: Thread object\n\xe2\x94\x82\n\xe2\x94\x9c\xe2\x94\x80 android.net.ConnectivityThread instance\n\xe2\x94\x82    Leaking: NO (PathClassLoader\xe2\x86\x93 is not leaking)\n\xe2\x94\x82    Thread name: \'ConnectivityThread\'\n\xe2\x94\x82    \xe2\x86\x93 Thread.contextClassLoader\n\xe2\x94\x9c\xe2\x94\x80 dalvik.system.PathClassLoader instance\n\xe2\x94\x82    Leaking: NO (InternalLeakCanary\xe2\x86\x93 is not leaking and A ClassLoader is never\n\xe2\x94\x82    leaking)\n\xe2\x94\x82    \xe2\x86\x93 ClassLoader.runtimeInternalObjects\n\xe2\x94\x9c\xe2\x94\x80 java.lang.Object[] array\n\xe2\x94\x82    Leaking: NO (InternalLeakCanary\xe2\x86\x93 is not leaking)\n\xe2\x94\x82    \xe2\x86\x93 Object[2191]\n\xe2\x94\x9c\xe2\x94\x80 leakcanary.internal.InternalLeakCanary class\n\xe2\x94\x82    Leaking: NO (MainActivity\xe2\x86\x93 is not leaking and a class is never leaking)\n\xe2\x94\x82    \xe2\x86\x93 static InternalLeakCanary.resumedActivity\n\xe2\x94\x9c\xe2\x94\x80 com.appname.MainActivity instance\n\xe2\x94\x82    Leaking: NO (Activity#mDestroyed is false)\n\xe2\x94\x82    mApplication instance of com.appname.MainApplication\n\xe2\x94\x82    mBase instance of androidx.appcompat.view.ContextThemeWrapper\n\xe2\x94\x82    \xe2\x86\x93 AppCompatActivity.mDelegate\n\xe2\x94\x82                        ~~~\n\xe2\x94\x9c\xe2\x94\x80 androidx.appcompat.app.AppCompatDelegateImpl instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 1.0 kB in 16 objects\n\xe2\x94\x82    mAppCompatCallback instance of com.appname.MainActivity with\n\xe2\x94\x82    mDestroyed = false\n\xe2\x94\x82    mContext instance of com.appname.MainActivity with mDestroyed = false\n\xe2\x94\x82    mHost instance of com.appname.MainActivity with mDestroyed = false\n\xe2\x94\x82    \xe2\x86\x93 AppCompatDelegateImpl.mActionBar\n\xe2\x94\x82                            ~~~~\n\xe2\x94\x9c\xe2\x94\x80 androidx.appcompat.app.ToolbarActionBar instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 552.4 kB in 2395 objects\n\xe2\x94\x82    \xe2\x86\x93 ToolbarActionBar.mDecorToolbar\n\xe2\x94\x82                       ~~~~~\n\xe2\x94\x9c\xe2\x94\x80 androidx.appcompat.widget.ToolbarWidgetWrapper instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 552.4 kB in 2391 objects\n\xe2\x94\x82    \xe2\x86\x93 ToolbarWidgetWrapper.mToolbar\n\xe2\x94\x82                           ~~~~\n\xe2\x94\x9c\xe2\x94\x80 com.swmansion.rnscreens.ScreenStackHeaderConfig$DebugMenuToolbar instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 552.2 kB in 2387 objects\n\xe2\x94\x82    View not part of a window view hierarchy\n\xe2\x94\x82    View.mAttachInfo is null (view detached)\n\xe2\x94\x82    View.mWindowAttachCount = 1\n\xe2\x94\x82    mPopupContext instance of com.facebook.react.uimanager.ThemedReactContext,\n\xe2\x94\x82    wrapping activity com.appname.MainActivity with mDestroyed = false\n\xe2\x94\x82    mContext instance of com.facebook.react.uimanager.ThemedReactContext,\n\xe2\x94\x82    wrapping activity com.appname.MainActivity with mDestroyed = false\n\xe2\x94\x82    \xe2\x86\x93 CustomToolbar.config\n\xe2\x94\x82                    ~~\n\xe2\x94\x9c\xe2\x94\x80 com.swmansion.rnscreens.ScreenStackHeaderConfig instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 2.0 kB in 14 objects\n\xe2\x94\x82    View not part of a window view hierarchy\n\xe2\x94\x82    View.mAttachInfo is null (view detached)\n\xe2\x94\x82    View.mID = R.id.null\n\xe2\x94\x82    View.mWindowAttachCount = 1\n\xe2\x94\x82    mContext instance of com.facebook.react.uimanager.ThemedReactContext,\n\xe2\x94\x82    wrapping activity com.appname.MainActivity with mDestroyed = false\n\xe2\x94\x82    \xe2\x86\x93 View.mParent\n\xe2\x94\x82           ~~~\n\xe2\x94\x9c\xe2\x94\x80 com.swmansion.rnscreens.Screen instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 511.8 kB in 1829 objects\n\xe2\x94\x82    View not part of a window view hierarchy\n\xe2\x94\x82    View.mAttachInfo is null (view detached)\n\xe2\x94\x82    View.mID = R.id.null\n\xe2\x94\x82    View.mWindowAttachCount = 1\n\xe2\x94\x82    mContext instance of com.facebook.react.uimanager.ThemedReactContext,\n\xe2\x94\x82    wrapping activity com.appname.MainActivity with mDestroyed = false\n\xe2\x94\x82    \xe2\x86\x93 Screen.fragment\n\xe2\x94\x82             ~~~~\n\xe2\x95\xb0\xe2\x86\x92 com.swmansion.rnscreens.ScreenStackFragment instance\n\xe2\x80\x8b     Leaking: YES (ObjectWatcher was watching this because com.swmansion.\n\xe2\x80\x8b     rnscreens.ScreenStackFragment received Fragment#onDestroy() callback and\n\xe2\x80\x8b     Fragment#mFragmentManager is null)\n\xe2\x80\x8b     Retaining 2.1 kB in 72 objects\n\xe2\x80\x8b     key = b116c7d1-e55c-4a42-9170-eca82ba9dd7d\n\xe2\x80\x8b     watchDurationMillis = 7292\n\xe2\x80\x8b     retainedDurationMillis = 2249\n\nMETADATA\n\nBuild.VERSION.SDK_INT: 28\nBuild.MANUFACTURER: HUAWEI\nLeakCanary version: 2.11\nApp process name: appname\nClass\xe2\x80\xa8    \xe2\x80\xa8    \xe2\x80\xa8    \xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n\xe2\x94\x82 GC Root: Global variable in native code\n\xe2\x94\x82\n\xe2\x94\x9c\xe2\x94\x80 com.swmansion.reanimated.NativeProxy instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 221 B in 8 objects\n\xe2\x94\x82    \xe2\x86\x93 NativeProxyCommon.mNodesManager\n\xe2\x94\x82                        ~~~~~\n\xe2\x94\x9c\xe2\x94\x80 com.swmansion.reanimated.NodesManager instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 9.4 kB in 318 objects\n\xe2\x94\x82    mContext instance of com.facebook.react.bridge.ReactApplicationContext,\n\xe2\x94\x82    wrapping com.appname.MainApplication\n\xe2\x94\x82    mReactApplicationContext instance of com.facebook.react.bridge.\n\xe2\x94\x82    ReactApplicationContext, wrapping com.appname.MainApplication\n\xe2\x94\x82    \xe2\x86\x93 NodesManager.mAnimationManager\n\xe2\x94\x82                   ~~~~~~~\n\xe2\x94\x9c\xe2\x94\x80 com.swmansion.reanimated.layoutReanimation.AnimationsManager instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 794 B in 24 objects\n\xe2\x94\x82    mContext instance of com.facebook.react.bridge.ReactApplicationContext,\n\xe2\x94\x82    wrapping com.appname.MainApplication\n\xe2\x94\x82    \xe2\x86\x93 AnimationsManager.mReanimatedNativeHierarchyManager\n\xe2\x94\x82                        ~~~~~~~~~~~\n\xe2\x94\x9c\xe2\x94\x80 com.swmansion.reanimated.layoutReanimation.ReanimatedNativeHierarchyManager\n\xe2\x94\x82  instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 942.6 kB in 6901 objects\n\xe2\x94\x82    \xe2\x86\x93 NativeViewHierarchyManager.mTagsToViews\n\xe2\x94\x82                                 ~~~~\n\xe2\x94\x9c\xe2\x94\x80 android.util.SparseArray instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 929.7 kB in 6881 objects\n\xe2\x94\x82    \xe2\x86\x93 SparseArray.mValues\n\xe2\x94\x82                  ~~~\n\xe2\x94\x9c\xe2\x94\x80 java.lang.Object[] array\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 923.6 kB in 6879 objects\n\xe2\x94\x82    \xe2\x86\x93 Object[217]\n\xe2\x94\x82            ~~~\n\xe2\x94\x9c\xe2\x94\x80 com.swmansion.rnscreens.Screen instance\n\xe2\x94\x82    Leaking: UNKNOWN\n\xe2\x94\x82    Retaining 2.2 kB in 17 objects\n\xe2\x94\x82    View not part of a window view hierarchy\n\xe2\x94\x82    View.mAttachInfo is null (view detached)\n\xe2\x94\x82    View.mID = R.id.null\n\xe2\x94\x82    View.mWindowAttachCount = 5\n\xe2\x94\x82    mContext instance of com.facebook.react.uimanager.ThemedReactContext,\n\xe2\x94\x82    wrapping activity com.appname.MainActivity with mDestroyed = false\n\xe2\x94\x82    \xe2\x86\x93 View.mParent\n\xe2\x94\x82           ~~~\n\xe2\x95\xb0\xe2\x86\x92 com.swmansion.rnscreens.ScreenStackFragment$ScreensCoordinatorLayout instance\n\xe2\x80\x8b     Leaking: YES (ObjectWatcher was watching this because com.swmansion.\n\xe2\x80\x8b     rnscreens.ScreenStackFragment received Fragment#onDestroyView() callback\n\xe2\x80\x8b     (references to its views should be cleared to prevent leaks))\n\xe2\x80\x8b     Retaining 3.7 kB in 72 objects\n\xe2\x80\x8b     key = 8b340022-4f6e-4653-a6c8-ad5639e3ff8e\n\xe2\x80\x8b     watchDurationMillis = 5828\n\xe2\x80\x8b     retainedDurationMillis = 827\n\xe2\x80\x8b     View not part of a window view hierarchy\n\xe2\x80\x8b     View.mAttachInfo is null (view detached)\n\xe2\x80\x8b     View.mWindowAttachCount = 1\n\xe2\x80\x8b     mContext instance of com.appname.MainActivity with mDestroyed = false\n\nMETADATA\n\nBuild.VERSION.SDK_INT: 28\nBuild.MANUFACTURER: HUAWEI\nLeakCanary version: 2.11\nApp process name:appname\nClass count: 18907\nInstance count: 266551\nPrimitive array count: 172560\nObject array count: 29962\nThread count: 57\nHeap total bytes: 26966868\nBitmap count: 75\nBitmap total bytes: 14020308\nLarge bitmap count: 0\nLarge bitmap total bytes: 0\nDb 1: open /data/user/0/appnamer/databases/RKStorage\nDb 2: open /data/user/0/com.appname/databases/com.\ngoogle.android.datatransport.events\nDb 3: open /data/user/0/com.appname/no_backup/androidx.work.workdb\nStats: LruCache[maxSize=3000,hits=84650,misses=180148,hitRate=31%]\nRandomAccess[bytes=9194085,reads=180148,travel=66525003196,range=33782477,size=4\n0598308]\nAnalysis duration: 15559 ms\n
Run Code Online (Sandbox Code Playgroud)\n

到目前为止尝试的解决方案没有成功:

\n
 // it fixes the leak when navigating between the ButtomTabs \n // but it leaks when navigating in the stack(ex:to screen2)\n- enableScreens(false)\n- super.onCreate(null);\n
Run Code Online (Sandbox Code Playgroud)\n

模块版本:

\n
   "react-native-screens": "^3.22.0",\n   "@react-navigation/bottom-tabs": "^6.3.2",\n   "@react-navigation/drawer": "^6.4.4",\n   "@react-navigation/native": "^6.0.11",\n   "@react-navigation/native-stack": "^6.7.0",\n   "react-native-reanimated": "^3.3.0",\n   "react-native-gesture-handler": "^2.12.0",\n
Run Code Online (Sandbox Code Playgroud)\n

该问题最初是在三年前 v4 版本发布期间报告的。尽管进行了更新,但它仍然保留在 v6 中。react-native-screens我怀疑问题的根源在于、react-native-reanimated和之间的相互作用react-navigation。任何解决这一挑战的解决方案或变通方法将不胜感激。

\n

相关报道

\n\n

Muh*_*man 2

这不是真正的内存泄漏

工具将行为描述为泄漏是将 s 保留ScreenFragment在内存中的结果。这样做是因为,在 中react-native,我们不能通过恢复 的状态来销毁然后创建新视图Fragment,因为每个视图都有其reactTag等。由于泄漏检测器工具的启发式,该行为显示为泄漏,它表示如果onDestroy在 a 上调​​用Fragment,那么对它的引用不应保留在任何地方,但是,如上所述,它不适用于react-native应用程序,因为我们不会重新创建 a 的视图Fragment,而是remove在它们变得不可见时调用它们然后add它们又回到Screen可见状态,并附有相同的Screen内容。

希望这能解决您的问题

Software Mansion 开发者参考https://github.com/software-mansion/react-native-screens/issues/843#issuecomment-832034119

参考react-native-screens