<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Vukovic</title>
    <description>The latest articles on DEV Community by Alex Vukovic (@alexvukovic).</description>
    <link>https://dev.to/alexvukovic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4101866%2F0799e855-c1eb-444d-a3b5-ee58327f3609.jpg</url>
      <title>DEV Community: Alex Vukovic</title>
      <link>https://dev.to/alexvukovic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexvukovic"/>
    <language>en</language>
    <item>
      <title>Why Android Apps Drain Battery in the Background</title>
      <dc:creator>Alex Vukovic</dc:creator>
      <pubDate>Sun, 30 Aug 2026 23:23:36 +0000</pubDate>
      <link>https://dev.to/alexvukovic/why-android-apps-drain-battery-in-the-background-28c8</link>
      <guid>https://dev.to/alexvukovic/why-android-apps-drain-battery-in-the-background-28c8</guid>
      <description>&lt;p&gt;Your app isn't open. You're not using it. And somehow it's still one of the biggest battery drains on the phone.&lt;/p&gt;

&lt;p&gt;That sounds contradictory, but "not open" only means the app doesn't have a visible activity. It says very little about what the app is doing.&lt;/p&gt;

&lt;p&gt;The process might still exist. A worker could be syncing data. An alarm could wake the device. A foreground service might be tracking location. Push messages could trigger network requests. Or the app could be running a coroutine loop that checks a server every few minutes because somebody needed a quick solution six months ago.&lt;/p&gt;

&lt;p&gt;The confusing part is that these cases look similar in Android's battery screen. They're not.&lt;/p&gt;

&lt;p&gt;Some background work is legitimate. A navigation app needs location while guiding the user. A podcast app needs to finish a download. A messaging app needs to react to incoming messages.&lt;/p&gt;

&lt;p&gt;The real problem starts when an app performs work more often, for longer, or with stricter timing than the feature actually requires.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Running in the Background" Can Mean Several Things
&lt;/h2&gt;

&lt;p&gt;An Android application doesn't have one simple open or closed state.&lt;/p&gt;

&lt;p&gt;Its process may remain cached after the user leaves the app. That doesn't mean it's actively burning battery. A cached process can sit in memory without using meaningful CPU time, and Android may remove it later when the system needs memory.&lt;/p&gt;

&lt;p&gt;Process lifetime is not an execution contract. A &lt;code&gt;Timer&lt;/code&gt;, &lt;code&gt;Handler&lt;/code&gt;, thread, or coroutine inside that process isn't guaranteed to keep running. If the process goes away, so does that work.&lt;/p&gt;

&lt;p&gt;At the same time, Android provides mechanisms that can schedule or allow work without a visible activity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WorkManager jobs&lt;/li&gt;
&lt;li&gt;Scheduled alarms&lt;/li&gt;
&lt;li&gt;Foreground services&lt;/li&gt;
&lt;li&gt;Bound or started services&lt;/li&gt;
&lt;li&gt;Broadcast handling&lt;/li&gt;
&lt;li&gt;Firebase Cloud Messaging delivery&lt;/li&gt;
&lt;li&gt;Location callbacks&lt;/li&gt;
&lt;li&gt;Media playback&lt;/li&gt;
&lt;li&gt;Bluetooth or connected-device communication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A service is also not automatically a background thread. By default, its callbacks run on the application's main thread. More importantly, creating a service doesn't exempt an app from background execution limits.&lt;/p&gt;

&lt;p&gt;So when I investigate battery usage, I don't ask, "Was the app running?" I ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What work was performed?&lt;/li&gt;
&lt;li&gt;What caused it to start?&lt;/li&gt;
&lt;li&gt;How often did it wake the CPU?&lt;/li&gt;
&lt;li&gt;How long did it run?&lt;/li&gt;
&lt;li&gt;Did it use location or the network?&lt;/li&gt;
&lt;li&gt;Did the user receive enough value to justify that cost?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those questions usually point toward the actual bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android Tries to Batch Work for a Reason
&lt;/h2&gt;

&lt;p&gt;Android's power management is built around avoiding many small, scattered wakeups.&lt;/p&gt;

&lt;p&gt;When a device enters Doze, the system restricts network access and defers regular jobs, syncs, and standard alarms. It periodically opens maintenance windows so apps can perform deferred work together. App Standby applies additional limits based on how recently and frequently the user interacts with an app.&lt;/p&gt;

&lt;p&gt;Exact behavior depends on the Android version, device state, standby bucket, manufacturer, and whether the device is charging. Vendor-specific power management can add another layer.&lt;/p&gt;

&lt;p&gt;This is why background code that appears reliable on a developer phone connected to USB may behave completely differently overnight on a real user's device.&lt;/p&gt;

&lt;p&gt;It also explains why fighting the scheduler is usually the wrong fix. Requesting battery optimization exemptions, using exact alarms, or keeping a foreground service alive can make work run more often, but it transfers the cost directly to the user's battery.&lt;/p&gt;

