从另一个fragment启动dialogfragments:IllegalStateException:在onSaveInstanceState之后无法执行此操作。

5

我一直在努力理解如何解决这个问题,但我真的无法解决它。现在我有一个带有一个按钮的片段。当您按下此按钮时,它将启动一个带有确定/取消按钮的自定义DialogFragment

如果我按下确定按钮,它将启动另一个自定义DialogFragment,这次是ProgressDialog片段。问题是,当确定/取消对话框出现时,如果我旋转然后按下确定按钮,然后调用ProgressDialog片段,我会得到这个错误。如果只在进度对话框片段显示时旋转,则根本没有问题。我使用支持包v4。以下是类:

MainActivity:

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() {

} }

okcancel对话框片段:

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)

在你的片段中使用setRetainInstance有什么原因吗? - user
我只在DialogFragment上使用了setReatainInstance,如果它们被设置为false,它们不会在旋转时消失吗? - Maxrunner
你可以尝试自己去看看 :). setRetainInstance 的意思是在配置更改后保留片段的实例,因此该片段不会被重新实例化。 - user
是的,我知道,但我一直在尝试查看解决方案,而且我没有完全理解它们。这个链接https://dev59.com/omct5IYBdhLWcg3wqvXL说我应该重写onResumeFragments方法,但我从主片段调用dialogfragment。 - Maxrunner
这是: https://www.dropbox.com/s/b96ab3xf9rcjjoe/DefaultProgressDialog.7z - Maxrunner
显示剩余5条评论
1个回答

1

这似乎是兼容性包的一个错误(尚未解决)。无论如何,您可以通过修改代码以更好地处理片段之间的通信来避免该错误。我已经修改了您的示例项目(可以在此处找到)。关于这一点,我不知道您的示例有多简单,但如果所有回调都指向活动,则应让活动类处理片段(例如启动对话框)和它们之间的通信,因为它“看到”并知道其中所有片段的状态。


嗨,Luksprog。我正在检查您的代码修改,似乎主要问题出在okcanceldialog的构造函数上,您能确认一下吗?您说我应该使用bundle将参数传递给它?这真的是主要问题吗?此外,我没有理解第二个对话框片段调用中的注释。您能详细说明一下吗? 问候, - Maxrunner
谢谢。不过这个错误的决定因素是什么? - Maxrunner
还忘了问一下为什么更改了TaskFragment的onDismiss方法? 对于晚回复感到抱歉... - Maxrunner
忘记问你一些事情,在TaskFragment中,你从未将mTask变量设置为null,这不是一个问题吗?因为Asynctask也引用了TaskFragment。 - Maxrunner
@Maxrunner 如果你有问题,请开一个新的问题,我们不能永远在同一个问题上发表评论,而且我们已经超出了原始问题的范围。为什么要将mTask设置为null?这不是因为可能存在内存泄漏的问题,因为TaskFragment被设置为保留其实例,所以AsyncTask始终具有对正确的TaskFragment的引用。循环引用TaskFragment-AsyncTask不是问题。从代码中可以推断出,如果没有关闭TaskFragment,则无法启动新的AsyncTask,这也会清除mTask - user
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接