安卓爬坑指南之FragmentStatePagerAdapter

一次開(kāi)發(fā)中,用到了viewpager嵌套viewpager,結(jié)果就踩到了這么一個(gè)坑。

先上圖:

image.png

圖片中顯示的界面布局和遇到的問(wèn)題是這樣的:首頁(yè)發(fā)現(xiàn)版塊是一個(gè)fragment,這個(gè)fragment中放了一個(gè)viewpager,這個(gè)viewpager有三頁(yè),其中最后一頁(yè)對(duì)應(yīng)的fragment又放了一個(gè)viewpager,內(nèi)層的viewpager有兩頁(yè)。進(jìn)入發(fā)現(xiàn)時(shí),從外層viewpager第一頁(yè)切到第三頁(yè),加載內(nèi)層viewpager第一頁(yè)的數(shù)據(jù),然后切回外層viewpager第一頁(yè),當(dāng)再次切到外層viewpager第三頁(yè)時(shí),出現(xiàn)了神奇的一幕,之前加載的內(nèi)層viewpager第一頁(yè)界面展示的數(shù)據(jù)神奇的不見(jiàn)了。

好吧,我自己都被說(shuō)暈了,反正大概就是這么一個(gè)情況。

首先我外層viewpager和內(nèi)層viewpager用的adapter都是繼承自FragmentStatePagerAdapter,viewpager的默認(rèn)緩存頁(yè)為1,因此首先我可以確認(rèn)的是,外層viewpager在第一頁(yè)和第三頁(yè)切換顯示時(shí),fragment會(huì)有銷毀和創(chuàng)建。當(dāng)外層viewpager從第三頁(yè)切回第一頁(yè)時(shí),此時(shí)第三頁(yè)的fragment被釋放,正常的邏輯是第三頁(yè)fragment內(nèi)層的viewpager包含的兩個(gè)fragment也是被釋放的,當(dāng)然,這也只是理論上的。為了驗(yàn)證自己的猜想,我在對(duì)應(yīng)fragment onDestory方法中寫一條Log,下面是控制臺(tái)輸出的截圖:

logcat.png

Log的顯示驗(yàn)證了我的猜想,所以問(wèn)題到底出在哪里呢?我們繼續(xù)寫Log,這次我們把外層viewpager第三頁(yè)的fragment和內(nèi)層viewpager第一頁(yè)fragment內(nèi)存地址輸出來(lái):

first.png
second.png

比較來(lái)回切換兩次的控制臺(tái)信息我們可以看到,外層viewpager第三頁(yè)的fragment內(nèi)存地址沒(méi)有變化,因?yàn)関iewpager數(shù)據(jù)源沒(méi)有變,fragment只是重走了生命周期,而fragment重新走生命周期時(shí),內(nèi)層viewpager對(duì)應(yīng)的數(shù)據(jù)源是重新創(chuàng)建的,控制臺(tái)打印的內(nèi)層viewpager第一頁(yè)fragment內(nèi)存地址不一樣正好驗(yàn)證了這是兩個(gè)fragment,既然兩個(gè)對(duì)象都不一樣,fragment重新創(chuàng)建,數(shù)據(jù)重新加載,就不存在界面數(shù)據(jù)不顯示的問(wèn)題,可是結(jié)果并不是我想的那樣,這就尷尬了!

冷靜的思考了一下,既然fragment是重新創(chuàng)建的,會(huì)不會(huì)出現(xiàn)adapter返回的fragment不一致呢?繼續(xù)寫Log:

first1.png
second1.png

看完Log我驚呆了,第二次重新創(chuàng)建了fragment,但是adapter竟然沒(méi)有返回fragment,wtf???那adapter沒(méi)有返回fragment,界面顯示的fragment哪里來(lái)的呢?第一反應(yīng)想的是不是適配器有緩存,切回來(lái)時(shí)直接走的緩存,查閱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<mFragments.size(); i++) {
            Fragment f = mFragments.get(i);
            if (f != null && f.isAdded()) {
                if (state == null) {
                    state = new Bundle();
                }
                String key = "f" + i;
                mFragmentManager.putFragment(state, key, f);
            }
        }
        return state;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
        if (state != null) {
            Bundle bundle = (Bundle)state;
            bundle.setClassLoader(loader);
            Parcelable[] fss = bundle.getParcelableArray("states");
            mSavedState.clear();
            mFragments.clear();
            if (fss != null) {
                for (int i=0; i<fss.length; i++) {
                    mSavedState.add((Fragment.SavedState)fss[i]);
                }
            }
            Iterable<String> 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);
                    }
                }
            }
        }
    }

當(dāng)傳入FragmentStatePagerAdapter的數(shù)據(jù)源不為空,viewpager在被銷毀時(shí),F(xiàn)ragmentStatePagerAdapter會(huì)自動(dòng)保存數(shù)據(jù);我們接著寫Log看看這兩個(gè)方法到底有沒(méi)有走:

first2.png
second2.png

果然,F(xiàn)ragmentStatePagerAdapter在外層viewpager第三頁(yè)fragment銷毀時(shí)保存了狀態(tài),再次切回來(lái)時(shí),雖然fragment重走了生命周期,但是由于FragmentStatePagerAdapterde直接取的緩存,銷毀時(shí)只保存了fragment的狀態(tài),切回時(shí)緩存的fragment狀態(tài)恢復(fù),但是數(shù)據(jù)源已經(jīng)釋放,從而導(dǎo)致界面數(shù)據(jù)不顯示。

至此,我們終于找到了bug罪魁禍?zhǔn)?!所以這個(gè)問(wèn)題的解決方案是去掉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) {
    }
}

講了這么多,不知道各位有沒(méi)有看懂!??????
如果大家有遇到類似的問(wèn)題,希望這篇博客對(duì)你們有幫助,最后,希望大家多多鼓勵(lì),我會(huì)繼續(xù)努力把博客寫的更好!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容