Fragment的IllegalStateException

一、背景问题:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)

二、问题再现

写了一段可以产生如此错误的程序:

public class MainFragment extends Fragment {
    static final int MSG_SHOW = 100;

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

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.content_main, container, false);
        view.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyHandler handler = new MyHandler(MainFragment.this, MainFragment.this.getFragmentManager());
                handler.sendEmptyMessageDelayed(MSG_SHOW, 2000);
            }
        });
        return view;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.e("TAG", "onSaveInstanceState main");
    }

    static class MyHandler extends Handler {
        private WeakReference<MainFragment> reference;
        private FragmentManager fm;

        public MyHandler(MainFragment reference, FragmentManager fm) {
            this.reference = new WeakReference<MainFragment>(reference);
            this.fm = fm;
        }

        @Override
        public void handleMessage(Message msg) {
            MainFragment fragment = reference.get();
            switch (msg.what) {
                case MSG_SHOW:
                    SecondFragment fr = SecondFragment.newInstance();
                    Log.e("TAG", "new instance");
                    fr.show(fragment.getFragmentManager(), "dialog"); IllegalStateException.
            }
            super.handleMessage(msg);
        }
    }
}

当点击按钮后,点击home键就会产生如上的exception。

三、原因

如错误中显示的那样,fragmentcommit 操作发生在了 onSaveInstanceState 之后。
在应用变为不可见时,安卓系统有可能会为了释放资源而杀死后台应用。为了使应用再返回时能够不影响用户的体验,应用需要在去到后台之前保存自身的一些状态,如EditText中的输入等。这个就是 onSaveInstanceState 函数完成的事情。所以在 onSaveInstanceState 之后,试图commit的话,这个新fragment的状态就无法得到保存,那么就会造成state的丢失,可能会影响用户的体验,所以安卓系统不允许这样的事情发生,就抛出丢失state的异常了!

四、一些知识

在Honeycomb之前和之后的版本中,新加了一个change,即什么时候应用在不可见时可以被kill。在之前的版本中,onPause 之后可以被kill,也就意味着onSaveInstanceState 需在此之前调用。在之后的版本中onStop 之后才能被kill,也就意味着onSaveInstanceState 在此之前调用即可。

在support library中(能够兼容不同版本),由于Honeycomb之前的版本在 onPause 之后就会报错有些太restriction了,于是安卓团队就容许在 onPauseonStop 之间有state loss的发生。也就是当commit在 onPauseonStop 之间,虽然在Honeycomb之前版本会有state loss发生,但是不报错了!
看到这里有点奇怪,为什么讨论commit的时间而不是commit和onSaveInstanceState 的时间了?因为onSaveInstanceState 会在onPause之前调用,原话是immediately before 也就认为commit如果在onPause之前的话就是在onSaveInstanceState 之前了。Honeycomb之后版本是在immediately before onStop 之前调用onSaveInstanceState

Versionpre-Honeycombpost-Honeycomb
commit() before onPause()OKOK
commit() between onPause() and onStop()STATE LOSSOK
commit() after onStop()EXCEPTIONEXCEPTION

五、解决方案

  1. 不要在AsyncTask和Callback中去commit
  2. 用PauseHandler,在onPause时,将message保存起来,在onResume时,再处理message。
    http://stackoverflow.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused
  3. 最后一招,用commitAllowingStateLoss,不过如果程序退出,可能会有nullpointerexception。最好不用。

六、一些还没想透的问题

  1. 会用callback的情况大概可以想象,比如dialog在中显示progress。但一般什么时候会在onResume的时候commit?
  2. How to handle AsyncTask onPostExecute when paused to avoid IllegalStateException。
  3. 如下

Be careful when committing transactions inside Activity lifecycle methods. A large majority of applications will only ever commit transactions the very first time onCreate() is called and/or in response to user input, and will never face any problems as a result. However, as your transactions begin to venture out into the other Activity lifecycle methods, such as onActivityResult(), onStart(), and onResume(), things can get a little tricky. For example, you should not commit transactions inside the FragmentActivity#onResume() method, as there are some cases in which the method can be called before the activity’s state has been restored (see the documentation for more information). If your application requires committing a transaction in an Activity lifecycle method other than onCreate(), do it in either FragmentActivity#onResumeFragments() or Activity#onPostResume(). These two methods are guaranteed to be called after the Activity has been restored to its original state, and therefore avoid the possibility of state loss all together. (As an example of how this can be done, check out my answer to this StackOverflow question for some ideas on how to commit FragmentTransactions in response to calls made to the Activity#onActivityResult() method).

七、参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值