Can not perform this action after onSaveInstanceState 问题

1,369 阅读2分钟

完美解决DialogFragment Can not perform this action after onSaveInstanceState 问题

问题解析

参考一下掘金一位大佬的解释让你不再俱怕 Fragment State Loss

简单的来说,就是dialogFragment所依赖的Fragment/FragmentActivity,
onSaveInstance之后,会调用dialogFragment的saveAllState,
使得fragmennt的成员变量mStateSaved=true。
在这之后 dialogFragment的FragmentTransaction执行了commit()
而commit操作过程中会进行***checkStateLoss()***校验,
如果mStateSaved || mStopped为true,就会抛出异常
throw new IllegalStateException("Can not perform this action after onSaveInstanceState");

在什么时机执行commit操作合适?

我们再来参考一篇文章
Fragment Transactions & Activity State Loss 我们来看一下表格

  1. 当Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
    在onStop()之后 commit会产生异常
  2. 当Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
    在onPause()和onStop()之间会loss state,在onStop()之后 commit会产生异常

那么我们只需要 让dialogFragment的FragmentTransaction的commit的执行时机发生在onResume-onPause之间就OK,即 dialog的show和dismiss在onResume-onPause,问题就迎刃而解了

解决方案

  1. 重写activity/fragment 的生命周期方法,添加一个成员变量 isStateSaved
    在每次show和dismiss时 进行isStateSaved判断
    就像让你不再俱怕 Fragment State Loss一样。
    问题:当activity/fragment不在前台时 dialog弹出时机有误,那么此次dialog弹出将会丢失。
    如:网络请求结果 或其他耗时操作的结果中 需要弹窗,在耗时操作期间,用户回到了home或前往其他activity时,这个弹窗事件将会丢失,需要再次处理
  2. 我们可以利用LiveData的特性来解决这个问题
    MVVM学习之LivaData

    liveData的特点
    1、感知对应Activity的生命周期,
    2、只有生命周期处于onStart与onResume时,LiveData处于活动状态,才会把更新的数据通知至对应的Activity
    3、当生命周期处于onStop或者onPause时,不回调数据更新,直至生命周期为onResume时,立即回调
    4、当生命周期处于onDestory时,观察者会自动删除

实现

//ViewModel
public class BaseDialogViewModel extends AndroidViewModel {
    private MutableLiveData<Boolean> isDialogShow;
    public BaseDialogViewModel(@NonNull Application application) {
        super(application);
        isDialogShow=new MutableLiveData<>();
    }

    public MutableLiveData<Boolean> getDialogLiveData() {
        return isDialogShow;
    }
}
//简易版的dialogFragment
public abstract class BaseDialogFragment extends DialogFragment {

    private BaseDialogViewModel baseDialogViewModel;
    private FragmentManager fragmentManager;

  
    public BaseDialogFragment(FragmentActivity fragmentActivity) {
        baseDialogViewModel = new ViewModelProvider(fragmentActivity)
                .get(BaseDialogViewModel.class);
        fragmentManager = fragmentActivity.getSupportFragmentManager();
        //初始化value
        baseDialogViewModel.getDialogLiveData().setValue(null);
        //添加观察
        baseDialogViewModel.getDialogLiveData().observe(fragmentActivity, isShow -> {
            LogUtil.log(" getDialogLiveData= " + isShow + "    " + this.toString());
            if (isShow == null) {
                return;
            }
            if (isShow) {
                show(fragmentManager, getFragmentTag());
            } else {
                //dismiss前 取消观察
                baseDialogViewModel.getDialogLiveData().removeObservers(fragmentActivity);
                dismiss();
            }
        });
    }

    public BaseDialogFragment(Fragment fragment) {
        baseDialogViewModel = new ViewModelProvider(fragment)
                .get(BaseDialogViewModel.class);
        fragmentManager = fragment.getChildFragmentManager();
        baseDialogViewModel.getDialogLiveData().setValue(null);
        baseDialogViewModel.getDialogLiveData().observe(fragment, isShow -> {
            LogUtil.log(" getDialogLiveData= " + isShow + "    " + this.toString());
            if (isShow == null) {
                return;
            }
            if (isShow) {
                show(fragmentManager, getFragmentTag());
            } else {
                baseDialogViewModel.getDialogLiveData().removeObservers(fragment);
                dismiss();
            }
        });
    }

    @Override
    public View onCreateView(@NotNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
  
        View v = inflater.inflate(getLayoutRes(), container, false);
        bindView(v);
        return v;
    }
    //showDialog和dismissDialog不在显示调用
    //通过post liveData value
    //利用liveData的特点,规避什么周期问题(即saveInstance时saveState变更)
    //同时能够在条件不符时,保留数据,直至onResume发送数据
    //或onDestory销毁
    public void showDialog() {
        LogUtil.log("showDialog");
        if (baseDialogViewModel != null) {
            baseDialogViewModel.getDialogLiveData().postValue(true);
        } else {
            LogUtil.log("初始化viewModel失败_show");
        }
    }

    public void dismissDialog() {
        LogUtil.log("dismissDialog");
        if (baseDialogViewModel != null) {
            baseDialogViewModel.getDialogLiveData().postValue(false);
        } else {
            LogUtil.log("初始化viewModel失败_dismiss");
        }
    }

    /**
     * @return layoutId
     */
    @LayoutRes
    public abstract int getLayoutRes();

    public abstract void bindView(View v);

}