Jetpack ComposeのViewCompositionStrategyの挙動まとめ

たくさんあってよく分からなくなるので、それぞれのコールバックからの挙動のまとめを作っておきます。

ComposeViewに対してsetContent{}を呼ぶときに、以下のように変更してあげることで、Composeのdisposeするタイミングを変更してあげることができます。
これによって[メモリリークを減らせるそう](https://developer.android.com/jetpack/compose/interop/interop-apis#composition-strategy)です。 

```kotlin
composeView.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
```

# まとめ

そんなに難しくないコードになっていて、コードを読むと以下のようになっているのが分かると思います。
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewCompositionStrategy.android.kt

| クラス名 | onViewAttachedToWindow時の挙動 | onViewDetachedFromWindow時の挙動 | PoolingContainerListener.onRelease()時 | lifecycleがdestroy |
|---|---|---|---|---|
| DisposeOnDetachedFromWindowOr<br>ReleasedFromPool(デフォルト) | 何もしない | ViewがpollingContainer内でない場合はview.disposeComposition()<br>(ViewがpollingContainer内の場合はPoolingContainerListener.onRelease()がWindowから消えるときも呼ばれる) | view.disposeComposition() | 何もしない |
| DisposeOnDetachedFromWindow | 何もしない | view.disposeComposition() | 何もしない | 何もしない |
| DisposeOnLifecycleDestroyed(lifecycle)<br>引数にlifecycleを渡すことで使える | 何もしない | 何もしない | 何もしない | view.disposeComposition() |
| DisposeOnViewTreeLifecycleDestroyed <br> Windowにすでに**アタッチされている**場合<br>ViewTreeLifecycleOwner.get(view)からlifecycleを取得する | 何もしない | 何もしない | 何もしない | view.disposeComposition() |
| DisposeOnViewTreeLifecycleDestroyed <br> Windowに**アタッチされていない場合** | ここでViewTreeLifecycleOwner.get(view)からlifecycleを取得 | 何もしない | 何もしない | view.disposeComposition() |

# それぞれいつ呼ばれるの？

## onViewAttachedToWindowとonViewDetachedFromWindow
onViewAttachedToWindow()はwindowにアタッチされたとき
onViewDetachedFromWindowはwindowからデタッチされたとき。

```java
    public interface OnAttachStateChangeListener {
        /**
         * Called when the view is attached to a window.
         * @param v The view that was attached
         */
        public void onViewAttachedToWindow(View v);
        /**
         * Called when the view is detached from a window.
         * @param v The view that was detached
         */
        public void onViewDetachedFromWindow(View v);
    }
```

## PoolingContainerListener.onRelease()

これはRecyclerViewの中にComposeViewがいるときに呼ばれます。
なぜこのイベントを使う必要があるのかというとRecyclerViewはリサイクルするために、子ViewをViewから外したりつけたりして管理するので、単純にWindowから外れたかどうかでライフサイクルでは管理できず、また親のViewのライフサイクルを持ってくるやり方ではRecyclerViewとしてはもう消しているViewでも保持し続けてしまうためです。

PoolingContainerListener.onRelease()はコンテナのView(RecyclerView)がViewを破棄したか、**コンテナ自身がウインドウから消えたとき**に呼ばれる。

> onRelease()
Signals that this view should dispose any resources it may be holding onto, because its container is either discarding the View or has been removed from the hierarchy itself.

ドキュメント通り、RecyclerView.onDetachedFromWindow()からも呼ばれていました

RecylerViewのコード

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/2f0f2f20-6f3e-8239-7560-dafcb42dde99.png)

スクロールしたりして、pool(パフォーマンスのために対比させておく場所)にViewHolderを入れるときにもうすでにpoolがいっぱいだったときにすぐに呼んだりなど

RecylerViewのコード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/31b4acd0-95fe-2d7d-5b52-b7a37eb0dd06.png)

## Destroy
これはおなじみのライフサイクルのものです。
基本的にはインスタンスで一回しか呼ばれないものです。

## ViewTreeLifecycleOwner.get()について
ライフサイクルオーナーはライフサイクルを持っていて、それを利用できます。基本的にはActivityかFragmentになります。
これはどこのライフサイクルオーナー取れるのかということで下記のようにViewのparentから取得するようです。

ViewTreeLifecycleOwnerのコード

```java
    @Nullable
    public static LifecycleOwner get(@NonNull View view) {
        LifecycleOwner found = (LifecycleOwner) view.getTag(R.id.view_tree_lifecycle_owner);
        if (found != null) return found;
        ViewParent parent = view.getParent();
        while (found == null && parent instanceof View) {
            final View parentView = (View) parent;
            found = (LifecycleOwner) parentView.getTag(R.id.view_tree_lifecycle_owner);
            parent = parentView.getParent();
        }
        return found;
    }
```

また以下がViewTreeOwnerたちです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27388/a5a472dc-d51f-35f8-19fe-61ef5c1116bf.png)

# どれを使うべきか？

デフォルトのDisposeOnDetachedFromWindowOrReleasedFromPoolではFragmentやTransitionや独自のライフサイクルの管理に向かないという言及があります

> By default, Compose uses the DisposeOnDetachedFromWindowOrReleasedFromPool strategy. However, this default value might be undesirable in some situations when the Compose UI View types are used in:
> Fragments. The Composition must follow the fragment's view lifecycle for Compose UI View types to save state.
> Transitions. Anytime the Compose UI View is used as part of a transition, it will be detached from its window when the transition starts instead of when the transition ends, thus causing your composable to dispose of its state while it is still on screen.
> Your own lifecycle-managed custom View.

https://developer.android.com/jetpack/compose/interop/interop-apis#composition-strategy

DisposeOnDetachedFromWindowOrReleasedFromPoolはRecyclerViewとの連携があるので、その場合やActivityなどでは良いのかもしれません。

FragmentではDisposeOnViewTreeLifecycleDestroyedを使う例があり、下記の記述もあるので、こちらのほうが良いです。

> Fragments. The Composition must follow the fragment's view lifecycle for Compose UI View types to save state.

https://developer.android.com/jetpack/compose/interop/interop-apis#compose-in-fragments

独自のライフサイクルではDisposeOnLifecycleDestroyedが使えます。