美文网首页
Fragment的详解

Fragment的详解

作者: code希必地 | 来源:发表于2020-07-06 13:23 被阅读0次

    一、Fragment定义?

    Fragment即片段,它必须始终嵌入Activity中,作为Activity中模块化组成部分,它有自己的生命周期(受Activity生命周期的影响),能接收自己的输入事件,并且可以在
    Activity运行时添加或移除(如果使用fragment标签在xml文件中声明,则不能在运行时添加、移除)。

    二、Fragment的作用

    Fragment的引入主要是为了给大屏幕提供动态和灵活的UI。比如新闻应用,可以在左侧使用一个片段来展示新闻列表,在右侧使用一个片段来展示新闻详情。这两个片段并排展示在同一
    Activity中,它们有各自的生命周期,并能接收各自的输入事件。因此,不需要一个Activity选择新闻,另一个Activity来展示新闻详情。
    Fragment的优点:

    • 模块化:不必把所有的业务逻辑写在Activity中,可以把代码写在各自的Fragment中。
    • 可重用:多个Activity可以重用一个Fragment。
    • 可适配:可以根据屏幕尺寸,能够实现不同的布局。

    三、生命周期

    Fragment的完整生命周期.png

    解释如下:

    • onAttach:Fragment和Activity关联时调用,我们可以在这个方法中获取绑定的Activity和使用getArguments()来获取Activity传递来的数据。
      注意:不建议使用getActivity()来获取Activity,建议在onAttach中将Context强转成Activity来使用。
    • onCreate:Fragment被创建时调用。
    • onCreateView():创建Fragment布局时调用。
      注意在inflate时,attachToRoot设置为false,因为Fragment在内部将View添加到了Container上
    • onActivityCreated:当Activity的onCreate完成时调用。
    • onStart:当Fragment可见不能交互时调用。
    • onResume:当Fragment可见并可交互时调用。
    • onPause:当Fragment可见但不可交互时调用。
    • onStop:当Fragment不可见时调用。
    • onDestoryView:Fragment的布局从Activity的视图结构中移除时调用。
    • onDestory:Fragment实例被销毁时调用。
    • onDetach:Fragment和Activity的关联被移除时调用。

    3.1、和Fragment管理方法间的关系

    • add:
    onAttach->
    onCreate->
    onCreateView->
    onActivityCreated->
    onStart->
    onResume
    
    • remove
      和add是相反的操作,移除一个Fragment的实例,如果在移除时这个fragment没有加入的回退栈中,就会销毁这个fragment实例。
    onPause->
    onStop->
    onDestoryView->
    onDestory->
    onDetach
    

    在使用addToBackStack()加入到回退栈的情况下:

    onPause->
    onStop->
    onDestoryView
    
    • replace
      先移除所有containerViewId中的实例,然后再add一个新的fragment实例。
    newFragment:onAttach->
    newFragment:onCreate->
    oldFragment:onPause->
    oldFragment:onStop->
    oldFragment:onDestoryView->
    oldFragment:onDestory->
    oldFragment:onDetach->
    newFragment:onCreateView->
    newFragment:onActivityCreated->
    newFragment:onStart->
    newFragment:onResume
    

    在使用addToBackStack()加入到回退栈的情况下:

    newFragment:onAttach->
    newFragment:onCreate->
    oldFragment:onPause->
    oldFragment:onStop->
    oldFragment:onDestoryView->
    newFragment:onCreateView->
    newFragment:onActivityCreated->
    newFragment:onStart->
    newFragment:onResume
    
    • detach
      销毁Fragment的视图,但是保留Fragment的实例
    onPause->
    onStop->
    onDestoryView
    
    • attach
      和detach是相对的
    onCreateView->
    onActivityCreated->
    onStart->
    onResume
    
    • hide
      不执行任何生命周期,只是把Fragment的视图设置为隐藏,会执行onHiddenChange()
    • show
      不执行任何生命周期,只是把Fragment的视图设置显示,只会执行onHiddenChanged()
      需要注意的是:如果Fragment的视图销毁了,则界面上保存的数据也会丢失,如果EditText上输入的数据,你执行remove并把事务添加到回退栈中,在按返回键时你会发现EditText上的数据丢失了。

    3.2、和Activity生命周期的关系

    Fragment是依赖于Activity的,其生命周期是由Activity调用的,关系图如下:


    2952813-0f4f821975d72317.png

    我们这里举个例子来理解Fragment生命周期方法。功能如下:共有两个Fragment:F1和F2,F1在初始化时就加入Activity,点击F1中的按钮调用replace替换为F2。

    • 当F1的初始化在Activity的onCreate中时
    MainActivity::onCreate->
    FragmentTest::onAttach->
    FragmentTest::onCreate->
    FragmentTest::onCreateView->
    FragmentTest::onActivityCreated->
    FragmentTest::onStart->
    MainActivity::onStart->
    MainActivity::onResume->
    FragmentTest::onResume->
    FragmentTest::onPause->
    MainActivity::onPause->
    FragmentTest::onSaveInstanceState->
    MainActivity::onSaveInstanceState->
    FragmentTest::onStop->
    MainActivity::onStop
    

    可能有的同学会有疑问,不是说Fragment的生命周期是由Activity调用的嘛,为什么上面的log中为啥Fragment的onStart、onPause、onStop会先于Activity呢?

    • 其实不是这样的,Fragment的onAttach、onCreate、onCreateView、onActivityCreated、onStart均是在Activity的super.onStart()方法中调用的
    • Fragment的onPause、onStop分别是在Activity中的super.onPause()、super.onStop()中调用的

    四、不为人知的细节

    4.1、Activity的重新创建对Fragment的影响

    先看下面的写法

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_life_cycle);
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            Fragment fragment = new FragmentOne();
            transaction.add(R.id.container, fragment,FRAGMENT_ONE_TAG);
            transaction.commit();
        }
    

    如果在Activity重启时会发生什么呢?
    在Activity重新创建时,FragmentManager会根据之前保存的状态重新创建Fragment实例,那么就会在每次重建后多创建一个Fragment。应该添加一个判断:

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_life_cycle);
            fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_ONE_TAG);
            if (fragment == null) {
                FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
                fragment = new FragmentOne();
                transaction.add(R.id.container, fragment,FRAGMENT_ONE_TAG);
                transaction.commit();
            }
        }
    

    FragmentManager中到底保存了Fragment的什么数据呢?这些被保存的信息保存在FragmentState类中。

    final class FragmentState implements Parcelable {
        final String mClassName;
        final int mIndex;
        final boolean mFromLayout;
        final int mFragmentId;
        final int mContainerId;
        final String mTag;
        final boolean mRetainInstance;
        final boolean mDetached;
        final Bundle mArguments;
        final boolean mHidden;
    
        Bundle mSavedFragmentState;
    
        Fragment mInstance;
    }
    
    • mClassName:保存的Fragment的类名
    • mIndex:在Fragment列表中的位置
    • mFragmentId:如果是通过<fragment>标签引入的,则指fragment的id,如果是动态添加了则和containerId相同。
    • mContainerId:Fragment所在container的id
    • mFromLayout:表示是否使用<fragment>标签引入的
    • mTag:标签
    • mRetainInstance:是否在Activity重建时保存Fragment的实例
    • mDetached:表示fragment是否被detach
    • mArguments:setArgument的参数,这也是为啥建议使用setArgument传递参数,而不用构造或者set方式传参
    • mHidden:表示Fragment是否hidden

    4.2、setRetainInstance

    如果Fragment调用了setRetainInstance(true),在Activity重新创建时不会销毁实例,只是销毁视图并detach,其声明周期如下:

    onPause-> onStop ->onDestroyView ->onDetach //没执行onDestory
    onAttach -> onCreateView-> onActivityCreated-> onStart->onResume //没执行onCreate
    

    这个方法主要的使用场景:当Fragment实例中的数据比较复杂,如果使用onSaveInstanceState()进行数据保存比较麻烦,则可以利用Activity重建时Fragment实例没销毁这种特性,创建一个没有界面的Fragment进行数据保存。

    4.3、Fragment重叠

    原因:

    • 24.0.0以下的版本的support库,未对mHidden进行保存,默认为false即show的状态,则在重启恢复时全部以show的状态进行恢复,则导致重叠,在v4-24.0.0+ 开始官方就修复了这个bug。
    • 多次创建了同一个fragment
    • 在切换fragment时未正确的隐藏显示

    4.4、getActivity为空

    getActivity()如果在onAttach之前或者onDetach之后调用的话,getActivity()肯定是为空的,比如在请求接口未完成且关闭页面未取消请求的话,那么在请求成功的回调中使用getActivity()肯定是会报空指针异常的,因为此时Activity已销毁。如果在fragment中定义Activity的成员变量并在onAttach中将context强转给Activity赋值,在请求接口的回调中使用activity来代替getActivity,此时虽然fragment和Activity的关联关系已解除,但是并不会报异常。因为线程并未结束,并且持有外部类的引用(此时已造成内存泄露),所以此时不会报错。

    4.5、异常:Can not perform this action after onSaveInstanceState

    在调用transaction.commit()方法时,内部最终会执行FragmentManagerImpl.enqueueAction()的方法:

    ### FragmentManagerImpl.java
    public void enqueueAction(FragmentManagerImpl.OpGenerator action, boolean allowStateLoss) {
            if (!allowStateLoss) {
                this.checkStateLoss();
            }
    //.......省略其他逻辑
    }
    
    private void checkStateLoss() {
            if (this.isStateSaved()) {
                throw new IllegalStateException("Can not perform this action after onSaveInstanceState");
            } else if (this.mNoTransactionsBecause != null) {
                throw new IllegalStateException("Can not perform this action inside of " + this.mNoTransactionsBecause);
            }
        }
    

    从源码看到在checkStateLoss方法中,如果状态已保存(即在执行了onSaveInstanceState()方法之后),调用commit()就会报错。
    注意:在调用popBakStack()中也会调用checkStateLoss()
    解决方法:

    • 该事务使用commitAllowingStateLoss()方法提交,但是有可能导致该次提交无效!(宿主Activity被强杀时),具体可查看FragmentManagerImpl.enqueueAction().

    4.6、getSupportFragmentManager()、getFragmentManager()、getChildFragmentManager()的区别

    • fragment是3.0之后才出现的,所以在3.0后可以使用getFragmentManager()获取fragment的管理器对象,那么在3.0之前呢?则使用getSupportFragmentManager()。
    • 对于fragment的嵌套来说:getFragmentManager()获取的是所在fragment的父容器的管理器(可能是Activity也可能是fragment)
      getChildFragmentManager()则获取的是所在fragment里容器的管理器对象。

    4.7、对于嵌套Fragment的startActivityForResult ()的使用

    • AActivity嵌套Fragment,在Fragment中调用startActivityForResult()打开一个BActivity,当在关闭页面时onActivityResult()的响应顺序:
    AActivity的onActivityResult()->Fragment的onActivityResult()。
    
    • AActivity中AFragment嵌套BFragment,在BFragment中调用startActivityForResult()打开BActivity,当B页面关闭时onActivityResult()的响应顺序:
     AActivity的onActivityResult()->BFragment的onActivityResult()。
    

    4.8、popBackStack()

    4.8.1、如何加入回退栈

    我们知道Activity有任务栈,用户通过startActivity将Activity加入栈,点击返回按钮将Activity出栈。Fragment也有类似的栈,称为回退栈(Back Stack),回退栈是由FragmentManager管理的。

    Fragment的回退栈.png
    从上图可以看到:回退栈管理的是事务FragmentTransaction。
    如果没有加入回退栈,则用户点击返回按钮会直接将Activity出栈;如果加入了回退栈,则用户点击返回按钮会回滚Fragment事务,实现类似于Activity的效果。
    默认情况下,Fragment事务是不会加入回退栈的,如果想将Fragment加入回退栈并实现事务回滚,首先需要在commit()方法之前调用事务的以下方法将其添加到回退栈中:
    addToBackStack(String tag):标记本次的回滚操作。
    

    4.8.2、如何实现出栈

    这就用到了popBackStack()系列方法,Fragment出栈默认会调用popBackStack(),将最上层的操作弹出回退栈,如果回退栈中有很多层,就需要使用如下方法:

    1、popBackStack(String tag,int flags) //tag 就是addToBackStack(String tag)中指定的tag
    2、popBackStack(int id,int flags) //id 是commit()返回的id
    

    flags的值为0或FragmentManager.POP_BACK_STACK_INCLUSIVE。
    当取值0时,表示除了参数指定这一层之上的所有层都退出栈,指定的这一层为栈顶层;当取值POP_BACK_STACK_INCLUSIVE时,表示连着参数指定的这一层一起退出栈。
    如果想要了解回退栈中Fragment的情况,可以通过以下2个方法来实现:

    getBackStackEntryCount():获取回退栈的个数。
    getBackStackEntryAt(int index):获取回退栈中该索引值下的回退栈实例。
    

    使用popBackStack()来弹出栈内容的话,调用该方法后会将事物操作插入到FragmentManager的操作队列,只有当轮询到该事物时才能执行。如果想立即执行事物的话,可以使用下面这几个方法:

    popBackStackImmediate()
    popBackStackImmediate(String tag)
    popBackStackImmediate(String tag, int flag)
    popBackStackImmediate(int id, int flag)
    

    其中参数和flag同上,不再赘述。

    相关文章

      网友评论

          本文标题:Fragment的详解

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