交付结果失败 - onActivityForResult

76
我有一个名为LoginActivity的页面(用户登录)。它本质上是一个自己主题的Activity,看起来像一个对话框。它出现在一个SherlockFragmentActivity上方。我的要求是:如果登录成功,应该有两个FragmentTransaction来更新视图。以下是代码:
LoginActivity中,如果登录成功,
setResult(1, new Intent());

SherlockFragmentActivity中:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (resultCode == 1) {
        LoggedStatus = PrefActivity.getUserLoggedInStatus(this);
        FragmentTransaction t = MainFragmentActivity.this.getSupportFragmentManager().beginTransaction();
        SherlockListFragment mFrag = new MasterFragment();
        t.replace(R.id.menu_frame, mFrag);
        t.commit();

        // Set up Main Screen
        FragmentTransaction t2 = MainFragmentActivity.this.getSupportFragmentManager().beginTransaction();
        SherlockListFragment mainFrag = new FeaturedFragment();
        t2.replace(R.id.main_frag, mainFrag);
        t2.commit();
    }
}

第一次提交时,它会崩溃并显示以下LogCat:

E/AndroidRuntime(32072): Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
E/AndroidRuntime(32072):    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1299)
E/AndroidRuntime(32072):    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1310)
E/AndroidRuntime(32072):    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:541)
E/AndroidRuntime(32072):    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:525)
E/AndroidRuntime(32072):    at com.kickinglettuce.rate_this.MainFragmentActivity.onActivityResult(MainFragmentActivity.java:243)
E/AndroidRuntime(32072):    at android.app.Activity.dispatchActivityResult(Activity.java:5293)
E/AndroidRuntime(32072):    at android.app.ActivityThread.deliverResults(ActivityThread.java:3315)

你是如何调用 startActivityForResult() 的? - Rohit
5个回答

300
首先,您应该阅读我的博客文章以获取更多信息(它讨论了为什么会出现这个异常以及您可以采取什么措施来预防它)。
调用commitAllowingStateLoss()更像是一种hack而不是修复方法。状态丢失很糟糕,应尽一切可能避免。在调用onActivityResult()时,活动/片段的状态可能尚未恢复,因此在此期间发生的任何事务都将因此丢失。这是一个非常重要的错误,必须加以解决!(请注意,此错误仅在系统杀死您的Activity后回到时才会发生...这取决于设备的内存量,有时可能很少...所以这种错误不是非常容易在测试中捕获)。
尝试将您的事务移到onPostResume()中(请注意,onPostResume()始终在onResume()之后调用,并且onResume()始终在onActivityResult()之后调用):
private boolean mReturningWithResult = false;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    mReturningWithResult = true;
}

@Override
protected void onPostResume() {
    super.onPostResume();
    if (mReturningWithResult) {
        // Commit your transactions here.
    }
    // Reset the boolean flag back to false for next time.
    mReturningWithResult = false;
}

这可能看起来有点奇怪,但这样做是必要的,以确保您的 FragmentTransaction 总是在Activity状态恢复到其原始状态后提交(onPostResume()保证在Activity状态已经被恢复后调用)。


4
在“碎片”中,我在缺少onPostResume的情况下使用了onResume。我再也没有遇到这个问题了,但也许你想对此发表评论。PS. 非常感谢你深入的文章! - Maragues
27
Fragment#onResume()中执行这个操作是可以的。这是因为FragmentActivity#onPostResume()会调用FragmentActivity#onResumeFragments(),然后调用FragmentManager#dispatchResume(),接着为每个活动的fragment调用Fragment#onResume()。因此,在FragmentActivity#onPostResume()之后调用Fragment#onResume()就不会有问题(你可以查看源代码来验证各自类的实现细节,当然你也可以相信我的:P)。非常感谢!很高兴您认为它们是有见地的。 :) - Alex Lockwood
1
@Alex,如果DialogFragment在进行网络调用时,Home键在DialogFragment接收异步响应之前被按下,那么在异步响应中调用this.dismiss()将会导致状态丢失异常。在这种情况下,应该在什么时候调用dismiss()以避免状态丢失?请注意,请求和响应都在DialogFragment内部,而不是在Activity中。 - crazy horse
2
@crazyhorse 如果是这种情况,最好取消异步任务和/或提高一个标志来确保在AsyncTask#onPostExecute()内部不会调用dismiss()(如果活动进入后台,则对话框将由FragmentManager自动解除,因此您无需在活动消失后自己解除对话框)。 - Alex Lockwood
1
@AlexLockwood 感谢您的这篇文章。在您的博客中,您说事务应该在 Activity#onPostResume 之后提交。但与此同时,您又说它们可以在 Activity#onCreate 中提交(甚至在 onResume 之前运行)。我相信我误解了什么,所以您能否解释一下。 - nutella_eater
显示剩余19条评论

1
这与@Alex Lockwood的答案类似,但使用Runnable
private Runnable mOnActivityResultTask;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    mOnActivityResultTask = new Runnable() {
        @Override
        public void run() {
            // Your code here
        }
    }
}

@Override
protected void onPostResume() {
    super.onPostResume();
    if (mOnActivityResultTask != null) {
        mOnActivityResultTask.run();
        mOnActivityResultTask = null;
    }
}

如果您使用的是Android 3.0及以上版本,并且使用了lambda表达式,请使用以下代码:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    mOnActivityResultTask = () -> {
        // Your code here
    }
}

我宁愿选择可运行的方式,因为这样可以让你在逻辑发生的地方编写代码。在我的情况下,它来自于rx订阅者。 - Fabio

-1

你的logcat清楚地显示:"Can not perform this action after onSaveInstanceState" -- 在那个时候,你的Activity已经死亡,无法返回任何结果。

只需移动:

Intent in = new Intent();
setResult(1, in);

将它返回到你的Activity中仍然存在的位置,一切都会很好。 并且别忘了finish()你的Activity以提交结果。


finish() 是我正在执行的最后一件事情。我在它上面刚好有意图的代码,我将它移动到了顶部,但出现了同样的错误。我正在添加代码来解决问题... - TheLettuceMaster
上一条评论还有一个要点。实际上我没有看到弹出消息。但是当我在崩溃后重新进入应用程序时,我已经登录了。 - TheLettuceMaster

-1
你可以使用ft.commitAllowingStateLoss()来解决这个问题。
原因:你的ft.commit()方法是在onSaveInstanceState之后切换的。

-1

在我的情况下,我面临了相同的问题,因为以下原因

public void onBackPressed() {
    super.onBackPressed();
    setIntents();

}


private void setIntents(){
    Intent searchConstaints=new Intent();
    searchConstaints.putExtra("min",20);
    searchConstaints.putExtra("max",80);
    setResult(101,searchConstaints);
    finish();
}

通过重新排列 onBackPressed() 中的函数调用来解决问题。

public void onBackPressed() {

    setIntents();
    super.onBackPressed();

}

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