凌霄的博客
使用viewpager+fragment发生oom
使用viewpager+fragment发生oom

不知道你们有没有过这样的需求,就是做一个卡片展示,一张屏幕需要展示多个卡片的那种。我首先想到的是使用FragmentStatePagerAdapter造成fragment无法被释放而导致的内存泄露,所以带着这个关键词去解决问题,结果嘛,问题当然是没有解决滴。

http://omsaa4hdo.bkt.clouddn.com/face/69EEF944967E141C224747403B6EC6C1.jpg

FragmentStatePagerAdapter的内部处理

显而易见,这个锅不能甩的FragmentStatePagerAdapter,事实上,当使用FragmentStatePagerAdapter的时候,超出缓存范围的fragment会被fragmentmanager给remove掉,就是说不用手动处理,交给FragmentStatePagerAdapter处理就好了。我遇到的情况是fragment超出了缓存区域,它的ondestory也执行了。但是它持有的内存并没有被释放,就是说他内部的数据等并未被销毁和释放,如果是在页面多的情况下,很容易就会造成程序的卡顿甚至OOM。

错误排查

使用android profiler(android studio3.0之后自带)可以监控设备内存和cpu使用情况,具体如何使用,可以参考这篇文章:AndroidStudio3.0 Android Profiler分析器(cpu memory network 分析器) 。博主就不在此赘述了。在android profiler里,我一个有意思的地方是,当我每次清空list时,内存都会下降一大截。所以我初步怀疑是list使用不当造成的内存泄露。我在使用viewpager时,创建了一个list用来管理要添加到viewpager的所有fragment:

private List<Fragment> mFrgmentList = new ArrayList<>();  //创建一个List,用来管理所有要添加到ViewPager的Fragment

在获取数据的回调方法里:

if (data.identifyResult > 1) {
       String personID = data.personID.trim();
       String personName = data.personName.trim();
       CardPagerFragment fragment = new CardPagerFragment();
       bundle.putString("personId", personID);
       bundle.putString("personName", personName);
       bundle.putByteArray("headImage",data.featureImage);
       fragment.setArguments(bundle);
       mFragmentList.add(fragment);
       cardAdapter.notifyDataSetChanged();
       vpCard.setCurrentItem(mFragmentList.size()-1);
  }

乍一看上面的代码似乎没什么问题,不过仔细一看问题就大了。这里的list可能强引用了所有的fragment,即使fragment的ondestory方法执行了,内存也无法得到释放,所以造成了内存泄露。要验证也很简单,在list数量达到一定程度时,执行一次clear操作,观察内存情况一目了然。

解决方案

听你哔哔这么久,到底怎么解决这个问题?不要着急,马上上干货!

http://omsaa4hdo.bkt.clouddn.com/face/01E8A62C3D0C704BF2E481FB170F1D77.jpg

首先不能用list来管理fragment对象了。ViewPager最麻烦的是你不能自己使用 FragmentManager 的transaction 来添加 Fragment ,因为这些操作是在 ViewPager 的内部去完成的。最后我使用list来管理fragment的class类,然后通过反射的方式,创建出fragment对象。这样list就不会持有fragment的引用了。如下:

private List<Class> mFrgmentList = new ArrayList<>();  //创建一个List,用来管理Fragment的class

adapter中的代码:

public class CardPagerAdapter extends FragmentPagerAdapter{
    private Context mContext;
    private List<Class> fragmentList;
    public CardPagerAdapter(FragmentManager fm, Context context, List<Class> fragmentList) {
        super(fm);
        this.mContext = context;
        this.fragmentList = fragmentList;
    }

    @Override
    public int getCount() {
        return fragmentList.size();
    }

    @Override
    public Fragment getItem(int position) {
        //反射加载fragment
        Fragment fragment = (Fragment) fragmentList.get(position).newInstance();
        return fragment;
    }

    /**
     *item的权重
     */
    @Override
    public float getPageWidth(int position) {
        return 1.0f/ ContentValue.ONE_PAGE_ITEM_COUNT;
    }
}

写完代码,点击运行验证,确实不会存在内存不断增加的情况了。至此问题解决。发生oom的时候,报错信息只有下面这一点:

http://omsaa4hdo.bkt.clouddn.com/crash/viewpager_oom.jpg

所以没多想就以为是bitmap处理不当造成的内存溢出,因为list造成的内存泄露我还没听过。所以list这样使用也会出现内存泄露是我以前都没想过的,这回算是见过大世面了,也让我知道考虑问题不要太死板,钻牛角尖。跳出原有的思维,说不定就会有收获。

发表评论

textsms
account_circle
email

使用viewpager+fragment发生oom
不知道你们有没有过这样的需求,就是做一个卡片展示,一张屏幕需要展示多个卡片的那种。我首先想到的是使用FragmentStatePagerAdapter造成fragment无法被释放而导致的内存泄露,所以带着…
扫描二维码继续阅读
2018-05-11