在学习viewpaper前,需要先了解预加载和懒加载。
1.什么是预加载?
Fragment切换的时候,会预先加载未可见的Fragment,就是预加载。
2.预加载的弊端:
a.预加载的越多就会越卡
b.一个Fragment占用 1M,5个就(5*1M),累计到后面就会OOM
c.如果预加载的Fragment在请求网络,不仅浪费流量,还会很卡顿
3.如何解决预加载的弊端?
懒加载
4.什么是懒加载?
防止预加载,用到才加载,可见才加载,不可见就不加载。
懒加载,其实也就是延迟加载,就是等到该页面的UI展示给用户时,再加载该页面的数据(从网络、数据库等),而不是依靠ViewPager预加载机制提前加载两三个,甚至更多页面的数据。
5.为什么要用懒加载?
这样可以提高所属Activity的初始化速度,也可以为用户节省流量.而这种懒加载的方式也已经/正在被诸多APP所采用。
接下来学习开始学习viewpaper源码:
6.阅读Viewpager源码
viewpaper需搭配adapter使用,而viewpaper控件中使用onMeasure()测量,onLayout()排版,onDraw()绘制。在onMeasure中有一个重要的方法populate(),这个方法,setOffscreenPageLimit里也会调用populate()。
6.1. 阅读populate()方法可知道重要的适配器原理:【FragmentPagerAdapter extends PagerAdapter 】
a.startUpdate()准备适配
b.instantiateItem创建适配的item数据
c.destroyItem销毁适配的item数据
d.setPrimaryItem设置当前显示item数据
e.finishUpdate完成适配
可以知道==>
a是在准备缓存初始化工作
bcd是在处理缓存数据,增加和删除缓存节点item信息
e是触发缓存,启动真正的数据Item更新业务
6.2. 在FragmentPagerAdapter中分析setUserVisibleHint()的执行时机:【【extends PagerAdapter】】
可以发现,有两个方法里有调用setUserVisibleHint(),分别是instantiateItem()和setPrimaryItem()
举例:有fragment1,fragment2,fragment3,fragment4,当fragment1跳转到fragment3时,可以发现:
会优先缓存fragment4,设置setUserVisibleHint(false),
再处理当前fragment1,设置setUserVisibleHint(false),
最后处理目标fragment3,设置setUserVisibleHint(true),
可以发现:
- 由于先调用setPrimaryItem(),再调用finishUpdate(),所以setUserVisibleHint()会优先调用,再调生命周期内的方法。
- instantiateItem()中会调用1次setUserVisibleHint,一次false;在setPrimaryItem()中会调用2次setUserVisibleHint,一次是true,一次是false。
Fragment 生命周期按先后顺序:
onAttach -> onCreate -> onCreatedView -> onActivityCreated -> onStart -> onResume ->onPause -> onStop -> onDestroyView -> onDestroy -> onDetach
ViewPager + Fragment 的实现我们需要关注的几个生命周期有:
onCreatedView + onResume + onPause + onDestroyView
非生命周期函数:
setUserVisibleHint + onHiddenChanged
7.实际应用
举例:有tab1,tab2,tab3,tab4,tab5,可以来回切换页面,tab2有一个嵌套的viewpaper,tab5有一个按钮,点击进入新的activity。下面将对他们进行优化
优化步骤
1.由tab1切换到tab2,运行奔溃,提示子fragment类中断更新onFragmentLoadStop对ui操作。
原因:由于setUserVisibleHint在生命周期函数之前执行,缓存的tab3会执行setUserVisibleHint(false),而onCreateView还来不及执行,因为就没有执行其中的initView()方法。
解决:新增isViewCreated判断,isViewCreated为true则能够执行分发可见和不可见的动作。
2.第一次打开页面时,页面一直处于loading的状态。
原因:执行动作的方法在setUserVisibleHint中,而执行这个方法时isViewCreateed为false。当生命周期方法走到onCreateView()时,isViewCreateed=true,可是并不会主动再调用一次setUserVisibleHint(true)的方法
解决:手动调用setUserVisibleHint(true),使得相关动作得以执行。
3.打开和关闭页面时,发现,有重新更新加载和重复暂停加载的日志
原因:需要从“不可见->可见变化过程”才是可见(加载);“可见”->"不可见"才是可见(停止)
解决:新增isVisibleStateUP用于记录上一次可见的状态,
在分发 可见 不可见 的动作的方法dispatchUserVisibleHint里:
4.当点击tab5进入一个新activity,再返回tab5时候,都没有执行setUserVisibleHint方法,只执行了onResume和onPause。
原因:从源码中可知,在ViewPager的populate函数中,当mAdapter去执行setPrimarvItem时,才会执行setUserVisibleHint。它是非生命周期函数。
解决:在onResume和onPause中,根据getUserVisibleHint() 和isVisibleStateUP,判断是否需要执行分发的方法。
5.
1).当执行tab1时,tab2内嵌套的fragment也会执行
原因:目标tab1时,会默认预加载tab2,执行生命周期方法onCreateView,会初始化嵌套的fragment中的第一个。
解决:判断t2可见并且嵌套的父控件也可见时,才执行。
// TODO 判断 父控件 是否可见, 什么意思? 例如: Fragment2_vp1子Fragment 的 父亲/父控件==Fragment2
2).从tab1切换到tab2时,tab2里面嵌套的viewPager的fragment默认不会分发执行
解决:在双重ViewPager嵌套的情况下,第一次滑到Frgment 嵌套ViewPager(fragment)的场景的时候此时只会加载外层Fragment的数据,而不会加载内嵌viewPager中的fragment的数据,因此,我们需要在此增加一个当外层Fragment可见的时候,分发可见事件给自己内嵌的所有Fragment显示。即需手动分发执行嵌套fragment里的。
重要代码:
public abstract class LazyFragment extends Fragment {
FragmentDelegater mFragmentDelegater;
private View rootView = null;
private boolean isViewCreated = false;//view是否加载
private boolean isVisibleStateUP = false;// 记录上一次可见的状态
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
E("onCreateView");
if (rootView == null) {
rootView = inflater.inflate(getLayoutRes(), container, false);
}
isViewCreated = true;
initView(rootView);
if (getUserVisibleHint()) {//tip2:手动调用使其执行
setUserVisibleHint(true);
}
return rootView;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
E("setUserVisibleHint");
if (isViewCreated) {//tip1:isViewCreated为true则能够执行分发可见和不可见的动作
if (isVisibleToUser && !isVisibleStateUP) {
dispatchUserVisibleHint(true);
} else if (!isVisibleToUser && isVisibleStateUP) {
dispatchUserVisibleHint(false);
}
}
}
@Override
public void onResume() {
super.onResume();
E("onResume");
//tip4.从不可见到可见,说明可见
if (getUserVisibleHint() && !isVisibleStateUP) {
dispatchUserVisibleHint(true);
}
}
@Override
public void onPause() {
super.onPause();
E("onPause");
//tip4.从可见到不可见,说明不可见
if (getUserVisibleHint() && isVisibleStateUP) {
dispatchUserVisibleHint(false);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
E("onDestroyView");
}
// 让子类完成,初始化布局,初始化控件
protected abstract void initView(View rootView);
protected abstract int getLayoutRes();
// -->>>停止网络数据请求
public void onFragmentLoadStop() {
E("onFragmentLoadStop");
}
// -->>>加载网络数据请求
public void onFragmentLoad() {
E("onFragmentLoad");
}
// 分发 可见 不可见 的动作 【第一版 1.2】
private void dispatchUserVisibleHint(boolean visibleState) {
this.isVisibleStateUP = visibleState;
if (visibleState && isParentInvisible()) {
return;
}
if (visibleState) {
// 加载数据
onFragmentLoad(); // 都是对第一层有效,嵌套无效
dispatchChildVisibleState(true);
} else {
// 停止一切操作
onFragmentLoadStop(); // 都是对第一层有效,嵌套无效
dispatchChildVisibleState(false);
}
}
// tip6:为了解决第二个问题,T1 到 T2 T2里面嵌套的ViewPager的Fragment默认不会分发执行
// 解决:需要手动的分发执行嵌套Fragment里面的
private void dispatchChildVisibleState(boolean b) {
FragmentManager fragmentManager = getChildFragmentManager();
List<Fragment> fragmentList = fragmentManager.getFragments();
if (fragmentList != null) {
for (Fragment fragment : fragmentList) {
if (fragment instanceof LazyFragment && !fragment.isHidden() && fragment.getUserVisibleHint()) {
((LazyFragment) fragment).dispatchUserVisibleHint(b);
}
}
}
}
private boolean isParentInvisible() {
Fragment parentFragment = getParentFragment();
if (parentFragment instanceof LazyFragment) {
LazyFragment fragment = (LazyFragment) parentFragment;
return !fragment.isVisibleStateUP;
}
return false;
}
public void setFragmentDelegater(FragmentDelegater mFragmentDelegater) {
this.mFragmentDelegater = mFragmentDelegater;
}
private void E(String string) {
if (mFragmentDelegater != null) {
mFragmentDelegater.dumpLifeCycle(string);
}
}
}
8. viewPager1和viewPager2 区别#
懒加载对比:
viewPager1是由setUsetVisibleHint和生命周期方法(onCreateView、onResume)等等配合实现的懒加载;
viewPager2中setUsetVisibleHint已过时,是由setMaxLifecycle代替
缓存页面对比:
viewPager1是由populate中的重要适配器原理等配合实现的缓存页面
viewPager2是由RecycleView中的缓存机制来实现的
使用方面对比:
a.基于RecyclerView实现。这意味着RecyclerView的优点将会被ViewPager2所继承。
b.支持竖直滑动。只需要一个参数就可以改变滑动方向。
c.支持关闭用户输入。通过setUserInputEnabled来设置是否禁止用户滑动页面
d.FragmentStatePagerAdapter 被 FragmentStateAdapter 替代
e.addPageChangeListener 被 registerOnPageChangeCallback替代