1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CoroutinesとHiltで進化する WorkManager:2025年のモダンな実装手法

Posted at

はじめに

Androidアプリ開発において、バックグラウンド処理は避けて通れない重要な機能です。データの同期、ファイルのアップロード、定期的な通知など、ユーザーがアプリを直接操作していない時でも確実に実行する必要があるタスクは数多く存在します。

従来、Androidのバックグラウンド処理には、JobScheduler、AlarmManager、Service、SyncAdapterなど、多様なAPIが存在していました。しかし、それぞれに異なるAPIや制約があり、デバイスのバージョンごとの互換性問題も抱えていました。

WorkManagerは、これらの課題を解決するためにGoogleが提供するJetpackライブラリです。統一されたAPIで、デバイスの再起動やアプリの終了後でも確実にタスクを実行できる、信頼性の高いバックグラウンド処理を実現します。

この記事では、WorkManagerの基本から実践的な使い方まで、2025年時点の最新情報を元に解説します。

WorkManager とは

WorkManagerは、遅延可能で信頼性の高いバックグラウンドタスクを実行するためのJetpackライブラリです。

主な特徴

  • 保証された実行: アプリが終了しても、デバイスが再起動しても、タスクは確実に実行される
  • 制約ベース: ネットワーク接続、充電状態、バッテリー残量などの条件を指定可能
  • バッテリー効率: Dozeモードなどの省電力機能に準拠
  • 柔軟なスケジューリング: 即時実行、遅延実行、定期実行をサポート
  • 後方互換性: API 14以降をサポート、内部で最適なAPIを自動選択

2025年の現状

項目 状態
最新バージョン WorkManager 2.10.5(SDK 35対応)
Hilt対応 androidx.hilt:hilt-work で完全サポート
Compose統合 StateFlowでの状態観測が容易

WorkManager が適しているケース

定期的なデータ同期: サーバーとのデータ同期(1日1回など)
ログやアナリティクスの送信: バックエンドへのログ送信
画像の圧縮やアップロード: 大きなファイルの処理
データベースのクリーンアップ: 古いデータの削除
通知の定期配信: リマインダーや更新通知

WorkManager が適していないケース

即時実行が必要: UIの更新など(→ Coroutines使用)
正確なタイミング: 目覚まし時計など(→ AlarmManager使用)
フォアグラウンド処理: 音楽再生など(→ Foreground Service使用)
サーバーからのPush通知: (→ Firebase Cloud Messaging使用)

基本的な使い方

1. Worker の作成

Workerクラスは、バックグラウンドで実行する処理を定義します。

標準的な Worker

class UploadWorker(
    context: Context,
    params: WorkerParameters
) : Worker(context, params) {

    override fun doWork(): Result {
        return try {
            // バックグラウンド処理を実行
            uploadData()

            // 成功
            Result.success()
        } catch (e: Exception) {
            Log.e("UploadWorker", "Upload failed", e)

            // 失敗(リトライ)
            Result.retry()
        }
    }

    private fun uploadData() {
        // アップロード処理
        Log.d("UploadWorker", "Uploading data...")
        Thread.sleep(2000) // シミュレーション
        Log.d("UploadWorker", "Upload complete")
    }
}

CoroutineWorker

Coroutinesを使用する場合は、CoroutineWorkerを継承します。

class UploadWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        return withContext(Dispatchers.IO) {
            try {
                // suspendな処理を実行
                uploadData()

                Result.success()
            } catch (e: Exception) {
                Log.e("UploadWorker", "Upload failed", e)
                Result.retry()
            }
        }
    }

    private suspend fun uploadData() {
        // 実際のAPI呼び出しなど
        Log.d("UploadWorker", "Uploading data...")
        kotlinx.coroutines.delay(2000)
        Log.d("UploadWorker", "Upload complete")
    }
}

2. WorkRequest の作成

WorkRequestは、Workerをいつ、どのように実行するかを定義します。

OneTimeWorkRequest(一度だけ実行)

// 即座に実行
val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
    .build()

WorkManager.getInstance(context).enqueue(uploadWorkRequest)

PeriodicWorkRequest(定期実行)

// 6時間ごとに実行(最小間隔は15分)
val periodicWorkRequest = PeriodicWorkRequestBuilder<UploadWorker>(
    6, TimeUnit.HOURS
).build()

WorkManager.getInstance(context).enqueue(periodicWorkRequest)

💡ポイント: PeriodicWorkRequestの最小間隔は15分です。それより短い間隔で実行したい場合は、OneTimeWorkRequestを使用し、Worker内で再スケジュールしてください。

3. Constraints(制約)の設定

Constraintsを使用すると、特定の条件が満たされた時のみタスクを実行できます。

val constraints = Constraints.Builder()
    // ネットワーク接続が必要
    .setRequiredNetworkType(NetworkType.CONNECTED)
    // Wi-Fi接続が必要
    .setRequiredNetworkType(NetworkType.UNMETERED)
    // 充電中のみ
    .setRequiresCharging(true)
    // バッテリー残量が少なくない時
    .setRequiresBatteryNotLow(true)
    // デバイスがアイドル状態(Android 6.0以降)
    .setRequiresDeviceIdle(true)
    // ストレージ容量が少なくない時
    .setRequiresStorageNotLow(true)
    .build()

