getting exception “IllegalStateException: Can not perform this action after onSaveInstanceState”

前端 未结 30 2039
迷失自我
迷失自我 2020-11-22 05:12

I have a Live Android application, and from market i have received following stack trace and i have no idea why its happening as its not happening in application code but it

相关标签:
30条回答
  • 2020-11-22 05:53

    I was always getting this when I tried to show fragment in onActivityForResult() method, so the problem was next:

    1. My Activity is paused and stopped, which means, that onSaveInstanceState() was called already (for both pre-Honeycomb and post-Honeycomb devices).
    2. In case of any result I made transaction to show/hide fragment, which causes this IllegalStateException.

    What I made is next:

    1. Added value for determining if action I want was done (e.g. taking photo from camere - isPhotoTaken) - it can be boolean or integer value depending how much different transactions you need.
    2. In overriden onResumeFragments() method I checked for my value and after made fragment transactions I needed. In this case commit() was not done after onSaveInstanceState, as state was returned in onResumeFragments() method.
    0 讨论(0)
  • 2020-11-22 05:53

    This happens whenever you are trying to load a fragment but the activity has changed its state to onPause().This happens for example when you try to fetch data and load it to the activity but by the time the user has clicked some button and has moved to next activity.

    You can solve this in two ways

    You can use transaction.commitAllowingStateLoss() instead of transaction.commit() to load fragment but you may end up losing commit operation that is done.

    or

    Make sure that activity is in resume and not going to pause state when loading a fragment. Create a boolean and check if activity is not going to onPause() state.

    @Override
    public void onResume() {
        super.onResume();
        mIsResumed = true;
    }
    
    @Override
    public void onPause() {
        mIsResumed = false;
        super.onPause();
    }
    

    then while loading fragment check if activity is present and load only when activity is foreground.

    if(mIsResumed){
     //load the fragment
    }
    
    0 讨论(0)
  • 2020-11-22 05:55

    Well, after trying all the above solutions without success (because basically i dont have transactions).

    On my case i was using AlertDialogs and ProgressDialog as fragments that, sometimes, on rotation, when asking for the FragmentManager, the error rises.

    I found a workaround mixing some many similar posts:

    Its a 3 step solution, all done on your FragmentActivity (in this case, its called GenericActivity):

    private static WeakReference<GenericActivity> activity = null; //To avoid bug for fragments: Step 1 of 3
    
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        //To avoid bug for fragments: Step 2 of 3
        activity = new WeakReference<GenericActivity>(this);
    }
    
    @Override
    public FragmentManager getSupportFragmentManager(){
        //To avoid bug for fragments: Step 3 of 3
        if (this == activity.get()) {
            return super.getSupportFragmentManager();
        }
        return activity.get().getSupportFragmentManager();
    }
    
    0 讨论(0)
  • 2020-11-22 05:56

    Looking in Android source code on what causes this issue gives that flag mStateSaved in FragmentManagerImpl class (instance available in Activity) has value true. It is set to true when the back stack is saved (saveAllState) on call from Activity#onSaveInstanceState. Afterwards the calls from ActivityThread don't reset this flag using available reset methods from FragmentManagerImpl#noteStateNotSaved() and dispatch().

    The way I see it there are some available fixes, depending on what your app is doing and using:

    Good ways

    Before anything else: I would advertise Alex Lockwood article. Then, from what I've done so far:

    1. For fragments and activities that don't need to keep any state information, call commitAllowStateLoss. Taken from documentation:

      Allows the commit to be executed after an activity's state is saved. This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user`. I guess this is alright to use if the fragment is showing read-only information. Or even if they do show editable info, use the callbacks methods to retain the edited info.

    2. Just after the transaction is commit (you just called commit()), make a call to FragmentManager.executePendingTransactions().

    Not recommended ways:

    1. As Ovidiu Latcu mentioned above, don't call super.onSaveInstanceState(). But this means you will lose the whole state of your activity along with fragments state.

    2. Override onBackPressed and in there call only finish(). This should be OK if you application doesn't use Fragments API; as in super.onBackPressed there is a call to FragmentManager#popBackStackImmediate().

    3. If you are using both Fragments API and the state of your activity is important/vital, then you could try to call using reflection API FragmentManagerImpl#noteStateNotSaved(). But this is a hack, or one could say it's a workaround. I don't like it, but in my case it's quite acceptable since I have a code from a legacy app that uses deprecated code (TabActivity and implicitly LocalActivityManager).

    Below 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!

    0 讨论(0)
  • 2020-11-22 05:57

    I have got the same issue in my App. I have been solved this issue just calling the super.onBackPressed(); on previous class and calling the commitAllowingStateLoss() on the current class with that fragment.

    0 讨论(0)
  • 2020-11-22 05:58

    this worked for me... found this out on my own... hope it helps you!

    1) do NOT have a global "static" FragmentManager / FragmentTransaction.

    2) onCreate, ALWAYS initialize the FragmentManager again!

    sample below :-

    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();
        }
    }
    
    0 讨论(0)
提交回复
热议问题