在onActivityResult()方法中提交FragmentTransaction是否安全?

13
多年前,我在我的应用程序中尝试在onActivityResult()回调中提交FragmentTransaction时遇到了问题。在网上搜索时,我发现这个问题和答案,其中说:

在调用onActivityResult()时,活动/片段的状态可能尚未恢复,因此在此期间发生的任何事务都将丢失。

我最终改编了同一答案中推荐的解决方案,并且一切顺利。然而,最近的实验表明,也许情况已经改变,现在可能安全地从onActivityResult()中提交FragmentTransaction

(支持v4)FragmentManager.beginTransaction()的文档将事务的安全窗口定义为:

注意:片段事务只能在活动保存其状态之前创建/提交。如果您尝试在FragmentActivity.onSaveInstanceState()之后(并在以下FragmentActivity.onStartFragmentActivity.onResume()之前)提交事务,则会收到错误消息。

阅读onActivityResult()的文档,我看到:

当您的活动重新启动时,在onResume()之前立即收到此调用。这让我相信在onActivityResult()中执行这些事务应该是安全的,因为onStart()已经被调用,将我放在安全窗口内。我制作了一个应用程序来测试这个问题,并且我成功地看到了我在onActivityResult()内创建和提交的对话框片段。我还记录了活动生命周期回调,以便检查它们的顺序,我每次都会看到onStart(),然后是onRestoreInstanceState(),最后是onActivityResult()。我是否遗漏了什么?或者框架是否发生了变化,onActivityResult()现在可以保证是片段事务的安全位置?这种行为是否因API级别而异?我找到了另一个问题和答案,似乎读取了与我一样的文档,但两者都超过一年了,也没有特别提到onActivityResult()作为处理事务的安全位置。

请查看http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html。 - elmorabea
@elmorabea那篇博客是我第一次遇到这个问题时找到的材料之一。然而,正如azizbekian在下面回答的那样,它似乎不再是关于安全提交FragmentTransactions的准确解释。 - Ben P.
1个回答

18

深入源码

FragmentManager 类中有一个布尔变量,名为 mStatedSaved。此变量根据活动的生命周期回调跟踪保存的状态。这是抛出众所周知异常的方法:


    private void checkStateLoss() {
        if (mStateSaved) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
        ...
    }

这意味着,只要该变量更改为 false,则可以自由执行片段事务。当活动的状态正在保存时,即 onSaveInstanceState() 时,此变量将变为 true


回到问题

你说过,之前在onActivityResult()中提交事务时遇到了问题。这应该意味着之前mStateSaved没有被赋值为false,而现在它是。实际上确实如此。

以下是O版本中onActivityResult()的实现:


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        mFragments.noteStateNotSaved();
        ...
    }

其中,noteStateNotSaved()会执行以下操作:


    public void noteStateNotSaved() {
        ...
        mStateSaved = false;
        ...
    }

相反地,您可以查看Jelly Bean版本的onActivityResult()的实现:


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        int index = requestCode>>16;
        if (index != 0) {
            index--;
            if (mFragments.mActive == null || index = mFragments.mActive.size()) {
                Log.w(TAG, "活动结果片段索引超出范围: 0x"
                        + Integer.toHexString(requestCode));
                return;
            }
            Fragment frag = mFragments.mActive.get(index);
            if (frag == null) {
                Log.w(TAG, "活动结果未找到与该索引对应的片段: 0x"
                        + Integer.toHexString(requestCode));
            } else {
                frag.onActivityResult(requestCode&0xffff, resultCode, data);
            }
            return;
        }
super.onActivityResult(requestCode, resultCode, data); }

如果不改变mStateSaved字段的值,就会导致提交事务时抛出异常。

实际上,这行代码 mFragments.noteStateNotSaved()是在Kit-Kat版本中引入的。正如Dianne Hackborn所做的提交评论所示:

当ActivityFragment收到onNewIntent()时,应该清除状态保存的标志。这可能发生在activity恢复之前,因此我们可能尚未清除它。对于onActivityResult()也需要做同样的事情。

总结

假设您使用的源包括commit 4ccc001,该提交是在2012年10月进行的,那么是的,onActivityResult()现在已经成为一个安全的fragment事务处理位置。


7
像这样的答案阅读起来是一种纯粹的快乐。谢谢。 - Vasiliy
谢谢,@Vasiliy,我也很喜欢你的回答/讲话。 - azizbekian
1
@azizbekian 当然,我想回应其他评论者,这是一个很好的答案,但我也想说,我没有立即接受+奖励的唯一原因是我想等待看看是否还有其他人加入。感谢您如此详细的解答;非常感激。 - Ben P.
@azizbekian感谢您这么深入的解释。通过阅读代码,我认为super.onActivityResult应该是onActivityResult内的第一行而不是最后一行,因为这是设置mStateSaved为false的调用.. 我是对的吗? - ADM
@ADM,除非您有特定原因不将super调用放在顶部,否则我看不出为什么您要将其移动到底部。谢谢。 - azizbekian
是的,我没有特定的原因。但我在很多代码中看到过,其中包括我的一些(旧时代的)。上面的代码也在底部有它。谢谢,这已经足够清楚了。超级调用应该放在第一位。 - ADM

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