一劳永逸,如何正确地将Fragments的实例状态保存在回栈中?

我在SO上发现了许多类似问题的实例,但不幸的是,没有答案满足我的要求。

我对纵向和横向有不同的布局,我正在使用back stack,它们都阻止我使用setRetainState()和使用配置更改例程的技巧。

我在TextView中向用户显示了某些信息,这些信息并未保存在默认处理程序中。 当仅使用活动编写我的应用程序时,以下运作良好:

TextView vstup;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.whatever);
    vstup = (TextView)findViewById(R.id.whatever);
    /* (...) */
}

@Override
public void onSaveInstanceState(Bundle state) {
    super.onSaveInstanceState(state);
    state.putCharSequence(App.VSTUP, vstup.getText());
}

@Override
public void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);
    vstup.setText(state.getCharSequence(App.VSTUP));
}

使用Fragment ,这只适用于非常特殊的情况。 具体来说,什么是可以破坏的替代片段,将其放入后端堆栈,然后在显示新片段时旋转屏幕。 根据我的理解,旧片段在被替换时没有收到对onSaveInstanceState()的调用,但仍然以某种方式链接到Activity并且稍后在其View不再存在时调用此方法,因此查找我的任何TextView结果变成NullPointerException

另外,我发现保留对我的TextViews的引用对于Fragment s来说并不是一个好主意,即使对于Activity来说也是如此。 在这种情况下, onSaveInstanceState()实际上会保存状态,但如果在隐藏片段时旋转屏幕两次,问题就会再次出现,因为onCreateView()不会在新实例中调用。

我想将onDestroyView()的状态保存到一些Bundle类型的类成员元素(它实际上是更多的数据,而不仅仅是一个TextView )并将其保存在onSaveInstanceState()但还有其他缺点。 主要是,如果片段当前显示,调用这两个函数的顺序是相反的,所以我需要考虑两种不同的情况。 必须有一个更清洁和正确的解决方案!


要正确保存Fragment的实例状态,您应该执行以下操作:

1.在片段中,通过覆盖onSaveInstanceState()并在onActivityCreated()恢复来保存实例状态:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    ...
    if (savedInstanceState != null) {
        //Restore the fragment's state here
    }
}
...
@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    //Save the fragment's state here
}

2. 重要的一点是 ,在活动中,您必须将片段的实例保存在onSaveInstanceState()并在onCreate()恢复。

public void onCreate(Bundle savedInstanceState) {
    ...
    if (savedInstanceState != null) {
        //Restore the fragment's instance
        mContent = getSupportFragmentManager().getFragment(savedInstanceState, "myFragmentName");
        ...
    }
    ...
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    //Save the fragment's instance
    getSupportFragmentManager().putFragment(outState, "myFragmentName", mContent);
}

希望这可以帮助。


这是我目前使用的方式......它非常复杂,但至少可以处理所有可能的情况。 如果有人感兴趣。

public final class MyFragment extends Fragment {
    private TextView vstup;
    private Bundle savedState = null;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.whatever, null);
        vstup = (TextView)v.findViewById(R.id.whatever);

        /* (...) */

        /* If the Fragment was destroyed inbetween (screen rotation), we need to recover the savedState first */
        /* However, if it was not, it stays in the instance from the last onDestroyView() and we don't want to overwrite it */
        if(savedInstanceState != null && savedState == null) {
            savedState = savedInstanceState.getBundle(App.STAV);
        }
        if(savedState != null) {
            vstup.setText(savedState.getCharSequence(App.VSTUP));
        }
        savedState = null;

        return v;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        savedState = saveState(); /* vstup defined here for sure */
        vstup = null;
    }

    private Bundle saveState() { /* called either from onDestroyView() or onSaveInstanceState() */
        Bundle state = new Bundle();
        state.putCharSequence(App.VSTUP, vstup.getText());
        return state;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        /* If onDestroyView() is called first, we can use the previously savedState but we can't call saveState() anymore */
        /* If onSaveInstanceState() is called first, we don't have savedState, so we need to call saveState() */
        /* => (?:) operator inevitable! */
        outState.putBundle(App.STAV, (savedState != null) ? savedState : saveState());
    }

    /* (...) */

}

或者 ,始终可以将被动View显示的数据保存在变量中,并使用View仅用于显示它们,同时保持两个事物同步。 不过,我不认为最后一部分很干净。


在最新的支持库上,这里讨论的解决方案都不再是必需的。 您可以使用FragmentTransaction随意使用您的ActivityFragmentTransaction 。 只要确保您的片段可以用ID或标签来标识。

只要您不尝试在每次调用onCreate()重新创建片段,片段就会自动恢复。 相反,您应该检查savedInstanceState是否为空,并在此情况下查找创建片段的旧引用。

这里是一个例子:

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

    if (savedInstanceState == null) {
        myFragment = MyFragment.newInstance();
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.my_container, myFragment, MY_FRAGMENT_TAG)
                .commit();
    } else {
        myFragment = (MyFragment) getSupportFragmentManager()
                .findFragmentByTag(MY_FRAGMENT_TAG);
    }
...
}

但请注意,恢复片段的隐藏状态时目前存在一个错误。 如果您在活动中隐藏了碎片,则在这种情况下需要手动恢复此状态。

链接地址: http://www.djcxy.com/p/31901.html

上一篇: Once for all, how to correctly save instance state of Fragments in back stack?

下一篇: onSaveInstanceState when click back button in Android