Leakcanary,Recyclerview 泄漏适配器

Leak canary, Recyclerview leaking mAdapter

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

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

这是适配器的代码以及 class 使用它的方法: https://gist.github.com/feresr/a53c7b68145d6414c40ec70b3b842f1e

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

首先,我引用 this file

It looks like v7.widget.RecyclerView is leaking the adapter, and not my application. But that can't be right.... right?

实际上是您的 适配器泄漏了 RecyclerView(跟踪图和 LeakCanary activity 的标题非常清楚)。但是,我不确定它是 "parent" RecyclerView 还是 HourlyViewHolder 中的嵌套视图,或者两者都是。 我认为罪魁祸首是您的 ViewHolders。通过使它们成为 non-static 内部 classes,您明确地为它们提供了对封闭适配器 class 的引用,并且这几乎直接将适配器与回收视图耦合,因为 parent 你持有人中的每个 itemView 都是 RecyclerView 本身。

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

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

我能够通过覆盖 RecyclerView 来解决这个问题。发生这种情况是因为 RecyclerView 永远不会从 AdapterDataObservable 中注销自身。

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

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

FragmentretainInstance 一起使用时,您应该清除 onDestroyView

中的所有 ui 引用
@Override
public void onDestroyView() {
     yourRecyclerView = null;
     super.onDestroyView();
}

在这里你可以找到来自这个link的详细信息: Retained Fragments with UI and memory leaks

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

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

否则适配器将保留对 RecyclerView 的引用,它应该已经超出内存。

如果屏幕涉及过渡动画,您实际上必须更进一步,只有在视图变为 detached:

时才清除适配器
@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();
}

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