β

【译】靠谱的保存/恢复Activity和Fragment的状态的最佳实践。(StatedFragme

Kyle's blog 2043 阅读

原文链接: http://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en

几个月之前,我发布了一篇关于如何保存和恢复Fragment状态的博文: Probably be the best way (?) to save/restore Android Fragment’s state so far 。收到了很多世界各地的Android开发者的宝贵反馈,非常感谢大家=)

不管怎么说,StatedFragment打破了设计模式,Android设计它的本来目的是让开发者更加容易地理解Fragment状态的保存和恢复,就好像Activity的做法一样(View的状态和实例的状态同时保存)。所以我做了一个实验,使用StatedFragment进行开发然后看它是怎么做的。是更容易理解了吗?还是这个模式对开发者更友好?

现在,在2个月的试验之后,我想我已经找到答案了。虽然StateFragment确实相对比较容易理解一些但是随之而来的还有一个相当大的问题。它打破了Android的View结构的设计模式。所以我认为它有会造成长远影响的问题,这是非常糟糕的。事实上,我已经开始对我自己的代码感到担忧了……

处于这个原因,我觉得从现在开始,废弃使用StatedFragment了。并且为了弥补我的错误,我写了这篇博客来展示如何按照Android的设计模式保存和恢复Fragment的状态,这次非常靠谱。

理解Activity的状态保存和恢复时发生了什么。

当Activity的onSaveInstanceState调用的时候,Activity会自动收集View层级中每个View的状态。请注意只有在内部实现了View的保存/恢复状态方法的View才会被收集到。一旦onRestoreInstanceState方法被调用,Activity会把收集的数据发送回给View结构树中具有相同android:id配置的View。

来看一下这个过程:

Activity state saving

Activity state restoring

这就是EditText中输入的文字即使在Activity被销毁的情况下依然会永久保存并且不需要我们做其他特别的事情的原因。这并不是魔法,那些View的状态被自动收集保存并恢复了。

这同时解释了为什么那些没有定义android:id的View不能恢复他们的状态。

虽然View的状态被自动保存了,但是Activity的成员变量并没有。他们会随着Activity一并销毁。你需要通过onSaveInstanceState和onRestoreInstanceState手动保存并恢复这些变量。

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 state saving

Fragment state restoring

这表示,所有的成员变量也同时被销毁了。你需要通过 onSaveInstanceState onActivityCreated 方法手动保存和恢复这些变量。请注意在Fragment内部,没有onRestoreInstanceState方法。

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从后退栈回来,它的的视图会被销毁并重建。

Fragment's life-cycle

这种情况下,Fragment并没有销毁,销毁的只是Fragment内部的视图。结果是并不会发生实例的保存。那么那些重建的View会发生什么呢?

没问题。Android就是这么设计的。这种情况下View状态的保存和恢复会在内部自动调用。作为结果,所有在内部实现了View的保存/恢复状态方法的View,例如EditText和配置了android:freezeText=”true”的TextView,他们的状态都会被自动保存和恢复。显示效果和之前说的一模一样

Fragment view state saving/restoring

请注意这种情况下只有视图被销毁(并重建),Fragment并没有,内部的成员变量也没有。所以你不需要再做任何事情,不需要额外代码。

public class MainFragment extends Fragment {
// These variable still persist in this case
private int someVarA;
private String someVarB;
...
}

你可能已经注意到了每个View都内部实现了View的保存/恢复方法,是不需要做任何工作的。因为这种情况下View的状态会自动恢复而且同时成员变量也一直存在着。

所以第一种情况下的Fragment状态保存和恢复的最佳实践是……

##项目中用到的每个View控件都必须在内部实现保存/恢复方法。

Android通过onSaveInstanceState和onRestoreInstanceState方法提供了视图进行保存和恢复的机制。这是开发者需要实现的:

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
}
...
}

基本上每个标准View控件例如EditText、TextView、CheckBox等等,都已经实现了这些方法。不过你可能需要去启用这些功能,比如对于TextView,你需要设置android:freezeText属性为true。

但是如果如果我们讨论下网上的第三方自定义控件,我要说它们中的很多还没有实现这部分代码,在使用过程中,可能会导致大问题。

如果你决定使用任何第三方的自定义控件,你应该保证它已经在内部是实现了View状态的保存/恢复方法,或者继承一个这个类的子类来自己实现这两个方法。

//
// 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都实现这个方法是非常重要的。

并且别忘了对每个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");
}
...
}

让我重复一遍。别在Fragment的onSaveInstanceState方法里面保存View的状态,反过来也一样。

到这里就结束了。这就是保存/恢复Activity,Fragment,View的状态的最佳实践。希望对你们有用=)

再见了StatedFragment,欢迎NestedActivityResultFragment

请按上面描述的方法保存/恢复Activity,Fragment,View的状态。让我们把StatedFragment标记废弃吧。

还有,StatedFragment的onActivityResult回调特性在NestedFragment中仍然是可用的。为了避免在未来会造成误解,我决定把这个功能放到一个新的类当中,NestedActivityResultFragment,在v0.10.0及后续版本可用。

更多的信息参见 https://github.com/nuuneoi/StatedFragment ,欢迎查看。

希望这篇博客中的动画可以帮助你清晰的了解Activity,Fragment,View的状态保存。并且对之前那篇文章的误导深表歉意。^^”

anyShare 分享到:

发表评论