Coroutine + AutoDisposeを作ってみた

Coroutine + AutoDisposeの実装について考えてみました。結論から言うと、ContinuationInterceptorを使えば上手くいきそうです。

ContinuationInterceptorとは?

ContinuationInterceptorは次のようなインターフェースです。

/**
 * Marks coroutine context element that intercepts coroutine continuations.
 * The coroutines framework uses [ContinuationInterceptor.Key] to retrieve the interceptor and
 * intercepts all coroutine continuations with [interceptContinuation] invocations.
 */
@SinceKotlin("1.3")
public interface ContinuationInterceptor : CoroutineContext.Element {
  public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
  public fun releaseInterceptedContinuation(continuation: Continuation<*>)
  ...
}

interceptContinuationからContinuationを受け取ることができ、Continuationは自身のCoroutineContextを持っているので、そこからJobを取得することが出来ます。それを利用することでAndroid Lifecycleと協調して動くContinuationInterceptorを実装することが出来ます。

class LifecycleContinuationInterceptor(
  private val lifecycle: Lifecycle
) : ContinuationInterceptor {
  override val key: CoroutineContext.Key<*>
    get() = ContinuationInterceptor

  override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
    // ContinuationからJobを取得
    val job = continuation.context[Job]
    if (job != null) {
      lifecycle.addJob(job)
    }
    return continuation
  }
}

fun LifecycleOwner.addJob(job: Job) {
  lifecycle.addJob(job)
}

fun Lifecycle.addJob(job: Job) {
  val state = this.currentState
  val event = when (state) {
      ...
  }
  val observer = LifecycleJobObserver(job, event, this)
  this.addObserver(observer)
  job.invokeOnCompletion(observer)
}

private class LifecycleJobObserver(
  private val job: Job,
  private val event: Lifecycle.Event,
  private val lifecycle: Lifecycle
) : LifecycleObserver, CompletionHandler {
  @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
  fun onEvent(owner: LifecycleOwner, event: Lifecycle.Event) {
    if (event == this.event) {
      owner.lifecycle.removeObserver(this)
      job.cancel()
    }
  }

  override fun invoke(cause: Throwable?) {
    lifecycle.removeObserver(this)
  }
}

これで、Android Lifecycleと協調して動くContinuationInterceptorが出来ました。

フルコードはここにあります。

使い方

使い方は次のようになります。

abstract class BaseActivity : AppCompatActivity(),
  CoroutineScope {

  private val job = Job()
  override val coroutineContext get() = job +
      Dispatchers.Main +
      LifecycleContinuationInterceptor(this) // ここでInterceptorを登録
}

class MainActivity : BaseActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // onCreateでlaunchしているので、onDestroyで自動的にキャンセルされる
    launch {
      ...
    }
  }

  override fun onResume() {
    super.onResume()

    // onResumeでlaunchしているので、onPauseで自動的にキャンセルされる
    launch {
      ...
    }
  }
}

となります。Rx-AutoDisposeのように実行したタイミングに応じて、キャンセルする場所を自動的に登録してくれます!!

まとめ

サンプルコードです😃satoshun-android-example/AutoDisposeExample

Written by