val workRequest = OneTimeWorkRequestBuilder<UploadWorker>()
    .setConstraints(constraints)
    .build()

WorkManager.getInstance(context).enqueue(workRequest)

Work の管理

一意な Work の実行

同じタスクを重複して実行しないようにするには、enqueueUniqueWorkを使用します。

WorkManager.getInstance(context).enqueueUniqueWork(
    "upload_work",                          // 一意な名前
    ExistingWorkPolicy.KEEP,                 // 既存のWorkを保持
    workRequest
)

ExistingWorkPolicy の種類

ポリシー 説明
REPLACE 既存のWorkをキャンセルして新しいWorkを実行
KEEP 既存のWorkが存在する場合は新しいWorkを無視
APPEND 既存のWorkの後に新しいWorkをチェーン
APPEND_OR_REPLACE 既存Workが失敗していたら置き換え、成功していたら追加

Work のチェーン

複数のWorkを順番に実行したり、並列実行したりできます。

順次実行

val compressWork = OneTimeWorkRequestBuilder<CompressWorker>().build()
val uploadWork = OneTimeWorkRequestBuilder<UploadWorker>().build()
val notifyWork = OneTimeWorkRequestBuilder<NotifyWorker>().build()

// 圧縮 → アップロード → 通知
WorkManager.getInstance(context)
    .beginWith(compressWork)
    .then(uploadWork)
    .then(notifyWork)
    .enqueue()

並列実行

val compressImageWork = OneTimeWorkRequestBuilder<CompressImageWorker>().build()
val compressVideoWork = OneTimeWorkRequestBuilder<CompressVideoWorker>().build()
val uploadWork = OneTimeWorkRequestBuilder<UploadWorker>().build()

// 画像圧縮と動画圧縮を並列実行 → 両方完了したらアップロード
WorkManager.getInstance(context)
    .beginWith(listOf(compressImageWork, compressVideoWork))
    .then(uploadWork)
    .enqueue()

複雑なチェーン

val workA = OneTimeWorkRequestBuilder<WorkerA>().build()
val workB = OneTimeWorkRequestBuilder<WorkerB>().build()
val workC = OneTimeWorkRequestBuilder<WorkerC>().build()
val workD = OneTimeWorkRequestBuilder<WorkerD>().build()
val workE = OneTimeWorkRequestBuilder<WorkerE>().build()

// A, B を並列実行 → C → D, E を並列実行
val continuation = WorkManager.getInstance(context)
    .beginWith(listOf(workA, workB))
    .then(workC)
    .then(listOf(workD, workE))

continuation.enqueue()

Hilt との統合

Application クラスの設定

HiltとWorkManagerを統合するには、ApplicationクラスでWorkManagerの初期化をカスタマイズする必要があります。Configuration.Providerインターフェースを実装することで、WorkManagerのデフォルト初期化を無効化し、HiltによるWorkerの依存性注入を有効にします。

@HiltAndroidApp
class MyApplication : Application(), Configuration.Provider {

    @Inject
    lateinit var workerFactory: HiltWorkerFactory

    override val workManagerConfiguration: Configuration
        get() = Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .setMinimumLoggingLevel(android.util.Log.DEBUG)
            .build()
}

ポイント:

  • Configuration.Providerを実装することで、WorkManagerの自動初期化が無効化されます
  • HiltWorkerFactoryがHiltから注入され、Worker内での依存性注入を可能にします
  • setMinimumLoggingLevelでログレベルを設定できます(デバッグ時はDEBUG、本番ではERROR推奨)

HiltWorker の作成

@HiltWorkerアノテーションを使用することで、Worker内でRepositoryやUseCaseなどの依存性を注入できます。これにより、ビジネスロジックを再利用しながら、テスタブルなWorkerを作成できます。

@HiltWorker
class UploadWorker @AssistedInject constructor(
    @Assisted context: Context,
    @Assisted params: WorkerParameters,
    private val uploadRepository: UploadRepository // Hiltで注入
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        return try {
            val filePath = inputData.getString("file_path") ?: return Result.failure()

            // Repositoryを使用してアップロード
            uploadRepository.uploadFile(filePath)

            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

ポイント:

  • @HiltWorkerアノテーションでWorkerをHiltの管理対象にします
  • コンストラクタに@AssistedInjectを付与します
  • ContextWorkerParametersには@Assistedを付与(WorkManagerが提供)
  • その他の依存性(Repository、UseCaseなど)は通常通りHiltで注入されます
  • 依存性注入により、Workerのユニットテストが容易になります

Compose StateFlowでの状態観測

WorkManagerとJetpack Composeを組み合わせることで、バックグラウンド処理の進捗をリアルタイムでUIに反映できます。WorkInfoをStateFlowとして観測し、ComposecollectAsState()で状態変化を監視します。これにより、アップロード進捗、処理状態、エラー情報などをリアクティブにUIに表示できます。

ViewModelでの状態管理

@HiltViewModel
class UploadViewModel @Inject constructor(
    private val workManager: WorkManager
) : ViewModel() {

    // WorkInfoをStateFlowとして公開
    fun observeUploadWork(workId: UUID): StateFlow<WorkInfo?> {
        return workManager.getWorkInfoByIdFlow(workId)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5000),
                initialValue = null
            )
    }

    // ユニークな名前でWorkを観測
    fun observeUniqueWork(uniqueWorkName: String): StateFlow<List<WorkInfo>> {
        return workManager.getWorkInfosForUniqueWorkFlow(uniqueWorkName)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5000),
                initialValue = emptyList()
            )
    }

    // アップロードを開始
    fun startUpload(filePath: String): UUID {
        val inputData = workDataOf("file_path" to filePath)

        val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
            .setInputData(inputData)
            .setConstraints(
                Constraints.Builder()
                    .setRequiredNetworkType(NetworkType.CONNECTED)
                    .build()
            )
            .build()

        workManager.enqueueUniqueWork(
            "upload_work",
            ExistingWorkPolicy.KEEP,
            uploadRequest
        )

        return uploadRequest.id
    }
}

