【kotlin】コールバック をラッピングして見通し良く

なんだか微妙な感じします。

APIの仕様がなのか、

サンプルのコードがなのか、

Camera2。

android-Camera2Basic/Camera2BasicFragment.kt at master · googlesamples/android-Camera2Basic

いくつかのコールバック処理が

連続して処理されますが

kotlin の芸当で分かりやすくしてみましょう。

 

コールバックのラッピング

多くの非同期処理APIは

コールバックスタイルのインターフェースを持ってます。

suspendCoroutine の 「suspend function」 を使うと

簡単にコールバックをその中にラッピングすることができます。

簡単な例を挙げてみます。


fun longComputation(params: Params, callback: (Result) -> Unit)

longComputation という function があって、

それのコールバックは Result という計算結果を受け取ります。

これは、以下のように簡単にラッピングできます。


suspend fun longComputation(params: Params): Result = suspendCoroutine { cont ->
  longComputation(params) { cont.resume(it) }
}

分かりやすく計算結果を返し、同じ非同期ですがスレッドをブロックしません。

kotlin-coroutines/kotlin-coroutines-informal.md at master · Kotlin/kotlin-coroutines

 

コールバックのラッピング

例えば、Camera2 APIでは必須のこれ。


void openCamera (String cameraId,
                CameraDevice.StateCallback callback,
                Handler handler)

CameraManager | Android Developers

コールバックとバックグラウンドハンドラやスレッドの

準備や後始末の処理が必要ですが、

それらがあちこちに分散してしまい辛くなりますが、

Kotlin coroutine に頼ると、

きれいにラッピングできます。


suspend fun CameraManager.openCamera(cameraId: String): CameraDevice? =
  suspendCoroutine { cont ->

    val callback = object : CameraDevice.StateCallback() {

      override fun onOpened(camera: CameraDevice) {
        cont.resume(camera)
      }

      override fun onDisconnected(camera: CameraDevice) {
        cont.resume(null)
      }

      override fun onError(camera: CameraDevice, error: Int) {
        cont.resume(null)
      }
   }
   openCamera(cameraId, callback, null)
 }

android - Existing 3-function callback to Kotlin Coroutines - Stack Overflow

他のいくつかのコールバックも

同じように書き換えていくと

かなり見通しよくなります。

しかし、

このGサンプルコードは分かりづらすぎぢゃんね?


Kotlin で 非同期処理 Coroutine #1 ~ launch(), async()

ネット上を調べてみてもよくわかりません。

難しい言葉や experimental な仕様の変更などあったりして。

少しづつ試してみながらマスターしていきましょう。

// build.gradle

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.22.2"

//gradle.properties

kotlin.coroutines=enable

kotlinx.coroutines/coroutines-guide-ui.md at master · Kotlin/kotlinx.coroutines

まず、これ。


for (i in 1..10) {
  Timber.d("$i")
  Thread.sleep(1000)
}

非同期にしたいですよね。
launch から始めます。


launch { // @
  for (i in 1..10) {
    Timber.d("$i")
    Thread.sleep(1000)
  }
}


launch {
  for (i in 1..10) {
    Timber.d("$i")
    delay(1000)  // @
  }
}

引数をつけて渡す。

UI :
UIスレッドで実行。

CommonPool :
バックグランドスレッドで実行。


launch(UI) {  // @
  for (i in 1..10) {
    Timber.d("$i")
    delay(1000)
  }
}


launch(CommonPool) {  // @
  for (i in 1..10) {
    Timber.d("$i")
    delay(1000)
  }
}


launch(UI + CommonPool) {  // @
  for (i in 1..10) {
    Timber.d("$i")
    delay(1000)
  }
}

launch() の戻りからキャンセルできます。


val job = launch(UI) {  // @
  for (i in 1..10) {
    Timber.d("$i")
    delay(1000)
  }
}

fab.setOnClickListener {
  job.cancel() // @
}

 

まとめ

UIスレッドに限定されたコルーチンは、UIスレッドをブロックすることなく、UI内の何かを自由に更新して中断することができます。

delay が待っている間UIスレッドはブロックされないのでUIはフリーズしません。ただ単にコルーチンを中断します。

Job.cancelは完全にスレッドセーフでノンブロッキングです。
実際に終了するのを待つことなく、コルーチンがそのジョブをキャンセルするように通知するだけです。 どこからでも呼び出すことができます。

基本的な非同期呼び出しは、launch() と async() の2つ。似ているが戻りが異なる。