Fragment事務管理源碼分析

一個簡單的FragmentTranscation事務相關的源碼分析和面試題的回答~
FragmentActivity#getSupportFragmentManager


通過FragmentManager獲取FragmentTransaction對象,實際上返回的是BackStackRecord這個類。FragmentTransaction是一個抽象類

FragmentTransaction#add
我們通常使用這樣的形式向指定的ViewGroup中添加Fragment:
包括很多的不同的add方法的重載,這些方法都會最終調用Fragment#doAddOp:
Fragment#doAddOp:
這一個方法主要是給Fragment設置相關的參數,主要做了三件事情:

tag
?containerViewid
fragmentId
最后將Fragment和opCmd(在這里就是OP_ADD)封裝成一個Op對象。

FragmentTransaction#addOp
然后就存放到了一個ArrayList中:
FragmentTransaction#replace remove hide detach attach
這些方法以及它的重載方法都是調用了doAddOp,但是cmdOp會使用不同的枚舉:
具體的看看源代碼就行~
FragmentTransaction#commit
FragmentTransaction 自身并沒有實現commit,真正的實現方法在BackStackRecord中。我們常用的commit和commitAllowingStateLoss都會調用commitInternal方法:
只不過commit中allowingStateLoss=false,commitAllowingStateLoss中為true。
在方法的尾部會調用FragmentManager#enqueueAction。這里傳遞的參數是this,所以BackStackRecord從FragmentTransaction中繼承的Op集合也會被傳過去。
FragmentManager#enqueueAction
Fragment狀態(tài)的保存是在Activity#onSaveInstanceState時進行的,調用FragmentManager#saveAllState保存,并且設置mStateSaved為true。而istStateSaved會檢查mStateSaved || mStopped;是否等于true,如果等于true就回拋出異常。
所以我們應該盡量避免在onStart、onResume中commit,因為這寫方法可能會被反復調用。
這里參考了這篇文章:cnblogs.com/kissazi2/p/4181093.html
所以,如果是commitAllowingStateLoss,FragmentManager在commit的時候就不會去檢查是否會發(fā)生stateLoss,但是這樣會帶來不好的用戶體驗??紤]下面兩種情形:
Activity由于發(fā)生了旋轉,Activity調用onSaveInstanceState恢復之前的狀態(tài),但是此時commit在onSavedInstanceState之后調用,本來想更新新的內容,但是卻恢復成了原來的內容,這個時候由于onSavedState=true,就回拋出異常。
Activity被回收了,mStopped=true,這個時候commit修改Activity的內容也是無效的。
所以我們不應該假設Activity不會發(fā)生stateLoss而調用FragmentTransaction#commitAllowingStateLoss。

回到我們原本的方法,enqueueAction也加了同步鎖機制,然后將當前的Action(BackStackRecord)添加到mPendingActions中,然后調用scheduleCommit:
FragmentManager#scheduleCommit
這個方法中會對加入到pendingActions中的操作進行同步鎖定,然后通過Activity的Handler發(fā)送出去:
在mExecCommit這個Runnable中實際上就回最終調用到我們BackStackManager#executeOps方法,在這個方法中會根據之前每一個Op實例設置的cmd執(zhí)行對應的FragmentManager的操作。所以commit的流程如圖所示:

總結:
1. commit和commitAllowingStateLoss
這兩個方法都會調用commitInternal,前者不允許stateLoss,后者允許。在Activity#onSavedInstanceState中會設置FragmentManager的mSavedState=true,如果我們在這個方法之后commit會拋出異常;同時我們也不應該在Activity Stop之后commit。
2.?commit 是異步的
我們在commit的時候最終會將操作添加到FragmentManager中的隊列中,最后通過Handler發(fā)送給主線程執(zhí)行。所以多一個transaction.commit的時候不能保證這個動作立刻執(zhí)行。而且transaction的動作,比如remove add replace都是在commit執(zhí)行時才會發(fā)生。如果想要立刻執(zhí)行事務
1. 可以使用commitNow()【commitNow不會將事務添加到回退棧中】。而且不可以和回退棧一起使用,以下的代碼會報錯。
因為:
而在commitNow中會調用:
2. 可以使用FragmentManager.executePending
但是Android SDK的注釋中也指出,使用這種方式會導致一些副作用,因為這種操作會執(zhí)行所有待執(zhí)行的任務。
3. 重建Fragment
當Activity意外銷毀時,會保存當前Fragment的狀態(tài)和Arguments。在這篇文章
https://juejin.cn/post/6984605769245130788#heading-24?中指出一點需要注意的問題,FragmentManager#restoreAllState方法會調用反射調用Fragment的無參構造方法恢復Fragment。所以盡量使用默認無參構造方法,而且獲取參數的同時,使用arguments。
不過這點我在androidx的代碼中看起來和文章中并不一樣,可能只是我沒看到.....?
但是還是可以注意一下~
4. 如何獲取FragmentManager?
在Activity中使用getSupportFragmentManager,在Fragment中使用getChildFragmentManager。這個變量在Fragment初始化的同時也會被初始化。
5. 不要使用startActivityForResult,應該使用Activity Results API
https://segmentfault.com/a/1190000037601888
6. Fragment的回退棧
所以實際上并不是Fragment加入了回退棧,而是Fragment的事務進入了回退棧。如果當Fragment進入了回退棧,返回的時候會回滾事務。