タイトルがここで言いたい事の全てです
Android における ViewModel 側で onCreate 等のライフサイクルが走った時に何かしたい、みたいなシーンは結構あると思います。
(例えばユーザ情報を fetch する、とか)
そういった時どうやって ViewModel 側にイベントを通知するかと言うと
class SampleActivity: AppCompatActivity() {
private val viewModel: SampleViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
viewModel.onViewCreated()
}
}
class SampleViewModel: ViewModel() {
fun onViewCreated() {
// API 叩いたりレスポンスを LiveData に流したり etc...
}
}
とすると思います。一番シンプルです。
しかし、せっかく Android の Architecture Component です。
ViewModel の初期化やリソースの解放なんかを他のクラスに任せるのはナンセンスな気がしました。
個人的に View -> ViewModel の方向性は少なくしてあげて、View は流れてくるデータをバインドするだけにしたい。
ViewModel の LiveData を observe してから onCreate を呼んで...
みたいな事を View 側が考慮するのはなんだかなぁ、です
ViewModel 側でライフサイクルを考慮してあげた方がスッキリしませんか?
と、言うわけで ViewModel 側で紐づく View のライフサイクルをフックしてあげましょう。
ViewModel で Lifecycle.Event をフックする
ライフサイクル対応コンポーネントによるライフサイクルへの対応 | Android デベロッパー | Android Developers
ViewModel に LifecycleObserver を implement しましょう。
ViewModel が View のライフサイクルにリンクされて画面が表示される度にリロードとかそんな事ができます。
class SampleViewModel: ViewModel(), LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onViewCreated() {
// なんやかんや
}
}
class SampleActivity: AppCompatActivity() {
private val viewModel: SampleViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(viewModel)
}
}
実際には Dagger を使ったりしていると、こんな事をして上げたりするかもしれませんね。
inline fun <reified T : ViewModel> AppCompatActivity.provideViewModels(crossinline viewModels: () -> T): Lazy<T> {
return viewModels {
object : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return viewModels() as T
}
}
}
}
class SampleActivity: AppCompatActivity() {
private val viewModel: SampleViewModel by provideViewModels {
SampleViewModel(repository).also {
lifecycle.addObserver(it)
}
}
}
これで View 側は通知されてくるデータに専念できそうです
ライフサイクルのオーバーライドと OnLifecycleEvent のコールスタック
結論は ↑ までになりますが、どっちが先に呼ばれるかみたいな記事がなかなか見つからなかったので
実際 View 側のライフサイクルイベントと Lifecycle.Event をフックしている関数はどっちが先に呼ばれるのか。
Lifecycle.Event がどうハンドリングされているかは、コールスタックを追って見ていくと分かります。
なぜか API が 28 以下と 29 以上でアルゴリズムが変わっていましたが通知される順番は変わりません。
ポイントになる人たち
- LifecycleRegistory
ライフサイクルイベントをハンドリングして一括で通知してくれるクラスです。
イベントが来たら登録された LifecyclerObserver に対してイベントを通知します。
(実際には通知されるまでに Adapter とかありますが)
- ReportFragment
ライフサイクルを監視するための不可視の Fragment です。
Activity 生成時に勝手にヒエラルキーに入れられています。
この Fragment の on~ で対応したライフサイクルのイベントが LifecyleRegisotry に渡されます。
API 28 (Android 9.0) 以下の場合
API 28 以下の場合の Lifecycle.Event.ON_CREATE のコールスタックは以下です。
(アノテーション付けたメソッドはリフレクションで呼び出されてますね。)
onCreated:47, SampleViewModel (com.example.lifecycle)
invoke:-1, Method (java.lang.reflect)
invokeCallback:216, ClassesInfoCache$MethodReference (androidx.lifecycle)
invokeMethodsForEvent:194, ClassesInfoCache$CallbackInfo (androidx.lifecycle)
invokeCallbacks:185, ClassesInfoCache$CallbackInfo (androidx.lifecycle)
onStateChanged:37, ReflectiveGenericLifecycleObserver (androidx.lifecycle)
dispatchEvent:361, LifecycleRegistry$ObserverWithState (androidx.lifecycle)
forwardPass:300, LifecycleRegistry (androidx.lifecycle)
sync:339, LifecycleRegistry (androidx.lifecycle)
moveToState:145, LifecycleRegistry (androidx.lifecycle)
handleLifecycleEvent:131, LifecycleRegistry (androidx.lifecycle)
dispatch:68, ReportFragment (androidx.lifecycle)
dispatch:144, ReportFragment (androidx.lifecycle)
onActivityCreated:102, ReportFragment (androidx.lifecycle)
performActivityCreated:2531, Fragment (android.app)
moveToState:1318, FragmentManagerImpl (android.app)
moveFragmentToExpectedState:1576, FragmentManagerImpl (android.app)
moveToState:1637, FragmentManagerImpl (android.app)
dispatchMoveToState:3050, FragmentManagerImpl (android.app)
dispatchActivityCreated:3002, FragmentManagerImpl (android.app)
dispatchActivityCreated:183, FragmentController (android.app)
performCreate:7334, Activity (android.app)
performCreate:7318, Activity (android.app)
callActivityOnCreate:1271, Instrumentation (android.app)
performLaunchActivity:3094, ActivityThread (android.app)
handleLaunchActivity:3257, ActivityThread (android.app)
execute:78, LaunchActivityItem (android.app.servertransaction)
executeCallbacks:108, TransactionExecutor (android.app.servertransaction)
execute:68, TransactionExecutor (android.app.servertransaction)
handleMessage:1948, ActivityThread$H (android.app)
dispatchMessage:106, Handler (android.os)
loop:214, Looper (android.os)
main:7050, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:494, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:964, ZygoteInit (com.android.internal.os)
実際コードを順に追っていくと、
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
dispatchActivityPreCreated(icicle);
mCanEnterPictureInPicture = true;
restoreHasCurrentPermissionRequest(icicle);
// Activity の onCreate のオーバーライド達が走る
if (persistentState != null) {
onCreate(icicle, persistentState);
} else {
onCreate(icicle);
}
...
// Lifecycle.State が CREATED に変わるところ
mFragments.dispatchActivityCreated();
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
// API 29 以上はここで post される
dispatchActivityPostCreated(icicle);
}
public void dispatchActivityCreated() {
mStateSaved = false;
dispatchMoveToState(Fragment.ACTIVITY_CREATED);
}
private void dispatchMoveToState(int state) {
if (mAllowOldReentrantBehavior) {
moveToState(state, false);
} else {
try {
mExecutingActions = true;
moveToState(state, false);
} finally {
mExecutingActions = false;
}
}
execPendingActions();
}
void moveToState(int newState, boolean always) {
...
if (mActive != null) {
boolean loadersRunning = false;
// Must add them in the proper order. mActive fragments may be out of order
final int numAdded = mAdded.size();
for (int i = 0; i < numAdded; i++) {
// ReportFragment の State を更新する
Fragment f = mAdded.get(i);
moveFragmentToExpectedState(f);
if (f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
}
}
...
}
}
void moveFragmentToExpectedState(final Fragment f) {
int nextState = mCurState;
if (f.mRemoving) {
if (f.isInBackStack()) {
nextState = Math.min(nextState, Fragment.CREATED);
} else {
nextState = Math.min(nextState, Fragment.INITIALIZING);
}
}
moveToState(f, nextState, f.getNextTransition(), f.getNextTransitionStyle(), false);
...
}
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
...
switch (f.mState) {
case Fragment.CREATED:
...
//
f.performActivityCreated(f.mSavedFragmentState);
...
}
...
}
void performActivityCreated(Bundle savedInstanceState) {
if (mChildFragmentManager != null) {
mChildFragmentManager.noteStateNotSaved();
}
mState = ACTIVITY_CREATED;
mCalled = false;
// ReportFragment の onActivityCreated が呼ばれる
onActivityCreated(savedInstanceState);
if (!mCalled) {
throw new SuperNotCalledException("Fragment " + this
+ " did not call through to super.onActivityCreated()");
}
if (mChildFragmentManager != null) {
mChildFragmentManager.dispatchActivityCreated();
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
dispatchCreate(mProcessListener);
dispatch(Lifecycle.Event.ON_CREATE);
}
private void dispatch(@NonNull Lifecycle.Event event) {
if (Build.VERSION.SDK_INT < 29) {
// Only dispatch events from ReportFragment on API levels prior
// to API 29. On API 29+, this is handled by the ActivityLifecycleCallbacks
// added in ReportFragment.injectIfNeededIn
dispatch(getActivity(), event);
}
}
static void dispatch(@NonNull Activity activity, @NonNull Lifecycle.Event event) {
// ここで Lifecycle.Event がハンドリングされる -> OnLifecycleEvent がフックしてるところがはしる
if (activity instanceof LifecycleRegistryOwner) {
((LifecycleRegistryOwner) activity).getLifecycle().handleLifecycleEvent(event);
return;
}
if (activity instanceof LifecycleOwner) {
Lifecycle lifecycle = ((LifecycleOwner) activity).getLifecycle();
if (lifecycle instanceof LifecycleRegistry) {
((LifecycleRegistry) lifecycle).handleLifecycleEvent(event);
}
}
}
となり、 各 onCreate のオーバーライドが走った後 ReportFragment が Activity の LifecycleRegistory に対してイベントを通知 します。
(長くなるのでいろいろ端折ってます...)
余談ですが、ちょっとややこしいですが、Fragment の mState は moveToState が終わってから更新されるので
dispatchActivityCreated 時の ReportFragment の mState は Fragment.CREATED です。
(FragmentManager.moveToState 内 switch のお話)
API 29 以上
ReportFragment にイベントが来るまでのシーケンスが変わっています。
が、ReportFragment 以降は API 28 以下の場合と同じです。
こっちの方がわかりやすいですね。
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
...
// Activity の onCreate のオーバーライド達が走る
// (ReportFragment の ActivityLifecycleCallbacks が登録される)
if (persistentState != null) {
onCreate(icicle, persistentState);
} else {
onCreate(icicle);
}
...
// API 29 以上はここで ReportFragment に post される
dispatchActivityPostCreated(icicle);
}
@SuppressLint("RestrictedApi")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// (ActivityLifecycleCallbacks が登録される)
ReportFragment.injectIfNeededIn(this);
}
public static void injectIfNeededIn(Activity activity) {
if (Build.VERSION.SDK_INT >= 29) {
// On API 29+, we can register for the correct Lifecycle callbacks directly
activity.registerActivityLifecycleCallbacks(
new LifecycleCallbacks());
}
...
}
static class LifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
...
@Override
public void onActivityPostCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
// 以降 API 28 以下と一緒
dispatch(activity, Lifecycle.Event.ON_CREATE);
}
...
}
onPause
以降の View が死んでいく時の通知順は逆になります。
(Lifecycle.Event.ON_PAUSE -> Activity.onPause()
)
先に ViewModel で解放してから View の destroy が走るみたいな流れになりますね。
なので、通知順は以下になります。
Conclusion
と、いうわけで
ViewModel で OnLifecycleEvent
をフックした方が、View 側でオーバーライドしたライフサイクルで viewmodel.on~ みたいに呼ぶより View 側が考慮する事がなくなってスッキリした気がしますね。
今回の話は ViewModel に限った話ではありませんが、
オーバーライドしたライフサイクル内であれこれやりすぎた結果
うわっ...私の Activity、太りすぎ...?
みたいな事にならない様上手く LifecycleObserver を使って責任を分散させましょう!
これは個人的な意見なので誰かの参考になればと思います。
後、いろいろ間違ってたりしたらご指摘いただけると嬉しいです...