一次FragmentTabHost切换Tab崩溃分析之旅

来源:互联网 发布:tcp网络调试助手 编辑:程序博客网 时间:2024/05/17 21:48

最近在写项目时,由于使用了FragmentTabHost这个控件导致我每次点击第二个Tab按钮的时候就崩溃。
然后开始搜stackoverflow,上面说原因在于MainActivity中使用了FragmentManager,MainActivty中的Fragment又嵌套了 viewpager+fragment这种模式所以嵌套的viewpager中不能再传FragmentManager,要传递getChildFragmentManager,兴冲冲的改过来后发现还是崩溃,最后分析源码才发现问题原因。

而我们看看异常崩溃栈信息

111.png

问题就出现在初始化这里.来分析一下
当我们点击第二个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一个内部类

QQ截图20170901134823.png

看下它拿到的FragmentTransaction是什么

    @Override    public FragmentTransaction beginTransaction() {        return new BackStackRecord(this);    }

123213.png

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它的时候初始化执行这个方法
2222.png

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,事务就不会被提交二次。

阅读全文
0 0