Android:Fragment與導航欄的“懶加載”

2018
寫在前面:這篇文章主要分析導航欄,也就是ViewPager+Fragment+FragmentPagerAdapter的懶加載模式,重點還是Fragment生命週期的應用。
如果對如何使用導航欄還不太瞭解,可以看看 底部導航欄標籤切換的實現 這篇文章。


(一)Fragment生命週期

大家都知道 Fragment 是綁定 Activity 的,不過,很多人會忽視了它們的生命週期也是會一定程度上同步的。下面表格的場景是在 Activity 內部實例化 Fragment,Fragment 與 Activity 生命週期之間的關係。

Activity Fragment
onCreate() onAttach(), onCreateView(), onCreateView, onActivityCreate()
onStart() onStart()
onResume() onResume()
onPause() onPause()
onStop() onStop()
onDestroy() onDestroyView(), onDestroy(), onDetach()

大多數時候我們將實例化 Fragment 放在 Activity.onCreate() 中,如new一個實例對象。不難理解 Fragment 在Activity.onCreate()之後纔開始一系列生命週期方法的調用。而其他方法執行順序會是下面這樣:

Activity.onStart()-->Fragment.onStart()
Activity.onResume()-->Fragment.onResume() 
Fragment.onPause()-->Activity.onPause()
Fragment.onStop()-->Activity.onStop()
Fragment.onDestroyView(),onDestroy(),onDetach()-->Activity.onDestroy()

Fragment 依賴於 Activity,故而,Fragment創建於Acivity之後,銷燬於Acrivity之前。

ViewPager中Fragment的生命週期

結合 ViewPager 後,頁面切換會有哪些生命週期方法的調用?

看過上一篇博文的朋友應該都比較清楚了,這裏就再不厭其煩地給沒看過的朋友講解一下。

單獨使用Fragment沒什麼好說的,調用的方法和上表一樣,會和 Activity 同步。而結合 ViewPager 取決於選擇哪一種適配器,現在大多數使用 FragmentPagerAdapter或者 FragmentPagerStateAdapter,兩者的區別是到底會不會回收Fragment的內存。

前者只回收View,除非內存不足,否則不會銷燬加載好的Fragment;後者爲達到節省內存的目的,對於不在當前頁面左右兩邊的其他Page,將會銷燬其Fragment,只保留最多三個頁面在內存中。

針對 FragmentPagerAdapter 的測試

Fragment--> 代表的是第一個Fragment 的生命週期,Activity--> 代表與之關聯的活動的生命週期:

// 程序啓動,初始化活動
I/MyTest: Activity-->onCreate() 
I/MyTest: Activity-->onStart() 
          Activity-->onResume()
// 加載Fragment
I/MyTest: Fragment-->getUserVisibleHint:true 
I/MyTest: Fragment-->setUserVisibleHint(false) 
          Fragment-->getUserVisibleHint:false
          Fragment-->setUserVisibleHint(true) 
I/MyTest: Fragment-->onAttach()
          Fragment-->onCreate() 
I/MyTest: Fragment-->onCreateView()
I/MyTest: Fragment-->onActivityCreated()
          Fragment-->onStart()
          Fragment-->onResume()
//點擊第三個頁面
I/MyTest: Fragment-->getUserVisibleHint:true
          Fragment-->setUserVisibleHint(false)
I/MyTest: Fragment-->onPause()
I/MyTest: Fragment-->onStop()
          Fragment-->onDestroyView() // View被回收,但是內存沒有回收
//返回第一個頁面
I/MyTest: Fragment-->getUserVisibleHint:false
          Fragment-->setUserVisibleHint(false)
          Fragment-->getUserVisibleHint:false
          Fragment-->setUserVisibleHint(true)
          Fragment-->onCreateView()
          Fragment-->onActivityCreated()
          Fragment-->onStart()
          Fragment-->onResume()
//銷燬活動,與Activity生命週期同步
I/MyTest: Fragment-->onPause()
          Activity-->onPause() 
I/MyTest: Fragment-->onStop()
I/MyTest: Activity-->onStop() 
I/MyTest: Fragment-->onDestroyView()
          Fragment-->onDestroy()
          Fragment-->onDetach()
          Activity-->onDestroy()

