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回取得し、データベースにデータを書き込みます。コルーチンの方法で実装されます:
// 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つの方法があります。
- 戻り値が必要ない場合は、
launch
startを使用します - 戻り値が必要な場合は、
async
startを使用します
例外:
コルーチンでの使用は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
サスペンドラムダ式がパラメーターとして渡されます。