相信小伙伴们会遇到过Fragment重叠的问题,不要慌
这里先针对需要救火的小伙伴给出解决方案,如果想知道原理,可以继续看下面的解释和源码分析。
解决方案:
在Fragment所在的Activity中,重写onSaveInstanceState方法,并添加以下两句话:
outState.putParcelable("android:support:fragments", null);
outState.putParcelable("android:fragments", null);
实际上一句就可以了,具体要看你使用的是什么包下的Fragment。
解释:
这行代码的含义很简单,就是activity执行onSaveInstanceState方法时清空里面已有的fragment变量,当新的fragment创建时,activity就不会存在新旧两套fragment,避免了产生Fragment重叠的现象。
那什么情况onSaveInstanceState会被调用呢?
有以下5种情况被调用:
1、按下home键的时候。因为按下home键后,系统不知道用户还要进行哪些操作,如果操作过多。应用很有可能被杀死。
2、长按home键或者菜单键(切换到其它应用)。
3、手机息屏时。
4、A Activity启动B Activity,A Activity就会调用,也就是说打开新Activity时,原Activity就会调用。
5、横竖屏切换时。
一句话总结就是当系统不知道这个activity(应用)还会使用多久,面临着被杀死回收的风险时就会调用这个方法,其设计目的是在应用可能被销毁时(非用户主动销毁,如back),提供用户进行数据的保存操作,这里就包括已添加过的fragment信息。
注意:onSaveInstanceState本身只是保存一些UI控件的状态数据(视图层),不适合做关键数据和持久化数据的保存工作。
为了模拟应用被回收重建的现象,有两个办法:
1.开发者选项-不保留活动:运行app,按home键退到桌面(回收),再点击app icon进入(重建)
2.旋转屏幕:前提是清单文件中没有设置 android:configChanges="keyboardHidden|orientation|screenSize"
源码分析:
现象重现了,大概原理也知道了,现在我们就要从源码入手,探究为什么要这样重写onSaveInstanceState方法。
我们将分析源码分为两大部分
第一部分,先看看onSaveInstanceState方法里做了什么
我有个习惯,看方法或者看类时,先看注释,一般注释里会解释此方法的作用及参数,可以帮我们更好的理解
这里的注释较多,我截取两段比较重要的:
*This method is called before an activity may be killed so that when it
* comes back some time in the future it can restore its state.
该方法在活动可能被杀死之前被调用,以便当它被杀死时在未来的某个时间回来,可以恢复它的状态。
* The default implementation takes care of most of the UI per-instance
* state for you by calling {@link android.view.View#onSaveInstanceState()} on each
* view in the hierarchy that has an id
默认实现会为你处理大部分UI每个实例的状态,在每个有id的view视图上
通过注释可以了解到onSaveInstanceState主要是用来在应用被杀死时保存视图的状态。
Step1. Activity 的 onSaveInstanceState(Bundle outState)方法
看方法里的实现,可以看到红框处,fragments.saveallState() 赋给 Parcelable 类型的变量p,p不为null时,把它当做value存放到outState中,其key为 FRAGMENTS_TAG,查看其定义:
static final String FRAGMENTS_TAG = "android:fragments";(重点)
这里先记住这个 FRAGMENTS_TAG ,后面会用到。
我们来看下,saveAllState里做了什么,点击saveAllState方法,跳转到了FragmentcController类的 saveAllState方法里。
Step2.FragmentcController的saveAllState()
这里可以看到是调用FragmentManager的saveAllState方法,跳转到FragmentManager来查看。
Step3.FragmentManager的saveAllState()方法
中间部分省略。。。
1:新建一个FragmentState类型的数组active
2:从成员变量mActive里取出Fragment, mActive的声明为:ArrayList
里面存放的是当前活动的Fragment列表。
3:把fragment包装成FragmentState类型的对象存放到active数组中
4.如果没有fragment,则返回null。回看Step1中,当p等于null时,则不会保存
5.最终,new 一个FragmentManagerState,把active数组赋给其成员变量mActive,并返回。
也就是说step1里要保存的p实际上就是这个FragmentManagerState。那么我们再看下FragmentManagerState里面有什么。
Step4.FragmentManager的内部类FragmentManagerState
FragmentManagerState是FragmentManager中的内部类,其声明如下
final class FragmentManagerState implements Parcelable,可以看出其目的为序列化的数据存储
FragmentManagerState 里很简单,主要是几个数组型的成员变量,这里我们主要来看FragmentState类型的数组mActive, 进入到FragmentState里。
Step5.Fragment的内部类FragmentState
FragmentState是Fragment的内部类,声明了如下的属性
可以看到这里记录了fragment的一些信息,并且还持有fragment的引用。在其构造方法中,可以看到把fragment同名属性的值赋值了过来,也就是说我们在Fragment类里面也能找到一一对应的属性,并且都有相关的注释说明:
具体的含义大家可以自行翻译,我就不做过多的介绍了。总之在step1中保存的p底层就是这些信息。
到这里,activity的onSaveInstanceState方法我们就大概清楚了,功能之一就是把fragment的一些状态进行保存。
接着,就是第二部分了,我们再看看activity创建的过程
Step6.Activity的onCreate(Bundle saveInstanceState)方法
直接进入到activity的onCreate方法
1:从bundle中取出名为FRAGMENT_TAG的p对象,没错就是在step1中的存入的那个key
2:从restoreAllState方法名就可以看出,恢复fragment所有状态
3:进入创建fragment流程
来看restoreAllState方法,mFragments是FragmentController类型的,进入。
Step7.FragmentController的restoreAllState(Parcelable state, List
这里又调用了FragmentManager的restoreAllState的方法。
Step8.FragmentManager的restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig)
红框处拿到了之前保存的P,并获取到里面的数组fms.mActive进行遍历
注意紫红色的注释:
Build the full list of active fragments, instantiating them from their saved state
构建活动Fragment的完整列表,从它们保存的状态实例化它们。
通过这句话进行实例化:Fragment f = fs.instantiate(mHost,mParent,childNonConfig)。
实例化后的fragment加到FragmentManagerImpl(FragmentManager的内部类)的成员变量ArrayList
所以,在restoreAllState方法中,主要是把保存的fragment实例化。
接着,我们看Step6的第3步,mFragments.dispatchCreate()方法,这里最终是调用FragmentManager的dispatchCreate方法
注意看,第一个参数传入了Fragment.CREATED常量(Fragment一共定义了5个,这里的常量int值在后面会用来做各种状态的大小判断)
很明显这里代表创建一个新的fragment。第二个参数为false。
再进入moveToState方法
Step9.FragmentManager.moveToState(...)
可以看到这个方法主要是做各种判断,根据fragment的状态来做下一步的处理(代码略长,我做了折叠处理),红框处将p里的fragment的state和newState值相比,newState就是之前第一个参数的CREATE。
我们在其中的一个分支看到 如下代码
这就是我们熟悉的将fragment view放到container中的流程了。
到这里,第二步Activity的onCreate流程就可以告一段落了,我们可以发现,onCreate里就会重建fragment,那本身程序里还有新建fragment的流程,这样相当于fragment创建了两次,当然就会重叠了。现在再回过头来看看我们的解决方案:
重写onSaveInstanceState方法,并添加以下代码:outState.putParcelable("android:fragments", null);
相当于bundle 的FRAGMENTS_TAG 值为空,在step8 restoreAllState方法中开头直接就return了,就没有接下来一系列的取出、实例化、创建等操作了,应用重新创建后,只有一套fragment,自然不会出现重叠现象。
看的累了吧,快缓缓(文中描述的也不一定100%正确,但大致流程应该是没问题的)
总结:
正常back键退出应用时(主动销毁),Activity及Fragment对象都会被销毁,因此再次进入时会创建新的Fragment对象。但是当非主动销毁(退到后台被回收等),Activity虽然被回收,但Fragment对象仍然保持,再次进入应用时,系统会恢复之前保存的Fragment,加上原有的fragment,就造成了重叠现象。
当然,使用一些其它的方法也是可以解决重叠问题的,比如判断新的fragment和旧的fragment是否是同一个,如果不是那么就将旧的赋值给新的fragment。这里还要看实际的业务,,原理清楚了,解决办法就多了。