commit和commitAllowingStateLoss方法的区别

96
空手接白刀
1.1 2019.03.25 09:04 字数 759

遇到的问题

 java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
        at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2044)
        at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2067)
        at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:680)
        at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:634)

前两天使用EventBus在别的页面对MainAcitvity里的Fragment进行操作时,爆出来这个异常,网上找到的解决方案是把 FragmentTransaction 调用的commit()方法替换为 commitAllowingStateLoss()方法就可以解决了,至于为什么用这个方法,最近研究了一下源码,在这里分享一下。

源码分析及验证

public abstract class FragmentTransaction {
   ……
    public abstract int commit();
    public abstract int commitAllowingStateLoss();
}

首先查看FragmentTransaction,很简单的一个抽象类,不多说,下面寻找真正的实现类


在这里插入图片描述

通过上图的方式,右键类名点击 Find Usages,然后在Find视图窗口里找到继承自FragmentTransaction的实现类:BackStackRecord,下面来看一下这个类:

final class BackStackRecord extends FragmentTransaction implements
        FragmentManager.BackStackEntry, Runnable {
    ……
    public int commit() {
        return commitInternal(false);
    }

    public int commitAllowingStateLoss() {
        return commitInternal(true);
    }
    int commitInternal(boolean allowStateLoss) {
        ……
        mManager.enqueueAction(this, allowStateLoss);
        ……
    }
    ……
}

省略掉无关逻辑,commit()和commitAllowingStateLoss()两个方法都调用了同一个方法commitInternal(),commitInternal()方法接收一个boolean的参数allowStateLoss(允许状态丢失),commitInternal()方法里调用了FragmentManager的enqueueAction()方法,并且把boolean参数传递了进去,然后继续看FragmentManager:

public abstract class FragmentManager {
    ……
    boolean mStateSaved;
    ……
    public void enqueueAction(Runnable action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            checkStateLoss();
        }
    }

    private void checkStateLoss() {
        if (mStateSaved) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
    }
    ……
    Parcelable saveAllState() {
        ……
        if (HONEYCOMB) {
            mStateSaved = true;
        }
        ……
    }
    ……
    public void noteStateNotSaved() {
        mStateSaved = false;
    }

    public void dispatchCreate() {
        mStateSaved = false;
        ……
    }

    public void dispatchActivityCreated() {
        mStateSaved = false;
        ……
    }

    public void dispatchStart() {
        mStateSaved = false;
        ……
    }

    public void dispatchResume() {
        mStateSaved = false;
        ……
    }
    ……
    public void dispatchStop() {
        mStateSaved = true;
        ……
    }
    ……
}

这里可以看出来,enqueueAction()根据allowStateLoss参数做了一个判断,如果allowStateLoss=false,也就是如果你调用的是commit()方法的话,那么就要做一个判断,如果mStateSaved为true,那么就要抛出异常,这个异常就是文章开头所说的那个异常。

问题的关键来了,这个mStateSaved是如何变化的:
上面的代码可以看出在saveAllState()方法和dispatchStop() 方法中,mStateSaved会变为true(HONEYCOMB意思是是否大于api11,不用管),这两个方法分别是在FragmentActivity的onSaveInstanceState()和onStop()中被调用,而onSaveInstanceState()的调用时机是在onPause()之后onStop()之前,这样可以总结出来当Activity的onSaveInstanceState()方法调用之后如果调用了该Activity的FragmentTransaction的commit方法,就会抛出异常(验证文章开头的结论)。

相反的,其他几个将mStateSaved变为false的方法,根据方法名可以推断出是在FragmentActivity的生命周期的其他几个回调方法里运行的,可以推断出,当Activity重新回到栈顶显示之后,调用commit是没有问题的。

总结

当Activity中的Fragment发生了变化,FragmentManager会在特定的时间点保存所有Fragment的状态,方便Activity因为被回收之后重建时,重新设置Fragment,如果状态没有被保存,那么Activity就只能按照默认方式显示每个Fragment,显示效果可能跟app的预期不一样。
如果项目中有特定的需求,比如需要在别的Activity中通过广播或者Eventbus的方式控制MainActivity的Fragment切换,那么就需要使用commitAllowingStateLoss()方法来避免异常。
大家需要按照自己的需求来选择合适的方法使用。

如果文章中有错误或者不严谨的地方,希望提出来,共同学习。

android开发
Web note ad 1