Getting exception 'IllegalStateException: cannot perform this operation after onSaveInstanceState'

Posted by weemee500 on Sun, 01 Mar 2020 05:05:55 +0100

I have a Live Android application. I have received the following stack trace information from the market. I don't know why it happened in the application code instead of happening, but caused by some or other events in the application (assumed)

I don't use Fragments, but I still have a reference to the fragment manager. If someone can understand some hidden facts to avoid such problems:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyDown(Activity.java:1962)
at android.view.KeyEvent.dispatch(KeyEvent.java:2482)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1720)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1258)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2851)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2824)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2011)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)  

#1 building

It's the stupidest mistake I've ever had. I have a Fragment application for API < 11 and Force Closing on API > 11.

I really don't know what happens to saveinstances in the Activity life cycle when they are called, but I solve this problem here:

@Override
protected void onSaveInstanceState(Bundle outState) {
    //No call for super(). Bug on API Level > 11.
}

I just don't call. super() and everything is fine. I hope this will save you some time.

Editor: after more research, this is known in the support package error .

If you need to save the instance and add content to the outState Bundle, you can use the following command:

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

EDIT2: this can also happen if you attempt to execute a transaction after you have an Activity in the background. To avoid this, you should use commitAllowingStateLoss()

EDIT3: from what I remember, the above solution is solving problems in the early support.v4 library. However, if you still have Doubt, Must also read @AlexLockwood Blog: Fragmented transactions and active state lost

A summary of the blog post (but I highly recommend that you read it):

  • Never trade after onPause() and onStop() in front of the hive
  • Be careful when committing transactions within an Activity lifecycle method. Use onCreate(), onResumeFragments() and onPostResume()
  • Avoid executing transactions in asynchronous callback methods
  • commitAllowingStateLoss() can only be used as a last resort

#2 building

I solved this problem through onconfigurationchanged. The trick is when you explicitly call intent (camera intent or any other) based on the lifecycle of an android activity; the activity is paused, in which case onsavedInstance is called. When rotating the device to a location other than during the activity, fragment operations such as fragment submission will cause illegal status exceptions. There are many complaints about it. This is related to android activity lifecycle management and correct method calls. In order to solve this problem, I did the following: 1. Rewrite the onsavedInstance method of the activity, determine the current screen direction (portrait or landscape), and then set the screen direction for it before pausing the activity. This way, if your activity is rotated by another, you can lock the rotation of that screen. 2 - then, override the active onresume method, now set the orientation mode to sensor so that after calling the onsaved method, it will call the onconfiguration again to properly handle the rotation.

You can copy / paste this code into your activity for processing:

@Override
protected void onSaveInstanceState(Bundle outState) {       
    super.onSaveInstanceState(outState);

    Toast.makeText(this, "Activity OnResume(): Lock Screen Orientation ", Toast.LENGTH_LONG).show();
    int orientation =this.getDisplayOrientation();
    //Lock the screen orientation to the current display orientation : Landscape or Potrait
    this.setRequestedOrientation(orientation);
}

//A method found in stackOverflow, don't remember the author, to determine the right screen orientation independently of the phone or tablet device 
public int getDisplayOrientation() {
    Display getOrient = getWindowManager().getDefaultDisplay();

    int orientation = getOrient.getOrientation();

    // Sometimes you may get undefined orientation Value is 0
    // simple logic solves the problem compare the screen
    // X,Y Co-ordinates and determine the Orientation in such cases
    if (orientation == Configuration.ORIENTATION_UNDEFINED) {
        Configuration config = getResources().getConfiguration();
        orientation = config.orientation;

        if (orientation == Configuration.ORIENTATION_UNDEFINED) {
        // if height and widht of screen are equal then
        // it is square orientation
            if (getOrient.getWidth() == getOrient.getHeight()) {
                orientation = Configuration.ORIENTATION_SQUARE;
            } else { //if widht is less than height than it is portrait
                if (getOrient.getWidth() < getOrient.getHeight()) {
                    orientation = Configuration.ORIENTATION_PORTRAIT;
                } else { // if it is not any of the above it will defineitly be landscape
                    orientation = Configuration.ORIENTATION_LANDSCAPE;
                }
            }
        }
    }
    return orientation; // return value 1 is portrait and 2 is Landscape Mode
}

