Battery Consumption Testing for Mobile Applications
App that drains phone battery in half day gets one-star reviews and gets deleted. iOS shows it in Settings under "High Energy". Android 26+ puts it in Doze mode and starts throttling background processes. Problem almost never in one big leak — usually several small: GPS doesn't turn off in background, WebSocket pings every 5 seconds, background Worker runs too often.
iOS: Xcode Energy Organizer and Instruments
Instruments → Energy Log — basic tool. Shows CPU, GPU, network, GPS, and screen activity over time. Each activity spike — energy cost.
Energy Impact in Xcode Debug Navigator: Low / High / Very High — rough real-time estimate. For precise measurements — XCTMetric:
func testBackgroundSyncEnergyImpact() throws {
let metrics: [XCTMetric] = [XCTCPUMetric(), XCTMemoryMetric(), XCTClockMetric()]
let options = XCTMeasureOptions()
options.iterationCount = 5
measure(metrics: metrics, options: options) {
// simulate background sync
let expectation = self.expectation(description: "sync")
BackgroundSyncService.shared.sync {
expectation.fulfill()
}
wait(for: [expectation], timeout: 30)
}
}
XCTCPUMetric + XCTClockMetric give CPU load and real execution time data. If cpuTime grows non-linearly with iteration count — state accumulating somewhere.
Common iOS Issues
CLLocationManager without pausesLocationUpdatesAutomatically = true and without explicit stopUpdatingLocation() on backgrounding continues working and drains battery. Right strategy for most apps — requestWhenInUseAuthorization() instead of requestAlwaysAuthorization(), and switch to startMonitoringSignificantLocationChanges() when precision not critical.
Timer with Timer.scheduledTimer(withTimeInterval: 1.0, ...) on main run loop, forgotten on background — classic issue. Check: all Timers invalidated in applicationDidEnterBackground or in view controller deinit.
Android: Battery Historian and Perfetto
Battery Historian — web tool from Google for bug report analysis:
adb bugreport bugreport.zip
# Open at https://bathist.ef.lc/ or locally via Docker
docker run -d -p 9999:9999 gcr.io/android-battery-historian/stable:3.1 --port 9999
On timeline see: wakelocks (what prevents processor from sleeping), syncs, GPS activity, network requests. WakeLock named myapp:background_sync held 40 out of 60 minutes — red flag.
Check wakelocks via adb:
adb shell dumpsys power | grep -A 3 "Wake Locks:"
adb shell dumpsys battery | grep level
WorkManager and Energy Efficiency
WorkManager is right way for Android background tasks. Wrong configuration kills battery:
// Bad: 15 minutes minimum interval by default
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES).build()
// Better: bind to network connection, battery not low
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.HOURS)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
)
.build()
setRequiresBatteryNotLow(true) — Worker won't run if charge below ~20%. setRequiredNetworkType(NetworkType.CONNECTED) — no attempts when no network.
Perfetto — more detailed tracing for Android. Shows activity at CPU track, network, I/O level. Useful when Battery Historian shows problem but doesn't narrow it to specific code.
Network Operations and Battery
Each radio module rise (WiFi or LTE) is consumption spike. Radio stays active 10–30 seconds after last request (tail energy). Instead of 10 requests for 1 object better 1 request for 10 objects — one rise instead of ten.
For WebSocket: pings every 5 seconds in background is 12 radio rises per minute. Reasonable interval — 30–60 seconds, accounting for NAT timeouts.
What's Included
- Energy profiling on iOS (Instruments Energy Log, XCTMetric)
- Battery Historian analysis for Android (wakelocks, syncs, GPS)
- Background processes check: WorkManager, BGTaskScheduler, push sessions
- Network patterns analysis for excessive requests
- Report with specific findings and code recommendations
Timeline
2–3 days — analyzing existing app, profiling on real devices, report. Cost is calculated individually.







