文中的源代碼基於Support包27.1.1版本java
你們都知道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
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 |
上個圖更加清晰:函數
咱們常用FragmentTransaction
中的add
、remove
、replace
、attach
、detach
、hide
、show
等方法對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
操做會引發Fragment
在INITIALIZING
和RESUMED
這兩個狀態之間遷移。 而attach/detach
操做會引發Fragment
在CREATED
和RESUMED
這兩個狀態之間遷移。學習
注:add函數這裏有一個須要注意的點,若是當前Activity處於
STARTED
狀態,Fragment是沒法進入RESUMED
狀態的,只有當Activity進入RESUME
狀態,而後觸發onResume
->FragmentManager.dispatchStateChange(Fragment.RESUMED)
,而後調用Fragment.onResume
函數以後Fragment
纔會進入RESUMED
狀態。測試
經過FragmentPagerAdapter
咱們能夠將Fragment
與ViewPager
結合起來使用,那麼ViewPager
中的Fragment
的生命週期又是怎樣的呢?this
其實也簡單,FragmentPagerAdapter
內部其實就是經過FragmentTransaction
對Fragment
進行操做的,主要涉及add
、detach
、attach
這三個方法。
@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
,後續經過attach
和detach
來操做。這些方法對應的生命週期咱們能夠參照上面的圖便可。 咱們舉例來模擬一下看看,假設有ViewPager
有5個頁面,以及offscreenPageLimit爲1,
add
函數被加載,處在RESUMED
狀態add
函數被加載的,處在RESUMED
狀態detach
函數被回收,處在CREATED
狀態,同時第四頁經過add
被加載處於RESUMED
狀態attach
被加載,處於RESUMED
狀態,第四頁被detach
處於CREATED
狀態總結:ViewPager
中當前頁與當前頁左右兩頁都處於RESUMED
狀態,其餘頁面要麼未被建立,要麼處於CREATED
狀態,滑動過程當中Fragment
的生命週期變化咱們能夠經過上面這個例子獲得。
在使用DialogFragment
的時候咱們習慣使用它提供的show
、hide
方法進行顯示或者隱藏。這兩方法內部其實使用了FragmentTransaction
的add
、remove
方法,這些方法對應的生命週期咱們已經講過了就不在贅述了。
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
比較特別的是內部還維護了一個Dialog
,DialogFragment
設計之初就是使用FragmentManager
來管理Dialog
,主要使用了Dialog
的show
、hide
、dismiss
這三個方法。對應關係以下
Fragment生命週期函數 | 對應的Dialog的方法 |
---|---|
onStart | show |
onStop | hide |
onDestoryView | dismiss |
Fragment
的添加方式有兩種:
FragmentTransaction
添加這裏咱們就來聊聊,這兩種不一樣的添加方式對於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
以後會觸發Fragment
的onAttach
和onCreate
的生命週期回調。但在當前這種場景下,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) { //... } //... } //... } 複製代碼
此處咱們以在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
從消息隊列中移除)。 FragmentActivity
在onStart
、onResume
和onPostResume
生命週期回調中都會調用FragmentManager.execPendingActions
,所以當咱們在Activity.onStart
、Activity.onResume
中經過代碼添加Fragment
時,Fragment
的狀態遷移分別會發生在Activity.onResume
、Activity.onPostResume
以後。 那麼在onPostResume
以後再添加Fragment會發生什麼呢? 此時因爲onPostResume
方法中的FragmentManager.execPendingActions
已經在super
中調用過了,所以mExecCommit
將會被觸發,這裏有一個最大的不一樣點就是Fragment
的生命週期變化與Activity
的生命週期變化不處於同一個消息週期。
咱們以一張圖對本節內容進行總結:
28.0.0版本的support包中移除了
STOPPED
狀態,可是通過測試,其生命變化與上圖保持一致