使用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
相关产品推荐
相关产品推荐

