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

使用Navigation组件的Activity启动新Root Activity后内存泄漏

解决Activity跳转时的内存泄漏问题

问题背景

应用以Activity A启动,该Activity包含一个FragmentContainer,布局代码如下:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mainView"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/register_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:defaultNavHost="true"
        tools:layout="@layout/fragment_login"
        tools:ignore="FragmentTagUsage" />
</androidx.constraintlayout.widget.ConstraintLayout>

Activity A中导航组件的初始化代码:

private var navHostFragment: NavHostFragment? = null
private var navController: NavController? = null
private fun setNavGraph() {
    navHostFragment = supportFragmentManager.findFragmentById(R.id.register_nav_host_fragment) as NavHostFragment            
    navController = navHostFragment?.navController
    val graph = navController?.navInflater?.inflate(R.navigation.registration_process_graph)
    navController?.setGraph(graph!!, intent.extras)
}

当启动新的Root Activity(DashboardActivity)时出现内存泄漏,启动代码如下:

val intent = Intent(this, DashboardActivity::class.java)
intent.putExtra(AppConstants.EXTRA_MODEL, getViewModel().getDashboardResponse())
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
releaseInstance()
startActivity(intent)

内存泄漏日志(来自LeakCanary):

┬───
│ GC Root: System class
│
├─ android.transition.TransitionManager class
│    Leaking: NO (a class is never leaking)
│    ↓ static TransitionManager.sPendingTransitions
│                               ~~~~~~~~~~~~~~~~~~~
├─ java.util.ArrayList instance
│    Leaking: UNKNOWN
│    Retaining 294.4 kB in 5955 objects
│    ↓ ArrayList[0]
│               ~~~
├─ android.widget.FrameLayout instance
│    Leaking: YES (View.mContext references a destroyed activity)
│    Retaining 89.6 kB in 1750 objects
│    View is part of a window view hierarchy
│    View.mAttachInfo is null (view detached)
│    View.mID = R.id.null
│    View.mWindowAttachCount = 0
│    mContext instance of android.view.ContextThemeWrapper, wrapping activity
│    com.demo.bm.auth.ui.AuthActivity with mDestroyed = true
│    ↓ View.mContext
├─ android.view.ContextThemeWrapper instance
│    Leaking: YES (FrameLayout↑ is leaking and ContextThemeWrapper wraps an
│    Activity with Activity.mDestroyed true)
│    Retaining 48 B in 2 objects
│    mBase instance of com.demo.bm.auth.ui.AuthActivity with mDestroyed =
│    true
│    ↓ ContextWrapper.mBase
╰→ com.demo.bm.auth.ui.AuthActivity instance
​     Leaking: YES (ObjectWatcher was watching this because com.demo.bm.
​     auth.ui.AuthActivity received Activity#onDestroy() callback and
​     Activity#mDestroyed is true)
​     Retaining 30.6 kB in 971 objects
​     key = 9dec9a77-29b1-46a6-a8ef-143f2811a4d1
​     watchDurationMillis = 5590
​     retainedDurationMillis = 575
​     mApplication instance of com.demo.bm.demoApp
​     mBase instance of androidx.appcompat.view.ContextThemeWrapper

已尝试的无效方案:跳转前释放局部变量并移除子视图,但问题未解决:

navHostFragment = null
navController = null
val navView = findViewById<FrameLayout>(R.id.register_nav_host_fragment)
(navView.parent as ViewManager).removeView(navView)

解决方案

从泄漏日志可以看出,问题根源是TransitionManager的静态字段sPendingTransitions持有了一个FrameLayout实例,而该实例引用了已销毁的Activity A的Context,导致内存泄漏。以下是针对性的解决步骤:

1. 清理TransitionManager的Pending Transitions

在Activity A的onDestroy方法中,通过反射清空静态的sPendingTransitions列表:

override fun onDestroy() {
    super.onDestroy()
    // 清理TransitionManager中待处理的过渡任务
    try {
        val field = TransitionManager::class.java.getDeclaredField("sPendingTransitions")
        field.isAccessible = true
        val pendingTransitions = field.get(null) as ArrayList<*>
        pendingTransitions.clear()
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

2. 正确清理导航组件资源

除了置空变量,还需要主动移除NavHostFragment并清理NavController:

override fun onDestroy() {
    // 弹出导航栈并清空控制器
    navController?.popBackStack()
    navController = null
    // 移除NavHostFragment
    supportFragmentManager.findFragmentById(R.id.register_nav_host_fragment)?.let {
        supportFragmentManager.beginTransaction().remove(it).commitAllowingStateLoss()
    }
    navHostFragment = null
    super.onDestroy()
}

3. 终止所有未完成的过渡动画

如果Activity或Fragment中使用了过渡动画,确保在销毁前结束所有未完成的动画:

override fun onDestroy() {
    // 结束所有视图的过渡动画
    TransitionManager.endTransitions(window.decorView)
    window.decorView.transitionName = null
    super.onDestroy()
}

总结

将以上三个步骤结合,即可解除TransitionManager对Activity A的引用,解决内存泄漏问题。

内容的提问来源于stack exchange,提问作者Abdalla Maged

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 19:00:23