Viewpager和Fragment的懒加载机制
一、背景
ViewPager默认加载当前页面,而且预加载当前页面的前后两个页面数据(即同时会加载3个页面的数据),如果3个页面都有网络请求,就会影响性能,影响用户体验,由此,懒加载就尤为重要了,可以在滑到哪个fragment的时候,该fragment再进行网络请求。
二、懒加载
所谓的懒加载,其实也就是延迟加载,就是等到该页面的UI展示给用户时,再加载该页面的数据(从网络、数据库等),而不是依靠ViewPager预加载机制提前加载两三个,甚至更多页面的数据.这样可以提高所属Activity的初始化速度,也可以为用户节省流量。
三、懒加载方案
1、通过ViewPager的setOffscreenPageLimit(int limit)
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
...
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();
}
}
该方法不可行,因为查看源码发现无论怎么设置,默认预加载页面数都是1,也就是说即使limit设置为0,最终还是默认为1。
2、setUserVisibleHint(boolean isVisibleToUser)
Fragment为我们提供了一个方法setUserVisibleHint(boolean isVisibleToUser),其中的参数isVisibleToUser就是表示该Fragment的UI对于用户是否可见。
所以可以重写此方法,然后根据回调的isVisibleToUser参数来进行相关的逻辑判断,以达到懒加载的效果。如果isVisibleToUser为true的话表示当前Fragment对用户可见,此时再去加载页面数据。由于ViewPager内会装载多个Fragment,而这种懒加载机制对于各个Fragment属于共同操作,因此适合将其抽取到BaseFragment中.
但是注意:
setUserVisibleHint(boolean isVisibleToUser)方法会多次回调,而且可能会在onCreateView()方法执行完毕之前回调。如果isVisibleToUser为true,然后进行数据加载和控件数据填充,但是onCreateView()方法并未执行完毕,此时就会出现NullPointerException空指针异常.
基于以上原因,进行数据懒加载的时机需要满足两个条件:
-
onCreateView()方法执行完毕
-
setUserVisibleHint(boolean isVisibleToUser)方法返回true
所以在BaseFragment中用两个布尔型标记来记录这两个条件的状态.只有同时满足了,才能加载数据。
四、懒加载的实现
public abstract class BaseFragment extends Fragment {
// 视图加载完成标志位
private boolean isViewCreated;
// 视图对用户可见标志位
private boolean isVisible;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(getFragmentView(), container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// fragment视图加载完成,设置视图加载完成标志位
isViewCreated = true;
// 可以开启懒加载
lazyLoad();
}
// 该方法是在onCreate方法之前调用的
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser){
isVisible = true;
lazyLoad();
}else {
isVisible = false;
}
}
private void lazyLoad(){
/**
* 牢记懒加载的2个重要条件:
* 1、fragment视图已经加载完成
* 2、fragment视图对用户可见了
*
* 所以,根据2个标志位进行判断
*/
if (isViewCreated && isVisible) {
loadData();
// 数据加载后重置标志位,避免重复加载
isVisible = false;
isViewCreated = false;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
// fragment视图销毁时也重置标志位
isVisible = false;
isViewCreated = false;
}
protected abstract void loadData();
protected abstract int getFragmentView();
}