&lt;p&gt;Battery optimization exemptions should be reserved for cases where the core function genuinely can't work under normal power restrictions. They aren't a general reliability setting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polling Is Usually the First Suspect
&lt;/h2&gt;

&lt;p&gt;A common implementation looks harmless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;syncWithServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've also seen the same design built with &lt;code&gt;Timer&lt;/code&gt;, &lt;code&gt;Handler.postDelayed()&lt;/code&gt;, RxJava intervals, or a sleeping thread.&lt;/p&gt;

&lt;p&gt;This has two problems.&lt;/p&gt;

&lt;p&gt;First, it's not durable. If the process dies, the loop disappears. If somebody puts it in a foreground service to prevent that, the app now keeps itself active indefinitely.&lt;/p&gt;

&lt;p&gt;Second, the schedule ignores device conditions. It may run when there's no useful data, when the battery is low, or when a larger batch could have been sent later.&lt;/p&gt;

&lt;p&gt;Frequent network requests are especially expensive. The request's CPU time may be short, but establishing connections, transferring headers, performing TLS work, parsing responses, and activating the cellular radio all add cost. Several tiny requests can be worse than one batched request.&lt;/p&gt;

&lt;p&gt;For durable work that can run later, WorkManager is usually a better fit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;constraints&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Constraints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRequiredNetworkType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NetworkType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CONNECTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRequiresBatteryNotLow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;syncRequest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;PeriodicWorkRequestBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AccountSyncWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HOURS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setConstraints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;constraints&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setBackoffCriteria&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;BackoffPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EXPONENTIAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SECONDS&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nc"&gt;WorkManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;enqueueUniquePeriodicWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"account-sync"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;ExistingPeriodicWorkPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;KEEP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;syncRequest&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is appropriate when the sync must eventually happen but doesn't need an exact execution time. WorkManager persists scheduled work, cooperates with system scheduling, supports constraints, and applies retry policies.&lt;/p&gt;

&lt;p&gt;Periodic WorkManager requests have a minimum interval of 15 minutes, but that doesn't mean they'll run exactly every 15 minutes. Execution can be delayed and batched by the system. If a feature needs second-level timing, periodic work is the wrong abstraction.&lt;/p&gt;

&lt;p&gt;Unique work also matters. Without it, opening the app repeatedly can accidentally enqueue several copies of the same sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  Push Beats Polling When the Server Knows First
&lt;/h2&gt;

&lt;p&gt;If the server already knows when data changes, repeatedly asking it for updates wastes power.&lt;/p&gt;

&lt;p&gt;FCM can notify the app that new data is available. The app can then fetch only what it needs. This changes the pattern from hundreds of empty requests to a small number of meaningful requests.&lt;/p&gt;

&lt;p&gt;Normal-priority FCM messages may be delayed during Doze. High-priority messages are intended for time-sensitive, user-visible events. They should not be used as a hidden heartbeat. Misusing high priority can lead to delivery being deprioritized, and it still doesn't justify long background execution.&lt;/p&gt;

&lt;p&gt;A useful messaging pattern is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The server sends a push indicating that data changed.&lt;/li&gt;
&lt;li&gt;The app performs a small, bounded update.&lt;/li&gt;
&lt;li&gt;Longer durable work is handed to WorkManager.&lt;/li&gt;
&lt;li&gt;Duplicate updates are collapsed using IDs or timestamps.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the message isn't time-sensitive, letting Android batch the work is usually fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alarms Are Not a General Background Scheduler
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;AlarmManager&lt;/code&gt; makes sense when time itself is part of the product requirement, such as an alarm clock or a user-created reminder.&lt;/p&gt;

&lt;p&gt;It isn't a good replacement for a custom polling loop.&lt;/p&gt;

&lt;p&gt;Wakeup alarms can wake the CPU while the device is asleep. Exact alarms are particularly costly because they reduce the system's ability to batch work. Modern Android versions also restrict exact alarm access, and apps should request it only when precise timing is central to the feature.&lt;/p&gt;

&lt;p&gt;For a periodic database cleanup, upload, or refresh, use WorkManager. For "notify me at 7:30 AM," an alarm may be appropriate.&lt;/p&gt;

&lt;p&gt;The difference is user intent and timing precision, not which API is easiest to call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Location Can Drain a Battery Fast
&lt;/h2&gt;

&lt;p&gt;Background location has a very visible cost because it can involve GPS, Wi-Fi scanning, cellular positioning, sensors, and CPU processing.&lt;/p&gt;

&lt;p&gt;The dangerous configuration is usually some combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High accuracy&lt;/li&gt;
&lt;li&gt;Short update intervals&lt;/li&gt;
&lt;li&gt;No minimum displacement&lt;/li&gt;
&lt;li&gt;Updates continuing after the feature ends&lt;/li&gt;
&lt;li&gt;A foreground service that never stops&lt;/li&gt;
&lt;li&gt;Uploading every location point immediately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A fitness tracker or active navigation session may justify frequent updates. A weather app usually doesn't.&lt;/p&gt;

