Trying to optimize your app's battery consumption without measuring it first is shooting arrows in the dark. This guide covers battery consumption optimization for both iOS and Android, grounded in official Apple and Google documentation — from measurement tools to network and location optimization, real-device A/B testing, and catching battery regressions in CI.
💡 Pro Tip: Before you start optimizing, open the Battery Usage panel in Xcode Organizer or Android's profiling tools — every change you make without knowing which category (network, location, CPU, display) dominates stays a guess.
Table of Contents
- The Four Sources of Battery Drain: CPU, Network, Location, Display
- CPU and Processing
- Network
- Location
- Display
- Measuring on iOS: Energy Log, MetricKit, and Organizer Data
- Battery Usage in Organizer
- Energy Impact Gauge (Debug Navigator)
- Production Data with MetricKit
- Measuring on Android: System Tracing, Macrobenchmark, and Power Profiler
- Current Tools: System Tracing, Macrobenchmark Power Metric, and Power Profiler
- Reading Logs with Battery Historian (Old But Still Useful)
- Common Battery Anti-Patterns
- Batching Network Requests and Scheduling Background Work
- Reducing Location Accuracy to Match Real Need
- Reducing Display and Processing Cost
- Real-Device A/B Measurement Protocol
- Catching Regressions in CI
- FAQ
- How do I measure my app's battery consumption?
- Why do background jobs drain the battery so fast?
- What are the battery-friendly patterns for location and network usage?
- When is a wake lock actually necessary?
- How do I catch a battery regression before release?
- How much does the display affect battery consumption?
- Update (September 2026)
- Conclusion
- Sources
The Four Sources of Battery Drain: CPU, Network, Location, Display
Apple's Xcode documentation breaks down an app's energy usage into categories: Audio, Networking, Processing (CPU/GPU), Display, Bluetooth, Location, Camera, Torch, NFC, and Other. This breakdown appears both in Xcode Organizer reports and in the Debug Navigator's Energy Impact gauge. Android terminology differs slightly, but the physics is the same: radios (cellular/Wi-Fi/GPS), the display panel, and the CPU/GPU are the three biggest consumers.
In practice, know which of these four sources dominates before starting optimization work — otherwise you might spend weeks on CPU while overlooking the real problem: network requests.
Source | Typical trigger | Measurement point (iOS) |
|---|---|---|
CPU/GPU (Processing) | Heavy computation, unnecessary render loop | Energy Impact gauge, XCTest CPU metric |
Network | Frequent polling, large payload, non-batched requests | Organizer Battery Usage, Networking category |
Location | Continuous high-accuracy GPS | Location category, MetricKit location metric |
Display | High brightness, frequent redraw | Display category |
CPU and Processing
CPU-heavy code consumes processor energy directly and also causes indirect battery loss by pushing the device into a higher frequency/thermal state. Unnecessary layout calculations on the main thread or frequently firing timers are the most common anti-patterns in this category.
Network
Google's official guide explicitly states that network requests are a major cause of battery drain, because cellular and Wi-Fi radios need to stay active. A radio doesn't go to sleep immediately after a request; that's why the sum of infrequent but large requests is generally cheaper than frequent but small ones.
Location
A continuous high-accuracy GPS lock alone can consume a large share of an app's daily battery budget. Google's guide lists holding a continuous GPS lock as one of the typical battery-drain patterns; you can detect it with system tracing or the Macrobenchmark power metric.
Display
The display, especially on OLED panels, is a variable but always significant consumer depending on brightness and color palette. Since this article focuses on measurement and background/network/location optimization, we don't go deep into color/brightness engineering here; the key takeaway is that display should be tracked as its own category in Xcode Organizer and Android profiling tools.
Measuring on iOS: Energy Log, MetricKit, and Organizer Data
On iOS, battery measurement operates on three layers: Instruments/Debug Navigator during development, Xcode Organizer in TestFlight/App Store builds, and MetricKit data collected from real user devices in production.
Battery Usage in Organizer
The Battery Usage panel in Xcode Organizer shows your app's foreground and background power usage broken down by version. This is the most concrete place to answer "did battery consumption increase in the new version?" — an ideal reference point for A/B comparison. Additionally, if your app uses a significant amount of energy within a 24-hour window, the system automatically generates an "energy exception report"; these reports come from real user devices without you needing to add any extra instrumentation.
Energy Impact Gauge (Debug Navigator)
During development, the Energy Impact gauge in Xcode's Debug Navigator shows the running app's instantaneous energy usage as a pie chart broken down by category. This gauge gives live feedback while you're building a feature: for example, if opening a list screen suddenly grows the Networking slice, that's an early sign of an unnecessary re-fetch.
Production Data with MetricKit
MetricKit delivers metric reports for the previous 24 hours from real user devices, at most once per day. It's the only reliable way to see real-world usage patterns that lab measurement can't capture (different network conditions, device models, background behavior). We don't cover MetricKit's full API surface here; what matters is that production battery data complements lab measurement rather than replacing it.
Measuring on Android: System Tracing, Macrobenchmark, and Power Profiler
Current Tools: System Tracing, Macrobenchmark Power Metric, and Power Profiler
Google's official guide now recommends these three tools for examining battery performance on Android: system tracing, the power metric in the Macrobenchmark library, and the Power Profiler in Android Studio. System tracing shows CPU/GPU/disk/network activity together on a timeline; Macrobenchmark's power metric lets you measure power consumption with automated tests and wire it into CI; Power Profiler lets you monitor live energy usage inside Android Studio.
Reading Logs with Battery Historian (Old But Still Useful)
Battery Historian is a tool that visualizes power-related events from system logs as an HTML timeline, letting you examine a device's battery history as a whole and see which component was active when. However, the tool's official documentation carries this warning (which was already present on the page as of this article's 2025-01-15 publish date): "Battery Historian is no longer actively maintained; if possible, consider using system tracing, the Macrobenchmark power metric, or the Power Profiler to get insights into battery performance." So rather than building a new measurement pipeline around Battery Historian, you might consider using it for reading old log files you already have.
Common Battery Anti-Patterns
Even though this tool is no longer maintained, Google's official guide states that Battery Historian typically detects these behaviors: wakeup alarms firing excessively often (every 10 seconds or more frequently), holding a continuous GPS lock, and scheduling jobs at very frequent intervals such as every 30 seconds. All three are the first patterns to check in code review — today you can also detect them with system tracing or the Macrobenchmark power metric.
An important practical detail: when you use WorkManager, JobScheduler, or DownloadManager, the wake lock is acquired on your behalf — you don't need to manage it manually. This is why it's a safer default than using PowerManager.WakeLock directly; if you still need a manual wake lock, choose the lightest approach and release it as quickly as possible, in a way that minimizes its impact on system resources.
You can find the broader picture of background task scheduling on the Android side in our WorkManager 2.10 Coroutines guide; for its iOS counterpart, see our iOS Background Processing article.
Batching Network Requests and Scheduling Background Work
There are two fundamental ways to make network requests battery-friendly: reducing the number and frequency of requests, and shifting those requests to time windows when the device is already in a favorable state (charging, on Wi-Fi).
Android's official guide directly recommends this second approach: scheduling background work to run under specific conditions, such as while the device is charging or connected to Wi-Fi. JobScheduler, and WorkManager built on top of it, let you declare these conditions declaratively.
1val constraints = Constraints.Builder()2 .setRequiredNetworkType(NetworkType.UNMETERED) // Prefer Wi-Fi3 .setRequiresCharging(true)4 .setRequiresBatteryNotLow(true)5 .build()6 7val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()8 .setConstraints(constraints)9 .build()10 11WorkManager.getInstance(context).enqueue(syncRequest)Constraint | When to use it |
|---|---|
setRequiresCharging(true) | Large sync jobs, archive downloads |
setRequiredNetworkType(UNMETERED) | Jobs that should wait for Wi-Fi instead of mobile data |
setRequiresBatteryNotLow(true) | Non-critical, deferrable jobs |
setRequiresDeviceIdle(true) | Maintenance jobs that should run while the device is idle |
If you think making the network layer itself battery-friendly is a deeper engineering topic, our Network Layer Optimization guide covers request batching, retry strategy, and caching in detail.
Reducing Location Accuracy to Match Real Need
Location services, especially when run continuously at high accuracy, are one of the most aggressive consumers of the battery budget. Both CLLocationManager on iOS and FusedLocationProviderClient on Android offer APIs that let you tune the accuracy level to your needs; in most scenarios outside real-time navigation (for example, "show nearby stores"), the highest accuracy level is unnecessary and drains battery for no reason.
1val locationRequest = LocationRequest.Builder(2 Priority.PRIORITY_BALANCED_POWER_ACCURACY,3 /* intervalMillis = */ 60_000L4).build()5 6fusedLocationClient.requestLocationUpdates(7 locationRequest,8 locationCallback,9 Looper.getMainLooper()10)Google's official guide also recommends two concrete habits: removing location updates once they're no longer needed (a typical mistake is calling requestLocationUpdates() in onStart()/onResume() without calling removeLocationUpdates() in the corresponding onPause()/onStop()), and batching request delivery in non-foreground scenarios — setIntervalMillis() sets how often location is computed, while setMaxUpdateDelayMillis() sets how often it's delivered to the device; for example, computing location every 10 minutes but batch-delivering it hourly via setMaxUpdateDelayMillis() lets the device wake up less often.
The practical rule is simple: design your location needs around "when, how much accuracy" instead of "always, everywhere, high accuracy." If you're building map-based features, our MapKit and Location Services guide shows you how to sync location updates with your UI.
Reducing Display and Processing Cost
On the display and CPU side, the highest returns usually come from three changes: preventing unnecessary redraws, not letting animations compete with low-priority work, and not rendering off-screen content in components like lists and tables (lazy loading). Most of the render performance optimizations on this topic were covered in detail in our SwiftUI Performance Optimization article — most of the techniques described there directly translate into battery savings too, because fewer CPU/GPU cycles mean less energy.
In practice, you can add these three checks to code review:
- Unnecessary redraw: Is a list cell or card component being redrawn on every scroll even when the data hasn't changed? Memoizing unchanged subviews reduces both render time and CPU/GPU energy.
- Competing animations: Are multiple continuous animations (spinner, parallax, live chart) running at the same time? These keep the CPU continuously busy while the screen is on; stopping animations when the user isn't interacting is a simple but effective win.
- Rendering invisible content: Are off-screen cells in list/table components still being computed? Lazy loading saves both memory and CPU/GPU, indirectly reducing battery consumption as well.
What these three checks have in common is that none of them requires a special "battery optimization" API — lower battery consumption is simply a natural side effect of good render/performance discipline.
Real-Device A/B Measurement Protocol
Measurements taken on a simulator or lab device don't reflect real user behavior; that's why the final validation of any battery optimization should always happen on a real device, and ideally with real user data. The recommended protocol works like this:
- Ship the pre-change version and establish a baseline on the Battery Usage panel in Organizer.
- Ship the version with the change and compare the version-by-version breakdown in the same panel.
- Track the increase/decrease in energy exception report count over a 24-hour window.
- If you find that a piece of code contributes significantly to overall energy usage through CPU-heavy activity, write a dedicated performance test for that code and wire it into CI.
This last step is the subject of the next section.
Catching Regressions in CI
Most battery regressions are noticed as "it was fine in the previous version, it got worse in this one" — which also means they can be caught in CI. XCTest lets you write performance tests that directly measure CPU usage with XCTCPUMetric; once you detect that a CPU-heavy piece of code contributes significantly to the app's overall energy usage, it's recommended that you create a performance test measuring that code's CPU usage.
1import XCTest2 3final class FeedProcessingPerformanceTests: XCTestCase {4 func testFeedDecodingCPUUsage() {5 let payload = loadFixture("large-feed.json")6 7 measure(metrics: [XCTCPUMetric()]) {8 _ = try? FeedDecoder().decode(payload)9 }10 }11}1#!/usr/bin/env bash2# CI step: run the performance test plan, store the result as an artifact.3set -euo pipefail4 5xcodebuild test \6 -scheme "App-PerformanceTests" \7 -destination "platform=iOS Simulator,name=iPhone 15" \8 -testPlan "PerformanceTests" \9 -resultBundlePath "PerformanceResults.xcresult"10 11xcrun xcresulttool get --legacy --path PerformanceResults.xcresult --format json \12 > performance-summary.jsonNote: the XCTCPUMetric value measured on a simulator is not an absolute energy consumption figure; read it in CI as a relative regression signal across versions, and validate the real battery impact only with the Battery Usage panel in Organizer (real-device data).
Put these pieces together — Debug Navigator during development, Organizer for version comparison, MetricKit in production, and CI performance tests for regression prevention — and battery consumption goes from a guess to a measurable engineering discipline.
The Android counterpart is wiring CPU/startup-time regressions into CI with measurement libraries like Macrobenchmark; making JobScheduler constraints (charging, network type) a mandatory code-review checklist item is the Android equivalent of the same discipline. On both platforms the goal is the same: catch battery regressions as a CI check, not as a user complaint.
GOLDEN TIP
The most valuable insight in this article
This tip holds the article's most important takeaway.
Easter Egg
You found a hidden gem!
There's a hidden detail in this section. Want to uncover it?
Reader Reward
We've condensed every measurement and optimization step in this article into a single checklist. You can follow the list below in order the next time you prepare a release.
FAQ
How do I measure my app's battery consumption?
On iOS, use a three-layer approach: the Energy Impact gauge in Debug Navigator during development, the Battery Usage panel in Xcode Organizer for version comparison, and MetricKit's daily metric reports for real user data in production. On Android, examine battery usage with system tracing, the Macrobenchmark power metric, or Power Profiler; you can also use the no-longer-maintained Battery Historian to read old log files.
Why do background jobs drain the battery so fast?
Because most background jobs keep a radio (network) or GPS active, and these components stay powered on for some time before going back to sleep. Frequently firing wakeup alarms (every 10 seconds or more often), a continuous GPS lock, and very frequently scheduled jobs (such as every 30 seconds) are the anti-patterns Google's official guide typically points to (they can also be detected with system tracing and the Macrobenchmark power metric, in addition to the no-longer-maintained Battery Historian).
What are the battery-friendly patterns for location and network usage?
For location: choose an accuracy level that matches real need (balanced accuracy instead of always-highest) and reduce update frequency. For network: batch requests, use event-driven updates instead of frequent polling, and tie large or non-critical jobs to a charging or Wi-Fi condition with WorkManager/JobScheduler.
When is a wake lock actually necessary?
Only when there's no other option. If you're using WorkManager, JobScheduler, or DownloadManager, the system manages the wake lock on your behalf; if you need a manual PowerManager.WakeLock, choose the lightest possible approach and release the lock as soon as possible.
How do I catch a battery regression before release?
Write performance tests in XCTest using XCTCPUMetric for CPU-heavy pieces of code and wire those tests into your CI pipeline. That way, when a code change significantly increases CPU usage, you'll catch it before it reaches production.
How much does the display affect battery consumption?
The display, especially on OLED panels, varies based on brightness and the color palette shown; giving an exact percentage would be misleading since it depends on the device and the content. The practical rule: the display should be tracked as its own category, on equal footing with CPU/network/location, in Xcode Organizer and Android Studio's Power Profiler; reducing unnecessary redraws and competing animations indirectly lowers consumption in the display category.
Update (September 2026)
The body of this article reflects the tools and API surface as of 2025-01-15. Since then, both platforms have seen significant changes on the measurement and scheduling side:
iOS: With iOS/iPadOS 26, Apple added a new Power Profiler tool to Instruments that can collect traces without requiring a device connection; this is an additional layer on top of the Organizer/Debug Navigator flow described in the body of this article. Xcode Organizer's energy exception reports also gained an AI-assisted triage feature called "Generate Recommendations." Additionally, MetricKit's primary interface moved, starting with iOS 27, to a new API that delivers MetricReport/DiagnosticReport values via async sequences through MetricManager — the "delivered at most once per day" behavior described in the body of this article is conceptually preserved, but the API surface has changed.
Android: JobScheduler quota logic expanded in Android 16: according to Google's Android 16 behavior changes page, regular and expedited job runtime quotas are now adjusted based on three factors — which app standby bucket the app is in (the active bucket now starts getting a generous quota), jobs that start while the app is visible (top state) and continue running after it goes to the background, and jobs running concurrently with a foreground service; all three are new boundaries to consider when planning the setRequiresCharging/setRequiredNetworkType constraints described in this article. A new stop reason (STOP_REASON_TIMEOUT_ABANDONED) and a new debugging API (getPendingJobReasonsHistory()) were also added; setImportantWhileForeground() is no longer honored (deprecated).
Conclusion
Battery consumption optimization in mobile apps should be driven by measurement, not guesswork: on iOS, the Organizer + Debug Navigator + MetricKit trio, and on Android, system tracing + Macrobenchmark power metric + JobScheduler/WorkManager constraints, form the backbone of this measurement (Battery Historian is still usable for your old log files). Batching network requests, lowering location accuracy to match real need, and tying background jobs to a charging/Wi-Fi condition are the three changes with the highest return. To go deeper on background task scheduling, see our WorkManager 2.10 Coroutines guide and our iOS Background Processing article; to harden the network layer, see our Network Layer Optimization guide; for location, see our MapKit and Location Services article; to reduce render cost, see our SwiftUI Performance Optimization guide.
Sources
- Apple — Analyzing your app's battery use — Explanation of the Xcode Organizer Battery Usage panel, energy exception report, and Energy Impact gauge.
- Apple — MetricKit — Daily metric report delivery behavior and the general MetricKit framework.
- Google — Optimize battery use (preserving-battery) — The role of network requests in battery consumption and conditional background work scheduling recommendations.
- Google — Use wakelocks — Wake lock usage rules and WorkManager/JobScheduler's automatic wake lock management.
- Google — Battery Historian — The function of the Battery Historian tool and the typical anti-patterns it detects (the page now states it is no longer maintained and that system tracing/Macrobenchmark power metric/Power Profiler should be preferred).
- Google — Optimize location for battery — Recommendations for removing location updates, adding a timeout, and batch-delivering requests.
- Google — Priority (Play services location) — The
PRIORITY_BALANCED_POWER_ACCURACYconstant and the definition of the location accuracy/battery tradeoff. - Google — Android 16 behavior changes — JobScheduler quota expansion, new stop reason, and debugging API (source for the Update section).
- Apple — Measuring your app's power use with Power Profiler — The Power Profiler tool added with iOS/iPadOS 26 (source for the Update section).
Tags
iOS Development News
Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.
We respect your privacy. You can unsubscribe at any time.

