安卓爬坑指南之FragmentStatePagerAdapter

一次开发中,用到了viewpager嵌套viewpager,结果就踩到了这么一个坑。

先上图:

安卓爬坑指南之FragmentStatePagerAdapter_第1张图片
image.png

图片中显示的界面布局和遇到的问题是这样的:首页发现版块是一个fragment,这个fragment中放了一个viewpager,这个viewpager有三页,其中最后一页对应的fragment又放了一个viewpager,内层的viewpager有两页。进入发现时,从外层viewpager第一页切到第三页,加载内层viewpager第一页的数据,然后切回外层viewpager第一页,当再次切到外层viewpager第三页时,出现了神奇的一幕,之前加载的内层viewpager第一页界面展示的数据神奇的不见了。

好吧,我自己都被说晕了,反正大概就是这么一个情况。

首先我外层viewpager和内层viewpager用的adapter都是继承自FragmentStatePagerAdapter,viewpager的默认缓存页为1,因此首先我可以确认的是,外层viewpager在第一页和第三页切换显示时,fragment会有销毁和创建。当外层viewpager从第三页切回第一页时,此时第三页的fragment被释放,正常的逻辑是第三页fragment内层的viewpager包含的两个fragment也是被释放的,当然,这也只是理论上的。为了验证自己的猜想,我在对应fragment onDestory方法中写一条Log,下面是控制台输出的截图:

安卓爬坑指南之FragmentStatePagerAdapter_第2张图片
logcat.png

Log的显示验证了我的猜想,所以问题到底出在哪里呢?我们继续写Log,这次我们把外层viewpager第三页的fragment和内层viewpager第一页fragment内存地址输出来:

安卓爬坑指南之FragmentStatePagerAdapter_第3张图片
first.png
安卓爬坑指南之FragmentStatePagerAdapter_第4张图片
second.png

比较来回切换两次的控制台信息我们可以看到,外层viewpager第三页的fragment内存地址没有变化,因为viewpager数据源没有变,fragment只是重走了生命周期,而fragment重新走生命周期时,内层viewpager对应的数据源是重新创建的,控制台打印的内层viewpager第一页fragment内存地址不一样正好验证了这是两个fragment,既然两个对象都不一样,fragment重新创建,数据重新加载,就不存在界面数据不显示的问题,可是结果并不是我想的那样,这就尴尬了!

冷静的思考了一下,既然fragment是重新创建的,会不会出现adapter返回的fragment不一致呢?继续写Log:

安卓爬坑指南之FragmentStatePagerAdapter_第5张图片
first1.png
安卓爬坑指南之FragmentStatePagerAdapter_第6张图片
second1.png

看完Log我惊呆了,第二次重新创建了fragment,但是adapter竟然没有返回fragment,wtf???那adapter没有返回fragment,界面显示的fragment哪里来的呢?第一反应想的是不是适配器有缓存,切回来时直接走的缓存,查阅FragmentStatePagerAdapter源码,果不其然,被我找到了!源码如下:

    @Override
    public Parcelable saveState() {
        Bundle state = null;
        if (mSavedState.size() > 0) {
            state = new Bundle();
            Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
            mSavedState.toArray(fss);
            state.putParcelableArray("states", fss);
        }
        for (int i=0; i keys = bundle.keySet();
            for (String key: keys) {
                if (key.startsWith("f")) {
                    int index = Integer.parseInt(key.substring(1));
                    Fragment f = mFragmentManager.getFragment(bundle, key);
                    if (f != null) {
                        while (mFragments.size() <= index) {
                            mFragments.add(null);
                        }
                        f.setMenuVisibility(false);
                        mFragments.set(index, f);
                    } else {
                        Log.w(TAG, "Bad fragment at key " + key);
                    }
                }
            }
        }
    }

当传入FragmentStatePagerAdapter的数据源不为空,viewpager在被销毁时,FragmentStatePagerAdapter会自动保存数据;我们接着写Log看看这两个方法到底有没有走:

安卓爬坑指南之FragmentStatePagerAdapter_第7张图片
first2.png
安卓爬坑指南之FragmentStatePagerAdapter_第8张图片
second2.png

果然,FragmentStatePagerAdapter在外层viewpager第三页fragment销毁时保存了状态,再次切回来时,虽然fragment重走了生命周期,但是由于FragmentStatePagerAdapterde直接取的缓存,销毁时只保存了fragment的状态,切回时缓存的fragment状态恢复,但是数据源已经释放,从而导致界面数据不显示。

至此,我们终于找到了bug罪魁祸首!所以这个问题的解决方案是去掉FragmentStatePagerAdapterde 缓存,具体代码如下:

public class BaseFragmentPageAdapter extends FragmentStatePagerAdapter {
    public BaseFragmentPageAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return null;
    }

    @Override
    public int getCount() {
        return 0;
    }

    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
    }
}

讲了这么多,不知道各位有没有看懂!
如果大家有遇到类似的问题,希望这篇博客对你们有帮助,最后,希望大家多多鼓励,我会继续努力把博客写的更好!

你可能感兴趣的:(安卓爬坑指南之FragmentStatePagerAdapter)