还在为Fragment重叠问题头疼?一行代码即可解决,妈妈再也不用担心了

相信小伙伴们会遇到过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)方法

Activity 的 onSaveInstanceState

看方法里的实现,可以看到红框处,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()

FragmentcController的saveAllState()

这里可以看到是调用FragmentManager的saveAllState方法,跳转到FragmentManager来查看。

Step3.FragmentManager的saveAllState()方法

FragmentManager的saveAllState(1)  

中间部分省略。。。

FragmentManager的saveAllState(2) 

1:新建一个FragmentState类型的数组active

2:从成员变量mActive里取出Fragment, mActive的声明为:ArrayList mActive;

里面存放的是当前活动的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

FragmentManagerState 里很简单,主要是几个数组型的成员变量,这里我们主要来看FragmentState类型的数组mActive, 进入到FragmentState里。

Step5.Fragment的内部类FragmentState

FragmentState是Fragment的内部类,声明了如下的属性

FragmentState

可以看到这里记录了fragment的一些信息,并且还持有fragment的引用。在其构造方法中,可以看到把fragment同名属性的值赋值了过来,也就是说我们在Fragment类里面也能找到一一对应的属性,并且都有相关的注释说明:

具体的含义大家可以自行翻译,我就不做过多的介绍了。总之在step1中保存的p底层就是这些信息。

到这里,activity的onSaveInstanceState方法我们就大概清楚了,功能之一就是把fragment的一些状态进行保存。

接着,就是第二部分了,我们再看看activity创建的过程

Step6.Activity的onCreate(Bundle saveInstanceState)方法

直接进入到activity的onCreate方法

Activity的onCreate()

1:从bundle中取出名为FRAGMENT_TAG的p对象,没错就是在step1中的存入的那个key

2:从restoreAllState方法名就可以看出,恢复fragment所有状态

3:进入创建fragment流程

来看restoreAllState方法,mFragments是FragmentController类型的,进入。

Step7.FragmentController的restoreAllState(Parcelable state, List nonConfigList)方法

FragmentController的restoreAllState()  

这里又调用了FragmentManager的restoreAllState的方法。

Step8.FragmentManager的restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig)

.FragmentManager的restoreAllState

红框处拿到了之前保存的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 mActive中

所以,在restoreAllState方法中,主要是把保存的fragment实例化。

接着,我们看Step6的第3步,mFragments.dispatchCreate()方法,这里最终是调用FragmentManager的dispatchCreate方法

FragmentManager的dispatchCreate()

注意看,第一个参数传入了Fragment.CREATED常量(Fragment一共定义了5个,这里的常量int值在后面会用来做各种状态的大小判断)

很明显这里代表创建一个新的fragment。第二个参数为false。

再进入moveToState方法

Step9.FragmentManager.moveToState(...)

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。这里还要看实际的业务,,原理清楚了,解决办法就多了。

你可能感兴趣的:(还在为Fragment重叠问题头疼?一行代码即可解决,妈妈再也不用担心了)