Fragment生命週期

文中的源代碼基於Support包27.1.1版本java

1 Fragment生命週期

你們都知道Fragment的生命週期,以及其對應的一些生命週期函數:android

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個狀態中流轉,調用對應的生命週期方法而後進入下一個狀態,以下圖 markdown

1.1 Fragment與Activity

Fragment的生命週期與Activity的生命週期密切相關 Activity管理Fragment生命週期的方式是在Activity的生命週期方法中調用FragmentManager的對應方法,經過FragmentManager將現有的Fragment遷移至下一個狀態,同時觸發相應的生命週期函數ide

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

上個圖更加清晰:函數

1.2 Fragment與FragmentTransaction

咱們常用FragmentTransaction中的addremovereplaceattachdetachhideshow等方法對Fragment進行操做,這些方法都會使Fragment的狀態發生變化,觸發對應的生命週期函數佈局

(假設此時Activity處於RESUME狀態)post

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這兩個狀態之間遷移。學習

注:add函數這裏有一個須要注意的點,若是當前Activity處於STARTED狀態,Fragment是沒法進入RESUMED狀態的,只有當Activity進入RESUME狀態,而後觸發onResume->FragmentManager.dispatchStateChange(Fragment.RESUMED),而後調用Fragment.onResume函數以後Fragment纔會進入RESUMED狀態。測試

1.3 Fragment與ViewPager

經過FragmentPagerAdapter咱們能夠將FragmentViewPager結合起來使用,那麼ViewPager中的Fragment的生命週期又是怎樣的呢?this

其實也簡單,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的添加方式有兩種:

  1. 經過在xml文件中使用fragment標籤添加
  2. 在代碼中使用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的生命週期變化不處於同一個消息週期。

2.3 總結

咱們以一張圖對本節內容進行總結:

28.0.0版本的support包中移除了STOPPED狀態,可是通過測試,其生命變化與上圖保持一致

相關文章
相關標籤/搜索