[译]Android Activity 和 Fragment 状态保存与恢复的最佳实践

译者亦枫注:对于 Activity、Fragment 和 View 是如何保存与恢复状态的问题,相信不少开发人员都处于只知其一;不知其二的状态。最近恰好在总结 Fragment 的使用注意事项,无心中从网上看到国外的一篇好文,对这个问题作了一个全面的解析。加之使用可视化的动画效果,使咱们理解起来更加轻松。拜读事后,豁然开朗,同时不得不感慨,国外做者对于知识通透的理解能力和写做清晰的表达能力。而后,而后就必定要翻译过来,加以学习并保存记录之。java

原文:The Real Best Practices to Save/Restore Activity's and Fragment's state. (StatedFragment is now deprecated)android

做者:「nuuneoi」,一名拥有六年安卓应用程序开发经验和超过十二年手机端应用开发行业经验的全栈工程师。git

几个月前我发表了一篇有关 Fragment 状态保存和恢复的文章:多是目前为止保存和恢复 Fragment 状态的最佳方式(亦枫注:该文章已被删除,但 GitHub 上依然保有代码实现,可参考 StatedFragment。另外,我发现中外做者在标题设定上怎么套路都是一致的 ^_^)。这篇文章收到了来自世界各地安卓开发人员的较有价值的反馈。很是感谢大家 =)github

不管如何,StatedFragment 打破了常规设计模式,以一种不一样的方式实现,就像 Android 设计 Fragment 之初就假定可以让安卓开发人员更容易理解 Fragment 的状态保存和恢复,如同 Activity 的作法同样(View 状态与 Instance 状态同时变迁)。因此我作了一个实验,开发出 StatedFragment 并看看到底能发展成怎样。是否更容易理解?这种模式是否更加利于开发?设计模式

此刻,经历了两个月的实践,我想我已经获得告终果。尽管 StatedFragment 理解起来稍微容易一些,但仍是遇到了一个大问题。StatedFragment 打破了 Android View 架构的设计模式,因此我想这会致使一个长久的负面问题。事实上,我已经开始感受到个人代码有些怪怪的了......微信

出于这个缘由,我决定从如今开始废弃 StatedFragment。同时为了对这个错误的出现表示歉意,我写下这篇博文,向大家展现如何用 Android 的设计方式保存和恢复 Fragment 状态的最佳实践。架构

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


当 Activity 的 onSaveInstanceState 方法被调用时,Activity 会自动收集 View Hierachy(视图层次)中每个 View 的状态。请注意,只有内部实现了 View 类状态保存和恢复方法的控件才能被收集状态数据。一旦 onRestoreInstanceState 方法被调用,Activity 将这些收集的数据回传给 View Hierachy 中的 View,而这种回传时数据与 View 一一对应关系的依据就是 View 提供以前保存数据时的相同 id,一般在布局中经过 android:id 属性定义的。app

让咱们经过可视化动画效果看一下:ide

Activity State Saving

Activity State Restoring

这就是为何输入在 EditText 中的文本内容在 Activity 已经被销毁同时咱们不用作任何事情的状况下依然可以保存的缘由。这没什么难以想象的。这些 View 的状态会自动被收集和恢复回来。布局

同时这也是为何那些没有定义 android:id 属性的 View 不能恢复状态的缘由。

虽然这些 View 的状态能够被自动保存,可是 Activity 成员变量却不行。他们将随着 Activity 一块儿被销毁。你不得不经过 onSaveInstanceStateonRestoreInstanceState 方法手动保存和恢复这些成员变量。

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 Instance 状态和 View 状态你所须要作的事情。

Fragment 状态保存和恢复时发生了什么


假设 Fragment 被系统销毁,就会像 Activity 那样发生全部事情:

Fragment State Saving

Fragment State Restoring

也意味着每个成员变量也被销毁。你不得不经过 onSaveInstanceStateonRestoreInstanceState 方法分别手动保存和恢复这些成员变量。但请注意,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 从回退栈(BackStack)中返回时,View 将会被销毁和重建。

这种状况属于,Fragment 没有被销毁,但 Fragment 的 View 被销毁。所以,没有发生 Instance 状态保存。那么那些经过 Fragment 生命周期从新建立的 View 发生了什么呢?

不是问题。Android 是这么设计的。在这种状况下,View 状态保存和恢复在 Fragment 内部被调用。所以,每个内部实现 View 类保存和恢复方法的 View,例如 EditText 或者 TextView,只要设置了 android:freezeText="true",都将被自动保存和恢复状态。数据和 View 的对应呈现关系和上面同样。

Fragment From BackStack

须要注意的是在这种状况下只有 View 被销毁和重建。Fragment 实例仍然在那儿,包括实例里的成员变量。因此你不须要对成员变量作任何事情。不须要额外添加任何代码:

public class MainFragment extends Fragment {

    // These variable still persist in this case
    private int someVarA;
    private String someVarB;

    ...

}复制代码

你可能已经注意到,若是 Fragment 中使用到的每个 View 内部都实现了 View 类恢复和保存的方法,在这种状况下你就不须要作任何事情,由于 View 状态会自动恢复而且 Fragment 中的成员变量也仍然存在。

因此,有关 Fragment 状态保存和恢复最佳实践的第一个条件是...

你项目中用到的每个 View 内部必须实现状态保存和恢复方法


Android 提供了一个经过 onSaveInstanceStateonRestoreInstanceState方法用于 View 内部保存和恢复状态的机制。开发人员在自定义 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
    }

    ...

}复制代码

基本上每个单独的标准的 View 控件,如 EditTextTextViewCheckbox 等,都在内部实现了这些事情。而你所须要作的就是开启这个功能,好比你必须设置TextViewandroid:freezeText 属性值为 true 来使用这个功能。

可是若是是来自网上的第三方库里面的自定义 View 呢?我不得不说他们中的不少都没有实现这部分代码而致使咱们在实际使用过程当中出现很大的问题。

若是你决定使用第三方自定义 View,你必须保证这些 View 内部已经实现 View 状态保存和恢复,不然你必须建立一个子类继承自这些 View 而且本身实现 onSaveInstanceStateonRestoreInstanceState 方法。

//
// 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 都要实现这部分代码。

同时也不要忘记分配 android:id 属性给 Layout 布局中你须要支持状态保存和恢复的每个 View,不然这些 View 根本不会支持恢复状态。

<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 状态,反之亦然。

StatedFragment


请按上面说起的方式保存和恢复 Activity、Fragment 和 View 的状态。如今让我将 StatedFragment 标记废除。

然而 StatedFragment 在嵌套 Fragment 中获取 onActivityResult 的功能使用起来仍然不错。为了不未来产生疑惑,我决定从 v0.10.0 版本开始将这个功能单独拆分到一个新的命名为 NestedActivityResultFragment 的类中。

有关它的更多信息都在网址 github.com/nuuneoi/Sta…,请随时自由查阅。

但愿这篇博文中的可视化动画可以帮助你清晰地理解 Activity 、Fragment 和 View 恢复状态的方式。另外对于以前文章形成的困惑表示歉意。>_<

欢迎关注我


本文由 亦枫 创做并首发于 亦枫的我的博客 ,同步推送我的微信公众号:技术鸟(NiaoTech),欢迎关注。

相关文章
相关标签/搜索