Android解决多个Fragment切换时布局重新实例化问题

布局文件:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<code class = "hljs xml" ><linearlayout android:layout_height= "match_parent" android:layout_width= "match_parent" android:orientation= "vertical" tools:context= "fan.fragmentdemo.MainActivity" xmlns:android= "http://schemas.android.com/apk/res/android" xmlns:tools= "http://schemas.android.com/tools" >
 
     //导航栏
     <linearlayout android:layout_height= "48dp" android:layout_width= "match_parent" android:orientation= "horizontal" >
 
         <textview android:gravity= "center" android:id= "@+id/tv_one" android:layout_height= "match_parent" android:layout_weight= "1" android:layout_width= "0dp" android:text= "第一个" >
 
         <textview android:background= "#EEE" android:layout_height= "match_parent" android:layout_width= "1dp" >
 
         <textview android:gravity= "center" android:id= "@+id/tv_two" android:layout_height= "match_parent" android:layout_weight= "1" android:layout_width= "0dp" android:text= "第二个" >
 
         <textview android:background= "#EEE" android:layout_height= "match_parent" android:layout_width= "1dp" >
 
         <textview android:gravity= "center" android:id= "@+id/tv_three" android:layout_height= "match_parent" android:layout_weight= "1" android:layout_width= "0dp" android:text= "第三个" >
 
     </textview></textview></textview></textview></textview></linearlayout>
 
     //内容区域
     <framelayout android:background= "#EEE" android:id= "@+id/content" android:layout_height= "match_parent" android:layout_width= "match_parent" >
 
 
</framelayout></linearlayout></code>





布局预览图:


这里写图片描述
 <喎�"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxiciAvPg0KPGJyIC8+DQo8YnIgLz4NCjxiciAvPg0K0tTHsNC0tuC49mZyYWdtZW50x9C7u8rHvq2zo8q508PV4tbWt723qMfQu7tmcmFnbWVudKO6PGJyIC8+DQombmJzcDs8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">/** * 使用replace切换页面 * 显示fragment */ private void showFragment(Fragment fg){ FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.content, fg); transaction.commit(); }





replace():该方法只是在上一个Fragment不再需要时采用的简便方法,弊端就是如果需要重复使用该fragment时,需要每次都要重新加载一次。比如我在第一个fragment输入信息后,切换第二个fragment后再切换回去,就会造成数据丢失,如下:



这里写图片描述







而且,如果每切换一次就实例化一次的话,FragmentManager管理下的栈也会爆满,最终会导致手机卡顿,这很明显不是正确的Fragment使用姿势,这时,我们就需要使用show()、hide()、add()了,正确的切换方式是add(),切换时hide(),add()另一个Fragment;再次切换时,只需hide()当前,show()另一个就行了,代码修改如下:
 

?
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
<code class = "hljs java" > /**
   * 使用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();
 
  }</code>



效果:

这里写图片描述







有上图,可以看出,即使切换到别的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。,修改如下:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<code class = "hljs java" > /**
   * 使用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();
 
  }</code>





别急,还没完,在当前Activity的onCreate()方法里面添加一下代码:
 

?
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
<code class = "hljs fsharp" > 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);
}</code>





OK,当app后台时遇到“内存重启”的情况下,再返回我们的app,就会恢复到show(fgOne)页面,而且还不会造成重叠问题!
 

 

很显然,这样结束是不道德的,因为有人会问了,如果想记录当前退出的状态以至于下次恢复时直接显示之前的fragment页面怎么办,恩,对于这个问题,我们可以在activity的onSaveInstanceState()方法中记录一下“内存重启”之前的Fragment的页面,然后在oncreate()中取出来,根据保存的页面来显示到指定的fragment,代码如下:

 

?
1
2
3
4
5
6
7
<code class = "hljs java" > @Override
protected void onSaveInstanceState(Bundle outState) {
 
     //“内存重启”时保存当前的fragment名字
     outState.putString(STATE_FRAGMENT_SHOW,currentFragment.getClass().getName());
     super .onSaveInstanceState(outState);
}</code>





然后在oncreate()方法中添加(修改上面的那个代码)
 

?
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
<code class = "hljs avrasm" > 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);
}</code>





OK,这样就可以了,我们通过保存当前显示的fragment的类名,当我们在第二个fragment页面时后台,等到“内存重启”后返回该app时,就根据之前保存的类名来判断加载指定的fragment,而且,重叠的问题也解决了!
 

本章代码及apk:点击免费下载



 

 

最后在说一点:

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的引用(有引起内存泄露的风险,但是相比空指针闪退,这种做法“安全”些),即:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<code class = "hljs java" > protected Activity mActivity;
@Override
public void onAttach(Activity activity) {
     super .onAttach(activity);
     this .mActivity = activity;
}
 
/**
*  如果你用了support 23的库,上面的方法会提示过时,有强迫症的小伙伴,可以用下面的方法代替
*/
@Override
public void onAttach(Context context) {
     super .onAttach(context);
     this .mActivity = (Activity)context;
}</code>


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值