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

前言

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

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

場景一: Viewpager + Tablayout + Fragment

什麼?不會用Viewpager,可以看一下這個入門系列:ViewPager 詳解(一)—基本入門

場景一應該是很多人都遇到過的情況,界面整體使用Viewpager + Tablayout + Fragment組合,左右滑動界面以展示數據給用戶,當你滑動到下一頁的時候,Fragment已經有數據了,但是這個時候我希望開始加載數據而不是已經有了數據,特別是Viewpager 的適配器使用FragmentPagerAdapter的時候,因爲這個適配器它會預加載好相鄰的Fragment頁面,這個預加載數量可以通過如下設置:

viewPager.setOffscreenPageLimit(0);

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

懶加載原理

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

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

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

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

可以看到此時setUserVisibleHint的調用時機總是在初始化時調用,可見時調用,由可見轉換成不可見時調用。

實現思路

下面講講場景一的懶加載實現思路:我們一般在Fragment的onActivityCreated中加載數據,這個時候我們可以判斷此時的Fragment是否對用戶可見,調用fragment.getUserVisibleHint()可以獲得isVisibleToUser的值,如果爲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;
                //...
            }
        }
    }

判讀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。

應用示例

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

{% asset_img fragment1.gif fragment1 %}

可以看到,當我滑倒這個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生命週期中的調用時機:

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

可以看到此時onHiddenChanged的調用時機總是在初始化時調用,hide時調用,show時調用。

實現思路

場景二是在setUserVisibleHint方法中做文章,而這次是在onHiddenChanged方法中做文章,如下:

  @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        //1、onHiddenChanged調用在Resumed之前,所以此時可能fragment被add, 但還沒resumed
        if(!hidden && !this.isResumed())
            return;
        //2、使用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;
            }
        }
    }

應用示例

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

{% asset_img fragment2.gif fragment2 %}

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

結語

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

參考文章:

Fragment 知識梳理(3)

FragmentPagerAdapter與FragmentStatePagerAdapter區別

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