AndroidのライフサイクルとCoroutineScope
またです。
前書いた記事よりスマートに、async-await
と例外処理とかは割愛です。
個人的には 1.3
から追加された Result
型をゴニョゴニョしてやってます。
前回書いたあれ、なかなかつらい感じだったのでもっといい感じにならないかと思ってたら SupervisorJobあるやんみたいな(今更)。
この SupervisorJob
その名の通り監督で、 その子どものJob
が失敗しても他の子供に影響が無く、親が子供の失敗で死ぬことも無いらしい。英語苦手だけど多分きっとそう。
親を殺せば子も死ぬので丁度いい、onDestroy
でSupervisorJob
殺せばいいだけだね!
とりあえずコードと解説貼ってきま。
(2018 12/12 AM 3:12 追記) `LifecycleScope`について、既に`Job`を持っている`CoroutineScope`を受け入れる状態になっていたため、受け入れないように修正しました。 おまけで書いたコードも気になったところを修正。コードと解説
AndroidScope
まずはAndroid用のCoroutineScopeを定義。
インスタンスをいっぱい作る必要も無いのでobject
にします。
import kotlinx.coroutines.*
import kotlin.coroutines.*
object AndroidScope : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main
}
LifecycleScope
次に、CoroutineScope
を元に作成される Android Architecture Component
のLifecycle
用のCoroutineScope
を定義。
import androidx.lifecycle.*
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
class LifecycleScope(
private val lifecycleOwner: LifecycleOwner,
coroutineScope: CoroutineScope = AndroidScope
) : LifecycleObserver, CoroutineScope {
init {
coroutineScope.coroutineContext[Job]
?.let { throw IllegalArgumentException("A Context with Job already passed") }
lifecycleOwner.lifecycle.addObserver(this)
}
private val mJob = SupervisorJob()
override val coroutineContext: CoroutineContext = (coroutineScope + mJob).coroutineContext
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
mJob.cancelChildren()
lifecycleOwner.lifecycle.removeObserver(this)
}
}
fun LifecycleScope.bindLaunch(
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
) = launch(coroutineContext, start, block)
onDestroy()
でSupervisorJob
の子を殺してからLifecycleOwner
から自身を外すだけ。
fun LifecycleScope.bindLaunch()
は無くてもいいけど一応定義。
CoroutineScope
をコンストラクタ
から渡すようにしているのは、テスト走らせるのときとかにAndroidScope
は使えないからです。
LifecycleScopeSupport
このままだとActivity
とかで使いづらいので使いやすくするためにそれ用のinterface
を定義
import
はLifecycleScope
と一緒だと思う
interface LifecycleScopeSupport {
val scope: LifecycleScope
}
fun LifecycleScopeSupport.bindLaunch(
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
) = scope.bindLaunch(start, block)
LifecycleScopeSupport
を任意のActivity
に実装すればあとはbindlaunch{}
みたいにするだけでライフサイクルにバインドされたJob
が起動する。
使うとき
import androidx.appcompat.app.AppCompatActivity
class SampleActivity : AppCompatActivity(), LifecycleScopeSupport {
override val scope = LifecycleScope(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//setContentViewとかごにょごにょ
bindLaunch {
//なんかごにょごにょ
}
}
}
楽ちん
LifecycleScope::launch
とかでも同じ結果得られると思うけど僕はこっちのが好みです(ほんとにただの好み)。
まとめ
ぶっちゃければこんなの作らなくてもActivity::onDestroy
でSupervisorJob::cancelChildren
呼べばいいだけ。
でも呼び忘れで消耗したくないのでLifecycle
に頼ります(忘れっぽいので)。
余談
スコープのスペルってScope
だったっけ・・・? ずっと見てたらなんか不安になってきた。
追加でおまけ
LifecycleScope
からonDestroy()
漏れてるのが気に食わない人向け(僕です)
(2018 12/12 AM 3:12 追記)
LifecycleObserverが漏れてるのも気になった。
import androidx.lifecycle.*
import kotlinx.coroutines.*
import kotlin.coroutines.*
interface LifecycleScope : CoroutineScope
private class LifecycleScopeImpl(
private val lifecycleOwner: LifecycleOwner,
coroutineScope: CoroutineScope
) : LifecycleScope, LifecycleObserver {
init {
coroutineScope.coroutineContext[Job]
?.let { throw IllegalArgumentException("A scope with a Job already passed") }
lifecycleOwner.lifecycle.addObserver(this)
}
private val mJob = SupervisorJob()
override val coroutineContext: CoroutineContext = (coroutineScope + mJob).coroutineContext
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
mJob.cancelChildren()
lifecycleOwner.lifecycle.removeObserver(this)
}
}
@Suppress("FunctionName")
fun LifecycleScope(
lifecycleOwner: LifecycleOwner,
coroutineScope: CoroutineScope = AndroidScope
): LifecycleScope = LifecycleScopeImpl(lifecycleOwner, coroutineScope)
参考