LoginSignup
5
1

More than 5 years have passed since last update.

Kotlinのasync/awaitをカジュアルに使う話

Posted at

Kotlinの1.2.xではまだexperimentalだけど、1.3で正式導入予定のCoroutine、特にasync/awaitはAPI呼び出しなどの非同期な処理に便利そうです。

ただ、AndroidのActivityやFragmentにはライフサイクルがあって、Activityが破棄されたあとにViewをいじったりすると例外が発生してしまうという問題があります。

その辺のことをまじめに考えずに、非同期処理を書いてしまっったら、上記の問題はどうなるのでしょうか?

結論を言ってしまうと、適当に書いても問題なさそうです。

MainActivity.kt
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等のライフサイクルを考えずに、つかってもよしなにしてくれると思っていいみたいです。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1