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

如何解读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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 04:15:02