泄漏金丝雀,Recyclerview 泄漏 mAdapter
Posted
技术标签:
【中文标题】泄漏金丝雀,Recyclerview 泄漏 mAdapter【英文标题】:Leak canary, Recyclerview leaking 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() ? 【参考方案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.setAdapter
为RecyclerView
设置适配器时,该适配器会将RecyclerView
注册为AdapterDataObserver
并在内部列表中保存对它的引用。如果适配器的寿命比RecyclerView
长(例如,如果您在Fragment
的onCreate
中定义适配器就是这种情况),它将持有对应该被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
它可能会导致旋转泄漏。
当使用Fragment
和retainInstance
时,您应该清除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
【讨论】:
以上是关于泄漏金丝雀,Recyclerview 泄漏 mAdapter的主要内容,如果未能解决你的问题,请参考以下文章