Запуск диалоговых фрагментов из другого фрагмента: IllegalStateException: невозможно выполнить это действие после onSaveInstanceState

Поэтому я пытался понять, как решить эту проблему, но я не могу ее исправить. Сейчас у меня есть один фрагмент с одной кнопкой. Когда вы нажмете эту кнопку, запустится пользовательское DialogFragment с кнопками ok/cancel.

Если я нажму кнопку «ОК», он запустит другой пользовательский DialogFragment, на этот раз это фрагмент ProgressDialog. Проблема в том, что когда появляется диалоговое окно ok/cancel, если я поворачиваю, а затем нажимаю кнопку ok, чтобы затем вызвать фрагмент ProgressDialog, я получаю эту ошибку. Если я вращаюсь только во время показа фрагмента progressdialog, проблем вообще нет. Я использую пакет поддержки v4. Вот классы:

Основная активность:

public class MainActivity extends FragmentActivity implements OnFragmentAttachedListener, Callbacks{

boolean mResumed = false;

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

    fragmentTransaction.add(R.id.main_id, new EmptyFragmentWithCallbackOnResume());
    fragmentTransaction.commitAllowingStateLoss();

}
@Override
public void onTaskFinished()
{
    // Hooray. A toast to our success.
    Toast.makeText(this, "Task finished!", Toast.LENGTH_LONG).show();
    // NB: I'm going to blow your mind again: the "int duration" parameter of makeText *isn't*
    // the duration in milliseconds. ANDROID Y U NO ENUM? 
}

@Override
public void OnFragmentAttached() {

} }

окнофрагмент диалога отмены:

public class OkCancelDialogFragment<T> extends DialogFragment {

public final static String TITLE="title";

private OkCancelDialogEvents<T> buttonEvents;
private T[] params;


public OkCancelDialogFragment(String title, OkCancelDialogEvents<T> buttonEvents, T... params) {

    this.buttonEvents=buttonEvents;

    Bundle args = new Bundle();
    args.putString(TITLE, title);
    this.setArguments(args);

    this.params=params;

}


@Override
public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    this.setRetainInstance(true);
}


@Override
public void onDestroyView() {
  if (getDialog() != null && getRetainInstance())
    getDialog().setDismissMessage(null);
  super.onDestroyView();
}


@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

     String title = getArguments().getString(TITLE);
     return new AlertDialog.Builder(getActivity())
    //.setIcon(R.drawable.alert_dialog_icon)
     .setTitle(title)
     .setPositiveButton("ok",
         new DialogInterface.OnClickListener() {
             public void onClick(DialogInterface dialog, int whichButton) {
                 buttonEvents.onPositiveClick(params);
             }
         }
     )
     .setNegativeButton("cancel",
         new DialogInterface.OnClickListener() {
             public void onClick(DialogInterface dialog, int whichButton) {
                 buttonEvents.onNegativeClick();
             }
         }
     )
     .create(); }} 

фрагмент диалога прогресса:

public class TaskFragment extends DialogFragment{
// The task we are running.
GenericTask<?,?> mTask;
ProgressDialog mProgressDialog;
String title, message;

public void setTask(MyTask task)
{
    mTask = task;

    // TellsetFragment the AsyncTask to call updateProgress() and taskFinished() on this fragment.

    mTask.setFragment(this);
}


public void setTitleMessage(String title, String message){
    this.title=title;
    this.message=message;       
}


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

    // Retain this instance so it isn't destroyed when MainActivity and
    // MainFragment change configuration.
    setRetainInstance(true);

}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

    mProgressDialog= new ProgressDialog(getActivity());
    mProgressDialog.setTitle(title);
    mProgressDialog.setMessage(message);
    mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    mProgressDialog.setCanceledOnTouchOutside(false);

    return mProgressDialog;
}

// This is to work around what is apparently a bug. If you don't have it
// here the dialog will be dismissed on rotation, so tell it not to dismiss.
@Override
public void onDestroyView()
{
    if (getDialog() != null && getRetainInstance())
        getDialog().setDismissMessage(null);
    super.onDestroyView();
}

// Also when we are dismissed we need to cancel the task.
@Override
public void onDismiss(DialogInterface dialog)
{
    super.onDismiss(dialog);
    // If true, the thread is interrupted immediately, which may do bad things.
    // If false, it guarantees a result is never returned (onPostExecute() isn't called)
    // but you have to repeatedly call isCancelled() in your doInBackground()
    // function to check if it should exit. For some tasks that might not be feasible.
    if (mTask != null)
        mTask.cancel(false);

    // You don't really need this if you don't want.
    if (getTargetFragment() != null)
        getTargetFragment().onActivityResult(MainFragment.TASK_FRAGMENT, Activity.RESULT_CANCELED, null);
}

