I have an activity that contains a View Pager that has an adapter FragmentStatePagerAdapter. each time enter the activity it will take up 200mb of memory, after going back out of the activity(finish()) and then re entering it it will append and double the memory used on the phone.
After troubleshooting the problem it seems as if the fragment manager is not releasing the fragments although im trying to remove them but its just not working.
I tried emptying the fragment that is being added to make sure its not something internal inside the fragment the the problem remains.
my adapter code is
private class ChildrenPagerAdapter extends FragmentStatePagerAdapter
{
private List<ChildBean> childrenBean;
public ChildrenPagerAdapter(FragmentManager fm, List<ChildBean> bean)
{
super(fm);
this.childrenBean = bean;
}
@Override
public int getItemPosition(Object object)
{
return PagerAdapter.POSITION_NONE;
}
@Override
public Fragment getItem(int position)
{
ReportFragment reportFragment = new ReportFragment();
reportFragment.childBean = childrenBean.get(position);
reportFragment.position = position;
reportFragment.mPager = mPager;
if(position == 0)
{
reportFragment.mostLeft = true;
}
if(position == childrenNumber - 1)
{
reportFragment.mostRight = true;
}
return reportFragment;
}
@Override
public int getCount()
{
return childrenNumber;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object)
{
// TODO Auto-generated method stub
super.destroyItem(container, position, object);
}
}
my activity code is
public class ReportActivity extends CustomActivity
{
public ImageLoader imageLoader;
private ViewPager mPager;
private PagerAdapter mPagerAdapter;
private int childrenNumber;
private int currentChild;
@Override
protected void onDestroy()
{
mPager.removeAllViews();
mPager.removeAllViewsInLayout();
mPager.destroyDrawingCache();
mPagerAdapter = null;
mPager = null;
System.gc();
super.onDestroy();
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setCustomTitle(string.title_activity_reports);
this.currentChild = getIntent().getIntExtra("itemselected", -1);
getSupportFragmentManager().
}
@Override
protected void onResume()
{
super.onResume();
mPager = (ViewPager) findViewById(R.id.vpchildren);
mPager.setOffscreenPageLimit(6);
childrenNumber = MainActivity.bean.size();
mPagerAdapter = new ChildrenPagerAdapter(getSupportFragmentManager(), MainActivity.bean);
mPager.setAdapter(mPagerAdapter);
mPager.setCurrentItem(currentChild);
}
}
Fragment code :
public class ReportFragment extends Fragment
{
public ChildBean childBean;
public int position;
public ImageView img;
public ImageLoader imageLoader;
public DisplayImageOptions options;
private int pee = 0;
private int poop = 0;
private double sleep = 0.0;
public ViewPager mPager;
public boolean mostLeft = false;
public boolean mostRight = false;
public ReportFragment()
{
}
@Override
public void onDestroyView()
{
super.onDestroyView();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.report_fragment, container, false);
if(mostLeft)
{
rootView.findViewById(id.btnleft).setVisibility(View.GONE);
}
if(mostRight)
{
rootView.findViewById(id.btnright).setVisibility(View.GONE);
}
rootView.findViewById(id.btnleft).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
mPager.setCurrentItem(mPager.getCurrentItem() - 1);
}
});
rootView.findViewById(id.btnright).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
mPager.setCurrentItem(mPager.getCurrentItem() + 1);
}
});
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
Date dobchild = new Date();
((TextView) rootView.findViewById(id.tvday)).setText(sdf.format(dobchild));
ImageView childimg = (ImageView) rootView.findViewById(id.img_child);
((TextView) rootView.findViewById(id.tvchildname)).setText(childBean.childname);
((TextView) rootView.findViewById(id.tvclassname)).setText(((CustomApplication) getActivity().getApplication()).preferenceAccess.getCurrentClassName());
Date dob = null;
String age = "";
try
{
dob = sdf.parse(childBean.childdob);
age = GeneralUtils.getAge(dob.getTime(), getString(string.tv_day), getString(string.tv_month), getString(string.tv_year));
}
catch(ParseException e)
{
// TODO:
}
((CustomTextView) rootView.findViewById(id.tvchildage)).setText(age);
DisplayImageOptions options =
new DisplayImageOptions.Builder().showImageForEmptyUri(drawable.noimage).showImageOnFail(drawable.noimage).showStubImage(drawable.noimage).cacheInMemory()
.imageScaleType(ImageScaleType.NONE).build();
imageLoader = ImageLoader.getInstance();
imageLoader.displayImage(childBean.childphoto, childimg, options);
final TextView tvpee = (TextView) rootView.findViewById(id.tvpeetime);
final TextView tvpoop = (TextView) rootView.findViewById(id.tvpootimes);
final TextView tvsleep = (TextView) rootView.findViewById(id.tvsleeptime);
rootView.findViewById(id.btnaddpee).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
pee = pee + 1;
if(pee > 9)
{
Toast.makeText(getActivity(), getString(string.tvareyousurepee), Toast.LENGTH_LONG).show();
}
tvpee.setText(String.format(getString(string.tvtimes), pee));
}
});
rootView.findViewById(id.btnminuspee).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
if(pee > 0)
{
pee = pee - 1;
tvpee.setText(String.format(getString(string.tvtimes), pee));
}
}
});
rootView.findViewById(id.btnpluspoo).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
poop = poop + 1;
if(poop > 9)
{
Toast.makeText(getActivity(), getString(string.tvareyousurepoop), Toast.LENGTH_LONG).show();
}
tvpoop.setText(String.format(getString(string.tvtimes), poop));
}
});
rootView.findViewById(id.btnminuspoo).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
if(poop > 0)
{
poop = poop - 1;
tvpoop.setText(String.format(getString(string.tvtimes), poop));
}
}
});
rootView.findViewById(id.btnaddsleep).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
sleep = sleep + 0.25;
tvsleep.setText(String.format(getString(string.tvhours), sleep));
}
});
rootView.findViewById(id.btnminussleep).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
if(sleep > 0)
{
sleep = sleep - 0.25;
tvsleep.setText(String.format(getString(string.tvhours), sleep));
}
}
});
rootView.findViewById(id.btnsave).setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
Toast.makeText(getActivity(), "Report Saved.", Toast.LENGTH_LONG).show();
getActivity().finish();
}
});
return rootView;
}
}
Please advise... Thanks
You'll notice that when a Fragment is detached, its onPause, onStop and onDestroyView methods are called only (in that order). On the other hand, when a Fragment is removed, its onPause, onStop, onDestroyView, onDestroy and onDetach methods are called (in that order).
It can't exist independently. We can't create multi-screen UI without using fragment in an activity, After using multiple fragments in a single activity, we can create a multi-screen UI. Fragment cannot be used without an Activity.
As Fragment is embedded inside an Activity, it will be killed when Activity is killed. As contents of activity are first killed, fragment will be destroyed just before activity gets destroyed.
The Android Developer guide has a decent section on the use of Fragments. One way to use Fragments is without a UI. There are a few references to using this as a means of background processing, but what advantages do Fragments bring to this area?
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.
First of all looking at your code I don't see you doing any memory releasing measures in your fragments onDestroy(). The fact that fragment itself is destroyed and gc'ed does not mean all resources you allocated were removed too.
For example, my big concern is:
imageLoader = ImageLoader.getInstance();
imageLoader.displayImage(childBean.childphoto, childimg, options);
From what I see here it seems that there is a static instance of ImageLoader that gets poked every time a new fragment appears, but I can't see where a dying fragment would ask ImageLoader to unload its stuff. That looks suspicious to me.
If I were you I would dump an HPROF file of my application the moment it took extra 200mb (as you claim) after activity restart 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.
Don't store 'strong' references to ViewPager or ImageView in your Fragment. You're creating a cyclical reference that will keep everything in memory. Instead, if you must keep a reference to ViewPager or any other element that references its context outside of your Activity, try using a WeakReference, e.g:
private WeakReference<ViewPager> mPagerRef;
...
mPagerRef = new WeakReference<ViewPager>(mPager);
...
final ViewPager pager = mPagerRef.get();
if (pager != null) {
pager.setCurrentItem(...);
}
Following this pattern with Objects that store a reference to the Activity or Application context (hint: any ViewGroup, ImageView, Activity, etc.) should prevent "memory leaks" in the form of "retain cycles" from occurring.
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