Tugas latar belakang yang harus dijamin selesai (mis. upload foto, sinkronisasi data) tidak boleh memakai coroutine biasa — Activity dapat di-destroy dan process dapat dimatikan oleh sistem. WorkManager adalah library Jetpack yang menjadwalkan tugas deferrable dengan jaminan: tugas akan dijalankan walau aplikasi keluar, walau peranti restart, dan sesuai constraints yang ditetapkan. Bab ini menjelaskan OneTimeWorkRequest, PeriodicWorkRequest, constraints, dan backoff policy.
OneTimeWorkRequest vs PeriodicWorkRequest #
OneTimeWorkRequest menjalankan tugas satu kali. PeriodicWorkRequest menjalankan tugas berulang dengan interval minimum 15 menit (batas sistem). Keduanya mendukung constraints, tag, dan backoff.
class UploadWorker(appContext: Context, params: WorkerParameters) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
return try {
uploadFoto(inputData.getInt("FOTO_ID", -1))
Result.success(workDataOf("URL" to hasilUrl))
} catch (e: Exception) {
Result.retry()
}
}
}
val request = OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(workDataOf("FOTO_ID" to 42))
.setConstraints(Constraints.Builder().setRequiredNetworkType(UNMETERED).build())
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(context).enqueue(request)Tipe Result menentukan tindakan WorkManager: success menyelesaikan tugas, failure menghentikan tanpa retry, retry menjadwalkan ulang dengan backoff.
Constraints #
Constraints membatasi kapan tugas boleh berjalan. Tanpa constraints, WorkManager menjalankan tugas sesegera mungkin. Dengan constraints, tugas menunggu sampai semua kondisi terpenuhi.
| Constraint | Kapan dipakai |
|---|---|
setRequiredNetworkType(UNMETERED) | Backup, sync besar (jangan di data seluler) |
setRequiresCharging(true) | Tugas berat (encode video, training model) |
setRequiresIdle(true) | Maintenance database, vacuum |
setRequiresBatteryNotLow(true) | Hindari tugas yang boros baterai |
Backoff Policy #
Saat worker mengembalikan Result.retry(), WorkManager menjadwalkan ulang dengan backoff. Dua pilihan: LINEAR (interval tetap) dan EXPONENTIAL (interval berlipat ganda). Backoff default adalah 30 detik dengan exponential.
val work = OneTimeWorkRequestBuilder<SyncWorker>()
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.build()Urutan retry dengan exponential: 30s, 60s, 120s, 240s, dst. Backoff mencegah server terbebani oleh worker yang selalu gagal dan segera mencoba ulang.
Simulasi WorkManager Queue #
Cuplikan berikut memodelkan WorkManager sebagai queue yang memeriksa constraints sebelum menjalankan worker. Worker yang gagal constraint di-skip dan di-enqueue ulang:
// background_workmanager.kt
// Mensimulasikan WorkManager: OneTimeWorkRequest, PeriodicWorkRequest,
// Constraints (network, charging), dan backoff policy. Tidak memerlukan
// Android SDK — hanya memetakan kontrak WorkManager sebagai queue.
data class WorkRequest(
val id: String,
val type: String, // "OneTime" atau "Periodic"
val constraints: WorkConstraints,
val backoffMs: Long = 1000L,
val maxRetries: Int = 3
)
data class WorkConstraints(
val requiresNetwork: Boolean = false,
val requiresCharging: Boolean = false,
val requiresIdle: Boolean = false
)
data class WorkResult(val id: String, val success: Boolean, val attempts: Int)
class WorkManager(
private val systemState: () -> WorkConstraints = { WorkConstraints(requiresNetwork = true, requiresCharging = false, requiresIdle = false) }
) {
private val queue = ArrayDeque<WorkRequest>()
private val results = mutableListOf<WorkResult>()
private val workerStatus = mutableMapOf<String, Int>() // id -> attempt count
fun enqueue(request: WorkRequest) {
queue.addLast(request)
println(" [Enqueue] ${request.id} (${request.type}) constraints=${request.constraints}")
}
fun runNext(): WorkResult? {
val req = queue.removeFirstOrNull() ?: return null
val attempts = (workerStatus[req.id] ?: 0) + 1
workerStatus[req.id] = attempts
// Cek constraints
val sys = systemState()
val constraintsMet = (!req.constraints.requiresNetwork || sys.requiresNetwork) &&
(!req.constraints.requiresCharging || sys.requiresCharging) &&
(!req.constraints.requiresIdle || sys.requiresIdle)
if (!constraintsMet) {
println(" [Skip] ${req.id} constraints belum terpenuhi, retry")
if (attempts < req.maxRetries) {
queue.addLast(req) // re-enqueue
}
return null
}
// Eksekusi worker (selalu sukses untuk demo)
val result = WorkResult(req.id, success = true, attempts = attempts)
results.add(result)
println(" [Run] ${req.id} success=true attempts=$attempts")
return result
}
fun dumpResults(): List<WorkResult> = results.toList()
}
fun main() {
val wm = WorkManager()
println("=== Enqueue 3 work requests ===")
wm.enqueue(WorkRequest("upload-foto-1", "OneTime", WorkConstraints(requiresNetwork = true)))
wm.enqueue(WorkRequest("sync-tugas", "Periodic", WorkConstraints(requiresNetwork = true, requiresCharging = true)))
wm.enqueue(WorkRequest("compress-cache", "OneTime", WorkConstraints()))
println("\n=== Run semua (state sistem: network=true, charging=false) ===")
repeat(5) { wm.runNext() }
println("\n=== Hasil ===")
wm.dumpResults().forEach { println(" $it") }
println("\n=== State sistem berubah: charging=true ===")
val wm2 = WorkManager { WorkConstraints(requiresNetwork = true, requiresCharging = true, requiresIdle = false) }
wm2.enqueue(WorkRequest("sync-tugas", "Periodic", WorkConstraints(requiresNetwork = true, requiresCharging = true)))
wm2.runNext()
wm2.dumpResults().forEach { println(" $it") }
}Output yang Dihasilkan:
=== Enqueue 3 work requests ===
[Enqueue] upload-foto-1 (OneTime) constraints=WorkConstraints(requiresNetwork=true, requiresCharging=false, requiresIdle=false)
[Enqueue] sync-tugas (Periodic) constraints=WorkConstraints(requiresNetwork=true, requiresCharging=true, requiresIdle=false)
[Enqueue] compress-cache (OneTime) constraints=WorkConstraints(requiresNetwork=false, requiresCharging=false, requiresIdle=false)
=== Run semua (state sistem: network=true, charging=false) ===
[Run] upload-foto-1 success=true attempts=1
[Skip] sync-tugas constraints belum terpenuhi, retry
[Run] compress-cache success=true attempts=1
[Skip] sync-tugas constraints belum terpenuhi, retry
[Skip] sync-tugas constraints belum terpenuhi, retry
=== Hasil ===
WorkResult(id=upload-foto-1, success=true, attempts=1)
WorkResult(id=compress-cache, success=true, attempts=1)
=== State sistem berubah: charging=true ===
[Enqueue] sync-tugas (Periodic) constraints=WorkConstraints(requiresNetwork=true, requiresCharging=true, requiresIdle=false)
[Run] sync-tugas success=true attempts=1
WorkResult(id=sync-tugas, success=true, attempts=1)Perhatikan bahwa sync-tugas yang membutuhkan requiresCharging = true di-skip ketika sistem tidak charging. Worker tersebut di-enqueue ulang dan akhirnya berjalan ketika sistem state berubah menjadi charging.
Referensi Lanjutan #
- Background work — panduan resmi opsi background work Android termasuk WorkManager untuk tugas yang perlu dijamin selesai
Selanjutnya #
Setelah tugas latar belakang terjadwal, kita perlu memberi tahu pengguna lewat notifikasi. Bab Notifications akan membahas notification channel, NotificationManager, dan foreground service.