Once for all, how to correctly save instance state of Fragments in back stack?
我在SO上发现了许多类似问题的实例,但遗憾的是没有答案符合我的要求。
我有纵向和横向的不同布局,我正在使用后台堆栈,这两个都阻止我使用配置更改例程使用
我在TextViews中向用户显示某些信息,这些信息不会保存在默认处理程序中。仅使用活动编写我的应用程序时,以下工作正常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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)); } |
使用
另外,我发现保持对
我想把
要正确保存
1.在片段中,通过覆盖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @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.重要的是,在活动中,您必须将片段的实例保存在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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); } |
希望这可以帮助。
这就是我现在使用的方式......它非常复杂但至少它可以处理所有可能的情况。如果有人有兴趣。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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()); } /* (...) */ } |
或者,始终可以将数据显示在变量中的被动
在最新的支持库中,这里讨论的解决方案都不再是必需的。您可以使用
只要您不在每次调用
这是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @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); } ... } |
但请注意,恢复片段的隐藏状态时存在错误。如果要在活动中隐藏片段,则需要在这种情况下手动恢复此状态。
我只想提供我想出的解决方案来处理本文中我从Vasek和devconsole派生的所有案例。此解决方案还可以处理特殊情况,当手机不能一次旋转而片段不可见时。
这是我存储捆绑以供以后使用,因为onCreate和onSaveInstanceState是片段不可见时唯一的调用
1 2 3 4 5 6 7 8 9 10 11 12 | MyObject myObject; private Bundle savedState = null; private boolean createdStateInDestroyView; private static final String SAVED_BUNDLE_TAG ="saved_bundle"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { savedState = savedInstanceState.getBundle(SAVED_BUNDLE_TAG); } } |
由于在特殊旋转情况下不调用destroyView,我们可以确定如果它创建状态我们应该使用它。
1 2 3 4 5 6 7 | @Override public void onDestroyView() { super.onDestroyView(); savedState = saveState(); createdStateInDestroyView = true; myObject = null; } |
这部分是一样的。
1 2 3 4 5 | private Bundle saveState() { Bundle state = new Bundle(); state.putSerializable(SAVED_BUNDLE_TAG, myObject); return state; } |
现在这里是棘手的部分。在我的onActivityCreated方法中,我实例化了"myObject"变量,但是onActivity和onCreateView上的旋转都没有被调用。因此,当方向旋转多次时,myObject在这种情况下将为null。我通过重复使用onCreate中保存的相同包作为外包来解决这个问题。
1 2 3 4 5 6 7 8 9 10 11 | @Override public void onSaveInstanceState(Bundle outState) { if (myObject == null) { outState.putBundle(SAVED_BUNDLE_TAG, savedState); } else { outState.putBundle(SAVED_BUNDLE_TAG, createdStateInDestroyView ? savedState : saveState()); } createdStateInDestroyView = false; super.onSaveInstanceState(outState); } |
现在,只要您想要恢复状态,只需使用savedState包
1 2 3 4 5 6 7 8 | @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ... if(savedState != null) { myObject = (MyObject) savedState.getSerializable(SAVED_BUNDLE_TAG); } ... } |
感谢DroidT,我做了这个:
我意识到如果Fragment没有执行onCreateView(),它的视图就不会被实例化。因此,如果后端堆栈上的片段没有创建其视图,我保存最后存储的状态,否则我使用我想要保存/恢复的数据构建自己的包。
1)扩展此课程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | import android.os.Bundle; import android.support.v4.app.Fragment; public abstract class StatefulFragment extends Fragment { private Bundle savedState; private boolean saved; private static final String _FRAGMENT_STATE ="FRAGMENT_STATE"; @Override public void onSaveInstanceState(Bundle state) { if (getView() == null) { state.putBundle(_FRAGMENT_STATE, savedState); } else { Bundle bundle = saved ? savedState : getStateToSave(); state.putBundle(_FRAGMENT_STATE, bundle); } saved = false; super.onSaveInstanceState(state); } @Override public void onCreate(Bundle state) { super.onCreate(state); if (state != null) { savedState = state.getBundle(_FRAGMENT_STATE); } } @Override public void onDestroyView() { savedState = getStateToSave(); saved = true; super.onDestroyView(); } protected Bundle getSavedState() { return savedState; } protected abstract boolean hasSavedState(); protected abstract Bundle getStateToSave(); } |
2)在你的片段中,你必须有:
1 2 3 4 5 6 7 8 9 10 11 12 | @Override protected boolean hasSavedState() { Bundle state = getSavedState(); if (state == null) { return false; } //restore your data here return true; } |
3)例如,您可以在onActivityCreated中调用hasSavedState:
1 2 3 4 5 6 7 8 9 10 | @Override public void onActivityCreated(Bundle state) { super.onActivityCreated(state); if (hasSavedState()) { return; } //your code here } |
1 2 3 4 5 | final FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.hide(currentFragment); ft.add(R.id.content_frame, newFragment.newInstance(context),"Profile"); ft.addToBackStack(null); ft.commit(); |