搞定兩種場景下的Fragment懶加載

前言

我對懶加載的定義是:數據的加載要等到頁面對用戶可見時才加載,不然的話會浪費用戶流量。網上實現懶加載的方案很是多,但大多數都是解決了我下面說到的場景一的懶加載,本文還解決場景二的懶加載方式。java

若是不想看下面的分析,直接這個類導入你的項目中,須要懶加載的Fragment繼承這個類,並重寫相應的方法就行:傳送門git

場景一: Viewpager + Tablayout + Fragment

什麼?不會用Viewpager,能夠看一下這個入門系列:ViewPager 詳解(一)---基本入門github

場景一應該是不少人都遇到過的狀況,界面總體使用Viewpager + Tablayout + Fragment組合,左右滑動界面以展現數據給用戶,當你滑動到下一頁的時候,Fragment已經有數據了,可是這個時候我但願開始加載數據而不是已經有了數據,特別是Viewpager 的適配器使用FragmentPagerAdapter的時候,由於這個適配器它會預加載好相鄰的Fragment頁面,這個預加載數量能夠經過以下設置:微信

viewPager.setOffscreenPageLimit(0);
複製代碼

那麼上面這句代碼不是把預加載數量設置爲0了嗎?這樣Fragment就不會預先加載了,這樣想你就太天真,經過看setOffscreenPageLimit的源碼得知,若是你傳入的數值小於1,那麼ViewPager就會把預加載數量設置成默認值,而默認值就是1,因此說就算你傳入了0,ViewPager仍是會預先加載好當前頁面的左右兩個Fragment頁面。app

懶加載原理

那麼怎麼解決呢?這時要認識Fragment中的一個函數:setUserVisibleHint(boolean isVisibleToUser)ide

setUserVisibleHint方法是Fragment中的一個回調函數。當前Fragment可見對用戶可見時,setUserVisibleHint()回調,其中參數isVisibleToUser=true,當前Fragment由可見到不可見或實例化時,setUserVisibleHint()回調,其中參數isVisibleToUser=false。函數

下面看一下這個方法在Fragment生命週期中的調用時機:佈局

  • 一、當Fragment被實例化時,即Fragment被裝載進ViewPager適配器中,並:setUserVisibleHint() ->onAttach() -> onCreate() -> onCreateView() -> onViewCreated() -> onActivityCreate() -> onStart() -> onResume()。此時setUserVisibleHint() 中的參數爲false。
  • 二、在Fragmente可見時,即ViewPager滑動到當前頁面時:setUserVisibleHint()。只會調用setUserVisibleHint方法,由於已經預加載過了,Fragment在以前生命週期已經走到onResume() 了。此時setUserVisibleHint() 中的參數爲true。
  • 三、在Fragment由可見變爲不可見,即ViewPager由當前頁面滑動到另外一個頁面:setUserVisibleHint()。只會調用setUserVisibleHint方法,由於還要保持當前頁面的預加載過程,此時setUserVisibleHint() 中的參數爲false。
  • 四、點擊由TabLayout直接跳轉到一個未預加載的頁面,此時生命週期的回調過程:setUserVisibleHint() -> setUserVisibleHint() -> onAttach() -> onCreate() -> onCreateView() -> onViewCreated() -> onActivityCreate() -> onStart() -> onResume()。回調了兩次setUserVisibleHint() ,一次表明初始化時,傳入參數是false,一次表明可見時,傳入參數是true。

能夠看到此時setUserVisibleHint的調用時機老是在初始化時調用,可見時調用,由可見轉換成不可見時調用。this

實現思路

下面講講場景一的懶加載實現思路:咱們通常在Fragment的onActivityCreated中加載數據,這個時候咱們能夠判斷此時的Fragment是否對用戶可見,調用fragment.getUserVisibleHint()能夠得到isVisibleToUser的值,若是爲true,表示可見,就加載數據,若是不可見,就不加載數據了,代碼以下:spa

@Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if(isFragmentVisible(this) && this.isAdded()){
            if (this.getParentFragment() == null || isFragmentVisible(this.getParentFragment())) {
                onLazyLoadData();
                isLoadData = true;
                //...
            }
        }
    }
複製代碼

判讀Fragment是否對用戶可見封裝在isFragmentVisible方法中, onLazyLoadData()是子類須要重寫的方法,用來加載數據,加載完數據後把isLoadData設置爲true,表示已經加載過數據。

上面就控制了當Fragment不可見時就不加載數據,並且此時Fragment的生命週期也走到onResume了,那麼當我滑到這個Fragment時,只會調用它的setUserVisibleHint方法,那麼就要在setUserVisibleHint方法中加載數據,代碼以下:

@Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(isFragmentVisible(this) && !isLoadData && isViewCreated && this.isAdded()){
            onLazyLoadData();
            isLoadData = true;
        }
    }
複製代碼

