Kotlinの1.2.xではまだexperimentalだけど、1.3で正式導入予定のCoroutine、特にasync/awaitはAPI呼び出しなどの非同期な処理に便利そうです。
ただ、AndroidのActivityやFragmentにはライフサイクルがあって、Activityが破棄されたあとにViewをいじったりすると例外が発生してしまうという問題があります。
その辺のことをまじめに考えずに、非同期処理を書いてしまっったら、上記の問題はどうなるのでしょうか?
結論を言ってしまうと、適当に書いても問題なさそうです。
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "MainActivity"
}
private val button by lazy { findViewById<TextView>(R.id.button) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
Log.d(TAG, "clicked")
launch(UI) {
// coroutineからでないとsuspendedなメソッドを使えない(コンパイル時にエラー)
Log.d(TAG, "clicked - launch")
onPressed()
}
Log.d(TAG, "clicked - finish")
finish()
Log.d(TAG, "clicked - finished")
}
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "destroy")
}
private suspend fun onPressed() {
Log.d(TAG, "pressed")
val v = async {
Thread.sleep(5000)
Log.d(TAG, "pressed - wake up")
System.currentTimeMillis().toString()
}
Log.d(TAG, "pressed - before await")
button.text = v.await()
Log.d(TAG, "pressed - after await")
}
}
こんなActivityを書いて、実行してみます。
onPressed()は、suspend(つまり一旦中断される事がある)で、かつ、UIをいじるメソッドなので、launch(UI)内で使うことになります。
onPressed()内での処理は、確認のために遅延が起こればいいだけなので、5秒スリープしてから、ボタンのキャプションを書き換えるようにしました。
さらに、button.setOnClickListenerでは、onPressed()の終了を待たずに、finish()します。
あと、開発者オプションで、アクティビティを保持しないようにしておけば、Activityが破棄されたあとに、Viewの書き換えが起こるはずです。
そのときのログは、こんな感じになりました。
D/MainActivity: clicked
D/MainActivity: clicked - finish
D/MainActivity: clicked - finished
D/MainActivity: pressed
D/MainActivity: pressed - before await
D/MainActivity: destroy
D/MainActivity: pressed - wake up
D/MainActivity: pressed - after await
D/MainActivity: clicked - launch
onDestroyのあとにViewをいじっても、エラーにならないんですね。
ちょっと気持ち悪い感じがしないでもないですが、async/awaitを使った非同期処理は、Activity等のライフサイクルを考えずに、つかってもよしなにしてくれると思っていいみたいです。