我决定现在是时候学习如何使用Leak Canary来检测我的应用程序中的泄漏,并且像往常一样,我试图在我的项目中实现它,以真正了解如何使用该工具。实现它很容易,困难的部分是阅读工具扔给我的东西。 我有一个滚动视图似乎在内存管理器中累积内存,因为我向上和向下滚动(即使它没有加载任何新数据)所以我认为这是一个很好的候选对象来跟踪泄漏,这是结果:
看起来v7.widget.RecyclerView正在泄漏适配器,而不是我的应用程序。但这不可能......对吧?
以下是适配器和使用它的类的代码: https://gist.github.com/feresr/a53c7b68145d6414c40ec70b3b842f1e
我开始对这个问题表示赏心悦目,因为它在完全不同的应用程序两年后重新出现了
答案 0 :(得分:24)
如果适配器的使用寿命超过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();
}
答案 1 :(得分:9)
我能够通过覆盖RecyclerView来解决这个问题。之所以发生这种情况,是因为RecyclerView永远不会从AdapterDataObservable中取消注册。
@Override protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (getAdapter() != null) {
setAdapter(null);
}
}
答案 2 :(得分:5)
首先,我引用this file。
看起来v7.widget.RecyclerView正在泄漏适配器,而不是我的应用程序。但这不是正确的......对吧?
实际上你的适配器漏掉了RecyclerView
(并且通过跟踪图和LeakCanary的标题非常明确了它活动)。但是,我不确定它是否是父母" RecyclerView或HourlyViewHolder中的嵌套版本,或两者兼而有之。
我认为罪魁祸首是你的ViewHolders。通过使它们成为非静态内部类,您明确地为它们提供了对封闭适配器类的引用,这几乎直接将适配器与循环视图耦合,因为持有者中每个itemView
的父级都是RecyclerView本身。
我解决这个问题的第一个建议是通过使它们成为静态内部类来解耦你的ViewHolders和Adapter。这样他们就不会对适配器进行引用,因此您的上下文字段将无法访问,这也是一件好事,因为上下文引用应该谨慎传递和存储(还要避免大内存泄漏)。当您需要上下文来获取字符串时,请在其他位置执行此操作,例如在适配器构造函数中,但不要将上下文存储为成员。最后,DayForecastAdapter
似乎也很危险:你将它的同一个实例传递给每个HourlyViewHolder
,这似乎是一个错误。
我认为修复设计并解耦这些类应该摆脱这种内存泄漏
答案 3 :(得分:0)
我无法打开您的图片并查看实际泄漏,但如果您在RecyclerView
中为Fragment
定义了一个本地变量,并设置了您的片段{{1} } retainInstanceState
它可能会导致旋转泄漏。
将true
与Fragment
一起使用时,您应该清除retainInstance
onDestroyView
您可以在此处找到此链接的详细信息: Retained Fragments with UI and memory leaks