添加Android Jetpack Lifecycle Observer后出现内存泄漏,求解决思路
嘿,看你碰到了添加Lifecycle Observer后出现的内存泄漏问题,从LeakCanary给出的泄漏追踪信息来看,核心是已销毁的MainActivity被系统InputMethodManager相关链持有,最终导致NavHostFragment无法被GC回收。咱们一步步梳理解决思路:
LeakingInstance(referenceKey=3f842e15-cca3-465a-8345-3a241c8befca, referenceName=, instanceClassName=androidx.navigation.fragment.NavHostFragment, watchDurationMillis=561193, excludedLeak=false, leakTrace=
┬
├─ android.view.inputmethod.InputMethodManager$1
│ 是否泄漏:否(它是GC根节点)
│ com.android.internal.view.IInputMethodClient$Stub的匿名子类
│ ↓ InputMethodManager$1.this$0
│ ~~~~~~
├─ android.view.inputmethod.InputMethodManager
│ 是否泄漏:未知
│ ↓ InputMethodManager.mImeInsetsConsumer
│ ~~~~~~~~~~~~~~~~~~
├─ android.view.ImeInsetsSourceConsumer
│ 是否泄漏:未知
│ ↓ ImeInsetsSourceConsumer.mController
│ ~~~~~~~~~~~
├─ android.view.InsetsController
│ 是否泄漏:未知
│ ↓ InsetsController.mViewRoot
│ ~~~~~~~~~
├─ android.view.ViewRootImpl
│ 是否泄漏:未知
│ ↓ ViewRootImpl.mActivityConfigCallback
│ ~~~~~~~~~~~~~~~~~~~~~~~
├─ android.app.-$$Lambda$ActivityThread$ActivityClientRecord$HOrG1qglSjSUHSjKBn2rXtX0gGg
│ 是否泄漏:未知
│ ↓ -$$Lambda$ActivityThread$ActivityClientRecord$HOrG1qglSjSUHSjKBn2rXtX0gGg.f$0
│ ~~~
├─ android.app.ActivityThread$ActivityClientRecord
│ 是否泄漏:未知
│ ↓ ActivityThread$ActivityClientRecord.activity
│ ~~~~~~~~
├─ com.x.x.MainActivity
│ 是否泄漏:是(Activity#mDestroyed为true)
│ ↓ MainActivity.mFragments
├─ androidx.fragment.app.FragmentController
│ 是否泄漏:是(MainActivity↑存在泄漏)
│ ↓ FragmentController.mHost
├─ androidx.fragment.app.FragmentActivity$HostCallbacks
│ 是否泄漏:是(FragmentController↑存在泄漏)
│ ↓ FragmentActivity$HostCallbacks.mFragmentManager
├─ androidx.fragment.app.FragmentManagerImpl
│ 是否泄漏:是(FragmentActivity$HostCallbacks↑存在泄漏)
│ ↓ FragmentManagerImpl.mPrimaryNav
╰→ androidx.navigation.fragment.NavHostFragment
是否泄漏:是(Fragment#mFragmentManager为null) , retainedHeapSize=null)
先拆解泄漏核心逻辑
从追踪链能看出来:
- GC根是系统的
InputMethodManager(本身不会泄漏),但它通过Insets相关的对象链,意外持有了已经标记为destroyed的MainActivity; - MainActivity又关联着FragmentController、FragmentManager,最终导致本该被回收的
NavHostFragment(它的mFragmentManager已经为null,说明FragmentManager已经尝试移除它)被强引用挂住,无法被GC回收。
具体解决思路
一、优先排查Lifecycle Observer的注册问题
这是你提到的新增Observer后出现的问题,大概率是Observer的引用方式出了问题:
- 绑定正确的生命周期:如果Observer是服务于Fragment的,一定要用
getViewLifecycleOwner().getLifecycle().addObserver(observer),而不是直接用Activity的Lifecycle,这样Observer会随Fragment视图生命周期自动解绑; - 手动移除Observer:如果是自定义的Observer,或者用了全局Lifecycle注册,一定要在组件的
onDestroy(Fragment用onDestroyView)中调用lifecycle.removeObserver(observer); - 用弱引用持有组件:如果Observer必须访问Activity/Fragment,把强引用改成
WeakReference,避免把组件死死挂住:
class MyObserver(private val activityRef: WeakReference<MainActivity>) : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume() { activityRef.get()?.let { activity -> // 执行操作,用完就释放引用 } } }
二、清理NavHostFragment的关联引用
- 移除导航回调:如果你给NavController加了
OnDestinationChangedListener或者其他监听,记得在Activity的onDestroy中手动移除:
override fun onDestroy() { navController?.removeOnDestinationChangedListener(destinationListener) super.onDestroy() }
- 检查ViewModel引用:如果NavHostFragment关联的ViewModel持有了Activity/Fragment的强引用,要么改成弱引用,要么让ViewModel只持有Application上下文。
三、解决InputMethodManager的间接泄漏
这是系统层的常见场景,Activity销毁时软键盘未正确关闭可能导致这类引用残留:
- 在MainActivity的
onDestroy中手动隐藏软键盘:
override fun onDestroy() { val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager currentFocus?.let { view -> imm.hideSoftInputFromWindow(view.windowToken, InputMethodManager.HIDE_NOT_ALWAYS) } super.onDestroy() }
- 检查Insets相关回调:如果你给View设置了
OnApplyWindowInsetsListener,确保回调没有持有Activity的强引用,或者在销毁时移除监听。
四、验证泄漏是否修复
用LeakCanary重新运行测试,或者通过Android Studio的Memory Profiler:
- 触发Activity销毁流程;
- 手动触发GC(点击Profiler中的垃圾桶图标);
- 搜索
NavHostFragment和MainActivity,看是否还有残留实例。
内容的提问来源于stack exchange,提问作者der_Fidelis

