从FragmentPagerAdapter管理Fragment生命周期及可见性

从FragmentPagerAdapter管理Fragment生命周期及可见性

使用ViewPager和Fragment相结合需要用到FragmentPagerAdapter适配器。那么我们先来看下FragmentPagerAdapter适配器带来的问题

问题1:如果Fragment有网络加载,那么我们会发现除了当前的Fragment的网络启动之外,别的Fragment的网络加载也启动了
问题2:显示上没问题,左右滑动也都没事,但是越用越卡
问题3:如何利用Fragment的生命周期让我们的页面顺滑起来

问题1:如果Fragment有网络加载,那么我们会发现除了当前的Fragment的网络启动之外,别的Fragment的网络加载也启动了!这里要说的是FragmentPagerAdapter的加载机制,为了让客户有更好的体验,当我们使用ViewPager显示一个Fragment的时候,FragmentPagerAdapter会自动加载其两侧的Fragment,进而让客户在滑动的时候感觉没有明显的卡顿和数据加载。但是如果我们写的网络加载里面有Toast的话,就会发生一些乌龙时间,好比第一页是有数据的,但是我在无数据页给了一个无数据提示,就会发生在有数据的页接到了无数据提示。而且更主要的是,同时开启两个数据加载连接必然产生两个请求数据返回的时间加长!这样对应当前想使用的页面体验又是不好的。再一个我们缓存两页,如果图片过多,又没有写好数据回收的工作,那么刚开始就是卡顿,再然后就是内存溢出。

问题2:就是Fragment左右加载的内存没有被释放导致的,这里建议使用Fresco去加载图片,因为Fresco的内存处理机制实在是太棒了!使用Fresco的缓存策略,只要图片不在当前页面显示那么图片的内存就被回收。

问题3:当我们滑动ViewPager的时候,在FragmentPagerAdapter下的Fragment生命周期回调。从Activity进入一个Fragment,其流程是

显示出来的页:onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume
之后两边的页:onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume

打印Log的时候我们可以看见同样的生命周期会输出三遍信息,这就是说当前的Fragment和左右的Fragment都被加载了。所以当我们将网络加载的代码放到onCreateView里面的时候,开启一个Fragment那么其实同时开启了3个网络加载线程!这里给出的解决策略是在setUserVisibleHint()方法中进行数据加载,当我们的Fragment显示的时候,启动数据加载单元。

当我们继续滑动Fragment的时候会看见那些生命周期呢?

被隐藏的页面:onPause->onStop->onDestroyView
新建的页面:onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume

这里有一个重点,就是Fragment的生命周期到onDestroyView就结束了,而不是onDestory,这是很重要的,这就说明只有和Fragment.RootView相关的View被销毁了,而其他的数据还在。如果需要下来刷新和上拉加载,那么就需要一个全局的Adapter,在Fragment#onDestroyView的时候,Fragment的数据还在,那么就可以结合之前说的在setUserVisibleHint()中根据Adapter是否被初始化来决定是否再次进行网络加载!进而起到减少网络加载,和在使用时按需加载的需求。

这里继续为大家补充一些关于setUserVisibleHint()的知识:

  • setUserVisibleHint()在Fragment创建时会先被调用一次,传入isVisibleToUser = false
  • 如果当前Fragment可见,那么setUserVisibleHint()会再次被调用一次,传入isVisibleToUser = true
  • 如果Fragment从可见->不可见,那么setUserVisibleHint()也会被调用,传入isVisibleToUser = false

