使用AndroidX Navigation Compose后横竖屏切换触发MainActivity内存泄漏
问题描述
在使用navHostController导航至新目标页后,第二次切换横竖屏时MainActivity会出现内存泄漏。
复现步骤
- 运行应用,默认加载仅包含
TopAppBar和一个Button的FooScreen - 点击「Open Bar screen」按钮,加载仅包含
TopAppBar的BarScreen - 将设备从竖屏切换为横屏
- 再将设备切回竖屏
复现后可在logcat中看到StrictMode抛出的泄漏告警:
2021-11-02 16:23:20.672 31230-31230/com.leinardi.template E/StrictMode: class com.leinardi.template.ui.MainActivity; instances=2; limit=1 android.os.StrictMode$InstanceCountViolation: class com.leinardi.template.ui.MainActivity; instances=2; limit=1 at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)
同时会收到LeakCanary的泄漏通知,对应日志如下:
┬─── │ GC Root: System class │ ├─ leakcanary.internal.InternalLeakCanary class │ Leaking: NO (MainActivity↓ is not leaking and a class is never leaking) │ ↓ static InternalLeakCanary.resumedActivity ├─ com.leinardi.template.ui.MainActivity instance │ Leaking: NO (Activity#mDestroyed is false) │ mApplication instance of com.leinardi.template.Template │ mBase instance of android.app.ContextImpl │ ↓ MainActivity.navHostController │ ~~~~~~~~~~~~~~~~~ ├─ androidx.navigation.NavHostController instance │ Leaking: UNKNOWN │ Retaining 5.5 kB in 140 objects │ activity instance of com.leinardi.template.ui.MainActivity with mDestroyed = false │ context instance of com.leinardi.template.ui.MainActivity with mDestroyed = false │ lifecycleOwner instance of com.leinardi.template.ui.MainActivity with mDestroyed = false │ ↓ NavController.viewModel │ ~~~~~~~~~ ├─ androidx.navigation.NavControllerViewModel instance │ Leaking: UNKNOWN │ Retaining 128.4 kB in 2406 objects │ ↓ NavControllerViewModel.viewModelStores │ ~~~~~~~~~~~~~~~ ├─ java.util.LinkedHashMap instance │ Leaking: UNKNOWN │ Retaining 128.3 kB in 2404 objects │ ↓ LinkedHashMap.header │ ~~~~~~ ├─ java.util.LinkedHashMap$LinkedHashMapEntry instance │ Leaking: UNKNOWN │ Retaining 32 B in 1 objects │ ↓ LinkedHashMap$LinkedHashMapEntry.after │ ~~~~~ ├─ java.util.LinkedHashMap$LinkedHashMapEntry instance │ Leaking: UNKNOWN │ Retaining 127.8 kB in 2389 objects │ ↓ HashMap$HashMapEntry.value │ ~~~~~ ├─ androidx.lifecycle.ViewModelStore instance │ Leaking: UNKNOWN │ Retaining 127.7 kB in 2388 objects │ ↓ ViewModelStore.mMap │ ~~~~ ├─ java.util.HashMap instance │ Leaking: UNKNOWN │ Retaining 127.7 kB in 2387 objects │ ↓ HashMap.table │ ~~~~~ ├─ java.util.HashMap$HashMapEntry[] array │ Leaking: UNKNOWN │ Retaining 127.7 kB in 2385 objects │ ↓ HashMap$HashMapEntry[].[1] │ ~~~ ├─ java.util.HashMap$HashMapEntry instance │ Leaking: UNKNOWN │ Retaining 126.6 kB in 2348 objects │ ↓ HashMap$HashMapEntry.value │ ~~~~~ ├─ androidx.navigation.compose.BackStackEntryIdViewModel instance │ Leaking: UNKNOWN │ Retaining 126.5 kB in 2347 objects │ ↓ BackStackEntryIdViewModel.saveableStateHolder │ ~~~~~~~~~~~~~~~~~~~ ├─ androidx.compose.runtime.saveable.SaveableStateHolderImpl instance │ Leaking: UNKNOWN │ Retaining 126.4 kB in 2343 objects │ ↓ SaveableStateHolderImpl.parentSaveableStateRegistry │ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ ├─ androidx.compose.ui.platform.DisposableSaveableStateRegistry instance │ Leaking: UNKNOWN │ Retaining 125.9 kB in 2321 objects │ ↓ DisposableSaveableStateRegistry.onDispose │ ~~~~~~~~~ ├─ androidx.compose.ui.platform.DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry$1 instance │ Leaking: UNKNOWN │ Retaining 125.6 kB in 2312 objects │ Anonymous subclass of kotlin.jvm.internal.Lambda │ ↓ DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry$1.$androidxRegistry │ ~~~~~~~~~~~~~~~~~ ├─ androidx.savedstate.SavedStateRegistry instance │ Leaking: UNKNOWN │ Retaining 125.6 kB in 2310 objects │ ↓ SavedStateRegistry.mComponents │ ~~~~~~~~~~~ ├─ androidx.arch.core.internal.SafeIterableMap instance │ Leaking: UNKNOWN │ Retaining 125.5 kB in 2309 objects │ ↓ SafeIterableMap.mEnd │ ~~~~ ├─ androidx.arch.core.internal.SafeIterableMap$Entry instance │ Leaking: UNKNOWN │ Retaining 125.3 kB in 2302 objects │ ↓ SafeIterableMap$Entry.mValue │ ~~~~~~ ├─ androidx.activity.ComponentActivity$$ExternalSyntheticLambda1 instance │ Leaking: UNKNOWN │ Retaining 125.3 kB in 2301 objects │ f$0 instance of com.leinardi.template.ui.MainActivity with mDestroyed = true │ ↓ ComponentActivity$$ExternalSyntheticLambda1.f$0 │ ~~~ ╰→ com.leinardi.template.ui.MainActivity instance Leaking: YES (ObjectWatcher was watching this because com.leinardi.template.ui.MainActivity received Activity#onDestroy() callback and Activity#mDestroyed is true) Retaining 125.3 kB in 2300 objects key = 2c6ea34a-19c0-4d26-a5ec-88625d79531f watchDurationMillis = 42581 retainedDurationMillis = 37580 mApplication instance of com.leinardi.template.Template mBase instance of android.app.ContextImpl METADATA Build.VERSION.SDK_INT: 24 Build.MANUFACTURER: Google LeakCanary version: 2.7 App process name: com.leinardi.template Stats: LruCache[maxSize=3000,hits=1619,misses=30537,hitRate=5%] RandomAccess[bytes=1518368,reads=30537,travel=10623307269,range=18917491,size=20769812] Heap dump reason: user request Analysis duration: 1249 ms
已观察到的现象
- 泄漏仅在第二次切换横竖屏时触发(若初始为竖屏,从横屏切回竖屏时出现)
- 若通过导航组件跳转至Debug页面,也会出现该泄漏
- 通过deeplink(
adb shell am start -d "template://bar" -a android.intent.action.VIEW)打开Bar页面不会触发泄漏 - 点击顶部导航栏的返回按钮从Bar页面回到Foo页面不会触发泄漏,Foo页面始终无泄漏
- 可在API 24、28、30版本上复现,其余版本未测试
已尝试的解决方案
- 移除Activity中
lateinit var navHostController: NavHostController的全局实例声明 - 在
templateNavigator.destinations.collect {}逻辑处(MainActivity.kt第71行)使用rememberCoroutineScope()替代LaunchedEffect()
曾尝试使用Android Studio Profiler排查泄漏,未得到有效结论。
该问题出现在多模块单Activity MVVM-MVI纯Compose模板项目中,应用仅包含单个Activity,除初始化导航宿主外无其他逻辑,导航逻辑统一放在core-navigation模块中,每个功能模块提供独立UI和业务逻辑。当前共有3个功能模块,每个对应1个页面:Foo、Bar、Debug。Foo页面仅包含一个跳转至Bar页面的按钮,在Bar页面两次切换横竖屏会触发泄漏,通过Deeplink访问Bar页面则不会出现该问题。
内容的提问来源于stack exchange,提问作者Roberto Leinardi
相关产品推荐
相关产品推荐

