Battery Historian is Google's web-based tool for visualizing Android battery usage data. The Energy Profiler in Android Studio provides in-session energy estimates. Together they tell you exactly which of your app's activities are consuming battery.
Android Studio Energy Profiler
Access: Run → Profile App → select "Energy" tab.
The Energy Profiler shows:
- CPU usage — correlated with your code activity
- Network activity — radio wake events
- Location usage — GPS/network location requests
- Wake lock state — when your app holds the CPU awake
Reading the profiler:
Timeline ──────────────────────────────────
CPU ░░░░████░░░░░░░████████░░░░░
Network ░░░░████░░░░░░░░░░░░░░░░░░░
Location ░░░░░░░░░░░████████░░░░░░░░
Wake lock ████░░░░░░░░░░░████░░░░░░░
Energy Low────────────High────────
Long Network bars paired with short CPU usage indicates background network polling — expensive and avoidable.
Battery Historian Setup
Battery Historian runs in Docker:
# Install Docker, then:
docker run -d -p 9999:9999 gcr.io/android-battery-historian/stable:3.1 --port 9999
# Or use the web version at: https://bathist.ef.lc/
Capturing Battery Stats
# Step 1: Reset stats to get a clean baseline
adb shell dumpsys batterystats --reset
# Step 2: Disable charging (or use adb to simulate)
adb shell dumpsys battery unplug
# Step 3: Run your app / usage scenario (10–30 minutes)
# Step 4: Export battery report
adb bugreport > /tmp/bugreport.zip
What to Look For in Battery Historian
Wakelock abuse:
KEY NAMES: wakelock_in
Pattern: Frequent short wakelocks = polling loops
Fix: Move to push (FCM) or WorkManager with constraints
Mobile radio activity:
KEY NAMES: mobile_radio, data_conn
Pattern: Frequent ON/OFF toggles = un-batched network
Fix: Batch requests; use OkHttp caching
GPS/location:
KEY NAMES: gps, scan_wifi
Pattern: GPS active when app is background = location leak
Fix: Remove updates in onStop; use geofencing for background
Identifying Battery Drains Programmatically
// Check if your app is in App Standby "Restricted" bucket
val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val isRestricted = usageStatsManager.appStandbyBucket == UsageStatsManager.STANDBY_BUCKET_RESTRICTED
// Check battery optimization exemption status
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
val isIgnoringBatteryOptimization = powerManager.isIgnoringBatteryOptimizations(packageName)
Wakelocks: Audit and Fix
// ❌ PowerManager WakeLock not released — device stays awake
val wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp:sync")
wl.acquire()
// ... work that might throw, leaving wl unreleased
// ✅ Always use try/finally; or use timeout
wl.acquire(60_000) // auto-release after 60 seconds maximum
try {
doSync()
} finally {
if (wl.isHeld) wl.release()
}
Battery Stats adb Shortcut
# Quick wakelocks summary without full bugreport
adb shell dumpsys batterystats | grep "Wakelock"
# Network usage by app
adb shell dumpsys netstats detail
# Top battery consumers
adb shell dumpsys batterystats --charged | grep "Estimated power use"
Common Battery Offenders
| Offender | How to spot | Fix |
|---|---|---|
| Unreleased WakeLock | Battery Historian wakelock row never drops to 0 | acquire(timeout) + finally { release() } |
| Background polling | Rapid network spikes while screen off | Replace with FCM push |
| GPS in background | Location row active when no user visible feature | Remove location updates in onStop |
| Excessive JobScheduler jobs | Many short job events in historian | Batch into fewer, longer jobs |
| Unregistered sensor listener | CPU row consistently active in background | Unregister in onStop |