IllegalStateException onSaveInstanceState显示DialogFragment后无法执行此操作

问题描述

这是我第一次遇到这个问题。

我已经对SO上的几个答案做了很长的研究,尤其是this onethis one,但是它并不能解决我的问题,并且大多数答案都不能用作安全可靠的解决方案。解决案件的有效方法

我已经尝试过:

  • 覆盖onSaveInstanceState,不要调用超级

但是在第一种情况下不能使用commitAllowingStateLoss。

我正在寻找一种解释,该解释如何避免抛出此异常以及如何实现引发异常的操作(在第一种情况下,显示dialogFragment)。我已经知道如何抛出该异常,但是,我不知道在我的情况下会抛出什么异常。 它在我的应用中出现了两次:

一个发生在一个非常简单的活动中,我有一个简单的动画,在此动画的结尾,我显示一个DialogFragment(SplashActivity):

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val potentialLanguage = storage.getString(Constants.LANGUAGE)
    val lang = if (potentialLanguage.isNotEmpty()) {
        potentialLanguage
    } else {
        Locale.getDefault().language
    }
    val language = Language.getFromName(lang)!!
    val dm = res.displayMetrics
    val conf = res.configuration
    conf.setLocale(Locale(language))
    saveLanguage(context,lang)
    // Use conf.locale = new Locale(...) if targeting lower versions
    res.updateConfiguration(conf,dm)
    initWarningDialog()
    RevelyGradient
        .radial()
        .colors(
            intArrayOf(
                getColor(R.color.backgroundOnBoardingStart),getColor(R.color.backgroundOnBoardingEnd)
            )
        )
        .onBackgroundOf(root)
    ivCap.animate()
        .alpha(1f)
        .setListener(object : Animator.AnimatorListener{
            override fun onAnimationEnd(p0: Animator?) {
                try {
                    commonDialog.show(supportFragmentManager,"CommonDialogSplash") //crash here commonDialog is a DialogFragment
                }
                catch (e: IllegalStateException){
                    try {
                        startActivity(Intent(this@SplashActivity,MainActivity::class.java))
                        finish()
                    }
                    catch (e: IllegalStateException){

                    }
                }
            }

            override fun onAnimationCancel(p0: Animator?) {

            }

            override fun onAnimationRepeat(p0: Animator?) {

            }

            override fun onAnimationStart(p0: Animator?) {

            }
        }).duration = 1000
}

private fun initWarningDialog(){
    commonDialog.isCancelable = false
    commonDialog.setTitle(getString(R.string.warning))
    commonDialog.setFirstTextButton(getString(R.string.ok))
    commonDialog.setDescription(getString(R.string.warning_message))
    commonDialog.setFirstButtonListener(object : CommonDialog.CommonDialogClickListener {
        override fun onClick() {
            commonDialog.dismiss()
            startActivity(Intent(this@SplashActivity,MainActivity::class.java))
            finish()
        }
    })
}

第二个是当我尝试在Firebase Firestore请求(TotoFragment)之后添加片段时:

fun pullChallenges(){
        val db = Firebase.firestore
        val docRef = db.collection("challenges").document(language.name.toLowerCase(Locale.ROOT))
        docRef
            .get()
            .addOnSuccessListener { result ->
                result.data?.let {data ->
                    data.values.map {values->
                        val alOfHm = values as ArrayList<HashMap<String,String>>
                        for (item in alOfHm){
                            val challenge = Challenge()
                            Challenge.ChallengeCategory.getValueOf(item["category"]!!)?.let {
                                challenge.challengeCategory = it
                            }
                            Game.GameMode.getValueOf(item["mode"]!!)?.let {
                                challenge.mode = it
                            }
                            challenge.challenge = item["content"]!!
                            challenges.add(challenge)
                        }
                    }
                }
                ChallengesManager.challenges = challenges
                listener.onChallengesReady(true)
            }
            .addOnFailureListener { exception ->
                listener.onChallengesReady(false)
                Timber.e("Error getting challenges $exception")
            }
    }

 override fun onChallengesReady(success: Boolean) {
        renderLoading()
        if (success) {
            try {
                gotochooseMode()
            }
            catch (e: IllegalStateException){

            }
        }
        else {
            Toast.makeText(requireContext(),getString(R.string.error_get_caps),Toast.LENGTH_SHORT).show()
        }
    }

    private fun gotochooseMode(){
            val bundle = Bundle()
            bundle.putStringArrayList(Constants.PLAYERS,ArrayList(viewmodel.players))
            activity.supportFragmentManager
                .beginTransaction()
                .addToBackStack(ChooseModeFragment::class.java.name)
                .setReorderingallowed(true)
                .add(R.id.fragmentContainer,ChooseModeFragment::class.java,bundle,ChooseModeFragment::class.java.name)
                .commit()
        }

了解该问题的任何帮助(针对该问题的想法或解释,或快速修复...)

解决方法

保存状态的目的是使用户可以离开应用程序,然后稍后再返回以找到与离开状态完全相同的状态的应用程序,因此他可以继续进行,就像什么都没有发生一样。在后台,Android可以杀死您的应用以释放资源,但用户不必知道这一点。

Android已经为您完成了很多状态保存工作,例如您添加的片段。抛出IllegalStateException的原因是因为您在保存状态后添加了Fragment,因此无法再次完全恢复其状态。在这两种情况下,您都将启动后台任务,而当您被“回叫”时,用户已经离开了(或者进行了配置更改,例如旋转设备)。

要处理此类情况,您可以:

  1. 使用FragmentTransaction来让您的commitAllowingStateLoss()允许状态丢失。请注意,您可以自己做show(),而不用在DialogFragment上调用FragmentTransaction,因为show()正是这样做的,请参见源代码。
  2. 通过在FragmentTransaction上调用isStateSaved(),在进行FragmentManager之前检查状态是否已经保存。

解决方案2(无状态丢失)要优于解决方案1,但确实需要您在配置更改时两次从FireStore提取数据。一种更现代的方法是使用ViewModel来保存您的数据,因此您只需要提取一次数据(在配置更改时)。这是因为ViewModel的生命周期比Fragment更长(非常方便)。