Fragment的详解

一、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同上,不再赘述。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354