Fragment进阶使用技巧

内存重启

当App运行在后台,系统由于资源紧张把App杀死回收,此时App从后台切回前台会产生重启现象,我们把这种情况称为“内存重启”。另外,屏幕旋转等配置变化也会造成当前Activity重启,本质与“内存重启”类似。

在系统要把app回收之前,系统会把Activity的状态保存下来,Activity的FragmentManager负责把Activity中的Fragment保存起来。在“内存重启”后,Activity的恢复是从栈顶逐步恢复,Fragment会在宿主Activity的onCreate方法调用后紧接着恢复(从onAttach生命周期开始)。

Fragment常用方法

Fragment 的动态添加、删除等操作都需要借助于FragmentTransaction类来完成,下面是几种常用的方法:

  • add() 系列:添加 Fragment 到 Activity 界面中;
  • remove():移除 Activity 中的指定 Fragment;
  • replace() 系列:通过内部调用 remove() 和 add() 完成 Fragment 的修改;
  • hide() 和 show():隐藏和显示 Activity 中的 Fragment;
  • addToBackStack():添加当前事务到回退栈中,即当按下返回键时,界面回归到当前事物状态;
  • commit():提交事务,所有通过上述方法对 Fragment 的改动都必须通过调用 commit() 方法完成提交

add、show、hide、replace的区别

区别:

  • show(),hide()最终是让Fragment的View setVisibility(true还是false),不会调用生命周期;
  • replace()的话会销毁视图,即调用onDestoryView、onCreateView等一系列生命周期;
  • 注意:add()和 replace()不要在同一个阶级的FragmentManager里混搭使用。

使用场景:

  • 如果你有一个很高的概率会再次使用当前的Fragment,建议使用show(),hide(),可以提高性能。
  • 大部分情况下用show(),hide(),而不是replace
  • 注意:如果你的app有大量图片,这时更好的方式可能是replace,配合你的图片框架在Fragment视图销毁时,回收其图片所占的内存。

onHiddenChanged()回调时机

当使用add()+show()/hide()跳转新的Fragment时,旧的Fragment回调onHiddenChanged(),不会回调onStop()等生命周期方法,而新的Fragment在创建时是不会回调onHiddenChanged()的

1
2
3
4
5
6
7
8
9
10
11
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if(activity!=null){
if(hidden){
//当该页面隐藏时
}else {
//当页面展现时
}
}
}

为何不使用构造传值

当Activity重建时会调用Fragment的无参构造来重建Fragment,会导致数据丢失。

Fragment栈视图

每个Fragment以及宿主Activity(继承自FragmentActivity)都会在创建时,初始化一个FragmentManager对象,处理好Fragment嵌套问题的关键,就是理清这些不同阶级的栈视图。

获取FragmentManager对象

  • 对于宿主Activity,getSupportFragmentManager()获取的FragmentActivity的FragmentManager对象;
  • 对于Fragment,getFragmentManager()是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()是获取自己的FragmentManager对象。

Fragment之懒加载

懒加载,其实也就是延迟加载,就是等到该页面的UI展示给用户时,再加载该页面的数据(从网络、数据库等)。

Fragment中实现懒加载主要涉及setUserVisibleHint()方法,该方法会在onCreateView之前执行,当Fragment从可见到不可见或是从不可见到可见时,都会调用此方法。此外可以使用getUserVisibleHint()来判断Fragment是否可见。

ViewPager与Fragment

在使用viewpager(或其他容器)与多个Fragment来组合使用,ViewPager 会默认一次加载当前页面前后隔一个页面,即使设置setofflimit(0)也无效果,也会预加载。这样把我们看不到的页面的数据也加载了,大大降低了性能,浪费初始化资源。然而我们就采用懒加载技术,只让用户看到的页面才会加载他的数据,大大提高效率。

