tl;dr
onPause ↓ onStop ↓ (onDestroyView) ↓ onCleared ↓ onDestroy
Jetpack ViewModel のドキュメント には Activity と紐つけた際の owner の lifecycle event と ViewModel の生存期間の関係についての図が載っている。
この図では ViewModel#onCleared
の実行は Activity#onDestroy
実行より後のタイミングで起こるように描かれており、そのイメージから ViewModel 登場以来ずっと ViewModel#onCleared
は onDestroy
より後に実行されるものと思っていたが、現在のコードを読んでみると実際はそうではなく onDestroy()
の前に実行されることに気がついた。この記事では ViewModel
破棄の実際の流れを追っていく。適当な ViewModel の onCleared()
が呼ばれる時の stacktrace を見ればわかる話なので、それで十分という人はここで読むのをやめて実際にそちらを見にいくと良い。
この記事の内容は以下の version で動作を確認している:
androidx.activity
:1.0.0-alpha01
および1.2.0
androidx.fragment
:1.1.0-alpha01
および1.3.0
確認に使ったコードの repository:
ViewModel が破棄される仕組み
ViewModel は生成時に ViewModelStore
に登録される。この ViewModelStore
は ViewModel を HashMap
で保持しており、 ViewModelStore#clear
が呼ばれた時に保持している ViewModel の clear()
を実行し、保持している ViewModel を破棄する。 ViewModel#onCleared
は ViewModel#clear
内で実行される。保持している ViewModel の clear が終わったら、 map を clear して参照を切る。
ViewModelStore#clear
が呼ばれる流れ: Activity 編
Activity は ViewModelStore#clear
が呼ばれている箇所から遡って辿るとわかりやすいのでそのように説明する。
Activity が持つ ViewModelStore
の clear()
は、 androidx.activity.ComponentActivity
(以下 ComponentActivity
*1 ) の constructor で自身の Lifecycle に対して登録している LifecycleEventObserver
の中の1つで呼ばれている。
public ComponentActivity() { // ... getLifecycle().addObserver(new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event == Lifecycle.Event.ON_DESTROY) { // Clear out the available context mContextAwareHelper.clearAvailableContext(); // And clear the ViewModelStore if (!isChangingConfigurations()) { getViewModelStore().clear(); } } } }); // ... }
上に該当する箇所を抜粋したが、 Lifecycle.Event.ON_DESTROY
が通知された際、configuration change によるものではない場合に ViewModelStore#clear
が実行されることがわかる。
この ComponentActivity
(を継承した Activity) の Lifecycle に対して lifecycle event を送るのは誰かというと、 ReportFragment
という event 通知用の内部 class である。
ReportFragment
は、static method の injectIfNeededIn()
で渡した Activity の、 Android Framework の方の FragmentManager
にこの Fragment を add することで、 Activity の Lifecycle*2 に対して lifecycle event を通知する機能を持っている。実際の通知の仕組みは API level 29 以上とそれ未満とで異なり、API level 29 以上では通知機能を実装した Application.ActivityLifecycleCallbacks
を Activity に登録して、 29 未満では ReportFragment
の各 lifecycle の callback method を通して通知を実現している。
public static void injectIfNeededIn(Activity activity) { if (Build.VERSION.SDK_INT >= 29) { // On API 29+, we can register for the correct Lifecycle callbacks directly LifecycleCallbacks.registerIn(activity); } // Prior to API 29 and to maintain compatibility with older versions of // ProcessLifecycleOwner (which may not be updated when lifecycle-runtime is updated and // need to support activities that don't extend from FragmentActivity from support lib), // use a framework fragment to get the correct timing of Lifecycle events android.app.FragmentManager manager = activity.getFragmentManager(); if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) { manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit(); // Hopefully, we are the first to make a transaction. manager.executePendingTransactions(); } } // ... @RequiresApi(29) static class LifecycleCallbacks implements Application.ActivityLifecycleCallbacks { // ... }
この ReportFragment
が Lifecycle.Event.ON_DESTROY
を Activity に通知するのは ActivityLifecycleCallbacks#onActivityPreDestroyed
または ReportFragment#onDestroy
である。片方はそのままな名前をしているのでわかったようなものだが、これらが実行されるのは android.app.Activity
の performDestroy
中、前者は dispatchActivityPreDestroyed()
で、後者は mFragments.dispatchDestroy()
の先であり、 onDestroy
の前となる。
final void performDestroy() { dispatchActivityPreDestroyed(); // 筆者註: API >= 29 の場合はここで ViewModel が破棄される mDestroyed = true; mWindow.destroy(); mFragments.dispatchDestroy(); // 筆者註: API < 29 の場合はここで ViewModel が破棄される onDestroy(); EventLogTags.writeWmOnDestroyCalled(mIdent, getComponentName().getClassName(), "performDestroy"); mFragments.doLoaderDestroy(); if (mVoiceInteractor != null) { mVoiceInteractor.detachActivity(); } dispatchActivityPostDestroyed(); }
ComponentActivity
は onCreate
で ReportFragment#injectIfNeededIn
に自身を渡しており、それによって上記のように Lifecycle に対して event 通知を受け、 finish の際に ON_DESTROY
を受け取ったところで ViewModel を破棄している、という流れになる。
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { // ... super.onCreate(savedInstanceState); mActivityResultRegistry.onRestoreInstanceState(savedInstanceState); ReportFragment.injectIfNeededIn(this); // ... }
ViewModelStore#clear
が呼ばれる流れ: Fragment 編
androidx.fragment.app.Fragment
の場合は起点はわかりやすいが辿る経路が長いのと実際に呼ばれてる場所がちょっと不思議なのでとりあえずスタート地点から。
Fragment に紐つく ViewModelStore
の clear()
が呼ばれるきっかけになるのは FragmentActivity#onDestroy
, つまり Fragment#onDestroy
が呼ばれるのと同じである。そこから FragmentController
-> FragmentManager
-> SpecialEffectsController
-> FragmentStateManager
と進んでいって、この FragmentStateManager#moveToExpectedState
で FragmentStateManager#destroy
に入る。この経路自体は特に重要ではないので適当に端折ったが、実際に追ってみたい場合は stacktrace を出してそれに沿って読んでみると良い。
この FragmentStateManager#destroy
の中、その先で Fragment#onDestroy
が実行される mFragment.performDestroy()
の直前にある mFragmentStore.getNonConfig().clearNonConfigState(mFragment)
, ここで Fragment に紐つく ViewModelStore
の clear が行われる。
void destroy() { // ... boolean beingRemoved = mFragment.mRemoving && !mFragment.isInBackStack(); boolean shouldDestroy = beingRemoved || mFragmentStore.getNonConfig().shouldDestroy(mFragment); if (shouldDestroy) { // ... if (beingRemoved || shouldClear) { mFragmentStore.getNonConfig().clearNonConfigState(mFragment); } mFragment.performDestroy(); // ... } else { // ... } }
ここで NonConfig と呼ばれる FragmentManagerViewModel
は、端的に言うと Activity の ViewModelStore
に登録される ViewModel で、それぞれの Fragment に対応する ViewModelStore
の管理や Activity の再生成時に retain される Fragment の保持といった役割を持っている。 clearNonConfigState()
では、与えられた Fragment に対応する ViewModelStore
と子の NonConfig の clear および管理下からの削除を行っている。
void clearNonConfigState(@NonNull Fragment f) { if (FragmentManager.isLoggingEnabled(Log.DEBUG)) { Log.d(TAG, "Clearing non-config state for " + f); } // Clear and remove the Fragment's child non config state FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho); if (childNonConfig != null) { childNonConfig.onCleared(); mChildNonConfigs.remove(f.mWho); } // Clear and remove the Fragment's ViewModelStore ViewModelStore viewModelStore = mViewModelStores.get(f.mWho); if (viewModelStore != null) { viewModelStore.clear(); mViewModelStores.remove(f.mWho); } }
Fragment まわりのコードはややこしくて読むのに苦労するが、とりあえず FragmentStateManager#destroy
の中で Fragment#performDestroy
(= Fragment#onDestroy
の実行) の直前に紐ついてる ViewModel の clear が行われていることがわかった。
雑多な話題
なんでドキュメントは嘘ついてるの?
実装当初は強ち嘘というわけではなかった。もともと ViewModelStore#clear
は FragmentActivity
, Fragment
それぞれの onDestroy
の中で実行されていて、super#onDestroy
を override した method の最後で呼んだ場合は呼ばれる順番として onDestroy -> onCleared
と捉えることもできた。
いつから現在の挙動に変わったの?
動作確認している version で察しがつく人もいると思うが、 release version では AndroidX として一番最初である activity:1.0.0-alpha01
/ fragment:1.1.0-alpha01
からである。 Commit のレベルでは 2018-10-23 に commit された以下の2つ:
- Activity:
af41e83
- Fragment:
bdb582b
これを挙動の変更と言えるかは難しくて、 Activity のCLにつけられたコメントを見ると、 super.onDestroy()
を呼ぶタイミングがアプリ開発者に委ねられてしまっていることを問題視していて、現在のような挙動を常にすることを保証したかったように読み取れる。
Through discussions with adamp@, we wanted to make a firm decision on exactly when this will run as the final LifecycleObserver going out. onDestroy() does not offer the same guarantee since developers can call it at any point in their onDestroy() method.
実際 onPause
~ onDestroy
の super method を最初に呼ぶ流派と最後に呼ぶ流派があるようだし*3、またうっかり super.onDestroy()
を呼び忘れることによって clear されないということも起こりうるので、フレームワークの仕組みとしては現在の形の方が正しいのかもしれない。そうするとやっぱりドキュメントの図は嘘をついてることになってしまうので直すべきでは? となるが...
書いてて気づいたが Fragment の destroy と Fragment に紐つく ViewModel の clear はまだ FragmentActivity#onDestroy
に依存しているので、 super.onDestroy()
の呼び忘れで実行されないということがありうる。呼ばれる順序は常に同じにすることができたけど、そもそも呼ばれないという問題はまだ解消できていないようだ。
viewModelScope
について
viewModelScope
は ViewModel#clear
実行時、 ViewModel#onCleared
が呼ばれる前に cancel される。
FragmentActivity
の LifecycleRegistry
には2回 ON_DESTROY
が通知されるけど
上で説明した ComponentActivity
由来の経路と FragmentActivity#onDestroy
で実行されている mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
で計2回 ON_DESTROY
が LifecycleRegistry
に送られている。
1回目の event 通知時の ViewModelStore#clear
で保持していた ViewModel は全て切り離されるので ViewModel#onCleared
が2回呼ばれるようなことはない。
まとめ
Activity (API >=29)
ViewModel cleared at com.github.nashcft.app.ActivityViewModel.onCleared(ActivityViewModel.kt:23) at androidx.lifecycle.ViewModel.clear(ViewModel.java:138) at androidx.lifecycle.ViewModelStore.clear(ViewModelStore.java:62) at androidx.activity.ComponentActivity$4.onStateChanged(ComponentActivity.java:261) at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354) at androidx.lifecycle.LifecycleRegistry.backwardPass(LifecycleRegistry.java:284) at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:302) at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:148) at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:134) at androidx.lifecycle.ReportFragment.dispatch(ReportFragment.java:68) at androidx.lifecycle.ReportFragment$LifecycleCallbacks.onActivityPreDestroyed(ReportFragment.java:224) at android.app.Activity.dispatchActivityPreDestroyed(Activity.java:1498) at android.app.Activity.performDestroy(Activity.java:8241) at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1344) at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:5096) at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:5140) at android.app.servertransaction.DestroyActivityItem.execute(DestroyActivityItem.java:44) at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7656) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Activity (API < 29)
ViewModel cleared at com.github.nashcft.app.ActivityViewModel.onCleared(ActivityViewModel.kt:23) at androidx.lifecycle.ViewModel.clear(ViewModel.java:138) at androidx.lifecycle.ViewModelStore.clear(ViewModelStore.java:62) at androidx.activity.ComponentActivity$4.onStateChanged(ComponentActivity.java:261) at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354) at androidx.lifecycle.LifecycleRegistry.backwardPass(LifecycleRegistry.java:284) at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:302) at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:148) at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:134) at androidx.lifecycle.ReportFragment.dispatch(ReportFragment.java:68) at androidx.lifecycle.ReportFragment.dispatch(ReportFragment.java:144) at androidx.lifecycle.ReportFragment.onDestroy(ReportFragment.java:134) at android.app.Fragment.performDestroy(Fragment.java:2782) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1451) at android.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1576) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1637) at android.app.FragmentManagerImpl.dispatchMoveToState(FragmentManager.java:3046) at android.app.FragmentManagerImpl.dispatchDestroy(FragmentManager.java:3026) at android.app.FragmentController.dispatchDestroy(FragmentController.java:248) at android.app.Activity.performDestroy(Activity.java:7394) at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1306) at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4443) at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4476) at android.app.servertransaction.DestroyActivityItem.execute(DestroyActivityItem.java:39) at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:145) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:6669) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Fragment
ViewModel cleared at com.github.nashcft.app.FragmentViewModel.onCleared(FragmentViewModel.kt:23) at androidx.lifecycle.ViewModel.clear(ViewModel.java:138) at androidx.lifecycle.ViewModelStore.clear(ViewModelStore.java:62) at androidx.fragment.app.FragmentManagerViewModel.clearNonConfigState(FragmentManagerViewModel.java:199) at androidx.fragment.app.FragmentStateManager.destroy(FragmentStateManager.java:769) at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:350) at androidx.fragment.app.SpecialEffectsController$FragmentStateManagerOperation.complete(SpecialEffectsController.java:742) at androidx.fragment.app.SpecialEffectsController$Operation.cancel(SpecialEffectsController.java:594) at androidx.fragment.app.SpecialEffectsController.forceCompleteAllOperations(SpecialEffectsController.java:329) at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3116) at androidx.fragment.app.FragmentManager.dispatchDestroy(FragmentManager.java:3091) at androidx.fragment.app.FragmentController.dispatchDestroy(FragmentController.java:334) at androidx.fragment.app.FragmentActivity.onDestroy(FragmentActivity.java:322) at com.github.nashcft.app.MyFragmentActivity.onDestroy(MyFragmentActivity.kt:43) at android.app.Activity.performDestroy(Activity.java:8245) at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1344) at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:5096) at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:5140) at android.app.servertransaction.DestroyActivityItem.execute(DestroyActivityItem.java:44) at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7656) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)