ViewPager 中 Fragment的生命周期 與 網(wǎng)絡(luò)請(qǐng)求懶加載

FragmentPagerAdapter VS FragmentStatePagerAdapter

  • FragmentPagerAdapter

Adapter 每頁(yè)都是一個(gè)Fragment,并且所有的Fragment實(shí)例一直保存在Fragment manager中。所以它適用于少量固定的fragment,比如一組用于分頁(yè)顯示的標(biāo)簽。除了當(dāng)Fragment不可見(jiàn)時(shí),它的視圖層有可能被銷(xiāo)毀外,每頁(yè)的Fragment都會(huì)被保存在內(nèi)存中。

經(jīng)過(guò)測(cè)試我們可以發(fā)現(xiàn),當(dāng)使用 FragmentPagerAdapter 的時(shí)候已經(jīng)被創(chuàng)建的頁(yè)面變?yōu)椴豢梢?jiàn)的時(shí)候生命周期為:

onPause -> onStop -> onDestroyView

  • FragmentStatePagerAdapter

每頁(yè)都是一個(gè)Fragment,當(dāng)Fragment不被需要時(shí)(比如不可見(jiàn)),整個(gè)Fragment都會(huì)被銷(xiāo)毀,除了saved state被保存外(保存下來(lái)的bundle用于恢復(fù)Fragment實(shí)例)。所以它適用于很多頁(yè)的情況。

經(jīng)過(guò)測(cè)試我們可以發(fā)現(xiàn),當(dāng)使用 FragmentPagerAdapter 的時(shí)候已經(jīng)被創(chuàng)建的頁(yè)面變?yōu)椴豢梢?jiàn)的時(shí)候生命周期為:

onPause -> onStop -> onDestoryView -> onDestroy -> onDetach

可以看出當(dāng)使用 FragmentStatePagerAdapter 的時(shí)候,不會(huì)保留不可見(jiàn)(不相鄰的)頁(yè)面在內(nèi)存中,而是直接銷(xiāo)毀了,等下次在可見(jiàn)(相鄰頁(yè)面可見(jiàn))的時(shí)候再次創(chuàng)建。


FragmentPagerAdapter 中 Fragment 的生命周期( setUserVisibleHint 亂入)

我們知道 Fragment 生命周期函數(shù)有 :

setUserVisibleHint(亂入的不用在意我) -> onAttach —> onCreate -> onCreateView -> onActivityCreated -> onStart -> onResume -> onPause -> onStop -> onDestroyView -> onDetach -> onDestroy

Fragment 本身作為片段存在于 Activity中,當(dāng) Fragment 單獨(dú)使用的時(shí)候其生命周期是伴隨著 Activity 的生命周期的,即 Activity onResume 的時(shí)候 內(nèi)部所有的 Fragment 也就走到了 onResume 這點(diǎn)初學(xué)者可能不會(huì)太在意,在這里作為題外話敲下黑板。

想要進(jìn)一步了解的請(qǐng)參照: 與 Activity 生命周期協(xié)調(diào)一致

[站外圖片上傳中...(image-489935-1517594338763)]

下面說(shuō)用 FragmentPagerAdapter 承載頁(yè)面的 Fragment 時(shí)候?qū)?yīng)的生命周期。這里說(shuō)的情況都是沒(méi)有設(shè)置 setOffscreenPageLimit 屬性的時(shí)候。這里假設(shè) ViewPager 中有3個(gè) Fragment :OutAFragment ,OutBFragment,OutCFragment 。當(dāng)首次進(jìn)入 Activity 時(shí)候 ViewPager 會(huì)初始化 OutAFragment,并預(yù)加載 OutBFragment,生命周期如下所示:

 // 首次進(jìn)入界面 A 
 E/TAG: OutAFragment  unVisibleToUser
 E/TAG: OutBFragment  unVisibleToUser
 E/TAG: OutAFragment  isVisibleToUser
 
 E/TAG: OutAFragment  onAttach
 E/TAG: OutAFragment  onCreate
 
 E/TAG: OutBFragment  onAttach
 E/TAG: OutBFragment  onCreate
 
 E/TAG: OutAFragment  onCreateView
 E/TAG: OutAFragment  onActivityCreated
 E/TAG: OutAFragment  onStart
 E/TAG: OutAFragment  onResume
 
 E/TAG: OutBFragment  onCreateView
 E/TAG: OutBFragment  onActivityCreated
 E/TAG: OutBFragment  onStart
 E/TAG: OutBFragment  onResume

