A systematic exercise to identify, measure, and eliminate background battery drains — targeting a 25% reduction in background energy consumption.
Setup: Baseline Measurement
# 1. Charge device to 100%
# 2. Disconnect charger via adb (simulates unplugged)
adb shell dumpsys battery unplug
# 3. Reset battery stats
adb shell dumpsys batterystats --reset
# 4. Launch app, use it normally for 5 minutes
# 5. Background the app — leave it for 20 minutes
# 6. Capture bugreport
adb bugreport > ~/Desktop/before_optimization.zip
# 7. Reconnect charger
adb shell dumpsys battery reset
Open before_optimization.zip in Battery Historian. Note:
- Total battery consumed during background period
- Top 3 sources: wakelock / network / location
Step 1: Audit Active Background Services
adb shell dumpsys activity services com.example.myapp
For each service found running in background:
- Is it a ForegroundService? (check for notification)
- Is it doing network work? (check against historian)
- Can it be replaced with WorkManager?
// Replace background Service doing periodic network sync:
// ❌ Service with a Handler loop
class SyncService : Service() {
private val handler = Handler(Looper.getMainLooper())
override fun onStartCommand(...) {
handler.postDelayed({ sync(); onStartCommand(...) }, 30_000)
return START_STICKY
}
}
// ✅ WorkManager with CONNECTED constraint
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES)
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork("sync", ExistingPeriodicWorkPolicy.KEEP, syncRequest)
Step 2: Audit WakeLocks
# Find wakelocks held by your app
adb shell dumpsys power | grep -A5 "com.example.myapp"
In Battery Historian: look for your package in the "Wakelock in" row. Any wakelock held for > 5 seconds during background is suspicious.
// Find leaked wakelock: search codebase for:
// powerManager.newWakeLock
// Ensure every acquire has a paired release in finally
// Audit checklist:
// [ ] All WakeLocks use acquire(timeoutMs) as a safety net
// [ ] All WakeLock.release() calls are in finally blocks
// [ ] No WakeLocks held across config changes (they leak with the Activity)
Step 3: Audit Network Activity
# Network usage summary
adb shell dumpsys netstats detail | grep "com.example.myapp"
In Battery Historian: look for the mobile_radio row during your background period. Each spike is a radio wake event.
For each spike:
- Correlate with your WorkManager/JobScheduler schedule
- If more frequent than scheduled → un-batched network calls
- Fix: ensure all background network goes through WorkManager, not direct
AsyncTask/Thread
// ❌ Unscheduled background network in BroadcastReceiver
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Thread { api.sync() }.start() // immediate network — no batching
}
}
// ✅ Schedule via WorkManager — respects Doze, batching
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
WorkManager.getInstance(context).enqueue(
OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build()
)
}
}
Step 4: Audit Location Usage
# Location access by app
adb shell dumpsys location | grep "com.example.myapp"
// Common location leak: requesting updates in onStart, forgetting to stop in onStop
class MapActivity : AppCompatActivity() {
override fun onStart() {
super.onStart()
locationManager.requestLocationUpdates(provider, 1000, 10f, listener)
}
// ❌ Missing: onStop doesn't remove updates
// ✅ Fix:
override fun onStop() {
super.onStop()
locationManager.removeUpdates(listener)
}
}
Step 5: After Optimization — Re-measure
adb shell dumpsys battery unplug
adb shell dumpsys batterystats --reset
# Background app for same 20 minutes
adb bugreport > ~/Desktop/after_optimization.zip
adb shell dumpsys battery reset
Compare:
- Wakelock hold time: before vs after
- Radio wake count: before vs after
- Background CPU time: before vs after
Expected Results
| Optimization | Typical battery saving |
|---|---|
| Replace background Service with WorkManager | 5–10% |
| Fix leaked WakeLocks | 2–5% |
| Batch network (5 calls → 1 batch) | 5–8% |
| Remove background location leak | 10–15% |
| Total | 22–38% |
Verify with Android Vitals
After shipping the fix, check Google Play Console → Android Vitals → Battery. The "Background Battery" metric should improve in the P90 cohort within 2–3 weeks of the update reaching users.