Android-kotlinコルーチンアプリケーション

1.依存関係を追加する

dependencies {
  ...
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x"
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x"
}

特定のバージョン番号は公式ウェブサイトで検索できます

2.コルーチンの役割

コルーチンは、コールバック地獄の問題を解決するために使用できます。新しいテクノロジーとして、これを以前のコードと常に比較して、新しいテクノロジーを使用することでどのような改善がもたらされるかを確認します。
最初に、例としてネットワークからデータを取得しました前の実装方法を見てみましょう:

@UiThread
fun makeNetworkRequest() {
    slowFetch { result ->
        show(result)
    }
}

次に、コルーチンを実装する方法を確認します。

@UiThread
suspend fun makeNetworkRequest() {
    val result = slowFetch()
    show(result)
}

// slowFetch is main-safe using coroutines
suspend fun slowFetch(): SlowResult { ... }

suspendキーワードは、他の言語のasyncに似ていますが、現在のスレッドをブロックせず、データが返されるのを非同期的に待機します。
比較により、コルーチンを使用すると次の利点があることがわかります。

  1. コードを理解しやすくし、コードをより合理化する
  2. スレッドをブロックしません
  3. 連鎖可能

上記の例では、ネットワークからデータを取得する方法が1つしかないため、その能力が発揮されない場合があります。次の要件があると仮定して、サーバーからデータを2回取得し、データベースにデータを書き込みます。コルーチンの方法で実装されます:

// Request data from network and save it to database with coroutines

// Because of the @WorkerThread, this function cannot be called on the
// main thread without causing an error.
@WorkerThread
suspend fun makeNetworkRequest() {
    // slowFetch and anotherFetch are suspend functions
    val slow = slowFetch()
    val another = anotherFetch()
    // save is a regular function and will block this thread
    database.save(slow, another)
}

// slowFetch is main-safe using coroutines
suspend fun slowFetch(): SlowResult { ... }
// anotherFetch is main-safe using coroutines
suspend fun anotherFetch(): AnotherResult { ... }

3.コルーチンの要点

コルーチンスコープ

コルーチンスコープは、ジョブを介してスコープ内のすべてのコルーチンを管理します。ジョブのキャンセルによってスコープ内のすべてのコルーチンがキャンセルされます。
アプリケーションシナリオ:アクティビティまたはフラグメントを終了するときに、ジョブのキャンセルを呼び出して、そのすべてのコルーチンをキャンセルできます

発車係

コルーチンを使用すると、ディスパッチャを介してスレッドを簡単に切り替えることができます。スレッド化スキルを指定し
ます。1.スレッドをブロックすることを心配せずにメインスレッドで直接操作できるため、スレッド間の切り替えによるリソース消費を節約できます
2.大きなオーバーヘッドたとえば、データベースの操作、大きなjsonファイルの解析
などでは、メインスレッド自体を占有しないRoom、Retrofitなどのバックグラウンドスレッドを指定する必要があります。これはメインスレッドで直接適用できるため、コードを簡略化できます。

viewModelScope

ViewModelScopeはViewModelの拡張メソッドです。このスコープはDispatchers.Mainにバインドされています。ViewModelonClearedになると自動的にキャンセルされます。もちろん、このメソッドを使用するには、依存関係を追加する必要があります:

dependencies {
  ...
  implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:x.x.x"
}

ViewModelでの使用例:
コードを使用する前に:

private fun updateTaps() {
   // TODO: Convert updateTaps to use coroutines
   tapCount++
   BACKGROUND.submit {
       Thread.sleep(1_000)
       _taps.postValue("$tapCount taps")
   }
}

viewModelScopeを使用した後のコード:

fun updateTaps() {
   // launch a coroutine in viewModelScope
   viewModelScope.launch {
       tapCount++
       // suspend this coroutine for one second
       delay(1_000)
       // resume in the main dispatcher
       // _snackbar.value can be called directly from main thread
       _taps.postValue("$tapCount taps")
   }
}

4.コルーチンの代替コールバック実装

使用する前にコードを見てください:

 fun refreshTitle() {
     // TODO: Convert refreshTitle to use coroutines
     _spinner.value = true
     repository.refreshTitleWithCallbacks(object : TitleRefreshCallback {
         override fun onCompleted() {
             _spinner.postValue(false)
         }

         override fun onError(cause: Throwable) {
             _snackBar.postValue(cause.message)
             _spinner.postValue(false)
         }
     })
 }

以前は、非同期の時間がかかるデータの
ヒント を処理するためにコールバックを一般的に使用していましたobject: TitleRefreshCallbackこれは、kotlinで匿名クラスを構築する方法であり、TitleRefreshCallbackインターフェースを実装する新しいインスタンスオブジェクトを作成します

次に、コルーチンの使用方法を確認します。

fun refreshTitle() {
  viewModelScope.launch {
     try {
          _spinner.value = true
          repository.refreshTitle()
      } catch (error: TitleRefreshError) {
          _snackBar.value = error.message
      }finally {
          _spinner.value = false
      }
    }
}

refreshTitleは、次のようにネットワーク要求を模倣します。

