Highway62
Highway62

Reputation: 800

Android memory leak issue when using ViewPagerAdapter with nested fragments

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

Answers (3)

j2emanue
j2emanue

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

Max Tayler
Max Tayler

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

Sergey Trukhachev
Sergey Trukhachev

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

Related Questions