You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用AndroidX Navigation Compose后横竖屏切换触发MainActivity内存泄漏

问题描述

在使用navHostController导航至新目标页后,第二次切换横竖屏时MainActivity会出现内存泄漏。

复现步骤

  1. 运行应用,默认加载仅包含TopAppBar和一个Button的FooScreen
  2. 点击「Open Bar screen」按钮,加载仅包含TopAppBar的BarScreen
  3. 将设备从竖屏切换为横屏
  4. 再将设备切回竖屏

复现后可在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 16:24:02