Fragment 内存泄漏之 RecyclerView 持有

前言

在项目开发中发现在 Fragment 使用 RecyclerView 时导致内存泄漏问题,记录一下问题原因以及解决办法。

原因分析

通常 Fragment 中使用 RecyclerView 是直接在 onCreateView 方法中初始化并设置 Adapter , 一般都会这样写:

    private lateinit var mAdapter: BaseAdapter<String>
    private lateinit var mRecyclerView: RecyclerView
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = DataBindingUtil.inflate(inflater, layoutId, container, false)
        mAdapter = BaseAdapter<String>()
        mRecyclerView = _binding.rvList
        mRecyclerView.adapter = mAdapter
        return _binding.root
    }

我们来看看 RecyclerView 的代码:

    private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();

    public void setAdapter(@Nullable Adapter adapter) {
        ... //ignore
        setAdapterInternal(adapter, false, true);
        ... //ignore
    }
    
   private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        ... //ignore
        mAdapter = adapter;
        if (adapter != null) {
             // 注册 RecyclerView 的数据观察
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
    }

在 RecyclerView 中 adapter 注册了 RecyclerView 的数据观察者 mObserver,mObserver 被 RecyclerView 持有,接着来看下 Adapter :

public abstract static class Adapter<VH extends ViewHolder> {
    private final AdapterDataObservable mObservable = new AdapterDataObservable();
    
    public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
        mObservable.registerObserver(observer);
    }

    public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) {
        mObservable.unregisterObserver(observer);
    }
}

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
    ... //ignore
}

实际调用到的是 Observable 的 registerObserver 方法:

public abstract class Observable<T> {
    protected final ArrayList<T> mObservers = new ArrayList<T>();

    public void registerObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            if (mObservers.contains(observer)) {
                throw new IllegalStateException("Observer " + observer + " is already registered.");
            }
            mObservers.add(observer);
        }
    }

    public void unregisterObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            int index = mObservers.indexOf(observer);
            if (index == -1) {
                throw new IllegalStateException("Observer " + observer + " was not registered.");
            }
            mObservers.remove(index);
        }
    }
}

可以看到观察者 Observer 被保存到了 ArrayList 中, Adapter 持有 RecyclerView 中 Observer 的强引用。当 Fragment 切换时,虽然会调用 onDestroyView 方法进行 View 的释放工作,但是 Fragment 未销毁,引用的 Adapter 不会释放,导致 RecyclerView 也不被释放,造成内存泄漏。下图梳理了三者之间的引用关系:

在这里插入图片描述

解决办法

即然找到了问题的原因,是对象间存在周期引用,那我们就打断它们之间的引用关系,如下图:

在这里插入图片描述

这里我们可以有以下几种解决思路:

1. Fragment 的 onDestroyView 时把 RecyclerView 的 Adapter 置空 :

    fun onDestroyView() {
        mRecyclerView.adapter = null
        super.onDestroyView()
    }

此用法实际是断开 RecyclerView 和 Adapter 的引用:

 private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            // 注销 RecyclerView 的数据观察 , 移除旧 Adapter 对 Observer 的引用
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        ... //ignore
    }

这种方法适用于切换 Fragment 时需要保留数据,方便页面回退时恢复原来的数据,不需要重新创建 Adapter ,例:

    private lateinit var mAdapter: BaseAdapter<String>
    override fun initView() {
        // mAdapter 未初始化时才进行初始化
        if (!::adapter.isInitialized) {
            mAdapter = BaseAdapter<String>()
        }
    }

2. Fragment 的 onDestroyView 时把 Fragment 的 Adapter 置空:

    fun onDestroyView() {
        adapter = null  // adapter is nullable
        super.onDestroyView()
    }

这种使用方式断开了 Fragment 和 Adapter 的引用关系,View 销毁时 Adapter 和 RecyclerView 可以被回收。

适用于不需要保留原始数据,界面恢复重新创建 Adapter 并加载数据的方式,例:

    private lateinit var adapter: BaseAdapter<ItemData>
    override fun initView() {
        adapter = BaseAdapter<ItemData>()
        loadData()
    }
    
    private fun loadData() {
        viewModel.loadData().autoDispose().subscribeBy {}
    }

3. Lifecycle 监听生命周期,复写 Fragment 扩展方法 autoCleared 自动实现 adapter 赋空操作:

fun <T : Any> Fragment.autoCleared() = AutoClearedValue<T>(this)
class AutoClearedValue<T : Any>(val fragment: Fragment) : ReadWriteProperty<Fragment, T> {
    private var _value: T? = null

    init {
        fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onCreate(owner: LifecycleOwner) {
                fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner ->
                    viewLifecycleOwner?.lifecycle?.addObserver(object : DefaultLifecycleObserver {
                        override fun onDestroy(owner: LifecycleOwner) {
                            _value = null
                        }
                    })
                }
            }
        })
    }
}

这种方法和第二种方法一样,都是在 Fragment 执行 onDestroy 方法时对 adapter 赋空,但是扩展方法通过对生命周期的监听实现了自动回收操作,不需要我们手动处理。例:

    private var adapter by autoCleared<BaseAdapter<String>>()  // Fragment 中的使用方式
    override fun initView() {
        adapter = BaseAdapter<String>()
        mRecyclerView.adapter = adapter
    }

参考文献

A Subtle Memory Leak - Fragment, RecyclerView and its Adapter

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值