【Android】Fragment懶加載和ViewPager的坑

本篇文章已受權微信公衆號 dasu_Android(大蘇)獨家發佈html

效果

老規矩,先來看看效果java

ANDROID和福利兩個Fragment是設置的Fragment可見時加載數據,也就是懶加載。圓形的旋轉加載圖標只有一個,因此,若是當前Fragment正處於加載狀態,在離開該Fragment時須要隱藏加載動畫,由於另外一個Fragment並不必定處於加載狀態,當返回Fragment時,若是仍是處於加載狀態,則要能夠實現自動顯示加載動畫,若是數據已經加載完畢則不須要再顯示出來。android

以上效果就是今天要介紹和分享的,那麼開始往下看吧。git

懶加載

懶加載意思也就是當須要的時候纔會去加載github

那麼,爲何Fragment須要懶加載呢,通常咱們都會在onCreate()或者onCreateView()裏去啓動一些數據加載操做,好比從本地加載或者從服務器加載。大部分狀況下,這樣並不會出現什麼問題,可是當你使用ViewPager + Fragment的時候,問題就來了,這時就應該考慮是否須要實現懶加載了。api

ViewPager + Fragment 的坑

ViewPager爲了讓滑動的時候能夠有很好的用戶的體驗,也就是防止出現卡頓現象,所以它有一個緩存機制。默認狀況下,ViewPager會提早建立好當前Fragment旁的兩個Fragment,舉個例子說也就是若是你當前顯示的是編號3的Fragment,那麼其實編號2和4的Fragment也已經建立好了,也就是說這3個Fragment都已經執行完 onAttach() -> onResume() 這之間的生命週期函數了。緩存


原本Fragment的 onResume()表示的是當前Fragment處於可見且可交互狀態,但因爲ViewPager的緩存機制,它已經失去了意義,也就是說咱們只是打開了「福利」這個Fragment,但其實「休息視頻」和「拓展資源」這兩個Fragment的數據也都已經加載好了。服務器

若是加載數據的操做都比較耗時或者都是相似圖片的佔用大量內存,這時就應該考慮想一想是否該實現懶加載。也就是,當我打開哪一個Fragment的時候,它纔會去加載數據。微信

懶加載實現?

setUserVisibleHint(boolean isVisibleToUser) 能夠?

當你去網上查找相關資料時,你會發現不少人推薦說把加載數據的操做放在這個函數裏,isVisibleToUser表示當前Fragment是否可見。那麼,是否真的能夠就這樣作呢?先來看個日誌:架構

題主是從 DayDataFragment 跳轉到 MeiziDataFragment 的,因此能夠看到日誌裏面:DayDataFragment打出了false,表示它不可見了。而MeiziDataFragment卻先打出了false,而後纔打出true,這是由於setUserVisibleHint()在Fragment實例化時會先調用一次,而且默認值是false,當選中當前顯示的Fragment時還會再調用一次。

因此,看上面的日誌,除了DayDataFragment外,其餘三個Fragment均沒有實例化,因此當打開MeiziDataFragment時,由於ViewPager的緩存機制,會同時建立三個Fragment的實例,因此打印了三條isVisibleToUeser: false的日誌,由於選中的是MeiziDataFragment,因此它還會觸發一次setUserVisibleHint(),而且打印出true。

那麼,是否能夠在setUserVisibleHint(boolean isVisibleToUser)裏進行數據加載操做來實現懶加載呢?

能夠是能夠,若是你只是須要數據的懶加載的話,但若是你還有如下的需求,那麼這種方式就不行了:

一、若是你在Fragment可見時須要進行一些控件的操做,好比顯示加載控件

二、若是你還須要在Fragment從 「可見 -> 不可見」 時進行一些操做的話,好比取消加載控件顯示

這邊再提一下,setUserVisibleHint()可能會在Fragment的生命週期以外被調用,也就是可能在view建立前就被調用,也可能在destroyView後被調用,因此若是涉及到一些控件的操做的話,可能會報 null 異常,由於控件還沒初始化,或者已經摧毀了。

進一步封裝

題主稍微進行了一些封裝,自定義了一個新的回調函數onFragmentVisibleChange(boolean isVisible),能夠實現的效果有:

一、只有兩種狀況會觸發該函數

二、一種是Fragment從「不可見 -> 可見」 時觸發,並傳入 isVisible = true

三、一種是Fragment從「可見 -> 不可見」 時觸發,並傳入 isVisible = false

四、能夠在該函數內進行控件的操做,不會報null異常

題主此次仍舊是從DayDataFragment 跳轉到 MeiziDataFragment, 但跟上上面的日誌圖片不一樣,這裏只打印了兩條日誌,也就是說即便有三個Fragment被實例化了,但只有顯示的那個Fragment和離開的那個Fragment纔會觸發回調函數,這樣就能夠支持咱們在可見狀態變化時進行一些操做,由於不會有多餘的false觸發。

另外,由於ViewPager緩存機制,因此題主進行了view的複用,防止onCreateView()重複的建立view,其實也就是將view設置爲成員變量,建立view時先判斷是否爲null。由於ViewPager裏對Fragment的回收和建立時,若是Fragment已經建立過了,那麼只會調用 onCreateView() -> onDestroyView() 生命函數,onCreate()和onDestroy並不會觸發,因此關於變量的初始化和賦值操做能夠在onCreate()裏進行,這樣就能夠避免重複的操做。

