close
一道面試題

前段時間面試,面試官先問了一下 fragment的 生命周期,我一看這簡單呀,直接按照下圖回答

面試官點點頭,然後問,如果 Activity 裡面有一個 fragment,那麼啟動他們時,他們的生命周期加載順序是什麼?

所以今天,我們好好了解了解這個用得非常多,但是對底層不是很理解的fragment吧

首先回答面試官的問題,Fragment 的 start 與 activity 的 start 的調用時機

調用順序:D/MainActivity: MainActivity: D/MainActivity: onCreate: startD/MainFragment: onAttach: D/MainFragment: onCreate: D/MainActivity: onCreate: end

D/MainFragment: onCreateView: D/MainFragment: onViewCreated: D/MainFragment: onActivityCreated: D/MainFragment: onViewStateRestored: D/MainFragment: onCreateAnimation: D/MainFragment: onCreateAnimator: D/MainFragment: onStart:

D/MainActivity: onStart: D/MainActivity: onResume: D/MainFragment: onResume:

可以看到 Activity 在 oncreate 開始時,Fragment 緊接着 attach,create,然後activity 執行完畢 onCreate 方法

此後都是 Fragment 在執行,直到 onStart 方法結束

然後輪到 Activity,執行 onStart onResume

也就是,Activity 創建的時候,Fragment 一同創建,同時 Fragment 優先在後台先展示好,最後 Activity 帶着 Fragment一起展示到前台。

是什麼?

Fragment 中文翻譯為」碎片「,在手機中,每一個 Activity 作為一個頁面,有時候太大了,尤其是在平板的橫屏下,我們希望左半邊是一根獨立模塊,右半邊是一個獨立模塊,比如一個新聞app,左邊是標題欄,右邊是顯示內容

此時就非常適合 Fragment

Fragment 是內嵌入Activity中的,可以在 onCreateView 中加載自定義的布局,使用 LayoutInflater,然後 Activity 持有 FragmentManager 對 Fragment 進行控制,下圖是他的代碼框架

fragment框架

我們的Activity一般是用AppCompatActivity,而AppCompatActivity繼承了FragmentActivity

