理解Activity的状态保存和恢复的流程
当调用Activity中的onSaveInstanceState方法的时候Activity会把整个界面中的每一个View的状态都保存起来,但是请注意只有View的内部实现了状态的保存/恢复方法那么状态才会被保存起来。一旦onRestoreInstanceState方法被调用了Activity就会把之前保存每一个View的状态根据android:id一个一个恢复回去。
我们根据动图来深入浅出理解:
这就是为什么界面中的EditText明明Activity已经被销毁了也没有做任何对数据的操作但是当Activity重建的时候EditText仍然有值,这不是什么魔法这是因为View状态能够自我保存和恢复。
这也充分证明了为什么一个View没有赋予android:id是无法自动保存和恢复状态。
尽管界面中的那么View的状态能够自我保存和恢复但是Activity中声明的变量就无法做到这点,如果你没有在onSaveInstanceState和onRestoreInstanceState两个方法中实现保存和恢复那么成员变量就会随Activity一起销毁了。
public class MainActivity extends AppCompatActivity {
// These variable are destroyed along with Activity
private int someVarA;
private String someVarB;
...
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("someVarA", someVarA);
outState.putString("someVarB", someVarB);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
someVarA = savedInstanceState.getInt("someVarA");
someVarB = savedInstanceState.getString("someVarB");
}
}
这就是所有你想恢复Activity和View的状态所必须做的事情。
理解Fragment的状态保存和恢复的流程
一旦Fragment被系统销毁了内部的流程跟Activity其实是相似的。
这就意味着Fragment中的成员变量将会一起被销毁了,所以你需要通过实现onSaveInstanceState和onActivityCreated方法来保存和恢复它,请注意Fragment中恢复的函数不是onRestoreInstanceState而是onActivityCreated方法。
public class MainFragment extends Fragment {
// These variable are destroyed along with Activity
private int someVarA;
private String someVarB;
...
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("someVarA", someVarA);
outState.putString("someVarB", someVarB);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
someVarA = savedInstanceState.getInt("someVarA");
someVarB = savedInstanceState.getString("someVarB");
}
}
对Fragment来讲我们有必要知道一些区别于Activity的特殊情况:一个Fragment从后退栈中恢复的话它的内部的Views将会被销毁和重新创建,这种情况下其实Fragment并没有被销毁只是内部的View是被销毁了。最终并没有什么状态发生保存和恢复,那么在Fragment恢复的时候这些View发生了什么呢?
这种情况Android是这么设计:View的状态会在Fragment内部实现自我保存和恢复,当然每个View只要内部实现了保存/恢复方法。比如说EditText或者TextView有android:freezeText将会自我保存和恢复的,使得跟之前的显示的方式一样。
我们需要注意的是这种情况下Fragment并没有被销毁,跟内部成员变量一样,但是这些View会自我销毁和重创,所以你不需要为他们做任何处理也不必要写额外的代码。
public class MainFragment extends Fragment {
// These variable still persist in this case
private int someVarA;
private String someVarB;
...
}
你是否意识到了如果Fragment内部的Views都实现了保存/恢复的方法那么我们可以像成员变量一样不需要为他做写额外的代码。所以说要实现自我保存和恢复的第一个条件就是该View内部实现了状态的保存和恢复了。
Android中提供了onSaveInstanceState和onRestoreInstanceState来实现View的状态保存和恢复,我们开发人员只要实现他们就好了。
public class CustomView extends View {
...
@Override
public Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
// Save current View's state here
return bundle;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
// Restore View's state here
}
...
}
如果我们基于EditText、TextView、Checkbox等等View,这些内部已经实现状态的保存和恢复功能,不论什么时候如果你想启动它,比如说TextView你需要把android:freezeText属性设置为true。
如果一个自定义View如果没有实现保存和恢复方法那么在现实使用中会出现各种难以预料的问题。
所以如果你要使用一个自定义View那么要么基于那些内部有自我保存和恢复的View作为基类,否则就需要自己重写onSaveInstanceState/onRestoreInstanceState方法。
//
// Assumes that SomeSmartButton is a 3rd Party view that
// View State Saving/Restoring are not implemented internally
//
public class SomeBetterSmartButton extends SomeSmartButton {
...
@Override
public Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
// Save current View's state here
return bundle;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
// Restore View's state here
}
...
}
如果你自定义一个View或者ViewGroup不要忘了实现这两个方法,因为无乱什么类型的View被应用所使用必然通过这两个方法所以他们很重要。
当然不要忘了给布局中的View的android:id属性设置值,没有的话状态将无法被保存和恢复。
<EditText
android:id="@+id/editText1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/editText2"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/cbAgree"
android:text="I agree"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
到此我们基本上已经大功告成了。
明确的把Fragment的状态和View的状态区分开来
为了让代码更整理易维护我们有必须把Fragment和View的状态保存代码分开实现,任何属于View的状态保存/恢复在View中,属于Fragment则写在Fragment中,明确的把他们区分开来。
public class MainFragment extends Fragment {
...
private String dataGotFromServer;
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("dataGotFromServer", dataGotFromServer);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
dataGotFromServer = savedInstanceState.getString("dataGotFromServer");
}
...
}
最后一定要把View和Fragment的状态分开自我保存。
来自文献:http://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en