&lt;p&gt;For less urgent features, developers can lower accuracy, increase intervals, request batched updates, use geofencing, or refresh location only when the user opens the relevant screen. Location callbacks should be removed as soon as they're no longer needed.&lt;/p&gt;

&lt;p&gt;Background location access is also restricted and policy-sensitive on modern Android. A permission grant doesn't mean continuous tracking is automatically a good design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wake Locks and CPU Wakeups
&lt;/h2&gt;

&lt;p&gt;When the screen is off, the CPU can enter low-power states. A partial wake lock asks the system to keep the CPU running.&lt;/p&gt;

&lt;p&gt;That can be valid during a short critical operation, but an unreleased wake lock is one of the fastest ways to produce obvious drain.&lt;/p&gt;

&lt;p&gt;Wake locks should be narrowly scoped and have a timeout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;wakeLock&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;powerManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newWakeLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;PowerManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PARTIAL_WAKE_LOCK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"${context.packageName}:upload"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;wakeLock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000L&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;uploadPendingData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wakeLock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isHeld&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;wakeLock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even this should prompt a design question. WorkManager and other system-managed components often handle the required wakefulness themselves. Adding another wake lock may be unnecessary.&lt;/p&gt;

&lt;p&gt;Also, wake lock duration isn't the only useful metric. An app can cause serious drain through thousands of brief CPU wakeups. A five-second task once per hour may be fine. A 100-millisecond task every ten seconds may prevent the device from spending enough time asleep.&lt;/p&gt;

&lt;p&gt;Count wakeups, not just total execution time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Foreground Services Are for User-Visible Ongoing Work
&lt;/h2&gt;

&lt;p&gt;A foreground service raises the process's importance and displays an ongoing notification. It makes sense for work the user actively expects to continue, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Turn-by-turn navigation&lt;/li&gt;
&lt;li&gt;Media playback&lt;/li&gt;
&lt;li&gt;An active workout&lt;/li&gt;
&lt;li&gt;A user-started file transfer&lt;/li&gt;
&lt;li&gt;Connected-device communication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It shouldn't exist only to keep a process alive or preserve a polling loop.&lt;/p&gt;

&lt;p&gt;Modern Android versions restrict when an app can start a foreground service from the background. They also require appropriate foreground service types and, in some cases, related permissions. These rules have become stricter across releases.&lt;/p&gt;

&lt;p&gt;Even when starting one is technically allowed, the service should have a clear lifecycle. Start it because the user began an ongoing operation. Stop it when that operation ends.&lt;/p&gt;

&lt;p&gt;A permanent notification saying "App is running" is usually evidence that the service serves the implementation rather than the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Track Down Background Battery Usage
&lt;/h2&gt;

&lt;p&gt;Android's Battery usage screen is a starting point, not a profiler. Its numbers are estimates attributed over a time window, and presentation varies across devices. A high background percentage tells me where to look, but not which line of code is responsible.&lt;/p&gt;

&lt;p&gt;For controlled testing, I reset battery statistics first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys batterystats &lt;span class="nt"&gt;--reset&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I unplug the device, run one specific scenario, leave it idle, and collect a bug report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb bugreport battery-test.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bug report and &lt;code&gt;batterystats&lt;/code&gt; data can reveal wake locks, jobs, alarms, network activity, and periods when the device remained awake. Battery Historian can make long sessions easier to inspect. Android Studio's Power Profiler is useful for correlating device power behavior with app activity on supported devices.&lt;/p&gt;

&lt;p&gt;I also inspect the scheduler rather than assuming my code ran when requested:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys jobscheduler
adb shell dumpsys alarm
adb shell dumpsys deviceidle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These outputs are noisy, so I search for the package name and compare timestamps with application logs.&lt;/p&gt;

&lt;p&gt;To test Doze behavior, I use a physical device, turn off the screen, disconnect power, and force idle when appropriate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys deviceidle force-idle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys deviceidle unforce
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forced idle isn't a perfect simulation of hours of normal use, but it quickly exposes code that assumes unrestricted networking or exact scheduling.&lt;/p&gt;

&lt;p&gt;Useful application metrics include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Worker starts, finishes, retries, and cancellation reasons&lt;/li&gt;
&lt;li&gt;Foreground service start and stop times&lt;/li&gt;
&lt;li&gt;Network request counts and transferred bytes&lt;/li&gt;
&lt;li&gt;Location request duration and configuration&lt;/li&gt;
&lt;li&gt;Wake lock acquisition duration&lt;/li&gt;
&lt;li&gt;Push delivery and processing timestamps&lt;/li&gt;
&lt;li&gt;Sync triggers and the amount of useful data changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Logging every five seconds can itself distort a test, so I keep instrumentation lightweight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normal Work Versus Real Drain
&lt;/h2&gt;

&lt;p&gt;I don't classify an app as wasteful merely because it does something in the background.&lt;/p&gt;

