旋转后 onPostExecute 中的 IllegalStateException 片段未附加到 activity 但未调用 onDetach

IllegalStateException fragment not attached to activity in onPostExecute after rotation but onDetach not called

我有一个使用 NavigationDrawer 模式的 AppCompatActivity,管理一些片段。其中一个没有 setRetainInstance(true),我展示了一个带有 ProgressDialog 的 DialogFragment 和一个带有以下代码的 AsyncTask:

SavingLoader savingLoader = SavingLoader.newInstance(savingLoaderMaxValue);
savingLoader.show(getChildFragmentManager(), SAVING_LOADER_TAG);
new MyAsyncTask().execute();

其中 SavingLoader class 是这个:

public class SavingLoader extends DialogFragment {

    private static final String MAX_VALUE_TAG = "MAX_VALUE_TAG";
    private static final String PROGRESS_VALUE_TAG = "PROGRESS_VALUE_TAG";

    public static SavingLoader newInstance(int max_value){
        SavingLoader s = new SavingLoader();
        Bundle args = new Bundle();
        args.putInt(MAX_VALUE_TAG, max_value);
        s.setArguments(args);
        return s;
    }

    private ProgressDialog dialog;

    public SavingLoader(){}

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setCancelable(false);
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState){
        dialog = new ProgressDialog(getActivity(), getTheme());
        dialog.setTitle(getString(R.string.dialog_title_saving));
        dialog.setMessage(getString(R.string.dialog_message_saving));
        dialog.setIndeterminate(false);
        int max = (savedInstanceState == null ?
            getArguments().getInt(MAX_VALUE_TAG) : savedInstanceState.getInt(MAX_VALUE_TAG));
        if (max >= 1){
            dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            dialog.setProgress((savedInstanceState == null ?
                0 : savedInstanceState.getInt(PROGRESS_VALUE_TAG)));
            dialog.setMax(max);
        } else dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        return dialog;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(MAX_VALUE_TAG, dialog.getMax());
        outState.putInt(PROGRESS_VALUE_TAG, dialog.getProgress());
    }

    public int getProgress(){
        return dialog.getProgress();
    }

    public int getMax(){
        return dialog.getMax();
    }

    public void incrementProgressBy(int value){
        if (dialog.getProgress() + value <= dialog.getMax())
            dialog.incrementProgressBy(value);
    }

}

onPostExecute() 方法中,我需要执行一些 UI 更新,所以这是我的问题:如果我启动对话框和 AsyncTask(如上)并且我不旋转我的 phone,一切都按预期工作。如果我在 onPostExecute() 方法之后旋转 phone 也是一样的。但是,如果我在 AsyncTask 仍为 运行 时旋转我的 phone,当它完成并到达 onPostExecute() 方法时,它会给我 IllegalStateException 说托管 AsyncTask 的片段和Dialogfragment 不再附加到 activity。因此,我尝试覆盖片段的 onAttach()onDetach() 方法(使用简单的 System.out.println),以查看 onPostExecute() 何时被调用。结果是当我旋转 phone 时,我总是得到这个输出:

onDetach
onAttach
... (if I rotate more my phone)
onPostExecute

那么当 AsyncTask 完成时不应该附加片段吗?谢谢大家的时间和关注。

我终于设法通过停止使用 AsyncTask 并使用 LoaderManager + AsyncTaskLoader following this article. In short, your fragment must implement the LoaderCallbacks 界面并管理 AsyncTaskLoader 来解决这个问题。骨骼片段可能是这样的:

public class MyFragment extends Fragment implements LoaderManager.LoaderCallbacks {

    @Override
    public View onCreateView(LayoutInflater inflater, final ViewGroup container,
                         Bundle savedInstanceState) {
        // Inflate here your view as you usually do and find your components
        // For example imagine to have a button tha will fire the task
        Button b = (Button) view.findViewById(R.id.my_button);
        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Use this to start task for the first time
                getLoaderManager().initLoader(0, null, this);
                // .. or this for restart the task, details in
                // the provided article
                // getLoaderManager().restartLoader(0, null, this);
            }
        });

        // Get fragments load manager
        LoaderManager lm = getLoaderManager();
        if (lm.getLoader(0) != null) {
            // Reconnect to an existing loader
            lm.initLoader(0, null, this);
        }

        // Return your view here
        return view;
    }

    // LoaderCallbacks methods to override
    @Override
    public Loader onCreateLoader(int id, Bundle args) {
        // Create an instance of the loader passing the context
        MyTaskLoader loader = new MyTaskLoader(getActivity());
        loader.forceLoad();
        return loader;
    }

    @Override
    public void onLoadFinished(Loader loader, Object data) {
        // Use this callback as you would use the AsyncTask "onPostExecute"
    }

    @Override
    public void onLoaderReset(Loader loader) {}

    // Now define the loader class
    private static class MyTaskLoader extends AsyncTaskLoader {
        public MyTaskLoader(Context context){
            super(context);
        }

        @Override
        public Object loadInBackground() {
            // Do here your async work
        }
    }

}