kako.dev

開発、自作アプリのこと

repeatOnLifecycleは中で何をしているのか?

雰囲気でrepeatOnLifecycleを使ってきたんですが、ちょっと気になったのでrepeatOnLifecycleの内部処理を覗いてみます!

確認環境

  • lifecycle-runtime-ktx: 2.5.1

コード

全体はこちらから見れます。

この記事内ではコードを部分的に切り出しつつ説明していきます。

例外処理と早期リターン

repeatOnLifecycleは引数で渡したライフサイクルステートによりFlowをCollectしたりCancelしたりしてくれますが、repeatOnLifecycle冒頭では以下の処理を行っています。

  • 引数に渡すステートがState.INITIALIZEDだと例外を返します
  • 現在のライフサイクルステートがDESTROYEDなら早期リターンします

repeatOnLifecycle.java

require(state !== Lifecycle.State.INITIALIZED) {
    "repeatOnLifecycle cannot start work with the INITIALIZED lifecycle state."
}

if (currentState === Lifecycle.State.DESTROYED) {
    return
}

require

@kotlin.internal.InlineOnly
public inline fun require(value: Boolean, lazyMessage: () -> Any): Unit {
    contract {
        returns() implies value
    }
    if (!value) {
        val message = lazyMessage()
        throw IllegalArgumentException(message.toString())
    }
}

メインスレッドで実行

処理はMainThreadで行うようになっています。Dispatchers.Main.immediateによりMainThread中は即時実行するようになっています。

coroutineScope {
    withContext(Dispatchers.Main.immediate) {
        if (currentState === Lifecycle.State.DESTROYED) return@withContext

try ~ finally で実行コルーチンをWrap

コルーチンをCancelするためかJobを受け取る変数があり、LifecycleEventをobserveするための変数も用意されています。

var launchedJob: Job? = null
var observer: LifecycleEventObserver? = null
try {
    // ... 
} finally {
    launchedJob?.cancel()
    observer?.let {
        this@repeatOnLifecycle.removeObserver(it)
    }
}

suspendCancellableCoroutineで正確にキャンセルされるようにケア

以降はtryの中身について書いています。 tryの中全体はsuspendCancellableCoroutineで括られていて、正確にキャンセルされるようになっています。 (「正確にキャンセル」という表現が適切かは自信がないです)

実行中にJobがキャンセルか実行完了したら CancellationException が投げられるため、キャンセルした後も実行され続けるということにはならないように考慮されていると解釈しています。

suspendCancellableCoroutine<Unit> { cont ->
    // ...
}

指定したLifecycle.StateによりCollect開始するイベント、キャンセルするイベントを決定

val startWorkEvent = Lifecycle.Event.upTo(state)
val cancelWorkEvent = Lifecycle.Event.downFrom(state)

Collect開始するイベント

Lifecycle.Event.upToの中身は以下のようになっていますが、まあ指定の通りのママになっています。

@Nullable
public static Event upTo(@NonNull State state) {
    switch (state) {
        case CREATED:
            return ON_CREATE;
        case STARTED:
            return ON_START;
        case RESUMED:
            return ON_RESUME;
        default:
            return null;
    }
}

キャンセルするイベント

Lifecycle.Event.downFromの中身は以下のようになっており、Collectするイベントに対応してキャンセルするイベントが定義されています。

Collect開始するイベントと対応するキャンセルイベント

  • CREATEDの場合、ON_DESTROYでキャンセル
  • STARTEDの場合、ON_STOPでキャンセル
  • RESUMEDの場合、ON_PAUSEでキャンセル
@Nullable
public static Event downFrom(@NonNull State state) {
    switch (state) {
        case CREATED:
            return ON_DESTROY;
        case STARTED:
            return ON_STOP;
        case RESUMED:
            return ON_PAUSE;
        default:
            return null;
    }
}

Mutaxの生成

Mutaxが生成されています。この後の処理を見るにrepeatOnLifecycle に渡す処理をwithLockで1つしか処理させないようにするためだと思われます。

val mutex = Mutex()

ライフサイクルイベントに合わせたObserve

ここが本処理というか、ライフサイクルに合わせて実行したい処理を収集キャンセルしているのがこのブロックになります。

LifecycleEventObserverでイベントをObserveします。その中でイベントによりコルーチンを開始したり、キャンセルしたりしています。

現在のイベントがON_DESTROYの時、中断されたコルーチンを再開しています。

observer = LifecycleEventObserver { _, event ->
    if (event == startWorkEvent) {
        launchedJob = this@coroutineScope.launch {
            mutex.withLock {
                coroutineScope {
                    block()
                }
            }
        }
        return@LifecycleEventObserver
    }
    if (event == cancelWorkEvent) {
        launchedJob?.cancel()
        launchedJob = null
    }
    if (event == Lifecycle.Event.ON_DESTROY) {
        cont.resume(Unit)
    }
}
this@repeatOnLifecycle.addObserver(observer as LifecycleEventObserver)

最後に

思ったより色々考慮されているなあという印象でした。 あとコード読みやすい気がしました。