美文网首页ui
还在为Fragment重叠问题头疼?一行代码即可解决,妈妈再也不

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

作者: 杰洛特world | 来源:发表于2020-09-17 16:23 被阅读0次

    相信小伙伴们会遇到过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<Fragment> 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<Fragment> 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<Fragment> 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重叠问题头疼?一行代码即可解决,妈妈再也不

        本文链接:https://www.haomeiwen.com/subject/fbphyktx.html