Fragment的详解

2020-07-06  本文已影响0人  code希必地

一、Fragment定义?

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

二、Fragment的作用

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

三、生命周期

Fragment的完整生命周期.png

解释如下:

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

onAttach->
onCreate->
onCreateView->
onActivityCreated->
onStart->
onResume
onPause->
onStop->
onDestoryView->
onDestory->
onDetach

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

onPause->
onStop->
onDestoryView
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
onPause->
onStop->
onDestoryView
onCreateView->
onActivityCreated->
onStart->
onResume

3.2、和Activity生命周期的关系

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


2952813-0f4f821975d72317.png

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

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呢?

四、不为人知的细节

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;
}

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重叠

原因:

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()
解决方法:

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

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

AActivity的onActivityResult()->Fragment的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同上,不再赘述。

上一篇 下一篇

猜你喜欢

热点阅读