Fragment生命周期

栏目: 后端 · 发布时间: 5年前

内容简介:大家都知道中只定义了6种状态

大家都知道 Fragment 的生命周期,以及其对应的一些生命周期函数:

Fragment生命周期
Fragment 的生命周期函数很多,但其实 Fragment

中只定义了6种状态

static final int INITIALIZING = 0;     // Not yet created.
static final int CREATED = 1;          // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
static final int STOPPED = 3;          // Fully created, not started.
static final int STARTED = 4;          // Created and started, not resumed.
static final int RESUMED = 5;          // Created started and resumed.
复制代码

Fragment 的整个生命周期一直在这6个状态中流转,调用对应的生命周期方法然后进入下一个状态,如下图

Fragment生命周期

1.1 Fragment与Activity

Fragment 的生命周期与 Activity 的生命周期密切相关 Activity 管理 Fragment 生命周期的方式是在 Activity 的生命周期方法中调用 FragmentManager 的对应方法,通过 FragmentManager 将现有的 Fragment 迁移至下一个状态,同时触发相应的生命周期函数

Activity生命周期函数 FragmentManager触发的函数 Fragment状态迁移 Fragment生命周期回调
onCreate dispatchCreate INITIALIZING->
CREATED
onAttach、onCreate
onStart dispatchStart CREATED->
ACTIVITY_CREATED->
STOPPED->
STARTED
onCreateView、onActivityCreated、onStart
onResume(准确来讲是onPostResume) dispatchResume STARTED->
RESUMED
onResume
onPause dispatchPause RESUMED->
STARTED
onPause
onStop dispatchStop STARTED->
STOPPED
onStop
onDestroy dispatchDestroy STOPPED->
ACTIVITY_CREATED->
CREATED->
INITIALIZING
onDestroyView、onDestroy、onDetach

上个图更加清晰:

Fragment生命周期

1.2 Fragment与FragmentTransaction

我们经常使用 FragmentTransaction 中的 addremovereplaceattachdetachhideshow 等方法对 Fragment 进行操作,这些方法都会使 Fragment 的状态发生变化,触发对应的生命周期函数

(假设此时 Activity 处于 RESUME 状态)

FragmentTransaction中的方法 Fragment触发的生命周期函数
add onAttach->
onCreate->
onCreateView->
onActivityCreated->
onStart->
onResume
remove onPause->
onStop->
onDestoryView->
onDestory->
onDetach
replace replace可拆分为add和remove,
detach (在调用detach之前需要先通过add添加Fragment)
onPause->
onStop->
onDestoryView
attach (调用attach之前需要先调用detach)
onCreateView->
onActivityCreated->
onStarted->
onResumed
hide 不会触发任何生命周期函数
show 不会触发任何生命周期函数

通过对 Fragment 生命周期的变化的观察,我们可以很容易发现, add/remove 操作会引起 FragmentINITIALIZINGRESUMED 这两个状态之间迁移。 而 attach/detach 操作会引起 FragmentCREATEDRESUMED 这两个状态之间迁移。

Fragment生命周期

注:add函数这里有一个需要注意的点,如果当前Activity处于 STARTED 状态,Fragment是无法进入 RESUMED 状态的,只有当Activity进入 RESUME 状态,然后触发 onResume -> FragmentManager.dispatchStateChange(Fragment.RESUMED) ,然后调用 Fragment.onResume 函数之后 Fragment 才会进入 RESUMED 状态。

1.3 Fragment与ViewPager

通过 FragmentPagerAdapter 我们可以将 FragmentViewPager 结合起来使用,那么 ViewPager 中的 Fragment 的生命周期又是怎样的呢?

其实也简单, FragmentPagerAdapter 内部其实就是通过 FragmentTransactionFragment 进行操作的,主要涉及 adddetachattach 这三个方法。

@SuppressWarnings("ReferenceEquality")
@Override
public Object instantiateItem(ViewGroup container, int position) {
    //...
    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        //如果已经存在Fragment实例
        //那么使用attach操作进行添加
        mCurTransaction.attach(fragment);
    } else {
        //Fragment实例还没创建,通过getItem创建一个实例
        //然后通过add操作添加
        fragment = getItem(position);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    }
    //...
    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    //...
    //使用detach销毁Fragment
    mCurTransaction.detach((Fragment)object);
}
复制代码

通过上述源码可知, FragmentPagerAdapter 通过 FragmentTransaction.add 方法添加 Fragment ,后续通过 attachdetach 来操作。这些方法对应的生命周期我们可以参照上面的图即可。 我们举例来模拟一下看看,假设有 ViewPager 有5个页面,以及offscreenPageLimit为1,

  1. 第一次加载时,第一第二页通过 add 函数被加载,处在 RESUMED 状态
  2. 滑动到第二页,第三页被加载,也是通过 add 函数被加载的,处在 RESUMED 状态
  3. 继续滑动到第三页,此时第一页通过 detach 函数被回收,处在 CREATED 状态,同时第四页通过 add 被加载处于 RESUMED 状态
  4. 滑动到第二页,此时第一页通过 attach 被加载,处于 RESUMED 状态,第四页被 detach 处于 CREATED 状态

