Skip to content
AIQGM

A 30-minute timer that stopped running when the phone went to sleep

We patched the symptom twice before replacing the thing that was actually wrong. Here's how that happened, and the fix.

An Android app refreshed a local cache every 30 minutes on an in-process timer, so the first screen after unlock would open warm. Users reported that after the phone sat idle, the first open was slow anyway.

Logs showed why. Once the device entered Doze, the timer stopped firing. One idle stretch went 44 minutes without a single refresh request.

The first patch added an extra refresh trigger on unlock. That caused repeat requests on devices where the refresh kept failing, so we added a backoff. Then the backoff ceiling was too long. Both layers existed only because the scheduler underneath was wrong, and the right tool was already in the same codebase running eight other background jobs:

WorkManagerkotlin
val request = PeriodicWorkRequestBuilder<PrefetchWorker>(30, TimeUnit.MINUTES)
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
    )
    .build()

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "prefetch",
    ExistingPeriodicWorkPolicy.KEEP,   // don't reset the schedule on every app start
    request
)

WorkManager runs inside the maintenance windows Doze allows, survives process death and reboots, and gets retry with backoff for free. The two compensating layers were removed.

This comes from our mobile apps work.

Tell us what you're working on.

A few paragraphs is plenty. We'll read it, reply by email, and if it looks like a fit, set up a 30-minute Google Meet with an engineer.