suspend fun refreshTitle() {
    delay(500)
}

上記のコードは、例外を使用してエラーシナリオを処理し、インターフェイスを使用してエラー処理をカスタマイズしないようにします

キーポイントの例

コルーチンの開始:
1.新しいコルーチンを作成します。コルーチンを開始するには、起動を使用する必要があります
。2. コルーチンがすでに存在します。コルーチン内からコルーチンを開始するには、2つの方法があります。

  1. 戻り値が必要ない場合は、launchstartを使用します
  2. 戻り値が必要な場合は、asyncstartを使用します

例外:
コルーチンでの使用uncaught exceptions基本的に通常のメソッドでの使用と似ていますが、デフォルトではキャンセルされることに注意してくださいJob
つまり、スコープ内のすべてのコルーチンをキャンセルするように親コルーチンに通知されます。例外がハンドルに到達しない場合、最終的にコルーチンスコープに渡されますCoroutineScope

5.コルーチンの置換に時間がかかる操作

コールバック実装スキーム:

fun refreshTitleWithCallbacks(titleRefreshCallback: TitleRefreshCallback) {
    // This request will be run on a background thread by retrofit
    BACKGROUND.submit {
        try {
            // Make network request using a blocking call
            val result = network.fetchNextTitle().execute()
            if (result.isSuccessful) {
                // Save it to database
                titleDao.insertTitle(Title(result.body()!!))
                // Inform the caller the refresh is completed
                titleRefreshCallback.onCompleted()
            } else {
                // If it's not successful, inform the callback of the error
                titleRefreshCallback.onError(
                        TitleRefreshError("Unable to refresh title", null))
            }
        } catch (cause: Throwable) {
            // If anything throws an exception, inform the caller
            titleRefreshCallback.onError(
                    TitleRefreshError("Unable to refresh title", cause))
        }
    }
}

コルーチンプログラムを使用した後:

suspend fun refreshTitle() {
   // interact with *blocking* network and IO calls from a coroutine
   withContext(Dispatchers.IO) {
       val result = try {
           // Make network request using a blocking call
           network.fetchNextTitle().execute()
       } catch (cause: Throwable) {
           // If the network throws an exception, inform the caller
           throw TitleRefreshError("Unable to refresh title", cause)
       }
      
       if (result.isSuccessful) {
           // Save it to database
           titleDao.insertTitle(Title(result.body()!!))
       } else {
           // If it's not successful, inform the callback of the error
           throw TitleRefreshError("Unable to refresh title", null)
       }
   }
}

スキルポイント:
1.コルーチンwithContext使用して切り替えますdispatcher
。2。コルーチンはデフォルトで3つのタイプを提供しますDispatchers
メイン:メインスレッド
IO:ネットワークまたはディスクからのデータの読み取りなどのIO操作用
デフォルト:主にCPU集中型のタスク用

部屋を使用し、コルーチンインターフェイスで改造する

上記の概要では、withContextを使用して自分でスレッドの切り替えを管理しています。これは、ネットワークからデータを取得してデータベースにデータを挿入することだけのためです。2つのステップがルームフレームワークとコルーチンサポート付きの改良フレームワークを使用する場合は、コードをさらに簡略化できます。まず、データベースコードを変換し、ネットワークからコードを取得します。

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTitle(title: Title)
interface MainNetwork {
   @GET("next_title.json")
   suspend fun fetchNextTitle(): String
}

次に、前の手順で手動管理スレッドコードを次のように変換します。

suspend fun refreshTitle() {
   try {
       // Make network request using a blocking call
       val result = network.fetchNextTitle()
       titleDao.insertTitle(Title(result))
   } catch (cause: Throwable) {
       // If anything throws an exception, inform the caller
       throw TitleRefreshError("Unable to refresh title", cause)
   }
}

このようにして、コードは以前よりも大幅に簡素化されます。
ヒント:部屋とレトロフィットは、使用されていない独自の内部カスタムスレッドですDispatchers.IO

高度な関数転送の一時停止方法

コードを作成するときは、繰り返しコードを抽出する必要があります。次の例では、高次関数を使用して待機コードを抽出します。まず、前の実装コードを見てください。

fun refreshTitle() {
    viewModelScope.launch {
        try {
            _spinner.value = true
            repository.refreshTitle()
        } catch (error: TitleRefreshError) {
            _snackBar.value = error.message
        }finally {
            _spinner.value = false
        }
    }
}

変換コードの直後:

fun refreshTitle() {
    launchDataLoading{
        repository.refreshTitle()
    }
}

private fun launchDataLoading(block: suspend () ->Unit) : Job {
    return viewModelScope.launch {
        try {
            _spinner.value = true
            block()
        } catch (error: TitleRefreshError) {
            _snackBar.value = error.message
        }finally {
            _spinner.value = false
        }
    }
}

上記の実装では、block: suspend () ->Unitサスペンドラムダ式がパラメーターとして渡されます。

159件のオリジナル記事を公開 22件の賞賛 90,000回以上の閲覧

おすすめ

転載: blog.csdn.net/ytuglt/article/details/105585408