通過(guò)上方的日志打印我們可以清楚的看到,OutAFragment,OutBFragment 都伴隨著 Activity 的生命周期走了對(duì)應(yīng)的生命周期方法。

接下來(lái)我們依次滑動(dòng)到 OutBFragment 和 OutCFragment 看下生命周期方法是怎么走的:

 // 首次進(jìn)入界面 B  因?yàn)?ViewPager 對(duì)界面進(jìn)行了緩存造成了

 E/TAG: OutCFragment  unVisibleToUser
 E/TAG: OutAFragment  unVisibleToUser
 E/TAG: OutBFragment  isVisibleToUser
 E/TAG: OutCFragment  onAttach
 E/TAG: OutCFragment  onCreate
 E/TAG: OutCFragment  onCreateView
 E/TAG: OutCFragment  onActivityCreated
 E/TAG: OutCFragment  onStart
 E/TAG: OutCFragment  onResume


 //首次進(jìn)入界面 C
 
E/TAG: OutBFragment  unVisibleToUser
E/TAG: OutCFragment  isVisibleToUser
E/TAG: OutAFragment  onPause
E/TAG: OutAFragment  onStop
E/TAG: OutAFragment  onDestroyView

不知道這里大家注意到了沒(méi)有由于 ViewPager 對(duì)相鄰頁(yè)面做了預(yù)加載處理,造成我們切換到 B 頁(yè)面的時(shí)候 OutBFragment 沒(méi)有走任何生命周期函數(shù)。只調(diào)用了 setUserVisibleHint 并傳入 true 正如上述 Log 打印一樣。 當(dāng)我們滑到 C 的時(shí)候 OutAFragment View 被回收,但 Fragment 對(duì)象本身并沒(méi)有被回收。

上述只是說(shuō)明了當(dāng)我們順序滑動(dòng)的時(shí)候片段的生命周期調(diào)用,如果我們以 A —>C 的方式進(jìn)行點(diǎn)擊切換生命周期就會(huì)發(fā)生變化。此時(shí)我們可以理解為 C 并沒(méi)被預(yù)加載而是直接初始化了,所以他不會(huì)像先前的一樣只調(diào)用 setUserVisibleHint 而是:

E/TAG: OutCFragment  unVisibleToUser
E/TAG: OutAFragment  unVisibleToUser
E/TAG: OutCFragment  isVisibleToUser

E/TAG: OutCFragment  onAttach
E/TAG: OutCFragment  onCreate
E/TAG: OutCFragment  onCreateView
E/TAG: OutCFragment  onActivityCreated
E/TAG: OutCFragment  onStart
E/TAG: OutCFragment  onResume

E/TAG: OutAFragment  onPause
E/TAG: OutAFragment  onStop
E/TAG: OutAFragment  onDestroyView

這樣生命周期大概就解釋完了。那么我們仔細(xì)想一下,我們是不是不能像 Activity 一樣簡(jiǎn)單的在 onResume 中調(diào)用網(wǎng)絡(luò)請(qǐng)求就可以實(shí)現(xiàn)界面可見(jiàn)的時(shí)候刷新了? 而對(duì)于產(chǎn)品來(lái)說(shuō)這種要求并不過(guò)分,相信一部分人已經(jīng)想到了,那么就是 Fragment 與 ViewPager 連用時(shí)的 網(wǎng)絡(luò)數(shù)據(jù)懶加載。


