【问题标题】:Android memory leak issue when using ViewPagerAdapter with nested fragments使用带有嵌套片段的 ViewPagerAdapter 时的 Android 内存泄漏问题
【发布时间】:2017-10-04 00:09:00
【问题描述】:

我有一个片段,片段 A,它包含一个 ViewPager。 ViewPager 加载不同的片段,用户可以“无限期地”浏览这些片段(我使用大量的页面/循环来模拟这一点)。当用户点击当前的 ViewPager 片段时,带有 ViewPager 的片段 A 会被片段管理器中的片段 B 替换。当用户从片段 B 返回时,使用popBackStackImmediate() 弹出回栈。如果用户多次重复此操作,堆开始一次填满大约 100kb,直到应用程序开始变得草率并随着内存填满而出现故障。我不确定到底是什么原因造成的,有人可以帮忙吗?

我的片段 A 与 ViewPager:

public class MainFragment extends Fragment {

    private MainWearActivity mMainWearActivity;
    View view;

    private int currentPage;
    private ViewPager pager;
    private ViewPagerAdapter adapter;
    private LinearLayout helpIcons;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mMainWearActivity = (MainWearActivity) getActivity();
        adapter = new ViewPagerAdapter(this.getChildFragmentManager());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.fragment_main, container, false);

        // Scrolling menu
        pager = (ViewPager) view.findViewById(R.id.watchNavPager);
        pager.setAdapter(adapter);
        pager.addOnPageChangeListener(adapter);
        // Set current item to the middle page
        pager.setCurrentItem(Consts.FIRST_PAGE);
        currentPage = Consts.FIRST_PAGE;
        // Set number of pages
        pager.setOffscreenPageLimit(4);
        // Set no margin so other pages are hidden
        pager.setPageMargin(0);

        return view;
    }


    @Override
    public void onDestroyView() {
        pager = null;
        super.onDestroyView();
    }
}

我的适配器类:

public class ViewPagerAdapter extends FragmentPagerAdapter implements
        ViewPager.OnPageChangeListener {


    public ViewPagerAdapter(FragmentManager fm) {
        super(fm);
    }


    @Override
    public Fragment getItem(int position)
    {
        position = position % Consts.PAGES;

        switch(position){
            case Consts.AUDIO_POS:
                return new AdapterAudioFragment();
            case Consts.VOICE_POS:
                return new AdapterVoiceFragment();
            case Consts.MAIL_POS:
                return new AdapterMailFragment();
            case Consts.INFO_POS:
                return new AdapterInfoFragment();
            default:
                return null;
        }
    }


    @Override
    public int getCount()
    {
        return Consts.PAGES * Consts.LOOPS; // (4 * 1000)
    }

    @Override
    public void onPageScrolled(int position, float positionOffset,
                               int positionOffsetPixels) {}

    @Override
    public void onPageSelected(int position) {}

    @Override
    public void onPageScrollStateChanged(int state) {}

}

适配器加载的我的一个片段(它们几乎都一样):

public class AdapterAudioFragment extends Fragment {

    private ImageView menuImg;
    private TextView menuText;
    private LinearLayout rootView;
    private MainWearActivity mMainWearActivity;
    private View.OnClickListener imgClickListener;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mMainWearActivity = (MainWearActivity) getActivity();
        imgClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mMainWearActivity.replaceFragment(mMainWearActivity.getFragment(Consts.FRAG_AUDIO), Consts.FRAG_AUDIO);
            }
        };
    }

    @Override
    public View onCreateView(LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {


        // Get root view of the fragment layout
        rootView = (LinearLayout) inflater.inflate(
                R.layout.fragment_nav_object, container, false);


        // Set the current menu image and text
        menuImg = (ImageView) rootView.findViewById(R.id.fragment_image);
        menuImg.setImageResource(R.mipmap.ic_audio);
        menuImg.setOnClickListener(imgClickListener);

        menuText = (TextView) rootView.findViewById(R.id.menuTxt);
        menuText.setText(Consts.MENU_HEADER_AUDIO);

        // Set the current menu selection
        mMainWearActivity.setCurrentSelection(Consts.AUDIO_POS);
        return rootView;
    }
}

我有一种感觉,适配器的片段都在被创建,但从未被破坏并堆积在堆中,但我不知道如何解决这个问题。我是否需要在适配器中调用 destroyItem 并手动销毁它们?任何帮助将不胜感激,谢谢。

【问题讨论】:

  • 嗨。你找到导致你的问题的原因了吗?我有一个类似的问题,仅在 L 以下的 Android 版本上重现。

标签: android android-viewpager fragmentmanager


【解决方案1】:

将此添加到 Fragment 为我阻止了泄漏:

@Override
public void onDestroyView() {
    super.onDestroyView();
    viewPager.setAdapter(null);
}

查看源代码,问题似乎是在调用ViewPager#setAdapter时,视图会将自己注册为适配器的观察者。因此,每次调用 onViewCreated 时,您的寻呼机适配器实例都会引用新创建的视图。

【讨论】:

    【解决方案2】:

    有一个特定的 PagerAdapter 可以满足您的需求 - FragmentStatePagerAdapter

    当有大量页面时,此版本的寻呼机更有用,更像列表视图。当页面对用户不可见时,它们的整个片段可能会被销毁,只保留该片段的已保存状态。与 FragmentPagerAdapter 相比,这允许分页器保留与每个访问的页面相关联的内存更少,但代价是在页面之间切换时可能会产生更多开销。

    【讨论】:

    • 切换到 FSPA 仅仅是改变超类的问题,还是我必须彻底改变类的实现方式?
    • 只需更改超类即可。
    • 我尝试切换到那里的 FSPA,但仍然遇到同样的问题。
    猜你喜欢
    • 2014-01-17
    • 2013-11-30
    • 1970-01-01
    • 2013-09-23
    • 2012-11-05
    • 1970-01-01
    • 2015-07-20
    • 2019-12-22
    • 1970-01-01
    相关资源
    最近更新 更多