我在 SO 上发现了许多类似问题的实例,但遗憾的是没有答案符合我的要求。
我有不同的纵向和横向布局,并且我正在使用后堆栈,这既阻止我使用 setRetainState()
又阻止我使用配置更改例程的技巧。
我在 TextViews 中向用户显示某些信息,这些信息不会保存在默认处理程序中。仅使用活动编写我的应用程序时,以下内容运行良好:
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
。
此外,我发现使用 Fragment
保留对我的 TextViews
的引用并不是一个好主意,即使使用 Activity
也可以。在这种情况下,onSaveInstanceState()
实际上会保存状态,但如果我在片段隐藏时旋转屏幕 两次,问题会再次出现,因为它的 onCreateView()
不会在新实例中被调用。
我想将 onDestroyView()
中的状态保存到某个 Bundle
类型的类成员元素中(实际上是更多数据,而不仅仅是一个 TextView
)并将 that 保存在 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.还有重点,在activity中,你必须将fragment的实例保存在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());
}
/* (...) */
}
或者,始终可以将数据显示在变量中的被动 View
中,并仅使用 View
来显示它们,从而使两者保持同步。不过,我不认为最后一部分很干净。
A
和 B
,其中 A
当前在后台堆栈上,而 B
是可见的,那么如果您将显示旋转 两次,您将失去 A
的状态(不可见的状态)。问题是在这种情况下没有调用 onCreateView()
,只有 onCreate()
。所以后来,在 onSaveInstanceState()
中没有可以保存状态的视图。必须存储然后保存在 onCreate()
中传递的状态。
App.VSTUP
和 App.STAV
都是字符串标签,代表他们试图获取的对象。示例:savedState = savedInstanceState.getBundle(savedGamePlayString);
或 savedState.getDouble("averageTime")
在最新的支持库中,这里讨论的解决方案都不再需要了。您可以使用 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);
}
...
}
但是请注意,在恢复片段的隐藏状态时当前存在 bug。如果您在活动中隐藏片段,则在这种情况下您需要手动恢复此状态。
FragmentPagerAdapter
或 FragmentStatePagerAdapter
实现的默认行为。例如,如果您查看 FragmentStatePagerAdapter
的代码,您将看到 restoreState()
方法从您在创建适配器时作为参数传递的 FragmentManager
恢复片段。
我只想提供我想出的解决方案,它可以处理我从 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);
}
...
}
感谢 DroidT,我做到了:
我意识到如果 Fragment 不执行 onCreateView(),它的视图就不会被实例化。因此,如果返回堆栈上的片段没有创建它的视图,我会保存最后存储的状态,否则我会使用我想要保存/恢复的数据构建我自己的包。
1)扩展这个类:
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)在你的片段中,你必须有这个:
@Override
protected boolean hasSavedState() {
Bundle state = getSavedState();
if (state == null) {
return false;
}
//restore your data here
return true;
}
3)比如可以在onActivityCreated中调用hasSavedState:
@Override
public void onActivityCreated(Bundle state) {
super.onActivityCreated(state);
if (hasSavedState()) {
return;
}
//your code here
}
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.hide(currentFragment);
ft.add(R.id.content_frame, newFragment.newInstance(context), "Profile");
ft.addToBackStack(null);
ft.commit();
getFragment()
即mContent = (ContentFragment)getSupportFragmentManager().getFragment(savedInstanceState, "mContent");