Fragment 與 ViewPager 連用時(shí)的 網(wǎng)絡(luò)數(shù)據(jù)懶加載

關(guān)于在 ViewPager 中為什么需要懶加載,當(dāng)然是因?yàn)?ViewPager 自身會(huì)幫我們緩存相鄰 pager 的 Fragment ,Android 本身為我們做了這件事,是為了提高 ViewPager 滑動(dòng)的流暢度,給用戶(hù)帶來(lái)更好的體驗(yàn)。那么懶加載應(yīng)用的場(chǎng)景是什么呢?

當(dāng) ViewPager 中的 Fragment 內(nèi)部有網(wǎng)絡(luò)請(qǐng)求的時(shí)候,展示數(shù)據(jù)依賴(lài)于網(wǎng)絡(luò)請(qǐng)求結(jié)果的時(shí)候,我們就應(yīng)該考慮使用懶加載方案。

試想一下,你現(xiàn)在有多個(gè)頁(yè)面包含在 Viewpager 中,并且頁(yè)面中有大量數(shù)據(jù)需要展示,提前加載這些數(shù)據(jù)勢(shì)必會(huì)對(duì)用戶(hù)造成不必要的流量損失,也會(huì)損失一部分內(nèi)存。

網(wǎng)上懶加載的例子很多,但是思想都是一樣的利用 「setUserVisibleHint」來(lái)完成的,下邊簡(jiǎn)單說(shuō)下我的實(shí)現(xiàn)方法。


創(chuàng)建 BaseLazyLoadFragment

創(chuàng)建 BaseLoadFragment 子類(lèi)只需要實(shí)現(xiàn)對(duì)應(yīng)網(wǎng)絡(luò)請(qǐng)求方法,Base 類(lèi)負(fù)責(zé)請(qǐng)求執(zhí)行的時(shí)機(jī)。上文說(shuō)到懶加載主要通過(guò) 「setUserVisibleHint」方法來(lái)判斷對(duì)應(yīng)的 Fragment 是否是用戶(hù)正在交互的片段。

  1. 對(duì)于 FragmentPagerAdapter 中所有 Fragment ,setUserVisibleHint 調(diào)用時(shí)機(jī)是在所有的生命周期之前,對(duì)于當(dāng)前所處的 pager ,isVisibleToUser = true,預(yù)加載的 pager isVisibleToUser = false。

  2. 由于 setUserVisibleHint 調(diào)用在生命周期之前,所以貿(mào)然在isVisibleToUser = true時(shí)候去請(qǐng)求數(shù)據(jù),當(dāng)網(wǎng)絡(luò)回調(diào)回來(lái)的時(shí)候可能會(huì)導(dǎo)致頁(yè)面沒(méi)初始化完畢,而造成設(shè)置數(shù)據(jù)的時(shí)候空指針的現(xiàn)象。

  3. 解決方法當(dāng)然是有的我們可以添加 view 是否創(chuàng)建完成的判斷,如下面所示,我們?cè)?inflate 完成 view 后將 onCreatedView 標(biāo)志位置位,那么被緩存的頁(yè)面下次在進(jìn)入的時(shí)候就可以在 setUserVisibleHint 中開(kāi)始網(wǎng)絡(luò)請(qǐng)求。

  4. 那么對(duì)于沒(méi)有被緩存的頁(yè)面就會(huì)在 onCreatedView 中進(jìn)行網(wǎng)絡(luò)請(qǐng)求。