@Override
public void onResume()
{
    super.onResume();
    // This is a little hacky, but we will see if the task has finished while we weren't
    // in this activity, and then we can dismiss ourselves.
    if (mTask == null)
        dismiss();
}

// This is called by the AsyncTask.
public void updateProgress(int percent)
{
    mProgressDialog.setProgress(percent);
}

// This is also called by the AsyncTask.
public void taskFinished()
{
    // Make sure we check if it is resumed because we will crash if trying to dismiss the dialog
    // after the user has switched to another app.
    if (isResumed())
        dismiss();

    // If we aren't resumed, setting the task to null will allow us to dimiss ourselves in
    // onResume().
    mTask = null;

    // Tell the fragment that we are done.
    if (getTargetFragment() != null)
        getTargetFragment().onActivityResult(MainFragment.TASK_FRAGMENT, Activity.RESULT_OK, null);
}

}

основной фрагмент:

public class MainFragment extends Fragment implements OkCancelDialogEvents<Void>, OnClickListener{
// This code up to onDetach() is all to get easy callbacks to the Activity. 
private Callbacks mCallbacks = sDummyCallbacks;

private static Callbacks sDummyCallbacks = new Callbacks()
{
    public void onTaskFinished() { }
};

@Override
public void onAttach(Activity activity)
{
    super.onAttach(activity);
    if (!(activity instanceof Callbacks))
    {
        throw new IllegalStateException("Activity must implement fragment's callbacks.");
    }
    mCallbacks = (Callbacks) activity;
}

@Override
public void onDetach()
{
    super.onDetach();
    mCallbacks = sDummyCallbacks;
}

// Save a reference to the fragment manager. This is initialised in onCreate().
private FragmentManager mFM;

// Code to identify the fragment that is calling onActivityResult(). We don't really need
// this since we only have one fragment to deal with.
static final int TASK_FRAGMENT = 0;

// Tag so we can find the task fragment again, in another instance of this fragment after rotation.
static final String TASK_FRAGMENT_TAG = "task";

@Override
public void onCreate(Bundle savedInstanceState)
{
    //this.setRetainInstance(true);
    super.onCreate(savedInstanceState);
    // At this point the fragment may have been recreated due to a rotation,
    // and there may be a TaskFragment lying around. So see if we can find it.
    mFM = getFragmentManager();
    // Check to see if we have retained the worker fragment.
    TaskFragment taskFragment = (TaskFragment)mFM.findFragmentByTag(TASK_FRAGMENT_TAG);

    if (taskFragment != null)
    {
        // Update the target fragment so it goes to this fragment instead of the old one.
        // This will also allow the GC to reclaim the old MainFragment, which the TaskFragment
        // keeps a reference to. Note that I looked in the code and setTargetFragment() doesn't
        // use weak references. To be sure you aren't leaking, you may wish to make your own
        // setTargetFragment() which does.
        taskFragment.setTargetFragment(this, TASK_FRAGMENT);
    }

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState)
{
    return inflater.inflate(R.layout.fragment_main, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {

    super.onActivityCreated(savedInstanceState);
    if(savedInstanceState!=null)
        Toast.makeText(getActivity(), savedInstanceState.getString("documents"), Toast.LENGTH_SHORT).show();
}

@Override
public void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    outState.putString("documents", "teste");
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
    super.onViewCreated(view, savedInstanceState);

    // Callback for the "start task" button. I originally used the XML onClick()
    // but it goes to the Activity instead.
    view.findViewById(R.id.taskButton).setOnClickListener(this);
}

@Override
public void onClick(View v)
{

    OkCancelDialogFragment<Void> dialog = new OkCancelDialogFragment<Void>("Teste", this);
    dialog.setTargetFragment(this, 2);
    dialog.show(getFragmentManager(), "basic_dialog");


}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
    if (requestCode == TASK_FRAGMENT && resultCode == Activity.RESULT_OK)
    {
        // Inform the activity. 
        mCallbacks.onTaskFinished();
    }
}


@Override
public void onPositiveClick(Void... params) {

    // We only have one click listener so we know it is the "Start Task" button.

    // We will create a new TaskFragment.
    TaskFragment taskFragment = new TaskFragment();
    // And create a task for it to monitor. In this implementation the taskFragment
    // executes the task, but you could change it so that it is started here.

    MyTask task=new MyTask();
    task.execute("one","two");

    taskFragment.setTask(task);
    taskFragment.setTitleMessage("File Download", "Downloading...");
    // And tell it to call onActivityResult() on this fragment.
    taskFragment.setTargetFragment(this, TASK_FRAGMENT);
    // Show the fragment.
    // I'm not sure which of the following two lines is best to use but this one works well.
    taskFragment.show(mFM, TASK_FRAGMENT_TAG);
    //mFM.beginTransaction().add(taskFragment, TASK_FRAGMENT_TAG).commit();

}

@Override
public void onNegativeClick() {


}}

