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

Navigation组件中Fragment因SpecialEffectsController操作泄漏求助

Fragment切换内存泄漏问题分析与解决

问题背景

使用以下依赖版本:

  • Navigation Component 2.9.0
  • androidx Fragments 1.8.6
  • androidx.transitions 1.6.0

已启用Predictive Back和手势导航,在导航图中频繁切换Fragment时,出现LoginFragment内存泄漏,LeakCanary检测日志如下:

┬───
│ GC Root: Thread object
│
├─ java.lang.Thread instance
│    Leaking: NO (the main thread always runs)
│    Thread name: 'main'
│    ↓ Thread.threadLocals
│             ~~~~~~~~~~~~
├─ java.lang.ThreadLocal$ThreadLocalMap instance
│    Leaking: UNKNOWN
│    Retaining 1.5 MB in 17658 objects
│    ↓ ThreadLocal$ThreadLocalMap.table
│                                 ~~~~~
├─ java.lang.ThreadLocal$ThreadLocalMap$Entry[] array
│    Leaking: UNKNOWN
│    Retaining 1.5 MB in 17657 objects
│    ↓ ThreadLocal$ThreadLocalMap$Entry[5]
│                                      ~~~
├─ java.lang.ThreadLocal$ThreadLocalMap$Entry instance
│    Leaking: UNKNOWN
│    Retaining 1.5 MB in 17534 objects
│    ↓ ThreadLocal$ThreadLocalMap$Entry.value
│                                       ~~~~~
├─ androidx.collection.ArrayMap instance
│    Leaking: UNKNOWN
│    Retaining 1.5 MB in 17533 objects
│    ↓ SimpleArrayMap.array
│                     ~~~~~
├─ java.lang.Object[] array
│    Leaking: UNKNOWN
│    Retaining 1.5 MB in 17531 objects
│    ↓ Object[1]
│            ~~~
├─ androidx.transition.Transition$AnimationInfo instance
│    Leaking: UNKNOWN
│    Retaining 32 B in 1 objects
│    ↓ Transition$AnimationInfo.mTransition
│                               ~~~~~~~~~~~
├─ androidx.transition.Slide instance
│    Leaking: UNKNOWN
│    Retaining 371.6 kB in 9596 objects
│    ↓ Transition.mCloneParent
│                 ~~~~~~~~~~~~
├─ androidx.transition.Slide instance
│    Leaking: UNKNOWN
│    Retaining 800 B in 17 objects
│    ↓ Transition.mParent
│                 ~~~~~~~
├─ androidx.transition.TransitionSet instance
│    Leaking: UNKNOWN
│    Retaining 861 B in 18 objects
│    ↓ Transition.mParent
│                 ~~~~~~~
├─ androidx.transition.TransitionSet instance
│    Leaking: UNKNOWN
│    Retaining 1.9 kB in 60 objects
│    ↓ Transition.mListeners
│                 ~~~~~~~~~~
├─ java.util.ArrayList instance
│    Leaking: UNKNOWN
│    Retaining 1.0 kB in 41 objects
│    ↓ ArrayList[1]
│               ~~~
├─ androidx.transition.FragmentTransitionSupport$4 instance
│    Leaking: UNKNOWN
│    Retaining 16 B in 1 objects
│    Anonymous class implementing androidx.transition.
│    Transition$TransitionListener
│    ↓ FragmentTransitionSupport$4.val$transitionCompleteRunnable
│                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ androidx.fragment.app.
│  DefaultSpecialEffectsController$TransitionEffect$$ExternalSyntheticLambda4
│  instance
│    Leaking: UNKNOWN
│    Retaining 16 B in 1 objects
│    ↓
│    DefaultSpecialEffectsController$TransitionEffect$$ExternalSyntheticLambda4.
│    f$0
│    ~~~
├─ androidx.fragment.app.SpecialEffectsController$FragmentStateManagerOperation
│  instance
│    Leaking: UNKNOWN
│    Retaining 237 B in 9 objects
│    ↓ SpecialEffectsController$Operation.fragment
│                                         ~~~~~~~~
╰→ com.app.login.LoginFragment instance
     Leaking: YES (ObjectWatcher was watching this because com.app.
     login.LoginFragment received Fragment#onDestroy() callback.
     Conflicts with Fragment.mLifecycleRegistry.state is INITIALIZED)
     Retaining 8.2 kB in 200 objects
     key = 273e40d7-b2ad-4f91-b6a1-9af85e1c1ffd
     watchDurationMillis = 6300
     retainedDurationMillis = 1300
     componentContext instance of dagger.hilt.android.internal.managers.
     ViewComponentManager$FragmentContextWrapper, wrapping activity com.
     app.MainActivity with mDestroyed = false

METADATA

Build.VERSION.SDK_INT: 34
Build.MANUFACTURER: samsung
LeakCanary version: 2.14
App process name: com.app
Class count: 37912
Instance count: 282042
Primitive array count: 187899
Object array count: 46537
Thread count: 71
Heap total bytes: 38826738
Bitmap count: 6
Bitmap total bytes: 1283848
Large bitmap count: 0
Large bitmap total bytes: 0
Db 1: closed /data/user/0/com.app.
databases/google_app_measurement_local.db
Db 2: open /data/user/0/com.app/databases/com.google.android.
datatransport.events
Db 3: open /data/user/0/com.app/databases/app_db
Stats: LruCache[maxSize=3000,hits=130652,misses=244920,hitRate=34%]
RandomAccess[bytes=12181917,reads=244920,travel=126574568852,range=46016226,size
=57532227]
Analysis duration: 19115 ms

泄漏原因分析

泄漏链显示:主线程ThreadLocal持有Transition相关对象,Transition的监听列表中保留了匿名TransitionListener,该Listener持有关联LoginFragment的Runnable,导致Fragment在onDestroy()后无法被GC回收。这是特定版本下Fragment、Transition组件与Predictive Back组合的已知兼容性问题。

解决方案

1. 升级依赖版本(最优方案)

Google已在后续版本修复该类泄漏,建议升级到以下稳定版本:

  • androidx Fragments:1.8.7及以上
  • androidx.transitions:1.6.1及以上
  • Navigation Component:2.9.1及以上(保持与Fragment版本兼容)

2. 禁用过渡动画(临时方案)

若无法立即升级,可对泄漏频发的导航动作禁用动画,避免Transition持有Fragment引用:

findNavController().navigate(
    R.id.action_login_to_target,
    null,
    NavOptions.Builder()
        .setEnterAnim(0)
        .setExitAnim(0)
        .setPopEnterAnim(0)
        .setPopExitAnim(0)
        .build()
)

3. 手动清理Transition监听(进阶方案)

若需保留动画,可在FragmentonDestroy()中通过反射清理Transition监听(仅作参考,需适配实际场景):

override fun onDestroy() {
    super.onDestroy()
    try {
        val controllerField = javaClass.getDeclaredField("mSpecialEffectsController")
        controllerField.isAccessible = true
        val controller = controllerField.get(this) as? SpecialEffectsController
        controller?.let {
            val transitionsField = it::class.java.getDeclaredField("mTransitions")
            transitionsField.isAccessible = true
            val transitions = transitionsField.get(it) as? List<Transition>
            transitions?.forEach { transition ->
                transition.removeAllListeners()
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

4. 检查Predictive Back适配

确保自定义OnBackPressedCallback使用WeakReference持有Fragment实例,避免强引用导致额外泄漏。


内容的提问来源于stack exchange,提问作者B W

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 02:22:08