はじめに
Android開発でもはや避けて通れないのが Kotlin Coroutines。
非同期処理をスッキリ書ける便利な機能ですが、使い方を間違えるとクラッシュやメモリリークの原因にもなります。
今回は、基本の使い方と、実務でハマりやすいポイントをセットでまとめてみました。
Coroutineの基本:launchとasync
まずは一番基本的な使い方から。
非同期処理を「並列に走らせたい」のか「順番に処理したい」のかで、使う関数が変わります。
GlobalScope.launch {
// launchは結果を返さない
delay(1000)
println("launch完了!")
}
GlobalScope.async {
// asyncは結果を返す
delay(1000)
"asyncの結果"
}.await()
launch
は「火をつけて放置する」タイプ、async
は「結果を受け取る」タイプと覚えておくとイメージしやすいですね。
スコープの正しい使い方
実務ではGlobalScope
を使うのはおすすめしません。
ActivityやViewModelなど、ライフサイクルに応じてキャンセルできるスコープを使うのが基本です。
class MainViewModel : ViewModel() {
fun fetchData() {
viewModelScope.launch {
val result = repository.loadData()
Log.d("Coroutine", "結果: $result")
}
}
}
viewModelScope
を使うと、ViewModelが破棄されるタイミングでCoroutineもキャンセルされます。
これだけでリーク防止になります。
Dispatchersを理解する
スレッドを切り替えるには、Dispatchers
を使います。
viewModelScope.launch(Dispatchers.IO) {
// ネットワークやDBなど重い処理
val data = api.getData()
withContext(Dispatchers.Main) {
// UIスレッドに戻って更新
binding.textView.text = data
}
}
このように、IO → Mainを意識的に切り替えるのがポイントです。
非同期処理でも「どのスレッドで動いているか」を意識しないとバグにつながります。
よくある落とし穴①:キャンセルされないCoroutine
例えばActivity内で以下のようにGlobalScope
を使うと、Activityが閉じても処理が走り続けます。
GlobalScope.launch {
delay(5000)
Log.d("Coroutine", "Activityはもうないけど動いてる…")
}
これは完全にアンチパターンです。
必ずlifecycleScope
やviewModelScope
など、
スコープをライフサイクルに紐づけて使いましょう。
よくある落とし穴②:Mainスレッドで重い処理
以下のように書いてしまうと、Mainスレッドでブロッキングが発生してしまいます。
lifecycleScope.launch {
val result = heavyTask() // ← delayではなくThread.sleepを使うなど
textView.text = result
}
内部処理が重い場合は、必ずwithContext(Dispatchers.IO)
で切り替えましょう。
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { heavyTask() }
textView.text = result
}
よくある落とし穴③:スコープの誤用
RecyclerView.AdapterなどでlifecycleScope
を直接呼ぶのも危険です。
Adapterはライフサイクルを持たないため、親FragmentやActivityからスコープを渡すようにしましょう。
class MyAdapter(private val scope: CoroutineScope) : RecyclerView.Adapter<ViewHolder>() {
fun update() {
scope.launch {
// 安全に動作
}
}
}
こうしておけば、親のライフサイクルと連動して安全にキャンセルされます。
OS16でのCoroutine挙動について
Android 16(予定)では、バックグラウンド制限やスレッド優先度管理がより厳密になります。
そのため、重い処理をMainスレッドで走らせると、強制キャンセルされるケースもあります。
Coroutineを使う際は、今後さらにDispatchers
の適切な指定が重要になります。
まとめ
- GlobalScopeは使わない
- スコープはライフサイクルに紐づける(viewModelScope / lifecycleScope)
- 重い処理はDispatchers.IO、UI更新はDispatchers.Main
- Android 16以降ではMainスレッド処理の制限がより強化される
Coroutineは「便利だけど危険な道具」。
正しくスコープを意識すれば、保守性もパフォーマンスも一気に上がります。
もしチームで使うなら、基本パターンを統一しておくのがおすすめです。
コメント