[譯] 保存/恢復 Activity 和 Fragment 狀態的最佳實踐

好久以前看過的一篇文章,寫的很好。轉載並譯於:https://inthecheesefactory.co...java

幾個月之前,我發佈了一篇關於Fragment狀態保存和恢復的文章,那多是目前爲止最好的方式用於保存/恢復 Android Fragment 的狀態。我收到了不少來自世界各地的Android開發者有價值的反饋。十分感謝大家 =)android

不管如何,StatedFragment打破了設計模式,我使用了不一樣於Android狀態保存/恢復的方式來設計它,這樣作的目的是爲了讓Android開發者可以更簡單的理解Fragment狀態的保存與恢復,就像Activity作的同樣(同時處理View狀態和實例狀態),因此我經過開發StatedFragment作了一個實驗,而且看看它是怎麼作的,以及StatedFragment這樣的設計是否更容易被理解?是否對開發者更加友好?git

如今,兩個月的實驗過去了,我相信我已經獲得告終果。雖然StatedFragment 有點容易被理解,可是它同時帶來了很大的問題。它破壞了Android View的基本框架.因此我認爲這是很糟糕的,可能會致使長遠的影響。事實上我已經對本身的代碼感到擔心了...github

因爲這個緣由,我決定從如今開始棄用StatedFragment。而且,爲了彌補我錯誤,我寫了這篇文章,用可見的方式來展現基於Android的設計如何保存和恢復Fragment的狀態的最佳實踐。設計模式

理解在Activity的狀態被保存/恢復的時候發生了什麼

當Activity的onSaveInstanceState被調用的時候,Activity將會從View 層次(View Hierachy)中的每個View中自動蒐集View的狀態。請注意,只會蒐集實現了View狀態保存/恢復的內部方法的View的數據。一旦onRestoreInstanceState被調用,Activity將會將這些蒐集到的數據一對一的返還給View 層次裏在蒐集的時候提供了一樣的android:id屬性的View。框架

讓咱們看看視覺上的效果。ide

圖片描述

圖片描述

這就是爲何儘管Activity已經被銷燬,而咱們並無作一些特別的事情來保存狀態,可是EditText中鍵入的文本仍然可以呈現的緣由。這並非什麼魔法,這些View 的狀態已經被自動的保存和恢復回來了。this

這也是爲何View 在沒有被設置android:id屬性的時候不能保存和恢復本身的狀態的緣由。spa

儘管這些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實例的狀態和View 狀態須要作的。

理解在Fragment的狀態被保存/恢復的時候發生了什麼

若是Fragment被系統銷燬,全部事情都會發生的像Activity發生的那樣。

這意味着每個單獨的成員變量被銷燬了。你必須分別地經過onSaveInstanceStateonActivityCreated方法,手動的保存和恢復這些變量。

請注意在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從後退棧中返回,它的View 會被銷燬,並從新建立

在這種狀況下,Fragment並不會被銷燬,只有Fragment中的View 會被銷燬。 結果是,並不會發生任何實例狀態的保存。可是上面展現的這些View在Fragment生命週期中被從新建立時,發生了什麼?

別驚訝,由於Android是這樣設計的。在這種狀況下,Fragment中的View 狀態的保存/恢復會被內部調用。結果就是,每個實現了內部View 狀態保存/恢復的View ,將會被自動的保存而且恢復狀態,例如帶有android:freezeText="true"屬性的EditText或者TextView。就以前完美顯示的同樣。

請注意,在這種狀況下,只有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 狀態的機制。開發者的任務就是實現它。

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
    }

    ...

}

基本上來講,每個單獨的Android提供的的標準View 組件都已經在內部完成了這些事情,例如EditText,TextView,Checkbox.
儘管也許你手動讓它生效,例如,你須要爲TextView設置android:freezeText爲true,來使用這個功能。

可是若是咱們討論關於互聯網上貢獻的第三方的自定義組件。我必須說,它們大多數都沒有實現這部分的代碼,在使用中會致使很大的問題。

若是你決定使用第三方的自定義組件,你不得不確保它已經在內部實現了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實現這部分都是很重要的。

而且不要忘記爲每個你須要開啓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/St...,請隨時查看。

但願這篇文章中這種動態圖片的方式可以幫助大家清楚的理解Activity,Fragment,View狀態的恢復。對於前一篇文章的致使的混淆,我感到十分抱歉。^^"

相關文章
相關標籤/搜索