懒加载的本意就是,让界面显示的时候再去加载数据。
对于Fragment来说,他的onCreateView()方法被执行了,界面才会出来。
ViewPager+Fragment模式
我们一般的做法是这样的
onCreate(){
mViewPager.setAdapter((new FragmentStatePagerAdapter(...));
mViewPager.setCurrentItem(1);
}
在这里,要稍微的解释下,这两个方法究竟在ViewPager中做了什么。
刚学Android经常犯一个错误,就是在onCreate()方法中去拿某个View的宽高,比如,在这里mViewPager.getWidth()肯定为0,那么为什么为0呢?一般的解释是此时整个View树还没有完成测量、布局的操作。那么View树的第一次测量布局究竟是什么时候执行的呢?
为了解释我这个困惑,尝试着在onResume()中去拿到ViewPager的宽高,不出所料,肯定还是0。
View树的第一次测量布局
在ActivityThread的handleResumeActivity()中,在之前的onCreate()中,整个View树的数据已经创建出来,但是还没有显示出来,所以,在这里,执行一个IPC操作,将View树添加到WMS中,那么在客户端进程中的流程是ViewRootImpl的setView()--->requestLayout(),该方法发送一个scheduleTraversals()的异步任务,注意,也就是是说,当Activity的onResume()方法执行时,只是发送了一个异步的scheduleTraversals任务到UI队列中去,要等到下一次UI线程处理队列中的这个任务时,才会执行。所以,在onResume()中也无法拿到宽高。
setAdapter()方法
经过上面的分析,也就是说setAdapter()执行的时候,界面还没有出来。
public void setAdapter(...){
//......
if(!wasFirstLayout) { //是否是第一次布局,默认为true,当onLayout执行后赋值为false,所以这里会执行requestLayout()方法,发送一个异步布局消息。
populate();
} else {
requestLayout();
}
}
假设后面我们执行了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);
}
}
这里会执行if中的代码,也就是该方法确定了mCurItem,也就是界面显示的时候究竟显示哪一个,此时是1。然后,也是发送了一个异步布局消息。
测量布局
onMeasure()方法
// Make sure we have created all fragments that we need to have shown.
mInLayout = true;
populate(); //根据适配器提供的数据,创建出相关数据,便于后面的绘制
mInLayout = false;
// Page views next.
size = getChildCount(); //当populate方法执行完后,子孩子已经被添加到ViewPager中去
setUserVisibleHint()什么时候执行
populate()--->addItem--->mAdapter.instantiateItem()--->setUserVisibleHint(false)
void populate(){
//根据pageOffsize等参数算出mCurItem,也就是当前显示界面的位置,默认为0,然后将0-mCurItem之间的Fragment全部初始化出来,addItem()方法不断调用(此时还没有去创建Fragment对象,只是内部保存了一个ItemInfo的数组信息)
//设置要显示的那个item,要显示的Fragment的setUserVisibleHint方法被调用
mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
//执行创建Fragment的操作,由FragmentPageAdapter实现
mAdapter.finishUpdate(this);
}
也就是当第一次确定要显示哪一个Fragment的时候,其实Fragment这个对象还并没有创建出来。那么懒加载在Fragment中应该考虑到这一点。
/**
* 进行懒加载
*/
private void lazyFetchDataIfPrepared() {
// 用户可见fragment && 没有加载过数据 && 视图已经准备完毕
if (getUserVisibleHint() && !hasFetchData && isViewPrepared) {
hasFetchData = true;
lazyFetchData();
}
}