@Override
public void onResume() {
    super.onResume();
    Toast.makeText(this, "Activity OnResume(): Unlock Screen Orientation ", Toast.LENGTH_LONG).show();
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
} 

#3 building

Looking at the Android source code that caused the problem, you can see that the value of the flag mStateSaved in the FragmentManagerImpl class (the instance available in the activity) is true. Set it to true when saving the Backstack (saveAllState) from the activity ා onsaveinstancestate call. After that, calls from ActivityThread do not reset this flag using the reset methods available for FragmentManagerImpl ා notestatenotsaved() and dispatch().

In my opinion, there are some fixes available, depending on what your application is doing and using:

Good method

Before that: I will Alex Lockwood's article Advertising. Then, from what I've done so far:

  1. For fragments and activities that do not need to retain any state information, call commitAllowStateLoss . From document:

    Allows a commit to be performed after the activity state has been saved. This is dangerous because if you need to restore the activity from its state later, the commit may be lost, so it should only be used if the UI state can change unexpectedly on the user. I think it can be used if the clip shows read-only information. Or even if they do display editable information, you can use callback methods to retain the edited information.

  2. After the transaction is committed (you just called commit() ), call now FragmentManager.executePendingTransactions() .

Not recommended:

  1. Do not call super.onSaveInstanceState(), as described in Ovidiu Latcu above. But this means that you will lose the entire state of the activity as well as the fragmented state.

  2. Override onBackPressed, where only finish() is called. If your application does not use the Fragments API, there should be no problem; like super.onBackPressed, there is a call to fragmentmanager ා popbackstackimmediate().

  3. If you are using the Fragments API at the same time, and the activity state is important / important, you can try to use the reflection API fragment manager impl ා notestatenotsaved() to call. But this is a hacker, or it can be said that this is a solution. I don't like it, but as far as I'm concerned, it's acceptable because I have a code from an older application that uses deprecated code (TabActivity and implicit localactivity manager).

Here is the code that uses reflection:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    invokeFragmentManagerNoteStateNotSaved();
}

@SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeFragmentManagerNoteStateNotSaved() {
    /**
     * For post-Honeycomb devices
     */
    if (Build.VERSION.SDK_INT < 11) {
        return;
    }
    try {
        Class cls = getClass();
        do {
            cls = cls.getSuperclass();
        } while (!"Activity".equals(cls.getSimpleName()));
        Field fragmentMgrField = cls.getDeclaredField("mFragments");
        fragmentMgrField.setAccessible(true);

        Object fragmentMgr = fragmentMgrField.get(this);
        cls = fragmentMgr.getClass();

        Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {});
        noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {});
        Log.d("DLOutState", "Successful call for noteStateNotSaved!!!");
    } catch (Exception ex) {
        Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex);
    }
}

Cheers!

#4 building

Read http://chris-alexander.co.uk/on-engineering/dev/android-fragments-within-fragments/

Article. The fragment.isResumed() check helps me implement onDestroyView using the onSaveInstanceState method.

#5 building

This is useful to me... I found it myself... I hope it can help you!

1) There is no global "static" FragmentManager / FragmentTransaction.

2) onCreate, always initialize FragmentManager again!

Here is an example:

public abstract class FragmentController extends AnotherActivity{
protected FragmentManager fragmentManager;
protected FragmentTransaction fragmentTransaction;
protected Bundle mSavedInstanceState;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mSavedInstanceState = savedInstanceState;
    setDefaultFragments();
}

protected void setDefaultFragments() {
    fragmentManager = getSupportFragmentManager();
    //check if on orientation change.. do not re-add fragments!
    if(mSavedInstanceState == null) {
        //instantiate the fragment manager

        fragmentTransaction = fragmentManager.beginTransaction();

        //the navigation fragments
        NavigationFragment navFrag = new NavigationFragment();
        ToolbarFragment toolFrag = new ToolbarFragment();

        fragmentTransaction.add(R.id.NavLayout, navFrag, "NavFrag");
        fragmentTransaction.add(R.id.ToolbarLayout, toolFrag, "ToolFrag");
        fragmentTransaction.commitAllowingStateLoss();

        //add own fragment to the nav (abstract method)
        setOwnFragment();
    }
}

Topics: Android Java Fragment hive