一、概述
官方是從3.0
開始引入Fragment
的,在文件中有對於其使用的詳細介紹,可以看出來,在它剛出現時大家對於它是十分推崇的。然而隨著使用Fragment
開發的專案越來越多,在一些複雜場景下逐漸暴露出了它的一些問題,對於是否繼續使用Fragment
大家也有不同的看法。目前在專案中也有大量之前留下來的使用 Fragment
的程式碼, monkey
有時會跑出一些莫名奇妙的問題,在這裡我們對於使用Fragment
的好壞先不做評價,而是看看它內部整個的實現過程,學習它的思想,在以後出現問題的時候也方便排查,同時大家可以根據自己的需求來決定是否使用Fragment
。
二、Fragment
事務的執行過程
在操作Fragment
時,第一件是就是通過Activity
的getFragmentManger()
方法得到一個FragmentManager
物件:
Fragment manager = getFragmentManager();
複製程式碼
我們看一下Activity.java
中的這個方法:
final FragmentController mFragments = FragmentController.createController(new HostCallbacks())
public FragmentManager getFragmentManager() {
return mFragments.getFragmentManager();
}
class HostCallbacks extends FragmentHostCallback<Activity> {
public HostCallbacks() {
super(Activity.this);
}
}
複製程式碼
可以看到getFragmentManager()
呼叫的是FragmentController
的介面,那麼我們再看看這個類對應的方法:
private final FragmentHostCallback<?> mHost;
public static final FragmentController createController(FragmentHostCallback<?> callbacks) {
return new FragmentController(callbacks)
}
private FragmentController(FragmentHostCallback<?> callbacks) {
mHost = callbacks;
}
public FragmentManager getFragmentManager() {
return mHost.getFragmentManagerImpl();
}
複製程式碼
原來FragmentController
是把一個callback
給包裝了起來,真正完成任務的是FragmentHostCallback
:
final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
FragmentManagerImpl getFragmentManagerImpl() {
return mFragmentManagerImpl;
}
複製程式碼
那麼結論就是我們實際得到的是Activity#mFragments#mHost#mFragmentManagerImpl
這個變數,得到這個例項之後,我們通過它的beginTransition
方法得到一個事務:
FragmentTransation transation = manager.beginTransation();
複製程式碼
我們看一下這個FragmentTransation
究竟是個什麼東西,FragmentManagerImpl
是FragmentManager
的一個內部類,它實現了該介面:
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
public FragmentTransaction beginTransation() {
return new BackStackRecord(this);
}
}
複製程式碼
原來這個FragmentTransation
也是一個介面,它的實現是BackStackRecord
,並且在每次 beginTransaction
時都是返回一個新的事務物件,包括之後進行的後退操作都是通過這個事務物件來管理的,這個物件中儲存了建立它的FragmentManagerImpl
例項。
BackStackRecord
其實是BackStackState
的一個內部類,我們平時就是通過它來進行add、remove、replace、attach、detach、hide、show
等操作的,進入原始碼看一下這些操作背後都幹了些什麼:
final class BackStackRecord extends FragmentTransation implements FragmentManager.BackStackEntry, Runnable {
public FragmentTransation add(Fragment fragment, String tag) {
doAddOp(0, fragment, tag, OP_ADD);
return this;
}
public FragmentTransation add(int containerViewId, Fragment fragment) {
doAddOp(containerViewId, fragment, null, OP_ADD);
return this;
}
public FragmentTransation add(int containerViewId, Fragment fragment, String tag) {
doAddOp(containerViewId, fragment, tag, OP_ADD);
}
public FragmentTransation replace(int containerViewId, Fragment fragment) {
return replace(containerViewId, fragment, null);
}
public FragmentTransation replace(int containerViewId, Fragment fragment, String tag) {
if (containerViewId == 0) {
//replace操作必須要指定containerViewId.
}
doAddOp(containerViewId, fragment, tag, OP_REPLACE);
}
public FragmentTransaction remove(Fragment fragment) {
Op op = new Op();
op.cmd = OP_REMOVE;
op.fragment = fragment;
addOp(op);
}
public FragmentTransaction hide(Fragment fragment) {
Op op = new Op();
op.cmd = OP_HIDE;
op.fragment = fragment;
addOp(op);
}
public FragmentTransaction show(Fragment fragment) {
Op op = new Op();
op.cmd = OP_SHOW;
op.fragment = fragment;
addOp(op);
}
public FragmentTransaction attach(Fragment fragment) {
Op op = new Op();
op.cmd = OP_ATTACH;
op.fragment = fragment;
addOp(op);
}
public FragmentTransaction detach(Fragment fragment) {
Op op = new Op();
op.cmd = OP_DETACH;
op.fragment = fragment;
addOp(op);
}
//新建一個操作,並給操作賦值。
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
fragment.mFragmentManager = mManager;
if (tag != null) {
if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
//如果這個 fragment 之前已經有了tag,那麼是不允許改變它的。
}
fragment.mTag = tag;
}
if (containerViewId != 0) {
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
//如果這個fragment已經有了mFragmentId,那麼不允許改變它。
}
fragment.mContainerId = fragment.mFragmentId = containerViewId;
}
Op op = new Op();
op.cmd = opcmd;
op.fragment = fragment;
addOp(op);
}
//把操作新增到連結串列之中。
void addOp() {
if (mHead == null) {
mHead = mTail = op;
} else {
op.prev = mTail;
mTail.next = op;
mTail = op;
}
mNumOp++;
}
}
複製程式碼
看完上面這段程式碼,我們可以得到下面這些資訊:
- 我們呼叫這些操作之後僅僅是把這個操作新增到了
BackStackRecord
當中的一個連結串列。 - 在進行
attach、detach、hide、show、remove
這五個操作時是不需要傳入containerViewId
的,因為在執行這些操作之前這個Fragment
必然已經經過了add
操作,它的containerId
是確定的。 - 在執行
add
操作時,需要保證Fragment
的mTag
和mContainerViewId
在不為空時(也就是這個Fragment
例項之前已經執行過add
操作),它們和新傳入的tag
和containerViewId
必須是相同的,這是因為在FragmentManager
的mActive
列表中儲存了所有被新增進去的Fragment
,而其提供的findFragmentById/Tag
正是通過這兩個欄位作為判斷的標準,因此不允許同一個例項在列表當中重複出現兩次。 replace
操作和add
類似,它增加了一個額外條件,就是containerViewId
不為空,因為replace
需要知道它是對哪個container
進行操作,後面我們會看到replace
其實是一個先remove
再add
的過程,因此`add 的那些判斷條件同樣適用。
那麼這個連結串列中的操作什麼時候被執行呢,看一下commit()
操作:
public int commit() {
return commitInternal(false);
}
int commitInternal(boolean allowStateLoss) {
if (mCommitted) {
//已經有事務處理,丟擲異常。
}
mCommited = true;
if (mAddToBackState) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
}
複製程式碼
它最後呼叫了FragmentManager
的enqueueAction
方法,我們進去看一下里面做了什麼:
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mHost == null) {
//如果Activity已經被銷燬,那麼丟擲異常。
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<Runnable>();
}
//因為BackStackRecord實現了Runnable介面,把加入到其中。
mPendingActions.add(action);
if (mPendingActions.size() == 1) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
}
}
Runnable mExecCommit = new Runnable {
public void run() {
execPendingActions();
}
}
public boolean execPendingActions() {
if (mExecutingActions) {
//已經有操作在執行,丟擲異常
}
if (Looper.myLooper() != mHost.getHandler().getLooper()) {
//不在主執行緒中執行,丟擲異常
}
boolean didSomething = false;
while (true) {
int numActions;
sychronized(this) {
if (mPendingActions == null || mPendingActions.size == 0) {
break;
}
//期望需要執行的事務個數
numActions = mPendingActions.size();
if (mTmpActions == null || mTmpActions.length < numActions) {
mTmpActions = new Runnable[numActions];
}
mPendingActions.toArray(mTmpActions);
mPendingActions.clear();
mHost.getHandler().removeCallbacks(mExecCommit);
}
mExecutingActions = true;
for (int i = 0; i < numActions; i++) {
//好吧,這裡就又回撥了BackStackRecord的run()方法
mTmpActions[i].run();
mTmpActions[i] = null;
}
mExecutingActions = false;
didSomething = true;
}
}
複製程式碼
- 在呼叫
commit
之後,把BackStackRecord
加入到FragmentManagerImpl
的mPendingActions
中,而且通過檢視FragmentManager
的原始碼也可以發現,所有對mPendingActions
的新增操作只有這一個地方呼叫, - 當其大小為1時,會通過主執行緒的
Handler post
一個Runnable mExecCommit
出去,當這個Runnable
執行時呼叫execPendingActions()
方法。 execPendingActions
它會拿出這個mPendingActions
當中的所有Runnable
執行(如果它是BackStackRecord
呼叫過來的,那麼就是呼叫BackStackRecord
的run
方法),並把這個列表清空。在每個BackStackRecord
的run
方法執行時,它是通過遍歷BackStackRecord
連結串列當中每個節點的cmd
來判斷我們之前通過FragmentTransation
加入期望執行的那些操作的。- 可以看出
execPendingActions
這個方法很關鍵,因為它決定了我們新增的操作什麼時候會被執行,我們看下還有那些地方呼叫到了它,為什麼要分析這個呢,因為我們在專案當中發現很多來自於Fragment
的異常都是由我們後面談論的moveToState
方法丟擲的,而moveToState
執行的原因是execPendingActions
被呼叫了,因此瞭解它被呼叫的時機是我們追蹤問題的關鍵,關於呼叫的時機,我們都總結在下面的註釋當中了:
<!-- Activity.java -->
final void performStart() {
mFragments.execPendingActions(); //有可能在 Activity 的 onStart 方法執行前
mInstrumentation.callActivityOnStart(this);
}
final void performResume() {
performRestart();
mFragments.execPendingActions(); //如果是從 Stopped 過來的,那麼有可能在 onStart 到 onResume 之間。
....
mInstrumentation.callActivityOnResume(this);
....
mFragments.dispatchResume();
mFragments.execPendingActions(); //有可能在 onResume 到 onPause 之間。
}
<!-- FragmentManager.java -->
public void dispatchDestroy() { //這個呼叫在 onDestroy 之前。
execPendingActions();
}
public boolean popBackStackImmediate() {
executePendingTransactions();
}
public boolean popBackStackImmediate(String name, int flags) {
executePendingTransactions();
}
複製程式碼
關於FragmentManager
的討論我們先暫時放一放,看一下BackStackRecord
是怎麼執行連結串列內部的操作的:
public void run() {
Op op = mHead;
while (op != null) {
switch(op.cmd) {
case OP_ADD:
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.addFragment(f, false);
break;
case OP_REPLACE: {
Fragment f = op.fragment;
int containerId = f.mContainerId;
if (mManager.mAdded != null) {
//遍歷 mAdded列表,找到 containerId 相同的 old Fragment.
if (old == f) {
op.fragment = f = null;
} else {
if (op.removed == null) {
op.removed = new ArrayList<Fragment>();
}
op.removed.add(old); //這裡要把replace之前的記下來是為了後退棧準備的。
if (mAddToBackStack) {
old.mBackStackNesting += 1;
}
mManager.removeFragment(old, transition, transitionStyle);
}
}
if (f != null) {
f.addFragment(f, false);
}
break;
//後面的remove,hide,show,attach,detach就是呼叫了FragmentManager中相應的方法,沒什麼特別的,就不貼出來了
case xxx:
}
}
op = op.next;
}
//mCurState此時為FragmentManager當前的狀態,其餘的引數不用管。
mManager.moveToState(mManager.mCurState, mTransition, mTransitionStyle, true);
if (mAddToBackStack) {
mManager.addBackStackState(this);
}
}
複製程式碼
我們來看一下FragmentManagerImpl
對應的addFragment
等操作:
public void addFragment(Fragment fragment, boolean moveToStateNow) {
makeActive(fragment); //加入到mActive列表中。
if (!fragment.mDetached) {
if (mAdd.contains(fragment)) {
//已經在mAdded列表,丟擲異常。
}
mAdded.add(fragment);
fragment.mAdded = true;
fragment.mRemoving = false;
if (moveToStateNow) {
moveToState(fragment);
}
}
}
public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
final boolean inactive = !fragment.isInBackStack();
if (!fragment.mDetach || inactive) {
if (mAdded != null) {
mAdded.remove(fragment); //從mAdded列表中移除。
}
fragment.mAdded = false;
fragment.mRemoving = true;
//這裡會根據是否加入後退棧來判斷新的狀態,最後會影響到Fragment生命週期的呼叫,如果是沒有加入後退棧的,那麼會多呼叫onDestroy、onDetach方法。
moveToState(fragment, inactive ? Fragment.INITIALZING : Fragment.CREATED, transition, transitionStyle, false);
}
}
public void hideFragment(Fragment fragment, int transition, int transitionStyle) {
if (!fragment.mHidden) {
fragment.mHidden = true;
if (fragment.mView != null) {
fragment.mView.setVisibility(View.GONE);
}
}
fragment.onHiddenChanged(true);
}
public void showFragment(Fragment fragment, int transition, int transitionStyle) {
if (fragment.mHidden) {
fragment.mHidden = false;
if (fragment.mView != null) {
fragment.mView.setVisibility(View.VISIBLE);
}
fragment.onHiddenChanged(false);
}
}
public void detachFragment(Fragment fragment, int transition, int transitionStyle) {
if (!fragment.mDetached) {
fragment.mDetached = true;
if (fragment.mAdded) {
if (mAdded != null) {
mAdded.remove(fragment);
}
fragment.mAdded = false;
moveToState(fragment, Fragment.CREATED, transition, transitionStyle, false);
}
}
}
public void attachFragment(Fragment fragment, int transition, int transitionStyle) {
if (fragment.mDetached) {
if (!fragment.mAdded) {
if (mAdded.contains(fragment)) {
//mAdded列表中已經有,丟擲異常,
}
mAdded.add(fragment);
fragment.mAdded = true;
moveToState(fragment, mCurState, transition, transitionStyle, false);
}
}
}
複製程式碼
這裡的操作很多,我們需要明白以下幾點:
mActive
和mAdded
的區別:mActive
表示執行過add
操作,並且其沒有被移除(移除表示的是在不加入後退棧的情況下被removeFragment
),所有被動改變Fragment
狀態的呼叫都是遍歷這個列表;而mAdded
則表示執行過add
操作,並且沒有執行detachFragment/removeFragment
,也就是說Fragment
是存在容器當中的,但是有可能是被隱藏的(hideFragment
)。attachFragment
必須保證其狀態是mDetach
的,而該屬性的預設值是false
,只有在執行過detach
方法後,才能執行,執行它會把f.mView
重新加入到container
中。detachFragment
會把f.mView
從container
中移除。removeFragment
和detachFragment
會強制改變Fragment
的狀態,這是因為它們需要改變Fragment
在佈局中的位置,而這通過被動地接收FragmentManager
狀態(即所在Activity
的狀態)是無法實現的。在removeFragment
時,會根據是否加入後退棧來區分,如果假如了後退棧,因為有可能之後會回退,而回退時遍歷的是mActive
列表,如果把它的狀態置為Fragment.INITIALZING
,那麼在moveToState
方法中就會走到最後一步,把它從mActive
列表中移除,就找不到了也就無法恢復,因此這種情況下Fragment
最終的狀態的和detachFragment
是相同的。- 在加入後退棧時,
detachFragment
時,會把mDetach
置為true
,這種情況下之後可以執行attachFragment
操作但不能執行addFragment
操作;removeFragment
之後可以執行addFragment
操作但不能執行attachFragment
操作。 showFragment
和hideFragment
並不會主動改變Fragment
的狀態,它僅僅是回撥onHiddenChanged
方法,其狀態還是跟著FragmentManager
來走。
mManager.moveToState(mManager.mCurState, mTransition, mTransitionStyle, true);
複製程式碼
這個方法才是 Fragment
的核心,它的思想就是根據 Fragment
期望進入的狀態和之前的狀態進行對比,從而呼叫 Fragment
相應的生命週期,那麼問題就來,什麼是期望進入的狀態呢,我認為可以這麼理解:
- 使用者沒有進行主動操作,但是
Fragment
和FragmentManager
的狀態不一致,這時需要發生變化。 - 使用者進行了主動操作,無論
Fragment
和FragmentManager
的狀態是否一致,因為Fragment
的狀態發生了變化,因此這時也需要執行。
這裡我們為了簡便起見先看一下和生命週期有關的程式碼:
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.
void moveToState(int newState, int transit, int transitStyle, boolean always) {
if (!always && mCurState == newState) {
return;
}
mCurState = newState;
if (mActive != null) {
for (int i = 0; i < mActive.size; i++) {
//在addFragment中通過makeActive加入進去
Fragment f = mActive.get(i);
moveToState(f, newState, transit, transitStyle, false);
}
}
}
void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) {
if (f.mState < newState) { //新的狀態高於當前狀態
switch(f.mState) {
case Fragment.INITIALZING:
f.onAttach(mHost.getContext());
if (f.mParentFragment == null) {
mHost.onAttachFragment(f);
}
if (!f.mRetaining) {
f.performCreate(f.mSavedFragmentState);
}
if (f.mFromLayout) {
f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), null, f.mSavedFragmentState);
if (f.mHidden) f.mView.setVisibility(View.GONE);
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
case Fragment.CREATED:
if (newState > Fragment.CREATED) {
if (!f.mFromLayout) {
ViewGroup container = null;
if (f.mCotainerId != null) {
cotainer = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
if (container == null && !f.mRestored) {
//no view found
}
}
f.mContainer = container;
f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), null, f.mSavedFragmentState);
if (f.mView != null) {
f.mInnerView = f.mView;
if (Build.VERSION.SDK >= 11) {
ViewCompact.setSaveFromParentEnable(f.mView, false);
} else {
f.mView = NoSaveStateFrameLayout.wap(f.mView);
}
if (f.mHidden) f.mView.setVisibility(View.GONE);
f.onViewCreated(f.mView, f.mSavedFragmentState);
} else {
f.mInnerView = null;
}
}
f.performActivityCreated(f.mSavedFragmentState);
}
case Fragment.ACTIVITY_CRREATED:
case Fragment.STOPPED:
if (newState > Fragment.STOPPED) {
f.performStart();
}
case Fragment.STARTED:
if (newState > Fragment.STARTED) {
f.performResume();
}
}
} else if (f.mState > newState) { //新的狀態低於當前狀態
switch(f.mState) {
case Fragment.RESUMED:
if (newState < Fragment.RESUMED) {
f.performPause();
}
case Fragment.STARTED:
if (newState < Fragment.STARTED) {
f.performStop();
}
case Fragment.STOPPED::
if (newState < Fragment.STOPPED) {
f.performReallyStop();
}
case Fragment.ACTIVITY_CREATED:
if (newState < Fragment.ACTIVITY_CREATED) {
f.performDestroyView(); //呼叫onDestory()
if (f.mView != null && f.mContainer != null) {
f.mContainer.removeView(f.mView); //把Fragment的View從檢視中移除。
}
}
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
if (f.mAnimationAway != null) {
...
} else {
if (!f.mRetaining) {
f.performDestory();
} else {
f.mState = Fragment.INITIALIZING;
}
f.onDetach();
if (!f.mRetaining) {
makeInActive(f);//把Fragment從mActive中移除,並把Fragment的所有狀態恢復成初始狀態,相當於它是一個全新的Fragment。
} else {
f.mHost = null;
f.mParentFragment = null;
f.mFragmentManager = null;
f.mChildFragmentManager = null;
}
}
}
}
}
複製程式碼
到 moveToState
方法中,我們看到了許多熟悉的面孔,就是我們平時最常談到的 Fragment
的生命週期,通過這段程式碼我們就可以對它的生命週期有更加直觀的理解。