概要
FragmentTransactionで任意のFragmentをcommitするとき、IllegalStateException回避でFragmentTransaction#commitAllowingStateLossを使うケースがありますが、commitAllowingStateLossを使った場合にどうなるかを軽く解説します。
詳細
commitAllowingStateLossのjavadoc
https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()
Like commit() but allows the commit to be executed after an activity's state is saved. This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user.
ポイントは「This is dangerous because the commit can be lost if the activity needs to later be restored from its state」の部分、google先生の直訳では「これは、後でアクティビティを状態から復元する必要がある場合にコミットが失われる可能性があるため、危険です。」とのこと。分かるようで分からん。。。
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
commitAllowingStateLossしないときはIllegalStateExceptionでonSaveInstanceStateの後にはcommitできないよ!って怒られる。
先ほどのjavadocを理解するためにもActivity#onSaveInstanceStateのソースを見てみる。
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);
}
Parcelable p = mFragments.saveAllState();
とoutState.putParcelable(FRAGMENTS_TAG, p);
でFragmentのオブジェクトをまるっとセーブしているもよう。
セーブしたFragmentはどこで復元されるかというと。。
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
if (getApplicationInfo().targetSdkVersion > O && mActivityInfo.isFixedOrientation()) {
final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
ta.recycle();
if (isTranslucentOrFloating) {
throw new IllegalStateException(
"Only fullscreen opaque activities can request orientation");
}
}
if (mLastNonConfigurationInstances != null) {
mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
}
if (mActivityInfo.parentActivityName != null) {
if (mActionBar == null) {
mEnableDefaultActionBarUp = true;
} else {
mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
}
}
if (savedInstanceState != null) {
mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED, false);
mLastAutofillId = savedInstanceState.getInt(LAST_AUTOFILL_ID,
View.LAST_APP_AUTOFILL_ID);
if (mAutoFillResetNeeded) {
getAutofillManager().onCreate(savedInstanceState);
}
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.fragments : null);
}
mFragments.dispatchCreate();
getApplication().dispatchActivityCreated(this, savedInstanceState);
if (mVoiceInteractor != null) {
mVoiceInteractor.attachActivity(this);
}
mCalled = true;
}
onCreateの中、Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
でセーブしたFragmentを取得し、mFragments.restoreAllState(p, mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.fragments : null);
で復元。
onSaveInstanceStateでFragmentをまるっとセーブし、onCreateでまるっとセーブしたFragmentを復元している実装がポイントのようです。
##Activityの実装を考慮すると
commitAllowingStateLossの「アクティビティを状態から復元する必要がある場合にコミットが失われる」という意味は、onSaveInstanceStateのあとにcommitAllowingStateLossで見た目的にFragmentをcommitしても、onSaveInstanceStateで既にFragmentのセーブは終わっているから、onSaveInstanceState~commitAllowingStateLossの間の変更は保持されないよ。Activity再生成のときに状態を復元できないよって意味と理解。
つまるところcommitAllowingStateLossの利用は、状態の保持が不要なときのみに限ったほうが良いということですね。
##終わりに
初めてcommitAllowingStateLossに出会ったとき、いったい何がLossされるんだろうと悩んで動作確認用のPG作って、あぁなるほどLossしてるなぁって自己解決したのですが、そのコードは失われてしまって今回は具体的に何がLossされるかを説明できないままです。。すみません。。。