Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

About me Daichi Furiya (降矢 大地) Google Developers Expert CATS, CyberAgent @wasabeef_jp wasabeef

Slide 3

Slide 3 text

Kotlin Coroutine ハンズオン by Droidkaigi

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Android Threading

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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 などの画面更新をしようとした場合に発生する例外

Slide 9

Slide 9 text

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.(HttpConnection.java:71) at libcore.net.http.HttpConnection.(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 をしようとした場合に発生する例外

Slide 10

Slide 10 text

そして、Android のスレッドにおいてのポイ ントとして主に二つの分類になるると思います Activity, Fragment のライフサイクルに紐づ けられるものとそうでないもの。 Android Threading

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Thread, Handler (API level 1)

Slide 13

Slide 13 text

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() } }

Slide 14

Slide 14 text

もっとも古くからある原始的な非同期処理 ライフサイクルに紐づいていない Thread, Handler はあらゆる非同期処理の内部 実装で使われている 今、これらだけでアプリ開発するのは telnet で メール送受信するようなものです Thread, Handler のメリットデメリット

Slide 15

Slide 15 text

AsyncTask (API level 3)

Slide 16

Slide 16 text

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() { private val imageWeakRef = WeakReference(view) override fun doInBackground(vararg params: String): Bitmap { return loadBitmap(params[0]) } override fun onPostExecute(bitmap: Bitmap) { imageWeakRef.get()?.setImageBitmap(bitmap) } } }

Slide 17

Slide 17 text

古くからある非同期処理 ライフサイクルに紐づいていない Thread, Handler のヘルパークラス AsyncTask 内で View を持つ場合には WeakReference などを使う API level によって同時実行数が異なる AsyncTask

Slide 18

Slide 18 text

Loader (API level 11)

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

IntentService (API level 3)

Slide 21

Slide 21 text

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 ΁ͷ௥Ճ͕ඞཁ // class SampleService : IntentService("SampleService") { override fun onHandleIntent(intent: Intent?) { // Do something } }

Slide 22

Slide 22 text

ライフサイクルに紐づいていない 生存期間が長い アプリがバックグラウドにいっても処理継 続したいものや、通知などで使われている UI の更新には向いていない IntentService

Slide 23

Slide 23 text

RxJava

Slide 24

Slide 24 text

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 { 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) } }

Slide 25

Slide 25 text

ライフサイクルに紐づいている(紐づけられる) リアクティブプログラミング 非同期処理ライブラリというわけではない 今でもスタンダードなライブラリ 機能全体を把握するのは難しく、学習コストが高い(と思う) でも便利で、今でも使いたくなる LiveData で複雑なことやりたい場合は RxJava で入れたり する RxJava

Slide 26

Slide 26 text

Kotlin Coroutines

Slide 27

Slide 27 text

コルーチンとは? suspend 関数 Coroutine Context Coroutine Builders Concurrent Execution
 (並列処理) Index Blocking Builder CoroutineScope Job Dispatchers withContext Exception Handling

Slide 28

Slide 28 text

コルーチンとは?

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

一時停止可能な計算インスタンス 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) } スレッド:生成、スケジューリング、割当て、起動とオーバーヘッドがある コルーチン:どの(再利用された)スレッドで実行されているかは知らない ※ スレッドプールを利用する事でオーバーヘッドを減らせる

Slide 33

Slide 33 text

suspend 関数

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

suspend 関数 suspend キーワードがあるこれらの事を suspend 関数 を呼びます suspend fun delay(timeMillis: Long) { } suspend fun loadBitmap(): Bitmap { }

Slide 36

Slide 36 text

Coroutine Context

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Coroutine Builders

Slide 40

Slide 40 text

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 () です ここでは参考程度。

Slide 41

Slide 41 text

Concurrent Execution (並列処理)

Slide 42

Slide 42 text

