溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android在多種設計下實現(xiàn)懶加載機制的方法

發(fā)布時間:2020-09-05 14:45:08 來源:腳本之家 閱讀:152 作者:leonsir 欄目:移動開發(fā)

前言

前段時間在自己的練習項目中想用到懶加載機制,查看了大多數資料只介紹了在 View Pager + Fragment 組合的情況下實現(xiàn)的懶加載,但是現(xiàn)在大多數App更多的是 Fragmentmanager 去管理主頁面多個 Fragment 的顯示與隱藏,然后主界面的某個或多個 Fragment 里又嵌套了多個 Fragment + ViewPager (詳細見下圖 ),對于這種情況,適用于第一種的方式是不能直接解決第二種的情況的,所以寫下這篇文章,記錄一下踩的幾個坑,希望對同像我一樣的初學者提供一種思考方式作為參考(如果有錯誤或者不合適的地方,希望各位前輩能在評論區(qū)指出,非常感謝?。?。

關于懶加載

1. 什么是懶加載?

懶加載也叫延遲加載,在APP中指的是每次只加載當前頁面,是一種很好的優(yōu)化APP性能的一種方式。

2.為什么要用懶加載?

優(yōu)化APP性能,提升用戶體驗 :如果用戶打開某頁面,就會去預加載其它的頁面時,數據集較小或者網絡性能較優(yōu)時還好,但是如果數據集過大或者網絡性能不佳時,就會造成用戶等待的時間較長,APP界面產生明顯的滯頓感的情況,嚴重影響到用戶的體驗。

減少無效資源的加載,減少服務器的壓力,節(jié)省用戶流量 :如果用戶只想瀏覽或者經常瀏覽某個特定的頁面,如果使用預加載的方式,就會造成資源浪費,增加服務器的壓力等。

 實現(xiàn)懶加載

 1.ViewPager+Fragment情況

Android在多種設計下實現(xiàn)懶加載機制的方法 

1.1遇到的問題

在我們平時開發(fā)中,經常使用 ViewPager+Fragment 的組合來實現(xiàn)左右滑動的頁面設計(如上圖),但是 ViewPger 有個 預加載 機制,默認會把 ViewPager 當前位置的左右相鄰頁面預先初始化(俗稱預加載),即使設置 setOffscreenPageLimit(0) 也無效果,也會預加載。通過點進源碼中發(fā)現(xiàn),如果不主動設置 setOffscreenPageLimit() 方法, mOffscreenPageLimit 默認值為1,即使設置了0(小于1)的值了,但是還會按照 mOffscreenPageLimit=limit=1 處理。

private int mOffscreenPageLimit = 1;//即使不設置,默認值就為1

public int getOffscreenPageLimit() {
    return this.mOffscreenPageLimit;
  }
  
public void setOffscreenPageLimit(int limit) {
    if (limit < 1) {//設置為0,還是會默認為1
      Log.w("ViewPager", "Requested offscreen page limit " + limit + " too small; defaulting to " + 1);
      limit = 1;
    }
    if (limit != this.mOffscreenPageLimit) {
      this.mOffscreenPageLimit = limit;
      this.populate();
    }

1.2 解決思路

Fragment 有一個非生命周期的 setUserVisibleHint(boolean isVisibleToUser) 回調方法, ViewPager 嵌套 Fragment 時會起作用 ,如果切換 ViewPager 則該方法也會被調用,參數 isVisibleToUsertrue 代表當前 Fragment 對用戶可見,否則不可見。 所以最簡單的思路: Fragment 可見時才去加載數據,不可見時就不讓它加載數據 。據我們創(chuàng)建抽象 BaseFragment ,對其進行封裝。首先我們引入 isVisibleToUser 變量,負責保存當前 Fragment 對用戶的可見狀態(tài)。 同時還有幾個值得注意的地方:

setUserVisibleHint(boolean isVisibleToUser) 方法的回調時機并沒有與 Fragment 的生命周期有確切的關聯(lián),比如說,回調時機有可能在 onCreateView() 方法之后,也可能在 onCreateView() 方法之前。因此,必須引入一個標志位 isPrepareView 判斷view是否創(chuàng)建完成,不然,很容易會造成空指針異常。我們初始化該變量為 false ,在 onViewCreated() 中,也就是view創(chuàng)建完成后,將其賦值為 true 。

數據初始化只應該加載一次,因此,引入第二個標志位, isInitData ,初始為 false, 在數據加載完成之后,將其賦值為 true ,下次返回此頁面時不會再自動加載。至此,我們的懶加載方法考慮了所有條件。也就是當 isVisibleToUsertrue , isInitDatafalse , isPrepareViewtrue 時,進行數據加載,并且加載后為了防止重復調用,將 isInitData 賦值為 true 。

將懶加載數據提取成一個方法,那么這個方法該何時調用呢?首先 setUserVisibleHint(boolean isVisibleToUser) 方法中是必須調用的,即當 Fragment 由可見變?yōu)椴豢梢姾筒豢梢娮優(yōu)榭梢姇r回調。 其次,很容易忽略的一點。對于第一個 Fragment ,如果 setUserVisibleHint(boolean isVisibleToUser ) 方法在 onCreateView() 之前調用的話,如果懶加載方法只在 setUserVisibleHint(boolean isVisibleToUser ) 中調用,那么該 Fragment 將只能在被主動切換一次之后才能加載數據,這肯定是不可能的,因此,我們需要在view創(chuàng)建完成之后,也進行一次調用。思來想去,在 onActivityCreated() 方法中是最合適的。我們在繼承的時候,在 onViewCreated() 方法中進行一些初始化就行了,這樣不會引起沖突。

1.3 BaseFragment代碼實現(xiàn)

public abstract class BaseFragment extends Fragment {

  private Boolean isInitData = false; //標志位,判斷數據是否初始化
  private Boolean isVisibleToUser = false; //標志位,判斷fragment是否可見
  private Boolean isPrepareView = false; //標志位,判斷view已經加載完成 避免空指針操作

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

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    isPrepareView=true;//此時view已經加載完成,設置其為true
  }
  /**
   * 懶加載方法
   */
  public void lazyInitData(){
    if(!isInitData && isVisibleToUser && isPrepareView){//如果數據還沒有被加載過,并且fragment已經可見,view已經加載完成
      initData();//加載數據
      isInitData=true;//是否已經加載數據標志重新賦值為true
    }
  }

  @Override
  public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    this.isVisibleToUser=isVisibleToUser;//將fragment是否可見值賦給標志isVisibleToUser
    lazyInitData();//懶加載
  }

  /**
   * fragment生命周期中onViewCreated之后的方法 在這里調用一次懶加載 避免第一次可見不加載數據
   * @param savedInstanceState
   */
  @Override
  public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    lazyInitData();//懶加載
  }

  /**
   * 由子類實現(xiàn)
   * @return 返回子類的布局id
   */
  abstract int getLayoutId();

  /**
   * 加載數據的方法,由子類實現(xiàn)
   */
  abstract void initData();
}

