ViewPager<第五篇>:預(yù)加載和懶加載

預(yù)加載

ViewPager為什么讓滑動流暢敢订,默認(rèn)將左右兩個頁面加載到了內(nèi)存,這叫做ViewPager的預(yù)加載罢吃,但是往往會遇到一些需求尿招,要求每次切換頁面都會重新更新當(dāng)前頁面的UI怪蔑。

那么丧荐,為了滿足這個需求弓坞,有沒有什么辦法禁止ViewPager預(yù)加載的特性呢渡冻?

ViewPager有個setOffscreenPageLimit方法可以設(shè)置預(yù)加載頁面的個數(shù)族吻,方法如下:

viewpager.setOffscreenPageLimit(1);

這個方法的源碼如下:

/**
 * Set the number of pages that should be retained to either side of the
 * current page in the view hierarchy in an idle state. Pages beyond this
 * limit will be recreated from the adapter when needed.
 *
 * <p>This is offered as an optimization. If you know in advance the number
 * of pages you will need to support or have lazy-loading mechanisms in place
 * on your pages, tweaking this setting can have benefits in perceived smoothness
 * of paging animations and interaction. If you have a small number of pages (3-4)
 * that you can keep active all at once, less time will be spent in layout for
 * newly created view subtrees as the user pages back and forth.</p>
 *
 * <p>You should keep this limit low, especially if your pages have complex layouts.
 * This setting defaults to 1.</p>
 *
 * @param limit How many pages will be kept offscreen in an idle state.
 */
public void setOffscreenPageLimit(int limit) {
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
        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();
    }
}

從源碼中獲取到的信息是:

  • ViewPager預(yù)加載頁面的數(shù)量默認(rèn)是1切平;
  • 如果將limit設(shè)置成0悴品,那么則強(qiáng)制設(shè)置成1苔严;
  • 如果設(shè)置成n届氢,則緩存當(dāng)前頁面的左右各n個頁面退子;(n > 0)

所以荐虐,通過setOffscreenPageLimit這個方法根本無法禁止ViewPager的預(yù)加載福扬。那么铛碑,只能從Fragment著手汽烦,F(xiàn)ragment有一種懶加載的概念可以滿足這個需求。

懶加載

以前的方案是這樣的庐船,如下:

【第一步】 Adapter構(gòu)造方法

public FragmentStatePagerAdapter(@NonNull FragmentManager fm) {
    this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}

@Deprecated
public FragmentPagerAdapter(@NonNull FragmentManager fm) {
    this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}

聲明Adapter時使用一個參數(shù)的構(gòu)造方法如叼。

【第二步】 在自定義Fragment中初始化基本參數(shù)

//是否已經(jīng)初始化,是否執(zhí)行了onCreateView
private boolean isInit = false;
//是否正在加載
private boolean isLoad = true;

【第三步】 在onCreateView中處理

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

    rootView = inflater.inflate(R.layout.item_base, container, false) ;

    //處理預(yù)加載問題,讓fragment懶加載
    isInit = true;
    //初始化的時候去加載數(shù)據(jù)
    loadData();

    return rootView;
}

【第四步】 重寫setUserVisibleHint方法人乓,以及處理

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);

    loadData();
}

【第五步】 loadData方法實現(xiàn)

/**
 * 加載數(shù)據(jù)
 */
private void loadData() {
    //視圖沒有初始化
    if (!isInit) {
        return;
    }

    //判斷視圖對用戶是否可見
    if (getUserVisibleHint()) {
        //懶加載
        lazyLoad();
        isLoad = true;
    } else {
        if (isLoad) {
            //停止加載
            stopLoad();
        }
    }

}

【第六步】 lazyLoad方法實現(xiàn)

/**
 * 當(dāng)視圖初始化并對用戶可見的時候去真正的加載數(shù)據(jù)
 */
protected void lazyLoad() {
    //里面開始對頁面進(jìn)行數(shù)據(jù)加載
    mContent = (String) getArguments().get("content");
    TextView textView = (TextView) rootView.findViewById(R.id.tv);
    textView.setText(mContent);
}

【第七步】 stopLoad方法實現(xiàn)

/**
 * 當(dāng)視圖已經(jīng)對用戶不可見并且加載過數(shù)據(jù)色罚,如果需要在切換到其他頁面時停止加載數(shù)據(jù)账劲,可以覆寫此方法
 */
