1,FragmentPagerAdapter, FragmentStatePagerAdapter 的区别
FragmentPagerAdapter 中, 即使fragment不可见了, 他的视图可能会 destory(执行 onViewDestory, 是否执行与setOffscreenPageLimit 方法设置的值有关), 但是他的实例仍然会存在于内存中. 当较多的fragment时, 会占用较大的内存.
FragmentSatePagerAdapter 中, 当fragment不可见时, 可能会将fragment的实例也销毁(执行 onDestory, 是否执行与setOffscreenPageLimit 方法设置的值有关). 所以内存开销会小些, 适合多fragment的情形.
2.setOffscreenPageLimit 方法分析与 Fragment生命周期分析
setOffscreenPageLimit 方法设置的默认值是1.这个设置的值有两层含义: 一是 ViewPager 会预加载几页; 二是 ViewPager 会缓存 2*n+1 页(n为设置的值).
明白了setOffscreenPageLimit 方法的含义后就明白了在 ViewPager 中Fragment的生命周期了:在 FragmentPagerAdapter 中 setOffscreenPageLimit 的值影响的是 onViewDestory 方法.当缓存的 fragment 超过 setOffscreenPageLimit 设置的值后, 那些 fragment 的onViewDestory 方法会回调; 在 FragmentStatePagerAdapter 中, 当缓存的 fragment 超过 setOffscreenPageLimit 设置的值后, 那些 fragment 的onDestory 方法会回调 .
总结起来就一句话: ViewPager 中的 fragment 是否执行 onViewDestory 或者 onDestory 与 setOffscreenPageLimit 方法设置的值有关.
3,懒加载实现思路
由于ViewPager 的setScreenPageLimit 方法默认值是 1 ,所以在 ViewPager 中至少会预加载一页. 当然了预加载页面是有好处的, 但是对于页面的数据, 那就没有必要去预加载了, 如果当前加载的页面有多个请求, 又预加载下个页面的请求, 导致多个任务等待, 这会影响到当前页面的请求速度. 所以合理的做法是当页面对于用户可见时再去请求数据.
可能有的同学会想到, 那就在 Fragment 的 onResume 去做请求数据的操作不就行了吗! 如果真是这样 easy, 那就没必要写这篇文章了~~ 因为有预加载, 下一页的 fragment 的 onStart, onResume 方法都会执行. 怎么办?
Fragment 里面有个方法 setUserVisibleHint(boolean isVisibleToUser) 方法. 这个方法可以判断当前页面是否对用户可见. 但是这个方法有点坑, 如果你直接在这个回调方法里面做请求, 那就坑你了,哈哈~~
setUserVisibleHint(boolean isVisibleToUser) 这个方法的回调不是和fragment 的生命周期方法同步的, 也就是说该方法的调用有可能会在Fragemnt的onCreateView()方法被调用之前调用,这样一来就会出现NullPointerException空指针异常。所以就需要满足控件初始化完成,用户可见,之后才能加载数据。
有了上面的思路我们封装基类如下:
public abstract class BaseLazyLoadFragment extends Fragment {
private boolean isInitView = false; // 是否完成了初始化的标识
protected View view;
private Unbinder bind;
protected Activity mActivity;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.mActivity = getActivity();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(setLayoutResourceID(),container,false);//让子类实现初始化视图
bind = ButterKnife.bind(this, view);
// 注册
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
}
initView();//初始化事件
isInitView = true;//视图创建完成,将变量置为true
if (getUserVisibleHint()) {//如果Fragment可见进行数据加载
onLazyLoad();
isInitView = false;
}
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind.unbind(); // 解绑butterknife
isInitView = false;//视图销毁将变量置为false
}
@Override
public void onDestroy() {
super.onDestroy();
// 取消注册
EventBus.getDefault().unregister(this);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isInitView && isVisibleToUser) {//视图变为可见并且是第一次加载
onLazyLoad();
isInitView = false;
}
}
//数据加载接口,留给子类实现
public abstract void onLazyLoad();
//初始化视图接口,子类必须实现
public abstract int setLayoutResourceID();
//初始化事件接口,留给子类实现
public void initView(){ }
/**
* 默认绑定一个事件,防止源码里面去找方法的时候找不到报错。
* @param fragment
*/ @Subscribe
public void onEvent(BaseLazyLoadFragment fragment){
}
}
关于懒加载的Fragment这里有两点需要指出:
1,setUserVisibleHint()方法只有在ViewPager+FragmentPagerAdapter+Fragment结合使用的时候才会被调用,单用Fragment的时候可以考虑使用onHiddenChanged(boolean hidden)方法,可以这么理解:只有用了FragmentPagerAdapter才会显示调用setUserVisibleHint()方法。
2,当Fragment的数量大于2个,你会发现等你翻到第三个,再重新返回第一个的时候,第一个又重新加载了,并且重新走了创建周期,这是因为ViewPager默认只会预加载下一页的Fragment,其他的Fragment会被移除并销毁,因此下一次再添加的时候就需要重新创建Fragment,那么如何解决这个问题呢?
viewPager.setOffscreenPageLimit(2);
这个方法可以设置ViewPager当前页左右两边预加载的页面数量,默认为1,不可能小于1,如果页数比较小(例如3到4个),可以加载所有的页面,这样可以减少页数创建的时间,滑动更流畅,当然了预加载页面数量设置少了,必然流畅度会越高。
一般的Fragment封装如下:
public abstract class BaseFragment extends Fragment {
private View mRootView;
protected Activity mActivity;
private Unbinder bind;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.mActivity = getActivity();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mRootView = inflater.inflate(getContentViewId(),container,false);
bind = ButterKnife.bind(this, mRootView);
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
}
initView();
return mRootView;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
initData();
setListener();
super.onActivityCreated(savedInstanceState);
}
protected void initView(){}
protected void initData(){}
protected void setListener(){}
protected abstract int getContentViewId();
@Override
public void onDestroyView() {
super.onDestroyView();
bind.unbind(); // 解绑butterknife
}
@Override
public void onDestroy() {
super.onDestroy();
// 取消注册
EventBus.getDefault().unregister(this);
}
/**
* 默认绑定一个事件,防止源码里面去找方法的时候找不到报错。
* @param fragment
*/ @Subscribe
public void onEvent(BaseFragment fragment){
}
}