&lt;p&gt;A messaging app processing a few pushes is normal. A navigation app using location during an active trip is normal. A backup app uploading a large file after the user requested it may consume substantial battery, but the work has a clear purpose.&lt;/p&gt;

&lt;p&gt;The suspicious cases look different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A process is kept important without active user-facing work.&lt;/li&gt;
&lt;li&gt;The CPU wakes frequently to discover there is nothing to do.&lt;/li&gt;
&lt;li&gt;Network requests return unchanged data most of the time.&lt;/li&gt;
&lt;li&gt;Location continues after the related feature ends.&lt;/li&gt;
&lt;li&gt;Failed jobs retry aggressively without backoff.&lt;/li&gt;
&lt;li&gt;A foreground service exists only to avoid process death.&lt;/li&gt;
&lt;li&gt;Several schedulers trigger duplicate copies of the same task.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best metric is often useful work per wakeup. If most wakeups produce no visible state change, no new data, and no completed user request, the schedule is probably too aggressive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the Design, Not Just the Code
&lt;/h2&gt;

&lt;p&gt;Battery testing needs a real device. Emulators are useful for behavior and API testing, but they don't reproduce real radios, thermal conditions, standby behavior, or manufacturer power policies.&lt;/p&gt;

&lt;p&gt;I test a release-like build under several conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Screen on and screen off&lt;/li&gt;
&lt;li&gt;Wi-Fi and cellular data&lt;/li&gt;
&lt;li&gt;Good and poor connectivity&lt;/li&gt;
&lt;li&gt;Doze and App Standby&lt;/li&gt;
&lt;li&gt;Low battery&lt;/li&gt;
&lt;li&gt;Server failures and repeated retries&lt;/li&gt;
&lt;li&gt;App opened frequently and left unused overnight&lt;/li&gt;
&lt;li&gt;Reboot followed by pending scheduled work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I compare against a baseline with the feature disabled. Battery percentage alone is too coarse for short tests, so I also inspect wakeups, wake lock time, job frequency, network transfers, and foreground service duration.&lt;/p&gt;

&lt;p&gt;The fixes are usually straightforward once the trigger is visible: replace polling with push, batch writes, add constraints, use exponential backoff, deduplicate work, lower location accuracy, stop services promptly, and remove wake locks that the platform scheduler already handles.&lt;/p&gt;

&lt;p&gt;Android isn't randomly killing apps, and background battery drain isn't caused by one API. The system manages processes and limits execution according to device state, app state, OS version, and manufacturer policy.&lt;/p&gt;

&lt;p&gt;Our job is to describe background work honestly. If it can wait, schedule it as deferrable work. If the server knows when something changes, use push. If timing must be exact, make sure the user actually asked for that precision. If work must remain active and visible, use a foreground service with a real stop condition.&lt;/p&gt;

&lt;p&gt;And if a five-minute loop feels like the easiest solution, that's usually the moment to step back and rethink the design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Official Android References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/background-work/background-tasks" rel="noopener noreferrer"&gt;Background work overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/background-work/background-tasks/persistent/getting-started" rel="noopener noreferrer"&gt;WorkManager documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/training/monitoring-device-state/doze-standby" rel="noopener noreferrer"&gt;Optimize for Doze and App Standby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/background-work/services/fgs" rel="noopener noreferrer"&gt;Foreground services overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/background-work/services/alarms" rel="noopener noreferrer"&gt;Schedule alarms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/background-work/background-tasks/awake/wakelock" rel="noopener noreferrer"&gt;Wake lock guidance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/about/versions/oreo/background-location-limits" rel="noopener noreferrer"&gt;Background location limits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://firebase.google.com/docs/cloud-messaging/android/message-priority" rel="noopener noreferrer"&gt;FCM message priority&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/topic/performance/power/setup-battery-historian" rel="noopener noreferrer"&gt;Inspect battery usage with Batterystats and Battery Historian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://timotic.me" rel="noopener noreferrer"&gt;Author’s website&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>performance</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Why Your Android App Stops Working After You Lock the Phone</title>
      <dc:creator>Alex Vukovic</dc:creator>
      <pubDate>Sun, 30 Aug 2026 23:03:50 +0000</pubDate>
      <link>https://dev.to/alexvukovic/why-your-android-app-stops-working-after-you-lock-the-phone-e0d</link>
      <guid>https://dev.to/alexvukovic/why-your-android-app-stops-working-after-you-lock-the-phone-e0d</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Then you lock the phone.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open doesn't mean allowed to run forever
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That can create a misleading test environment.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Once the screen is locked, the activity usually moves through &lt;code&gt;onPause()&lt;/code&gt; and &lt;code&gt;onStop()&lt;/code&gt;. The process may continue running, but the app is no longer in the foreground from Android's point of view.&lt;/p&gt;

