Reputation: 800
I have a fragment, fragment A, which holds a ViewPager. The ViewPager loads different fragments which the user can swipe through "indefinitely" (I use a really high number of pages/loops to emulate this). When a user clicks on the current ViewPager fragment, then fragment A with the ViewPager is replaced by fragment B in the fragment manager. When the user returns from fragment B, the backstack is popped using popBackStackImmediate()
. If the user repeats this action several times, the heap begins to fill up by about 100kb at a time until the app starts to become sloppy and malfunction as the memory fills up. I'm unsure what exactly is causing this, can anyone help?
My fragment A with the ViewPager:
public class MainFragment extends Fragment {
private MainWearActivity mMainWearActivity;
View view;
private int currentPage;
private ViewPager pager;
private ViewPagerAdapter adapter;
private LinearLayout helpIcons;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMainWearActivity = (MainWearActivity) getActivity();
adapter = new ViewPagerAdapter(this.getChildFragmentManager());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_main, container, false);
// Scrolling menu
pager = (ViewPager) view.findViewById(R.id.watchNavPager);
pager.setAdapter(adapter);
pager.addOnPageChangeListener(adapter);
// Set current item to the middle page
pager.setCurrentItem(Consts.FIRST_PAGE);
currentPage = Consts.FIRST_PAGE;
// Set number of pages
pager.setOffscreenPageLimit(4);
// Set no margin so other pages are hidden
pager.setPageMargin(0);
return view;
}
@Override
public void onDestroyView() {
pager = null;
super.onDestroyView();
}
}
My adapter class:
public class ViewPagerAdapter extends FragmentPagerAdapter implements
ViewPager.OnPageChangeListener {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position)
{
position = position % Consts.PAGES;
switch(position){
case Consts.AUDIO_POS:
return new AdapterAudioFragment();
case Consts.VOICE_POS:
return new AdapterVoiceFragment();
case Consts.MAIL_POS:
return new AdapterMailFragment();
case Consts.INFO_POS:
return new AdapterInfoFragment();
default:
return null;
}
}
@Override
public int getCount()
{
return Consts.PAGES * Consts.LOOPS; // (4 * 1000)
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {}
@Override
public void onPageSelected(int position) {}
@Override
public void onPageScrollStateChanged(int state) {}
}
One of my fragments that the adapter loads (they are all pretty much the same):
public class AdapterAudioFragment extends Fragment {
private ImageView menuImg;
private TextView menuText;
private LinearLayout rootView;
private MainWearActivity mMainWearActivity;
private View.OnClickListener imgClickListener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMainWearActivity = (MainWearActivity) getActivity();
imgClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
mMainWearActivity.replaceFragment(mMainWearActivity.getFragment(Consts.FRAG_AUDIO), Consts.FRAG_AUDIO);
}
};
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
// Get root view of the fragment layout
rootView = (LinearLayout) inflater.inflate(
R.layout.fragment_nav_object, container, false);
// Set the current menu image and text
menuImg = (ImageView) rootView.findViewById(R.id.fragment_image);
menuImg.setImageResource(R.mipmap.ic_audio);
menuImg.setOnClickListener(imgClickListener);
menuText = (TextView) rootView.findViewById(R.id.menuTxt);
menuText.setText(Consts.MENU_HEADER_AUDIO);
// Set the current menu selection
mMainWearActivity.setCurrentSelection(Consts.AUDIO_POS);
return rootView;
}
}
I have a feeling that the adapter's fragments are all being created but never destroyed and piling up in the heap but I can't figure out how to resolve this. Do I need to call destroyItem in the adapter and manually destroy them? Any help would be most appreciated, thanks.
Upvotes: 0
Views: 3146
Reputation: 62549
only way i could fix it is with the other constructor it offers:
adapter = MyPagerFragmentAdapter(
lifecycle = hostFragment.viewLifecycleOwner.lifecycle,
fm = hostFragment.childFragmentManager
)
if that fails just use viewpager #1 not viewpager2
Upvotes: 0
Reputation: 11
Adding this to Fragment stopped leaks for me:
@Override
public void onDestroyView() {
super.onDestroyView();
viewPager.setAdapter(null);
}
Looking at the source code, the problem seems to be that when calling ViewPager#setAdapter
the view will register itself as observer for the adapter. So each time onViewCreated is called your pager adapter instance will have reference of the newly created view.
Upvotes: 1
Reputation: 538
There is a specific PagerAdapter for your needs - FragmentStatePagerAdapter
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.
Upvotes: 0