publicclassAppCompatActivityextendsFragmentActivityimplementsAppCompatCallback,TaskStackBuilder.SupportParentable,ActionBarDrawerToggle.DelegateProvider{

也就是說 Activity 之所支持 fragment,是因為有 FragmentActivity,他內部有一個FragmentController,這個 controller 持有一個 FragmentManager,真正做事的就是這個 FragmentManager 的實現類 FragmentManagerImpl

整體架構

回到我們剛才的面試題,關於生命周期絕對是重中之重,但是實際上,生命周期本質只是被其他地方的方法被動調用而已,關鍵是 Fragment 自己的狀態變化了,才會回調生命周期方法,所以我們來看看 fragment 的狀態轉移

staticfinalintINITIALIZING=0;初始狀態,Fragment未創建staticfinalintCREATED=1;已創建狀態,Fragment視圖未創建staticfinalintACTIVITY_CREATED=2;已視圖創建狀態,Fragment不可見staticfinalintSTARTED=3;可見狀態,Fragment不處於前台staticfinalintRESUMED=4;前台狀態,可接受用戶交互

fragment有五個狀態,

調用過程如下

fragment狀態變化

Fragment 的狀態轉移過程主要受到宿主,事務的影響,宿主一般就是 Activity,在我們剛剛的題目中,看到了 Activity 與 Fragment 的生命周期交替執行,本質上就是,Activity 執行完後通知了 Fragment 進行狀態轉移,而 Fragment 執行了狀態轉移後對應的回調了生命周期方法

下圖可以更加清晰

Activity與Fragment生命周期對應關係宿主改變Fragment狀態

那麼我們不禁要問,Activity 如何改變 Fragment 的狀態?

我們知道 Activity 繼承於 FragmentActivity,最終是通過持有的 FragmentManager來控制 Fragment,我們去看看

FragmentActivity@OverrideprotectedvoidonCreate(@NullableBundlesavedInstanceState){mFragments.attachHost(null/*parent*/);super.onCreate(savedInstanceState);...mFragments.dispatchCreate();}

可以看到,onCreate 方法中執行了mFragments.dispatchCreate();,看起來像是通知 Fragment 的 onCreate 執行,這也印證了我們開始時的周期回調順序

D/MainActivity:MainActivity:D/MainActivity:onCreate:start//進入onCreateD/MainFragment:onAttach://執行mFragments.dispatchCreate();D/MainFragment:onCreate:D/MainActivity:onCreate:end//退出onCreate

類似的 FragmentActivity 在每一個生命周期方法中都做了相同的事情

@OverrideprotectedvoidonDestroy(){super.onDestroy();if(mViewModelStore!=null&&!isChangingConfigurations()){mViewModelStore.clear();}mFragments.dispatchDestroy();}

我們進入 dispatchCreate 看看,

RunnablemExecCommit=newRunnable(){@Overridepublicvoidrun(){execPendingActions();}//內部修改了兩個狀態publicvoiddispatchCreate(){mStateSaved=false;mStopped=false;dispatchStateChange(Fragment.CREATED);privatevoiddispatchStateChange(intnextState){try{mExecutingActions=true;moveToState(nextState,false);//轉移到nextState}finally{mExecutingActions=false;}execPendingActions();}//一路下來會執行到voidmoveToState(Fragmentf,intnewState,inttransit,inttransitionStyle,booleankeepActive){//FragmentsthatarenotcurrentlyaddedwillsitintheonCreate()state.if((!f.mAdded||f.mDetached)&&newState>Fragment.CREATED){newState=Fragment.CREATED;}if(f.mRemoving&&newState>f.mState){if(f.mState==Fragment.INITIALIZING&&f.isInBackStack()){//Allowthefragmenttobecreatedsothatitcanbesavedlater.newState=Fragment.CREATED;}else{//Whileremovingafragment,wecan'tchangeittoahigherstate.newState=f.mState;}}...}

可以看到上面的代碼,最終執行到 moveToState,通過判斷 Fragment 當前的狀態,同時 newState > f.mState,避免狀態回退,然後進行狀態轉移

狀態轉移完成後就會觸發對應的生命周期回調方法

事務管理

如果 Fragment 只能隨着 Activity 的生命周期變化而變化,那就太不靈活了,所以Android給我們提供了一個獨立的操作方案,事務

同樣由 FragManager 管理,具體由 FragmentTransaction 執行,主要是添加刪除替換 Fragment 等,執行操作後,需要提交來保證生效

FragmentManagerfragmentManager=...FragmentTransactiontransaction=fragmentManager.beginTransaction();transaction.setReorderingAllowed(true);transaction.replace(R.id.fragment_container,ExampleFragment.class,null);//替換Fragmenttransaction.commit();//這裡的commit是提交的一種方法

Android給我們的幾種提交方式

FragmentTransaction是個掛名抽象類,真正的實現在BackStackState回退棧中,我們看下commit

@Overridepublicintcommit(){returncommitInternal(false);}intcommitInternal(booleanallowStateLoss){...mCommitted=true;if(mAddToBackStack){mIndex=mManager.allocBackStackIndex(this);//1}else{mIndex=-1;}//入隊操作mManager.enqueueAction(this,allowStateLoss);//2returnmIndex;}

可以看到,commit的本質就是將事務提交到隊列中,這裡出現了兩個數組,注釋1處

ArrayList<BackStackRecord>mBackStackIndices;ArrayList<Integer>mAvailBackStackIndices;publicintallocBackStackIndex(BackStackRecordbse){synchronized(this){if(mAvailBackStackIndices==null||mAvailBackStackIndices.size()<=0){if(mBackStackIndices==null){mBackStackIndices=newArrayList<BackStackRecord>();}intindex=mBackStackIndices.size();mBackStackIndices.add(bse);returnind}else{ intindex=mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1);mBackStackIndices.set(index,bse);returnindex;}}}

mBackStackIndices 數組,每個元素是一個回退棧,用來記錄索引。比如說,當有五個 BackStackState 時,移除掉1,3兩個,就是在 mBackStackIndices 將對應元素置為null,然後 mAvailBackStackIndices 會添加這兩個回退棧,記錄被移除的回退棧

當下次commit時,就判定mAvailBackStackIndices中的索引,對應的BackStackState 一定是 null,直接寫到這個索引即可

而一組操作都 commit 到同一個隊列裡面,所以要麼全部完成,要麼全部不做,可以保證原子性

注釋二處是一個入隊操作

publicvoidenqueueAction(OpGeneratoraction,booleanallowStateLosssynchronized(this){...mPendingActions.add(action);scheduleCommit();//真正的提交}}publicvoidscheduleCommit(){synchronized(this){booleanpostponeReady=mPostponedTransactions!=null&&!mPostponedTransactions.isEmpty();booleanpendingReady=mPendingActions!=null&&mPendingActions.size()==1;if(postponeReady||pendingReady){mHost.getHandler().removeCallbacks(mExecCommit);mHost.getHandler().post(mExecCommit);//發送請求}}

這裡最後 mHost.getHandler() 是拿到了宿主 Activity的handler,使得可以在主線程執行,mExecCommit 本身是一個線程

我們繼續看下這個 mExecCommit

RunnablemExecCommit=newRunnable(){@Overridepublicvoidrun(){execPendingActions();}};publicbooleanexecPendingActions(){ensureExecReady(true);...doPendingDeferredStart();burpActive();returndidSomething;}voiddoPendingDeferredStart(){if(mHavePendingDeferredStart){mHavePendingDeferredStart=false;startPendingDeferredFragments();}}voidstartPendingDeferredFragments(){if(mActive==null)return;for(inti=0;i<mActive.size();i++){Fragmentf=mActive.valueAt(i);if(f!=null){performPendingDeferredStart(f);}}}publicvoidperformPendingDeferredStart(Fragmentf){if(f.mDeferStart){f.mDeferStart=false;// 最終到moveTostatemoveToState(f,mCurState,0,0,false);}}

還記得我們在宿主改變 Fragment 狀態,裡面的最終路徑嗎?是的,就是這個 moveToState,無論是宿主改變 Fragment 狀態,還是事務來改變,最終都會執行到 moveToState,然後call對應的生命周期方法來執行,這也是為什麼我們要將狀態轉移作為學習主線,而不是生命周期。

除了 commit,可以看到 FragmentTransaction 有眾多對 Fragment 進行增刪改查的方法

都是由 BackStackState 來執行,最後都會執行到 moveToState 中

具體是如何改變的,有很多細節,這裡不再贅述。

小結

本節我們講了 Fragment 在 android 系統中的狀態,那就是通過自身狀態轉移來回調對應生命周期方法,這塊是自動實現的,我們開發時不太需要關注狀態轉移,只要知道什麼時候執行某個生命周期方法,然後再在對應方法中寫業務邏輯即可

有兩個方法可以讓 Fragment 狀態轉移,

宿主 Activity 生命周期內自動修改 Fragment 狀態,回調 Fragment 的生命周期方法

通過手動提交事務,修改 Fragment 狀態,回調 Fragment 的生命周期方法

參考資料
https://juejin.cn/post/6844904001935261710#heading-8
https://juejin.cn/post/6984605769245130788#heading-15

END



推薦閱讀

Compose Multiplatform 實戰聯機小遊戲

最新出爐|2021 Android 面試被錘之旅

關於Handler的這20個問題,你都清楚嗎?

加好友進交流群,技術乾貨聊不停

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()