
みなさまこんにちは〜!
メモリアインクのすだです。
本日は、
Androidアプリ開発における非同期処理の役割やKotlin Coroutinesの使い方についてわかりやすく解説していきます!
この記事を読んでわかること…
・非同期処理とは
・Kotlin Coroutinesの使い方
環境
- Kotlin (ver 1.9.0)
- Android Studio (Giraffe | 2022.3.1 Patch 3)
非同期処理とは?なぜ必要?
非同期処理とは、「時間がかかる処理を、待たずに別で進める」仕組みです。
たとえば以下のような処理は、非同期で実行しないとアプリが固まってしまいます。
- ネットワーク通信(APIの呼び出し)
- データベース操作
- 画像読み込みやファイル処理
Android アプリでは、UIを操作するメインスレッドが止まると「アプリが応答しない(ANR)」 という問題になります。(ユーザーからは、画面がフリーズしているように見えてしまう)
そのため、重い処理は必ず非同期で行う必要があるのです。
アプリ開発では「端末の画面(UI)」を直接制御しているため、非同期処理が“ユーザー体験”に直結します。
Kotlin Coroutinesとは?
Coroutines(コルーチン) は、Kotlin が提供する非同期処理のための仕組みで、
「軽量スレッド(通常のスレッドよりもコストが小さく、効率的な小さな実行単位)」のように動作します。
非同期処理(Coroutines)とは、「メインスレッドを止めずに、重たい処理を裏側で実行する仕組み」のことです。
つまり、メインスレッドとバックグラウンドスレッドを“適切に切り替えながら” 並行処理を実現できる機能とも言えます。
スレッドの役割
メインスレッド(UIスレッド):画面の描画、ボタンの反応など、すべてのUI処理を担当。ここを止めてしまうとアプリが固まります。
バックグラウンドスレッド:ネット通信や重たい計算処理など、UIに関係ない処理を裏で担当。時間がかかる処理は基本的にこちらで行います。
Kotlin の Coroutines を使えば、これらのスレッドを意識しすぎることなく、簡単・安全に非同期処理を記述できます。
図解↓
【シングルスレッドで処理した場合】
UI ← 通信中...(画面フリーズ) ← データ取得後に再開
【Coroutineを使った非同期処理】
UI(メインスレッド)────▶ 表示処理続ける
│
└────▶ 通信処理(バックグラウンドスレッド)
➡ UIは止まらず、通信は裏で進む → 結果だけUIに戻す
Coroutinesの基本構文と用語
CoroutineScope
CoroutineScope とは、「コルーチンがどこで動くか・いつ終わるかを管理する“枠”のこと」です。
画面(Activity)や ViewModel にスコープを紐づけることで、画面が閉じられたら、その中の処理(コルーチン)も止める ことができます。
よく使う CoroutineScope の種類としては以下があります。
CoroutineScope(…)
… 独自のスコープを作りたいときviewModelScope
… ViewModelのライフサイクルに合わせて動く(UIが閉じたら処理も終了)lifecycleScope
… ActivityやFragmentのライフサイクルに合わせて動く(Jetpack)
suspend
suspend
をつけることで、その関数は「途中で一時停止できる関数」になります。
一時停止(中断)とは:
- 通信中に一旦止まって、終わったら続きから再開
- 重たい処理を非同期で中断・再開できるようにする
ということを意味します。
suspend fun fetchUserName(): String {
delay(1000) // ← コルーチンの「一時停止」
return "Alice"
}
実用的なケース▼
たとえば、ファイルをダウンロードする suspend
関数があるとします。
このとき、ユーザーが「キャンセル」ボタンを押せば、そのダウンロード処理(=コルーチン)を途中でキャンセル(中断)することができます。これは、suspend
関数が中断可能であるからこそ実現できる仕組みです。
launchとasync / await
launch
とasync / await
は、どちらもコルーチンビルダーと呼ばれるもので、
コルーチンをスタートさせるための命令です。
簡単に説明すると、launch
… 「何かを並行してやりたいけど、結果はいらない」という時に使うasync / await
… 「非同期処理の結果が欲しい」という時に使う
という分け方です。
実際にコードを用いて比較してみます。
まず、非同期でユーザー名を取得する関数を用意します:
suspend fun fetchUserName(): String {
delay(1000) // 通信に1秒かかるイメージ
return "Alice"
}
①launch
だけを使う場合(結果を使わない)▼
viewModelScope.launch {
fetchUserName() // 呼び出すだけ。結果は使わない
println("処理完了!")
}
→結果をどこにも入れてないので、戻り値は無視されます。
これは例えば「ログ送信」「DBに保存」など、結果が不要な処理に向いています。
②launch
+async
+ await
を使う場合(結果を使いたい)▼
viewModelScope.launch {
val nameDeferred = async { fetchUserName() }
val name = nameDeferred.await()
println("取得したユーザー名: $name")
}
→こちらは、非同期で処理を開始(launch)し、後で結果を使う(await)パターンです。
async { ... }
:非同期で処理開始(Deferred<String>
を返す).await()
:その結果を待って取得
また、以下のように複数の処理を同時に走らせて、結果をまとめて取得できます▼
viewModelScope.launch {
val userDeferred = async { fetchUserName() } // ① 非同期で開始
val ageDeferred = async { fetchUserAge() } // ② 同時にこっちも開始
val name = userDeferred.await() // ③ 結果を待つ
val age = ageDeferred.await() // ④ 結果を待つ
println("名前: $name, 年齢: $age")
}
※ awaitは「中断」するだけなので、メインスレッドは止まりません・
Dispatchers
Dispatchers
は「この処理をどのスレッドで実行するか」を決めるものです。
基本的には launch
や withContext()
の中に指定して書きます。
launch
に Dispatcher を指定するパターン:
そのブロック全体を指定したスレッドで動かす場合です▼
viewModelScope.launch(Dispatchers.IO) {
val data = fetchData()
println(data)
}
withContext
を使ってスレッドを切り替えるパターン:
処理の一部だけを別スレッドに切り替える場合です▼
viewModelScope.launch {
val data = withContext(Dispatchers.IO) {
fetchData() // 重たい処理だけIOでやる
}
updateUI(data) // ← Mainスレッド(元に戻る)
}
よく使う Dispatcher の種類
Dispatchers.Main | UIスレッド | 画面の更新、UIの操作 |
Dispatchers.IO | 入出力(通信・DB) | ネットワーク通信、DB、ファイル操作など |
Dispatchers.Default | 計算・CPU重い処理 | データ加工、リストのソートなど |
実践形式で非同期処理を学ぶ
① 以下は キャンセル可能なファイルダウンロード処理を、Coroutinesを使って実現する例です。
import kotlinx.coroutines.*
var isDownloaded = false
suspend fun downloadJob(): Job {
try {
delay(7_000L)
} catch (e: CancellationException) {
println("ダウンロードがキャンセルされました")
}
}
fun main() = runBlocking {
println("ダウンロードスタート")
val job = launch {
isDownloaded = downloadFile()
}
delay(1_000L)
job.cancelAndJoin()
if(isDownloaded) {
println(ダウンロード成功")
} else {
println(ダウンロード失敗")
}
println("ダウンロード状況: $isDownloaded")
}
このコードでは、suspend
関数として定義した中断可能な処理(downloadFile
)を、launch
ブロックの中で実行しています。
その後、cancelAndJoin()
を使って 「そのコルーチンをキャンセルし、キャンセル処理の完了まで待機」する仕組みになっています。
cancel()
:コルーチンにキャンセルの合図を送るjoin()
:コルーチンの完了を待つcancelAndJoin()
:この2つをまとめて行う便利な関数です
delay()
はキャンセル可能な suspend
関数なので、cancel()
が呼ばれると CancellationException
が発生し、catch
ブロックに入ります。
すなわち、この処理の実行結果は以下のようになります。
ダウンロードスタート
ダウンロードがキャンセルされました
ダウンロード失敗
ダウンロード状況: false
② 次は ViewModel 内で Coroutines を使って、APIからデータを取得し、LiveData に流す例です。
class UserViewModel : ViewModel() {
private val _user = MutableLiveData<String>()
val user: LiveData<String> = _user
private suspend fun fetchUserFromApi(): String {
delay(1000) // 疑似API
return "ユーザー名:山田太郎"
}
fun fetchUser() {
viewModelScope.launch {
try {
val result = withContext(Dispatchers.IO) {
fetchUserFromApi()
}
_user.value = result
} catch (e: Exception) {
_user.value = "エラーが発生しました"
}
}
}
}
このコードでも、suspend
関数として定義した中断可能な処理(fetchUserFromApi
)を、launch
ブロックの中で実行しています。
通信が走る処理なので、withContext(Dispatchers.IO)
を使用しています。
非同期処理が完了したら、LiveData
に値をセットする仕組みです。
まとめ
おつかれさまでした。いかがでしたでしょうか!
Android アプリ開発では、非同期処理をうまく扱えるかどうかが、アプリの品質に大きく関わってきます。
Coroutinesを使った非同期処理の書き方をぜひ覚えてください!



技術者としてのキャリアパスを次のレベルへと進めたい皆様、
<未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】>
自分の市場価値をさらに向上させてみませんか?
それではまた次回の記事でお会いしましょう!
コメント