Kotlin coroutines on Android

A coroutine is concurrency design pattern that you can use onAndroid to simplify code that executes asynchronously.Coroutines was added to Kotlin in version 1.3 and are based on establishedconcepts from other languages.

On Android, coroutines help to manage long running tasks that mightotherwise block main thread and cause your app to be unresponsive.このトピックでは、Kotlin のコルーチンを使用してこれらの問題に対処し、よりクリーンで簡潔なアプリ コードを記述できるようにする方法について説明します。

  • 軽量。 コルーチンが実行されているスレッドをブロックしないサスペンドをサポートするため、1つのスレッドで多くのコルーチンを実行することができます。 サスペンドは、多くの同時処理をサポートしながら、ブロッキングよりもメモリを節約します。
  • ビルトイン キャンセルのサポート: キャンセルは、実行中のコルーチン階層を通して自動的に伝搬されます。 多くの Jetpack ライブラリは、コルーチンの完全なサポートを提供する拡張機能を含んでいます。

Examples overview

Guide to the app architecture に基づき、このトピックの例はネットワーク要求を行い、結果をメインスレッドに返し、そこでアプリはユーザーに結果を表示することができます。 このガイドでは、コルーチンを使用してメイン スレッドをブロックされないようにするさまざまな解決策を繰り返します。

ViewModel には、コルーチンで直接動作する KTX 拡張のセットがあります。 これらの拡張機能はlifecycle-viewmodel-ktxライブラリであり、このガイドで使用されています。

Dependency info

あなたの Android プロジェクトでコルーチンを使用するには、次の依存関係をアプリの build.gradle ファイルに追加します:

dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'}

Executing in a background thread

Main thread でネットワーク要求を作成すると、応答を受信するまで待つ、つまりブロックするようになります。 スレッドがブロックされているため、OS は onDraw() を呼び出すことができず、アプリがフリーズし、潜在的に ANR (Application Not Responding) ダイアログにつながる可能性があります。

First, let’s take at our Repository class and see how it’smaking the network request:

sealed class Result<out R> { data class Success<out T>(val data: T) : Result<T>() data class Error(val exception: Exception) : Result<Nothing>()}class LoginRepository(private val responseParser: LoginResponseParser) { private const val loginUrl = "https://example.com/login" // Function that makes the network request, blocking the current thread fun makeLoginRequest( jsonBody: String ): Result<LoginResponse> { val url = URL(loginUrl) (url.openConnection() as? HttpURLConnection)?.run { requestMethod = "POST" setRequestProperty("Content-Type", "application/json; utf-8") setRequestProperty("Accept", "application/json") doOutput = true outputStream.write(jsonBody.toByteArray()) return Result.Success(responseParser.parse(inputStream)) } return Result.Error(Exception("Cannot open HttpURLConnection")) }}

makeLoginRequest is synchronous and blocks the calling thread.より良い使用経験のために、この操作を背景スレッド上で実行します。

ViewModel は、ユーザーがたとえばボタンをクリックすると、ネットワーク リクエストをトリガーします:

class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun login(username: String, token: String) { val jsonBody = "{ username: \"$username\", token: \"$token\"}" loginRepository.makeLoginRequest(jsonBody) }}

前のコードでは、LoginViewModel はネットワーク リクエストを作成するときに UI スレッドをブロックしています。 メイン スレッドから実行を移す最も簡単な方法は、新しいコルーチンを作成し、I/O スレッドでネットワーク リクエストを実行することです。 すべてのコルーチンはアスコープで実行されなければならないことに注意してください。

  • launch は、コルーチンを生成し、その関数本体の実行を対応するディスパッチャにディスパッチする関数である。
  • login 関数は次のように実行されます:

    • アプリはメインスレッドの View レイヤーから login 関数を呼び出します。
    • launch は新しいコルーチンを作成し、ネットワーク要求は I/O 処理用に確保したスレッドで独立に実行されます。

    このコルーチンはviewModelScopeで開始されているので、ViewModelのスコープ内で実行される。 ユーザーが画面から離れるために ViewModel が破棄されると、viewModelScope は自動的にキャンセルされ、実行中のすべてのコルーチンがキャンセルされます。

    前の例での 1 つの問題は、makeLoginRequest を呼び出すものは明示的にメインスレッドから実行を移動することを忘れない必要があることです。

    Use coroutines for main-safety

    We consider a function main-safe when it doesn’t block UI updates on themain thread.これは、メイン スレッドでの UI 更新をブロックしない関数をメイン安全と見なします。 メインスレッドから makeLoginRequest を呼び出すと UI がブロックされるため、makeLoginRequest 関数はメインセーフではありません。 Coroutines ライブラリの withContext() 関数を使用して、コルーチンの実行を別のスレッドに移動します。

    class LoginRepository(...) { ... suspend fun makeLoginRequest( jsonBody: String ): Result<LoginResponse> { // Move the execution of the coroutine to the I/O dispatcher return withContext(Dispatchers.IO) { // Blocking network request code } }}

    withContext(Dispatchers.IO) はコルーチンの実行を I/O スレッドに移動し、呼び出し関数をメインセーフにするとともに必要に応じて UI の更新をできるようにします。 このキーワードは、コルーチン内から呼び出される関数を強制するための Kotlin の方法である。makeLoginRequest はメインスレッドから実行を移動するので、login 関数のコルーチンはメインスレッドで実行できるようになります。

    このコードは前の login の例といくつかの点で異なっています:

    • launchDispatchers.IO パラメータをとりません。 launchDispatcher を渡さない場合、viewModelScope から起動されたすべてのコルーチンはメインスレッドで実行されます。
    • ネットワーク要求の結果は、後継の障害 UI を表示するように処理されるようになりました。

    ログイン関数は次のように実行されます:

    • アプリはメインスレッド上のView層からlogin()関数を呼び出します。
    • launchはメインスレッド上でネットワーク要求を行う新しいコールーチンを作り、コールーチンが実行を開始しました。
    • コルーチン内で loginRepository.makeLoginRequest()now を呼び出すと、makeLoginRequest()withContext ブロックが終了するまでコルーチンの実行が停止します。
    • withContextブロックが終了すると、login()のコルーチンがネットワーク要求の結果を使用してメインスレッド上で実行を再開します。

    例外の処理

    Repository層が投げることができる例外を処理するには、Kotlin に組み込まれている例外サポートを使用します。次の例では、try-catchブロックを使用しています。

    Leave a Reply