2.Fragment+ViewPager+Fragment情況

Android在多種設計下實現(xiàn)懶加載機制的方法 

2.1 遇到的問題

如圖2,對于這種由 Fragmentmanager 管理主頁面的多個 Fragment 的顯示與隱藏,在其中的某個 Fragment 中又嵌套了多個 Fragment 的情況( 如上圖 ),上面的方案是無法解決的,如果主頁面的 Fragment 直接繼承上面的 BaseFragment ,就會出現(xiàn)主頁的幾個 Fragment 都不會加載的現(xiàn)象,為什么會這樣呢,按道理說 Fragment 應該可見了,加載數據的判斷邏輯應該沒問題啊,而且上面那個demo也跑成功了。最終我發(fā)現(xiàn),問題出在 setUserVisibleHint() 這個方法上,點進去它的源碼發(fā)現(xiàn)注釋中有這么一句話:

This may be used by the system to prioritize operations such as fragment lifecycle updates or loader ordering behavior.

也就是說這個可能被用來在一組有序的 Fragment 里 ,例如 Fragment 生命周期的更新。告訴我們這個方法被調用希望在一個pager里 ,因此 FragmentPagerAdapter 所以可以使用這個,而主頁面的幾個 Fragment 我們是通過 Fragmentmanager 管理的,所以 setUserVisibleHint() 是不會被調用,而我們設置的 isVisibleToUser=false 默認值一直不會變,那么 lazyInitData() 方法也就一直不會執(zhí)行。

 /**
   * 懶加載方法
   */
  public void lazyInitData(){
    if(!isInitData && isVisibleToUser && isPrepareView){//因為isVisibleToUser一直都是false,所以iniData()是不會被執(zhí)行的
      initData();//加載數據
      isInitData=true;
    }
  }

2.2 解決思路

這里我的處理方式是,在lazyInitData()中多加了一段處理邏輯,如下:

/**
   * 懶加載方法
   */
  public void lazyInitData(){
    if(!isInitData && isVisibleToUser && isPrepareView){//如果數據還沒有被加載過,并且fragment已經可見,view已經加載完成
      initData();//加載數據
      isInitData=true;//是否已經加載數據標志重新賦值為true
    }else if (!isInitData && getParentFragment()==null && isPrepareView){
      initData();
      isInitData=true;
    }
  }
  
  /**
   * Fragment顯示隱藏監(jiān)聽
   * @param hidden
   */
  @Override
  public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (!hidden) {
    lazyInitData(); 
    }
  }

