ViewPager中Fragment的懶加載和可見狀態監聽

一. 前言

在Android開發中常常會使用到ViewPager, ViewPager若是和Fragment一塊兒使用的話, 就要考慮懶加載和預加載的問題. ViewPager有個方法setOffscreenPageLimit 這個方法能夠配置緩存數量. 那是否是直接設置0就能夠實現懶加載了呢? 不是的, 查看源碼:git

public void setOffscreenPageLimit(int limit) {
        if (limit < DEFAULT_OFFSCREEN_PAGES) {  //DEFAULT_OFFSCREEN_PAGES爲1
            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                    + DEFAULT_OFFSCREEN_PAGES);
            limit = DEFAULT_OFFSCREEN_PAGES;
        }
        if (limit != mOffscreenPageLimit) {
            mOffscreenPageLimit = limit;
            populate();
        }
    }
複製代碼

設置0後會無效的, limit 仍是被設置成默認的1github

懶加載就是當用戶滑動到當前的frament才能去加載數據, 這樣避免加載了數據可是沒有使用到, 形成了浪費.
預加載是爲了提早加載數據, 讓用戶減小等待時間. 懶加載和預加載應該根據具體的業務要求去使用. 沒有誰好誰壞之分. 但二者的前提都是要搞清楚Fragment在ViewPager中的生命週期, 下面先來弄清楚生命週期的調用.緩存

二. Fragment在ViewPager中的生命週期

首先寫一個簡單的Activity裏面有ViewPager 代碼以下:bash

public class MainActivity extends AppCompatActivity {

    private ViewPager viewById;
    private List<Fragment> list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        viewById = findViewById(R.id.vp);
        list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            SimpleFragment simpleFragment = SimpleFragment.newInstance(i);
            list.add(simpleFragment);
        }
        MineAdapter mineAdapter = new MineAdapter(getSupportFragmentManager(),list);
        viewById.setOffscreenPageLimit(1);
        viewById.setAdapter(mineAdapter);
    }
}
複製代碼

關於這個SimpleFragment代碼就不用貼出來了 很簡單就是在各個生命週期中加入log. 這個MineAdapter 很簡單的. 以下:網絡

public class MineAdapter extends FragmentPagerAdapter {
    List<Fragment>  mPages;
    public MineAdapter(FragmentManager fm, List<Fragment> pages) {
        super(fm);
        mPages=pages;
    }
    @Override
    public Fragment getItem(int i) {
        return mPages.get(i);
    }
    @Override
    public int getCount() {
        return mPages.size();
    }
}
複製代碼

運行程序 來看看Fragment生命週期 日誌以下: 爲了方便敘述第0個fragement簡稱爲0號. 這時候實際上是緩存數量爲1ide

image.png
經過日誌能夠看到首次進入這個activity頁面的時候. 首先加載的是0號和1號, 並且不是說等0號(當前顯示界面)加載完 再加載1號. 而是0號和1號生命週期交錯進行. 而且都一直走到了onResume方法. 滑動到1號 這時候 1號爲當前展現界面. 0號和2號爲緩存. 繼續看日誌
image.png
這時候1號只走了一個方法setUserVisibleHint true . 而後2號生命週期從onAttach到onResume. 再滑動一下, 這時候2號爲當前展現界面. 這時候會緩存1號和3號, 0號進入銷燬.
image.png

三. 總結生命週期中的規律

經過以上的日誌 能夠總結關鍵幾點.ui

  1. setUserVisibleHint 方法都是比較先走的. 首次進入的時候同一個Fragment的setUserVisibleHint 要走兩次 一次true, 一次false.
  2. 被預加載的Fragment的生命週期 除了setUserVisibleHint true沒走以外 其餘的生命週期也走了.
  3. 預加載的Fragment 到顯示的時候 其實只走了 setUserVisibleHint true.
  4. Fragment 銷燬的時候 只走到了onDestroyView方法 並無走onDestroy onDetach方法. 這點對於 執行一些回收操做很是有必要了解.

四. 懶加載的實現

懶加載是滑動到當前Fragment的時候纔去調用的方法. 通常在實際業務中就是滑到了要展現的頁面去調接口獲取數據.
寫一個基礎的BaseLazyFragment , 繼承這個BaseLazyFragment 重寫lazyInit()方法. 這個方法裏寫你須要執行的懶加載操做.spa

