最近在写项目时,由于使用了FragmentTabHost这个控件导致我每次点击第二个Tab按钮的时候就崩溃。
然后开始搜stackoverflow,上面说原因在于MainActivity中使用了FragmentManager,MainActivty中的Fragment又嵌套了 viewpager+fragment这种模式所以嵌套的viewpager中不能再传FragmentManager,要传递getChildFragmentManager,兴冲冲的改过来后发现还是崩溃,最后分析源码才发现问题原因。
而我们看看异常崩溃栈信息
问题就出现在初始化这里.来分析一下
当我们点击第二个tab时候,我们看下FragmentTabHost的执行流程,首先会回掉这个方法
@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自己实现了这个方法监听Tab点击,如果点击Tab改变情况下,就会调用FragmentTransaction 的commit方法提交事务,commit这个方法在FragmentTransaction 中是个抽象方法,那么我们就看看具体实现,找到getSupportFragmentManger()拿到的具体类一直点击最终会发现得到的是一个FragmentManagerImpl这个对象,它是FragmentManager一个内部类
看下它拿到的FragmentTransaction是什么
@Override
public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}
ok,是这个类,好我们可以点进去查一下commit方法具体实现了
@Override
public int commit() {
return commitInternal(false);
}
继续跟进
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);
最后会调用FragmentManager的enqueueAction方法
好我们看看FragmentManager中的enqueueAction方法怎么实现的
/**
* 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();
}
}
最后一行可以看到调用了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发送了一个消息,看看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;
}
分析了这么长现在回到了我们上面打印异常栈信息的开始,好继续分析,第一行就执行了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;
}
}
每次新提交的事务都会调用到execPendingActions()这个方法,在同一个FragmentManager中,如果第一个commit事务没有执行完毕,就又提交一个新事务那么就会判断mExecutingActions 这个变量,mExecutingActions 为true代表还有未处理完毕的事务,那么下个事务提交时mExecutingActions 为true就会抛出传说中的”FragmentManager is already executing transactions”异常
那我们第一次点击commit这个值应该是false,好执行完这个方法会走下面optimizeAndExecuteOps方法,由于后面源码都比较长就截取片段了.
接着会走这个方法
if (startIndex != recordNum) {
executeOpsTogether(records, isRecordPop, startIndex, recordNum);
}
next
if (!allowOptimization) {
FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex,
false);
}
next就会调用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然后就会调用这个Fragment的onCreate
if (!f.mRetaining) {
f.performCreate(f.mSavedFragmentState);
dispatchOnFragmentCreated(f, f.mSavedFragmentState, false);
} else {
f.restoreChildFragmentState(f.mSavedFragmentState);
f.mState = Fragment.CREATED;
}
接着以此调用onViewCreate等Fragment生命周期方法
f.onViewCreated(f.mView, f.mSavedFragmentState);
我在BaseFragment中使用了这个库
mLoadingAndRetryManager = new LoadingAndRetryManager(mActivity.get(), mOnLoadingAndRetryListener);
由于BaseFragment使用并初始化了LoadingAndRetryManager这个控件,在new它的时候初始化执行这个方法
addView,当最终add依附到最顶层的ViewGroup之后就会调用,dispatchAttachedToWindow方法,然后会调用FragmentTabHost的onAttachedToWindow方法,看看它的实现
@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();
}
}
最终会调用 mFragmentManager.executePendingTransactions(); 这在异常栈信息上面也可以看到
而这个方法实现在FragmentManagerImpl
@Override
public boolean executePendingTransactions() {
boolean updates = execPendingActions();
forcePostponedTransactions();
return updates;
}
它会调用execPendingActions()这个方法。
我们分析下问题的原因所在
当我们点击第二个Tab按钮的时候,会调用Commit进行事务提交然后调用到execPendingActions()这个方法,这个方法在执行 optimizeAndExecuteOps(mTmpRecords, mTmpIsPop);方法之前会将mExecutingActions赋值为true,接着会调用一系列方法后走第二个Fragment的onCreate()方法,然后在创建LoadingAndRetryManager对象时会移除添加activity的contentview这样会触发onAttachToWindow这个方法,最终会调用
mFragmentManager.executePendingTransactions();这一句,然后又会调用execPendingActions()这个方法, 在ensureExecReady方法中会判断mExecutingActions标记值,前面刚被设置为true,所以这里崩溃了
这里有个小疑惑,我将创建LoadingAndRetryManager对象这句放在Fragment的onCreate()中会出现上述崩溃问题,而放在onViewCreate()就好了为什么呢?
我们接着分析.
其实原因就在这个方法里
private void executeOpsTogether(ArrayList records,
ArrayList 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 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的状态,我们注意这段代码
if (!allowOptimization) {
FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex,
false);
}
executeOps(records, isRecordPop, startIndex, endIndex);
if中方法会启动事物,最终会调用Fragment的onCreate等系列生命周期方法,上面已经分析到,下面那行方法是做什么的呢
/**
* 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 records,
ArrayList 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(); 当我们调用fragment的detach方法后只会把这个detach命令和fragment对象存储到这个Op对象里,调用这个方法后才会真正的从集合列表中移除fragment并且把fragment的mDetached变量设置为true。
上面调用FragmentTransition.startTransitions后会调用到onCreate然后会创建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又提交了事务导致上面分析的崩溃。
而创建LoadingAndRetryManager对象放到onViewCreate里的话,在executeOpsTogether 方法中if执行后executeOps(records, isRecordPop, startIndex, endIndex)就会执行,这样第一个Fragment由于detach了mDetached就会赋值true,然后在onAttachedToWindow中循环tabs时候第一个tab就不走if,第二个tab的mDetached是false就会走if,并且由于第二个tab就是我们点击的当前tab所以里面也会走if,最终ft并不会被赋值,所以也不会走最后一行的if,事务就不会被提交二次。