懶加載的本意就是,讓界面顯示的時候再去加載數(shù)據(jù)。
對于Fragment來說,他的onCreateView()方法被執(zhí)行了,界面才會出來。
ViewPager+Fragment模式
我們一般的做法是這樣的
onCreate(){
mViewPager.setAdapter((new FragmentStatePagerAdapter(...));
mViewPager.setCurrentItem(1);
}
在這里,要稍微的解釋下,這兩個方法究竟在ViewPager中做了什么。
剛學(xué)Android經(jīng)常犯一個錯誤,就是在onCreate()方法中去拿某個View的寬高,比如,在這里mViewPager.getWidth()肯定為0,那么為什么為0呢?一般的解釋是此時整個View樹還沒有完成測量、布局的操作。那么View樹的第一次測量布局究竟是什么時候執(zhí)行的呢?
為了解釋我這個困惑,嘗試著在onResume()中去拿到ViewPager的寬高,不出所料,肯定還是0。
View樹的第一次測量布局
在ActivityThread的handleResumeActivity()中,在之前的onCreate()中,整個View樹的數(shù)據(jù)已經(jīng)創(chuàng)建出來,但是還沒有顯示出來,所以,在這里,執(zhí)行一個IPC操作,將View樹添加到WMS中,那么在客戶端進(jìn)程中的流程是ViewRootImpl的setView()--->requestLayout(),該方法發(fā)送一個scheduleTraversals()的異步任務(wù),注意,也就是是說,當(dāng)Activity的onResume()方法執(zhí)行時,只是發(fā)送了一個異步的scheduleTraversals任務(wù)到UI隊(duì)列中去,要等到下一次UI線程處理隊(duì)列中的這個任務(wù)時,才會執(zhí)行。所以,在onResume()中也無法拿到寬高。
setAdapter()方法
經(jīng)過上面的分析,也就是說setAdapter()執(zhí)行的時候,界面還沒有出來。
public void setAdapter(...){
//......
if(!wasFirstLayout) { //是否是第一次布局,默認(rèn)為true,當(dāng)onLayout執(zhí)行后賦值為false,所以這里會執(zhí)行requestLayout()方法,發(fā)送一個異步布局消息。
populate();
} else {
requestLayout();
}
}
假設(shè)后面我們執(zhí)行了setCurrentItem(1)方法,同樣
public void setCurrentItem(){
if (mFirstLayout) { //此時為true
// We don't have any idea how big we are yet and shouldn't have any pages either.
// Just set things up and let the pending layout handle things.
mCurItem = item;
// ......
requestLayout();
} else {
populate(item);
scrollToItem(item, smoothScroll, velocity, dispatchSelected);
}
}
這里會執(zhí)行if中的代碼,也就是該方法確定了mCurItem,也就是界面顯示的時候究竟顯示哪一個,此時是1。然后,也是發(fā)送了一個異步布局消息。
測量布局
onMeasure()方法
// Make sure we have created all fragments that we need to have shown.
mInLayout = true;
populate(); //根據(jù)適配器提供的數(shù)據(jù),創(chuàng)建出相關(guān)數(shù)據(jù),便于后面的繪制
mInLayout = false;
// Page views next.
size = getChildCount(); //當(dāng)populate方法執(zhí)行完后,子孩子已經(jīng)被添加到ViewPager中去
setUserVisibleHint()什么時候執(zhí)行
populate()--->addItem--->mAdapter.instantiateItem()--->setUserVisibleHint(false)
void populate(){
//根據(jù)pageOffsize等參數(shù)算出mCurItem,也就是當(dāng)前顯示界面的位置,默認(rèn)為0,然后將0-mCurItem之間的Fragment全部初始化出來,addItem()方法不斷調(diào)用(此時還沒有去創(chuàng)建Fragment對象,只是內(nèi)部保存了一個ItemInfo的數(shù)組信息)
//設(shè)置要顯示的那個item,要顯示的Fragment的setUserVisibleHint方法被調(diào)用
mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
//執(zhí)行創(chuàng)建Fragment的操作,由FragmentPageAdapter實(shí)現(xiàn)
mAdapter.finishUpdate(this);
}
也就是當(dāng)?shù)谝淮未_定要顯示哪一個Fragment的時候,其實(shí)Fragment這個對象還并沒有創(chuàng)建出來。那么懶加載在Fragment中應(yīng)該考慮到這一點(diǎn)。
/**
* 進(jìn)行懶加載
*/
private void lazyFetchDataIfPrepared() {
// 用戶可見fragment && 沒有加載過數(shù)據(jù) && 視圖已經(jīng)準(zhǔn)備完畢
if (getUserVisibleHint() && !hasFetchData && isViewPrepared) {
hasFetchData = true;
lazyFetchData();
}
}