Dispatchers.IO
Jetpack ComposeにおけるDispatchers.IOは、UIをフリーズさせないために、通信やファイル読み書きなどの重い非同期処理をバックグラウンドスレッドで実行するための仕組みです。
Jetpack ComposeのUI描画は常に「Mainスレッド(UIスレッド)」で行われるため、時間のかかる処理をそのまま実行すると画面がカクついたり、アプリが強制終了(ANR)したりします。それを防ぐために Dispatchers.IO へ処理を委託します。
役割と特徴
- I/O処理に特化:ネットワーク通信(APIコール)、データベース操作(Room等)、ファイルの読み書きに最適化されています。
- スレッドの自動管理:処理の「待ち時間」が発生することを想定し、必要に応じて多くのスレッドを柔軟に生成・共有するスレッドプールを持っています。
- Mainセーフの実現:重い処理を行う関数内部で
withContext(Dispatchers.IO)を使うことで、呼び出し元のMainスレッドをブロックしない「メインセーフ」な関数を作ることができます。
ディスパッチャーの使い分け
処理の内容に応じて、適切なディスパッチャーを選択する必要があります。
| ディスパッチャー | 主な用途 | 具体例 |
|---|---|---|
Dispatchers.Main | UI操作、軽量な処理 | 状態(State)の更新、トースト表示 |
Dispatchers.IO | ディスク・ネットワーク通信 | Retrofitによる通信、Roomの読み書き |
Dispatchers.Default | CPU負荷の高い計算処理 | 大規模なリストのソート、画像の加工、JSON解析 |
Jetpack Composeでの具体的なコード例
Composeの画面内で Dispatchers.IO を利用する場合、コンポジションのライフサイクルに連動する LaunchedEffect などを組み合わせて使用します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
@Composable fun UserProfileScreen(userId: String) { var userData by remember { mutableStateOf<String?>(null) } var isLoading by remember { mutableStateOf(true) } // キー(userId)が変わるか、初回表示時に実行される LaunchedEffect(userId) { isLoading = true // 1. Dispatchers.IOに切り替えて重い処理(通信など)を実行 val result = withContext(Dispatchers.IO) { fetchUserDataFromNetwork(userId) } // 2. 処理が終わると自動でMainスレッドに戻り、UIの状態を更新 userData = result isLoading = false } // UIの描画処理(Mainスレッドで動作) if (isLoading) { CircularProgressIndicator() } else { Text(text = userData ?: "データがありません") } } // メインセーフに設計されたサスペンド関数 suspend fun fetchUserDataFromNetwork(userId: String): String { // 擬似的なネットワーク遅延 delay(2000) return "User: $userId のデータ" } |
実装時の注意点
- UI状態の更新タイミング:
withContext(Dispatchers.IO)の内部で Compose のMutableState(上記例のuserDataなど)を直接更新することは避けてください。UIの更新は、必ずwithContextブロックが終了した後の Main スレッド上で行うのが安全です。 - ビジネスロジックの分離:本来、通信やデータ取得の処理は Composable 関数の中に直接書くのではなく、Android公式ドキュメントが推奨するように ViewModel(
viewModelScope) やリポジトリレイヤに隠蔽し、UI側は単にその状態を観察(Observe)する設計にすることが望ましいです。
普通の関数にsuspendと = withContext(Dispatchers.IO) を付けるだけ
suspend fun save(tableData: List<List<String>>, password: String?) = withContext(Dispatchers.IO) {}
suspend fun load(password: String?): List<List<String>>? = withContext(Dispatchers.IO) {}