在onSaveInstanceState WHEN提交后无法执行此操作

时间:2015-09-25 11:55:57

标签: android

我在ft.commit()时遇到异常,我不知道为什么。

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
           at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1448)
           at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1466)
           at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:634)
           at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:613)
           at MainActivity.attachFragment(MainActivity.java:242)
           at MainActivity.attachFragment(MainActivity.java:225)
           at MainActivity.showHome(MainActivity.java:171)
           at MainActivity.onComplete(MainActivity.java:278)
           at MDownloadManager.onDownloadComplete(MDownloadManager.java:83)
           at DownloadRequestQueue$CallBackDelivery$2.run(DownloadRequestQueue.java:61)
           at android.os.Handler.handleCallback(Handler.java:733)
           at android.os.Handler.dispatchMessage(Handler.java:95)
           at android.os.Looper.loop(Looper.java:149)
           at android.app.ActivityThread.main(ActivityThread.java:5257)
           at java.lang.reflect.Method.invokeNative(Method.java)
           at java.lang.reflect.Method.invoke(Method.java:515)
           at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
           at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:609)
           at dalvik.system.NativeStart.main(NativeStart.java)

这是崩溃即将来临的方法。

FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();

        if(addToBackStack) {
            ft.addToBackStack(null);
            ft.add(R.id.frame_container, fragment, tag);
        } else {
            ft.replace(R.id.frame_container, fragment, tag);
        }

        ft.commit();

你知道出了什么问题吗? 我不在项目中使用onSaveInstanceState。

7 个答案:

答案 0 :(得分:5)

完整的解决方案,位于Solution for IllegalStateException

重写onSaveInstanceSate是一种黑客,不一定对所有场景都有效。另外,使用commitAllowingStateLoss()也是危险的,并且可能导致UI异常。

我们需要了解,当我们在丢失活动状态后尝试提交片段时会遇到IllegalStateException-活动不在前台(以了解有关活动状态read this的更多信息)。因此,为避免(解决)此异常,我们只是将片段事务延迟到恢复状态为止

声明两个私有布尔变量

public class MainActivity extends AppCompatActivity {

    //Boolean variable to mark if the transaction is safe
    private boolean isTransactionSafe;

    //Boolean variable to mark if there is any transaction pending
    private boolean isTransactionPending;

现在在onPostResume()和onPause中,我们设置和取消设置布尔变量isTransactionSafe。想法是仅在活动处于前台时才将事务标记为安全,这样就不会造成状态损失。

/*
onPostResume is called only when the activity's state is completely restored. In this we will
set our boolean variable to true. Indicating that transaction is safe now
 */
public void onPostResume(){
    super.onPostResume();
    isTransactionSafe=true;
}
/*
onPause is called just before the activity moves to background and also before onSaveInstanceState. In this
we will mark the transaction as unsafe
 */

public void onPause(){
    super.onPause();
    isTransactionSafe=false;

}

private void commitFragment(){
    if(isTransactionSafe) {
        MyFragment myFragment = new MyFragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.frame, myFragment);
        fragmentTransaction.commit();
    }
}

到目前为止,我们所做的一切都可以从IllegalStateException中保存,但是如果在活动移至后台之后完成交易,我们的交易将丢失,就像commitAllowStateloss()一样。为此,我们提供了isTransactionPending布尔变量

public void onPostResume(){
   super.onPostResume();
   isTransactionSafe=true;
/* Here after the activity is restored we check if there is any transaction pending from
the last restoration
*/
   if (isTransactionPending) {
      commitFragment();
   }
}


private void commitFragment(){

 if(isTransactionSafe) {
     MyFragment myFragment = new MyFragment();
     FragmentManager fragmentManager = getFragmentManager();
     FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
     fragmentTransaction.add(R.id.frame, myFragment);
     fragmentTransaction.commit();
     isTransactionPending=false;
 }else {
     /*
     If any transaction is not done because the activity is in background. We set the
     isTransactionPending variable to true so that we can pick this up when we come back to
foreground
     */
     isTransactionPending=true;
 }
}

