Androidで協程を正しく使うにはどうすればいいですか?

9361 ワード

前言
Google IOがAndroid 1級開発言語にKotlinを正式に発表したのを覚えていますか?Google IO 2017です.今から2年が経ち、Android開発者の立場から見ると、Kotlinの生態環境はますますよくなり、関連するオープンソースプロジェクトや学習資料も豊富になり、身の回りでKotlinを使用したり試したりしたい友达も増えています.長年にわたって金を掘ってきた私も、Kotlinのラベルの下の文章がだんだん多くなってきたことを明らかに感じることができます(実はまだ少ないのがかわいそうです).今年のGoogle IOもKotlin Firstのスローガンを発表し、多くの新しいAPIと機能特性がKotlinサポートを優先的に提供する.だから、今日になって、アンドロイド開発者がKotlinを勉強しない理由が見つからない.
今日お話ししたいのはKotlin Coroutineです.Kotlinリリース当初からコンセンサスがあったが、2018年のKotlinConf大会までJetBrainがKotlin 1.3 RCをリリースして安定版コンセンサスをもたらした.安定版のコラボレーションが1年以上発表されても、十分なユーザーはいないようですが、少なくとも私から見ればそうです.私が協程を学ぶ各段階の中で、問題に直面しても助けを求める場所は珍しく、技術群に投げつけると基本的に石が海に沈む.基本的には英語のドキュメントで問題を解決するしかありません.
協程に関する文章はたくさん読んだことがありますが、まとめてみると、次のようなものにすぎません.
1つ目はMediumの人気記事の翻訳ですが、実は私も翻訳しました.
Androidで使用協程(一):Getting The Background
Androidで使用協程(二):Getting started
Androidでの使用協程(三):Real Work
正直に言うと、この3つの文章は確かに私の協力に対する理解を深めた.
第2類は公式文書の翻訳で、私は少なくとも5つの翻訳バージョンを見たことがありますが、やはり公式サイトの文書を見たほうがいいと思います.もし英語が本当に骨が折れるなら、Kotlin中国語ステーションの翻訳と照らして読むことができます.
公式文書を長い間読んでいたが、私はほとんどGlobalScopeしか知らなかった.確かに、公式文書では基本的にGlobalScopeでサンプルコードを書いています.だから一部の開発者は、私自身も含めて、自分のコードを書くときも直接GlobalScopeになりました.偶然の機会にこのような問題が大きいことに気づいた.Androidでは一般的にGlobalScopeを直接使うことはお勧めしません.では、Androidではどのように協程を正しく使うべきでしょうか.もう少し細分化して、Activityで直接使うにはどうすればいいですか?ViewModel、LiveData、LifeCycleなどにどう合わせて使うのでしょうか?私は簡単なサンプルコードでAndroidの協力使用を説明します.あなたも手について叩くことができます.
Androidでのコラボレーションの使用
GlobalScope
一般的なアプリケーションシーンでは、ネットワークリクエスト、データ処理など、時間のかかるタスクを非同期で行うことを望んでいます.現在のページを離れるときも、進行中の非同期タスクをキャンセルすることを望んでいます.この2点は,まさに使用協程において注意すべき点である.GlobalScopeを直接使用することをお勧めしない以上、まずそれを使用するとどのような効果があるかを試してみましょう.
private fun launchFromGlobalScope() {
    GlobalScope.launch(Dispatchers.Main) {
        val deferred = async(Dispatchers.IO) {
            // network request
            delay(3000)
            "Get it"
        }
        globalScope.text = deferred.await()
        Toast.makeText(applicationContext, "GlobalScope", Toast.LENGTH_SHORT).show()
    }
}
launchFromGlobalScope()メソッドでは、GlobalScope.launch()を介して直接コモンシップを開始し、delay(3000)はネットワーク要求をシミュレートし、3秒後にToastプロンプトがポップアップします.使用上は何の問題もなく、正常にToastをイジェクトできます.しかし、この方法を実行すると、すぐに戻るキーを押して前のページに戻ると、Toastがポップアップされます.実際に開発中にネットワークを介してページの更新を要求すると,ユーザがこのページにいなくなった場合,これ以上要求する必要はなく,リソースを浪費するだけである.GlobalScopeは明らかにこの特性に合致していない.Kotlinドキュメントでは、以下のように詳細に説明されています.
Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely. Another use of the global scope is operators running in Dispatchers.Unconfined, which don’t have any job associated with them.
Application code usually should use an application-defined CoroutineScope. Using async or launch on the instance of GlobalScope is highly discouraged.
一般に、Global scopeは、アプリケーションのライフサイクル全体で実行され、早期にキャンセルされないトップレベルのコラボレーションを開始するために使用されます.プログラムコードは、通常、カスタムのコモンシップドメインを使用する必要があります.GlobalScopeを直接使用するasyncまたはlaunch法は強く推奨されない.
GlobalScopeによって作成されるコンシステントには親コンシステントはありません.GlobalScopeは通常、ライフサイクルコンポーネントにバインドされません.手動で管理しない限り、実際の開発のニーズを満たすことは難しい.だから、GlobalScopeはできるだけ使わないでください.
MainScope
公式ドキュメントでは、カスタマイズされたコラボレーションドメインを使用することについて説明しています.もちろん、Kotlinは適切なコラボレーションドメインMainScopeを提供しています.MainScopeの定義を見てみましょう.
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