Composeでの状態観測とUI表示

@Composable
fun UploadScreen(
    viewModel: UploadViewModel = hiltViewModel()
) {
    var workId by remember { mutableStateOf<UUID?>(null) }

    // WorkInfoの状態を観測
    val workInfo by viewModel.observeUploadWork(workId ?: UUID.randomUUID())
        .collectAsState()

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        // アップロードボタン
        Button(
            onClick = {
                workId = viewModel.startUpload("/path/to/file.jpg")
            },
            enabled = workInfo?.state?.isFinished != false
        ) {
            Text("アップロード開始")
        }

        Spacer(modifier = Modifier.height(16.dp))

        // 状態表示
        when (workInfo?.state) {
            WorkInfo.State.ENQUEUED -> {
                Text("待機中...")
                CircularProgressIndicator()
            }
            WorkInfo.State.RUNNING -> {
                Text("アップロード中...")
                LinearProgressIndicator(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(vertical = 8.dp)
                )

                // プログレス情報を表示
                workInfo?.progress?.let { progress ->
                    val current = progress.getInt("current", 0)
                    val total = progress.getInt("total", 100)
                    Text("進捗: $current / $total")
                }
            }
            WorkInfo.State.SUCCEEDED -> {
                Icon(
                    imageVector = Icons.Default.CheckCircle,
                    contentDescription = "成功",
                    tint = Color.Green,
                    modifier = Modifier.size(48.dp)
                )
                Text("アップロード完了!")
            }
            WorkInfo.State.FAILED -> {
                Icon(
                    imageVector = Icons.Default.Error,
                    contentDescription = "失敗",
                    tint = Color.Red,
                    modifier = Modifier.size(48.dp)
                )
                Text("アップロード失敗")
            }
            WorkInfo.State.CANCELLED -> {
                Text("キャンセルされました")
            }
            else -> {
                // 初期状態または未定義
            }
        }
    }
}

Workerでのプログレス更新

Worker側から進捗情報を送信することで、UIでリアルタイムに進捗を表示できます。

@HiltWorker
class UploadWorker @AssistedInject constructor(
    @Assisted context: Context,
    @Assisted params: WorkerParameters,
    private val uploadRepository: UploadRepository
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        return withContext(Dispatchers.IO) {
            try {
                val filePath = inputData.getString("file_path")
                    ?: return@withContext Result.failure()

                val fileSize = getFileSize(filePath)
                var uploadedBytes = 0L

                // 進捗をUI側に送信
                uploadRepository.uploadFileWithProgress(filePath) { bytes ->
                    uploadedBytes += bytes
                    val progress = ((uploadedBytes.toFloat() / fileSize) * 100).toInt()

                    // プログレス更新
                    setProgress(
                        workDataOf(
                            "current" to uploadedBytes.toInt(),
                            "total" to fileSize.toInt(),
                            "progress" to progress
                        )
                    )
                }

                Result.success()
            } catch (e: Exception) {
                Log.e("UploadWorker", "Upload failed", e)
                Result.retry()
            }
        }
    }

    private fun getFileSize(filePath: String): Long {
        return File(filePath).length()
    }
}

ポイント:

  • WorkManager.getWorkInfoByIdFlow()またはgetWorkInfosForUniqueWorkFlow()でFlowを取得
  • stateIn()でFlowをStateFlowに変換し、ViewModelのスコープで管理
  • Compose側でcollectAsState()を使って状態を監視
  • WorkInfo.Stateで実行状態を判定(ENQUEUED、RUNNING、SUCCEEDED、FAILEDなど)
  • setProgress()でWorker側からUIへ進捗情報を送信可能
  • WorkInfo.progressから進捗データを取得してプログレスバーに反映

まとめ

WorkManagerは、2025年現在、Androidのバックグラウンド処理における最も信頼性が高く、推奨される選択肢です。遅延可能で確実に実行する必要があるタスクには、WorkManagerを使用することで、ユーザー体験を損なうことなく、効率的なバックグラウンド処理を実現できます。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?