Android Studio: уроки на Kotlin. Работа с WorkManager

Представьте, что вашему приложению нужно синхронизировать данные с сервером, отправить аналитику или сжать фотографии, но вы не хотите заставлять пользователя ждать. Можно попробовать запустить задачу в фоновом потоке, однако, если закрыть приложение, всё прервётся. Android Studio предоставляет отличное решение — WorkManager. Эта библиотека гарантирует, что ваша работа будет выполнена, даже если телефон перезагрузят. А благодаря Kotlin и корутинам код остаётся простым и понятным. Этот урок как раз о том, как настроить фоновую задачу с нуля.


Что такое WorkManager и когда его использовать

WorkManager — это часть Android Jetpack, предназначенная для отложенных и гарантированных фоновых задач. В отличие от корутин, которые живут только пока работает приложение, или AlarmManager, который сложен в настройке, WorkManager сам выбирает подходящий способ выполнения в зависимости от версии Android. Он поддерживает периодические задачи, цепочки зависимостей и наблюдение за прогрессом.

Типичные сценарии: синхронизация локальной базы данных с сервером раз в сутки, загрузка обновлений, очистка кэша старых файлов. Главное правило: если задача должна завершиться обязательно, даже если пользователь свернул приложение, выбирайте WorkManager.


Добавление библиотеки в проект

Для начала откройте build.gradle.kts уровня модуля и добавьте зависимость. На момент написания статьи актуальна версия 2.10.0, совместимая с Kotlin 2.1.x.

dependencies {
    implementation("androidx.work:work-runtime-ktx:2.10.0")
}

После синхронизации Gradle можно сразу создавать первую фоновую задачу. Никаких дополнительных разрешений в манифесте не требуется.


Создание простого Worker

Основной элемент — класс, наследующий CoroutineWorker. В нём нужно реализовать метод doWork(), который возвращает результат: Result.success(), Result.failure() или Result.retry(). Корутины позволяют использовать suspend-функции прямо внутри.

import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters

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

    override suspend fun doWork(): Result {
        return try {
            // имитация долгой работы: синхронизация данных
            Log.d("SyncWorker", "Синхронизация началась")
            // здесь мог бы быть вызов Retrofit
            Log.d("SyncWorker", "Синхронизация завершена")
            Result.success()
        } catch (e: Exception) {
            Result.retry() // попробовать ещё раз при ошибке
        }
    }
}

Запуск задачи из Activity или ViewModel

Чтобы запланировать работу, создайте объект OneTimeWorkRequest и передайте его в WorkManager. Можно задать ограничения — например, выполнять только при наличии интернета.

import androidx.work.Constraints
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager

val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .build()

val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
    .setConstraints(constraints)
    .build()

WorkManager.getInstance(context).enqueue(syncRequest)

Теперь работа гарантированно выполнится, даже если сразу после вызова свернуть приложение. WorkManager сам выберет момент, когда устройство не занято и соблюдены ограничения.


Наблюдение за состоянием работы

Иногда нужно показать пользователю прогресс или обновить интерфейс после завершения. Для этого используют LiveData или Flow, возвращаемые WorkManager.

WorkManager.getInstance(context)
    .getWorkInfoByIdLiveData(syncRequest.id)
    .observe(lifecycleOwner) { workInfo ->
        if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
            // обновить UI
        }
    }

Если вы используете Compose, можно обернуть LiveData в observeAsState() или напрямую подписаться на Flow через collectAsState().


Периодические задачи

Для регулярной синхронизации, например раз в 12 часов, используйте PeriodicWorkRequestBuilder. Минимальный интервал — 15 минут, система сама сгруппирует задачи для экономии батареи.

val periodicRequest = PeriodicWorkRequestBuilder<SyncWorker>(
    12, TimeUnit.HOURS
)
    .setConstraints(constraints)
    .build()

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "daily_sync",
    ExistingPeriodicWorkPolicy.KEEP, // не дублировать, если уже есть
    periodicRequest
)

Метод enqueueUniquePeriodicWork предотвращает создание дубликатов: если задача с таким именем уже существует, она не будет добавлена повторно.


Практические советы

  • Не злоупотребляйте частотой. Периодические задачи чаще 15 минут запрещены системой. Для более частых обновлений используйте корутины внутри приложения.
  • Обрабатывайте retry. Если doWork() возвращает Result.retry(), WorkManager повторит задачу с экспоненциальной задержкой.
  • Передавайте параметры. В Worker можно передать данные через setInputData() и получить их внутри через inputData.
  • Тестируйте. WorkManager предоставляет тестовый контекст TestListenableWorkerBuilder для модульных тестов.

Итог

Сегодня в статье были показаны примеры использования Kotlin и WorkManager. Вы изучили создание Worker, настройку ограничений и запуск как однократных, так и периодических задач. Теперь ваше приложение может выполнять синхронизацию, очистку кэша или отправку логов, не опасаясь прерывания из-за закрытия экрана. Этот шаг важен для профессиональной разработки на Android с использованием современных инструментов.