&lt;p&gt;That distinction matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An app process can still exist without having permission to do unrestricted background work.&lt;/li&gt;
&lt;li&gt;An activity being stopped does not guarantee that the process will be terminated.&lt;/li&gt;
&lt;li&gt;A process remaining alive does not guarantee that its timers, network access, jobs, or services can run whenever they want.&lt;/li&gt;
&lt;li&gt;Work stored only in memory disappears if Android later removes the process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why &lt;code&gt;onStop()&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changes when the screen turns off
&lt;/h2&gt;

&lt;p&gt;Locking the screen doesn't immediately flip one giant "stop all apps" switch.&lt;/p&gt;

&lt;p&gt;Several things can happen, sometimes right away and sometimes after the device has been idle for a while.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;These mechanisms overlap, but they aren't the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background execution limits are often the first problem
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;stopSelf()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means an old pattern like this is not a reliable way to run indefinitely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;startService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SyncService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It might work while the activity is visible. It might continue briefly after the screen is locked. Then it stops.&lt;/p&gt;

&lt;p&gt;On newer Android versions, trying to start a normal background service while the app is already in the background can also throw an &lt;code&gt;IllegalStateException&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doze is delayed, but it changes a lot
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Once the device enters Doze, Android starts deferring a number of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regular network access is suspended.&lt;/li&gt;
&lt;li&gt;Standard alarms are deferred.&lt;/li&gt;
&lt;li&gt;Jobs and WorkManager work may be postponed.&lt;/li&gt;
&lt;li&gt;Wake locks are ignored.&lt;/li&gt;
&lt;li&gt;Background CPU activity is heavily limited.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Most apps should adapt their scheduling instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Standby is related, but not triggered by one screen lock
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Battery optimization and user restrictions add another layer
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;"Optimized" is generally the normal state. It allows Android's standard battery management to apply.&lt;/p&gt;

&lt;p&gt;"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.&lt;/p&gt;

&lt;p&gt;There's also the system's general background activity restriction. This is separate from Android's rules about starting an &lt;code&gt;Activity&lt;/code&gt; from the background, though the names are easy to confuse.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  A foreground service is not a hidden keep-alive trick
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;It must show an ongoing notification and declare an appropriate foreground service type. Recent Android releases also require corresponding permissions for certain service types.&lt;/p&gt;

&lt;p&gt;A basic start looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;intent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TrackingService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;ContextCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startForegroundService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service then needs to promote itself to the foreground promptly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onStartCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
    &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;startId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;startForeground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;TRACKING_NOTIFICATION_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;createTrackingNotification&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;START_NOT_STICKY&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;There are also restrictions on starting foreground services.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;dataSync&lt;/code&gt; and &lt;code&gt;mediaProcessing&lt;/code&gt; foreground services when an app targets that version.&lt;/p&gt;

&lt;p&gt;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 foreground service is probably the wrong model.&lt;/p&gt;

&lt;h2&gt;
  
  
  WorkManager is usually right for deferred, durable work
&lt;/h2&gt;

&lt;p&gt;For background work that must eventually happen, WorkManager is usually where I start.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uploading locally saved data&lt;/li&gt;
&lt;li&gt;Syncing when a network connection is available&lt;/li&gt;
&lt;li&gt;Retrying a failed API request&lt;/li&gt;
&lt;li&gt;Processing data after a push message&lt;/li&gt;
&lt;li&gt;Performing periodic maintenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A one-time request can declare what it needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;constraints&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Constraints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRequiredNetworkType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NetworkType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CONNECTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;syncRequest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OneTimeWorkRequestBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SyncWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setConstraints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;constraints&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setBackoffCriteria&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;BackoffPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EXPONENTIAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SECONDS&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nc"&gt;WorkManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;enqueueUniqueWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"account-sync"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;ExistingWorkPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;KEEP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;syncRequest&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WorkManager stores the request and coordinates with Android's scheduling APIs. If the process disappears, the request isn't just lost with an in-memory coroutine.&lt;/p&gt;

&lt;p&gt;The tradeoff is timing. WorkManager guarantees that eligible work will be scheduled, not that it will start at an exact second. Doze, constraints, quotas, standby buckets, and system load can all delay it.&lt;/p&gt;

&lt;p&gt;Periodic work is also intentionally inexact, with a minimum repeat interval of 15 minutes. It isn't appropriate for a timer that must fire every minute.&lt;/p&gt;

&lt;p&gt;Expedited work can request faster execution, but it has quotas and should be used for genuinely time-sensitive tasks. It isn't an unlimited express lane.&lt;/p&gt;

&lt;p&gt;Long-running WorkManager tasks can run with foreground service support, but that still brings foreground service rules into the picture. WorkManager doesn't erase platform restrictions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Push is better than polling for server-driven events
&lt;/h2&gt;

&lt;p&gt;If the app needs to react when something changes on a server, keeping a process alive and polling every few minutes is usually the wrong design.&lt;/p&gt;

&lt;p&gt;Firebase Cloud Messaging gives the server a way to notify the device that something happened.&lt;/p&gt;

