Android | 玩转fragment,使用commit出现闪退问题解析

龙旋

共 4256字,需浏览 9分钟

 ·

2021-03-17 09:58

在最近的项目过程中,使用了fragment,我们常规的用法如下:

 getSupportFragmentManager()            .beginTransaction()            .add(R.id.fragment_content, new TestFragment())            .commit();


这样的用法对于很多童鞋而言都是比较熟悉的,但是在测试的过程中我们有时候会报如下错误信息:

Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState


从错误信息就是说我们不能在调用onSaveInstanceState进行commit操作。网上的解决办法是使用commitAllowingStateLoss替换commit。试了一下确实能解决这个问题,但是为什么呢?这就是写这篇的主要原因,划重点。


下面通过源码去看这两个方法的区别:


首先从我们获取 FragmentTransaction 类的实例开始,

即getSupportFragmentManager() ,源码如下:

    /**     * Return the FragmentManager for interacting with fragments associated     * with this activity.     */    public FragmentManager getSupportFragmentManager() {        return mFragments;    }


而这个返回的 mFragments 是一个 FragmentManagerImpl 类 的实例,他继承自FragmentManager 这个类:

final FragmentManagerImpl mFragments = new FragmentManagerImpl();


我们在 FragmentManager 这个类中还看到 beginTransaction() 这个抽象方法,打开他的实现类

    final class FragmentManagerImpl extends FragmentManager {      ......
@Override public FragmentTransaction beginTransaction() { return new BackStackRecord(this); } .... ... }

 

我们看到这个实现类中的该方法是返回一个 BackStateRecord 类的实体,我们继续去追踪这个类,就会发现,这个类其实是继承自 FragmentTransaction 的,并且,我们在这里看到我们熟悉的方法:

    final class BackStackRecord extends FragmentTransaction implements            FragmentManager.BackStackEntry, Runnable {        public BackStackRecord(FragmentManagerImpl manager) {            mManager = manager;        }
public int commit() { return commitInternal(false); }
public int commitAllowingStateLoss() { return commitInternal(true); }
int commitInternal(boolean allowStateLoss) { if (mCommitted) throw new IllegalStateException("commit already called"); if (FragmentManagerImpl.DEBUG) { LogWriter logw = new LogWriter(TAG); PrintWriter pw = new PrintWriter(logw); dump(" ", null, pw, null); } mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); return mIndex; } }


这里省略了其他不必要的代码,只留下我们需要用的核心代码,看我们要找的两个方法 :

commit() 和 commitAllowingStateLoss()


 他们都调用了:

 commitInternal(boolean allowStateLoss)

这个方法,只是传入的参数不同,我们去看 commitInternal 方法,该方法首先去判断是否已经 commit ,这个简单,直接跳过,最后他执行的是 FragmentTransactionImpl 类的 enqueueAction 方法,我们继续去追踪这个方法:

 public void enqueueAction(Runnable action, boolean allowStateLoss) {        if (!allowStateLoss) {            checkStateLoss();        }        synchronized (this) {            if (mActivity == null) {                throw new IllegalStateException("Activity has been destroyed");            }            if (mPendingActions == null) {                mPendingActions = new ArrayList<Runnable>();            }            mPendingActions.add(action);            if (mPendingActions.size() == 1) {                mActivity.mHandler.removeCallbacks(mExecCommit);                mActivity.mHandler.post(mExecCommit);            }        }    }


通过这个方法我们可以看到,他首先去根据:

commit 和 commitAllowingStateLoss

这两个方法传入的参数不同去判断,然后将任务扔进 activity 的线程队列中,这里我们重点去看的是 checkStateLoss() 这个方法:

   private void checkStateLoss() {        if (mStateSaved) {            throw new IllegalStateException(                    "Can not perform this action after onSaveInstanceState");        }        if (mNoTransactionsBecause != null) {            throw new IllegalStateException(                    "Can not perform this action inside of " + mNoTransactionsBecause);        }    }


只有调用 commit 方法才会执行这里,commitAllowingStateLoss 则直接跳过这步,这里我们调用 commit 方法时,系统判断状态(mStateSaved)是否已经保存,如果已经保存,则抛出:

Can not perform this action after onSaveInstanceState

异常,这就是我们遇到的问题了,而用 commitAllowingStateLoss 方法则不会这样,这就与我们之前分析的activity去保存状态对应上了,在 activity 保存状态完成之后调用 commit 时,这个 mStateSaved 就是已经保存状态,所以会抛出异常。


到这里就结束啦!

浏览 117
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报