【问题标题】:Leak canary, Recyclerview leaking mAdapter泄漏金丝雀,Recyclerview 泄漏 mAdapter
【发布时间】:2016-06-01 22:47:12
【问题描述】:

我决定是时候学习如何使用 Leak Canary 来检测我的应用程序中的泄漏了,并且像往常一样,我尝试在我的项目中实施它以真正了解如何使用该工具。实现它很容易,困难的部分是阅读该工具向我抛出的内容。 我有一个滚动视图,当我上下滚动时,它似乎在内存管理器中累积内存(即使它没有加载任何新数据),所以我认为这是一个很好的候选对象来跟踪泄漏,结果如下:

看起来 v7.widget.RecyclerView 正在泄漏适配器,而不是我的应用程序。但这不可能是对的……对吧?

这是适配器和使用它的类的代码: https://gist.github.com/feresr/a53c7b68145d6414c40ec70b3b842f1e

我开始为这个问题悬赏,因为它在两年后在一个完全不同的应用程序上重新出现

【问题讨论】:

  • 看起来您正在传递应用程序上下文,而您可能应该使用 RecyclerView 的上下文或您的活动上下文。应用程序上下文是长期存在的,这会阻止收集。
  • GapWorker 是执行预取的静态类。当 RecyclerView 被销毁时,它会在 onDetachedFromWindow() 中正确地从 GapWorker 中注销自己。您是否在自定义 RecyclerView 中覆盖了 onDetachedFromWindow() 并忘记调用 super.onDetachedFromWindow() ?

标签: android memory-leaks leakcanary


【解决方案1】:

如果适配器的寿命比RecyclerView 长,您必须清除onDestroyView 中的适配器引用:

@Override
public void onDestroyView() {
    recyclerView.setAdapter(null);
    super.onDestroyView();
}

否则适配器将持有对应该已经耗尽内存的RecyclerView 的引用。

如果屏幕涉及到过渡动画,你实际上必须更进一步,只有在视图变得分离时才清除适配器:

@Override
public void onDestroyView() {
    recyclerView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
        @Override
        public void onViewAttachedToWindow(View v) {
            // no-op
        }

        @Override
        public void onViewDetachedFromWindow(View v) {
            recyclerView.setAdapter(null);
        }
    });
    super.onDestroyView();
}

【讨论】:

  • 这就是我最终所做的,实际上解决了这个问题。我在其他一些 SO 问题中看到了同样的答案。我想了解为什么会这样。参考链应该是 Fragment -> Recyclerview -> Adapter。所以,如果片段停止引用 Recyclerview,recyclerview 和它的适配器都应该被垃圾回收。
  • 当您使用RecyclerView.setAdapterRecyclerView 设置适配器时,该适配器会将RecyclerView 注册为AdapterDataObserver 并在内部列表中保存对它的引用。如果适配器的寿命比RecyclerView 长(例如,如果您在FragmentonCreate 中定义适配器就是这种情况),它将持有对应该被GC 处理的RecyclerView 的引用反而。调用 recyclerView.setAdapter(null) 会取消注册该适配器的 RecyclerView
  • 如果我在动画屏幕应该怎么办?
  • 我自己也遇到过这个问题。在这种情况下,您要确保在视图分离之后才清除适配器。
  • 我已经更新了答案,包括处理屏幕动画的情况。
【解决方案2】:

我可以通过覆盖 RecyclerView 来解决这个问题。这是因为 RecyclerView 永远不会从 AdapterDataObservable 注销自己。

@Override protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (getAdapter() != null) {
        setAdapter(null);
    }
}

【讨论】:

  • 这个问题在一个全新的应用程序中再次出现,这仍然是解决它的最佳方法吗?
  • @feresr 它从未消失 :) 自 android 21 以来我仍然使用相同的 HackyRecyclerView
  • 哈哈很高兴知道我不是唯一一个面临这个问题的人。我目前正在做 onDestroyView() { recyclerView.adapter = null }。这也解决了这个问题,但我想首先知道是什么原因造成的。
  • 难以置信。我们还需要修补这个吗?有任何 Google 开发人员对此发表过评论吗?
  • @Bolein95,你有 kotlin 解决方案吗?
【解决方案3】:

首先,我引用的是this file

看起来 v7.widget.RecyclerView 正在泄漏适配器,而不是我的应用程序。但这不可能是对的……对吧?

实际上是您的适配器泄漏了RecyclerView(跟踪图和 LeakCanary 活动的标题清楚地表明了这一点)。但是,我不确定它是“父” RecyclerView 还是 HourlyViewHolder 中的嵌套视图,或者两者兼而有之。 我认为罪魁祸首是你的 ViewHolders。通过使它们成为非静态内部类,您可以显式地为它们提供对封闭适配器类的引用,这几乎直接将适配器与回收的视图耦合在一起,因为您的持有者中每个 itemView 的父级都是 RecyclerView 本身。

