popBackStack 导致 java.lang.IllegalStateException: Can't perform this action after onSaveInstanceState

Posted

技术标签:

【中文标题】popBackStack 导致 java.lang.IllegalStateException: Can\'t perform this action after onSaveInstanceState【英文标题】:popBackStack causing java.lang.IllegalStateException: Can not perform this action after onSaveInstanceStatepopBackStack 导致 java.lang.IllegalStateException: Can't perform this action after onSaveInstanceState 【发布时间】:2019-05-03 03:48:48 【问题描述】:

我在 manager.popBackStack 行收到此错误。有没有解决的办法?它发生了很多。

public void updateView(Fragment fragment) 

        IFragment currentFragment = (IFragment)fragment;

        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = manager.beginTransaction();
        fragmentTransaction.replace(R.id.content_frame, fragment);

        if(currentFragment != null)
        
            if(currentFragment.isRoot())
            
                manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
            
            else
            
                fragmentTransaction.addToBackStack("test");
            
        

        fragmentTransaction.commitAllowingStateLoss();

        if(drawerManager.DrawerLayout != null) 
            drawerManager.DrawerLayout.closeDrawer(drawerManager.DrawerList);
        
    

致命异常:java.lang.IllegalStateException:无法执行此操作 onSaveInstanceState 之后的操作 在 android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2044) 在 android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2067) 在 android.support.v4.app.FragmentManagerImpl.popBackStack(FragmentManager.java:799) 在 com.exposure.activities.BaseActivity.updateView(BaseActivity.java:239) 在 com.exposure.activities.events.EventActivity.setupEvent(EventActivity.java:204) 在 com.exposure.activities.events.EventActivity.getData(EventActivity.java:117) 在 com.exposure.utilities.ActivityContainer.getData(ActivityContainer.java:83) 在 com.exposure.utilities.DataTask.onPostExecute(DataTask.java:37) 在 android.os.AsyncTask.finish(AsyncTask.java:695) 在 android.os.AsyncTask.-wrap1(未知来源) 在 android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:712) 在 android.os.Handler.dispatchMessage(Handler.java:105) 在 android.os.Looper.loop(Looper.java:164) 在 android.app.ActivityThread.main(ActivityThread.java:6938) 在 java.lang.reflect.Method.invoke(Method.java) 在 com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)

【问题讨论】:

您正在泄漏一个异步任务,它可能会在包含活动已完成或暂停时完成。 有什么解决办法或者我应该做的吗? 看看Fragment Transactions & Activity State Loss。这很可能是问题所在。 我确实看过那个帖子,但我真的没有答案。我不会忽视状态损失。 如果你查看堆栈跟踪@Cheticamp,它来自我的异步调用 【参考方案1】:

正如Fragment Transactions & Activity State Loss 在项目符号“避免在异步回调方法中执行事务”中解释的那样

避免在异步回调方法中执行事务。 这包括常用的方法,例如AsyncTask#onPostExecute()LoaderManager.LoaderCallbacks#onLoadFinished()。在这些方法中执行事务的问题在于,它们在被调用时不知道 Activity 生命周期的当前状态。

这似乎是您遇到的问题,因为 updateView() 是从异步任务中调用的,但让我们测试一下这个假设。

以下演示应用创建一个片段,模拟后台处理和一个模拟异步回调的回调。代码中有一个标志,mFixIt 设置为 true 时会导致应用程序正常运行(不会崩溃),而设置为 false 时会导致应用程序失败。

使用 mFixIt == 错误。触发器是导致应用进入停止状态的主页按钮:

这是堆栈跟踪:

14967-15003 E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.example.illegalstatepopbackstack, PID: 14967
    java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
        at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2080)
        at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2106)
        at android.support.v4.app.FragmentManagerImpl.popBackStack(FragmentManager.java:832)
        at com.example.illegalstatepopbackstack.MainActivity.updateView(MainActivity.java:70)
        at com.example.illegalstatepopbackstack.ui.main.MainFragment$1.run(MainFragment.java:63)
        at java.lang.Thread.run(Thread.java:764)

现在mFixIt == true。这次的不同之处在于,当 Activity 处于停止状态时,应用程序会识别异步回调,并记录这已经发生,并在应用程序重新启动时完成处理。视觉只是简单地按下主页按钮并从“最近”恢复。该应用程序只是在开始时放置片段,然后在重新启动时更改顶部的 TextView 文本并从后台弹出片段。

可以看出,处理按预期完成。

这是一个简单的例子。如果您的处理涉及更多,或者您只是想要一种更正式的方式来处理这种情况,我建议您查看this solution。

这是演示应用的代码:

MainActivity.java

