🤹

ゆめみ社のAndroidインターンに参加してきたので学んだことをまとめておく(非同期処理編)

2022/09/07に公開

ゆめみ社の Android インターンに参加しました。
なかなかにありがたい経験をさせてもらえたので、アウトプットします 👍

  • 基本的には課題を進めていく 🚶
  • 課題は AndroidView で開発する前提になっていたが、Jetpack Compose の方が得意という旨を伝えると、Compose を使って UI を作っていくことに。柔軟に対応していただくことができた。感謝 😊

非同期処理 (Coroutine)

参考:
良さげな記事 1
https://qiita.com/kawmra/items/d024f9ab32ffe0604d39
JetBranin のドキュメント
https://kotlinlang.org/docs/coroutines-basics.html#coroutines-are-light-weight
良さげな記事 2
https://qiita.com/duke105/items/b5be074c79c6bed4d560
Coroutine とその実装に迫ったスライド
https://speakerdeck.com/sys1yagi/kotlin-korutinwo-li-jie-siyou?slide=60
公式ドキュメント
https://developer.android.com/kotlin/coroutines?hl=ja

個人的に、非同期処理は参加したインターンで一番勉強したかったところです。
(「suspendとかCoroutineとかScopeとかなんやねん」って感じだったので)

Kotlin の Coroutine で抑えたいのは Coroutine とsuspendキーワードでしょう。

Coroutine を理解する

大事なこと 1: Coroutine を使うと処理を中断できる

Coroutine を理解するためにまずこれを頭に叩き入れます。Coroutine を使うと処理が中断できるようになります。

// playMusic()がはじまった後にそれを中断することはできない
playMusic()
launch{
  playMusic()
}
// ここでとある書き方をするとplayMusic()を中断できる!

大事なこと 2: Coroutine とは

Coroutine とはとあるブロックが紐づいたインスタンス(ドキュメントでは計算インスタンスと呼ばれていました)です。例えば、以下のコードを見てみます。

この main 関数にはコメントで開始と終了が示された 1 つブロックがあります。ブロックとは{ ... }のこと(赤色部分)でこの中には処理がかけます。つまりブロックとは処理のまとまりを表します
このブロック一つ一つはオブジェクトではありません(よってインスタンスでもありません)。ですがこのブロックをオブジェクトとして扱いたいのです。

なぜブロックをオブジェクトとして扱いたいのでしょうか?答えはブロックをオブジェクトとして扱うことでとあるブロック.中断する()といったコードが書けるようになるためです。前項で Coroutine では処理を中断できると言いましたが、これができるのはブロックをオブジェクトとして扱うことができるからです。

Coroutine でブロックをオブジェクトとして扱う

実際にブロックをオブジェクトとして扱うためにはどうするのでしょうか?例を示します。

val job = launch {
  playMusic()
}

以上です。launch 関数を使ってブロック(ラムダ式)をオブジェクトとして生成し、jobという変数に代入しています。Coroutine ではブロックをJobクラスというクラスとして扱います。この job クラスにcancelメソッドという中断できるメソッドが含まれているので、job.cancel()とすることでブロック(処理)を中断することができるのです。

val job = launch {
  playMusic()
  playGame()
}
job.cancel()  //ジョブをキャンセル

launch メソッドは引数として渡されたラムダ式を Job クラスのインスタンスに変換し(厳密にはちょっと違うけど)、その Job を実行してくれます。この動きをコルーチンを起動するとかいったりします。

Coroutine はあくまで処理を中断できるように分けるだけ

Coroutine はそれを使って通信だとか並列処理だとか重い処理だとかを行うことができますが、処理をいくつかに分けているだけです。その分けた処理を中断できるように制御したり、再開してみたりするのが Coroutine だと認識しています。

suspend 関数を理解する

大事なこと 1: suspend 関数の特徴

suspend関数の例
suspend fun doSomething(){
  // ...
}

以下を頭に叩き込んでください

  • suspendがついた関数はその実行中に中断できます。
  • suspend 関数は他の suspend 関数内もしくはlaunch関数内でしか呼び出しできません

suspend 関数の実装を見てみる

Coroutine の実装を見てみます。(この記事なんかがいいかもです)

suspend fun susFunc() {
  println("test-1")
  delay(1_000)
  println("test-2")
}

このコードを

class susFunc(prevFunc) {
  var current: Int  // 現在の処理状況を表す
  init {
    current = 0
  }
  fun doNext() {  // 次の処理を実行
    when(current){
      0 -> {
        println("test-1")
	current ++
	delay(1_000,this)  // delay終了時にthis.doNext()を呼び出してもらう
      }
      1 -> {
        println("test-2")
	current ++
      }
      2 -> {
        prevFunc.doNext()  // 呼び出しもとの中断していた後の処理を行う。
      }
    }
  }
}

こんな感じに実装しているそうです。(だいぶ端折りました)特徴をまとめると、

  • 引数として追加で呼び出しもとを受け取る(自身終了時に呼び出しもと.doNext()を呼ぶため)
  • 関数実行時には doNext()が呼ばれる。
  • 関数を実行しているときにdelay()のようなsuspend関数を呼び出すことになったら自身を渡して呼び出した関数が終わり次第this.doNext()を読んでもらう。
  • 自身の処理が終了したら自身の呼び出しもと.doNext()を呼び出す。(これにより中断されていた呼び出しもとの処理が再開される)

図解すると、
普通の関数は

に対し suspend 関数は

と関数の呼び出し時に再開したい関数(呼び出し元関数)を渡されるようになり、呼び出し元関数の再開ができるように、つまりは中断しても後で再開できるようになるわけです。

suspend 関数をいろんなところで実行したい

何度も言うように suspend 関数は中断ができる関数です。ここでは、Android アプリにおいてはどのように使うのか見てみましょう。

ViewModelで使う
  class MyViewModel : ViewModel(){
    fun onClick(){
+     viewModelScope.launch{
+       // suspend関数を呼べる
+     }
    }
  }

viewModelScope.launchを使用します。viewModelScope にて launch されたコルーチンはViewModel が死ぬと自動的に内部のコルーチンをキャンセルしてくれます(あら便利)。

別スレッドで実行したい

Coroutine を使えば別スレッドで処理を行いたい時もいい感じに処理をかけます。

別スレッドで実行する
  class MyViewModel : ViewModel(){
    fun onClick(){
      viewModelScope.launch{
+       val userData = withContext(Dispatchers.IO) { getUserData() }
        // ...
      }
    }
  }


余談

今回紹介したlaunch関数はCoroutine ビルダーと呼ばれます。Coroutine ビルダーはlaunch以外にもasyncなどがあります。asyncawait()することで Coroutine の実行結果を得ることができます。

val user = async {
  getUserData()
}.await()

インターンアウトプットシリーズ一覧

Discussion