Concurrent Execution(並列処理) async() もビルダーです。コルーチンから値を返すために利用できます。 二つの API を結果を待ち合わせたりなど。 launch { val user: Deferred = async { getUser("wasabeef") } val status: Deferred = async { getStaus("8044") } // async() ͸ΩϟϯηϧՄೳͳ Deferred Λฦ͠·͢ // ·࣮ͩߦ͞ΕΔ͕ɺuser/statusͳͲσʔλ͸࢖͑·ͤΜ // async(start = LAZY) Λ࢖͏ͱ await ·Ͱ࣮ߦΛ଴ͭ͜ͱ͕Ͱ͖·͢ // await() Λݺͼग़͢ࣄͰɺͦΕΒͷσʔλ͕࢖͑ΔΑ͏ʹͳΓ·͢ print(user.await() + status.await()) }

Slide 43

Slide 43 text

Blocking Builder

Slide 44

Slide 44 text

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 ") }

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Blocking Builder C

Slide 49

Slide 49 text

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 が落ちないようにします

Slide 50

Slide 50 text

Blocking Builder 実際、Thread.sleep でメインスレッドを停止 させるのは良くありません。ここで、 runBlocking() という別のビルダーがあります が、これも特殊なので使用方法には注意が必要 です。テストコードで使うことがあります。

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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 スコープ内で実行中のコルーチンが終わるまでは終了しません 現在のスレッドをブロックします

Slide 53

Slide 53 text

CoroutineScope

Slide 54

Slide 54 text

CoroutineScope CoroutineScope は生存期間を制御します。 そのスコープ内で実行されたコルーチンは Job で管理されます。Android の場合では、 Activity, Fragment, ViewModel の単位で スコープが作られていることがあります

Slide 55

Slide 55 text

coroutineScope { } coroutineScope { } を使うことによって、自分でスコープを作成できます fun main() = runBlocking { val myScope = coroutineScope { // make coroutine } }

Slide 56

Slide 56 text

coroutineScope { } これまでにも Coroutines Builders の説明をしましたが コルーチンは CoroutineScope 内で起動します fun main() = runBlocking { val myScope = coroutineScope { launch { // do something } } }

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

CoroutineScope by MainScope() また、MainScope() を使うことで簡単にUIスレッドのスコープと親 Job の指定ができます abstract class ScopedActivity: AppCompatActivity() { }

Slide 59

Slide 59 text

CoroutineScope by MainScope() abstract class ScopedActivity: AppCompatActivity(), CoroutineScope by MainScope() { } この例では、Activity で CoroutineScope by MainScope() で実装します

Slide 60

Slide 60 text

CoroutineScope by MainScope() MainScope() を使う場合でも親 Job のキャンセルは必要なので、onDestroy などで行います abstract class ScopedActivity: AppCompatActivity(), CoroutineScope by MainScope() { override fun onDestroy() { super.onDestroy() cancel() // CoroutineScope.cancel } }

Slide 61

Slide 61 text

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(){...} }

Slide 62

Slide 62 text

Job

Slide 63

Slide 63 text

Job 通常、Job は launch() 呼び出して作成されます。 コンス トラクター Job() を使用して作成することもできます。 親または子として、他の Job の階層にすることもできま す。その場合、親 Job をキャンセルすると、そのすべて の子もキャンセルされ、子 Job が失敗またはキャンセル すると、その親および親階層もキャンセルされます。

Slide 64

Slide 64 text

Job で親 Job がキャンセルされた場合 Job がこのようにな階層構造になることがよくあります ParentJob Job A Job B Job F Job C Job D Job E Job G Job H

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

Job を launch() で生成する ああああああ val job = launch { // do something } // ... job.cancel()

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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 }

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

SupervisorJob を生成する Job() を SupervisorJob() に変えるだけで使用できます。 val parentJob = SupervisorJob() val job = launch(parentJob) { // do something } parentJob.cancel()

Slide 84

Slide 84 text

Dispatchers

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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 }

Slide 87

Slide 87 text

Dispatchers.Default Default メインスレッド外。CPUに負荷がかかる処理向け JSONの解析、DiffUtilsの利用時など coroutineScope { launch(Dispatchers.Default) { // do something } }

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

Dispatchers.IO coroutineScope { launch(Dispatchers.IO) { // do something } } IO メインスレッド外。ディスクおよびネットワーク IO に向け データベース、ファイルの読み書き、ネットワーク通信

Slide 91

Slide 91 text

withContext

Slide 92

Slide 92 text

withContext withContext は CoroutineContext を切 り替えるために用意されています。現在の コルーチンで Dispatchers を切り替えるこ とができます。コルーチンを新たに生成す ることなくコンテキストを変更できます。

Slide 93

Slide 93 text

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) } } }

Slide 94

Slide 94 text

Conclusion..

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

Image Resources Photos: - https://unsplash.com - https://www.pexels.com Illustrations: - http://www.chojugiga.com - https://www.irasutoya.com

Slide 97

Slide 97 text

twitter.com/wasabeef_jp wasabeef.jp github.com/wasabeef

Slide 98

Slide 98 text

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