預(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方法怀浆。
[本章完...]