1.什么是懒加载,为什么要用懒加载?
如果我们的项目中使用了ViewPager+Framgment实现底部Tab可点可滑,那么我们都知道ViewPager有预加载功能,通过viewpager.setOffscreenPageLimit();来设置,不设置默认加载上一个和下一个Fragment页面,带上本身也就是三个页面(当然如果你刚进入就是首页,那么它会加载首页和下一个页面,因为首页上面没有页面呀)。预加载功能会暴露一个问题,比如我刚进入加载首页的数据,但是因为有预加载功能,那么就会执行下一个Tab对应的Fragmeng的生命周期,如果我下一个Tab页数据量小还好,如果我有比较耗时的操作或者网络请求,势必会影响程序的性能,影响用户的体验。那么我们要做的就是禁止ViewPager预加载或者提供一个只在Fragemnt可见的情况下,才去进行耗时操作的方法,只要Fragmeng可见我们就执行该方法。
2.懒加载解决方式
2.1 尝试设置setOffscreenPageLimit(失败)
之前想既然setOffscreenPageLimit可以设置,那我就将其设置为0,结果“然并卵”,查看源码如下:
image.png
可以看到如果我们设置的值,小于DEFAULT_OFFSCREEN_PAGES这个常量值,那么就会被赋值为DEFAULT_OFFSCREEN_PAGES,那么我们看看DEFAULT_OFFSCREEN_PAGES值是多少:
image.png
也就是你设置的setOffscreenPageLimit要大于1才可以生效
2.2 试试懒加载
我们先来看看我们的页面:
image.png
就是正常的viewpager+fragment,每个页面有一个TextView,可点可划。三个Fragment依次是:HomeFragment,InvestFragment,UserFragment,
本次要用到的生命周期的方法是:
onCreatedView + onResume + onPause + onDestroyView本次要用到的非生命周期的方法是:setUserVisibleHint
简单介绍一下此方法:当fragment被用户可见时,setUserVisibleHint()会调用且传入true值,当fragment不被用户可见时,setUserVisibleHint()则得到false值,此方法先于生命周期方法执行Fragment 主要的三个状态:第一次可见,每次可见,每次不可见
对于ViewPager+Fragment使用过程中的三种情况
(1) 使用 FragmentPagerAdapter ,FragmentPagerStateAdapter不设置 setOffscreenPageLimit数,采用默认方式
(2)使用 FragmentPagerAdapter ,FragmentPagerStateAdapter设置 setOffscreenPageLimit数,设置为底部Tab总数
(3)使用 FragmentPagerAdapter ,FragmentPagerStateAdapter进入到其他页面或者点击Home键,返回到桌面。
其他注意的是:使用FragmentPagerAdapter 和FragmentPagerStateAdapter的区别在于FragmentPagerStateAdapter会在Fragment不可见的时候走 detach,而FragmentPagerAdapter不会。
当然我测试用的是FragmentPagerAdapter,我们先看一看正常滑动,不设置setOffscreenPageLimit(),Fragment生命周期是怎么走的,先写一个BaseLazyLoadFragment类继承自Fragment.重写我们刚才说的生命周期的方法,首页等Fragment继承BaseLazyLoadFragment打印生命周期(默认进来先加载首页):
BaseLazyLoadFragment部分代码如下
image.png
Fragment代码如下
image.png
不设置setOffscreenPageLimit(),FragmentPagerAdapter 结果如下
image.png
不设置setOffscreenPageLimit(),FragmentPagerStateAdapter 结果如下:
这个结果我之前有过测试,直接告诉你们把区别是:FragmentPagerStateAdapter 会走一下两个生命周期方法,包括从切到Home页面在返回这是一下走两个生命周期方法。
设置setOffscreenPageLimit()个数为总Tab数,FragmentPagerAdapter 和FragmentPagerStateAdapter结果一致,如下:
设置setOffscreenPageLimit为总数之后,一开始就加载了所有的Fragment com.hxzk_bj_demo E/HomeFragment: isVisibleToUser------>false com.hxzk_bj_demo E/InvestFragment:isVisibleToUser------>false com.hxzk_bj_demo E/UserFragment: isVisibleToUser------>false com.hxzk_bj_demo E/HomeFragment: isVisibleToUser------>true com.hxzk_bj_demo E/HomeFragment: ------>onCreateView com.hxzk_bj_demo E/HomeFragment: ------>onStart com.hxzk_bj_demo E/HomeFragment: ------>onResume com.hxzk_bj_demo E/InvestFragment: ------>onCreateView com.hxzk_bj_demo E/UserFragment: ------>onCreateView com.hxzk_bj_demo E/InvestFragment: ------>onStart com.hxzk_bj_demo E/InvestFragment: ------>onResume com.hxzk_bj_demo E/UserFragment: ------>onStart com.hxzk_bj_demo E/UserFragment: ------>onResume//第一个Fragment切换到第二个Fragmentcom.hxzk_bj_demo E/HomeFragment: isVisibleToUser------>false com.hxzk_bj_demo E/InvestFragment: isVisibleToUser------>true//第二个Fragment切换到第三个Fragmentcom.hxzk_bj_demo E/InvestFragment:isVisibleToUser------>false com.hxzk_bj_demo E/UserFragment: isVisibleToUser------>true//点击Homecom.hxzk_bj_demo E/HomeFragment: ------>onPause com.hxzk_bj_demo E/InvestFragment: ------>onPause com.hxzk_bj_demo E/UserFragment: ------>onPause//点击Home后返回com.hxzk_bj_demo E/HomeFragment: ------>onStart com.hxzk_bj_demo E/InvestFragment: ------>onStart com.hxzk_bj_demo E/UserFragment: ------>onStart com.hxzk_bj_demo E/HomeFragment: ------>onResume com.hxzk_bj_demo E/InvestFragment: ------>onResume com.hxzk_bj_demo E/UserFragment: ------>onResume
没啥的。
针对不设置setOffscreenPageLimit(),FragmentPagerAdapter 结果我们分析一下:可以看到进入到第一个Fragment的时候,也执行了下一个Fragment的生命周期,执行了不必要的操作。那大家有没有发现,如果那个Fragment的状态为可见其setUserVisibleHint的值就为true,其余Fragment的值为false,那我们只需要判断,如果setUserVisibleHint的值就为true即改Fragment为可见状态,我们就执行耗时操作,其他Fragment为false,就不执行网络请求的操作呗。那我们写一个公共的方法,注意此方法执行,要放到onActivityCreate()之后,否则我请求回来的数据载体控件的Activity都没有创建,所以我要定义几个变量来查看Fragment的状态,我们之前也说了Fragement有首次可见,可见和不可见三种状态,代码如下:
View rootView; /**当前Fragment是否首次可见,默认是首次可见**/ private boolean mIsFirstVisible = true; /**当前Fragment的View是否已经创建**/ private boolean isViewCreated = false; /**当前Fragment的可见状态,一种当前可见,一种当前不可见**/ private boolean currentVisibleState = false; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { LogUtil.e(getClass().getSimpleName(),"-----> onCreateView"); if(rootView == null){ rootView = inflater.inflate(getLayoutId(), container, false); initView(rootView); } isViewCreated=true;//在onCreateView执行完毕,将isViewCreated改为true; return rootView; }
不同生命周期变量值的变更及涉及的相关代码:
onStart
@Override public void onStart() { super.onStart(); LogUtil.e(getClass().getSimpleName(),"-----> onStart"); //isHidden()是Fragment是否处于隐藏状态和isVisible()有区别 //getUserVisibleHint(),Fragement是否可见 if(!isHidden()&& getUserVisibleHint()){//如果Fragment没有隐藏且可见 //执行分发的方法,三种结果对应自Fragment的三个回调,对应的操作,Fragment首次加载,可见,不可见 disPatchFragment(true); } }
onResume
@Override public void onResume() { super.onResume(); LogUtil.e(getClass().getSimpleName(),"-----> onResume"); if(!mIsFirstVisible){//表示点击home键又返回操作,设置可见状态为ture if(!isHidden()&& !getUserVisibleHint() && currentVisibleState){ disPatchFragment(true); } } }
onPause
@Override public void onPause() { super.onPause(); //表示点击home键,原来可见的Fragment要走该方法,更改Fragment的状态为不可见 if(!isHidden()&& getUserVisibleHint()){ disPatchFragment(false); } }
onDestroyView
@Override public void onDestroyView() { super.onDestroyView(); LogUtil.e(getClass().getSimpleName(),"-----> onStart"); //当 View 被销毁的时候我们需要重新设置 isViewCreated mIsFirstVisible 的状态 isViewCreated = false; mIsFirstVisible = true; }
Fragment不同状态对应的回调方法
/** * * @param visible Fragment当前是否可见,然后调用相关方法 */ public void disPatchFragment(boolean visible){ currentVisibleState=visible; if(visible){//Fragment可见 if(mIsFirstVisible){//可见又是第一次 mIsFirstVisible=false;//改变首次可见的状态 onFragmentFirst(); }else{//可见但不是第一次 LogUtil.e(getClass().getSimpleName(),"可见"); } }else {//不可见 LogUtil.e(getClass().getSimpleName(),"不可见"); } }; //Fragemnet首次可见的方法 public void onFragmentFirst(){ LogUtil.e(getClass().getSimpleName(),"首次可见"); }; //Fragemnet可见的方法 public void onFragmentVisble(){//子Fragment调用次方法,执行可见操作 LogUtil.e(getClass().getSimpleName(),"可见"); }; //Fragemnet不可见的方法 public void onFragmentInVisible(){ LogUtil.e(getClass().getSimpleName(),"不可见"); };
最后来一个总的代码:
public abstract class BaseLazyLoadFragment extends android.support.v4.app.Fragment {
View rootView;/**当前Fragment是否首次可见,默认是首次可见**/private boolean mIsFirstVisible = true;/**当前Fragment的View是否已经创建**/private boolean isViewCreated = false;/**当前Fragment的可见状态,一种当前可见,一种当前不可见**/private boolean currentVisibleState = false;@Nullable@Overridepublic View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { LogUtil.e(getClass().getSimpleName(),"-----> onCreateView"); if(rootView == null){ rootView = inflater.inflate(getLayoutId(), container, false); initView(rootView); } isViewCreated=true;//在onCreateView执行完毕,将isViewCreated改为true; return rootView; }@Overridepublic void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); LogUtil.e(getClass().getSimpleName(),"----->"+isVisibleToUser); if (isViewCreated) { if (isVisibleToUser && !currentVisibleState) {//Fragment可见且状态不是可见(从一个Fragment切换到另外一个Fragment,后一个设置状态为可见) disPatchFragment(true); } else if (!isVisibleToUser && currentVisibleState) {//Fragment不可见且状态是可见(从一个Fragment切换到另外一个Fragment,前一个更改状态为不可见) disPatchFragment(false); } } }/**返回子Fragment的布局id**/public abstract int getLayoutId();/**初始化View的方法**/public abstract void initView(View rootView);@Overridepublic void onStart() { super.onStart(); LogUtil.e(getClass().getSimpleName(),"-----> onStart"); //isHidden()是Fragment是否处于隐藏状态和isVisible()有区别 //getUserVisibleHint(),Fragement是否可见 if(!isHidden()&& getUserVisibleHint()){//如果Fragment没有隐藏且可见 //执行分发的方法,三种结果对应自Fragment的三个回调,对应的操作,Fragment首次加载,可见,不可见 disPatchFragment(true); } }@Overridepublic void onResume() { super.onResume(); LogUtil.e(getClass().getSimpleName(),"-----> onResume"); if(!mIsFirstVisible){ //表示点击home键又返回操作,设置可见状态为ture if(!isHidden()&& !getUserVisibleHint() && currentVisibleState){ disPatchFragment(true); } } }@Overridepublic void onPause() { super.onPause(); //表示点击home键,原来可见的Fragment要走该方法,更改Fragment的状态为不可见 if(!isHidden()&& getUserVisibleHint()){ disPatchFragment(false); } }@Overridepublic void onDestroyView() { super.onDestroyView(); LogUtil.e(getClass().getSimpleName(),"-----> onStart"); //当 View 被销毁的时候我们需要重新设置 isViewCreated mIsFirstVisible 的状态 isViewCreated = false; mIsFirstVisible = true; }/** * * @param visible Fragment当前是否可见,然后调用相关方法 */public void disPatchFragment(boolean visible){ currentVisibleState=visible; if(visible){//Fragment可见 if(mIsFirstVisible){//可见又是第一次 mIsFirstVisible=false;//改变首次可见的状态 onFragmentFirst(); }else{//可见但不是第一次 LogUtil.e(getClass().getSimpleName(),"可见"); } }else {//不可见 LogUtil.e(getClass().getSimpleName(),"不可见"); } };//Fragemnet首次可见的方法public void onFragmentFirst(){ LogUtil.e(getClass().getSimpleName(),"首次可见"); };//Fragemnet可见的方法public void onFragmentVisble(){//子Fragment调用次方法,执行可见操作 LogUtil.e(getClass().getSimpleName(),"可见"); };//Fragemnet不可见的方法public void onFragmentInVisible(){ LogUtil.e(getClass().getSimpleName(),"不可见"); };
}
我们的Fragment只需要继承BaseLazyLoadFragment,然后对应调用首次可见方法,再次可见方法,不可见方法做相应的操作就可以了。
懒加载进阶
我们上面说的是一层的ViewPager加Fragment,但大家也一定遇到过Fragemgt中又来了一层ViewPager+Fragment,如图:
那这种的怎么办呢?之前的方法还管用不,别急,我们先看看其生命周期打印:
纳尼,InvestFragment(投资)内部第一个Tab,Invest_MineFragment竟然执行了首次可见的方法,要知道此时的InvestFragmetn都没有显示。
针对此问题,我的解决方法是,先判断父Fragment如果没有显示,那么不触发我们定义的方法,代码如下:
/** *判断多层嵌套的父Fragment是否显示 */ private boolean isParentFragmentInvisible() { //获取父Fragment的可见状态 BaseLazyLoadFragment fragment = (BaseLazyLoadFragment) getParentFragment(); //如果两个都满足即父Fragment不为空且父Fragment不可见,返回true return fragment != null && !fragment.getCurrentVisibleState(); } private boolean getCurrentVisibleState() { return currentVisibleState; }
ok,我们在运行一下,打印如下:
image.png
感觉没啥问题?
别急,还有剩余部分打印信息:
image.png
也就是我们还需要一个第一个子Fragment的状态信息:解决思路如下:
由于父Fragment的执行在子Fragment之前,所以,当我们在父 Fragment 分发完成自己的可见事件后,让子 Fragment 再次调用自己的可见事件分发方法,这次我们让 isParentFragmentVsible() 返回 false ,可见状态将会正确分发了,有点类似于父类完成后,又调用方法刷新子类
。
/** * * @param visible Fragment当前是否可见,然后调用相关方法 */ public void disPatchFragment(boolean visible){ String aa =getClass().getSimpleName(); //如果父Fragment不可见,则不向下分发给子Fragment if(visible && isParentFragmentVsible())return; currentVisibleState=visible; if(visible){//Fragment可见 if(mIsFirstVisible){//可见又是第一次 mIsFirstVisible=false;//改变首次可见的状态 onFragmentFirst(); }//可见但不是第一次 onFragmentVisble(); //可见状态的时候内层 fragment 生命周期晚于外层 所以在 onFragmentResume 后分发 dispatchChildFragmentVisibleState(true); }else {//不可见 onFragmentInVisible(); dispatchChildFragmentVisibleState(false); } }; /** * 父Fragment分发完成之后再次调用,重新分发给子Fragment * @param visible */ private void dispatchChildFragmentVisibleState(boolean visible) { FragmentManager childFragmentManager = getChildFragmentManager(); @SuppressLint("RestrictedApi") List<Fragment> fragments = childFragmentManager.getFragments(); if(fragments != null){ if (!fragments.isEmpty()) { for (Fragment child : fragments) { if (child instanceof BaseLazyLoadFragment && !child.isHidden() && child.getUserVisibleHint()) { ((BaseLazyLoadFragment) child).disPatchFragment(visible); } } } } }
这样就ok了,然后我们点击home键:
这又是什么问题,Invest_YourFragment为什么走了两边?
原因处在顺序调用上,我刚才说了:父 Fragment总是优先于子 Fragment,而对于不可见事件,内部的 Fragment 生命周期总是先于外层 Fragment。回到我们代码里:父Fragment调用自身的 disPatchFragment方法分发了不可见事件,又会再次调用 dispatchChildFragmentVisibleState ,导致子 Fragment 再次调用自己的 disPatchFragment再次调用了一次 不可见事件onFragmentInVisible,故产生了两次。
解决办法就是我们之前定义的变量:currentVisibleState,如果当前的 Fragment 要分发的状态与 currentVisibleState 相同我们就没有必要去做分发了。
代码及添加位置如下:
``
image.png
``
最后附上总代码,编写Fragment时,只需要继承该类,然后调用可见的方法就好了。
public abstract class BaseLazyLoadFragment extends Fragment { View rootView; /**当前Fragment是否首次可见,默认是首次可见**/ private boolean mIsFirstVisible = true; /**当前Fragment的View是否已经创建**/ private boolean isViewCreated = false; /**当前Fragment的可见状态,一种当前可见,一种当前不可见**/ private boolean currentVisibleState = false; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { LogUtil.e(getClass().getSimpleName(),"-----> onCreateView"); if(rootView == null){ rootView = inflater.inflate(getLayoutId(), container, false); initView(rootView); } isViewCreated=true;//在onCreateView执行完毕,将isViewCreated改为true; return rootView; } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isViewCreated) { if (isVisibleToUser && !currentVisibleState) {//Fragment可见且状态不是可见(从一个Fragment切换到另外一个Fragment,后一个设置状态为可见) disPatchFragment(true); } else if (!isVisibleToUser && currentVisibleState) {//Fragment不可见且状态是可见(从一个Fragment切换到另外一个Fragment,前一个更改状态为不可见) disPatchFragment(false); } } } /**返回子Fragment的布局id**/ public abstract int getLayoutId(); /**初始化View的方法**/ public abstract void initView(View rootView); @Override public void onStart() { super.onStart(); //isHidden()是Fragment是否处于隐藏状态和isVisible()有区别 //getUserVisibleHint(),Fragement是否可见 if(!isHidden()&& getUserVisibleHint()){//如果Fragment没有隐藏且可见 //执行分发的方法,三种结果对应自Fragment的三个回调,对应的操作,Fragment首次加载,可见,不可见 disPatchFragment(true); } } @Override public void onResume() { super.onResume(); if(!mIsFirstVisible){ //表示点击home键又返回操作,设置可见状态为ture if(!isHidden()&& !getUserVisibleHint() && currentVisibleState){ disPatchFragment(true); } } } @Override public void onPause() { super.onPause(); //表示点击home键,原来可见的Fragment要走该方法,更改Fragment的状态为不可见 if(!isHidden()&& getUserVisibleHint()){ disPatchFragment(false); } } @Override public void onDestroyView() { super.onDestroyView(); //当 View 被销毁的时候我们需要重新设置 isViewCreated mIsFirstVisible 的状态 isViewCreated = false; mIsFirstVisible = true; } /** * * @param visible Fragment当前是否可见,然后调用相关方法 */ public void disPatchFragment(boolean visible){ String aa =getClass().getSimpleName(); //如果父Fragment不可见,则不向下分发给子Fragment if(visible && isParentFragmentVsible())return; // 如果当前的 Fragment 要分发的状态与 currentVisibleState 相同(都为false)我们就没有必要去做分发了。 if (currentVisibleState == visible) { return; } currentVisibleState=visible; if(visible){//Fragment可见 if(mIsFirstVisible){//可见又是第一次 mIsFirstVisible=false;//改变首次可见的状态 onFragmentFirst(); }//可见但不是第一次 onFragmentVisble(); //可见状态的时候内层 fragment 生命周期晚于外层 所以在 onFragmentResume 后分发 dispatchChildFragmentVisibleState(true); }else {//不可见 onFragmentInVisible(); dispatchChildFragmentVisibleState(false); } }; /** * 重新分发给子Fragment * @param visible */ private void dispatchChildFragmentVisibleState(boolean visible) { FragmentManager childFragmentManager = getChildFragmentManager(); @SuppressLint("RestrictedApi") List<Fragment> fragments = childFragmentManager.getFragments(); if(fragments != null){ if (!fragments.isEmpty()) { for (Fragment child : fragments) { if (child instanceof BaseLazyLoadFragment && !child.isHidden() && child.getUserVisibleHint()) { ((BaseLazyLoadFragment) child).disPatchFragment(visible); } } } } } //Fragemnet首次可见的方法 public void onFragmentFirst(){ LogUtil.e(getClass().getSimpleName(),"首次可见"); }; //Fragemnet可见的方法 public void onFragmentVisble(){//子Fragment调用次方法,执行可见操作 LogUtil.e(getClass().getSimpleName(),"可见"); }; //Fragemnet不可见的方法 public void onFragmentInVisible(){ LogUtil.e(getClass().getSimpleName(),"不可见"); }; /** *判断多层嵌套的父Fragment是否显示 */ private boolean isParentFragmentVsible() { BaseLazyLoadFragment fragment = (BaseLazyLoadFragment) getParentFragment(); return fragment != null && !fragment.getCurrentVisibleState(); } private boolean getCurrentVisibleState() { return currentVisibleState; } }
作者:薛之涛
链接:https://www.jianshu.com/p/8d1fe9ecb445
共同学习,写下你的评论
评论加载中...
作者其他优质文章