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

来源:互联网 发布:数控车床排刀架编程 编辑:程序博客网 时间:2024/04/29 22:10

本文借鉴自:http://www.jianshu.com/p/d9143a92ad94



至于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

阅读全文
0 0
原创粉丝点击