一次FragmentTabHost切换Tab崩溃分析之旅
来源:互联网 发布:tcp网络调试助手 编辑:程序博客网 时间:2024/05/17 21:48
最近在写项目时,由于使用了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<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的状态,我们注意这段代码
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<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(); 当我们调用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,事务就不会被提交二次。
- 一次FragmentTabHost切换Tab崩溃分析之旅
- Android之tab实现切换页面效果—FragmentTabHost
- FragmentTabHost实现底部tab切换
- Tab效果之FragmentTabhost
- Android FragmentTabHost实现底部tab切换
- FragmentTabHost、Fragment实现底部点击切换Tab页面
- FragmentTabHost、Fragment实现底部点击切换Tab页面
- Ext之Tab切换
- 微信小程序之Tab切换
- JavaScript 动画之tab切换
- jquery动画之tab切换
- Android典型界面设计——FragmentTabHost+Fragment实现底部tab切换
- FragmentTabHost 阻止切换
- FragmentTabHost底部切换
- 使用FragmentTabHost实现Tab页
- jQuery之Tab切换代码改进
- Android Tab切换之Fragment方法
- Tab页面切换之3种方法
- MySQL的基本设定
- python SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 2-3: trunca
- GMSSL SM9-密钥交换流程
- Android Studio工程导入,导入依赖包
- 准备好迎接你的“新同事”了吗?他叫人工智能 | 精选
- 一次FragmentTabHost切换Tab崩溃分析之旅
- SSS1629|7.1游戏耳机方案|网吧游戏耳机方案|USB耳麦方案|开发设计
- sql内连接、外连接和自连接查询
- 【AI英雄风云榜】为TA投票:谁是2017中国AI领域最牛的人?
- (十五)消息队列MQ
- Android studio http请求获取数据失败或者获取不到数据原因
- Qt定时器事件与计数器
- 火爆不亚于中国?看看印度聊天机器人市场现状 | 分析
- 政府牵头!中国要造强大AI芯片挑战英伟达地位 | 聚焦