LoginSignup
2
2

More than 3 years have passed since last update.

AAC の ViewModel で View のライフサイクルとリンクさせる時は OnLifecycleEvent でフックするといいよね、と言う話

Last updated at Posted at 2021-01-18

タイトルがここで言いたい事の全てです:innocent:

Android における ViewModel 側で onCreate 等のライフサイクルが走った時に何かしたい、みたいなシーンは結構あると思います。
(例えばユーザ情報を fetch する、とか)

そういった時どうやって ViewModel 側にイベントを通知するかと言うと

SampleActivity.kt
class SampleActivity: AppCompatActivity() {
    private val viewModel: SampleViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        viewModel.onViewCreated()
    }
}
SampleViewModel.kt
class SampleViewModel: ViewModel() {
    fun onViewCreated() {
        // API 叩いたりレスポンスを LiveData に流したり etc...
    }
}

とすると思います。一番シンプルです。

しかし、せっかく Android の Architecture Component です。
ViewModel の初期化やリソースの解放なんかを他のクラスに任せるのはナンセンスな気がしました。
個人的に View -> ViewModel の方向性は少なくしてあげて、View は流れてくるデータをバインドするだけにしたい。

ViewModel の LiveData を observe してから onCreate を呼んで...
みたいな事を View 側が考慮するのはなんだかなぁ、です:thinking:
ViewModel 側でライフサイクルを考慮してあげた方がスッキリしませんか?

と、言うわけで ViewModel 側で紐づく View のライフサイクルをフックしてあげましょう。

ViewModel で Lifecycle.Event をフックする

ライフサイクル対応コンポーネントによるライフサイクルへの対応  |  Android デベロッパー  |  Android Developers

ViewModel に LifecycleObserver を implement しましょう。
ViewModel が View のライフサイクルにリンクされて画面が表示される度にリロードとかそんな事ができます。

SampleViewModel.kt
class SampleViewModel: ViewModel(), LifecycleObserver {
    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun onViewCreated() {
        // なんやかんや
    }
}
SampleActivity.kt
class SampleActivity: AppCompatActivity() {

    private val viewModel: SampleViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        lifecycle.addObserver(viewModel)
    }
}

実際には Dagger を使ったりしていると、こんな事をして上げたりするかもしれませんね。

SampleActivity.kt
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 側は通知されてくるデータに専念できそうです:clap:

ライフサイクルのオーバーライドと 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)

実際コードを順に追っていくと、

Activity.java
    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);
    }
FragmentManager.java
    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);                
            ...
        }
        ...
    }

Fragment.java
    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();
        }
    }
ReportFragment.java
    @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 以下の場合と同じです。
こっちの方がわかりやすいですね。

Activity.java
    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);
    }
ComponentActivity.java
    @SuppressLint("RestrictedApi")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // (ActivityLifecycleCallbacks が登録される)
        ReportFragment.injectIfNeededIn(this);
    }
ReportFragment.java
    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 が走るみたいな流れになりますね。

なので、通知順は以下になります。

image.png

Conclusion

と、いうわけで
ViewModel で OnLifecycleEvent をフックした方が、View 側でオーバーライドしたライフサイクルで viewmodel.on~ みたいに呼ぶより View 側が考慮する事がなくなってスッキリした気がしますね。

今回の話は ViewModel に限った話ではありませんが、
オーバーライドしたライフサイクル内であれこれやりすぎた結果
うわっ...私の Activity、太りすぎ...?
みたいな事にならない様上手く LifecycleObserver を使って責任を分散させましょう!

これは個人的な意見なので誰かの参考になればと思います。
後、いろいろ間違ってたりしたらご指摘いただけると嬉しいです...

参考

Android Architecture Components 雑感2。 - なるようになるかも

Android Architecture Component -- Lifecycle 浅析 - 简书

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2