我在SO上发现了许多类似问题的实例,但不幸的是没有答案符合我的要求。
我有不同的纵向和横向布局,我使用背堆栈,这都阻止我使用setRetainState()和使用配置更改例程的技巧。
我在TextViews中显示某些信息给用户,这些信息不会保存在默认处理程序中。当我只使用Activities编写应用程序时,以下工作得很好:
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));
}
With Fragments, this works only in very specific situations. Specifically, what breaks horribly is replacing a fragment, putting it in the back stack and then rotating the screen while the new fragment is shown. From what I understood, the old fragment does not receive a call to onSaveInstanceState() when being replaced but stays somehow linked to the Activity and this method is called later when its View does not exist anymore, so looking for any of my TextViews results into a NullPointerException.
此外,我发现,保持引用我的TextViews不是一个好主意与片段,即使它是OK与活动的。在这种情况下,onSaveInstanceState()实际上保存了状态,但如果我在片段隐藏时旋转屏幕两次,问题就会再次出现,因为它的onCreateView()在新实例中没有被调用。
我想在onDestroyView()保存状态到一些捆绑类型的类成员元素(它实际上是更多的数据,不只是一个TextView)和保存在onSaveInstanceState(),但有其他缺点。首先,如果当前显示了片段,则调用两个函数的顺序颠倒了,因此我需要考虑两种不同的情况。一定会有更干净和正确的解决方案!
要正确保存Fragment的实例状态,您应该执行以下操作:
1. 在片段中,通过覆盖onSaveInstanceState()来保存实例状态,并在onActivityCreated()中恢复:
class MyFragment extends Fragment {
@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()中恢复。
class MyActivity extends Activity {
private MyFragment
public void onCreate(Bundle savedInstanceState) {
...
if (savedInstanceState != null) {
//Restore the fragment's instance
mMyFragment = getSupportFragmentManager().getFragment(savedInstanceState, "myFragmentName");
...
}
...
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Save the fragment's instance
getSupportFragmentManager().putFragment(outState, "myFragmentName", mMyFragment);
}
}
要正确保存Fragment的实例状态,您应该执行以下操作:
1. 在片段中,通过覆盖onSaveInstanceState()来保存实例状态,并在onActivityCreated()中恢复:
class MyFragment extends Fragment {
@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()中恢复。
class MyActivity extends Activity {
private MyFragment
public void onCreate(Bundle savedInstanceState) {
...
if (savedInstanceState != null) {
//Restore the fragment's instance
mMyFragment = getSupportFragmentManager().getFragment(savedInstanceState, "myFragmentName");
...
}
...
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Save the fragment's instance
getSupportFragmentManager().putFragment(outState, "myFragmentName", mMyFragment);
}
}
这是一个非常古老的答案。
我不再为Android写东西了,所以最新版本的功能不能保证,也不会有任何更新。
这就是我现在使用的方法……它非常复杂,但至少它处理了所有可能的情况。如果有人感兴趣的话。
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());
}
/* (...) */
}
另外,也可以将数据显示在变量中的被动视图中,并仅使用视图来显示它们,从而保持两者的同步。不过,我觉得最后一部分不太干净。
我只是想给出我提出的解决方案,处理本文中提出的所有情况,这些情况都是我从Vasek和devconsole中派生出来的。这个解决方案也可以处理手机旋转不止一次而碎片不可见的特殊情况。
这里是我存储的包供以后使用,因为onCreate和onSaveInstanceState是唯一的调用,当片段是不可见的
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在特殊的旋转情况下没有被调用,我们可以确定如果它创建了状态,我们应该使用它。
@Override
public void onDestroyView() {
super.onDestroyView();
savedState = saveState();
createdStateInDestroyView = true;
myObject = null;
}
这部分是一样的。
private Bundle saveState() {
Bundle state = new Bundle();
state.putSerializable(SAVED_BUNDLE_TAG, myObject);
return state;
}
现在是棘手的部分。在我的onActivityCreated方法中,我实例化了“myObject”变量,但旋转发生onActivity和onCreateView没有被调用。因此,在这种情况下,当方向旋转多次时,myObject将为空。我通过重用在onCreate中保存的相同的捆绑包来解决这个问题。
@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包
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
if(savedState != null) {
myObject = (MyObject) savedState.getSerializable(SAVED_BUNDLE_TAG);
}
...
}
在最新的支持库中,这里讨论的解决方案都不再是必需的。你可以使用FragmentTransaction来处理Activity的片段。只需确保您的片段可以使用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);
}
...
}
但是请注意,在恢复片段的隐藏状态时,目前有一个错误。如果您在活动中隐藏片段,在这种情况下,您将需要手动恢复此状态。