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);
}
}
Adding this to Fragment
stopped leaks for me:
@Override
public void onDestroyView() {
super.onDestroyView();
recyclerView.setAdapter(null);
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With