N Jay
N Jay

Reputation: 1824

Fragments are not being released from memory

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

Upvotes: 20

Views: 15826

Answers (4)

N Jay
N Jay

Reputation: 1824

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;
    }
}

Upvotes: 1

cjohn
cjohn

Reputation: 11660

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.

Upvotes: 3

EvilDuck
EvilDuck

Reputation: 4436

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.

Upvotes: 22

Asem Daaboul
Asem Daaboul

Reputation: 61

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

Upvotes: 2

Related Questions