&lt;p&gt;There are two broad message patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Notification messages can be displayed by the FCM SDK when the app is in the background.&lt;/li&gt;
&lt;li&gt;Data messages are delivered to app code through &lt;code&gt;FirebaseMessagingService&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That difference matters. If a notification message works while the screen is off but a data message doesn't complete its processing, the issue may be what the app is doing inside &lt;code&gt;onMessageReceived()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That callback is intended for short work. If the app needs to download files, update a large database, or perform something retryable, it should hand the task to WorkManager.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onMessageReceived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RemoteMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OneTimeWorkRequestBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MessageSyncWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInputData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;workDataOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"messageId"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nc"&gt;WorkManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;applicationContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FCM priority also matters.&lt;/p&gt;

&lt;p&gt;Normal-priority messages can be delayed during Doze. High-priority messages are meant for urgent, user-visible events and can temporarily wake the device. If high priority is repeatedly used for messages that don't produce user-visible results, FCM may deprioritize later messages.&lt;/p&gt;

&lt;p&gt;FCM delivery also isn't a precision clock. Messages can be delayed, collapsed, or expire based on their configuration and device connectivity.&lt;/p&gt;

&lt;p&gt;If FCM says the message arrived but no notification appears, I check a different set of problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the app have notification permission on Android 13 or later?&lt;/li&gt;
&lt;li&gt;Is the notification channel disabled?&lt;/li&gt;
&lt;li&gt;Is the channel importance too low?&lt;/li&gt;
&lt;li&gt;Did the app actually create the notification?&lt;/li&gt;
&lt;li&gt;Was the message a notification payload or a data-only payload?&lt;/li&gt;
&lt;li&gt;Was the app force-stopped?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A force-stopped package is a special case. Android keeps it in a stopped state until the user launches it again or otherwise interacts with it. That can block scheduled work and message delivery. Swiping an app out of the recent apps screen is not normally the same thing as force-stopping it, although manufacturer behavior can complicate the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why in-process timers fail so easily
&lt;/h2&gt;

&lt;p&gt;A lot of screen-lock bugs come from code that assumes the process will keep running.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Handler.postDelayed()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Timer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ScheduledExecutorService&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A coroutine with &lt;code&gt;delay()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A loop that sleeps between network requests&lt;/li&gt;
&lt;li&gt;An RxJava interval&lt;/li&gt;
&lt;li&gt;A JavaScript timer inside a WebView or cross-platform runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools are fine while the process is alive and getting CPU time. They aren't persistent schedulers.&lt;/p&gt;

&lt;p&gt;If Android removes the process, the timer is gone. If the CPU suspends, it doesn't keep ticking normally. If Doze blocks network access, the callback might run later but still be unable to complete its request.&lt;/p&gt;

&lt;p&gt;For a short UI-related delay, an in-process timer is fine. For work that must survive the app leaving the screen, I use a platform-backed mechanism.&lt;/p&gt;

&lt;p&gt;If exact user-facing timing is genuinely required, an alarm may be appropriate. Exact alarms have their own permission and policy restrictions on modern Android, so they shouldn't be used as a general replacement for background scheduling.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I diagnose the actual restriction
&lt;/h2&gt;

&lt;p&gt;I try not to guess from the symptom alone. "It stops after the screen locks" isn't enough to identify the mechanism.&lt;/p&gt;

&lt;p&gt;First, I log the boundaries of the work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Sync requested"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Worker started"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Network request started"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Network request completed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Worker finished"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also log:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Activity lifecycle transitions&lt;/li&gt;
&lt;li&gt;Service creation and destruction&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onStartCommand()&lt;/code&gt; calls&lt;/li&gt;
&lt;li&gt;WorkManager attempt counts&lt;/li&gt;
&lt;li&gt;FCM receipt times and message IDs&lt;/li&gt;
&lt;li&gt;Network failures and timeout causes&lt;/li&gt;
&lt;li&gt;Notification creation&lt;/li&gt;
&lt;li&gt;Whether the process was started fresh&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That tells me whether the work wasn't scheduled, was scheduled but delayed, started and failed, or completed without producing the expected UI.&lt;/p&gt;

&lt;p&gt;Then I inspect system state with ADB.&lt;/p&gt;

&lt;p&gt;To see Doze and device idle state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys deviceidle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To force the device into idle mode for a controlled test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys battery unplug
adb shell dumpsys deviceidle force-idle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To leave forced idle mode and restore battery reporting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys deviceidle unforce
adb shell dumpsys battery reset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;battery unplug&lt;/code&gt; command makes Android treat the device as unplugged, which matters because a USB-connected test phone may otherwise behave like a charging device.&lt;/p&gt;

&lt;p&gt;To simulate an inactive app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell am set-inactive com.example.app &lt;span class="nb"&gt;true
&lt;/span&gt;adb shell am get-inactive com.example.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On versions that support standby bucket commands, I can also test a restrictive bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell am set-standby-bucket com.example.app rare
adb shell am get-standby-bucket com.example.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To inspect scheduled jobs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys jobscheduler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To inspect running services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys activity services com.example.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For alarms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys alarm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for the full story, I keep Logcat running while the screen is off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb logcat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filtering for the package, &lt;code&gt;ActivityManager&lt;/code&gt;, &lt;code&gt;JobScheduler&lt;/code&gt;, WorkManager, and Firebase Messaging usually makes the output manageable.&lt;/p&gt;