代码案例:

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
public abstract class BaseMVPLazyFragment<T extends IBasePresenter> extends BaseMVPFragment<T> {
/**
* Fragment的View加载完毕的标记
*/
protected boolean isViewInitiated;
/**
* Fragment对用户可见的标记
*/
protected boolean isVisibleToUser;
/**
* 是否懒加载
*/
protected boolean isDataInitiated;

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

/**
* 第一步,改变isViewInitiated标记
* 当onViewCreated()方法执行时,表明View已经加载完毕,此时改变isViewInitiated标记为true,并调用lazyLoad()方法
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
isViewInitiated = true;
//只有Fragment onCreateView好了,
//另外这里调用一次lazyLoad()
prepareFetchData();
//lazyLoad();
}

/**
* 第二步
* 此方法会在onCreateView()之前执行
* 当viewPager中fragment改变可见状态时也会调用
* 当fragment 从可见到不见,或者从不可见切换到可见,都会调用此方法
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
prepareFetchData();
}

/**
* 第四步:定义抽象方法fetchData(),具体加载数据的工作,交给子类去完成
*/
public abstract void fetchData();

/**
* 第三步:在lazyLoad()方法中进行双重标记判断,通过后即可进行数据加载
* 第一种方法
* 调用懒加载,getUserVisibleHint()会返回是否可见状态
* 这是fragment实现懒加载的关键,只有fragment 可见才会调用onLazyLoad() 加载数据
*/
private void lazyLoad() {
if (getUserVisibleHint() && isViewInitiated && !isDataInitiated) {
fetchData();
isDataInitiated = true;
}
}

/**
* 第二种方法
* 调用懒加载
*/
public void prepareFetchData() {
prepareFetchData(false);
}

/**
* 第三步:在lazyLoad()方法中进行双重标记判断,通过后即可进行数据加载
*/
public void prepareFetchData(boolean forceUpdate) {
if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
fetchData();
isDataInitiated = true;
}
}
}

在BaseMVPLazyFragment中需要在onActivityCreated()及setUserVisibleHint()方法中都调了一次lazyLoad() 方法。如果仅仅在setUserVisibleHint()调用lazyLoad(),当默认首页首先加载时会导致viewPager的首页第一次展示时没有数据显示,切换一下才会有数据。因为首页fragment的setUserVisible()在onActivityCreated() 之前调用,此时isPrepared为false 导致首页fragment 没能调用onLazyLoad()方法加载数据。

onLazyLoad()加载数据条件

  • getUserVisibleHint()会返回是否可见状态,这是fragment实现懒加载的关键,只有fragment 可见才会调用onLazyLoad() 加载数据。
  • isPrepared参数在系统调用onActivityCreated时设置为true,这时onCreateView方法已调用完毕(一般我们在这方法里执行findviewbyid等方法),确保 onLazyLoad()方法不会报空指针异常。
  • isLazyLoaded确保ViewPager来回切换时BaseFragment的initData方法不会被重复调用,onLazyLoad在该Fragment的整个生命周期只调用一次,第一次调用onLazyLoad()方法后马上执行 isLazyLoaded = true。
  • 然后再继承这个BaseMVPLazyFragment实现onLazyLoad() 方法就行。他会自动控制当fragment 展现出来时,才会加载数据

使用FragmentPagerAdapter需要注意的

在给ViewPager绑定FragmentPagerAdapter时,new FragmentPagerAdapter(fragmentManager)的FragmentManager,一定要保证正确。如果ViewPager是Activity内的控件,则传递getSupportFragmentManager(),如果是Fragment内的控件,则传递getChildFragmentManager()。只要记住ViewPager内的Fragments是当前组件的子Fragment这个原则即可。

关于事务Transaction

Fragment不能独立存在,它必须嵌入到activity中,而且Fragment的生命周期直接受所在的activity的影响。

transaction只是记录了从一个状态到另一个状态的变化过程,即比如从FragmentA替换到FragmentB的过程,当通过函数transaction.addToBackStack(null)将这个事务添加到回退栈,则会记录这个事务的状态变化过程,如从FragmentA —>FragmentB,当用户点击手机回退键时,因为transaction的状态变化过程被保存,则可以将事务的状态变化过程还原,即将FragmentB —> FragmentA。

1
2
3
4
5
6
7
8
9
10
11
// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

关于commit()与commitAllowingStateLoss()

若在Activity的onSaveInstanceState()方法之后调用commit()方法提交事务会出错,并提示“Can not perform this action after onSaveInstanceState”。因为onSaveInstanceState方法是在该Activity即将被销毁前调用,来保存Activity数据的,如果在保存完状态后再给它添加Fragment就会出错。如果无法确定onSaveInstanceState()的调用时机,使用commitAllowingStateLoss()方法。

看看源码:

1
2
3
4
5
6
7
8
9
@Override
public int commit() {
return commitInternal(false);
}

@Override
public int commitAllowingStateLoss() {
return commitInternal(true);
}

从源代码来看,它们调用了同一个方法只是传递的参数不同,那么就是这个参数导致的不同了,继续往下看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int commitInternal(boolean allowStateLoss) {
.......
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
........
}
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException(
"Can not perform this action inside of " + mNoTransactionsBecause);
}
}

可以看到allowStateLoss的值主要是影响了checkStateLoss()的调用,当allowStateLoss为true时就会在onSaveInstanceState()被调用后抛出异常。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!