onSaveInstanceStateについてちゃんと知る

[Android Advent Calendar 2017](https://qiita.com/advent-calendar/2017/android) 8日目の記事です。(遅れてしまいましたすみません...)

Activityの再生成に備えるため、onSaveInstanceStateを利用してViewやFragment、メンバ変数などを保存して復元するということが多々あります。

再生成については、AndroidManifestのconfigChangeで対応するのか、FragmentならsetRetainInstanceを設定するのかなど、画面によって適切な選択をする必要があり、何気なく使っていたのですが仕組みをちゃんと理解すべく書いていこうと思います。


# onSaveInstanceStateはいつ呼ばれるか
onPauseの直後に呼ばれる。

```
12-09 22:57:13.727 D/Lifecycle: onCreate
12-09 22:57:13.879 D/Lifecycle: onStart
12-09 22:57:13.889 D/Lifecycle: onResume
--------------- 画面回転など -----------------------
12-09 22:57:19.375 D/Lifecycle: onPause
12-09 22:57:19.375 D/Lifecycle: onSaveInstanceState
12-09 22:57:19.379 D/Lifecycle: onStop
12-09 22:57:19.379 D/Lifecycle: onDestroy
12-09 22:57:19.434 D/Lifecycle: onCreate
12-09 22:57:19.507 D/Lifecycle: onStart
12-09 22:57:19.508 D/Lifecycle: onRestoreInstanceState
12-09 22:57:19.510 D/Lifecycle: onResume
```

参考:

- 
[Activityのライフサイクル再確認](https://qiita.com/calciolife/items/39b2696a9a03e8591d40#%E5%85%A8%E4%BD%93-1)

# onCreateとonRestoreInstanceState
onSavedInstanceStateで保存した値は、onCreateもしくはonRestoreInstanceStateで復元が可能。

```java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        score = savedInstanceState.getInt(STATE_SCORE);
        level = savedInstanceState.getInt(STATE_LEVEL);
    } 
    ...
}

@Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        score = savedInstanceState.getInt(STATE_SCORE);
        level = savedInstanceState.getInt(STATE_LEVEL);
    }
```

onRestoreInstanceStateはonStartの直後に呼ばれるが、常に呼ばれるわけではなく、保存後にActivityが破棄された次のライフサイクルのタイミングでのみ呼ばれます。
onRestoreInstanceStateが呼ばれる場合には、onCreateも呼ばれているはずでなので、復旧のタイミングを分けたい場合に使い分ける。
onRestoreInstanceStateではnullチェックが不要です。

参考:

- [Android Developers - onRestoreInstanceState](https://developer.android.com/reference/android/app/Activity.html#onRestoreInstanceState(android.os.Bundle))

# Bundleはどこに保存される？
Bundleは前述のタイミングでonSavedInstanceStateが呼ばれた後、アプリのプロセスからSystemServerのプロセスのActivityRecord(各Activityのキャッシュのようなもの)に保存されます。

SystemServerプロセスは前面に出ているアプリや、通話アプリのような永続化プロセスよりも優先度が高くなっているため、相当な異常がない限りはkillされません。

参考:

- [Androidを支える技術II スタックから見た！Acticityのライフサイクル](http://gihyo.jp/book/2017/978-4-7741-8861-4)

# 保存しない方が良いもの
BundleはSystemServerのプロセスのメモリを使用するので、大きいデータを保存するとSystemServerのメモリが大きくなり、メモリ確保のために、バックグラウンドのActivityなどがkillされやすくなってしまいます。

Architecture Components のViewModelがこの問題に対するアプローチとして用意され、Activityよりも長いライフサイクルで存在して状態を保持したりといったことができる。

参考:

- [Android Developers - アクティビティの再作成](https://developer.android.com/training/basics/activity-lifecycle/recreating.html?hl=ja)
- [Android Developers - ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel.html#the_lifecycle_of_a_viewmodel)

# どのように保存されるか
ActivityクラスのonSaveInstanceStateは以下のようになっていて、ViewやFragment, Autofillの状態が保存されています。

```java
protected void onSaveInstanceState(Bundle outState) {
    outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());

    outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    if (mAutoFillResetNeeded) {
        outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
        getAutofillManager().onSaveInstanceState(outState);
    }
    getApplication().dispatchActivitySaveInstanceState(this, outState);
}
```
mWindow.saveHierachyStateによってViewツリーのルートから状態を保存していきます。

カスタムViewなどを作っているとViewもonSavedInstanceStateやonRestoreInstanceStateを持っていることに気づきますが、ActivityのonSaveInstanceStateが走ったタイミングで、このsaveHierachyStateから段階的に子ViewのonSaveInstanceStateが走るようになっています。

View.javaを見るとsaveHierachyStateはdispatchSaveInstanceStateを呼び出すことで子ViewのonSaveInstanceStateを呼び出してParcalableに状態を保存していきます。

```java:View.java
public void saveHierarchyState(SparseArray<Parcelable> container) {
    dispatchSaveInstanceState(container);
}

protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
        mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
        Parcelable state = onSaveInstanceState();
        if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
            throw new IllegalStateException(
                    "Derived class did not call super.onSaveInstanceState()");
        }
        if (state != null) {
            // Log.i("View", "Freezing #" + Integer.toHexString(mID)
            // + ": " + state);
            container.put(mID, state);
        }
    }
}

@CallSuper
@Nullable protected Parcelable onSaveInstanceState() {
    mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
    if (mStartActivityRequestWho != null || isAutofilled()
            || mAutofillViewId > LAST_APP_AUTOFILL_ID) {
        BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);

        if (mStartActivityRequestWho != null) {
            state.mSavedData |= BaseSavedState.START_ACTIVITY_REQUESTED_WHO_SAVED;
        }

        if (isAutofilled()) {
            state.mSavedData |= BaseSavedState.IS_AUTOFILLED;
        }

        if (mAutofillViewId > LAST_APP_AUTOFILL_ID) {
            state.mSavedData |= BaseSavedState.AUTOFILL_ID;
        }

        state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
        state.mIsAutofilled = isAutofilled();
        state.mAutofillViewId = mAutofillViewId;
        return state;
    }
    return BaseSavedState.EMPTY_STATE;
}
```
再生成によって、Viewがうまく表示されないことがあれば、この辺を追いかければ良いのかなと思います。

参考:

 - [Androidを支える技術II　ActivityとViewツリーの狭間で](http://gihyo.jp/book/2017/978-4-7741-8861-4)


## コラム
ActivityRecord.javaをみていたら以下のような変数があり、

```java
Bundle  icicle;         // last saved activity state
```
Icepickライブラリ由来これなんだなーってなって楽しくなってたら昔はsavedInstanceStateはicicleって名前だったのですね。歴史の勉強になりました。

参考:

- [Icepickにノスタルジックな変更が入りました](http://blog.nkzn.info/entry/2015/07/24/014438)