问题描述
这是我第一次遇到这个问题。
我已经对SO上的几个答案做了很长的研究,尤其是this one和this 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
,因此无法再次完全恢复其状态。在这两种情况下,您都将启动后台任务,而当您被“回叫”时,用户已经离开了(或者进行了配置更改,例如旋转设备)。
要处理此类情况,您可以:
- 使用
FragmentTransaction
来让您的commitAllowingStateLoss()
允许状态丢失。请注意,您可以自己做show()
,而不用在DialogFragment
上调用FragmentTransaction
,因为show()
正是这样做的,请参见源代码。 - 通过在
FragmentTransaction
上调用isStateSaved()
,在进行FragmentManager
之前检查状态是否已经保存。
解决方案2(无状态丢失)要优于解决方案1,但确实需要您在配置更改时两次从FireStore提取数据。一种更现代的方法是使用ViewModel来保存您的数据,因此您只需要提取一次数据(在配置更改时)。这是因为ViewModel
的生命周期比Fragment
更长(非常方便)。