20

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

N Jay
  • 1,774
  • 1
  • 17
  • 36
  • 1
    Can you post the code of ReportFragment? – Henrique Aug 14 '13 at 20:22
  • 1
    Does your app really takes 200mb? `FragmentStatePagerAdapter` is used for efficiency to keep the smallest amount of fragments in memory. In your app, however, you're kind of going against this by using `mPager.setOffscreenPageLimit(6);` basically keeping in memory a maximum of 13 fragments. – user Aug 16 '13 at 16:49
  • @Luksprog yes im reaching a 600 mb and them the activity is closing, i have put this to have a smooth scroll inside the application.. the weird thing is that im actually every time i scroll back to a fragment i have already scrolled through it will pill up more memory... i have put more than 20 hours on this... and no luck.. appreciate the help... btw i will make the setoofscreenpagelimit to 2 only. thats for noticing that . – N Jay Aug 17 '13 at 08:42
  • Use traceview to see what's happening. – user Aug 17 '13 at 08:46
  • @Luksprog the memory is boosting onCreateView in the inflater code. and it seems that the layout that is being inflated is not being cleared by anyway. – N Jay Aug 17 '13 at 08:55
  • 1
    how could your app use 600MB of RAM, if the maximum amount of heap size is much less than that? do you use JNI? if so, you must release its memory when you don't need it, by yourself. also, i think you should try a totally new project that demonstrates only the problem, instead of showing us the entire code (since most of it probably has nothing to do with the problem). try to make the minimal code that shows the problem. – android developer Aug 17 '13 at 10:02
  • @androiddeveloper i make sure to put all the code since i have reached a dead end... when the memory reaches 600 the activity closes so practically its not and im not using JNI... the problem is narrowed down to the the view and inflator not being destroyed since wen the view is inflated it piles up memory after that it doesnt remove anything from memory.. and if u go back to the view it will pile it up again... – N Jay Aug 19 '13 at 08:19
  • @NaderAyyad sorry is still don't understand you. you can't reach 600MB without using any special trick. heap sizes still don't reach this amount. please try to minimize the code. – android developer Aug 19 '13 at 08:28
  • Are you sure you're not off by a `0`? 200 MB (the equivalent of a 52 megapixel bitmap!) seems unlikely because of reasons already pointed out by @androiddeveloper. An out or memory error (I assume that's what you're getting) at ~60+ MB is much more plausible. Anyways, regardless of leaking 20 MB or 200 MB per page, this should be easy to track down by doing a memory dump and analyzing it. Be on the lookout for strong references to instances of your fragments (or their views). – MH. Aug 21 '13 at 09:17
  • @MH. it's ok if he reference to fragments, as long as he takes care of objects that take a lot of memory. of course, if he has infinite number of fragments (or even too many), it becomes a problem. he can also have something in the middle - use weakReference instead. – android developer Aug 21 '13 at 09:43
  • @androiddeveloper: sorry, I meant to add `static` somewhere to that last sentence too. :) Anywho, analyzing a memory dump is almost certainly the best option in this scenario, so I'm not sure a bounty is really going to help. – MH. Aug 21 '13 at 09:52

4 Answers4

22

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.

EvilDuck
  • 4,386
  • 23
  • 32
3

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.

cjohn
  • 11,370
  • 3
  • 30
  • 17
2

it seems that your code is not destroying the view, check this Destroy item from the ViewPager's adapter might solve this issue.

Community
  • 1
  • 1
1

After using the memory analyzer tool in eclipse i found out that what is sticking in my memory is the actual layout of my fragments. Relative layout in specific.

The reason for this is a CustomTextView that i created that has a custom font set as a typeface.

 Typeface face=Typeface.createFromAsset(context.getAssets(), "Helvetica_Neue.ttf"); 
 this.setTypeface(face); 

To solve the memory leak i simply did the following answer found here:

public class FontCache {

    private static Hashtable<String, Typeface> fontCache = new Hashtable<String, Typeface>();

    public static Typeface get(String name, Context context) {
        Typeface tf = fontCache.get(name);
        if(tf == null) {
            try {
                tf = Typeface.createFromAsset(context.getAssets(), name);
            }
            catch (Exception e) {
                return null;
            }
            fontCache.put(name, tf);
        }
        return tf;
    }
}
Community
  • 1
  • 1
N Jay
  • 1,774
  • 1
  • 17
  • 36
  • 1
    And you think that unaccepting answer that actually gave you right directions (MAT) and information is appropriate? – EvilDuck Sep 25 '13 at 19:11