この定義を覚えておくと、後のViewModelのコンセンサス使用でもこの書き方を参考にします.
私たちのActivityに独自のコラボレーションドメインを実現します.
class BasicCorotineActivity : AppCompatActivity(), CoroutineScope by MainScope() {}

拡張関数launch()を使用すると、主スレッドで直接コパスを開始できます.サンプルコードは次のとおりです.
private fun launchFromMainScope() {
    launch {
        val deferred = async(Dispatchers.IO) {
            // network request
            delay(3000)
            "Get it"
        }
        mainScope.text = deferred.await()
        Toast.makeText(applicationContext, "MainScope", Toast.LENGTH_SHORT).show()
    }
}

最後に、onDestroy()でコンシステントをキャンセルし、関数cancel()を拡張することによって実現することを忘れないでください.
override fun onDestroy() {
    super.onDestroy()
    cancel()
}
launchFromMainScope()の方法を試してみましょう.これはあなたのニーズに完全に合っていることに気づきます.実際の開発ではMainScopeをBaseActivityに統合できるので,テンプレートコードを繰り返し書く必要はない.
ViewModelScope
MVVMアーキテクチャを使用している場合は、Activityに論理コードを書くことはありません.コラボレーションを開始することは言うまでもありません.この時、ほとんどの仕事はView Modelに任せます.では、どのようにしてViewModelでコラボレーションドメインを定義しますか?上のMainScope()の定義を覚えていますか?そうです.引っ越して直接使えばいいです.
class ViewModelOne : ViewModel() {

    private val viewModelJob = SupervisorJob()
    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

    val mMessage: MutableLiveData = MutableLiveData()

    fun getMessage(message: String) {
        uiScope.launch {
            val deferred = async(Dispatchers.IO) {
                delay(2000)
                "post $message"
            }
            mMessage.value = deferred.await()
        }
    }

    override fun onCleared() {
        super.onCleared()
        viewModelJob.cancel()
    }
}

ここのuiScopeは実はMainScopeに等しい.呼び出しgetMessage()メソッドは、以前のlaunchFromMainScope()と同じ効果で、ViewModelのonCleared()コールでコンシステントをキャンセルしたことを覚えています.
これらのロジックを処理するためにBaseViewModelを定義し、テンプレートコードを繰り返し書くことを避けることができます.しかしKotlinはあなたに同じことをさせて、もっと少ないコードを書いて、viewmodel-ktxに来ました.ktxを見ると、コードを簡略化するために使われていることがわかります.次の依存関係を導入します.
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha03"

そして、何もする必要はなく、コパスドメインviewModelScopeをそのまま使えばよい.viewModelScopeは、次のように定義されたViewModelの拡張属性です.
val ViewModel.viewModelScope: CoroutineScope
        get() {
            val scope: CoroutineScope? = this.getTag(JOB_KEY)
            if (scope != null) {
                return scope
            }
            return setTagIfAbsent(JOB_KEY,
                CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main))
        }