Вот ошибки:

   12-12 11:24:52.144: E/AndroidRuntime(2451): FATAL EXCEPTION: main
12-12 11:24:52.144: E/AndroidRuntime(2451): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.support.v4.app.DialogFragment.show(DialogFragment.java:127)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at com.example.progressdialog.MainFragment.onPositiveClick(MainFragment.java:149)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at com.example.progressdialog.MainFragment.onPositiveClick(MainFragment.java:1)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at com.example.progressdialog.OkCancelDialogFragment$1.onClick(OkCancelDialogFragment.java:56)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at com.android.internal.app.AlertController$ButtonHandler.handleMessage(AlertController.java:196)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.os.Handler.dispatchMessage(Handler.java:99)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.os.Looper.loop(Looper.java:123)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.app.ActivityThread.main(ActivityThread.java:4627)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at java.lang.reflect.Method.invokeNative(Native Method)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at java.lang.reflect.Method.invoke(Method.java:521)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:858)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at dalvik.system.NativeStart.main(Native Method)

person Maxrunner    schedule 12.12.2012    source источник
comment
Есть ли причина использовать setRetainInstance в ваших фрагментах?   -  person user    schedule 12.12.2012
comment
Ну, я использую setReatainInstance только для диалоговых фрагментов. Не исчезнут ли они при ротации, если для них установлено значение false?   -  person Maxrunner    schedule 12.12.2012
comment
Вы можете попробовать и убедиться сами :). setRetainInstance означает, что экземпляр фрагмента будет сохранен после изменения конфигурации, поэтому фрагмент не будет повторно создан.   -  person user    schedule 12.12.2012
comment
да, я знаю это, но я пытался увидеть решения, и я не понимаю их полностью. Этот, stackoverflow.com/questions/11900785/, говорит, что я должен переопределить метод onResumeFragments, но я вызываю диалоговый фрагмент из основного фрагмента.   -  person Maxrunner    schedule 12.12.2012
comment
Ошибка заканчивается этой строкой: taskFragment.show(mFM, TASK_FRAGMENT_TAG); это onPositiveClick из диалогового окна okcancel.   -  person Maxrunner    schedule 12.12.2012
comment
Исключение исходит из вашего кода, я сделал простой тест, и то, что вы пытаетесь сделать, работает (по крайней мере, как я понял из вашего вопроса). Можете ли вы предоставить работающий пример проекта с кодом, который генерирует это исключение, чтобы я мог посмотреть на него (что-то простое)?   -  person user    schedule 12.12.2012
comment
То, что я сделал прямо сейчас, использует один фрагмент диалога для всего, но я попытаюсь дать последний код. Как я могу разместить это здесь?   -  person Maxrunner    schedule 13.12.2012
comment
В любом случае, Luksprog, вы тоже можете опубликовать свой тест? Я постараюсь опубликовать пример проекта здесь, когда я вернусь домой сегодня вечером. с уважением,   -  person Maxrunner    schedule 13.12.2012
comment
Вы можете поместить его куда угодно, pastebin, gist и т. д. См. мой небольшой тест здесь github.com/luksprog/DroidPlayground/blob/master/src/com/   -  person user    schedule 13.12.2012
comment
Вот он: dropbox.com/s/b96ab3xf9rcjjoe/DefaultProgressDialog.7z   -  person Maxrunner    schedule 14.12.2012


Ответы (1)


Похоже, это ошибка с пакетом совместимости (которая еще не решена). В любом случае, вы можете избежать этой ошибки, изменив свой код, чтобы лучше обрабатывать связь между фрагментами. Я изменил ваш образец проекта (который можно найти здесь). Что касается этого, я не знаю, насколько прост ваш образец, но если все обратные вызовы указывают на действие, тогда вы должны позволить классу действия обрабатывать фрагменты (например, запуск диалогов) и связь между ними, как это " видит» и знает состояние всех фрагментов в нем.