&lt;p&gt;ADB commands and output can vary between Android versions. I treat them as test tools, not behavior that production code should depend on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing with the phone actually locked
&lt;/h2&gt;

&lt;p&gt;A quick screen-off test isn't enough. I test several distinct states because they exercise different paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App visible with the screen on&lt;/li&gt;
&lt;li&gt;App backgrounded with the screen on&lt;/li&gt;
&lt;li&gt;Screen locked for a short time&lt;/li&gt;
&lt;li&gt;Device forced into Doze&lt;/li&gt;
&lt;li&gt;App placed in a restrictive standby bucket&lt;/li&gt;
&lt;li&gt;Device disconnected from power&lt;/li&gt;
&lt;li&gt;Network disconnected and restored&lt;/li&gt;
&lt;li&gt;Process removed without force-stopping the package&lt;/li&gt;
&lt;li&gt;App explicitly force-stopped&lt;/li&gt;
&lt;li&gt;Device rebooted before scheduled work runs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also test more than one Android version. Android 8's background service limits, Android 10's activity launch restrictions, Android 12's foreground service start restrictions, Android 13's notification permission, and newer foreground service requirements can all change what fails.&lt;/p&gt;

&lt;p&gt;At least one physical device is useful too. Emulators are great for forced-state testing, but they don't reproduce every manufacturer battery policy.&lt;/p&gt;

&lt;p&gt;For delayed work, I don't stare at the UI and assume nothing happened. I collect timestamps from the app and server, then compare when the work was requested, when Android started it, and when the request reached the backend.&lt;/p&gt;

&lt;p&gt;That separates a scheduling delay from a notification bug or failed API call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the mechanism that matches the job
&lt;/h2&gt;

&lt;p&gt;The choice gets easier when I stop asking, "How do I keep my app running?" and ask, "What kind of work is this?"&lt;/p&gt;

&lt;p&gt;For durable work that can run later, I use WorkManager.&lt;/p&gt;

&lt;p&gt;For continuous, user-visible work that needs to happen now, I use a foreground service with the correct service type and notification.&lt;/p&gt;

&lt;p&gt;For server-driven events, I use FCM, then hand longer processing to WorkManager when needed.&lt;/p&gt;

&lt;p&gt;For an exact, user-facing event such as an alarm clock, I evaluate the exact alarm APIs and their current permission rules.&lt;/p&gt;

&lt;p&gt;For ordinary UI work, I keep it tied to the activity or screen lifecycle and accept that it stops when the UI goes away.&lt;/p&gt;

&lt;p&gt;What I don't do is rely on a loop, timer, coroutine, or plain background service to keep an Android app alive forever. It may survive a basic test with the screen on, but that test proves almost nothing about Android background execution.&lt;/p&gt;

&lt;p&gt;The practical fix is usually not to fight every battery restriction. It's to make the work durable, schedulable, and honest about how urgent it really is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Official references
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/about/versions/oreo/background" rel="noopener noreferrer"&gt;Background execution limits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/training/monitoring-device-state/doze-standby" rel="noopener noreferrer"&gt;Doze and App Standby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/background-work/background-tasks" rel="noopener noreferrer"&gt;Background tasks overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/background-work/background-tasks/persistent/getting-started" rel="noopener noreferrer"&gt;WorkManager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/background-work/services/fgs" rel="noopener noreferrer"&gt;Foreground services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start" rel="noopener noreferrer"&gt;Restrictions on starting foreground services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/guide/components/activities/background-starts" rel="noopener noreferrer"&gt;Background activity launch restrictions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://firebase.google.com/docs/cloud-messaging/android/receive" rel="noopener noreferrer"&gt;Receiving Firebase Cloud Messaging messages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://timotic.me" rel="noopener noreferrer"&gt;Author’s website&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobile</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Why Your Android Phone Isn't Showing Notifications Even With Good Internet (Adaptive Battery Is Probably The Culprit)</title>
      <dc:creator>Alex Vukovic</dc:creator>
      <pubDate>Sun, 30 Aug 2026 22:22:05 +0000</pubDate>
      <link>https://dev.to/alexvukovic/why-your-android-phone-isnt-showing-notifications-even-with-good-internet-adaptive-battery-is-3ml8</link>
      <guid>https://dev.to/alexvukovic/why-your-android-phone-isnt-showing-notifications-even-with-good-internet-adaptive-battery-is-3ml8</guid>
      <description>&lt;p&gt;I almost missed a really important call last month because my phone just... didn't buzz. No notification, nothing. My WiFi was working, my mobile data was fine, and I later found out even the Google push connection was working perfectly. So what was going on?&lt;/p&gt;

&lt;p&gt;Turns out the villain here wasn't my internet. It was a setting sitting quietly on my phone the entire time, doing exactly what Google designed it to do: aggressively killing apps to save battery life. That setting is called Adaptive Battery, and if you're on Android 17, it's probably wrecking your notifications too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Notifications Are Getting There, Your Phone Just Won't Let Them Through
&lt;/h2&gt;

&lt;p&gt;Here's the thing that took me a while to understand. When people say "I'm not getting notifications," they usually blame their WiFi, their carrier, or the app itself being broken. But in a ton of cases, none of that is the real problem.&lt;/p&gt;

&lt;p&gt;The push notification actually arrives at your phone through Google's FCM servers (that's Firebase Cloud Messaging, the system almost every app uses to send you notifications). The connection is solid. The message shows up. And then Android just decides your app doesn't deserve to wake up and show it to you.&lt;/p&gt;

&lt;p&gt;That's Adaptive Battery in action. It's a system that learns your usage patterns and decides which apps are "important" enough to run in the background. If it decides an app is low priority, it straight up kills it, or restricts it so hard that even an incoming push notification can't wake it up properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apps I've Personally Seen This Happen With
&lt;/h2&gt;

&lt;p&gt;This isn't some rare edge case. I've seen this mess with notifications on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WhatsApp&lt;/li&gt;
&lt;li&gt;Signal&lt;/li&gt;
&lt;li&gt;Telegram&lt;/li&gt;
&lt;li&gt;Gmail (yes, even Google's own app sometimes)&lt;/li&gt;
&lt;li&gt;Messenger&lt;/li&gt;
&lt;li&gt;Slack&lt;/li&gt;
&lt;li&gt;Discord&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically any app that relies on staying alive in the background to instantly show you a notification can get hit by this. And the annoying part is it's inconsistent. Sometimes you get the notification fine, other times it just vanishes into nothing, especially if the app hasn't been opened in a while or your battery optimization decided to "help" you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pixel Paradox Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Here's what really gets me. Android 17 is still pretty fresh, and right now it's mostly Pixel owners dealing with this since Pixels get the update first. So the people running into this notification nightmare are, ironically, the same people using Google's own phones with Google's own operating system.&lt;/p&gt;

&lt;p&gt;You'd think if anyone's notifications would work flawlessly it would be on a Pixel, running stock Android, straight from the source. But nope. The most aggressive battery management is hitting the exact users who should have the smoothest experience. Kind of a wild contradiction if you think about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Adaptive Battery Actually Is
&lt;/h2&gt;

&lt;p&gt;Just to clear this up, Adaptive Battery is a feature Android uses to extend your battery life by limiting background activity for apps it thinks you don't use often, or that it decides shouldn't be running constantly. It sounds great on paper. Save battery, sounds like a win. Except it doesn't always accurately judge which apps you actually need alerts from in real time, and messaging apps get caught in the crossfire constantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Turn Off Adaptive Battery
&lt;/h2&gt;

&lt;p&gt;If you're tired of missing texts, calls, and important alerts, here's how to shut this thing off:&lt;/p&gt;

&lt;p&gt;Open the Settings app on your phone.&lt;/p&gt;

&lt;p&gt;Tap on Battery.&lt;/p&gt;

&lt;p&gt;Choose Battery Saver or look for Adaptive Preferences.&lt;/p&gt;

&lt;p&gt;Tap on Adaptive Battery and toggle the switch to the Off position.&lt;/p&gt;

&lt;p&gt;That's it. Takes about ten seconds and it might save you from missing something important down the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Actually Give Up By Turning It Off
&lt;/h2&gt;

&lt;p&gt;I'll be honest with you, turning this off does mean your battery life probably takes a small hit. Your apps will run more freely in the background, which uses more power throughout the day. If you're someone who's already squeezing every bit of battery life out of your phone, you'll notice the difference.&lt;/p&gt;

&lt;p&gt;But here's how I look at it. I'd rather charge my phone a little more often than miss a call from my mom, a message from my kid's school, or something urgent from work because my phone decided an app wasn't "important enough" to stay awake. For me, that trade-off is a no brainer. Battery percentage means nothing if the notification that actually mattered never made it to my screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Last Thing
&lt;/h2&gt;

&lt;p&gt;I want to be upfront that adaptive battery isn't the only reason notifications go missing on Android. Sometimes it's app specific battery restrictions, sometimes it's Do Not Disturb settings, sometimes it's just a buggy app update. But in my experience, and from what I've seen a lot of other people running into, adaptive battery is one of the most common and most overlooked causes, especially right now with Android 17 rolling out on Pixel devices.&lt;/p&gt;

&lt;p&gt;If your notifications have been randomly disappearing and you've already ruled out your internet connection, give this a try. Hopefully it saves you the same headache it caused me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://timotic.me" rel="noopener noreferrer"&gt;Author’s website&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>mobile</category>
      <category>debugging</category>
      <category>devbugsmash</category>
    </item>
  </channel>
</rss>
