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. 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 などの画面更新をしようとした場合に発生する例外
  2. 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 をしようとした場合に発生する例外
  3. 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() } }
  4. 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) } } }
  5. 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 } }
  6. 23.
  7. 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) } }
  8. 27.

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

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

    CoroutineScope by MainScope() abstract class ScopedActivity: AppCompatActivity(), CoroutineScope by MainScope()

    { } この例では、Activity で CoroutineScope by MainScope() で実装します
  21. 60.

    CoroutineScope by MainScope() MainScope() を使う場合でも親 Job のキャンセルは必要なので、onDestroy などで行います abstract class

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

    Job

  24. 63.

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

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

    Job で親 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H トップレベルの Job がキャンセルされると、全ての子 Job がキャンセルされます
  26. 67.

    Job で親 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H トップレベルの Job がキャンセルされると、全ての子 Job がキャンセルされます
  27. 68.

    Job で親 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H トップレベルの Job がキャンセルされると、全ての子 Job がキャンセルされます
  28. 71.

    Job で子 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H 子 Job がキャンセルされると、全ての親、子 Job がキャンセルされます
  29. 72.

    Job で子 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H 子 Job がキャンセルされると、全ての親、子 Job がキャンセルされます
  30. 73.

    Job で子 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H 子 Job がキャンセルされると、全ての親、子 Job がキャンセルされます
  31. 74.

    Job で子 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H 子 Job がキャンセルされると、全ての親、子 Job がキャンセルされます
  32. 75.

    Job で子 Job がキャンセルされた場合 ParentJob Job A Job B Job

    F Job C Job D Job E Job G Job H 子 Job がキャンセルされると、全ての親、子 Job がキャンセルされます
  33. 78.
  34. 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 }
  35. 81.

    子 Job がキャンセルされると、親はキャンセルされず、子 Job だけキャンセルされます ParentJob Job A Job B

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

    ParentJob Job A Job B Job F Job C Job

    D Job E Job G Job H SupervisorJob で子 Job がキャンセルされた場合 子 Job がキャンセルされると、親はキャンセルされず、子 Job だけキャンセルされます
  37. 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 }
  38. 89.

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

    Unconfined 呼び出し元のスレッドで実行されます ただし、一時停止した場合は他のスレッドで再開することがあります
  39. 90.

    Dispatchers.IO coroutineScope { launch(Dispatchers.IO) { // do something } }

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