Android解决多个Fragment切换时布局重新实例化问题和getActivity空指针问题
来源:互联网 发布:php curl 模拟cookie 编辑:程序博客网 时间:2024/05/16 17:40
至于fragment的使用就不多说了,直奔主题,
布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="fan.fragmentdemo.MainActivity"> //导航栏 <LinearLayout android:layout_width="match_parent" android:layout_height="48dp" android:orientation="horizontal"> <TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:text="第一个" android:id="@+id/tv_one" android:gravity="center" /> <TextView android:layout_width="1dp" android:layout_height="match_parent" android:background="#EEE" /> <TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:text="第二个" android:id="@+id/tv_two" android:gravity="center" /> <TextView android:layout_width="1dp" android:layout_height="match_parent" android:background="#EEE" /> <TextView android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:text="第三个" android:id="@+id/tv_three" android:gravity="center" /> </LinearLayout> //内容区域 <FrameLayout android:id="@+id/content" android:background="#EEE" android:layout_width="match_parent" android:layout_height="match_parent" /></LinearLayout>
- 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
- 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
布局预览图:
以前写多个fragment切换是经常使用这种方法切换fragment:
/** * 使用replace切换页面 * 显示fragment */ private void showFragment(Fragment fg){ FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.content, fg); transaction.commit(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
replace():该方法只是在上一个Fragment不再需要时采用的简便方法,弊端就是如果需要重复使用该fragment时,需要每次都要重新加载一次。比如我在第一个fragment输入信息后,切换第二个fragment后再切换回去,就会造成数据丢失,如下:
而且,如果每切换一次就实例化一次的话,FragmentManager管理下的栈也会爆满,最终会导致手机卡顿,这很明显不是正确的Fragment使用姿势,这时,我们就需要使用show()、hide()、add()了,正确的切换方式是add(),切换时hide(),add()另一个Fragment;再次切换时,只需hide()当前,show()另一个就行了,代码修改如下:
/** * 使用show() hide()切换页面 * 显示fragment */ private void showFragment(Fragment fg){ FragmentTransaction transaction = fragmentManager.beginTransaction(); //如果之前没有添加过 if(!fg.isAdded()){ transaction .hide(currentFragment) .add(R.id.content,fg); }else{ transaction .hide(currentFragment) .show(fg); } //全局变量,记录当前显示的fragment currentFragment = fg; transaction.commit(); }
- 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
- 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
效果:
有上图,可以看出,即使切换到别的fragment,再切换回来,数据还依然存在,这就避免了Fragment切换时布局重新实例化。
安卓app有一种特殊情况,就是 app运行在后台的时候,系统资源紧张的时候导致把app的资源全部回收(杀死app的进程),这时把app再从后台返回到前台时,app会重启。这种情况下文简称为:“内存重启”。(屏幕旋转等配置变化也会造成当前Activity重启,本质与“内存重启”类似)
在系统要把app回收之前,系统会把Activity的状态保存下来,Activity的FragmentManager负责把Activity中的Fragment保存起来。在“内存重启”后,Activity的恢复是从栈顶逐步恢复,Fragment会在宿主Activity的onCreate方法调用后紧接着恢复
当我们不退出软件,只是后台挂着去干别的事,当系统内存不足回收我们这个app时,再切换回来,app的这几个Fragment界面会重叠。,如下图:
由上图可以看出,三个fragment全部叠在了一起,而且点击上面菜单也不能消除重叠。显然,这并不是我们想要的,没事,继续解决问题,使用findFragmentByTag:即在add()或者replace()时绑定一个tag,一般我们是用fragment的类名作为tag,然后在发生“内存重启”时,通过findFragmentByTag找到对应的Fragment,并hide()需要隐藏的fragment。,修改如下:
/** * 使用show() hide()切换页面 * 显示fragment */ private void showFragment(Fragment fg){ FragmentTransaction transaction = fragmentManager.beginTransaction(); //如果之前没有添加过 if(!fg.isAdded()){ transaction .hide(currentFragment) .add(R.id.content,fg,fg.getClass().getName()); //第三个参数为当前的fragment绑定一个tag,tag为当前绑定fragment的类名 }else{ transaction .hide(currentFragment) .show(fg); } currentFragment = fg; transaction.commit(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
别急,还没完,在当前Activity的onCreate()
方法里面添加一下代码:
if (savedInstanceState != null) { // “内存重启”时调用 //从fragmentManager里面找到fragment fgOne = (OneFragment) fragmentManager.findFragmentByTag(OneFragment.class.getName()); fgTwo = (TwoFragment) fragmentManager.findFragmentByTag(TwoFragment.class.getName()); fgThree = (ThreeFragment) fragmentManager.findFragmentByTag(ThreeFragment.class.getName()); //解决重叠问题show里面可以指定恢复的页面 fragmentManager.beginTransaction() .show(fgOne) .hide(fgTwo) .hide(fgThree) .commit(); //把当前显示的fragment记录下来 currentFragment = fgOne;}else{ //正常启动时调用 fgOne = new OneFragment(); fgTwo = new TwoFragment(); fgThree = new ThreeFragment(); showFragment(fgOne);}
- 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
- 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
OK,当app后台时遇到“内存重启”的情况下,再返回我们的app,就会恢复到show(fgOne)
页面,而且还不会造成重叠问题!
很显然,这样结束是不道德的,因为有人会问了,如果想记录当前退出的状态以至于下次恢复时直接显示之前的fragment页面怎么办,恩,对于这个问题,我们可以在activity的onSaveInstanceState()
方法中记录一下“内存重启”之前的Fragment的页面,然后在oncreate()
中取出来,根据保存的页面来显示到指定的fragment,代码如下:
@Overrideprotected void onSaveInstanceState(Bundle outState) { //“内存重启”时保存当前的fragment名字 outState.putString(STATE_FRAGMENT_SHOW,currentFragment.getClass().getName()); super.onSaveInstanceState(outState);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
然后在oncreate()
方法中添加(修改上面的那个代码)
if (savedInstanceState != null) { // “内存重启”时调用 //获取“内存重启”时保存的fragment名字 String saveName = savedInstanceState.getString(STATE_FRAGMENT_SHOW); //从fragmentManager里面找到fragment fgOne = (OneFragment) fragmentManager.findFragmentByTag(OneFragment.class.getName()); fgTwo = (TwoFragment) fragmentManager.findFragmentByTag(TwoFragment.class.getName()); fgThree = (ThreeFragment) fragmentManager.findFragmentByTag(ThreeFragment.class.getName()); //如果为空就默认操作 if(TextUtils.isEmpty(saveName)){ //解决重叠问题 fragmentManager.beginTransaction() .show(fgOne) .hide(fgTwo) .hide(fgThree) .commit(); //把当前显示的fragment记录下来 currentFragment = fgOne; }else{ if(saveName.equals(fgOne.getClass().getName())){ //如果推出之前是OneFragment //解决重叠问题 fragmentManager.beginTransaction() .show(fgOne) .hide(fgTwo) .hide(fgThree) .commit(); //把当前显示的fragment记录下来 currentFragment = fgOne; }else if(saveName.equals(fgTwo.getClass().getName())){ //如果推出之前是TwoFragment //解决重叠问题 fragmentManager.beginTransaction() .show(fgTwo) .hide(fgOne) .hide(fgThree) .commit(); //把当前显示的fragment记录下来 currentFragment = fgTwo; }else{ //如果推出之前是ThreeFragment //解决重叠问题 fragmentManager.beginTransaction() .show(fgThree) .hide(fgTwo) .hide(fgOne) .commit(); //把当前显示的fragment记录下来 currentFragment = fgThree; } }}else{ //正常启动时调用 fgOne = new OneFragment(); fgTwo = new TwoFragment(); fgThree = new ThreeFragment(); showFragment(fgOne);}
- 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
- 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
OK,这样就可以了,我们通过保存当前显示的fragment的类名,当我们在第二个fragment页面时后台,等到“内存重启”后返回该app时,就根据之前保存的类名来判断加载指定的fragment,而且,重叠的问题也解决了!
本章代码及apk:点击免费下载
当然,有网友问了,如果fragment比较多,那么多if多麻烦,是啊,上面的代码主要就是让大家理解一下思路,具体开发不建议那么写,下面,就说一下上面的代码该如何精简吧:
创建一个List用来存所有的fragment,给fragment设置tag可以使用当前的索引index,然后在onSaveInstanceState
中保存当前的索引,在恢复时通过索引来找到fragment
全部代码修改精简后如下:::
//当前显示的fragmentprivate static final String CURRENT_FRAGMENT = "STATE_FRAGMENT_SHOW";private TextView tvone;private TextView tvtwo;private TextView tvthree;private FragmentManager fragmentManager;private Fragment currentFragment = new Fragment();private List<Fragment> fragments = new ArrayList<>();private int currentIndex = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.tvthree = (TextView) findViewById(R.id.tv_three); this.tvtwo = (TextView) findViewById(R.id.tv_two); this.tvone = (TextView) findViewById(R.id.tv_one); fragmentManager = getSupportFragmentManager(); tvthree.setOnClickListener(this); tvtwo.setOnClickListener(this); tvone.setOnClickListener(this); if (savedInstanceState != null) { // “内存重启”时调用 //获取“内存重启”时保存的索引下标 currentIndex = savedInstanceState.getInt(CURRENT_FRAGMENT,0); //注意,添加顺序要跟下面添加的顺序一样!!!! fragments.removeAll(fragments); fragments.add(fragmentManager.findFragmentByTag(0+"")); fragments.add(fragmentManager.findFragmentByTag(1+"")); fragments.add(fragmentManager.findFragmentByTag(2+"")); //恢复fragment页面 restoreFragment(); }else{ //正常启动时调用 fragments.add(new OneFragment()); fragments.add(new TwoFragment()); fragments.add(new ThreeFragment()); showFragment(); }}@Overrideprotected void onSaveInstanceState(Bundle outState) { //“内存重启”时保存当前的fragment名字 outState.putInt(CURRENT_FRAGMENT,currentIndex); super.onSaveInstanceState(outState);}@Overridepublic void onClick(View v) { switch (v.getId()){ case R.id.tv_one: currentIndex = 0; break; case R.id.tv_two: currentIndex = 1; break; case R.id.tv_three: currentIndex = 2; break; } showFragment();}/** * 使用show() hide()切换页面 * 显示fragment */private void showFragment(){ FragmentTransaction transaction = fragmentManager.beginTransaction(); //如果之前没有添加过 if(!fragments.get(currentIndex).isAdded()){ transaction .hide(currentFragment) .add(R.id.content,fragments.get(currentIndex),""+currentIndex); //第三个参数为添加当前的fragment时绑定一个tag }else{ transaction .hide(currentFragment) .show(fragments.get(currentIndex)); } currentFragment = fragments.get(currentIndex); transaction.commit();}/** * 恢复fragment */private void restoreFragment(){ FragmentTransaction mBeginTreansaction = fragmentManager.beginTransaction(); for (int i = 0; i < fragments.size(); i++) { if(i == currentIndex){ mBeginTreansaction.show(fragments.get(i)); }else{ mBeginTreansaction.hide(fragments.get(i)); } } mBeginTreansaction.commit(); //把当前显示的fragment记录下来 currentFragment = fragments.get(currentIndex);}
- 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
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 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
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
精简后的代码下载:点击下载
最后在说一点:
getActivity()空指针
可能你遇到过getActivity()返回null,或者平时运行完好的代码,在“内存重启”之后,调用getActivity()的地方却返回null,报了空指针异常。
大多数情况下的原因:你在调用了getActivity()时,当前的Fragment已经onDetach()了宿主Activity。
比如:你在pop了Fragment之后,该Fragment的异步任务仍然在执行,并且在执行完成后调用了getActivity()方法,这样就会空指针。
解决办法:
更”安全”的方法:(对于Fragment已经onDetach这种情况,我们应该避免在这之后再去调用宿主Activity对象,比如取消这些异步任务,但我们的团队可能会有粗心大意的情况,所以下面给出的这个方案会保证安全)
在Fragment基类里设置一个Activity mActivity的全局变量,在onAttach(Activity activity)里赋值,使用mActivity代替getActivity(),保证Fragment即使在onDetach后,仍持有Activity的引用(有引起内存泄露的风险,但是相比空指针闪退,这种做法“安全”些),即:
protected Activity mActivity;@Overridepublic void onAttach(Activity activity) { super.onAttach(activity); this.mActivity = activity;}/*** 如果你用了support 23的库,上面的方法会提示过时,有强迫症的小伙伴,可以用下面的方法代替*/@Overridepublic void onAttach(Context context) { super.onAttach(context); this.mActivity = (Activity)context;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- Android解决多个Fragment切换时布局重新实例化问题和getActivity空指针问题
- Android解决多个Fragment切换时布局重新实例化问题
- Android解决多个Fragment切换时布局重新实例化问题
- Android解决多个Fragment切换时布局重新实例化问题
- Android解决多个Fragment切换时布局重新实例化问题
- Android解决多个Fragment切换时布局重新实例化问题(挺详细的)
- Android学习之解决多个Fragment切换时重新实例化的问题
- fragment里getactivity空指针问题
- 当多个fragment来回切换时,getActivity = null的问题
- Android - 多个Fragment切换不重新实例化
- fragment的handler中getActivity空指针问题
- Android 解决多个Fragment切换时不断实例化
- 解决在Fragment中getActivity()为空问题
- 在Fragment中getActivity()为空问题已解决
- fragment getActivity()空指针
- Fragment getActivity()空指针?
- 多个Fragment 切换时不重新实例化
- 多个Fragment 切换时不重新实例化
- Bootstrap table th td 实现文字垂直居中
- 纳税服务系统【用户模块之使用POI导入excel、导出excel】
- java中的this引用
- viewpager图片广告条点击转跳fragment-----【轮播图点击转跳详情界面】
- sql大全
- Android解决多个Fragment切换时布局重新实例化问题和getActivity空指针问题
- 属性的分类
- 外部排序和内部排序
- 数据结构-图-知识点总结
- apache + varnish 实现负载均衡
- C# 插入数据库datetime类型问题
- 【c#】imagelist图片失真
- Kotlin语法(四)
- J-Link v8固件丢失修复