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