Android Fragment 进阶
01.什么是内存重启
- 安卓app有一种特殊情况,就是 app运行在后台的时候,系统资源紧张的时候导致把app的资源全部回收(杀死app的进程),这时把app再从后台返回到前台时,app会重启。这种情况下文简称为:“内存重启”。(屏幕旋转等配置变化也会造成当前Activity重启,本质与“内存重启”类似)
- 在系统要把app回收之前,系统会把Activity的状态保存下来,Activity的FragmentManager负责把Activity中的Fragment保存起来。在“内存重启”后,Activity的恢复是从栈顶逐步恢复,Fragment会在宿主Activity的onCreate方法调用后紧接着恢复(从onAttach生命周期开始)。
02.Fragment常用方法
- Fragment 的动态添加、删除等操作都需要借助于 FragmentTransaction 类来完成,比如上面提到的 commit() 操作,下面是几种常用的方法:
- add() 系列:添加 Fragment 到 Activity 界面中;
- remove():移除 Activity 中的指定 Fragment;
- replace() 系列:通过内部调用 remove() 和 add() 完成 Fragment 的修改;
- hide() 和 show():隐藏和显示 Activity 中的 Fragment;
- addToBackStack():添加当前事务到回退栈中,即当按下返回键时,界面回归到当前事物状态;
- commit():提交事务,所有通过上述方法对 Fragment 的改动都必须通过调用 commit() 方法完成提交
- replace()和hide()区别
- replace()和hide()都可以动态的在Activity中显示多个Fragment,并且可以来回灵活的切换,但是它们有很大的区别,replace() 方法不会保留 Fragment 的状态,也就是说诸如 EditText 内容输入等用户操作在 remove() 时会消失;但是hide()却不会,能完整的保留用户的处理信息。
- addToBackStack()退栈
- 当用户按下返回键时,如果回退栈中保存有之前的事务,会先执行事务回退,然后再执行Activity的finish()方法 。
- add(), show(), hide(), replace()区别
- 它们之间区别
- show(),hide()最终是让Fragment的View setVisibility(true还是false),不会调用生命周期;
- replace()的话会销毁视图,即调用onDestoryView、onCreateView等一系列生命周期;
- add()和 replace()不要在同一个阶级的FragmentManager里混搭使用。
- 使用场景
- 如果你有一个很高的概率会再次使用当前的Fragment,建议使用show(),hide(),可以提高性能。
- 在我使用Fragment过程中,大部分情况下都是用show(),hide(),而不是replace
- 注意:如果你的app有大量图片,这时更好的方式可能是replace,配合你的图片框架在Fragment视图销毁时,回收其图片所占的内存。
- 它们之间区别
03.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 {
//当页面展现时
}
}
}
04.传递和接收参数
4.1 最常用的方法
对Fragment传递数据,建议使用setArguments(Bundle args),而后在onCreate中使用getArguments()取出
- 在 “内存重启”前,系统会帮你保存数据,不会造成数据的丢失。和Activity的Intent恢复机制类似。
- 使用newInstance(参数) 创建Fragment对象,优点是调用者只需要关系传递的哪些数据,而无需关心传递数据的Key是什么。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public static WyNewsFragment newInstance(String param) {
WyNewsFragment fragment = new WyNewsFragment();
Bundle args = new Bundle();
args.putString(TYPE, param);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mType = getArguments().getString(TYPE);
}
}
4.2 Fragment与Activity之间传值
- Fragment与Activity之间是如何传值的?
- 1.Activity向Fragment传值:
- 步骤:
- 要传的值,放到bundle对象里;
- 在Activity中创建该Fragment的对象fragment,通过调用
- fragment.setArguments()传递到fragment中;
- 在该Fragment中通过调用getArguments()得到bundle对象,就能得到里面的值。
- 2.Fragment向Activity传值:
- 第一种:
- 在Activity中调用getFragmentManager()得到fragmentManager,,调用findFragmentByTag(tag)或者通过findFragmentById(id)
- FragmentManager fragmentManager = getFragmentManager();
- Fragment fragment = fragmentManager.findFragmentByTag(tag);
- 第二种:
- 通过回调的方式,定义一个接口(可以在Fragment类中定义),接口中有一个空的方法,在fragment中需要的时候调用接口的方法,值可以作为参数放在这个方法中,然后让Activity实现这个接口,必然会重写这个方法,这样值就传到了Activity中。
- 第一种:
- 1.Activity向Fragment传值:
4.3 Fragment与Fragment之间传值
- Fragment与Fragment之间是如何传值的?
- 第一种:
- 通过findFragmentByTag得到另一个的Fragment的对象,这样就可以调用另一个的方法了。
- 第二种:
- 通过接口回调的方式。
- 第三种:
- 通过setArguments,getArguments的方式。
- 第一种:
4.4 为何不构造传值
- 为什么fragment传递数据不用构造方法传递?
- activity给fragment传递数据一般不通过fragment的构造方法来传递,会通过setArguments来传递,因为当横竖屏会调用fragment的空参构造函数,数据丢失。
05.FragmentManager栈视图
- 简要的关系图
- 每个Fragment以及宿主Activity(继承自FragmentActivity)都会在创建时,初始化一个FragmentManager对象,处理好Fragment嵌套问题的关键,就是理清这些不同阶级的栈视图。
- 获取FragmentManager对象
- 对于宿主Activity,getSupportFragmentManager()获取的FragmentActivity的FragmentManager对象;
- 对于Fragment,getFragmentManager()是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()是获取自己的FragmentManager对象。
06.Fragment之懒加载使用
- Fragment之懒加载使用
- 懒加载,其实也就是延迟加载,就是等到该页面的UI展示给用户时,再加载该页面的数据(从网络、数据库等),而不是依靠ViewPager预加载机制提前加载两三个,甚至更多页面的数据。这样可以提高所属Activity的初始化速度,也可以为用户节省流量.而这种懒加载的方式也已经/正在被诸多APP所采用。
- 具体可以参考这篇博客,Android 懒加载优化:https://www.jianshu.com/p/cf1f4104de78
- 使用FragmentPagerAdapter+ViewPager时
- 使用FragmentPagerAdapter+ViewPager时,切换回上一个Fragment页面时(已经初始化完毕),不会回调任何生命周期方法以及onHiddenChanged(),只有setUserVisibleHint(boolean isVisibleToUser)会被回调,所以如果你想进行一些懒加载,需要在这里处理。
- 在给ViewPager绑定FragmentPagerAdapter时
- 在给ViewPager绑定FragmentPagerAdapter时,new FragmentPagerAdapter(fragmentManager)的FragmentManager,一定要保证正确,如果ViewPager是Activity内的控件,则传递getSupportFragmentManager(),如果是Fragment的控件中,则应该传递getChildFragmentManager()。只要记住ViewPager内的Fragments是当前组件的子Fragment这个原则即可。
07.首页Fragment使用
首页tab对应4个fragment,用法如下所示
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
80private void showFragment(int index) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
hideFragment(ft);
position = index;
switch (index) {
case FRAGMENT_HOME:
/**
* 如果Fragment为空,就新建一个实例
* 如果不为空,就将它从栈中显示出来
*/
if (homeFragment == null) {
homeFragment = BaseFragmentFactory.getInstance().getHomeFragment();
ft.add(R.id.fl_main, homeFragment, HomeFragment.class.getName());
} else {
ft.show(homeFragment);
}
break;
case FRAGMENT_MOVIE:
if (mMovieFragment == null) {
mMovieFragment = BaseFragmentFactory.getInstance().getMovieFragment();
ft.add(R.id.fl_main, mMovieFragment, MovieFragment.class.getName());
} else {
ft.show(mMovieFragment);
}
break;
case FRAGMENT_VIDEO:
if (videoFragment == null) {
videoFragment = BaseFragmentFactory.getInstance().getVideoFragment();
ft.add(R.id.fl_main, videoFragment, VideoFragment.class.getName());
} else {
ft.show(videoFragment);
}
break;
case FRAGMENT_ME:
if (meFragment == null) {
meFragment = BaseFragmentFactory.getInstance().getMeFragment();
ft.add(R.id.fl_main, meFragment, MeFragment.class.getName());
} else {
ft.show(meFragment);
}
break;
case FRAGMENT_NEWS:
if (newsFragment == null) {
newsFragment = BaseFragmentFactory.getInstance().getNewsFragment();
ft.add(R.id.fl_main, newsFragment, MeFragment.class.getName());
} else {
ft.show(newsFragment);
}
break;
default:
break;
}
ft.commit();
}
private void hideFragment(FragmentTransaction ft) {
// 如果不为空,就先隐藏起来
if (homeFragment != null) {
setHide(ft,homeFragment);
}
if (newsFragment != null) {
setHide(ft,newsFragment);
}
if (mMovieFragment != null) {
setHide(ft,mMovieFragment);
}
if (videoFragment != null) {
setHide(ft,videoFragment);
}
if (meFragment != null) {
setHide(ft,meFragment);
}
}
private void setHide(FragmentTransaction ft, Fragment fragment) {
if(fragment.isAdded()){
ft.hide(fragment);
}
}
08.思考Fragment能否不依赖Activity吗
Fragment能否不依赖于Activity存在?
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();