protected void stopLoad() {
    //讓已經(jīng)在加載過數(shù)據(jù)并不可見的頁面停止加載(例如 視頻播放時切換過去不可見時,要讓它停止播放)

}

【第八步】 銷毀時的處理

@Override
public void onDestroy() {
    super.onDestroy();

    isInit = false;
    isLoad = false;
}

在Android 9.0之前瀑焦,重寫Fragment的setUserVisibleHint方法可以得到isVisibleToUser參數(shù)榛瓮,這個參數(shù)可以控制UI的顯示和隱藏,進(jìn)而可以實現(xiàn)Fragment的懶加載(延遲加載)禀晓,但是自從Android9.0之后,AndroidX也隨之誕生驻右,setUserVisibleHint方法已被棄用堪夭,被FragmentTransaction的setMaxLifecycle替代。所以森爽,F(xiàn)ragment的懶加載有了新的方案爬迟。

setMaxLifecycle定義在FragmentTransaction中,和之前的add计福、attach徽职、remove、detach说订、show潮瓶、hide等方法是并列關(guān)系;

我們看下源碼:

/**
 * Set a ceiling for the state of an active fragment in this FragmentManager. If fragment is
 * already above the received state, it will be forced down to the correct state.
 *
 * <p>The fragment provided must currently be added to the FragmentManager to have it's
 * Lifecycle state capped, or previously added as part of this transaction. The
 * {@link Lifecycle.State} passed in must at least be {@link Lifecycle.State#CREATED}, otherwise
 * an {@link IllegalArgumentException} will be thrown.</p>
 *
 * @param fragment the fragment to have it's state capped.
 * @param state the ceiling state for the fragment.
 * @return the same FragmentTransaction instance
 */
@NonNull
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
        @NonNull Lifecycle.State state) {
    addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
    return this;
}

在AndroidX中埂伦,Adapter的構(gòu)造方法也發(fā)生了變化

@Deprecated
public FragmentStatePagerAdapter(@NonNull FragmentManager fm) {
    this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}

@Deprecated
public FragmentPagerAdapter(@NonNull FragmentManager fm) {
    this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}

我們發(fā)現(xiàn)思恐,這兩個方法已經(jīng)被廢棄,被兩個參數(shù)的構(gòu)造方法替代,如下:

public FragmentStatePagerAdapter(@NonNull FragmentManager fm,
        @Behavior int behavior) {
    mFragmentManager = fm;
    mBehavior = behavior;
}

public FragmentPagerAdapter(@NonNull FragmentManager fm,
        @Behavior int behavior) {
    mFragmentManager = fm;
    mBehavior = behavior;
}

在AndroidX中嗜逻,F(xiàn)ragmentPagerAdapter和FragmentStatePagerAdapter的構(gòu)造方法的第二個參數(shù)是一個Behavior缭召,這個值有兩種可能:BEHAVIOR_SET_USER_VISIBLE_HINT逆日、BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT萄凤。

如果繼續(xù)使用帶有一個參數(shù)的構(gòu)造方法靡努,Behavior默認(rèn)取值為BEHAVIOR_SET_USER_VISIBLE_HINT,當(dāng)然兽泄,在AndroidX中漾月,Behavior的取值需要指定為BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT

假如蜓陌,我們現(xiàn)在使用的是AndroidX吩蔑,在FragmentPagerAdapter或FragmentStatePagerAdapter方法中instantiateItem方法,源碼如下:

@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
    // If we already have this item instantiated, there is nothing
    // to do.  This can happen when we are restoring the entire pager
    // from its saved state, where the fragment manager has already
    // taken care of restoring the fragments we previously had instantiated.
    if (mFragments.size() > position) {
        Fragment f = mFragments.get(position);
        if (f != null) {
            return f;
        }
    }

    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }

    Fragment fragment = getItem(position);
    if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
    if (mSavedState.size() > position) {
        Fragment.SavedState fss = mSavedState.get(position);
        if (fss != null) {
            fragment.setInitialSavedState(fss);
        }
    }
    while (mFragments.size() <= position) {
        mFragments.add(null);
    }
    fragment.setMenuVisibility(false);
    if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
        fragment.setUserVisibleHint(false);
    }

    mFragments.set(position, fragment);
    mCurTransaction.add(container.getId(), fragment);

    if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
        mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
    }

    return fragment;
}

我們會發(fā)現(xiàn),如果Behavior取值為BEHAVIOR_SET_USER_VISIBLE_HINT厌秒,則使用

fragment.setUserVisibleHint(true|false)