person user    schedule 14.12.2012
comment
Привет, Люкспрог. Я проверяю ваше изменение кода, и кажется, что основная проблема была с конструктором для okcanceldialog, можете ли вы это подтвердить? вы говорите, что я должен использовать пакет для передачи ему параметров? действительно ли это была основная проблема? t понял комментарий в вызове второго фрагмента диалога. можешь уточнить? с уважением, - person Maxrunner; 15.12.2012
comment
@Maxrunner Нет, ошибка произошла из-за ошибки. Я внес некоторые изменения, потому что ваши фрагменты не были должным образом построены и плохо взаимодействовали. У вас не должно быть Fragment с конструкторами с параметрами, потому что Android не сможет создать экземпляр этого фрагмента, когда это необходимо (вы этого не видели, потому что использовали setRetainInstance(true)). Да, вы должны использовать Bundle для передачи параметров, если у вас есть что-то тяжелое, я добавил дополнительный метод в интерфейс OkCancelDialogEvents, который OkCancelDialogFragment может вызывать buttonevents для получения дополнительных данных. - person user; 15.12.2012
comment
@Maxrunner Вы не должны создавать задачу в MainFragment, вместо этого вы должны передать данные (которые нужны задаче) в TaskFragment и позволить ему понять, что с ними делать. Кроме того, я видел, что все обратные вызовы в конечном итоге указывают на класс FragmentActivity, поэтому вы можете захотеть, чтобы действие обрабатывало фрагменты, передавая данные и показывая их. - person user; 15.12.2012
comment
Итак, вы говорите, что Asynctask (MyTask) должен создаваться TaskFragment, а не MainFragment? Обратные вызовы к действию существуют по причинам примера. мне это не нужно. - person Maxrunner; 15.12.2012
comment
@Maxrunner Да, AsyncTask должен создаваться и управляться TaskFragment, потому что он практически привязан к нему (у других частей вашего кода нет причин возиться с этим). MainFragment будет просто передавать необходимые данные или объекты для AsyncTask, и с этого момента TaskFragment позаботится об этом. - person user; 16.12.2012
comment
Спасибо. Тем не менее, что было решающим фактором для этой ошибки? - person Maxrunner; 16.12.2012
comment
@Maxrunner Я не знаю. Может быть, вы получите ответ на баг-трекере (если я не ошибаюсь, вы разместили там вопрос). - person user; 16.12.2012
comment
Вы имеете в виду сайт code.google.com? В любом случае, если вы установите mainfragment onCreate() с setRetainInstance равным true, это даст ту же ошибку - person Maxrunner; 18.12.2012
comment
@Maxrunner Да, я имел в виду code.google.com. Это может вызвать это исключение, но я не понимаю, почему вы должны использовать setRetainInstance для этого фрагмента. setRetainInstance следует использовать в некоторых случаях, например, при наличии невидимого фрагмента, который выполняет задачу загрузки данных и должен сохраниться после изменения конфигурации. - person user; 18.12.2012
comment
Также забыл спросить, почему изменение метода onDismiss у TaskFragment? Спс за поздний ответ... - person Maxrunner; 21.12.2012
comment
@Maxrunner Если я правильно помню, я поставил флаг mResumingCold, чтобы мы не запускали onActivityResult более одного раза для задачи, которая уже завершилась и (предположительно) уже вызвала метод taskFinished()). mEndStatus — это флаг, определяющий, успешно ли завершена задача (единственное место, где установлен флаг false, — это обратный вызов taskFinished, в любом другом случае, когда диалоговое окно закрывается, onActivityResult будет вызываться с RESULT_CANCEL). Это то, что я вспомнил, это может быть неправильно во всех ситуациях. - person user; 21.12.2012
comment
Забыл вас кое-что спросить, в TaskFragment вы никогда не присваивали переменной mTask значение null, разве это не проблема, поскольку Asynctask также является ссылкой на TaskFragment? - person Maxrunner; 04.01.2013
comment
@Maxrunner Пожалуйста, начните новый вопрос, если у вас есть вопросы, мы не можем вечно комментировать один и тот же вопрос, и мы выходим за рамки исходного вопроса. Зачем устанавливать mTask на null? Это не проблема из-за возможной утечки памяти, поскольку TaskFragment настроен на сохранение своего экземпляра, поэтому AsyncTask всегда имеет ссылку на правильный TaskFragment. Циклическая ссылка TaskFragment-AsyncTask не является проблемой. И из кода я предполагаю, что вы не можете запустить новую AsyncTask, не закрыв TaskFragment, который также очистит mTask. - person user; 04.01.2013