Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Codelabs using kotlin coroutines by Droidkaigi

Codelabs using kotlin coroutines by Droidkaigi

Codelabs using kotlin coroutines by Droidkaigi

Daichi Furiya (Wasabeef)

September 14, 2019
Tweet

More Decks by Daichi Furiya (Wasabeef)

Other Decks in Programming

Transcript

  1. 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 などの画面更新をしようとした場合に発生する例外
  2. 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 をしようとした場合に発生する例外
  3. 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() } }
  4. 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) } } }
  5. 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 } }
  6. 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) } }
  7. コルーチンとは? suspend 関数 Coroutine Context Coroutine Builders Concurrent Execution
 (並列処理)

    Index Blocking Builder CoroutineScope Job Dispatchers withContext Exception Handling
  8. 一時停止可能な計算インスタンス 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) } スレッド:生成、スケジューリング、割当て、起動とオーバーヘッドがある コルーチン:どの(再利用された)スレッドで実行されているかは知らない ※ スレッドプールを利用する事でオーバーヘッドを減らせる
  9. 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<*> } } 参考程度に
  10. 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 () です ここでは参考程度。
  11. 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()) }
  12. 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 ") }
  13. 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
  14. 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
  15. 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
  16. 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 が落ちないようにします
  17. 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 スコープ内で実行中のコルーチンが終わるまでは終了しません 現在のスレッドをブロックします
  18. CoroutineScope by MainScope() abstract class ScopedActivity: AppCompatActivity(), CoroutineScope by MainScope()

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

    ScopedActivity: AppCompatActivity(), CoroutineScope by MainScope() { override fun onDestroy() { super.onDestroy() cancel() // CoroutineScope.cancel } }
  20. 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(){...} }
  21. Job

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

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

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

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

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

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

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

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

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

    F Job C Job D Job E Job G Job H 子 Job がキャンセルされると、全ての親、子 Job がキャンセルされます
  31. 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 }
  32. 子 Job がキャンセルされると、親はキャンセルされず、子 Job だけキャンセルされます ParentJob Job A Job B

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

    D Job E Job G Job H SupervisorJob で子 Job がキャンセルされた場合 子 Job がキャンセルされると、親はキャンセルされず、子 Job だけキャンセルされます
  34. 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 }
  35. Dispatchers.Unconfined coroutineScope { launch(Dispatchers.Unconfined) { // do something } }

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

    IO メインスレッド外。ディスクおよびネットワーク IO に向け データベース、ファイルの読み書き、ネットワーク通信
  37. 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) } } }