2026'da WorkManager: Android'de benzersiz iş, zincirleme ve arka plan işlerini test etme
Android'de WorkManager için pratik bir rehber — benzersiz iş politikaları, zincirleme, hızlandırılmış iş ve bir arka plan işini göndermeden önce nasıl gerçekten test edersiniz.
Vahşi doğada gördüğüm WorkManager kodlarının çoğu, kuyruğa alınıp unutulan tek bir OneTimeWorkRequest’ten ibaret. Bu, kullanıcı bir düğmeye iki kez dokunana, uygulama süreci iş ortasında ölene ya da bir incelemeci işin gerçekten çalıştığını nereden bildiğinizi sorana kadar işe yarar. WorkManager’ın asıl değeri “bunu sonra planla” değil — dünya güvenilmez olduğunda bir arka plan işini doğru tutan benzersizlik, zincirleme ve yeniden deneme garantileridir. Bu değerin çoğu kullanılmadan kalıyor çünkü bunun için gereken API yüzeyini atlamak kolay.
WorkManager’ı üç farklı uygulamada üç farklı iş için kullanıyorum — Subly’de gecelik bir dışa aktarma, Stocky’de kiler verisi yeniden hesaplama ve Granyn’de CSV yedekleme yazmaları — ve gönderdiğim hataların hepsi bu üç parçadan birini atlamaya dayanıyor.
Benzersiz iş: yinelenen işlere karşı koruma
En yaygın WorkManager hatası bir çökme değil, bir yinelemedir. Bir kullanıcı ilk dokunuşun spinner’ı görünmeden önce “dışa aktar”a iki kez dokunur ve şimdi iki özdeş dışa aktarma işi kuyruğa alınmıştır. Tek başına enqueue() bunu önlemek için hiçbir şey yapmaz — ikisini de mutlu bir şekilde kuyruğa alır.
enqueueUniqueWork çözümdür ve insanların yanlış anladığı kısım politika argümanıdır:
fun scheduleExport(context: Context) {
val request = OneTimeWorkRequestBuilder<ExportWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.NOT_REQUIRED)
.build(),
)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(
"monthly_export",
ExistingWorkPolicy.KEEP,
request,
)
}
Üç ExistingWorkPolicy değeri üç farklı niyete karşılık gelir ve yanlışını seçmek asıl hatadır:
KEEP— bu isimle bir iş zaten beklemedeyse veya çalışıyorsa, yeni isteği düşür. “Dışa aktar” için doğrusu budur — ikinci bir dokunuş ilkini yeniden başlatmamalı veya çoğaltmamalı.REPLACE— mevcut işi iptal et ve yeniden başla. Yeni istek eskisini geçersiz kılan güncellenmiş girdi verisi taşıdığında doğrusu budur — kullanıcı akış ortasında dışa aktarmanın tarih aralığını değiştirdi.APPEND_OR_REPLACE— henüz başlamadıysa mevcut işe zincirlen, aksi halde yeni bir zincir başlat. Nadirdir; çoğunlukla istendikleri sırayla çalışması gereken ardışık işler içindir.
enqueueUniquePeriodicWork tekrarlayan işler için aynı politika argümanını alır ve aynı mantık geçerlidir: gecelik bir yeniden hesaplama işi neredeyse her zaman KEEP veya UPDATE olmalı, REPLACE değil — değiştirmek periyodik zamanlamanın çapa zamanını sıfırlar, bu da işin ne zaman çalıştığını sessizce kaydırır.
Zincirleme: elle yazılmış bir geri çağırma olmadan sıralama
Bir yedekleme akışı nadiren tek adımdır — veriyi serileştir, sıkıştır, diske yaz, yazmayı doğrula. WorkRequest’leri zincirlemek bunu iç içe geçmiş geri çağırmalar yerine veri olarak ifade eder:
val serialize = OneTimeWorkRequestBuilder<SerializeWorker>().build()
val compress = OneTimeWorkRequestBuilder<CompressWorker>().build()
val write = OneTimeWorkRequestBuilder<WriteToDiskWorker>().build()
val verify = OneTimeWorkRequestBuilder<VerifyBackupWorker>().build()
WorkManager.getInstance(context)
.beginUniqueWork("full_backup", ExistingWorkPolicy.REPLACE, serialize)
.then(compress)
.then(write)
.then(verify)
.enqueue()
Her worker’ın çıktısı otomatik olarak bir sonraki worker’ın girdisi olur, Data aracılığıyla:
class SerializeWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result {
val path = serializeToTempFile()
return Result.success(workDataOf("serialized_path" to path))
}
}
class CompressWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result {
val path = inputData.getString("serialized_path") ?: return Result.failure()
val compressedPath = compress(path)
return Result.success(workDataOf("compressed_path" to compressedPath))
}
}
Data bilinçli olarak küçüktür — genel amaçlı bir yük kanalı değil, boyut sınırlı bir dahili veritabanı tarafından desteklenir. Dosya içeriklerinin kendisini değil, dosya yollarını ve kimlikleri iletin. Zincirdeki bir adım başarısız olursa, Result.failure() alt akıştaki her şeyin çalışmasını durdurur; zincir eksik girdiyle sessizce devam etmez.
Yeniden deneme ve geri çekilme: varsayılanın sizi şaşırtmasına izin vermeyin
Result.retry() WorkManager’a worker’ı tekrar denemesini söyler ve varsayılan olarak 30 saniye bekler, ardından üstel olarak geri çekilir, 5 saatte üst sınırlanır. Gerçek bir ağ bağımlılığı olan bir iş için varsayılan genellikle iyidir. Yerel, geçici bir hata nedeniyle yeniden denenen bir iş için — bir dosya kilidi, geçici bir düşük depolama durumu — 30 saniye kullanıcının aktif olarak beklediği bir şey için genellikle çok uzundur.
Varsayılana sessizce güvenmek yerine politikayı açıkça ayarlayın:
OneTimeWorkRequestBuilder<WriteToDiskWorker>()
.setBackoffCriteria(
BackoffPolicy.LINEAR,
10, TimeUnit.SECONDS,
)
.build()
Ve Result.retry()’yi Result.failure()’dan worker’ın kendisinde bilinçli olarak ayırt edin — insanların ters yaptığı kısım burasıdır:
override suspend fun doWork(): Result {
return try {
writeBackupFile()
Result.success()
} catch (e: IOException) {
if (runAttemptCount < 3) Result.retry() else Result.failure()
} catch (e: SecurityException) {
// Permission won't fix itself by retrying.
Result.failure()
}
}
Beş saat boyunca beş kez yeniden denenen bir izin hatası dayanıklılık değildir, kullanıcı fark etmeden önce beş kez sessizce başarısız olan bir iştir. Yalnızca zamanın gerçekten düzeltebileceği hata modlarını yeniden deneyin.
Hızlandırılmış iş: kullanıcının izlediği iş için
Her arka plan işi WorkManager’ın zamanlayıcısının ne zaman çalışacağına karar vermesini bekleyemez. Bir kullanıcı “şimdi dışa aktar”a dokunuyor ve sistemin Doze sezgisel yöntemlerinin izin verdiği herhangi bir zamanda değil, saniyeler içinde başlamasını bekliyorsa, onu hızlandırılmış olarak işaretleyin:
OneTimeWorkRequestBuilder<ExportWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
Hızlandırılmış iş hemen çalışır (uygulamanın günlük yürütme kotasına tabi olarak) ve uygulama çalışma ortasında arka plana geçse bile kısa bir ek süre alır. OutOfQuotaPolicy argümanı, uygulama günün kotasını harcadıktan sonra ne olacağına karar verir: RUN_AS_NON_EXPEDITED_WORK_REQUEST hata fırlatmak yerine normal zamanlamaya geri döner. Buna yalnızca gerçekten kullanıcı tarafından başlatılan, kullanıcının gördüğü işler için başvurun — bunu rutin arka plan senkronizasyonu için kullanmak, WorkManager’ın tüm amacı olan pil dostu zamanlamayı boşa çıkarır.
Test etme: neredeyse herkesin atladığı adım
WorkManager, bir worker’ın doğrulama için çalışan bir uygulamaya ihtiyaç duymaması için özel olarak bir test artifact’ı gönderir. Neredeyse hiç kimse bunu kullanmıyor ve bu, bir girdi verisi hatasını kod incelemesinde bulmakla bir kullanıcının hata raporundan bulmak arasındaki farktır.
@RunWith(AndroidJUnit4::class)
class ExportWorkerTest {
@Before
fun setup() {
val config = Configuration.Builder()
.setExecutor(SynchronousExecutor())
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(
ApplicationProvider.getApplicationContext(),
config,
)
}
@Test
fun exportWorker_writesFile_onSuccess() {
val request = OneTimeWorkRequestBuilder<ExportWorker>().build()
val workManager = WorkManager.getInstance(
ApplicationProvider.getApplicationContext(),
)
workManager.enqueue(request).result.get()
val info = workManager.getWorkInfoById(request.id).get()
assertThat(info.state).isEqualTo(WorkInfo.State.SUCCEEDED)
}
}
SynchronousExecutor, işin bir arka plan iş parçacığı yerine satır içinde çalışmasını sağlar, böylece test tamamlanmayı beklemek için bir uyku veya mandal gerektirmez. Bu, zincirlemenin ve yeniden deneme mantığının manuel testte özellikle iyi gizlediği iki hatayı yakalar: bir istisnayı sessizce yutan ve yine de success() döndüren bir worker ve inputData’dan yanlış anahtarı okuyan bir zincir adımı.
Gerçekten önemli olan
WorkManager üzerinde çalıştırdığım üç işte, işe yarayan model akıllıca değildi — tutarlıydı: her benzersiz işi açıkça adlandırın ve ExistingWorkPolicy’sini bilerek seçin, çok adımlı işleri geri çağırmalar iç içe geçirmek yerine zincirleyin, yalnızca zamanın düzeltebileceği hataları yeniden deneyin ve bir zincire üretimde güvenmeden önce WorkManagerTestInitHelper testini yazın. Bunların hiçbiri egzotik API’ler değil. Bunlar WorkManager’ın varsayılanlarında bırakılması kolay olan parçalar ve varsayılanlar önemli olacak kadar sık yanlış.
// İlgili okumalar
Günlükten dahası
2026'da Android App Links: https:// linkleriniz hâlâ neden tarayıcıda açılıyor
Digital Asset Links doğrulaması, assetlinks.json'daki gizli tuzaklar ve Android'in linki neden uygulamanıza vermediğini söyleyen adb komutları.
Android In-App Review API: sıkıcı olmadan puan istemek
Google'ın In-App Review API'sine pratik bir rehber — gerçekte nasıl çalıştığı, ne zaman tetiklenmesi gerektiği ve alışılmış 'bizi puanlayın' popup'ının Play Store puanınızı sessizce nasıl zedelediği.
Android uygulama kısayolları ve Hızlı Ayarlar Karosu: uygulamayı açmadan su kaydetmek
Android'in dinamik ShortcutManager API'sine ve TileService'e pratik bir rehber — tek dokunuşlu bir eylemin uygulamayı tamamen atlamasını nasıl sağlarsınız, çoğu uygulamayı çuvallatan tuzaklarla birlikte.