如果Behavior取值為BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT擅憔,則使用

mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);

所以,在Fragment的UI加載之前蚌讼,F(xiàn)ragment的生命周期就被指定為Lifecycle.State.STARTED个榕,此時執(zhí)行Fragment的onStart生命周期。

當(dāng)Viewpager切換頁面時凰萨,會執(zhí)行到Adapter的setPrimaryItem方法,源碼如下:

@SuppressWarnings({"ReferenceEquality", "deprecation"})
@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    Fragment fragment = (Fragment)object;
    if (fragment != mCurrentPrimaryItem) {
        if (mCurrentPrimaryItem != null) {
            mCurrentPrimaryItem.setMenuVisibility(false);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                if (mCurTransaction == null) {
                    mCurTransaction = mFragmentManager.beginTransaction();
                }
                mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
            } else {
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
        }
        fragment.setMenuVisibility(true);
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
        } else {
            fragment.setUserVisibleHint(true);
        }

        mCurrentPrimaryItem = fragment;
    }
}

該方法只告訴我們:當(dāng)切換到當(dāng)前Fragment時,執(zhí)行

mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);

將Fragment的生命周期切換到onResume冶忱,執(zhí)行onResume方法境析。

結(jié)論:從源碼中得到的結(jié)論是,F(xiàn)ragment數(shù)據(jù)的初始化應(yīng)當(dāng)在onResume方法中執(zhí)行眶拉,可實現(xiàn)懶加載憔儿。

下面開始代碼實現(xiàn):

【第一步】 構(gòu)造方法

public FragmentPagerAdapter(@NonNull FragmentManager fm,
        @Behavior int behavior) {
    mFragmentManager = fm;
    mBehavior = behavior;
}

public FragmentStatePagerAdapter(@NonNull FragmentManager fm,
        @Behavior int behavior) {
    mFragmentManager = fm;
    mBehavior = behavior;
}

需要注意的是,第二個參數(shù)behavior取值必須是BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT朝刊。

【第二步】 在Fragment中重寫onResume方法蜈缤,加載數(shù)據(jù)

@Override
public void onResume() {
    super.onResume();
    //懶加載
    lazyLoad();
}

【第三步】 在Fragment中重寫onPause方法底哥,處理隱藏頁面的邏輯

@Override
public void onPause() {
    super.onPause();
    //停止加載
    stopLoad();
}
綜上所述

上面利用兩種方法實現(xiàn)Fragment的懶加載,前者通過setUserVisibleHint來獲取Fragment的可見和非可見狀態(tài)续滋,整理邏輯稍微麻煩了點孵奶。后者在AndroidX才可以使用,通過生命周期的方式實現(xiàn)數(shù)據(jù)的懶加載朗恳,當(dāng)Fragment不可見時執(zhí)行onPause方法载绿,當(dāng)Fragment可見時,執(zhí)行onResume方法怀浆。

[本章完...]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末揉稚,一起剝皮案震驚了整個濱河市熬粗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌灌诅,老刑警劉巖含末,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佣盒,死亡現(xiàn)場離奇詭異,居然都是意外死亡盯仪,警方通過查閱死者的電腦和手機(jī)蜜葱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門牵囤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人揭鳞,你說我怎么就攤上這事汹桦。” “怎么了钥弯?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵督禽,是天一觀的道長。 經(jīng)常有香客問我睛蛛,道長,這世上最難降的妖魔是什么荸频? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任旭从,我火速辦了婚禮场仲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鸽素。我一直安慰自己亦鳞,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布舵匾。 她就那樣靜靜地躺著坐梯,像睡著了一般刹帕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹋辅,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天挫掏,我揣著相機(jī)與錄音,去河邊找鬼褒傅。 笑死袄友,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的支竹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼馒吴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤莹捡,失蹤者是張志新(化名)和其女友劉穎扣甲,沒想到半個月后琉挖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡寥茫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年纱耻,在試婚紗的時候發(fā)現(xiàn)自己被綠了险耀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡蘑志,死狀恐怖贬派,靈堂內(nèi)的尸體忽然破棺而出搞乏,到底是詐尸還是另有隱情,我是刑警寧澤查描,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站匀油,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏敌蚜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纷跛。 院中可真熱鬧,春花似錦唬血、人聲如沸唤崭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兜挨。三九已至眯分,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間噪舀,已是汗流浹背飘诗。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留纺座,地道東北人溉潭。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像馋贤,于是被迫代替她去往敵國和親配乓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容