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

时间:2015-06-25 18:13:55

标签: android android-fragments android-asynctask android-dialogfragment

I've an AppCompatActivity that uses the NavigationDrawer pattern, managing some fragments. In one of these, that has no setRetainInstance(true), I show a DialogFragment with a ProgressDialog inside and an AsyncTask with this code:

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

Where the SavingLoader class is this one:

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);
    }

}

In the onPostExecute() method I need to perform some UI update so here's my problem: if I start the dialog and the AsyncTask (like above) and I don't rotate my phone, all works as expected. Same thing if I rotate phone AFTER the onPostExecute() method. But if I rotate my phone WHILE the AsyncTask is still running, when it completes and reach the onPostExecute() method it gives me the IllegalStateException saying that the fragment hosting the AsyncTask and the Dialogfragment is no longer attached to the activity. So I tried to override both the onAttach() and the onDetach() methods (with a simple System.out.println) of my fragment, to see when the onPostExecute() gets called. The result is that when I rotate my phone, I always got this output:

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

So shouldn't the fragment be attached when the AsyncTask completes? Thank you all for your time and attention.

1 个答案:

答案 0 :(得分:2)

我终于设法通过在此LoaderManager之后停止使用AsyncTask并使用AsyncTaskLoader + article来解决此问题。简而言之,您的片段必须实现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
        }
    }

}