Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView doesn't unregister itself from the adapter on orientation change

I used to use ListView in my android application and I recently switched to RecyclerView and observed that it introduced some memory leaks on orientation change. On further investigation, the reason became apparent

SETUP

A single activity which hosts a fragment, the instance of which is retained across config changes. The fragment contains a single RecyclerView in its layout file that is populated using a custom adapter

DRILLING DOWN

Whenever an Adapter is set for any of those 2 views, they register themselves with the adapter to monitor changes to the data and update on the UI. ListView unregisters itself on config changes by

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    ...

    if (mAdapter != null && mDataSetObserver != null) {
        mAdapter.unregisterDataSetObserver(mDataSetObserver);
        mDataSetObserver = null;
    }

    ...
}

Unfortunately, RecyclerView doesn't do that

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (mItemAnimator != null) {
        mItemAnimator.endAnimations();
    }
    mFirstLayoutComplete = false;

    stopScroll();
    mIsAttached = false;
    if (mLayout != null) {
        mLayout.onDetachedFromWindow(this, mRecycler);
    }
    removeCallbacks(mItemAnimatorRunner);
}

PROOF

I changed the orientation a good number of times and then took a heap dump, and read it using Eclipse's MAT. I did see that there were a good number of instances of my activity because the RecyclerView instances didn't unregister and they have strong references to my activity!!

Am I missing something? How do you guys make sure that the RecyclerView doesn't leak your activity?

FRAGMENT

public class ExampleFragment extends Fragment {

    private ExampleAdapter mAdapter = null;

    public static ExampleFragment newInstance() {
        return new ExampleFragment();
    }


    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        setupAdapterIfRequired();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_example, container, false);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setupRecyclerView(getView());
    }

    private void setupAdapterIfRequired() {
        if (mAdapter == null) {
            mAdapter = new ExampleAdapter();
        }
    }

    private void setupRecyclerView(View rootView) {
        RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.list);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
        recyclerView.setAdapter(mAdapter);
    }
}
like image 391
x-treme Avatar asked May 08 '15 20:05

x-treme


2 Answers

Adding this to Fragment stopped leaks for me:

@Override
public void onDestroyView() {
    super.onDestroyView();
    recyclerView.setAdapter(null);
}
like image 81
InTwoMinds Avatar answered Nov 19 '22 08:11

InTwoMinds


It not the problem with RecyclerView. It because you are setting setRetainInstance(true).

setRetainInstance() should only be use with fragment without view or it will cause memory leak.

When orientation change activity will be killed, but view in fragment still using context from that activity. That is why you see memory leak.

like image 3
Pongpat Avatar answered Nov 19 '22 10:11

Pongpat