發現

  • 切換到不是 當前頁面左右兩邊的頁面 時,當前頁面會被回收View,但不被銷燬,原因是Adapter默認只完整加載左右兩邊頁面。
  • 切換到 當前頁面左右兩邊的頁面 時,只會調用setUserVisibleHint(true)
  • Fragment 的生命週期與Activity息息相關。經過另外的測試,如果Activity變得不可見(比如開啓新活動),那麼所有加載好的Fragment會和Activity一起執行onPause()onStop() ,返回時又會一起執行onStart()onResume(),這就是生命週期的同步。
  • 初始化的時候setUserVisibleHint()比Fragment任何生命週期方法要早調用。而從較遠頁面切換回來setUserVisibleHint()也至少比onCreateView()先執行,如頁面3到頁面1。

還是貼張圖表示這個過程:

在這裏插入圖片描述

(二)懶加載在不同業務場景下的實現

有了上面的知識儲備,靈活地使用“懶加載”也不難了,根據業務需要,我們可以設置不同的加載方式。

基本方法

使用 setUserVisibleHint() ,在進入一個頁面之前我們肯定會需要調用這個方法。

場景1

只需要拉取一次數據,但是很可能整個活動期間都不會切換到這個頁面,所以希望省點流量,只有切換到才加載,而且只有一次。寫法如下:

private boolean hasLoad;

@Override
public void setUserVisibleHint(boolean isVisible){
    //設置爲可見並且沒有加載過數據的時候進行網絡請求
    if(isVisible && (!hasLoad)){
        /*
         * 網路請求數據
         */
         hasLoad = true;
    }        
    super.setUserVisibleHint(isVisible);
}

場景2

每次進入頁面都要拉取數據,但只要加載數據成員不需要更新UI,下面這種寫法就可以了。

@Override
public void setUserVisibleHint(boolean isVisible){
    if(isVisible){
        /*
         * 網路請求數據
         */
    }        
    super.setUserVisibleHint(isVisible);
}

場景3

每次進入都需要拉取數據,數據加載後要更新UI。

爲什麼和第一種區分,留意“發現”的最後一點的兩種情況,此時Fragment還沒有加載View實例,而更新UI必須要確保已經獲取View實例。那不妨使用一個boolean變量來標識獲取View的狀態,確保View獲取了就加載,反之不加載。很有道理,不過還有缺陷,因爲等View加載完,我們已經不會調用setUserVisibleHint() 了,所以還要在View獲取後完成一次補充加載

下面這種寫法就能確保無論是初始化、還是從較遠頁面切換過來或者從左右頁面切換過來,都能馬上拉取數據。大家可以直接用這種格式哦。

private boolean hasView;

@Override
public void setUserVisibleHint(boolean isVisible){
    if(isVisible && hasView){
        /*
         * 網路請求數據 + 更新UI
         */
    }        
    super.setUserVisibleHint(isVisible);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_layout_1, container,false);    //動態加載佈局
    /*
     * 使用 view.findViewById(long resourceId)來獲取View實例
     */
    hasView = true;   //View加載好了
    if(getUserVisibleHint()){
        /*
         * 數據請求 + UI更新
         */
    }
    return view;
}

@Override
public void onDestroyView(){
    hasView=false;    // View被回收了
    super.onDestroyView();
}

留意上面onCreateView() 中的 if 代碼塊,這就是我們說的,如果是沒有View的情況下不能直接使用 setUserVisibleHint() 加載,需要之後採取一個補充加載


(三)強調ViewPager中四種不同的Fragment狀態

  1. 當前頁面:Fragment 生命週期執行到onResume(),可見性getUserVisibleHint()true
  2. 預加載頁面:位於當前頁面左右兩邊,Fragment 生命週期執行到onResume()
    可見性getUserVisibleHint()false
  3. 曾經加載過但是被回收了View的頁面:肯定不在當前頁面左右,Fragment 生命週期執行到onCreate()getUserVisibleHint()false
  4. 從來沒有加載過的頁面:也肯定不在當前頁面左右,所有方法都還沒調用,getUserVisibleHint()false

暫時寫到這裏,關於數據懶加載我們也瞭解得差不多了,如有不正之處歡迎指正。

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