andnroid

解决IllegalStateException: Can not

2021-08-02  本文已影响0人  有爱的梦_大东

实际项目出现此问题

一般是FragmentTransaction事务提交时报错,将commit换为commitAllowingStateLoss

从这里可以引申出Fragment的FragmentTransaction 的commit()和commitAllowingStateLoss()以及commitNow()和commitNowAllowingStateLoss()

commit

只能在activity存储它的状态(onSaveInstanceState(),当用户要离开activity时)之前调用commit(),如果在存储状态之后调用commit(),将会抛出一个异常,这是因为当activity再次被恢复时commit之后的状态将丢失。如果丢失也没关系,那么使用commitAllowingStateLoss()方法,commit和CommitNow都会抛出异常,如果在onSaveInstanceState()后执行的话。allowStateLoss=false,方法后面会执行检查checkStateLoss(),就是已经保存状态或stop的就会抛出异常IllegalStateException

commitNow

commitNow不允许addToBackStack,commitNow是不允许加入BackStack中去的,会将mAddToBackStack标志设置为false

commitAllowingStateLoss

同commit一样调用内部的commitInternal()方法,只不过传递的参数不同,commitAllowStateLoss的allowStateLoss是true,允许丢失状态不做检查,所以不会抛异常

代码

@Override
    public int commit() {
        return commitInternal(false);
    }
//允许转台丢失
    @Override
    public int commitAllowingStateLoss() {
        return commitInternal(true);
    }

可以看到两方法都调用commitInternal方法,参数是allowStateLoss的boolean类型

int commitInternal(boolean allowStateLoss) {
        if (mCommitted) throw new IllegalStateException("commit already called");
        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
            Log.v(TAG, "Commit: " + this);
            LogWriter logw = new LogWriter(TAG);
            PrintWriter pw = new PrintWriter(logw);
            dump("  ", pw);
            pw.close();
        }
        mCommitted = true;
        if (mAddToBackStack) {
            mIndex = mManager.allocBackStackIndex();
        } else {
            mIndex = -1;
        }
//加入队列
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }

可以看到是通过 mManager.enqueueAction(this, allowStateLoss);来执行

void enqueueAction(@NonNull OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            if (mHost == null) {
                if (mDestroyed) {
                    throw new IllegalStateException("FragmentManager has been destroyed");
                } else {
                    throw new IllegalStateException("FragmentManager has not been attached to a "
                            + "host.");
                }
            }
            checkStateLoss();
        }
        synchronized (mPendingActions) {
            if (mHost == null) {
                if (allowStateLoss) {
                    // This FragmentManager isn't attached, so drop the entire transaction.
                    return;
                }
                throw new IllegalStateException("Activity has been destroyed");
            }
            mPendingActions.add(action);
            scheduleCommit();
        }
    }

allowStateLoss参数是来执行检测操作的 checkStateLoss();这就是报错的地方

private void checkStateLoss() {
        if (isStateSaved()) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
    }

如果activity的状态被保存了,这里再提交就会检查这个状态,符合这个条件就抛出一个异常来终止应用进程。也就是说在activity调用了onSaveInstanceState()之后,再commit一个事务就会出现该异常。那如果不想抛出异常,也可以很简单调用commitAllowingStateLoss()方法来略过这个检查就可以了,但是这是危险的,如果activity随后需要从它保存的状态中恢复,这个commit是会丢失的。因此它仅仅适用在ui状态的改变对用户来说是可以接受的,允许丢失一部分状态。

总结

1.在Activity的生命周期方法中提交事务要小心,越早越好,比如onCreate。尽量避免在onActivityResult()方法中提交。
2.避免在异步的回调方法中执行commit,因为他们感知不到当前Activity生命周期的状态。
3.使用commitAllowingStateLoss()代替commit()。相比于异常crash,UI状态的改变对用户来说是可以接受的。
4.如果你需要在Activity执行完onSaveInstanceState()之后还要进行提交,而且不关心恢复时是否会丢失此次提交,那么可以使用commitAllowingStateLoss()或commitNowAllowingStateLoss()。

commitNow以及commitNowAllowingstateLoss()

在API_24版本FragmentTranslation里添加了该两个方法:
该方法不支持加入BackStack回退栈中disallowAddToBackStack()。
官方更推荐使用commitNow()和commitNowAllowingStateLoss()来代替先执行commit()/commitAllowingStateLoss()。我们在解决的时候看看最小支持版本

这个问题也会出现在dialog的show和dismiss中(DialogFragment)

解决,在重写show和dismiss

override fun show(manager: FragmentManager, tag: String?) {
        if (isAdded) {
            return
        }
        try {
            val mDismissed = DialogFragment::class.java.getDeclaredField("mDismissed")
            mDismissed.isAccessible = true
            mDismissed.setBoolean(this, false)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        try {
            val mShownByMe = DialogFragment::class.java.getDeclaredField("mShownByMe")
            mShownByMe.isAccessible = true
            mShownByMe.setBoolean(this, true)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        val ft = manager.beginTransaction()
        ft.add(this, tag)
        ft.commitAllowingStateLoss()
    }

override fun onDismiss(dialog: DialogInterface) {
        super.onDismiss(dialog)
        dismissAllowingStateLoss()
    
上一篇下一篇

猜你喜欢

热点阅读