preface

In the development of the project, memory leakage occurred when Fragment used RecyclerView. Record the cause and solution of the problem.

Cause analysis,

Create a RecyclerView Adapter in your Fragment. Create a RecyclerView Adapter in your Fragment. Create a RecyclerView Adapter in your Fragment.

    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
    }
Copy the code

RecyclerView code:

    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) {
             // Register RecyclerView data observation
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this); }}Copy the code

In RecyclerView, the Adapter registers the data observer mObserver of RecyclerView. MObserver is held by RecyclerView.

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
}
Copy the code

The Observable registerObserver method is actually called:

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); }}}Copy the code

As you can see, the Observer is stored in the ArrayList. The Adapter holds a strong reference to the Observer in RecyclerView. When the Fragment switches, the onDestroyView method will be called to release the View. However, the Fragment is not destroyed, and the referenced Adapter will not be released. As a result, RecyclerView will not be released, resulting in memory leakage. The following figure sorts out the citation relationships among the three:

The solution

We found the cause of the problem, which is the periodic reference between objects, so we break the reference relationship between them, as shown in the following figure:

Here we can have the following solutions:

Modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify modify

    fun onDestroyView(a) {
        mRecyclerView.adapter = null
        super.onDestroyView()
    }
Copy the code

This usage actually breaks a reference to RecyclerView and Adapter:

 private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if(mAdapter ! =null) {
            // Remove the old Adapter reference to the Observer
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this); }...//ignore
    }
Copy the code

This method is suitable for preserving data when switching fragments, so that the original data can be restored when the page is rolled back without creating Adapter again. For example:

    private lateinit var mAdapter: BaseAdapter<String>
    override fun initView(a) {
        // The mAdapter is initialized before it is initialized
        if(! ::adapter.isInitialized) { mAdapter = BaseAdapter<String>() } }Copy the code

When onDestroyView of the Fragment emptying the Adapter of the Fragment:

    fun onDestroyView(a) {
        adapter = null  // adapter is nullable
        super.onDestroyView()
    }
Copy the code

This usage breaks the Fragment and Adapter reference relationship, and the Adapter and RecyclerView can be recycled when the View is destroyed.

It is applicable to recreate Adapter and load data without retaining original data. For example:

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

Lifecycle () Lifecycle (); autoCleared (); Lifecycle ();

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}})}})}}Copy the code

This method, like the second method, empties the Adapter when the Fragment executes onDestroy, but the extension method automatically retrieves the adapter by listening for the lifecycle and does not require manual handling. Ex. :

    private var adapter by autoCleared<BaseAdapter<String>>()  // How to use the Fragment
    override fun initView(a) {
        adapter = BaseAdapter<String>()
        mRecyclerView.adapter = adapter
    }
Copy the code

reference

A Subtle Memory Leak – Fragment, RecyclerView and its Adapter