ViewPager中Fragment的生命週期

切換ViewPager時Fragment的生命週期變化

這裏有一個MainActivity,MainActivity中有一個ViewPager,使用的適配器是FragmentPagerAdapter,ViewPager中有三個Fragment:FirstFragment,SecondFragment,ThirdFragment

剛進入MainActivity,Log是這樣的:

這裏寫圖片描述

可以看到,MainActivity按照常規順序調用後,FirstFragment和SecondFragment先後調用了onAttach()和onCreate()實例化了Fragment,並且在FirstFragment調用onResume()之後,SecondFragment也調用了onResume(),也就是是說SecondFragment已經加載好了,隨時準備變爲可見,接下來滑動到SecondFragment,是這樣的:

這裏寫圖片描述

然後再滑到第三個ThirdFragment,可以看到:

這裏寫圖片描述

爲什麼會這樣呢?

原來ViewPager默認會加載當前頁面當前頁面左右的兩個頁面。ViewPager中有一個成員變量紀錄着加載的左右頁面的數量private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES,默認值就是private static final int DEFAULT_OFFSCREEN_PAGES = 1

我們可以調用如下方法調整默認加載的數量:

    /**
     * Set the number of pages that should be retained to either side of the
     * current page in the view hierarchy in an idle state. Pages beyond this
     * limit will be recreated from the adapter when needed.
     *
     * <p>This is offered as an optimization. If you know in advance the number
     * of pages you will need to support or have lazy-loading mechanisms in place
     * on your pages, tweaking this setting can have benefits in perceived smoothness
     * of paging animations and interaction. If you have a small number of pages (3-4)
     * that you can keep active all at once, less time will be spent in layout for
     * newly created view subtrees as the user pages back and forth.</p>
     *
     * <p>You should keep this limit low, especially if your pages have complex layouts.
     * This setting defaults to 1.</p>
     *
     * @param limit How many pages will be kept offscreen in an idle state.
     */
    public void setOffscreenPageLimit(int limit) {
        if (limit < DEFAULT_OFFSCREEN_PAGES) {
            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                    + DEFAULT_OFFSCREEN_PAGES);
            limit = DEFAULT_OFFSCREEN_PAGES;
        }
        if (limit != mOffscreenPageLimit) {
            mOffscreenPageLimit = limit;
            populate();
        }
    }

我們可不可以只加載當前的頁面,把mOffscreenPageLimit設爲0呢?
顯然不能,我們發現源碼中有這麼一段:

        if (limit < DEFAULT_OFFSCREEN_PAGES) {
            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                    + DEFAULT_OFFSCREEN_PAGES);
            limit = DEFAULT_OFFSCREEN_PAGES;
        }

所以最少加載當前頁面的左右各一頁,把mOffscreenPageLimit 設爲0,ViewPager也會替你修改成1

當然超出mOffscreenPageLimit 的範圍,Fragment的視圖就會被銷燬。所以就有了上面打印的日誌。

實現懶加載

既然ViewPager默認給我們加載當前頁面和當前頁面左右的兩個頁面,那該如何實現只加載當前頁面呢?

第一個方法,自己複製一份ViewPager的代碼,改名爲LazyViewPager,然後修改代碼,再使用LazyViewPager替代ViewPager。

第二個方法,直接修改ViewPager源碼,自己打個v4包。

當然以上兩種方法不推薦。

數據的懶加載

其實一般爲了避免不必要的流量浪費,像微信一樣我們只要實現數據的懶加載即可。

利用setUserVisibleHint(),如下:

public class BaseFragment extends Fragment {

    protected boolean isViewCreated = false;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (getUserVisibleHint()) {
            lazyLoadData();
        }
    }

    /**
     * 懶加載方法
     */
    protected void lazyLoadData() {

    }
}

爲什麼要判斷getUserVisibleHint()的值,因爲查看FragmentPagerAdapter的源碼會發現setUserVisibleHint()第一次調用是在onAttach()之前,這樣顯然不可以。我們只有等Fragment的視圖加載好,才能加載數據。

或者利用Fragment的onHiddenChanged()通過類似的思路也能實現數據的懶加載。

也可以利用ViewPager中OnPageChangeListener接口的onPageSelected(),或者TabLayout中TabLayoutOnPageChangeListener接口的onPageSelected()

FragmentPagerAdapter和FragmentStatePagerAdapter的區別

之前的案例都是使用的FragmentPagerAdapter,這裏講講FragmentPagerAdapter和FragmentStatePagerAdapter的區別。

當ViewPager切換的時候,FragmentPagerAdapter會銷燬Fragment的視圖並且回調onDestroyView(),但是會保存內存中Fragment的實例不會執行onDestroy()

I/fragment: onPause()
I/fragment: onStop()
I/fragment: onDestroyView()


當ViewPager切換的時候,FragmentStatePagerAdapter不僅會銷燬Fragment的視圖,而且會刪除內存中Fragment的實例並且和Activity解綁

I/fragment: onPause()
I/fragment: onStop()
I/fragment: onDestroyView()
I/fragment: onDestroy()
I/fragment: onDetach()

還有一點就是FragmentStatePagerAdapter會將onSaveInstanceState(Bundle outState)中的Bundle信息保存下來
也就是說,你可以在onSaveInstanceState(Bundle outState)保存一些數據,在onCreate()中進行恢復。

FragmentStatePagerAdapter相對於FragmentPagerAdapter節約內存,FragmentStatePagerAdapter類上有如下聲明:

 * <p>This version of the pager is more useful when there are a large number
 * of pages, working more like a list view.  When pages are not visible to
 * the user, their entire fragment may be destroyed, only keeping the saved
 * state of that fragment.  This allows the pager to hold on to much less
 * memory associated with each visited page as compared to
 * {@link FragmentPagerAdapter} at the cost of potentially more overhead when
 * switching between pages.

參考:
1.Android Fragment 你應該知道的一切
2.源碼探索系列43—關於FragmentStatePagerAdapter和FragmentPagerAdapter
3.使用FragmentPagerAdapter和FragmentStatePagerAdapter時Fragment生命週期區別
4.ViewPager中Fragment的生命週期
5.ViewPager+Fragment生命週期方法(一)
6.死磕 Fragment 的生命週期
7.實現類似微信Viewpager-Fragment的惰性加載,lazy-loading

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