我解决这个问题的第一个建议是通过将 ViewHolders 和 Adapter 设为 static 内部类来解耦它们。这样他们就没有对适配器的引用,因此他们将无法访问您的 context 字段,这也是一件好事,因为应该谨慎地传递和存储上下文引用(也避免大内存泄漏)。当您只需要上下文来获取字符串时,请在其他地方进行,例如在适配器构造函数中,但不要将上下文存储为成员。最后,DayForecastAdapter 似乎也很危险:你将它的同一个实例传递给每个HourlyViewHolder,这似乎是一个错误。

我认为修复设计和解耦这些类应该可以消除这种内存泄漏

【讨论】:

  • 谢谢!我现在正在尝试这个!,我也会避免在每个 HourlyViewHolder 上传递相同的 DayForecastAdapter 实例
  • 不走运,结果相同...如果您切换到分支“forecastadapter-leak”,您将看到我应用的更改。不幸的是,错误仍然存​​在。不过感谢您的提示,将 VH 设为静态是个好主意。
  • 看看你的分支,我真的认为你应该创建 DayForecastAdapter 的每个持有者实例,并在第 157 行而不是 labelViewHolder.recyclerView.setAdapter(dayForecastAdapter) 做类似 labelViewHolder.recyclerView.adapter .setData(...) 也应该调用适配器上的 notifyDataSetChanged() 方法。因为你的 dayForecastAdapter 是每个 hourlyViewHolder 的,ForecastAdapter 和 dayForecastAdapter 的 viewHolder 以一种奇怪的方式耦合,也许这就是问题的根源?
  • 感谢您抽出宝贵的时间来实际查看代码。我今天一下班就会检查一下,告诉你进展如何。
  • 又不走运了,由于某种原因,泄漏量始终为 196B,并且泄漏发生时无需旋转屏幕(滚动通过 recyclerview 就可以了),我有点迷路了。 .. 我会尝试将所有 ViewHolders 提取到单独的类中,以使这个神级类更具可读性,看看我是否能以这种方式找到问题
【解决方案4】:

我无法打开您的图像并查看实际泄漏,但如果您在 Fragment 中为您的 RecyclerView 定义一个局部变量并设置片段的 retainInstanceState true 它可能会导致旋转泄漏。

当使用FragmentretainInstance 时,您应该清除onDestroyView 中的所有用户界面引用

@Override
public void onDestroyView() {
     yourRecyclerView = null;
     super.onDestroyView();
}

您可以在此链接中找到详细信息: Retained Fragments with UI and memory leaks

【讨论】:

    【解决方案5】:

    所以,这可能是一个有点矫枉过正的解决方案,但我们的团队已经厌倦了不得不担心每个 RecyclerView 适配器泄漏。

    这是一个抽象的 RecyclerView.Adapter 子类,它一劳永逸地解决泄漏问题。

    abstract class AbstractRecyclerViewAdapter<VH : RecyclerView.ViewHolder>(fragment: Fragment) :
        RecyclerView.Adapter<VH>() {
        private val fragmentRef = WeakReference(fragment)
    
        override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
            super.onAttachedToRecyclerView(recyclerView)
            setupLifecycleObserver(recyclerView)
        }
    
        private fun setupLifecycleObserver(recyclerView: RecyclerView) {
            val fragment = fragmentRef.get() ?: return
            val weakThis = WeakReference(this)
            val weakRecyclerView = WeakReference(recyclerView)
    
            // Observe the fragment's lifecycle events in order
            // to set the Recyclerview's Adapter when the Fragment is resumed
            // and unset it when the Fragment is destroyed.
            fragment.viewLifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
                override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
                    val actualRecyclerView = weakRecyclerView.get() ?: return
                    when (event.targetState) {
                        Lifecycle.State.DESTROYED -> actualRecyclerView.adapter = null
                        Lifecycle.State.RESUMED -> {
                            val self = weakThis.get() ?: return
                            if (actualRecyclerView.adapter != self) {
                                actualRecyclerView.adapter = self
                            }
                        }
                        else -> {
                        }
                    }
                }
            })
        }
    }
    

    基本上,Adapter 会观察包含它的 Fragment 的生命周期事件,并负责将自身设置和取消设置为 RecyclerView 的适配器。

    您只需在自己的适配器中继承AbstractRecyclerViewAdapter,并在构建它们时提供容器片段。

    用法:

    你的适配器

    class MyAdapter(fragment: Fragment): AbstractRecyclerViewAdapter<MyViewHolder>(fragment) {
        // Your usual adapter code...
    }
    

    你的片段

    class MyFragment : Fragment {
        // Instantiate with the fragment.
        private val myAdapter = MyAdapter(this)
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            // Set the RecyclerView adapter as you would normally.
            myRecyclerView.adapter = myAdapter
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多