代碼


2016-04-21 更新:該博客封裝的懶加載實現有些不足,好比不支持數據只有第一次打開Fragment時才進行加載的應用場景,所以從新寫了篇博客,能夠移步至此觀看:再來一篇Fragment的懶加載(只加載一次哦)


最後附上代碼,另外注意一下,題主是從項目裏抽出代碼,進行一些修改,讓它儘可能能夠直接複製粘貼使用,但並無進行過測試,因此若是不行的話能夠留言,題主會查看。或者你直接到我原項目裏去查看,代碼已託管至Github上,由於項目是針對具體需求的,因此類裏面會增長不少其餘無關的代碼。再或者,你能夠嘗試本身進行封裝下,代碼不多,不到50行,理解思路就好了。

/**
 * Created by dasu on 2016/9/27.
 * https://github.com/woshidasusu/Meizi
 *
 * Viewpager + Fragment狀況下,fragment的生命週期因Viewpager的緩存機制而失去了具體意義
 * 該抽象類自定義一個新的回調方法,當fragment可見狀態改變時會觸發的回調方法,介紹看下面
 *
 * @see #onFragmentVisibleChange(boolean)
 */
public abstract class ViewPagerFragment extends Fragment {

    /**
     * rootView是否初始化標誌,防止回調函數在rootView爲空的時候觸發
     */
    private boolean hasCreateView;
    
    /**
     * 當前Fragment是否處於可見狀態標誌,防止因ViewPager的緩存機制而致使回調函數的觸發
     */
    private boolean isFragmentVisible;
    
    /**
     * onCreateView()裏返回的view,修飾爲protected,因此子類繼承該類時,在onCreateView裏必須對該變量進行初始化
     */
    protected View rootView;

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        Log.d(getTAG(), "setUserVisibleHint() -> isVisibleToUser: " + isVisibleToUser);
        if (rootView == null) {
            return;
        }
        hasCreateView = true;
        if (isVisibleToUser) {
            onFragmentVisibleChange(true);
            isFragmentVisible = true;
            return;
        }
        if (isFragmentVisible) {
            onFragmentVisibleChange(false);
            isFragmentVisible = false;
        }
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initVariable();
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if (!hasCreateView && getUserVisibleHint()) {
            onFragmentVisibleChange(true);
            isFragmentVisible = true;
        }
    }

    private void initVariable() {
        hasCreateView = false;
        isFragmentVisible = false;
    }

    /**************************************************************
     *  自定義的回調方法,子類可根據需求重寫
     *************************************************************/

    /**
     * 當前fragment可見狀態發生變化時會回調該方法
     * 若是當前fragment是第一次加載,等待onCreateView後纔會回調該方法,其它狀況回調時機跟 {@link #setUserVisibleHint(boolean)}一致
     * 在該回調方法中你能夠作一些加載數據操做,甚至是控件的操做,由於配合fragment的view複用機制,你不用擔憂在對控件操做中會報 null 異常
     *
     * @param isVisible true  不可見 -> 可見
     *                  false 可見  -> 不可見
     */
    protected void onFragmentVisibleChange(boolean isVisible) {
        Log.w(getTAG(), "onFragmentVisibleChange -> isVisible: " + isVisible);
    }
}

用法

新建類ViewPagerFragment,將上面代碼複製粘貼進去,添加須要的import語句 -> 新建你須要的Fragment類,繼承ViewPagerFragment,在onCreateView()裏對rootView進行初始化 -> 重寫onFragmentVisibleChange(),在這裏進行你須要的操做,好比數據加載,控制顯示等。

@Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (rootView == null) {
            rootView = inflater.inflate(R.layout.fragment_android, container, false);
        
        }
        return rootView;
    } 
    
    @Override
    protected void onFragmentVisibleChange(boolean isVisible) {
        super.onFragmentVisibleChange(isVisible);
        if (isVisible) {
        //   do things when fragment is visible    
        //    if (ListUtils.isEmpty(mDataList) && !isRefreshing()) {
        //        setRefresh(true);
        //        loadServiceData(false);
            } else {
        //        setRefresh(false);
            }
        }
    }

項目Github 地址

PS

以上就是此次的內容了,最近題主想利用 Gank公開的api,作一個相似於Meizi的應用出來,雖然這個App已經有無數人都作過了,但確實是一個很值得學習的項目,題主仍然是小白一個,因此仍是好好學習下。drakeet的Meizi項目用到了不少高級技術,好比Rxjava之類的,題主看不懂,其餘Github上一些比較出名的Meizi App要麼是MVP架構,要麼仍是用到了目前小白的我看懂的技術,因此此次就決定本身用最基礎的MVC,一些簡單經常使用的第三方庫來作這個App,畢竟路要一步一步走,若是這個完成了,收穫和體驗應該會不少(這不就收穫了這篇隨筆了嗎O(∩_∩)O),因此,若是有興趣的話,歡迎Start,歡迎指點,歡迎拍磚,你們一塊兒學習進步。


QQ圖片20180316094923.jpg 最近剛開通了公衆號,想激勵本身堅持寫做下去,初期主要分享原創的Android或Android-Tv方面的小知識,感興趣的能夠點一波關注,謝謝支持~~

相關文章
相關標籤/搜索