总结:setUserVisibleHint()除了Fragment的可见状态发生变化时会被回调外,在new Fragment()时也会被回调。如果我们需要在 Fragment 可见与不可见时干点事,直接使用setUserVisibleHint()就会发生多余的回调,那么就需要重新封装一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
public abstract class LazyFragment extends Fragment {

private static final String TAG = LazyFragment.class.getSimpleName();

private boolean isFragmentVisible;
private boolean isReuseView;
private boolean isFirstVisible;
private View rootView;

//setUserVisibleHint()在Fragment创建时会先被调用一次,传入isVisibleToUser = false
//如果当前Fragment可见,那么setUserVisibleHint()会再次被调用一次,传入isVisibleToUser = true
//如果Fragment从可见->不可见,那么setUserVisibleHint()也会被调用,传入isVisibleToUser = false
//总结:setUserVisibleHint()除了Fragment的可见状态发生变化时会被回调外,在new Fragment()时也会被回调
//如果我们需要在 Fragment 可见与不可见时干点事,直接使用`setUserVisibleHint()`就会发生多余的回调
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//setUserVisibleHint()有可能在fragment的生命周期外被调用
if (rootView == null) {
return;
}
if (isFirstVisible && isVisibleToUser) {
onFragmentFirstVisible();
isFirstVisible = false;
}
if (isVisibleToUser) {
onFragmentVisibleChanged(true);
isFragmentVisible = true;
return;
}
if (isFragmentVisible) {
isFragmentVisible = false;
onFragmentVisibleChanged(false);
}
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initVariable();
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
//如果setUserVisibleHint()在rootView创建前调用时,那么就等到rootView创建完后才回调onFragmentVisibleChanged(true)
//保证onFragmentVisibleChanged()的回调发生在rootView创建完成之后,以便支持UI操作
if (rootView == null) {
rootView = view;
if (getUserVisibleHint()) {
if (isFirstVisible) {
onFragmentFirstVisible();
isFirstVisible = false;
}
onFragmentVisibleChanged(true);
isFragmentVisible = true;
}
}
super.onViewCreated(isReuseView ? rootView : view, savedInstanceState);
}

@Override
public void onDestroyView() {
super.onDestroyView();
}

@Override
public void onDestroy() {
super.onDestroy();
initVariable();
}

private void initVariable() {
isFirstVisible = true;
isFragmentVisible = false;
rootView = null;
isReuseView = true;
}

/**
* 设置是否使用 View 的复用,默认开启
* View 的复用是指,ViewPager 在销毁和重建 Fragment 时会不断调用 onCreateView() -> onDestroyView()
* 之间的生命函数,这样可能会出现重复创建 View 的情况,导致界面上显示多个相同的 Fragment
* View 的复用其实就是指保存第一次创建的 View,后面再 onCreateView() 时直接返回第一次创建的 View
*
* @param isReuse
*/
protected void reuseView(boolean isReuse) {
isReuseView = isReuse;
}

/**
* 去除setUserVisibleHint()多余的回调场景,保证只有当fragment可见状态发生变化时才回调
* 回调时机在View创建完后,所以支持UI操作,解决在setUserVisibleHint()里进行UI操作有可能报null异常的问题
* 可在该回调方法里进行一些UI显示与隐藏,比如加载框的显示和隐藏
*
* @param isVisible true 不可见 -> 可见
* false 可见 -> 不可见
*/
protected void onFragmentVisibleChanged(boolean isVisible) {

}

/**
* 在Fragment首次可见时回调,可在这里进行加载数据,保证只在第一次打开Fragment时才会加载数据,这样就可以防止每次进入都重复加载数据
* 该方法会在 onFragmentVisibleChanged() 之前调用,所以第一次打开时,可以用一个全局变量表示数据下载状态,然后在该方法内将状态设置为下载状态,接着去执行下载的任务
* 最后在 onFragmentVisibleChanged() 里根据数据下载状态来控制下载进度UI控件的显示与隐藏
*/
protected void onFragmentFirstVisible() {

}

protected boolean isFragmentVisible() {
return isFragmentVisible;
}
}

FragmentStatePagerAdapter和FragmentPagerAdapter的区别

  1. FragmentPagerAdapter适用于Fragment比较少的情况,因为FragmentPagerAdapter会把每一个Fragment保存在内存中,不用每次切换的时候去保存现场,切换回来再重新创建,所以用户体验比较好。没有onDestored()
  2. 而对于Fragment比较多的情况,我们需要切换的时候销毁以前的Fragment以释放内存,就可以使用FragmentStatePagerAdapter.onDestored()

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :