Why Your Android App Stops Working After You Lock the Phone
The app works perfectly while you're looking at it. Data keeps syncing, timers fire, network requests finish, and notifications show up right on time. Then you lock the phone. A few minutes later, the sync hasn't happened. A background task appears stuck. Maybe the expected notification never arrives. You turn the screen back on, and suddenly everything wakes up and starts working again. I've learned not to treat this as one specific Android bug. Locking the screen can expose several different problems that look almost identical from the outside. It could be Doze, background execution limits, a stopped service, an unreliable in-process timer, an OEM battery setting, or simply an incorrect assumption about how long an app process gets to stay active. The phone usually isn't just "killing the app." What's happening is more specific, and figuring out which restriction you're hitting is the useful part. Open doesn't mean allowed to run forever When an app's activity is visible, Android treats it as something the user is actively using. The process has a high priority, network access is generally available, and code started by the UI often keeps running without obvious trouble. That can create a misleading test environment. Suppose I start a coroutine, executor task, timer, or regular background service from an activity. While the app is open, it may work every time. But none of those things automatically becomes durable background work just because it was launched successfully. Once the screen is locked, the activity usually moves through onPause() and onStop() . The process may continue running, but the app is no longer in the foreground from Android's point of view. That distinction matters: - An app process can still exist without having permission to do unrestricted background work. - An activity being stopped does not guarantee that the process will be terminated. - A process remaining alive does not guarantee that its timers, network access, jobs, or services can run whenever they want. - Work stored only in memory disappears if Android later removes the process. This is why onStop() should never be treated as a signal that the app has a few more hours to finish something. Android lifecycle callbacks describe component state. They aren't a background execution contract. What actually changes when the screen turns off Locking the screen doesn't immediately flip one giant "stop all apps" switch. Several things can happen, sometimes right away and sometimes after the device has been idle for a while. First, the activity is no longer visible. If the app has no foreground component, its process importance drops. Android now has more freedom to reclaim that process if the system needs memory. Second, the CPU may suspend when nothing is holding an appropriate wake lock. A Java or Kotlin timer doesn't keep the hardware awake by itself. If the CPU sleeps, the timer isn't getting regular execution time. Third, background execution rules start to matter. Depending on the Android version and app state, Android may limit background services, defer jobs, batch alarms, or restrict network access. After the device remains unused, Doze can apply. App Standby and standby buckets may also affect apps that the user hasn't interacted with recently. These mechanisms overlap, but they aren't the same thing. Background execution limits are often the first problem Android 8.0 introduced major limits on what apps can do with background services. When an app moves to the background, it usually gets a short window in which existing background services can continue. After that window, Android stops those services as if the app had called stopSelf() . That means an old pattern like this is not a reliable way to run indefinitely: startService(Intent(this, SyncService::class.java)) It might work while the activity is visible. It might continue briefly after the screen is locked. Then it stops. On newer Android versions, trying to start a normal background service while the app is already in the background can also throw an IllegalStateException . This still doesn't mean Android has necessarily terminated the entire process. The system may only be restricting or stopping the background service. Other parts of the process might remain around for some time. That's one reason this bug can feel inconsistent. The process can appear alive in one log while the work you expected is no longer allowed to continue. Doze is delayed, but it changes a lot Doze is Android's idle power-saving state. It doesn't usually begin the instant the screen turns off. The device needs to meet idle conditions, and the timing varies by Android version and system behavior. Once the device enters Doze, Android starts deferring a number of things: - Regular network access is suspended. - Standard alarms are deferred. - Jobs and WorkManager work may be postponed. - Wake locks are ignored. - Background CPU activity is heavily limited. The device periodically enters maintenance windows where deferred work gets a chance to run. If the phone stays idle, those windows can become less frequent. This explains a common pattern: the app works for a little while after the phone is locked, then stops making progress, then catches up later. Doze isn't supposed to block everything forever. It's designed to batch background work so the phone doesn't keep waking up for every app that wants to poll a server or run a timer. An app being exempt from battery optimization changes some Doze behavior, but asking users for that exemption shouldn't be the default fix. Google Play policy and Android guidance reserve broad exemptions for cases where the app's core function truly can't work under normal power management. Most apps should adapt their scheduling instead. App Standby is related, but not triggered by one screen lock App Standby deals more with apps the user hasn't actively used for a while. Android can defer their network access and background jobs because the system considers them inactive. Newer Android versions also place apps into standby buckets such as active, working set, frequent, rare, and restricted. The bucket affects how often an app can run jobs, receive alarms, and use background resources. The exact bucket behavior has changed across Android releases, so I don't build logic around a fixed promise such as "rare apps get exactly this many minutes." The useful point is that Android gives recently used apps more freedom than apps that haven't been opened in a long time. One screen lock normally isn't enough to make App Standby the immediate cause. But it can explain why the issue only happens after the app hasn't been used for hours or days. Battery optimization and user restrictions add another layer Users can usually choose a battery mode for an app. The labels vary by Android version and phone manufacturer, but common choices include unrestricted, optimized, and restricted. "Optimized" is generally the normal state. It allows Android's standard battery management to apply. "Restricted" can be much harsher. Background jobs, alarms, services, and other behavior may be limited enough that the app doesn't behave as expected. Android also warns that restricted apps may not work correctly in the background. There's also the system's general background activity restriction. This is separate from Android's rules about starting an Activity from the background, though the names are easy to confuse. Background activity launch restrictions control when an app can suddenly place UI on the screen. Starting with Android 10, apps generally can't launch activities from the background unless they meet a documented exception. A notification is usually the correct way to ask the user to open something. None of these settings is perfectly consistent across manufacturers. Some vendors add their own auto-start controls, sleeping app lists, power modes, or process management policies. An app can behave correctly on one phone and get delayed aggressively on another, even when both report the same Android version. I still design around Android's documented APIs first. Manufacturer-specific instructions are a fallback for confirmed device-specific behavior, not the foundation of the app. A foreground service is not a hidden keep-alive trick A foreground service is meant for work the user can actively notice. Navigation, workout tracking, media playback, and an ongoing file transfer are typical examples. It must show an ongoing notification and declare an appropriate foreground service type. Recent Android releases also require corresponding permissions for certain service types. A basic start looks like this: val intent = Intent(context, TrackingService::class.java) ContextCompat.startForegroundService(context, intent) The service then needs to promote itself to the foreground promptly: override fun onStartCommand( intent: Intent?, flags: Int, startId: Int ): Int { startForeground( TRACKING_NOTIFICATION_ID, createTrackingNotification() ) return START_NOT_STICKY } This gives the service a higher-priority execution state. It doesn't make the app immune to every system rule, and it doesn't automatically keep the CPU awake. If the work genuinely requires the CPU while the screen is off, wake-lock handling may still be relevant. That needs to be narrowly scoped because an incorrectly held wake lock can wreck battery life. There are also restrictions on starting foreground services. Since Android 12, apps generally can't start a foreground service while already in the background unless one of the documented exceptions applies. Newer Android versions enforce service types, related permissions, and additional time limits for some types. For example, Android 15 introduced time limits for dataSync and mediaProcessing foreground services when an app targets that version. So the answer isn't "convert every service into a foreground service." If the user can't reasonably tell why the app is running right now, a
Comments
No comments yet. Start the discussion.