ViewPager如何通過預(yù)加載機(jī)制提高滑動(dòng)性能,以及如何屏蔽預(yù)加載策略的方法
ViewPager默認(rèn)具有預(yù)加載機(jī)制,會(huì)預(yù)先加載當(dāng)前頁面前后的一定數(shù)量的頁面,以便在滑動(dòng)時(shí)能夠迅速顯示新頁面。預(yù)加載的數(shù)量可以通過setOffscreenPageLimit(int limit)方法來設(shè)置,但最小值通常為1,意味著前后各預(yù)加載一個(gè)頁面。
public void setOffscreenPageLimit(int limit) {
if (limit < 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();
}
}
下面看看預(yù)加載的是什么,在預(yù)加載數(shù)量更新的時(shí)候,執(zhí)行polulate()方法。
void populate(int newCurrentItem) {
ViewPager.ItemInfo oldCurInfo = null;
if (this.mCurItem != newCurrentItem) {
oldCurInfo = this.infoForPosition(this.mCurItem);
this.mCurItem = newCurrentItem;
}
if (this.mAdapter == null) {
this.sortChildDrawingOrder();
} else if (this.mPopulatePending) {
this.sortChildDrawingOrder();
} else if (this.getWindowToken() != null) {
this.mAdapter.startUpdate(this);//被棄用了
int pageLimit = this.mOffscreenPageLimit;//當(dāng)前的緩存頁面?zhèn)€數(shù)
int startPos = Math.max(0, this.mCurItem - pageLimit);//計(jì)算緩存的開始位置
int N = this.mAdapter.getCount();//adapter的子內(nèi)容的數(shù)量
int endPos = Math.min(N - 1, this.mCurItem + pageLimit);//計(jì)算緩存的結(jié)束位置
if (N != this.mExpectedAdapterCount) {
String resName;
try {
resName = this.getResources().getResourceName(this.getId());
} catch (NotFoundException var17) {
resName = Integer.toHexString(this.getId());
}
throw new IllegalStateException("The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: " + this.mExpectedAdapterCount + ", found: " + N + " Pager id: " + resName + " Pager class: " + this.getClass() + " Problematic adapter: " + this.mAdapter.getClass());
} else {
int curIndex = true;
ViewPager.ItemInfo curItem = null;
//開始去找 ViewPager.ItemInfo
int curIndex;
for(curIndex = 0; curIndex < this.mItems.size(); ++curIndex) {
ViewPager.ItemInfo ii = (ViewPager.ItemInfo)this.mItems.get(curIndex);
if (ii.position >= this.mCurItem) {
if (ii.position == this.mCurItem) {
curItem = ii;
}
break;
}
}
//沒找到就去創(chuàng)建 ViewPager.ItemInfo,并放入ArrayList<ViewPager.ItemInfo> mItems中
if (curItem == null && N > 0) {
curItem = this.addNewItem(this.mCurItem, curIndex);
}
int itemIndex;
ViewPager.ItemInfo ii;
int i;
·····一些計(jì)算操作,省略
}
this.mAdapter.finishUpdate(this);//完成條目的更新
int childCount = this.getChildCount();
for(itemIndex = 0; itemIndex < childCount; ++itemIndex) {
View child = this.getChildAt(itemIndex);
ViewPager.LayoutParams lp = (ViewPager.LayoutParams)child.getLayoutParams();
lp.childIndex = itemIndex;
if (!lp.isDecor && lp.widthFactor == 0.0F) {
ViewPager.ItemInfo ii = this.infoForChild(child);
if (ii != null) {
lp.widthFactor = ii.widthFactor;
lp.position = ii.position;
}
}
}
this.sortChildDrawingOrder();
}
}
ViewPager通過計(jì)算當(dāng)前頁面的索引(mCurItem)和預(yù)加載頁面的限制數(shù)量(mOffscreenPageLimit或pageLimit)來確定需要預(yù)加載的頁面范圍。通過Math.max(0, mCurItem - pageLimit)計(jì)算預(yù)加載的起始頁面索引(startPos),通過Math.min(N-1, mCurItem + pageLimit)計(jì)算預(yù)加載的結(jié)束頁面索引(endPos),N是頁面總數(shù)。在這個(gè)范圍內(nèi),ViewPager會(huì)提前加載并創(chuàng)建頁面實(shí)例,以減少用戶滑動(dòng)到這些頁面時(shí)的加載時(shí)間。
當(dāng)ViewPager的當(dāng)前頁面改變時(shí),會(huì)根據(jù)新的當(dāng)前頁面索引和預(yù)加載范圍來加載或銷毀頁面實(shí)例。如果某個(gè)頁面實(shí)例不再處于預(yù)加載范圍內(nèi),它將被銷毀以釋放內(nèi)存。
ViewPager.ItemInfo addNewItem(int position, int index) {
ViewPager.ItemInfo ii = new ViewPager.ItemInfo();
ii.position = position;
ii.object = this.mAdapter.instantiateItem(this, position);
ii.widthFactor = this.mAdapter.getPageWidth(position);
if (index >= 0 && index < this.mItems.size()) {
this.mItems.add(index, ii);
} else {
this.mItems.add(ii);
}
return ii;
}
調(diào)用instantiateItem來創(chuàng)建object,在FragmentPagerAdapter的instantiateItem這個(gè)方法中,創(chuàng)建的是Fragment,即緩存的就是Fragment,F(xiàn)ragment在創(chuàng)建時(shí),會(huì)有UI操作,網(wǎng)絡(luò)操作,在還未可見的時(shí)候初始化Fragment非常消耗性能,所以應(yīng)該懶加載的方式來加載,不去緩存Fragment。
ViewPager屏蔽預(yù)加載方式:
- 「設(shè)置setOffscreenPageLimit(int limit)方法的limit為0」:
理論上,這是最直接的方法,實(shí)際上setOffscreenPageLimit(int limit)方法中的limit值有一個(gè)最小值限制,即使你設(shè)置為0,也會(huì)自動(dòng)調(diào)整為1。因?yàn)閂iewPager的設(shè)計(jì)初衷就是為了預(yù)加載相鄰的頁面以提高滑動(dòng)性能。
- 「自定義ViewPager」:
「方法一」:復(fù)制ViewPager的源代碼,并修改DEFAULT_OFFSCREEN_PAGES常量的值為0。
「方法二」:繼承ViewPager類,重寫setOffscreenPageLimit(int limit)方法,利用反射修改mOffscreenPageLimit屬性的值。這種方法可能涉及到對(duì)Android系統(tǒng)內(nèi)部實(shí)現(xiàn)的深入了解,并且可能隨著Android版本的更新而失效。
- 「調(diào)整PagerAdapter」:
可以通過調(diào)整PagerAdapter中的instantiateItem和destroyItem方法來控制頁面的加載和銷毀。例如,可以在這些方法中實(shí)現(xiàn)更精細(xì)的頁面管理策略,以減少不必要的資源消耗。
- 「懶加載技術(shù)」:
在Fragment或頁面內(nèi)容中使用懶加載技術(shù)。頁面只有在真正需要顯示時(shí)才會(huì)被加載。這可以通過在Fragment的setUserVisibleHint(boolean isVisibleToUser)方法中判斷頁面是否可見來實(shí)現(xiàn)。
- 「注意事項(xiàng)」:
屏蔽預(yù)加載可能會(huì)影響ViewPager的滑動(dòng)性能,當(dāng)用戶滑動(dòng)到新的頁面時(shí),該頁面可能需要花費(fèi)更長(zhǎng)的時(shí)間來加載。
在決定屏蔽預(yù)加載之前,權(quán)衡好性能和資源消耗之間的平衡。