Irbisa · Cheatsheet

Background Work & Services

WorkManager, foreground services, service types, AlarmManager, Doze, background limits

Middle Developer · 22 questions

  1. 01

    What is a Service, how do started, bound and foreground services differ, and on which thread does a Service run?

    A Service is an app component for work without a UI.

  2. 02

    What is WorkManager, what does it guarantee, and what does it not promise?

    WorkManager is the Jetpack API for deferrable work that must finish: it persists each request, runs it again after process death and reboot, and handles constraints and retries.

    class UploadWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {  override suspend fun doWork(): Result {    val uri = inputData.getString("uri")?.toUri() ?: return Result.failure()    return try {      repo.upload(uri)      Result.success()…
  3. 03

    What is the difference between OneTimeWorkRequest and PeriodicWorkRequest, and why can periodic work not run every five minutes?

    OneTimeWorkRequest runs once and ends in a final state; PeriodicWorkRequest repeats until it is cancelled.

    // Raised to the 15-minute floor: no exception, only a logcat warningval tooFast = PeriodicWorkRequestBuilder<FeedWorker>(5, TimeUnit.MINUTES).build() // Once an hour, somewhere in the last 15 minutes of each hourval feed = PeriodicWorkRequestBuilder<FeedWorker>(  1, TimeUnit.HOURS,      // repeat interval…
  4. 04

    How do you pass input to a Worker and get a result back, and why can you not pass a 2 MB image through Data?

    Input goes in with setInputData(workDataOf(...)) and the result comes back with Result.success(workDataOf(...)).

    // wrong: IllegalStateException, Data cannot occupy more than 10240 bytes// val bad = workDataOf("image" to jpegBytes) // right: the bytes go to disk, Data carries the pathval file = File(context.cacheDir, "upload-${UUID.randomUUID()}.jpg")file.writeBytes(jpegBytes)…
  5. 05

    How do you choose between a coroutine, WorkManager, a foreground service and AlarmManager for a background task?

    Ask two things: must the work survive the user leaving the app, and must it run at an exact moment or while the user watches?

  6. 06

    This Service runs the nightly sync and the Activity binds to it to read progress. What is wrong with it, and what should run the sync?

    class SyncService : Service() {  private var percent = 0  inner class LocalBinder : Binder() { fun progress() = percent }   override fun onBind(intent: Intent?): IBinder = LocalBinder()   override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {    repo.syncBlocking { percent = it }    return START_STICKY  }} // Called when the app has no visible UI and no recent user interaction (e.g. from a sync callback):context.startService(Intent(context, SyncService::class.java))

    Almost every line: syncBlocking runs on the main thread, startService() while the app is in the background throws IllegalStateException on Android 8+, and START_STICKY is not durability.

  7. 07

    How does WorkManager work under the hood: where is work stored, what actually runs it, and how does it survive a reboot?

    Every request is a row in WorkManager's own Room database, and schedulers hand it to the platform JobScheduler or run it in-process while the app is alive.

  8. 08

    This Worker keeps copying files long after WorkManager stopped it. Why, and how do Worker, CoroutineWorker and ListenableWorker handle a stop?

    class CopyWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {  override fun doWork(): Result {    for (file in files) {      copy(file)    }    return Result.success()  }}

    Stopping is cooperative.

  9. 09

    Find the bug: the upload Worker always ends as SUCCEEDED, yet some photos never reach the server.

    class UploadWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {  override fun doWork(): Result {    val paths = inputData.getStringArray("paths") ?: return Result.failure()    CoroutineScope(Dispatchers.IO).launch {      for (path in paths) {        api.upload(File(path))        File(path).delete()      }    }    return Result.success()  }}

    doWork() starts the uploads in a scope of its own and returns Result.success() at once, so WorkManager marks the work SUCCEEDED and stops tracking it.

  10. 10

    How do WorkManager constraints work, and what happens to a running Worker when a constraint stops being met?

    Constraints are conditions (network type, charging, battery or storage not low) that must hold before the work starts and are watched for the whole run.

    val request = OneTimeWorkRequestBuilder<BackupWorker>()  .setConstraints(    Constraints.Builder()      .setRequiredNetworkType(NetworkType.UNMETERED)  // watched during the whole run      .setRequiresBatteryNotLow(true)      .setRequiresStorageNotLow(true)…
  11. 11

    This code runs in Application.onCreate. Why do users get duplicate syncs, and why does the new two-hour interval never take effect?

    val wm = WorkManager.getInstance(this) wm.enqueue(OneTimeWorkRequestBuilder<SyncWorker>().build()) wm.enqueueUniquePeriodicWork(  "sync",  ExistingPeriodicWorkPolicy.KEEP,  PeriodicWorkRequestBuilder<SyncWorker>(2, TimeUnit.HOURS).build(),)

    Plain enqueue() adds a new independent request on every app start, and ExistingPeriodicWorkPolicy.KEEP ignores the new request while the old schedule exists, so the interval from the first install wins forever.

  12. 12

    How do retries and backoff work in WorkManager, and how do you stop a Worker from retrying forever?

    Returning Result.retry() reschedules the work after a backoff delay, and there is no attempt limit: only your code stops the loop.

    class SyncWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {   override suspend fun doWork(): Result = try {    api.sync()    Result.success()  } catch (e: HttpException) {…
  13. 13

    How do you chain Workers so that three uploads run in parallel and a publish step runs after all of them, and what happens if one upload fails?

    beginWith(listOf(a, b, c)).then(publish).enqueue(): a list in beginWith runs in parallel, then waits for all of them, and publish starts only when all three succeed.

    val uploads = photos.map { photo ->  OneTimeWorkRequestBuilder<UploadWorker>()    .setInputData(workDataOf("path" to photo.path))    .build()}…
  14. 14

    How do you show progress from a long-running Worker, and when does it need setForeground and a foreground service type?

    Inside the Worker call setProgress(workDataOf(...)) and observe WorkInfo.progress through a Flow keyed by a unique work name.

    class UploadWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {   override suspend fun doWork(): Result {    setForeground(getForegroundInfo())  // long run: become a foreground service first    val files = dao.pending()    files.forEachIndexed { i, file ->…
  15. 15

    What does setExpedited give you, and what does OutOfQuotaPolicy decide?

    setExpedited asks the system to start a one-time request as soon as possible, paid from a limited per-app quota.

    val sync = OneTimeWorkRequestBuilder<SyncWorker>()  .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)  .setConstraints(    Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()  )  // .setInitialDelay(...) here would throw: expedited work cannot be delayed…
  16. 16

    What changed for foreground services in Android 12, 14 and 15, and what does a foreground service type require?

    Android 12 stopped apps from starting a foreground service from the background, apart from exempt cases.

    // AndroidManifest.xml// <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />// <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />// <service android:name=".UploadService"//     android:foregroundServiceType="dataSync" android:exported="false" />…
  17. 17

    What are Doze mode and App Standby buckets, and which background limits has Android added since 8.0?

    Doze defers jobs, alarms and network access on an idle, unplugged device into short maintenance windows; App Standby buckets ration jobs and alarms per app by how recently the user used it.

  18. 18

    Find the bug: run tracking works on office Wi-Fi, but on a weak mobile network the app crashes a few seconds after the user taps Start.

    // Started from the Run screen: ContextCompat.startForegroundService(context, intent)class RunTrackingService : Service() {  private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)  override fun onBind(intent: Intent?): IBinder? = null  override fun onDestroy() = scope.cancel()  override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {    scope.launch {      val route = routeRepo.loadActiveRoute() // HTTP call      ServiceCompat.startForeground(this@RunTrackingService, NOTIFICATION_ID,        buildNotification(route.name), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)      tracker.track(route)    }    return START_NOT_STICKY  }}

    startForeground() is called only after a network request, but a service launched with startForegroundService() must promote itself within about five seconds.

  19. 19

    How do you build an alarm clock that still rings at 6:00 after the phone spent the night in Doze and rebooted at 3:00?

    Schedule an exact alarm with setAlarmClock, hold the exact-alarm permission, and rebuild every alarm from your own database in a boot receiver: AlarmManager forgets all alarms on reboot and app update.

    class AlarmScheduler(private val ctx: Context, private val dao: AlarmDao) {  private val am = ctx.getSystemService(AlarmManager::class.java)   fun armNext(): Boolean {    val next = dao.nextEnabled() ?: return true    // false: the UI sends the user to Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM…
  20. 20

    Your nightly sync runs reliably on a Pixel but never on many Xiaomi and Samsung phones. Why, and what can you honestly do about it?

    On top of Doze and App Standby, many vendors ship their own battery managers that freeze or kill apps outside an allowlist (jobs and alarms included), and no API or targetSdk opts you out.

  21. 21

    The client must pull new data as soon as the server changes it. How do you drive that with FCM without wasting the app's quota or battery?

    Treat the push as a wake-up signal, not as the work: onMessageReceived enqueues a unique WorkManager job and returns, and only messages that really cannot wait are sent with high priority.

    class AppMessagingService : FirebaseMessagingService() {   override fun onMessageReceived(message: RemoteMessage) {    // Did FCM honour our high priority, or downgrade it?    if (message.priority != message.originalPriority) {      analytics.log("fcm_priority_downgraded")…
  22. 22

    One user says the sync worker never runs and you cannot reproduce it. How do you find out what the scheduler actually did with the job?

    Ask the scheduler directly: adb shell dumpsys jobscheduler shows which constraints are unmet and when the job may run, and WorkManager's diagnostics broadcast logs its own view of the work.

    # What did the scheduler decide about our job?adb shell dumpsys jobscheduler | grep -A 30 "com.example.app"#   Required constraints: CONNECTIVITY BATTERY_NOT_LOW#   Satisfied constraints: BATTERY_NOT_LOW     <- no network: this is the answer#   Standby bucket: RARE…