如何解读LeakCanary内存泄漏堆栈信息并修复对应问题
内存泄漏堆栈分析与修复方案
如何阅读LeakCanary堆栈信息
LeakCanary输出的堆栈是从GC根(不会被回收的对象)到泄漏对象的完整引用链,阅读逻辑:
- 从顶部GC根开始向下看,最终指向的标注Leaking: YES的对象就是泄漏目标(通常是已经销毁的Activity/Fragment)
- 中间的引用链路就是导致泄漏对象无法被GC回收的原因
第一个泄漏:ResultVer2Activity 泄漏
泄漏原因
引用链逻辑:主线程Looper的线程本地变量持有AnimationHandler → AnimationHandler持有未结束的ValueAnimator → 动画监听持有ViewPropertyAnimator → 持有ID为R.id.iv_energy的AppCompatImageView → ImageView持有已经调用过onDestroy的ResultVer2Activity。
本质是页面销毁后,iv_energy关联的属性动画还在运行,持有页面引用无法释放。
修复方案
在ResultVer2Activity的onDestroy方法中主动取消关联动画:
@Override protected void onDestroy() { // 取消iv_energy的所有属性动画 ivEnergy.animate().cancel(); // 如果是自定义的ValueAnimator,也要主动取消并移除监听 // customAnimator.removeAllListeners(); // customAnimator.cancel(); super.onDestroy(); }
第二个泄漏:ExamEntryActivity 泄漏
泄漏原因
引用链逻辑:Native层全局变量持有Toast的TN实例 → TN持有ToastPresenter → ToastPresenter持有已经销毁的ExamEntryActivity作为Context。
本质是创建Toast时传入了Activity作为Context,而Toast的TN实例是全局持有的,会导致Activity引用被长期持有无法释放。
修复方案
所有Toast创建统一使用Application的Context,不要传入Activity实例:
// 错误写法 Toast.makeText(ExamEntryActivity.this, "提示内容", Toast.LENGTH_SHORT).show(); // 正确写法 Toast.makeText(getApplicationContext(), "提示内容", Toast.LENGTH_SHORT).show();
如果需要和页面生命周期绑定的提示,优先使用Snackbar代替Toast,Snackbar会跟随页面销毁自动释放资源。
通用排查注意事项
- 所有动画、异步任务、定时器、轮询逻辑,都要在页面销毁时主动取消、移除监听
- 单例、全局工具类不要持有Activity/Fragment等短生命周期对象的引用,优先使用ApplicationContext
- 自定义回调、监听如果被长生命周期对象持有,要在对应的生命周期节点主动置空
内容的提问来源于stack exchange,提问作者c-an
相关产品推荐
相关产品推荐

