Pro Yearly is on sale from $80 to $50! »

Codelabs using kotlin coroutines by Droidkaigi

Codelabs using kotlin coroutines by Droidkaigi

Codelabs using kotlin coroutines by Droidkaigi

6dd0483f1353a4a359e92633cfd65c64?s=128

Daichi Furiya (Wasabeef)

September 14, 2019
Tweet

Transcript

  1. Kotlin Coroutine ハンズオン by Droidkaigi Wasabeef #droidkaigi_roadshow

  2. About me Daichi Furiya (降矢 大地) Google Developers Expert CATS,

    CyberAgent @wasabeef_jp wasabeef
  3. Kotlin Coroutine ハンズオン by Droidkaigi

  4. コードラボに必要に必要知識の学習 Android における非同期処理 Kotlin coroutines この発表のゴールは..

  5. Android における非同期処理 Thread, AsyncTask, Loader, IntentService RxJava Kotlin coroutines Index

  6. Android Threading

  7. まず Android のスレッドにおいては初心者 の頃、もしくは Android アプリ開発黎明期 に一度くらいは体験したことがあるであろう スレッド関連の例外といえば… Android Threading

  8. CalledFromWrongThreadException android.view.ViewRootImpl: Only the original thread that created a view

    hierarchy can touch its views. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7900) at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1170) at android.view.View.requestLayout(View.java:20043) at android.view.View.requestLayout(View.java:20043) at android.view.View.requestLayout(View.java:20043) at android.view.View.requestLayout(View.java:20043) at android.view.View.requestLayout(View.java:20043) at android.view.View.requestLayout(View.java:20043) at android.view.View.requestLayout(View.java:20043) at android.widget.TextView.checkForRelayout(TextView.java:8024) at android.widget.TextView.setText(TextView.java:4959) at android.widget.TextView.setText(TextView.java:4786) at android.widget.TextView.append(TextView.java:4451) at android.widget.TextView.append(TextView.java:4441) メインスレッド以外から View などの画面更新をしようとした場合に発生する例外
  9. NetworkOnMainThreadException FATAL EXCEPTION: main android.os.NetworkOnMainThreadException at android.os.StrictMode.onNetwork(StrictMode.java:1084) at java.net.InetAddress.lookupHostByName(InetAddress.java:391) at

    java.net.InetAddress.getAllByNameImpl(InetAddress.java:242) at java.net.InetAddress.getAllByName(InetAddress.java:220) at libcore.net.http.HttpConnection.<init>(HttpConnection.java:71) at libcore.net.http.HttpConnection.<init>(HttpConnection.java:50) at libcore.net.http.HttpConnection.connect(HttpConnection.java:351) … at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551) at dalvik.system.NativeStart.main(Native Method) メインスレッドから http request をしようとした場合に発生する例外
  10. そして、Android のスレッドにおいてのポイ ントとして主に二つの分類になるると思います Activity, Fragment のライフサイクルに紐づ けられるものとそうでないもの。 Android Threading

  11. Thread, Handler AsyncTask Loader IntentService RxJava etc... Android Threading

  12. Thread, Handler (API level 1)

  13. Thread, Handler class SampleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState:

    Bundle?) { super.onCreate(savedInstanceState) Thread { val bitmap = loadBitmap("https://wasabeef.jp/image.png") Handler(mainLooper).post { imageView.setImageBitmap(bitmap) } }.start() } }
  14. もっとも古くからある原始的な非同期処理 ライフサイクルに紐づいていない Thread, Handler はあらゆる非同期処理の内部 実装で使われている 今、これらだけでアプリ開発するのは telnet で メール送受信するようなものです

    Thread, Handler のメリットデメリット
  15. AsyncTask (API level 3)

  16. AsyncTask class SampleActivity : AppCompatActivity(R.layout.activity_sample) { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) MyTask(imageView).execute("https://wasabeef.jp/image.png") } inner class MyTask(view: ImageView) : AsyncTask<String, Void, Bitmap>() { private val imageWeakRef = WeakReference<ImageView>(view) override fun doInBackground(vararg params: String): Bitmap { return loadBitmap(params[0]) } override fun onPostExecute(bitmap: Bitmap) { imageWeakRef.get()?.setImageBitmap(bitmap) } } }
  17. 古くからある非同期処理 ライフサイクルに紐づいていない Thread, Handler のヘルパークラス AsyncTask 内で View を持つ場合には WeakReference<T>

    などを使う API level によって同時実行数が異なる AsyncTask
  18. Loader (API level 11)

  19. AsyncTask がライフサイクルに対して対 応が難しいところを改良したが、その結果 複雑になった API 28 で廃止された ViewModel, LiveData の出現により。

    Loader
  20. IntentService (API level 3)

  21. IntentService UI の更新向きではないので、用途が少し違います class SampleActivity : AppCompatActivity(R.layout.activity_sample) { override fun

    onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) startService(Intent(this, SampleService::class.java)) } } // AndroidManifest.xml ΁ͷ௥Ճ͕ඞཁ // <service android:name=".main.SampleService" /> class SampleService : IntentService("SampleService") { override fun onHandleIntent(intent: Intent?) { // Do something } }
  22. ライフサイクルに紐づいていない 生存期間が長い アプリがバックグラウドにいっても処理継 続したいものや、通知などで使われている UI の更新には向いていない IntentService

  23. RxJava

  24. RxJava スライド数枚では説明しきれないくらい、機能がたくさんあります class SampleActivity : AppCompatActivity(R.layout.activity_sample) { private lateinit var

    compositeDisposable: CompositeDisposable override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) compositeDisposable = CompositeDisposable() val observable = Observable.create<Bitmap> { emitter -> val bitmap = loadBitmap("https://wasabeef.jp/image.png") emitter.onNext(bitmap) emitter.onComplete() }.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { bitmap -> imageView.setImageBitmap(bitmap) }, { error -> /** do something **/ }) .addTo(compositeDisposable) } }
  25. ライフサイクルに紐づいている(紐づけられる) リアクティブプログラミング 非同期処理ライブラリというわけではない 今でもスタンダードなライブラリ 機能全体を把握するのは難しく、学習コストが高い(と思う) でも便利で、今でも使いたくなる LiveData で複雑なことやりたい場合は RxJava で入れたり

    する RxJava
  26. Kotlin Coroutines

  27. コルーチンとは? suspend 関数 Coroutine Context Coroutine Builders Concurrent Execution
 (並列処理)

    Index Blocking Builder CoroutineScope Job Dispatchers withContext Exception Handling
  28. コルーチンとは?

  29. ライフサイクルに紐づいている(紐づけられる) 軽量スレッド Android 界では 2019 年の流行語 ドキュメントの説明が難しい(気がする) コルーチン

  30. Kotlin/KEEP によるとコルーチンは 「一時停止可能な計算のインスタンス」 であると定義されています。 コルーチンとは? KEEP: http://bit.ly/Kotlin-KEEP-coroutines

  31. コルーチンは作成され開始されますが、特 定のスレッドに縛られていません。1つの スレッドで実行を中断し、別のスレッドで 再開する場合があります。 一時停止可能な計算インスタンス

  32. 一時停止可能な計算インスタンス Thread { val bitmap = loadBitmap("https://wasabeef.jp/image.png") Handler(mainLooper).post { imageView.setImageBitmap(bitmap)

    } }.start() launch { val bitmap = loadBitmap("https://wasabeef.jp/image.png").await() imageView.setImageBitmap(bitmap) } スレッド:生成、スケジューリング、割当て、起動とオーバーヘッドがある コルーチン:どの(再利用された)スレッドで実行されているかは知らない ※ スレッドプールを利用する事でオーバーヘッドを減らせる
  33. suspend 関数

  34. suspend 関数 suspend 関数 は、その関数を呼ばれると現在のス レッドをブロックせずに他の関数などを実行すること ができます。ここまではいわゆる Thread などでも同 じだと思います。コルーチンの場合は、処理を一時中

    断して他のその時点で別のコルーチンの実行に戻り、 後に残したコルーチンの実行を再開できます。
  35. suspend 関数 suspend キーワードがあるこれらの事を suspend 関数 を呼びます suspend fun delay(timeMillis:

    Long) { } suspend fun loadBitmap(): Bitmap { }
  36. Coroutine Context

  37. Coroutine Context Coroutine Context は、コルーチンに関する永 続的なデータのセットです。コルーチンスレッ ドポリシー、ロギング、コルーチン実行のセ キュリティとトランザクション、コルーチンの ID や名前などのオブジェクトが含まれていま

    す。
  38. Coroutine Context interface CoroutineContext { operator fun <E : Element>

    get(key: Key<E>): E? fun <R> fold(initial: R, operation: (R, Element) -> R): R operator fun plus(context: CoroutineContext): CoroutineContext fun minusKey(key: Key<*>): CoroutineContext interface Key<E : Element> interface Element : CoroutineContext { val key: Key<*> } } 参考程度に
  39. Coroutine Builders

  40. Coroutine Builders CoroutineScope は後述しますが、コルーチンは CoroutineScope 内で起動します launch { // do

    something } // Builders.common.kt public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine } 主なビルダーが launch () です ここでは参考程度。
  41. Concurrent Execution (並列処理)

  42. Concurrent Execution(並列処理) async() もビルダーです。コルーチンから値を返すために利用できます。 二つの API を結果を待ち合わせたりなど。 launch { val

    user: Deferred<User> = async { getUser("wasabeef") } val status: Deferred<Status> = async { getStaus("8044") } // async() ͸ΩϟϯηϧՄೳͳ Deferred<T> Λฦ͠·͢ // ·࣮ͩߦ͞ΕΔ͕ɺuser/statusͳͲσʔλ͸࢖͑·ͤΜ // async(start = LAZY) Λ࢖͏ͱ await ·Ͱ࣮ߦΛ଴ͭ͜ͱ͕Ͱ͖·͢ // await() Λݺͼग़͢ࣄͰɺͦΕΒͷσʔλ͕࢖͑ΔΑ͏ʹͳΓ·͢ print(user.await() + status.await()) }
  43. Blocking Builder

  44. Blocking Builder さてここでクイズです。JVM環境で出力した場合に結果はどうなるでしょうか fun main() { GlobalScope.launch { delay(1000L) val

    hello = async { "Hello, " } val world = async { "World" } print(hello.await() + world.await()) } println(" from Droidkaigi ") }
  45. Blocking Builder fun main() { GlobalScope.launch { delay(1000L) val hello

    = async { "Hello, " } val world = async { "World" } print(hello.await() + world.await()) } println(" from Droidkaigi ") } A: Hello, World from Droidkaigi B: from Droidkaigi Hello, World C: from Droidkaigi
  46. Blocking Builder fun main() { GlobalScope.launch { delay(1000L) val hello

    = async { "Hello, " } val world = async { "World" } print(hello.await() + world.await()) } println(" from Droidkaigi ") } A: Hello, World from Droidkaigi B: from Droidkaigi Hello, World C: from Droidkaigi
  47. Blocking Builder fun main() { GlobalScope.launch { delay(1000L) val hello

    = async { "Hello, " } val world = async { "World" } print(hello.await() + world.await()) } println(" from Droidkaigi ") } A: Hello, World from Droidkaigi B: from Droidkaigi Hello, World C: from Droidkaigi
  48. Blocking Builder C

  49. Blocking Builder fun main() { GlobalScope.launch { delay(1000L) val hello

    = async { "Hello, " } val world = async { "World" } print(hello.await() + world.await()) } Thread.sleep(2000L) // ⭐ println(" from Droidkaigi ") } Thread.sleep でメインスレッドを停止させ、JVM が落ちないようにします
  50. Blocking Builder 実際、Thread.sleep でメインスレッドを停止 させるのは良くありません。ここで、 runBlocking() という別のビルダーがあります が、これも特殊なので使用方法には注意が必要 です。テストコードで使うことがあります。

  51. runBlockingTest kotlinx-coroutines-test には runBlockingTest というテストコード向 けのものが含まれています。 delay(1000L) などの遅延を無視し、すぐ に実行結果が得られます

  52. Blocking Builder fun main() = runBlocking { launch { //

    GlobalScope.launch Ͱ͸ͳ͘͜ͷείʔϓ಺ͷ launch Λ࢖༻͢Δ delay(1000L) val hello = async { "Hello, " } val world = async { "World" } print(hello.await() + world.await()) } println(" from Droidkaigi ") } runBlocking スコープ内で実行中のコルーチンが終わるまでは終了しません 現在のスレッドをブロックします
  53. CoroutineScope

  54. CoroutineScope CoroutineScope は生存期間を制御します。 そのスコープ内で実行されたコルーチンは Job で管理されます。Android の場合では、 Activity, Fragment, ViewModel

    の単位で スコープが作られていることがあります
  55. coroutineScope { } coroutineScope { } を使うことによって、自分でスコープを作成できます fun main() =

    runBlocking { val myScope = coroutineScope { // make coroutine } }
  56. coroutineScope { } これまでにも Coroutines Builders の説明をしましたが コルーチンは CoroutineScope 内で起動します

    fun main() = runBlocking { val myScope = coroutineScope { launch { // do something } } }
  57. CoroutineScope() CoroutineScope() コンストラクタを使うことで、実行スレッドや親 Job の指定ができます それぞれについては後述します fun main() = runBlocking

    { val job = Job() val myScope = CoroutineScope(Dispatchers.Default + job) myScope.launch { // do something } }
  58. CoroutineScope by MainScope() また、MainScope() を使うことで簡単にUIスレッドのスコープと親 Job の指定ができます abstract class ScopedActivity:

    AppCompatActivity() { }
  59. CoroutineScope by MainScope() abstract class ScopedActivity: AppCompatActivity(), CoroutineScope by MainScope()

    { } この例では、Activity で CoroutineScope by MainScope() で実装します
  60. CoroutineScope by MainScope() MainScope() を使う場合でも親 Job のキャンセルは必要なので、onDestroy などで行います abstract class

    ScopedActivity: AppCompatActivity(), CoroutineScope by MainScope() { override fun onDestroy() { super.onDestroy() cancel() // CoroutineScope.cancel } }
  61. CoroutineScope by MainScope() 以上で、onCreate などでコルーチンが作成できるようになります abstract class ScopedActivity: AppCompatActivity(), CoroutineScope

    by MainScope() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) launch { // do something } } override fun onDestroy(){...} }
  62. Job

  63. Job 通常、Job は launch() 呼び出して作成されます。 コンス トラクター Job() を使用して作成することもできます。 親または子として、他の

    Job の階層にすることもできま す。その場合、親 Job をキャンセルすると、そのすべて の子もキャンセルされ、子 Job が失敗またはキャンセル すると、その親および親階層もキャンセルされます。
  64. Job で親 Job がキャンセルされた場合 Job がこのようにな階層構造になることがよくあります ParentJob Job A Job

    B Job F Job C Job D Job E Job G Job H
  65. Job で親 Job がキャンセルされた場合 トップレベルの Job がキャンセルされると、全ての子 Job がキャンセルされます ParentJob

    Job A Job B Job F Job C Job D Job E Job G Job H
  66. Job で親 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H トップレベルの Job がキャンセルされると、全ての子 Job がキャンセルされます
  67. Job で親 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H トップレベルの Job がキャンセルされると、全ての子 Job がキャンセルされます
  68. Job で親 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H トップレベルの Job がキャンセルされると、全ての子 Job がキャンセルされます
  69. Job で子 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H
  70. Job で子 Job がキャンセルされた場合 子 Job がキャンセルされると、全ての親、子 Job がキャンセルされます ParentJob

    Job A Job B Job F Job C Job D Job E Job G Job H
  71. Job で子 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H 子 Job がキャンセルされると、全ての親、子 Job がキャンセルされます
  72. Job で子 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H 子 Job がキャンセルされると、全ての親、子 Job がキャンセルされます
  73. Job で子 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H 子 Job がキャンセルされると、全ての親、子 Job がキャンセルされます
  74. Job で子 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H 子 Job がキャンセルされると、全ての親、子 Job がキャンセルされます
  75. Job で子 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H 子 Job がキャンセルされると、全ての親、子 Job がキャンセルされます
  76. Job を launch() で生成する ああああああ val job = launch {

    // do something } // ... job.cancel()
  77. Job をコンストラクタで生成する 1/2 ああああああ val job = Job() // do

    something job.cancel()
  78. Job をコンストラクタで生成する 2/2 ああああああ val parentJob = Job() val job

    = launch(parentJob) { // do something } parentJob.cancel()
  79. SupervisorJob SupervisorJob は Job と違い、子の失敗またはキャンセルでも伝播されずに、親は キャンセルされません。 親が指定されている場合、この SupervisorJob はその親が 失敗またはキャンセルされるとキャンセルされます。

    // Supervisor.kt public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent) private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) { override fun childCancelled(cause: Throwable): Boolean = false }
  80. SupervisorJob で子 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H
  81. 子 Job がキャンセルされると、親はキャンセルされず、子 Job だけキャンセルされます ParentJob Job A Job B

    Job F Job C Job D Job E Job G Job H SupervisorJob で子 Job がキャンセルされた場合
  82. ParentJob Job A Job B Job F Job C Job

    D Job E Job G Job H SupervisorJob で子 Job がキャンセルされた場合 子 Job がキャンセルされると、親はキャンセルされず、子 Job だけキャンセルされます
  83. SupervisorJob を生成する Job() を SupervisorJob() に変えるだけで使用できます。 val parentJob = SupervisorJob()

    val job = launch(parentJob) { // do something } parentJob.cancel()
  84. Dispatchers

  85. Dispatchers Dispatchers はコルーチンが実行に使用す るスレッドまたはスレッドプールを決定し ます。 ディスパッチャは、コルーチンを 特定のスレッドに限定できます。

  86. Dispatchers Default, Main, Main.immediate, Unconfined, IO があります このコードは参考程度に眺めて見えてください // Dispachers.kt

    public actual object Dispatchers { @JvmStatic public actual val Default: CoroutineDispatcher = createDefaultDispatcher() @JvmStatic public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher @JvmStatic public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined @JvmStatic public val IO: CoroutineDispatcher = DefaultScheduler.IO }
  87. Dispatchers.Default Default メインスレッド外。CPUに負荷がかかる処理向け JSONの解析、DiffUtilsの利用時など coroutineScope { launch(Dispatchers.Default) { // do

    something } }
  88. Dispatchers.Main, Main.immediate Main, Main.immediate メインスレッド。 UIイベントで即時応答する場合には immediate が有効です coroutineScope {

    launch(Dispatchers.Main) { // do something } launch(Dispatchers.Main.immediate) { // do something } }
  89. Dispatchers.Unconfined coroutineScope { launch(Dispatchers.Unconfined) { // do something } }

    Unconfined 呼び出し元のスレッドで実行されます ただし、一時停止した場合は他のスレッドで再開することがあります
  90. Dispatchers.IO coroutineScope { launch(Dispatchers.IO) { // do something } }

    IO メインスレッド外。ディスクおよびネットワーク IO に向け データベース、ファイルの読み書き、ネットワーク通信
  91. withContext

  92. withContext withContext は CoroutineContext を切 り替えるために用意されています。現在の コルーチンで Dispatchers を切り替えるこ とができます。コルーチンを新たに生成す

    ることなくコンテキストを変更できます。
  93. withContext Bitmap を生成するスレッドは IO を指定し その結果である Bitmap を ImageView に

    UI スレッドで渡します class MainActivity : ScopedAppActivity() { suspend fun loadBitmap(view: ImageView, url: String) { val bitmap = withContext(Dispatchers.IO) { // do something } withContext(Dispatchers.Main) { view.setImageBitmap(bitmap) } } }
  94. Conclusion..

  95. References: - https://d.android.com - https://github.com/Kotlin/kotlinx.coroutines - https://droidkaigi.github.io/codelabs-kotlin-coroutines-ja - https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md -

    https://www.raywenderlich.com/2117501-kotlin-coroutines-tutorial-for- android-advanced - https://play.kotlinlang.org Doc Resources
  96. Image Resources Photos: - https://unsplash.com - https://www.pexels.com Illustrations: - http://www.chojugiga.com

    - https://www.irasutoya.com
  97. twitter.com/wasabeef_jp wasabeef.jp github.com/wasabeef

  98. Kotlin Coroutines https://bit.ly/DkCkC さあ、コードラボをやってみましょう