在android开发过程中,一个Activity里面可能会以Viewpager(或其他容器)与多个Fragment来组合使用(比如微信、轮播图、引导页)。而ViewPager默认会缓存三页数据,即:Viewpager每加载一个Fragment,都会预先加载此Fragment左侧或右侧的Fragment。而如果每个fragment都需要去加载数据,或从本地加载,或从网络加载,那么在这个activity刚创建的时候就变成需要初始化大量资源,浪费用户流量不止,还造成卡顿,这样的结果,我们当然不会满意。那么,能不能做到当切换到这个fragment的时候,它才去初始化呢?
为了更好的知道懒加载的实现原理,下面通过几个测试来进行逐步分析与突破。
demo是一个Activity中使用ViewPager+4个fragment的形式,viewpager加载数据的方式采取的是系统模式的.生命周期对应的日志如下:
通过上面的生命周期日志,我们发现系统的确默认会进行缓存1个界面,即当前页面的相邻1个页面都会被缓存(从右往左滑动也是类似的)。
对于viewPager实现缓存的源码如下:
......
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
......
final int pageLimit = mOffscreenPageLimit;
.....
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;
.....
}
}
这部分源码比较简单:如果你设置的缓存页面数量limit小于默认值1,就将1复制给limit;如果设置的缓存页面数量limit不等于1,将采用你设置的值作为缓存数目.由于setOffscreenPageLimit是从上往下执行的,if (limit != mOffscreenPageLimit)在这里其实是limit大于1.
(1)将limit赋值为2:
代码如下:
ViewPager vp = (ViewPager) findViewById(R.id.viewpager);
......
vp.setOffscreenPageLimit(2);
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!mUserVisibleHint && isVisibleToUser && mState < STARTED&& mFragmentManager != null && isAdded()) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
mDeferStart = mState < STARTED && !isVisibleToUser;
}
源码写得比较模糊,那我们就看看源码这个方法上的注释:
/**
* Set a hint to the system about whether this fragment's UI is currently visible
* to the user. This hint defaults to true and is persistent across fragment instance
* state save and restore.
*
* An app may set this to false to indicate that the fragment's UI is
* scrolled out of visibility or is otherwise not directly visible to the user.
* This may be used by the system to prioritize operations such as fragment lifecycle updates
* or loader ordering behavior.
*
* Note: This method may be called outside of the fragment lifecycle.
* and thus has no ordering guarantees with regard to fragment lifecycle method calls.
*
* @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
* false if it is not.
*/
注释描述得比较详细,总结起来就是:
1.当isVisibleToUser 为true则进行数据加载,当isVisibleToUser为false则不进行数据加载.
2.对于已经加载过数据的fragment,再次被滑动到也不在进行加载数据,也就是每个fragment仅做一次数据加载工作.
有的人可能说,我的英语很菜,四级都没过,如果是这样的话,那就直接打开在线翻译工具,把对应的英语贴上去,就有对应的中文翻译.虽然翻译得不是那么顺畅,但基本意思还是看得出来的.
一般需要使用到fragment的情况,往往都是多个fragment一起使用.我们建立一个fragment的基类,然后在基类上进行操作(源码中的BaseLazyFragment).
关于Base的封装,不属于本篇博客的讲解内容,这里将核心代码贴出来(完整代码见源码链接),并进行讲解:
onCreateView()方法:
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
convertView = inflater.inflate(getLayoutId(), container, false);
......
initView();
isInitView = true;
lazyLoadData();
return convertView;
}
重写setUserVisibleHint()方法:
if (isVisibleToUser) {
isVisible = true;
lazyLoadData();
} else {
isVisible = false;
}
super.setUserVisibleHint(isVisibleToUser);
如果某界面可见的话,加载数据;否则不进行加载.
加载数据的代码如下:
private void lazyLoadData() {
if (isFirstLoad) {
Log.e(TAG, "第一次加载 " + " isInitView " + isInitView + ".............." + "isVisible" + isVisible + ".............." + this.getClass().getSimpleName());
} else {
Log.e(TAG, "不是第一次加载 " + " isInitView " + isInitView
+ ".............." + "isVisible " + isVisible + ".............." + this.getClass().getSimpleName());
}
if (!isFirstLoad || !isVisible || !isInitView) {
Log.e(TAG, "不加载" + ".............." + this.getClass().getSimpleName());
return;
}
Log.e(TAG, "完成数据第一次加载" + ".............." + this.getClass().getSimpleName());
initData();
isFirstLoad = false;
}
上述代码同样使用了一个技巧,通过isFirstLoad的boolean值进行判断.!isFirstLoad || !isVisible || !isInitView 这三个条件只有满足一个,以后的代码就不执行.如果往下执行了,才初始化数据.
另外,有一个细节必须注意:
initView方法是在onCreateView中调用,而initData只有执行过onCreateView才会调用,这样的顺序安排就不会导致在initData中执行数据加载过程,找不到需要的view而报错。
相关日志信息如下图:
看完图4和图5,你是不是整个人都不好了?费了那么多劲重写setUserVisibleHint()方法,生命周期和默认情况(图1、图2)是一样的,并没有实现赖加载呀.别急呀,我们看一下BaseLazyFragment的日志:
可以看出,fragment只会执行一次initData,从而实现懒加载功能这一需求.