如何通过LeakCanary日志定位RewardFragment内存泄漏的具体代码行
如何定位RewardFragment内存泄漏的具体代码位置?
先看LeakCanary给出的泄漏追踪链:
ApplicationLeak(className=com.pilotflyingj.plugin.a_android.ui.component.loyalty.RewardFragment, leakTrace= ┬ ├─ android.view.inputmethod.InputMethodManager │ Leaking: NO (InputMethodManager↓ is not leaking and a class is never leaking) │ GC Root: System class │ ↓ static InputMethodManager.sInstance ├─ android.view.inputmethod.InputMethodManager │ Leaking: NO (DecorView↓ is not leaking and InputMethodManager is a singleton) │ ↓ InputMethodManager.mNextServedView ├─ com.android.internal.policy.DecorView │ Leaking: NO (View attached) │ mContext instance of com.android.internal.policy.DecorContext, wrapping activity com.pilotflyingj.plugin.a_android.ui.component.main.MainFrameActivity with mDestroyed = false │ Parent android.view.ViewRootImpl not a android.view.View │ View#mParent is set │ View#mAttachInfo is not null (view attached) │ View.mWindowAttachCount = 1 │ ↓ DecorView.mAttachInfo │ ~~~~~~~~~~~ ├─ android.view.View$AttachInfo │ Leaking: UNKNOWN │ ↓ View$AttachInfo.mTreeObserver │ ~~~~~~~~~~~~~ ├─ android.view.ViewTreeObserver │ Leaking: UNKNOWN │ ↓ ViewTreeObserver.mOnScrollChangedListeners │ ~~~~~~~~~~~~~~~~~~~~~~~~~ ├─ android.view.ViewTreeObserver$CopyOnWriteArray │ Leaking: UNKNOWN │ ↓ ViewTreeObserver$CopyOnWriteArray.mData │ ~~~~~ ├─ java.util.ArrayList │ Leaking: UNKNOWN │ ↓ ArrayList.elementData │ ~~~~~~~~~~~ ├─ java.lang.Object[] │ Leaking: UNKNOWN │ ↓ array Object[].[2] │ ~~~ ├─ com.pilotflyingj.plugin.a_android.ui.component.loyalty.-$$Lambda$RewardFragment$-e5xQ8M432-uGOrhL3ajFrvOVQg │ Leaking: UNKNOWN │ ↓ -$$Lambda$RewardFragment$-e5xQ8M432-uGOrhL3ajFrvOVQg.f$0 │ ~~~ ╰→ com.pilotflyingj.plugin.a_android.ui.component.loyalty.RewardFragment Leaking: YES (Fragment#mFragmentManager is null and ObjectWatcher was watching this) key = 11be43e3-0bcf-42fb-a087-025e52576844 watchDurationMillis = 8225 retainedDurationMillis = 3220 , retainedHeapByteSize=8730107)
从这个追踪链能明确泄漏核心:你的RewardFragment的一个Lambda实例被ViewTreeObserver的滚动监听列表持有,而这个监听链最终关联到了InputMethodManager系统单例,导致Fragment无法被GC回收。
接下来按这几步定位具体代码并修复:
第一步:找到Fragment中注册滚动监听的代码
搜索RewardFragment里的viewTreeObserver.addOnScrollChangedListener相关调用,重点找Lambda形式的实现(就是trace里那个自动生成的-$$Lambda$RewardFragment$-e5xQ8M432-uGOrhL3ajFrvOVQg对应的逻辑)。
注意:这个监听可能不是直接在Fragment根View上注册的,也可能是在子View(比如RecyclerView、ScrollView、自定义滚动容器)的ViewTreeObserver上添加的。第二步:检查监听的移除逻辑是否完整
问题大概率出在「只注册了监听,没在合适的生命周期移除」:- 如果你用的是匿名Lambda注册监听(比如下方示例),每次创建的Lambda都是新实例,后续根本无法准确移除,必须把Lambda赋值给Fragment的成员变量:
// 错误写法:匿名Lambda无法被移除 someView.viewTreeObserver.addOnScrollChangedListener { // 处理滚动逻辑 } // 正确写法:将Lambda赋值给成员变量 private val scrollChangeListener = ViewTreeObserver.OnScrollChangedListener { // 你的滚动处理逻辑 } - 然后在Fragment的
onDestroyView(推荐,因为View在这里销毁)或者onDestroy方法中,移除这个监听:override fun onDestroyView() { super.onDestroyView() // 先判断ViewTreeObserver是否存活,避免空指针 if (someView.viewTreeObserver.isAlive) { someView.viewTreeObserver.removeOnScrollChangedListener(scrollChangeListener) } }
- 如果你用的是匿名Lambda注册监听(比如下方示例),每次创建的Lambda都是新实例,后续根本无法准确移除,必须把Lambda赋值给Fragment的成员变量:
第三步:验证修复效果
修复后重新运行App,触发Fragment的销毁流程(比如切换到其他页面、从返回栈弹出),然后:- 观察LeakCanary是否还会报告该泄漏;
- 用Android Studio的Profiler工具,手动触发GC,查看内存堆中是否还有
RewardFragment的残留实例。
额外提醒:除了滚动监听,还要检查Fragment中是否有其他注册到全局单例、系统服务的监听/回调,确保在Fragment生命周期结束时都能及时解绑,避免类似的泄漏问题。
内容的提问来源于stack exchange,提问作者bimal chawla
相关产品推荐
相关产品推荐

