最近在寫項目時,由于使用了FragmentTabHost這個控件導致我每次點擊第二個Tab按鈕的時候就崩潰镀赌。
然后開始搜stackoverflow,上面說原因在于MainActivity中使用了FragmentManager,MainActivty中的Fragment又嵌套了 viewpager+fragment這種模式所以嵌套的viewpager中不能再傳FragmentManager,要傳遞getChildFragmentManager际跪,興沖沖的改過來后發(fā)現(xiàn)還是崩潰,最后分析源碼才發(fā)現(xiàn)問題原因商佛。
而我們看看異常崩潰棧信息
問題就出現(xiàn)在初始化這里.來分析一下
當我們點擊第二個tab時候,我們看下FragmentTabHost的執(zhí)行流程喉钢,首先會回掉這個方法
@Override
public void onTabChanged(String tabId) {
if (mAttached) {
final FragmentTransaction ft = doTabChanged(tabId, null);
if (ft != null) {
ft.commit();
}
}
if (mOnTabChangeListener != null) {
mOnTabChangeListener.onTabChanged(tabId);
}
}
FragmentTabHost自己實現(xiàn)了這個方法監(jiān)聽Tab點擊,如果點擊Tab改變情況下,就會調(diào)用FragmentTransaction 的commit方法提交事務,commit這個方法在FragmentTransaction 中是個抽象方法,那么我們就看看具體實現(xiàn),找到getSupportFragmentManger()拿到的具體類一直點擊最終會發(fā)現(xiàn)得到的是一個FragmentManagerImpl這個對象,它是FragmentManager一個內(nèi)部類
看下它拿到的FragmentTransaction是什么
@Override
public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}
ok,是這個類,好我們可以點進去查一下commit方法具體實現(xiàn)了
@Override
public int commit() {
return commitInternal(false);
}
繼續(xù)跟進
int commitInternal(boolean allowStateLoss) {
if (mCommitted) throw new IllegalStateException("commit already called");
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Commit: " + this);
LogWriter logw = new LogWriter(TAG);
PrintWriter pw = new PrintWriter(logw);
dump(" ", null, pw, null);
pw.close();
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
注意這行
mManager.enqueueAction(this, allowStateLoss);
最后會調(diào)用FragmentManager的enqueueAction方法
好我們看看FragmentManager中的enqueueAction方法怎么實現(xiàn)的
/**
* Adds an action to the queue of pending actions.
*
* @param action the action to add
* @param allowStateLoss whether to allow loss of state information
* @throws IllegalStateException if the activity has been destroyed
*/
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mHost == null) {
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<>();
}
mPendingActions.add(action);
scheduleCommit();
}
}
最后一行可以看到調(diào)用了scheduleCommit方法
/**
* Schedules the execution when one hasn't been scheduled already. This should happen
* the first time {@link #enqueueAction(OpGenerator, boolean)} is called or when
* a postponed transaction has been started with
* {@link Fragment#startPostponedEnterTransition()}
*/
private void scheduleCommit() {
synchronized (this) {
boolean postponeReady =
mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
if (postponeReady || pendingReady) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
}
}
在這里通過Handler post發(fā)送了一個消息,看看mExecCommit
Runnable mExecCommit = new Runnable() {
@Override
public void run() {
execPendingActions();
}
};
/**
* Only call from main thread!
*/
public boolean execPendingActions() {
ensureExecReady(true);
boolean didSomething = false;
while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
mExecutingActions = true;
try {
optimizeAndExecuteOps(mTmpRecords, mTmpIsPop);
} finally {
cleanupExec();
}
didSomething = true;
}
doPendingDeferredStart();
return didSomething;
}
分析了這么長現(xiàn)在回到了我們上面打印異常棧信息的開始,好繼續(xù)分析,第一行就執(zhí)行了ensureExecReady(true)這個方法,它是干什么用的呢良姆?點進去看看
/**
* Broken out from exec*, this prepares for gathering and executing operations.
*
* @param allowStateLoss true if state loss should be ignored or false if it should be
* checked.
*/
private void ensureExecReady(boolean allowStateLoss) {
if (mExecutingActions) {
throw new IllegalStateException("FragmentManager is already executing transactions");
}
if (Looper.myLooper() != mHost.getHandler().getLooper()) {
throw new IllegalStateException("Must be called from main thread of fragment host");
}
if (!allowStateLoss) {
checkStateLoss();
}
if (mTmpRecords == null) {
mTmpRecords = new ArrayList<>();
mTmpIsPop = new ArrayList<>();
}
mExecutingActions = true;
try {
executePostponedTransaction(null, null);
} finally {
mExecutingActions = false;
}
}
每次新提交的事務都會調(diào)用到execPendingActions()這個方法,在同一個FragmentManager中,如果第一個commit事務沒有執(zhí)行完畢,就又提交一個新事務那么就會判斷mExecutingActions 這個變量,mExecutingActions 為true代表還有未處理完畢的事務,那么下個事務提交時mExecutingActions 為true就會拋出傳說中的"FragmentManager is already executing transactions"異常
那我們第一次點擊commit這個值應該是false,好執(zhí)行完這個方法會走下面optimizeAndExecuteOps方法,由于后面源碼都比較長就截取片段了.
接著會走這個方法
if (startIndex != recordNum) {
executeOpsTogether(records, isRecordPop, startIndex, recordNum);
}
next
if (!allowOptimization) {
FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex,
false);
}
next就會調(diào)用FragmentTransition的startTransitions方法
if (isPop) {
calculatePopFragments(record, transitioningFragments, isOptimized);
} else {
calculateFragments(record, transitioningFragments, isOptimized);
}
然后會走else
接著走這個方法,會走manager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
addToFirstInLastOut(transaction, op, transitioningFragments, false, isOptimized);
if (fragment.mState < Fragment.CREATED && manager.mCurState >= Fragment.CREATED
&& !transaction.mAllowOptimization) {
manager.makeActive(fragment);
manager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
}
會走if然后就會調(diào)用這個Fragment的onCreate
if (!f.mRetaining) {
f.performCreate(f.mSavedFragmentState);
dispatchOnFragmentCreated(f, f.mSavedFragmentState, false);
} else {
f.restoreChildFragmentState(f.mSavedFragmentState);
f.mState = Fragment.CREATED;
}
接著以此調(diào)用onViewCreate等Fragment生命周期方法
f.onViewCreated(f.mView, f.mSavedFragmentState);
我在BaseFragment中使用了這個庫
mLoadingAndRetryManager = new LoadingAndRetryManager(mActivity.get(), mOnLoadingAndRetryListener);
由于BaseFragment使用并初始化了LoadingAndRetryManager這個控件,在new它的時候初始化執(zhí)行這個方法
addView肠虽,當最終add依附到最頂層的ViewGroup之后就會調(diào)用,dispatchAttachedToWindow方法,然后會調(diào)用FragmentTabHost的onAttachedToWindow方法,看看它的實現(xiàn)
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
final String currentTag = getCurrentTabTag();
// Go through all tabs and make sure their fragments match
// the correct state.
FragmentTransaction ft = null;
for (int i = 0, count = mTabs.size(); i < count; i++) {
final TabInfo tab = mTabs.get(i);
tab.fragment = mFragmentManager.findFragmentByTag(tab.tag);
if (tab.fragment != null && !tab.fragment.isDetached()) {
if (tab.tag.equals(currentTag)) {
// The fragment for this tab is already there and
// active, and it is what we really want to have
// as the current tab. Nothing to do.
mLastTab = tab;
} else {
// This fragment was restored in the active state,
// but is not the current tab. Deactivate it.
if (ft == null) {
ft = mFragmentManager.beginTransaction();
}
ft.detach(tab.fragment);
}
}
}
// We are now ready to go. Make sure we are switched to the
// correct tab.
mAttached = true;
ft = doTabChanged(currentTag, ft);
if (ft != null) {
ft.commit();
mFragmentManager.executePendingTransactions();
}
}
最終會調(diào)用 mFragmentManager.executePendingTransactions(); 這在異常棧信息上面也可以看到
而這個方法實現(xiàn)在FragmentManagerImpl
@Override
public boolean executePendingTransactions() {
boolean updates = execPendingActions();
forcePostponedTransactions();
return updates;
}
它會調(diào)用execPendingActions()這個方法。
我們分析下問題的原因所在
當我們點擊第二個Tab按鈕的時候歇盼,會調(diào)用Commit進行事務提交然后調(diào)用到execPendingActions()這個方法舔痕,這個方法在執(zhí)行 optimizeAndExecuteOps(mTmpRecords, mTmpIsPop);方法之前會將mExecutingActions賦值為true,接著會調(diào)用一系列方法后走第二個Fragment的onCreate()方法,然后在創(chuàng)建LoadingAndRetryManager對象時會移除添加activity的contentview這樣會觸發(fā)onAttachToWindow這個方法豹缀,最終會調(diào)用
mFragmentManager.executePendingTransactions();這一句,然后又會調(diào)用execPendingActions()這個方法慨代, 在ensureExecReady方法中會判斷mExecutingActions標記值邢笙,前面剛被設置為true,所以這里崩潰了
這里有個小疑惑,我將創(chuàng)建LoadingAndRetryManager對象這句放在Fragment的onCreate()中會出現(xiàn)上述崩潰問題侍匙,而放在onViewCreate()就好了為什么呢氮惯?
我們接著分析.
其實原因就在這個方法里
private void executeOpsTogether(ArrayList<BackStackRecord> records,
ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
final boolean allowOptimization = records.get(startIndex).mAllowOptimization;
boolean addToBackStack = false;
if (mTmpAddedFragments == null) {
mTmpAddedFragments = new ArrayList<>();
} else {
mTmpAddedFragments.clear();
}
if (mAdded != null) {
mTmpAddedFragments.addAll(mAdded);
}
Fragment oldPrimaryNav = getPrimaryNavigationFragment();
for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
final BackStackRecord record = records.get(recordNum);
final boolean isPop = isRecordPop.get(recordNum);
if (!isPop) {
oldPrimaryNav = record.expandOps(mTmpAddedFragments, oldPrimaryNav);
} else {
oldPrimaryNav = record.trackAddedFragmentsInPop(mTmpAddedFragments, oldPrimaryNav);
}
addToBackStack = addToBackStack || record.mAddToBackStack;
}
mTmpAddedFragments.clear();
if (!allowOptimization) {
FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex,
false);
}
executeOps(records, isRecordPop, startIndex, endIndex);
int postponeIndex = endIndex;
if (allowOptimization) {
ArraySet<Fragment> addedFragments = new ArraySet<>();
addAddedFragments(addedFragments);
postponeIndex = postponePostponableTransactions(records, isRecordPop,
startIndex, endIndex, addedFragments);
makeRemovedFragmentsInvisible(addedFragments);
}
if (postponeIndex != startIndex && allowOptimization) {
// need to run something now
FragmentTransition.startTransitions(this, records, isRecordPop, startIndex,
postponeIndex, true);
moveToState(mCurState, true);
}
for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
final BackStackRecord record = records.get(recordNum);
final boolean isPop = isRecordPop.get(recordNum);
if (isPop && record.mIndex >= 0) {
freeBackStackIndex(record.mIndex);
record.mIndex = -1;
}
record.runOnCommitRunnables();
}
if (addToBackStack) {
reportBackStackChanged();
}
}
它在FragmentManager中,主要處理Fragment的狀態(tài),我們注意這段代碼
if (!allowOptimization) {
FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex,
false);
}
executeOps(records, isRecordPop, startIndex, endIndex);
if中方法會啟動事物,最終會調(diào)用Fragment的onCreate等系列生命周期方法想暗,上面已經(jīng)分析到妇汗,下面那行方法是做什么的呢
/**
* Run the operations in the BackStackRecords, either to push or pop.
*
* @param records The list of records whose operations should be run.
* @param isRecordPop The direction that these records are being run.
* @param startIndex The index of the first entry in records to run.
* @param endIndex One past the index of the final entry in records to run.
*/
private static void executeOps(ArrayList<BackStackRecord> records,
ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
for (int i = startIndex; i < endIndex; i++) {
final BackStackRecord record = records.get(i);
final boolean isPop = isRecordPop.get(i);
if (isPop) {
record.bumpBackStackNesting(-1);
// Only execute the add operations at the end of
// all transactions.
boolean moveToState = i == (endIndex - 1);
record.executePopOps(moveToState);
} else {
record.bumpBackStackNesting(1);
record.executeOps();
}
}
}
注意最后一段 record.executeOps(); 當我們調(diào)用fragment的detach方法后只會把這個detach命令和fragment對象存儲到這個Op對象里,調(diào)用這個方法后才會真正的從集合列表中移除fragment并且把fragment的mDetached變量設置為true说莫。
上面調(diào)用FragmentTransition.startTransitions后會調(diào)用到onCreate然后會創(chuàng)建LoadingAndRetryManager對象然后走onAttachToWindow
for (int i = 0, count = mTabs.size(); i < count; i++) {
final TabInfo tab = mTabs.get(i);
tab.fragment = mFragmentManager.findFragmentByTag(tab.tag);
if (tab.fragment != null && !tab.fragment.isDetached()) {
if (tab.tag.equals(currentTag)) {
// The fragment for this tab is already there and
// active, and it is what we really want to have
// as the current tab. Nothing to do.
mLastTab = tab;
} else {
// This fragment was restored in the active state,
// but is not the current tab. Deactivate it.
if (ft == null) {
ft = mFragmentManager.beginTransaction();
}
ft.detach(tab.fragment);
}
}
}
此時fragment的mDetached值還是false杨箭,所以會走if,而第一個fragment的tab標簽肯定不等于點擊的第二個標簽储狭,所以又會走到else里互婿,這樣ft就被賦值了,然后走if又提交了事務導致上面分析的崩潰辽狈。
而創(chuàng)建LoadingAndRetryManager對象放到onViewCreate里的話,在executeOpsTogether 方法中if執(zhí)行后executeOps(records, isRecordPop, startIndex, endIndex)就會執(zhí)行慈参,這樣第一個Fragment由于detach了mDetached就會賦值true,然后在onAttachedToWindow中循環(huán)tabs時候第一個tab就不走if刮萌,第二個tab的mDetached是false就會走if驮配,并且由于第二個tab就是我們點擊的當前tab所以里面也會走if,最終ft并不會被賦值着茸,所以也不會走最后一行的if壮锻,事務就不會被提交二次。