public class BaseLazyFragment extends Fragment {
    private boolean isViewPrepared; // 標識fragment視圖已經初始化完畢
    private boolean hasFetchData; // 標識已經觸發過懶加載數據

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        lazyFetchDataIfPrepared(); //通過了預加載頁面, 而後展現 
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        isViewPrepared = true;
        lazyFetchDataIfPrepared(); //首次進入, 沒有預加載直接加載數據
    }

    /**
     * 懶加載方法,獲取數據什麼的放到這邊來使用,在切換到這個界面時才進行網絡請求
     */
    private void lazyFetchDataIfPrepared() {
        // 用戶可見fragment && 沒有加載過數據 && 視圖已經準備完畢
        if (getUserVisibleHint() && !hasFetchData && isViewPrepared) {
            hasFetchData = true; //已加載過數據
            lazyInit();
        }
    }

    /**
     * 執行須要懶加載的方法
     */
    protected void lazyInit() {
        Log.i("zmin........." + getArguments().getInt("key"), ".............加載完成數據");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        hasFetchData = false;
        isViewPrepared = false;
        Log.i("zmin........." + getArguments().getInt("key"), ".............onDestroyView");
    }
}
複製代碼

五. 預加載

1. 預加載在viewpager中

viewpager其實對預加載有很好的支持. 能夠直接調用方法setOffscreenPageLimit來設置緩存的數量.3d

2. 監聽可見和不可見的狀態

在實際業務中, 可能存在這樣一種需求. 雖然是須要預加載的, 可是要監聽Fragment的可見狀態. 好比Fragment中有視頻播放. 若是Fragment可見的話就要播放. 不可見的時候就須要暫停. 這時候還須要考慮的是Fragment可能會跳轉到其餘界面. Fragment雖然可見和不可見有個生命週期方法setUserVisibleHint回調, 可是沒法直接得知當前狀態是一直不可見的,仍是由可見轉爲不可見的 . 下面來實現這個功能 :日誌

public class BaseAppearFragment extends Fragment {

    private boolean isViewPrepared; // 標識fragment視圖已經初始化完畢
    private boolean hasAppear; //標識界面當前可見

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        //當前fragment轉爲可見狀態
        if (isVisibleToUser && isViewPrepared && !hasAppear) {
            onFragmentAppear();
            hasAppear = true;
        }
        //當前fragment轉爲不可見狀態
        if (!isVisibleToUser && hasAppear) {
            onFragmentDismiss();
            hasAppear = false;
        }
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.i("zmin........." + getArguments().getInt("key"), ".............onCreateView");
        return inflater.inflate(R.layout.activity_fragment, null);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        isViewPrepared = true;
        TextView tv = getView().findViewById(R.id.tv);
        tv.setText(String.valueOf(getArguments().getInt("key")));
        Log.i("zmin........." + getArguments().getInt("key"), ".............onViewCreated");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.i("zmin........." + getArguments().getInt("key"), ".............onResume(");
        if (getUserVisibleHint()) {
            onFragmentAppear();
            hasAppear = true;
        }
    }

    @Override
    public void onDestroyView() {
        isViewPrepared = true;
        super.onDestroyView();
        Log.i("zmin........." + getArguments().getInt("key"), ".............onDestroyView");
    }

    /**
     * 界面可見
     */
    public void onFragmentAppear() {
        Log.i("zmin........." + getArguments().getInt("key"), "......界面可見..onFragmentAppear");
    }

    /**
     * 界面由可見轉爲不可見
     */
    public void onFragmentDismiss() {
        Log.i("zmin........." + getArguments().getInt("key"), "...由可見轉爲不可見.........onFragmentDismiss");
    }
}

複製代碼

能夠看到主要在在setUserVisibleHint和onResume方法中作判斷. 由於Fragment切換的時候, 不少生命週期方法是不走的.

  1. 當0號滑動到1號的時候, 這時候1號只走setUserVisibleHint方法. onResume方法是不走的.
  2. 當1號跳轉到其餘界面再返回的時候 會執行onResume可是不執行setUserVisibleHint .
  3. 而首次進入的時候setUserVisibleHint 和onResume都執行.

六 總結

經過詳細的日誌 分析了Fragment生命週期的執行. 從而實現懶加載和預加載中對可見狀態監聽. 不少業務場景下須要用到. 若是要懶加載能夠直接繼承BaseLazyFragment 類便可. 若是要監聽可見隱藏狀態則能夠繼承 BaseAppearFragment . 若是還想本身去看看打印的日誌. 能夠clone代碼, github地址 github.com/zmin666/Zmi… 但願這些總結對你有幫助.

相關文章
相關標籤/搜索