Home » Android » android – How to destroy old fragments in FragmentStatePagerAdapter

android – How to destroy old fragments in FragmentStatePagerAdapter

Posted by: admin April 23, 2020 Leave a comment

Questions:

I want to implement this:
enter image description here
I use a ViewPager with a FragmentStatePagerAdapter.
I started with the example from this page:
http://developer.android.com/reference/android/support/v4/app/FragmentStatePagerAdapter.html

This is my ViewPager adapter:

    public static class MyAdapter extends FragmentStatePagerAdapter {
        public MyAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public int getCount() {
            return NUM_ITEMS;
        }

        @Override
        public Fragment getItem(int position) {
            return ArrayListFragment.newInstance(position);
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            super.destroyItem(container, position, object);
        }
    }

Every page of my ViewPager contains a ListView with some data.
At the moment when I switch to a new page in ViewPager it will increase the RAM memory very quickly.
How should I remove the old fragments ?
I also used this but it does nothing:

public void destroyItem(ViewGroup container, int position, Object object) {

    FragmentManager manager = ((Fragment) object).getFragmentManager();
    FragmentTransaction trans = manager.beginTransaction();
    trans.remove((Fragment) object);
    trans.commit();

    super.destroyItem(container, position, object);
}

There is also a 1-2 seconds delay after I switch quickly to a new page or old page. Is there any technique to remove that delay. If I switch to a new page and wait for 2 second then on next switch there is no more delay.

Tested on Nexus 7.

How to&Answers:

You should not try to interfere with how Android manages your Fragment implementations. The default for the setOffScreenPageLimit should already be one. This means that Android will destroy old fragments when memory runs low. As long as you do not have a memory issue, just leave it be.

The reason why your memory increases is because Android keeps Fragment instances in memory to be able to reconnect to them instead of having to instantiate them. I recommend you account for the contingency of your Fragment instances being destroyed by the OS, saving their state if that happens, and let the OS do its job.

The delay you are experiencing could be due to some intensive computation on the UI thread. If it is, I suggest moving that out to, for example, an AsyncTask. Without the code it is, however, just a guess as to what might cause the issue. But there being only an initial delay suggests that you are loading something which might block the UI thread.

Update: Have a look at https://stackoverflow.com/a/9646622/170781 which outlines very neatly how the ViewPager handles Fragment instances.

Answer:

I had the same problem. But in my case ViewPager was inside of the another fragment. and after removing ViewPagerFragment from FragmentManager, all fragments from FragmentStatePagerAdapter stays in fragment manager. so after few such changes it was OutOfMemoryError. Then I switch on FragmentManager logs by:

FragmentManager.enableDebugLogging(true);

And found that id of every new fragment inreases every time.
It happens only with StatePagerAdapter. To solve this problem I call remove for every fragment that was instantinated.

protected void dispatchOnDetach(Iterable<Fragment> fragments) {
    if (fragments == null)
        return;

    Activity aa = getActivity();
    if (aa == null)
        return;

    IBaseActivity ba = (IBaseActivity) aa;
    if (ba.isActivityStopped())
        return;

    FragmentManager frMan = ba.getSupportFragmentManager();
    FragmentTransaction frTr = frMan.beginTransaction();

    for (Fragment fr : fragments) {
        if (fr != null) {
            frTr.remove(fr);
        }
    }

    frTr.remove(this);
    frTr.commit();

}

In your case. if you do not change ViewPager during runtime it is possible, that garbage collector can not destroy your fragments even after removing from fragment manager because of some references to them. You should check if some global classes use them.

And for optimizations purposes you could cache every instantinated fragment using SoftReference or LruCache. Example:

public class MyAdapter extends FragmentStatePagerAdapter {

private final LruCache<Integer, Fragment> mCache;

public MyAdapter(FragmentManager fm) {
    super(fm);
    mCache = new LruCache<Integer, Fragment>(10);
}

@Override
public int getCount() {
    return NUM_ITEMS;
}

@Override
public Fragment getItem(int position) {
    return mCache.get(position);
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
}

private class MyCache extends LruCache<Integer, Fragment> {

    public MyCache(int maxSize) {
        super(maxSize);
    }

    @Override
    protected Fragment create(Integer key) {
        return ArrayListFragment.newInstance(key);
    }
}
}

Answer:

The FragmentStatePagerAdapter is already very frugal with memory as it destroys your unneeded fragments automatically. It just keeps the fragments’ views directly left and right of the currently-shown item and destroys the others.

Example: So once you swipe to right direction, it pre-loads the soon to be right-neighbour-fragment and destroys the fragment that is now two slots on the left of the current displayed fragment.

Answer:

I think the problem is not with the ViewPager is on the ListFragments. What kind of content do you show on them? Do you allocate many images? Can you post the code of your ListFragment?

I would preferred to make a comment but since I have not enough points I hope to help by editing this response.

Answer:

ViewPager itself has a method setOffscreenPageLimit which allows you to specify number of pages kept by the adapter. So your fragments that are far away will be destroyed.

It is a bit hard to say what may be your particular issue becase I don’t know what your fragment does. By the sound of 1-2 second delay it seems that you might be doing some work on the UI thread. Also what else you are doing in your fragment that is memory consuming? Maybe you are loading images to some static memory cache and not freeing them up upon fragment removal? Could you please provide your fragment code, so I could see what it is doing?

In general I would recommend would dumping an HPROF file of your application the moment it took extra memory and analyze references via MAT (memory analyzer tool). You are clearly having memory leaks issue and I highly doubt the problem is in Fragments themselves not being destroyed.

In case you don’t know how to analyze memory heap, here is a good video. I can’t count how many times it helped me identifying and getting rid of memory leaks in my apps.

Answer:

Override this in FragmentStatePagerAdapter, notice the slight change.

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (position >= getCount()) {
    FragmentManager manager = ((Fragment) object).getFragmentManager();
    FragmentTransaction trans = manager.beginTransaction();
    trans.remove((Fragment) object);
    trans.commit();
}