isViewCreated字段表示佈局是否被初始化,它在onViewCreated方法中被賦值爲true,以下:

@Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        isViewCreated = true;
    }

複製代碼

onViewCreated方法的回調在onCreateView方法後,當調用onViewCreated方法時,Fragment的View佈局必定建立好了。

咱們再回到setUserVisibleHint方法中,在if中它會依此判斷當前Fragment可見、尚未加載數據、佈局已經建立好等這些條件知足後才加載數據,並把isLoadData賦值爲true。

應用示例

下面是我在項目中使用的狀況:

能夠看到,當我滑倒這個Fragment時才加載數據。

場景二:FragmentManager + FragmentTransaction+ Fragment

這個場景就是你把幾個Fragment經過FragmentTransaction的add方法add到FragmentManager 中,切換Fragment的時候經過FragmentTransaction的hide和show方法配合使用,相似於微信的主界面,底部有一個tab,而後點擊tab,切換頁面。

當Fragment被add進manager中時,Fragment生命週期已經執行到onResume了,因此在後續的hide和show方法切換Fragment時,Fragment已經有數據了,在個人項目中,我想要的效果是,當我點到這個tab時,該tab對於的Fragment才加載數據,因此我對這種狀況實現了懶加載。

懶加載原理

那麼要怎麼實現呢?照搬場景2的實現方式?惋惜了,不行,由於這種狀況下setUserVisibleHint方法不會被調用。這個時候咱們又從新認識一個方法onHiddenChanged(boolean hidden)

onHiddenChanged方法是當Fragment的隱藏狀態變化示被調用,當Fragment沒有被隱藏時即調用show方法,當前onHiddenChanged回調,其中參數hidde=false,當Fragment被隱藏時即調用hide了方法,onHiddenChanged()回調,其中參數hidde=true。還有一點注意的是使用hide和show時,fragment的全部生命週期方法都不會調用,除了onHiddenChanged()。

下面看一下這個方法在Fragment生命週期中的調用時機:

  • 一、當Fragment被add進manager時:onAttach() -> onCreate() -> onCreateView() -> onViewCreated() -> onActivityCreate() -> onHiddenChanged() -> onStart() -> onResume()。此時onHiddenChanged() 中的參數爲false。
  • 二、當用hide方法隱藏Fragment時:onHiddenChanged(),只會調用onHiddenChanged方法,此時setUserVisibleHint() 中的參數爲true。
  • 三、當用show方法顯示Fragment時:onHiddenChanged(),只會調用onHiddenChanged方法,此時setUserVisibleHint() 中的參數爲false。

能夠看到此時onHiddenChanged的調用時機老是在初始化時調用,hide時調用,show時調用。

實現思路

場景二是在setUserVisibleHint方法中作文章,而此次是在onHiddenChanged方法中作文章,以下:

@Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        //一、onHiddenChanged調用在Resumed以前,因此此時可能fragment被add, 但還沒resumed
        if(!hidden && !this.isResumed())
            return;
        //二、使用hide和show時,fragment的全部生命週期方法都不會調用,除了onHiddenChanged()
        if(!hidden && isFirstVisible && this.isAdded()){
            onLazyLoadData();
            isFirstVisible = false;
        }
    }
複製代碼

首先看註釋1,由於當add的時候,onHiddenChanged調用在onResumed以前,此時尚未執行onResume方法,用戶還看不見這個Fragment,若是此時加載數據就沒有什麼用,等於用戶看到這個Fragmen時它就已經執行完數據了,因此這裏要加一個判斷,若是Fragment尚未Resume,就直接return,不作操做。

接下來看註釋2,執行到註釋2表示此時Fragment已經可見了,就能夠經過hidden字段控制懶加載,hidden爲false表示調用了show方法,經過isFirstVisible控制只加載一次,爲何要用isFirstVisible呢,由於在onActivityCreate方法中就有可能已經加載過數據,若是加載過就不用再加載了,在onActivityCreate中會把這個字段賦值爲true,以下:

@Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if(isFragmentVisible(this) && this.isAdded()){
            if (this.getParentFragment() == null || isFragmentVisible(this.getParentFragment())) {
                onLazyLoadData();
                isLoadData = true;
                if(isFirstVisible)
                    isFirstVisible = false;
            }
        }
    }
複製代碼

應用示例

下面是我在項目中使用的狀況:

能夠看到,當我點擊到這個tab時,對應的Fragment才加載數據。

結語

以上就是個人懶加載歷程,雖然如今也有一些Fragment庫能夠實現這個效果,可是它的原理也是這個,咱們要知其因此然,該懶加載類整合場景一和場景二,只有簡單的幾句代碼,只要繼承就能在兩種場景下使用。

參考文章:

Fragment 知識梳理(3)

FragmentPagerAdapter與FragmentStatePagerAdapter區別

相關文章
相關標籤/搜索