public class MainActivity extends AppCompatActivity 
    // Set to true to fix the problem; false will cause the IllegalStateException
    private boolean mFixIt = false;

    private MainFragment mFragment;
    private TextView mTextView;
    private boolean mIsPaused;
    private boolean mUpdateViewNeeded;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);
        mTextView = findViewById(R.id.textView);

        FragmentManager fm = getSupportFragmentManager();
        if (savedInstanceState == null) 
            // Create out fragment
            mFragment = MainFragment.newInstance();
            fm.beginTransaction()
                .replace(R.id.container, mFragment)
                .addToBackStack(FRAGMENT_TAG)
                .commit();
         else 
            // Find the restored fragment.
            mFragment = (MainFragment) fm.findFragmentByTag(FRAGMENT_TAG);
        
    

    @Override
    protected void onStop() 
        super.onStop();

        // Simulate a background task that does something useful. This one just waits a few
        // second then does a callback to updateView(). The activity will be fully paused by then.
        mFragment.doSomethingInBackground();
        mIsPaused = true;
        Log.d("MainActivity","<<<< stopped");
    

    @Override
    protected void onStart() 
        super.onStart();
        mIsPaused = false;
        if (mUpdateViewNeeded) 
            // Execute delayed processing now that the activity is resumed.
            updateView(getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG));
        
    

    public void updateView(Fragment fragment) 
        if (mIsPaused && mFixIt) 
            // Delay processing
            mUpdateViewNeeded = true;
         else 
            // Do out update work. If we are paused, this will get an IllegalStateException. If
            // we are resumed, this will work as intended.
            mTextView.setText("Replaced...");
            getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
            mUpdateViewNeeded = false;
        
    

    public static final String FRAGMENT_TAG = "MyFragment";

MainFragment.java

public class MainFragment extends Fragment 

    MainActivity mMainActivity;

    public static MainFragment newInstance() 
        return new MainFragment();
    

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) 
        return inflater.inflate(R.layout.main_fragment, container, false);
    

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) 
        super.onActivityCreated(savedInstanceState);
    

    @Override
    public void onAttach(Context context) 
        super.onAttach(context);
        mMainActivity = (MainActivity) context;
    

    @Override
    public void onDetach() 
        super.onDetach();
        mMainActivity = null;
    

    @Override
    public void onStart() 
        super.onStart();

    

    public void doSomethingInBackground() 
        new Thread(new Runnable() 
            @Override
            public void run() 
                try 
                    Thread.sleep(2000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                if (mMainActivity != null) 
                    mMainActivity.updateView(MainFragment.this);
                
            
        ).start();
    

ma​​in_activity.xml

<TextView
    android:id="@+id/textView"
    android:layout_
    android:layout_
    android:background="@android:color/holo_blue_light"
    android:gravity="center"
    android:text="To be replaced..."
    android:textSize="36sp"
    android:textStyle="bold" />

<FrameLayout
    android:id="@+id/container"
    android:layout_
    android:layout_
    tools:context=".MainActivity" />

ma​​in_fragment.xml

<android.support.constraint.ConstraintLayout 
    android:id="@+id/main"
    android:layout_
    android:layout_
    android:background="@android:color/holo_green_light"
    tools:context=".ui.main.MainFragment">

    <TextView
        android:id="@+id/message"
        android:layout_
        android:layout_
        android:text="MainFragment"
        android:textSize="36sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

【讨论】:

我基本上做了你所做的,在 onStop 或 onPause 上加了一点,如果暂停则存储 Fragment 以便 onStart 再次运行。【参考方案2】:

这对我有用的是检查片段管理器是否在 popBackStack() 之前没有保存状态

if (fragmentManager != null && !fragmentManager.isStateSaved()) fragmentManager.popBackStack();

【讨论】:

【参考方案3】:

只需调用 popBackStackImmediate(),因为常规 popBackStack() 是异步的...

【讨论】:

【参考方案4】:

问题基本上是:

IllegalStateException: onSaveInstanceState 之后无法执行此操作。

因此,请与!isFinishing() &amp;&amp; !isDestroyed() 联系.updateView()

因为这会在onSaveInstanceState() 上返回true ...

同样可以缓解这种情况并防止崩溃。

【讨论】:

如果它没有完成它到底会做什么,什么都没有?我要重新运行一些东西吗? @MikeFlynn 当BaseActivity! isFinishing() 时,代码应该运行 - 否则不运行,因为它在不应该运行并因此崩溃的情况下运行。显式调用finish() 时未调用onSaveInstanceState(),应在此处考虑;但是 isFinishing() 仍然应该是正确的 - 然后更新视图是没有意义的。 没用,把它包装在语句中,仍然绕过它 @MikeFlynn 它可能已经是isDestroyed();这就是接下来的状态。您可以自己在onSaveInstanceState() 上设置boolean,然后进行检查;万一它仍然不起作用。错误消息清楚地暗示,在该方法之后,该操作是在非法状态下执行的。 使用这个解决方案可以避免致命异常,但是,我得到一个 W/System.err: java.lang.NullPointerException: Attempt to invoke virtual method 'int android.view.View.getVisibility( )' 在空对象引用上。

以上是关于popBackStack 导致 java.lang.IllegalStateException: Can't perform this action after onSaveInstanceState的主要内容,如果未能解决你的问题,请参考以下文章

popBackStack导致一次又一次调用片段的oncreateView

Android FragmentTabHost popBackStack() 导致 IllegalStateException Fragment 已添加

是啥导致“java.lang.IllegalArgumentException:无法设置 java.lang.Integer 字段”

是啥导致 java.lang.***Error

为啥popbackstack会调用fragment的onCreateView?

是啥导致 java.lang.ArrayIndexOutOfBoundsException 以及如何防止它?