总结: ViewPager 中当前页与当前页左右两页都处于 RESUMED 状态,其他页面要么未被创建,要么处于 CREATED 状态,滑动过程中 Fragment 的生命周期变化我们可以通过上面这个例子得到。

1.4 Fragment与DialogFragment

在使用 DialogFragment 的时候我们习惯使用它提供的 showhide 方法进行显示或者隐藏。这两方法内部其实使用了 FragmentTransactionaddremove 方法,这些方法对应的生命周期我们已经讲过了就不在赘述了。

public void show(FragmentManager manager, String tag) {
    mDismissed = false;
    mShownByMe = true;
    FragmentTransaction ft = manager.beginTransaction();
    //核心操作
    ft.add(this, tag);
    ft.commit();
}


void dismissInternal(boolean allowStateLoss) {
    //...
    if (mBackStackId >= 0) {
        //...
    } else {
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        //核心操作
        ft.remove(this);
        if (allowStateLoss) {
            ft.commitAllowingStateLoss();
        } else {
            ft.commit();
        }
    }
}

复制代码

DialogFragment 比较特别的是内部还维护了一个 DialogDialogFragment 设计之初就是使用 FragmentManager 来管理 Dialog ,主要使用了 Dialogshowhidedismiss 这三个方法。对应关系如下

Fragment生命周期函数 对应的Dialog的方法
onStart show
onStop hide
onDestoryView dismiss

2 不同的添加方式对Fragment的生命周期有什么影响

Fragment 的添加方式有两种:

FragmentTransaction

这里我们就来聊聊,这两种不同的添加方式对于 Fragment 的生命周期回调会产生什么样的影响。

2.1 使用fragment标签添加

xml中的Fragment的实例创建最终会交由FragmentManager负责,方法为 onCreateView

//FragmentManager.java
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    //判断是否是Fragment标签
    if (!"fragment".equals(name)) {
        return null;
    }

    //下面这些代码是获取xml中定义的
    //Fragment的一些信息
    //如类名(全路径)、id、tag
    String fname = attrs.getAttributeValue(null, "class");
    TypedArray a =  context.obtainStyledAttributes(attrs, FragmentTag.Fragment);
    if (fname == null) {
        fname = a.getString(FragmentTag.Fragment_name);
    }
    int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID);
    String tag = a.getString(FragmentTag.Fragment_tag);
    a.recycle();

    //检查指定的Fragment类是否派生子Fragment
    if (!Fragment.isSupportFragmentClass(mHost.getContext(), fname)) {
        return null;
    }
    
    //必须满足id不为空或者tag不为空或者包裹Fragment的Container的id不为空
    //否则抛出异常
    int containerId = parent != null ? parent.getId() : 0;
    if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
        throw new IllegalArgumentException(attrs.getPositionDescription()
                + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
    }

    // If we restored from a previous state, we may already have
    // instantiated this fragment from the state and should use
    // that instance instead of making a new one.
    Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null;
    if (fragment == null && tag != null) {
        fragment = findFragmentByTag(tag);
    }
    if (fragment == null && containerId != View.NO_ID) {
        fragment = findFragmentById(containerId);
    }

    //log...
    
    //通过反射创建Fragment实例
    if (fragment == null) {
        fragment = Fragment.instantiate(context, fname);
        //这个字段标志该Fragment实例是来自于xml文件
        fragment.mFromLayout = true;
        fragment.mFragmentId = id != 0 ? id : containerId;
        fragment.mContainerId = containerId;
        fragment.mTag = tag;
        fragment.mInLayout = true;
        fragment.mFragmentManager = this;
        fragment.mHost = mHost;
        fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState);
        //重点方法
        //第二个参数名为moveToStateNow
        //此处为true,因此该Fragment将会立即
        //迁移到当前FragmentManager所记录的状态
        //通常我们在onCreate方法中设置layout
        //因此通常来讲此时FragmentManager
        //处于CREATED状态
        addFragment(fragment, true);

    } else if (fragment.mInLayout) {
        //...
    } else {
        //...
    }

    if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
        //如果当前FragmentManager处于INITIALIZING状态
        //那么强制将该Fragment迁移至CREATED状态
        moveToState(fragment, Fragment.CREATED, 0, 0, false);
    } else {
        //如果此时FragmentManager的状态大于CREATED
        //那么将该Fragment迁移至对应的状态
        moveToState(fragment);
    }

    //...
    return fragment.mView;
}
复制代码

