ViewPager中Fragment的生命週期和FragmentPageAdapter與FragmentStatePageAdapter對其的影響

首先,我們創建一個Activity和一個Fragment,並在Fragment的各個生命週期打好日誌,並把Fragments丟進Viewpager,這裏我們往Viewpager裏丟了3個Fragment,夠用了。

同時設置Viewpager.adapter = FragmentPagerAdapter

0.剛打開Avtivity

viewpager剛進入時刻

viewpager剛進入時刻:viewpager顯示fragment1,fragment2同時被預加載,currentItem = 0

1.往左翻動一頁

左滑一頁

此時滑動一頁來到第二頁,currentItem = 1,可以看到前2個fragment沒有生命週期的變化,只是第三個faragment走了一遍前2個走過的路,這裏大家也都知道,viewpager有個setOffscreenPageLimit()方法,用來確認左右兩邊同時各加載幾個頁面,默認爲1,這裏adapter.getCount()==3所有滑到中間的時候,所有頁面都是在活躍的狀態,所以前兩個頁面不會有生命週期的調用。

2.繼續往左翻動一頁

左滑一頁

再次滑到下一頁,currentItem = 2 可以看到第2、3個頁面生命週期沒有變化,只有第一個頁面經歷了onPause->onStop->onDestroyView這麼一個變化。第一個頁面的視圖被銷燬了。

3.往右翻動一頁

回滑一頁

此時currentItem=1,可以看到第一個頁面的Fragment的視圖重建了,從onCreateView開始走一遍。不過並沒有經歷onAttach/onCreate。

4.按返回鍵退出Activity

退出activity

fragment生命週期有這些變化,可以看到第2、3個頁面也走過了onPause->onStop->onDestroyView這麼一個變化,由於第一個頁面在上一步就銷燬了view走過了這個步驟,所以接下來就是所有的Fragment經歷onDestroy->onDetach這麼一個過程


接下來設置viewpager.adapter = FragmentStatePagerAdapter

0.首先,老規矩,剛打開Activity時候的狀態

viewpager剛進入時刻

可以看到沒有任何一點變化,跟上面同時期的狀態是一毛一樣的。currentItem = 0

1.往左翻動一頁

左滑一頁

可以看到,跟上面同時期還是沒有any變化......currentItem = 1

2.繼續往左翻動一頁

左滑一頁

好吧,咱繼續翻,currentItem = 2,這裏有區別了:可以看到第一個頁面還是經過了onPause->onStop->onDestoryView這麼一個過程,不過在此之前,調用了onSaveInstanceState來保存一些必要的信息,在此後,該fragment也隨即從內存中銷燬,因爲經過了onDestroy->onDetach這麼一個過程,與activity脫離關係。那麼我們再往右滑動一頁,回到currentItem=1的時候。

3.往右翻動一頁

回滑一頁

可以看到此時第一個頁面的fragment重建了,重新走了一遍onAttach->...->onResume這麼個過程,並且在其中有傳入saveInstanceState的方法中傳入了一個不爲null的對象,日誌中我們可以看到bundle中有個testKey=0,是因爲我們重寫了fragment的一個方法:

 override fun onSaveInstanceState(outState: Bundle?) {
        super.onSaveInstanceState(outState)
        outState?.putInt("testKey",arguments.getInt("index"))
        log("onSaveInstanceState")
    }

這裏arguments中的int extra是自己傳進去的。

那麼我們看下FragmentStateViewPager的源碼:

 @Override
    public Object instantiateItem(ViewGroup container, int position) {
        ...
        if (mSavedState.size() > position) {
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
                fragment.setInitialSavedState(fss);
            }
        }
        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        ...
        return fragment;
    }
    
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, fragment.isAdded()
                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
        mFragments.set(position, null);

        ...
    }

省略掉無關代碼,就剩下以上這些,可以看到FragmentStateViewPager$destroyItem中爲我們保存了Fragment退出時的狀態,而在FragmentStateViewPager$instantiateItem中爲新建的Fragment設置了初始化state,以此達到爲我們保存Fragment state的目的。

總結 FragmentPagerAdapter&FragmentStatePagerAdapter:

相似點:

他們都無法保存視圖,即在offScreenLimit之外的Fragment總是要被destroyView。

不同點:

最大的區別就是對於在offScreenLimit之外的Fragment,FragmentPagerAdapter會銷燬試圖,但Fragmnet不會detach,也就是Fragment還是在內存中的,當需要再次顯示時他會createView,這意味着我們可以在Fragment對象中保存一些我們需要存儲的信息,createView的時候做自己的選擇。

而FragmentStatePagerAdapter就不同了,相同的情況下對於在offScreenLimit之外的Fragment,被destroyView只有還有detach,也就是此前的Fragment對象不復存在了,那麼我們肯定不能在Fragment中保存必要的信息了,此時可以重寫onSaveInstanceState來保存必要信息,並在onCreateView的時候重新拿出來用。FragmentStatePagerAdapter中爲我們保存這些state是通過一個ArrayList來實現的,意味着他是記着Fragment的index作爲key來保存或者取出的,


那麼對於我們開發者來說,可以在此做個選擇。在使用Viewpager中Fragment頁面較少的情況可以選擇FragmentPagerAdapter,這樣代碼會少幾行,簡單。

在viewpager中Fragment頁面比較多的時候選擇FragmentStatePagerAdapter來減少內存佔用,Fragment試圖銷燬的同時detach Fargment,但需要通過saveInstaceState來達到保存關鍵信息的目的。

至於Fragment的生命週期,關鍵的應該都已經在上面出現過了,有時間再添加上hide/show/repalce時候的生命週期。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章