【问题标题】:Fragments are not being released from memory片段没有从内存中释放
【发布时间】:2013-09-26 00:02:33
【问题描述】:

我有一个包含 View Pager 的活动,该 View Pager 有一个适配器 FragmentStatePagerAdapter。 每次进入activity会占用200mb的内存,退出activity(finish())再重新进入后会追加和加倍手机使用的内存。

在对问题进行故障排除后,似乎片段管理器没有释放片段,尽管我试图删除它们,但它只是不起作用。

我尝试清空正在添加的片段,以确保它不是问题仍然存在的片段内部的东西。

我的适配器代码是

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

我的活动代码是

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

片段代码:

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

请指教...谢谢

【问题讨论】:

  • 能贴出ReportFragment的代码吗?
  • @Henrique 完成,谢谢
  • 你的应用真的需要 200mb 吗? FragmentStatePagerAdapter 用于提高效率以在内存中保留最少数量的碎片。然而,在您的应用程序中,您通过使用 mPager.setOffscreenPageLimit(6); 基本上在内存中最多保留 13 个片段来反对这一点。
  • @Luksprog 是的,我达到了 600 mb,他们的活动正在关闭,我已经把它放在应用程序内平滑滚动.. 奇怪的是,我实际上每次滚动回我已经滚动浏览过的片段会占用更多内存...我已经为此花费了 20 多个小时...但没有运气..感谢您的帮助...顺便说一句,我只会将 setoofscreenpagelimit 设置为 2。那是为了注意到这一点。
  • 如果最大堆大小远小于该值,您的应用程序如何使用 600MB 的 RAM?你使用 JNI 吗?如果是这样,您必须在不需要时自行释放它的内存。另外,我认为您应该尝试一个仅演示问题的全新项目,而不是向我们展示整个代码(因为其中大部分可能与问题无关)。尝试制作显示问题的最少代码。

标签: android android-fragments android-viewpager


【解决方案1】:

ViewPager 本身有一个方法setOffscreenPageLimit,它允许您指定适配器保留的页面数。所以你远处的碎片会被销毁。

首先查看您的代码,我没有看到您在 onDestroy() 片段中执行任何内存释放措施。片段本身被销毁和 gc'ed 的事实并不意味着您分配的所有资源也被删除。

例如,我最关心的是:

imageLoader = ImageLoader.getInstance();
imageLoader.displayImage(childBean.childphoto, childimg, options);

从我在这里看到的情况来看,似乎每次出现新片段时都会触发 ImageLoader 的静态实例,但我看不到垂死的片段会要求 ImageLoader 卸载其内容的位置。我觉得这很可疑。

如果我是你,我会在活动重启后花费额外的 200mb(如你所称)并通过 MAT(内存分析器工具)分析引用时转储我的应用程序的 HPROF 文件。您显然有内存泄漏问题,我非常怀疑问题在于片段本身没有被破坏。

如果您不知道如何分析内存堆,这里有一个很好的video。我数不清有多少次它帮助我识别和消除了我的应用程序中的内存泄漏。

【讨论】:

    【解决方案2】:

    不要在 Fragment 中存储对 ViewPager 或 ImageView 的“强”引用。您正在创建一个循环引用,它将所有内容都保存在内存中。相反,如果您必须在 Activity 之外保留对 ViewPager 或任何其他引用其上下文的元素的引用,请尝试使用 Wea​​kReference,例如:

    private WeakReference<ViewPager> mPagerRef; 
    ... 
    mPagerRef = new WeakReference<ViewPager>(mPager);
    ...
    final ViewPager pager = mPagerRef.get();
    
    if (pager != null) {
        pager.setCurrentItem(...);
    }
    

    使用存储对 Activity 或应用程序上下文(提示:任何 ViewGroup、ImageView、Activity 等)的引用的对象遵循此模式应该可以防止以“保留周期”形式出现的“内存泄漏”。

    【讨论】:

    • 你能举个例子吗?对于每个片段中的每个视图?
    【解决方案3】:

    您的代码似乎没有破坏视图,检查此Destroy item from the ViewPager's adapter 可能会解决此问题。

    【讨论】:

      【解决方案4】:

      在 Eclipse 中使用内存分析器工具后,我发现留在我记忆中的是我的片段的实际布局。 具体的相对布局。

      原因是我创建的 CustomTextView 将自定义字体设置为字体。

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

      为了解决内存泄漏,我简单地做了以下答案,找到了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;
          }
      }
      

      【讨论】:

      • 您认为实际上给您正确方向 (MAT) 和信息的不可接受的答案是适当的吗?
      猜你喜欢
      • 1970-01-01
      • 2013-09-21
      • 2019-08-12
      • 2016-02-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多