コードを見てみればわかるはずですが、よく知っているセットです.ViewModel.onCleared()が呼び出されると、viewModelScopeは、アクティブドメイン内のすべてのコヒーレンスを自動的にキャンセルする.使用例は次のとおりです.
fun getMessageByViewModel() {
    viewModelScope.launch {
        val deferred = async(Dispatchers.IO) { getMessage("ViewModel Ktx") }
        mMessage.value = deferred.await()
    }
}

ここまで書くと、viewModelScopeは需要を満たす最も簡単な書き方です.実際、全編を書き終えて、viewModelScopeは依然として私が思っている最高の選択です.
LiveData
Kotlinは同様にLiveDataに直接協程を使用する能力を与えた.次の依存関係を追加します.
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03"

非同期で実行する必要がある保留関数をliveData {}コードブロックに直接呼び出し、emit()関数を呼び出して処理結果を送信する.サンプルコードは次のとおりです.
val mResult: LiveData = liveData {
    val string = getMessage("LiveData Ktx")
    emit(string)
}

ここには何の表示呼び出しもないようですが、liveDataコードブロックは何で実行されているのでしょうか.ライブデータがactive状態に入ると、liveData{ }が自動的に実行されます.LiveDataがinactive状態になると、構成可能なtimeoutが経過すると自動的にキャンセルされます.それが完了する前にキャンセルされた場合、LiveDataが再びactiveになったときに再実行されます.前回の実行が正常に終了した場合、再実行は行われません.つまり自動キャンセルされたliveData{ }のみが運転を再開できるということです.その他の理由(例えばCancelationException)によるキャンセルも再実行されません.
したがって、livedata-ktxの使用には一定の制限があります.ユーザがアクティブにリフレッシュする必要があるシーンでは、満足できません.完全なライフサイクルでは、一度正常に実行されると、これ以上トリガーできません.この言葉は正しいかどうか分かりませんが、個人的にはそう理解しています.したがって、viewmodel-ktxの適用性はより広く、制御性もより良い.
LifecycleScope
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha03"
lifecycle-runtime-ktxは、各LifeCycleオブジェクトに対して、拡張属性によってコモンアクティブドメインlifecycleScopeを定義する.lifecycle.coroutineScopeまたはlifecycleOwner.lifecycleScopeでアクセスできます.サンプルコードは次のとおりです.
fun getMessageByLifeCycle(lifecycleOwner: LifecycleOwner) {
    lifecycleOwner.lifecycleScope.launch {
        val deferred = async(Dispatchers.IO) { getMessage("LifeCycle Ktx") }
        mMessage.value = deferred.await()
    }
}

LifeCycleがonDestroy()にコールバックすると、コモンアクティブドメインlifecycleScopeは自動的にキャンセルされます.Activity/Fragmentなどのライフサイクルコンポーネントでは便利に使用できますが、MVVMではあまりViewレイヤで論理処理を行うことはありません.viewModelScopeは基本的にView Modelのニーズを満たすことができ、lifecycleScopeも少し味気ないように見えます.しかし、彼には特別な使い方があります.
suspend fun  Lifecycle.whenCreated()
suspend fun  Lifecycle.whenStarted()
suspend fun  Lifecycle.whenResumed()
suspend fun  LifecycleOwner.whenCreated()
suspend fun  LifecycleOwner.whenStarted()
suspend fun  LifecycleOwner.whenResumed()

少なくとも特定のライフサイクルの後に保留関数を実行することを指定でき、Viewレイヤの負担をさらに軽減できます.
まとめ
以上、Androidでのコラボレーションの合理的な使用方法について簡単に説明しました.サンプルコードはGithubにアップロードされています.MVVM + の実戦種目については、私のオープンソース種目wanandroidを見てもいいし、貴重な意見も期待できます.
文章の先発微信公衆番号: 、Java、Androidのオリジナル知識の共有に専念し、LeetCodeの問題解.
もっと最新のオリジナル文章、コードをスキャンして私に注目してください!