答案 1 :(得分:1)

onSaveInstanceState方法是活动生命周期的一部分。因此,即使您没有明确地调用它,也可以通过您的活动来调用它。

所以问题是你在活动生命周期中使用了你向我们展示的代码? 一种解决方法是使用commitAllowingStateLoss而不是提交片段事务。

(您应该阅读链接中的说明,看看您是否可以使用此方法)

答案 2 :(得分:1)

我遇到了同样的问题,但是我能够通过覆盖onSaveInstanceState来解决这个问题,并在片段中注释调用它的super这样的行。

@Override
public void onSaveInstanceState(Bundle outState) {
 //   super.onSaveInstanceState(outState);
}

希望有所帮助。

答案 3 :(得分:1)

这是使用 Kotlin 的更新解决方案。有关完整的详细信息,请查看本文:Avoid Fragment IllegalStateException: Can not perform this action after onSaveInstanceState

class MainActivity : AppCompatActivity(){
private var isActivityResumed = false
private var lastCall: (() -> Unit)? = null

companion object {
    private const val ROOT_FRAGMENT = "root"
}
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    //Call some expensive async operation that will result in onRequestCallback below
    myExpensiveAsyncOperation()
}
override fun onPause() {
    super.onPause()
    //Very important flag
    isActivityResumed = false
}
override fun onResume() {
    super.onResume()
    isActivityResumed = true
    //If we have some fragment to show do it now then clear the queue           
    if(lastCall != null){
        updateView(lastCall!!)
        lastCall = null
    }
}
/**
 * Fragment Management
 */
private val fragmentA : () -> Unit = {
    supportFragmentManager.beginTransaction()
        .replace(R.id.fragmentContainer_fl, FragmentA())
        .addToBackStack(ROOT_FRAGMENT)
        .commit()
}

private val fragmentB : () -> Unit = {
    supportFragmentManager.beginTransaction()
        .replace(R.id.fragmentContainer_fl, FragmentB())
        .addToBackStack(ROOT_FRAGMENT)
        .commit()
}

private val popToRoot : () -> Unit = { supportFragmentManager.popBackStack(ROOT_FRAGMENT,0) }
// The function responsible for all our transactions
private fun updateView(action: () -> Unit){
    //If the activity is in background we register the transaction
    if(!isActivityResumed){
        lastCall = action
    } else {
    //Else we just invoke it
        action.invoke()
    }
}
// Just an example
private fun onRequestCallback() {
    if(something) {
        updateView(fragmentA)
    else {
        updateView(fragmentB)
    }
}

答案 4 :(得分:0)

这非常简单,在调用onSaveInstanceState(Bundle outState)后,您无法在活动中提交片段事务。您的代码何时被调用?

发生配置更改时,

onSavedInstanceState将作为活动生命周期的一部分进行调用。你无法控制它。

答案 5 :(得分:0)

  

希望这会有所帮助

EDIT1:经过一些研究后,这是支持包中已知的bug

如果您需要保存实例,并向outState Bundle添加内容,则可以使用以下内容:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
    super.onSaveInstanceState(outState);
}

EDIT2:如果您在后台Activity消失后尝试执行交易,也可能会发生这种情况。为避免这种情况,您应该使用commitAllowingStateLoss()

EDIT3:上述解决方案正在修复早期的support.v4库中的问题。但是,如果您仍然遇到此问题,必须还请阅读@AlexLockwood的博客:Fragment Transactions & Activity State Loss

博客文章摘要(但我强烈建议您阅读):

  • 从事<{1}}之前的事件<{1}}前蜂窝事件,commit()事后蜂窝事件
  • onPause()生命周期方法中提交事务时要小心。 使用 onStop()ActivityonCreate()
  • 避免在异步回调方法中执行事务
  • 仅将onResumeFragments()作为最后手段使用

答案 6 :(得分:0)

如果您在项目中使用协程,您可以轻松确保您的代码在生命周期状态至少为 Started 且未销毁时运行。

lifecycleScope.launchWhenStarted{}