你以為我現(xiàn)在肯定要放代碼了,to naive ! 對(duì)于懶加載我們還應(yīng)該考慮到實(shí)際需求,如有些界面我們只需要在頁(yè)面第一次顯示的時(shí)候請(qǐng)求數(shù)據(jù),當(dāng) Fragment 下次在可見(jiàn)的時(shí)候需要用戶(hù)手動(dòng)去刷新界面,當(dāng)然這個(gè)需求是最常見(jiàn)的,也能滿(mǎn)足大部分 Feed 流應(yīng)用的需求??墒怯行?shí)時(shí)要求比較高的頁(yè)面,比如股票類(lèi)應(yīng)用的持倉(cāng)頁(yè)面,錢(qián)數(shù)每分每秒都在變化那么只是首次進(jìn)入刷新是遠(yuǎn)遠(yuǎn)不夠的,這時(shí)候就需要每次用戶(hù)可見(jiàn)的時(shí)候都刷新一次,當(dāng)然這是舉個(gè)例子,實(shí)際上在真正的股票類(lèi)應(yīng)用持倉(cāng)頁(yè)是 socket 刷新的。所以面對(duì)千變?nèi)f化的需求就需要我們的懶加載基類(lèi)能夠支撐的起這兩種方案,我們可以暴露兩個(gè)方法給子類(lèi) Fragment

    1. 如果需求僅僅是想要在第一可見(jiàn)的時(shí)候自動(dòng)刷新 就調(diào)用 requestData
    1. 如果用戶(hù)想要每次可見(jiàn)的時(shí)候都刷新,那么就調(diào)用 requestDataAutoRefresh

這樣這兩種需求都滿(mǎn)足了。只需要根據(jù)指定頁(yè)面復(fù)寫(xiě)滿(mǎn)足需求的方法就好了。下面來(lái)看具體代碼:

public abstract class LazyLoadBaseFragment extends Fragment {

    public static final String TAG = "Fragment";

    private View rootView = null;
    private boolean isViewCreated;
    private boolean isFirstVisible = true;
    private boolean isFragmentVisible;