對于主頁面的多個 Fragment 只會在第二個判斷邏輯處理(因為它的 isVisibleToUser 值一直等于 false ),對于嵌套的 Fragment 只會經過第一個處理邏輯(因為它的 getParentFragment()!=null ),然后通過 onHiddenChanged() 方法去加載 lazyInitData() 方法,這樣以來就能處理這種情況了。

但是這時候又會出現(xiàn)一個問題,如果一個APP里第一種,第二種情況并存的話,這段代碼又不適合第一種情況了,因為對于第一種的情況當判定 isVisibleToUserfalse 時,雖然不走第一個處理邏輯,但是它的 getParentFragment() 一直是等于 null 的,那么它就會走第二個判斷邏輯,這樣又會預加載了。

對于這種情況,我的處理方式:給每個Fragment設置一個標志值,當是第一種情況時,設為true,第二種情況時,設置false,然后再分別處理相應的判斷邏輯。代碼如下:

 /**
   * 懶加載方法
   */
  public void lazyInitData(){
    if(setFragmentTarget()){
      if(!isInitData && isVisibleToUser && isPrepareView){//如果數據還沒有被加載過,并且fragment已經可見,view已經加載完成
        initData();//加載數據
        isInitData=true;//是否已經加載數據標志重新賦值為true
      }
    }else {
      if(!isInitData && isVisibleToUser && isPrepareView){//如果數據還沒有被加載過,并且fragment已經可見,view已經加載完成
        initData();//加載數據
        isInitData=true;//是否已經加載數據標志重新賦值為true
      }else if (!isInitData && getParentFragment()==null && isPrepareView ){
        initData();
        isInitData=true;
      }
    }
  }
  
   /**
   * 設置Fragment target,由子類實現(xiàn)
   */
  abstract boolean setFragmentTarget();

經過這樣的處理之后,第一種情況和第二種情況,或兩者并存的情況下都能保證在繼承一個base下,實現(xiàn)懶加載。

2.3 BaseFragmentTwo最終代碼實現(xiàn)

public abstract class BaseFragmentTwo extends Fragment {
  private Boolean isInitData = false; //標志位,判斷數據是否初始化
  private Boolean isVisibleToUser = false; //標志位,判斷fragment是否可見
  private Boolean isPrepareView = false; //標志位,判斷view已經加載完成 避免空指針操作

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

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    isPrepareView=true;//此時view已經加載完成,設置其為true
  }
  /**
   * 懶加載方法
   */
  public void lazyInitData(){
    if(setFragmentTarget()){
      if(!isInitData && isVisibleToUser && isPrepareView){//如果數據還沒有被加載過,并且fragment已經可見,view已經加載完成
        initData();//加載數據
        isInitData=true;//是否已經加載數據標志重新賦值為true
      }
    }else {
      if(!isInitData && isVisibleToUser && isPrepareView){//如果數據還沒有被加載過,并且fragment已經可見,view已經加載完成
        initData();//加載數據
        isInitData=true;//是否已經加載數據標志重新賦值為true
      }else if (!isInitData && getParentFragment()==null && isPrepareView ){
        initData();
        isInitData=true;
      }
    }
  }



  @Override
  public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (!hidden) { lazyInitData(); }
  }

  @Override
  public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    this.isVisibleToUser=isVisibleToUser;//將fragment是否可見值賦給標志isVisibleToUser
    lazyInitData();//加載懶加載
  }

  /**
   * fragment生命周期中onViewCreated之后的方法 在這里調用一次懶加載 避免第一次可見不加載數據
   * @param savedInstanceState
   */
  @Override
  public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    lazyInitData();
  }

  /**
   * 由子類實現(xiàn)
   * @return 返回子類的布局id
   */
  abstract int getLayoutId();

  /**
   * 加載數據的方法,由子類實現(xiàn)
   */
  abstract void initData();

  /**
   * 設置Fragment target,由子類實現(xiàn)
   */
  abstract boolean setFragmentTarget();

}

其它需要注意:

①給 viewpager 設置 adapter 時,一定要傳入 getChildFragmentManager() ,否則 getParentFragment() 將會一直等于 null ,這會影響 lazyInitData() 的判斷,導致懶加載出現(xiàn)混亂甚至無效的情況。

②demo中我使用的是 ViewPager+Tablayout 的組合方式,在使用 Tablayout 時一定要保證 styles.xml 中的主題應該使用 Theme.AppCompat.Light.NoActionBar 或者 Theme.AppCompat.LightTheme.AppCompat.XXX 的主題。

項目地址

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI