47

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

在此处输入图像描述

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

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

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

4

5 回答 5

79

如果适配器的寿命比实际更长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();
}
于 2017-10-26T14:50:26.560 回答
19

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

@Override protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (getAdapter() != null) {
        setAdapter(null);
    }
}
于 2016-10-28T09:33:08.197 回答
6

首先,我引用这个文件

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

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

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

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

于 2016-02-20T14:15:24.980 回答
1

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

使用FragmentwithretainInstance时,应清除所有 ui 引用onDestroyView

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

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

于 2017-10-26T14:00:34.313 回答
0

所以,这可能是一个有点矫枉过正的解决方案,但我们的团队已经厌倦了不得不担心每个 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 -> {
                    }
                }
            }
        })
    }
}

基本上,适配器会观察包含它的 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
    }
}
于 2021-09-20T08:49:19.357 回答