2018
写在前面:这篇文章主要分析导航栏,也就是ViewPager+Fragment+FragmentPagerAdapter的懒加载模式,重点还是Fragment生命周期的应用。
如果对如何使用导航栏还不太了解,可以看看 底部导航栏标签切换的实现 这篇文章。
(一)Fragment生命周期
大家都知道 Fragment 是绑定 Activity 的,不过,很多人会忽视了它们的生命周期也是会一定程度上同步的。下面表格的场景是在 Activity 内部实例化 Fragment,Fragment 与 Activity 生命周期之间的关系。
Activity | Fragment |
---|---|
onCreate() | onAttach(), onCreateView(), onCreateView, onActivityCreate() |
onStart() | onStart() |
onResume() | onResume() |
onPause() | onPause() |
onStop() | onStop() |
onDestroy() | onDestroyView(), onDestroy(), onDetach() |
大多数时候我们将实例化 Fragment 放在 Activity.onCreate()
中,如new
一个实例对象。不难理解 Fragment 在Activity.onCreate()
之后才开始一系列生命周期方法的调用。而其他方法执行顺序会是下面这样:
Activity.onStart()-->Fragment.onStart()
Activity.onResume()-->Fragment.onResume()
Fragment.onPause()-->Activity.onPause()
Fragment.onStop()-->Activity.onStop()
Fragment.onDestroyView(),onDestroy(),onDetach()-->Activity.onDestroy()
Fragment 依赖于 Activity,故而,Fragment创建于Acivity之后,销毁于Acrivity之前。
ViewPager中Fragment的生命周期
结合 ViewPager 后,页面切换会有哪些生命周期方法的调用?
看过上一篇博文的朋友应该都比较清楚了,这里就再不厌其烦地给没看过的朋友讲解一下。
单独使用Fragment没什么好说的,调用的方法和上表一样,会和 Activity 同步。而结合 ViewPager 取决于选择哪一种适配器,现在大多数使用 FragmentPagerAdapter或者 FragmentPagerStateAdapter,两者的区别是到底会不会回收Fragment的内存。
前者只回收View,除非内存不足,否则不会销毁加载好的Fragment;后者为达到节省内存的目的,对于不在当前页面左右两边的其他Page,将会销毁其Fragment,只保留最多三个页面在内存中。
针对 FragmentPagerAdapter 的测试
Fragment-->
代表的是第一个Fragment 的生命周期,Activity-->
代表与之关联的活动的生命周期:
// 程序启动,初始化活动
I/MyTest: Activity-->onCreate()
I/MyTest: Activity-->onStart()
Activity-->onResume()
// 加载Fragment
I/MyTest: Fragment-->getUserVisibleHint:true
I/MyTest: Fragment-->setUserVisibleHint(false)
Fragment-->getUserVisibleHint:false
Fragment-->setUserVisibleHint(true)
I/MyTest: Fragment-->onAttach()
Fragment-->onCreate()
I/MyTest: Fragment-->onCreateView()
I/MyTest: Fragment-->onActivityCreated()
Fragment-->onStart()
Fragment-->onResume()
//点击第三个页面
I/MyTest: Fragment-->getUserVisibleHint:true
Fragment-->setUserVisibleHint(false)
I/MyTest: Fragment-->onPause()
I/MyTest: Fragment-->onStop()
Fragment-->onDestroyView() // View被回收,但是内存没有回收
//返回第一个页面
I/MyTest: Fragment-->getUserVisibleHint:false
Fragment-->setUserVisibleHint(false)
Fragment-->getUserVisibleHint:false
Fragment-->setUserVisibleHint(true)
Fragment-->onCreateView()
Fragment-->onActivityCreated()
Fragment-->onStart()
Fragment-->onResume()
//销毁活动,与Activity生命周期同步
I/MyTest: Fragment-->onPause()
Activity-->onPause()
I/MyTest: Fragment-->onStop()
I/MyTest: Activity-->onStop()
I/MyTest: Fragment-->onDestroyView()
Fragment-->onDestroy()
Fragment-->onDetach()
Activity-->onDestroy()
发现
- 切换到不是 当前页面左右两边的页面 时,当前页面会被回收View,但不被销毁,原因是Adapter默认只完整加载左右两边页面。
- 切换到 当前页面左右两边的页面 时,只会调用
setUserVisibleHint(true)
。 - Fragment 的生命周期与Activity息息相关。经过另外的测试,如果Activity变得不可见(比如开启新活动),那么所有加载好的Fragment会和Activity一起执行
onPause()
,onStop()
,返回时又会一起执行onStart()
,onResume()
,这就是生命周期的同步。 - 初始化的时候
setUserVisibleHint()
比Fragment任何生命周期方法要早调用。而从较远页面切换回来setUserVisibleHint()
也至少比onCreateView()
先执行,如页面3到页面1。
还是贴张图表示这个过程:
(二)懒加载在不同业务场景下的实现
有了上面的知识储备,灵活地使用“懒加载”也不难了,根据业务需要,我们可以设置不同的加载方式。
基本方法
使用 setUserVisibleHint()
,在进入一个页面之前我们肯定会需要调用这个方法。
场景1
只需要拉取一次数据,但是很可能整个活动期间都不会切换到这个页面,所以希望省点流量,只有切换到才加载,而且只有一次。写法如下:
private boolean hasLoad;
@Override
public void setUserVisibleHint(boolean isVisible){
//设置为可见并且没有加载过数据的时候进行网络请求
if(isVisible && (!hasLoad)){
/*
* 网路请求数据
*/
hasLoad = true;
}
super.setUserVisibleHint(isVisible);
}
场景2
每次进入页面都要拉取数据,但只要加载数据成员不需要更新UI,下面这种写法就可以了。
@Override
public void setUserVisibleHint(boolean isVisible){
if(isVisible){
/*
* 网路请求数据
*/
}
super.setUserVisibleHint(isVisible);
}
场景3
每次进入都需要拉取数据,数据加载后要更新UI。
为什么和第一种区分,留意“发现”的最后一点的两种情况,此时Fragment还没有加载View实例,而更新UI必须要确保已经获取View实例。那不妨使用一个boolean变量来标识获取View的状态,确保View获取了就加载,反之不加载。很有道理,不过还有缺陷,因为等View加载完,我们已经不会调用setUserVisibleHint()
了,所以还要在View获取后完成一次补充加载。
下面这种写法就能确保无论是初始化、还是从较远页面切换过来或者从左右页面切换过来,都能马上拉取数据。大家可以直接用这种格式哦。
private boolean hasView;
@Override
public void setUserVisibleHint(boolean isVisible){
if(isVisible && hasView){
/*
* 网路请求数据 + 更新UI
*/
}
super.setUserVisibleHint(isVisible);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_layout_1, container,false); //动态加载布局
/*
* 使用 view.findViewById(long resourceId)来获取View实例
*/
hasView = true; //View加载好了
if(getUserVisibleHint()){
/*
* 数据请求 + UI更新
*/
}
return view;
}
@Override
public void onDestroyView(){
hasView=false; // View被回收了
super.onDestroyView();
}
留意上面onCreateView()
中的 if
代码块,这就是我们说的,如果是没有View的情况下不能直接使用 setUserVisibleHint()
加载,需要之后采取一个补充加载。
(三)强调ViewPager中四种不同的Fragment状态
- 当前页面:Fragment 生命周期执行到
onResume()
,可见性getUserVisibleHint()
为true
。 - 预加载页面:位于当前页面左右两边,Fragment 生命周期执行到
onResume()
,
可见性getUserVisibleHint()
为false
。 - 曾经加载过但是被回收了View的页面:肯定不在当前页面左右,Fragment 生命周期执行到
onCreate()
,getUserVisibleHint()
为false
。 - 从来没有加载过的页面:也肯定不在当前页面左右,所有方法都还没调用,
getUserVisibleHint()
为false
。
暂时写到这里,关于数据懒加载我们也了解得差不多了,如有不正之处欢迎指正。