    /**
     * 在 FragmentPageAdapter 中的 Fragment 都會(huì)走這里兩次, 且比任何生命周期都要先走
     * 1. fragment 被預(yù)加載 此時(shí)為 false ,切換到此 Fragment  再次走這里賦值為 true
     * 2. 跨 tab 切換時(shí)候 首先也會(huì)走一次 false 然后去執(zhí)行跳轉(zhuǎn)之前 tab 的 setUserVisibleHint(false) 后去執(zhí)行臨近(左右) fragment 的
     * setUserVisibleHint(false) 最后會(huì)在調(diào)用一次當(dāng)前 tab 的 setUserVisibleHint(true)
     * <p>
     * 懶加載是為了用戶(hù)能在頁(yè)面可見(jiàn)的時(shí)候在再去請(qǐng)求數(shù)據(jù)
     * <p>
     * 實(shí)際需求有:
     * 1. 用戶(hù)第一可見(jiàn)自動(dòng)加載數(shù)據(jù),之后需要用戶(hù)手動(dòng)刷新去加載數(shù)據(jù)
     * 2. 用戶(hù)每次可見(jiàn)的時(shí)候去自動(dòng)刷新最新數(shù)據(jù)
     * <p>
     * <p>
     * 分析;
     * 我們可能認(rèn)為Adapter 中的 Fragment 和 Activity 一樣每次用戶(hù)可見(jiàn)去調(diào)用 onResume 事實(shí)上并不是這樣的,
     * 由于 FragmentPagerAdapter 存在預(yù)加載,onResume 事件當(dāng)預(yù)加載的時(shí)候已經(jīng)執(zhí)行了,如果僅考慮兩個(gè) tab 之間相互
     * 切換那么 這兩個(gè) Fragment 之后只會(huì)調(diào)用 setUserVisibleHint 當(dāng)參數(shù)為 true 的時(shí)候 Fragment 可見(jiàn),false 的時(shí)候不可見(jiàn)。
     * <p>
     * 那么回到上述需求:我們可以提供兩個(gè)方法 requestData requestDataEveryTime
     * <p>
     * 1. 如果需求僅僅是想要在第一可見(jiàn)的時(shí)候自動(dòng)刷新 就調(diào)用 requestData
     * 2. 如果用戶(hù)想要每次可見(jiàn)的時(shí)候都刷新,那么就調(diào)用 requestDataAutoRefresh
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

        isFragmentVisible = isVisibleToUser;

        //當(dāng) View 創(chuàng)建完成切 用戶(hù)可見(jiàn)的時(shí)候請(qǐng)求 且僅當(dāng)是第一次對(duì)用戶(hù)可見(jiàn)的時(shí)候請(qǐng)求自動(dòng)數(shù)據(jù)
        if (isVisibleToUser && isViewCreated && isFirstVisible) {
            Log.e(TAG, "只有自動(dòng)請(qǐng)求一次數(shù)據(jù)  requestData");
            requestData();
            requestDataAutoRefresh();
            isFirstVisible = false;

        }

        // 由于每次可見(jiàn)都需要刷新所以我們只需要判斷  Fragment 展示在用戶(hù)面面前了,view 初始化完成了 然后即可以請(qǐng)求數(shù)據(jù)了
        if (isVisibleToUser && isViewCreated) {
            // Log.e(TAG, "每次都可見(jiàn)數(shù)據(jù)  requestDataAutoRefresh");
            requestDataAutoRefresh();
        }

        if (!isVisibleToUser && isViewCreated) {
            stopRefresh();
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {

        if (rootView == null) {
            rootView = inflater.inflate(getLayoutRes(), container, false);
        }

        isViewCreated = true;

        initView(rootView);

        //Adapter 默認(rèn)展示的那個(gè) Fragment ,或者隔 tab 選中的時(shí)候,
        //由于沒(méi)有預(yù)加載tab 在 setUserVisibleHint 中 isVisibleToUser = true 的時(shí)候 view 還沒(méi)創(chuàng)建成功
        //即 isViewCreated = false 所以該 tab 的請(qǐng)求延遲到了 view 創(chuàng)建完成了
        //但是已經(jīng)緩存的 Fragment 就不可以走這里了因?yàn)轭A(yù)加載 Fragment 在 onCreateView 中 isFragmentVisible 為 false
        if (isFragmentVisible && isFirstVisible) {
            Log.e(TAG, "Adapter 默認(rèn)展示的那個(gè) Fragment ,或者隔 tab 選中的時(shí)候  requestData 推遲到 onCreateView 后 ");
            requestData();
            requestDataAutoRefresh();
            isFirstVisible = false;
        }

        return rootView;
    }


    /**
     * 只有在 Fragment 第一次對(duì)用戶(hù)可見(jiàn)的時(shí)候才去請(qǐng)求
     */
    protected void requestData() {
    }

    /**
     * 每次 Fragment 對(duì)用戶(hù)可見(jiàn)都會(huì)去請(qǐng)求
     */
    protected void requestDataAutoRefresh() {

    }

    /**
     * 當(dāng) Fragment 不可見(jiàn)的時(shí)候停止某些輪詢(xún)請(qǐng)求的時(shí)候調(diào)用該方法停止請(qǐng)求
     */
    protected void stopRefresh() {

    }

    /**
     * 返回布局 resId
     *
     * @return layoutId
     */
    protected abstract int getLayoutRes();


    /**
     * 初始化view
     *
     * @param rootView
     */
    protected abstract void initView(View rootView);

}

小結(jié)

我們這篇文章分享了 ViewPager 中 Fragment 的生命周期,以及如何實(shí)現(xiàn) Fragment 與 ViewPager 連用時(shí)的 網(wǎng)絡(luò)數(shù)據(jù)懶加載,下一篇我將會(huì)說(shuō)明 Fragment 嵌套,以及 FragmentAdapter 嵌套 FragmentAdapter 時(shí)的生命周期與注意事項(xiàng)。 本文 github 鏈接 https://github.com/ImportEffort/FragmentLiftCycle

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容