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

基于LeakCanary检测结果修复内存泄漏解决App崩溃问题

修复ExoPlayer音频应用中的内存泄漏问题

问题背景

在Android Studio中对使用ExoPlayer播放MP3的应用进行性能分析时,发现应用频繁崩溃,通过LeakCanary检测到内存泄漏,以下是检测报告及修复方案。

LeakCanary检测日志

┬───
│ GC根节点:Native代码中的输入或输出参数
│
├─ java.util.TaskQueue实例
│    泄漏状态:未知
│    保留528 B,涉及2个对象
│    ↓ TaskQueue.queue
│                ~~~~~
├─ java.util.TimerTask[]数组
│    泄漏状态:未知
│    保留512 B,涉及1个对象
│    ↓ TimerTask[0]
│               ~~~
├─ com.github.islamkhsh.CardSliderViewPager$SlidingTask实例
│    泄漏状态:未知
│    保留2.0 MB,涉及13670个对象
│    ↓ CardSliderViewPager$SlidingTask.this$0
│                                      ~~~~~~
├─ com.github.islamkhsh.CardSliderViewPager实例
│    泄漏状态:未知
│    保留2.0 MB,涉及13668个对象
│    View未属于窗口视图层级
│    View.mAttachInfo为null(视图已分离)
│    View.mID = R.id.viewPager
│    View.mWindowAttachCount = 1
│    mContext为com.pkasemer.mwonyaa.RootActivity实例,mDestroyed = false
│    ↓ View.mParent
│           ~~~~~~~
├─ androidx.constraintlayout.widget.ConstraintLayout实例
│    泄漏状态:未知
│    保留1.6 MB,涉及11594个对象
│    View未属于窗口视图层级
│    View.mAttachInfo为null(视图已分离)
│    View.mID = R.id.playerView
│    View.mWindowAttachCount = 1
│    mContext为com.pkasemer.mwonyaa.RootActivity实例,mDestroyed = false
│    ↓ View.mParent
│           ~~~~~~~
├─ androidx.recyclerview.widget.RecyclerView实例
│    泄漏状态:未知
│    保留1.5 MB,涉及10856个对象
│    View未属于窗口视图层级
│    View.mAttachInfo为null(视图已分离)
│    View.mID = R.id.main_recycler
│    View.mWindowAttachCount = 1
│    mContext为com.pkasemer.mwonyaa.RootActivity实例,mDestroyed = false
│    ↓ View.mParent
│           ~~~~~~~
├─ androidx.swiperefreshlayout.widget.SwipeRefreshLayout实例
│    泄漏状态:未知
│    保留3.9 kB,涉及81个对象
│    View未属于窗口视图层级
│    View.mAttachInfo为null(视图已分离)
│    View.mID = R.id.main_swiperefresh
│    View.mWindowAttachCount = 1
│    mContext为com.pkasemer.mwonyaa.RootActivity实例,mDestroyed = false
│    ↓ View.mParent
│           ~~~~~~~
╰→ android.widget.FrameLayout实例
​     泄漏状态:是(ObjectWatcher监控到该对象,因为com.pkasemer.mwonyaa.Fragments.Search收到Fragment#onDestroyView()回调,应清除视图引用以防止泄漏)
​     保留2.0 kB,涉及45个对象
​     key = fe8776a5-8c6c-452c-86ea-f07c94c6c300
​     watchDurationMillis = 19352
​     retainedDurationMillis = 14352
​     View未属于窗口视图层级
​     View.mAttachInfo为null(视图已分离)
​     View.mWindowAttachCount = 1
​     mContext为com.pkasemer.mwonyaa.RootActivity实例,mDestroyed = false

元数据

  • Build.VERSION.SDK_INT: 29
  • Build.MANUFACTURER: INFINIX MOBILITY LIMITED
  • LeakCanary版本: 2.10
  • 应用进程名称: com.pkasemer.mwonyaa
  • 类数量: 16415
  • 实例数量: 216004
  • 基本类型数组数量: 122153
  • 对象数组数量: 34818
  • 线程数量: 53
  • 堆总字节数: 23265560
  • Bitmap数量: 62
  • Bitmap总字节数: 17227346
  • 大Bitmap数量: 1
  • 大Bitmap总字节数: 6553601
  • 数据库状态:
    • 已打开: /data/user/0/com.pkasemer.mwonyaa/databases/Mwonya(3个实例)
    • 已打开: /data/user/0/com.pkasemer.mwonyaa/no_backup/androidx.work.workdb
    • 已打开: /data/user/0/com.pkasemer.mwonyaa/databases/com.google.android.datatransport.events
    • 已关闭: /data/user/0/com.pkasemer.mwonyaa/databases/google_app_measurement_local.db
  • 统计信息: LruCache[maxSize=3000,hits=69898,misses=182744,hitRate=27%]
  • 随机访问[bytes=10784339,reads=182744,travel=58867131075,range=27352144,size=34355609]
  • 分析时长: 91735 ms

修复方案

1. 解决CardSliderViewPager的TimerTask泄漏

从日志链可以看出,泄漏的根源是CardSliderViewPager$SlidingTask(TimerTask子类)持有ViewPager的强引用,而ViewPager所在Fragment已执行onDestroyView,但TimerTask仍被Native层TaskQueue引用,导致整个视图树无法被GC回收。

  • 自定义CardSliderViewPager场景:在View的onDetachedFromWindow方法中停止Timer并清除引用:
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (timer != null) {
            timer.cancel(); // 取消所有待执行任务
            timer.purge(); // 移除已取消任务
            timer = null;
        }
    }
    
  • 第三方库CardSliderViewPager场景:
    • 优先检查库是否提供停止自动滑动的API,在SearchFragment的onDestroyView中调用;
    • 若无公开API,通过反射获取Timer实例并取消:
      @Override
      public void onDestroyView() {
          super.onDestroyView();
          CardSliderViewPager viewPager = view.findViewById(R.id.viewPager);
          try {
              Field timerField = CardSliderViewPager.class.getDeclaredField("timer");
              timerField.setAccessible(true);
              Timer timer = (Timer) timerField.get(viewPager);
              if (timer != null) {
                  timer.cancel();
                  timer.purge();
              }
          } catch (NoSuchFieldException | IllegalAccessException e) {
              e.printStackTrace();
          }
      }
      

2. 清理Fragment的视图引用

在SearchFragment的onDestroyView方法中,主动清除所有视图变量引用,避免Fragment残留引用导致泄漏:

@Override
public void onDestroyView() {
    super.onDestroyView();
    // 清空所有视图对象引用
    mainRecycler = null;
    mainSwipeRefresh = null;
    playerView = null;
    viewPager = null;
}

3. 确保ExoPlayer资源正确释放

虽本次泄漏直接诱因不是ExoPlayer,但需在Fragment/Activity销毁时释放资源,避免潜在泄漏:

@Override
public void onDestroy() {
    super.onDestroy();
    if (exoPlayer != null) {
        exoPlayer.release();
        exoPlayer = null;
    }
}

4. 优化数据库连接管理

元数据显示多个Mwonya数据库实例被打开,需通过单例模式管理数据库,避免重复创建实例,且在操作完成后关闭连接:

  • 实现数据库单例类,确保全局仅一个实例;
  • 数据库操作完成后,调用close()方法关闭连接(注意不要在仍有未完成操作时关闭)。

内容的提问来源于stack exchange,提问作者PATRICK OKELLO

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 14:10:26