onCreateView 的工作基本上就是创建 Fragment 实例并将其迁移至指定状态了,我们以一个 Activity 正常启动的流程作为分析的场景,那么此时 Fragment 将最终进入 CREATED 状态。

在前面学习 Fragment 生命周期的时候,我们有提到过 Activity 进入 onCreate 之后会触发 FragmentonAttachonCreate 的生命周期回调。但在当前这种场景下, Fragment 会提前触发 onCreateView 来创建视图,这一点可以在 moveToState 的源码中得到印证:

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {
            
    //...
     switch (f.mState) {
        case Fragment.INITIALIZING:
            //...
        case Fragment.CREATED:
            //...
            //下面这个if语句来自于ensureInflatedFragmentView方法
            //为了方便,这里直接贴上了该方法的代码
            //如果该Fragment来自于布局文件
            //那么触发onCreateView创建试图实例
             if (f.mFromLayout && !f.mPerformedCreateView) {
                f.mView = f.performCreateView(f.performGetLayoutInflater(
                        f.mSavedFragmentState), null, f.mSavedFragmentState);
                if (f.mView != null) {
                    f.mInnerView = f.mView;
                    f.mView.setSaveFromParentEnabled(false);
                    if (f.mHidden) f.mView.setVisibility(View.GONE);
                    f.onViewCreated(f.mView, f.mSavedFragmentState);
                    dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState, false);
                } else {
                    f.mInnerView = null;
                }
            }
            if (newState > Fragment.CREATED) {
                //...
            }
        //...
     }
    //...

}
复制代码

2.2 在代码中使用FragmentTransaction添加

此处我们以在 Activity.onCreate 方法中add一个Fragment作为分析场景

public class DemoActivity extends FragmentActivity{
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.demo);
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.add(R.id.container, new DemoFragment());
        ft.commit();
    }
}
复制代码

先不管 add 里面进行了什么操作,我们知道如果不调用commit方法,那么add操作是不会起效的的。 commit 方法会经历以下调用链 commit -> commitInternal -> FragmentManager.enqueueAction

//FragmentTransaction的实现类为BackStackRecord
//action的实际类型是BackStackRecord
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            checkStateLoss();
        }
        synchronized (this) {
            //...
            mPendingActions.add(action);
            synchronized (this) {
                boolean postponeReady =
                        mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
                boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
                if (postponeReady || pendingReady) {
                    //重点
                    //getHandler拿到的是一个主线程的Handler
                    //这里没有直接调用moveToState,而是抛了一个
                    //消息至消息队列,这将导致Fragment的状态迁移被延后
                    mHost.getHandler().removeCallbacks(mExecCommit);
                    mHost.getHandler().post(mExecCommit);
                }
            }
        }
    }
复制代码

mExecCommit 被触发就会经历下面的调用链 FragmentManager.execPendingActions -> BackStackRecord.generateOps -> ...-> BackStackRecord.executeOps -> FragmentManager.xxxFragment -> FragmentManager.moveToState 最终发生了 Fragment 的状态迁移

那么 mExecCommit 是否真的就老老实实待在消息队列中等待被执行呢?答案是否定的。 我们来看看 FragmentActivity.onStart 方法

protected void onStart() {
    super.onStart();
    //...
    
    //敲黑板
    mFragments.execPendingActions();

    //...

    mFragments.dispatchStart();
    //...
}
复制代码

可以看到, execPendingActions 被提前触发了,再搭配下面的 dispatchStart ,那么 Fragment 将从 INITIALIZING 一下子迁移至 STARTED ( execPendingActions 方法触发后会将 mExecCommit 从消息队列中移除)。 FragmentActivityonStartonResumeonPostResume 生命周期回调中都会调用 FragmentManager.execPendingActions ,因此当我们在 Activity.onStartActivity.onResume 中通过代码添加 Fragment 时, Fragment 的状态迁移分别会发生在 Activity.onResumeActivity.onPostResume 之后。 那么在 onPostResume 之后再添加Fragment会发生什么呢? 此时由于 onPostResume 方法中的 FragmentManager.execPendingActions 已经在 super 中调用过了,因此 mExecCommit 将会被触发, 这里有一个最大的不同点就是 Fragment 的生命周期变化与 Activity 的生命周期变化不处于同一个消息周期。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员的修炼

程序员的修炼

Jeff Atwood / 陆其明、杨溢 / 人民邮电出版社 / 2014-4 / 45.00元

《程序员的修炼——从优秀到卓越》是《高效能程序员的修炼》的姊妹篇,包含了Coding Horror博客中的精华文章。全书分为8章,涵盖了时间管理、编程方法、Web设计、测试、用户需求、互联网、游戏编程以及技术阅读等方面的话题。作者选取的话题,无一不是程序员职业生涯中的痛点。很多文章在博客和网络上的点击率和回帖率居高不下。 Jeff Atwood于2004年创办Coding Horror博客(......一起来看看 《程序员的修炼》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具