美文网首页
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同上,不再赘述。

相关文章

  • Android 你需要掌握的知识(三)

    目录 一.Fragment详解 一.Fragment基础 1.Fragment Fragment为什么会出现,它其...

  • 碎片之Fragment详解

    一、Fragment详解 onAttach:Fragment和Activity建立关联时调用 onCreate:F...

  • Fragment使用三部曲

    1.Fragment进阶 - 基本用法2.Fragment进阶-FragmentTransaction详解3.Fr...

  • fragment

    fragment详解一 1.Fragment有自己的生命周期 2.Fragment依赖于Activity 3.Fr...

  • Fragment-基础梳理

    网上很多关于Fragment 的基础详解,Fragment 对于我们也不陌生了,今天来整理一下Fragment的用...

  • 2.2基础知识-Fragment

    Fragment详解 一.Fragment为什么被称为第五大组件?(Activity、Service、Conten...

  • 「转载」Tablayout+ ViewPage + Fragme

    Tablayout + ViewPage + Fragment详解 昨天用到tablayou+viewpage嵌套...

  • Android Fragment详解

    参考网址:Android Fragment详解[https://blog.csdn.net/qq_37982823...

  • Fragment的详解

    一、Fragment定义? Fragment即片段,它必须始终嵌入Activity中,作为Activity中模块化...

  • Fragment详解

    一、动态添加碎片的步骤1.创建待添加的碎片实例。2.获取FragmentManager,在活动中可以直接通过调用g...

网友评论

      本文标题:Fragment的详解

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