Detecting and Fixing Android App Power Drain with BatteryCanary
This article explains how Android apps consume power, details the underlying BatteryStatsService, introduces the BatteryCanary tool for monitoring and diagnosing power‑drain issues, and shares practical optimization cases and benchmark results for developers seeking to reduce app energy usage.
App “Power Drain Syndrome”
When we say an app is power‑hungry, we may refer to high CPU usage, frequent background scans, or device heating; unlike crash or ANR metrics, Android power optimization is a complex, multi‑dimensional problem.
Power‑drain causes vary—from CPU/GPU load, screen, sensors, to hardware components—each requiring different investigation methods, and OEMs provide inconsistent monitoring frameworks, making detection highly project‑specific.
Detecting various power‑drain anomalies and defining clear rules is key to optimizing energy metrics. This article breaks down Android app power issues into three parts: power‑statistics principles, monitoring solutions, and optimization cases.
App Power‑Statistics Principles
Power Calculation Formula
Power = Voltage × Current; in digital devices voltage is constant, so current approximates power, leading to the use of mAh rather than kWh.
Android Hardware Module Power Accounting
App power = Σ(module power × module time). Modules fall into three categories:
Fixed‑power modules (Camera, Flashlight, MediaPlayer, sensors) – calculate by usage duration.
Variable‑power modules (Wi‑Fi, Mobile, Bluetooth) – use tiered power levels similar to electricity billing.
Complex modules (CPU, screen) – CPU power = Σ(core power) + cluster power + chip power; screen power is approximated via WakeLock duration.
All these power and time values are estimates, so calculated energy may differ significantly from real consumption.
Android System Power‑Statistics Service
The BatteryStatsService handles power accounting.
Power profiles from
power_profile.xmldefine nominal power for each hardware module.
Duration tracking via
StopWatchand
SamplingCounter.
Computation via module‑specific
PowerCalculatorimplementations.
Persistent data stored in
batterystats.bin.
Workflow
BatteryStatsService performs duration tracking and power calculation.
Duration Tracking
Each app has a Uid entry; when an app uses a hardware module, the corresponding
StopWatchor
SamplingCounterstarts. For example, a Wi‑Fi scan triggers a chain of calls ending with
BatteryStats#noteWifiScanStartedLocked(uid), and stopping the scan calls
BatteryStats#noteWifiScanStoppedLocked(uid).
Power Calculation
BatteryStatsHelpercombines duration data, power profiles, and module calculators to produce a sorted
BatterySipper[]array of per‑app energy consumption.
<code>public class WifiPowerCalculator extends PowerCalculator {
@Override
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
// ...
app.wifiPowerMah = ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa))
/ (1000*60*60);
}
}
</code>BatteryCanary
BatteryCanary brings the same statistical approach into the app process, providing thread monitoring and system‑service call tracking to detect power‑drain bugs.
Thread Monitoring
Uses
SystemClock.currentThreadTimeMillis()and Linux
/proc/[pid]/stat&
/proc/[pid]/task/[tid]/statto dump thread runtime (utime, stime, etc.). Jiffies (kernel ticks) are converted to milliseconds (≈10 ms per jiffy on typical Android kernels).
<code>cat /proc/<mypid>/task/<tid>/stat
10966 (terycanary.test) S 699 699 0 0 -1 1077952832 6187 0 0 0 22 2 0 0 20 0 17 0 9087400 5414273024 24109 ...
</code>System‑Service Call Monitoring
Implemented via SystemService Hook and ASM instrumentation; Hooking is more compatible across API levels, while ASM offers broader coverage but may miss dynamically loaded components.
Implementation Details
Key challenges include accurate app‑state accounting (foreground/background, charging, screen on/off) and handling long‑running or frequent tasks that keep the CPU busy even when the app is idle.
App State Statistics
Uses an “Event Slice” approach: each state change records a timestamp; during a monitoring window, the proportion of time spent in each state is calculated.
Thread‑Pool Issues
Two solutions for identifying costly tasks:
Wrap each
Runnableto measure Jiffies before and after execution.
Apply an Event‑Slice‑like aggregation to determine which tasks dominate the Jiffies delta within a time window.
Loop‑Related Power Bugs
Common patterns such as infinite
while(true)loops, nested loops, or loops with
sleep()can cause persistent CPU usage and thus high power drain.
<code>// Example of missing exit condition
while (true) {
if (shouldExit()) {
break;
}
}
</code>Using BatteryCanary
BatteryCanary logs lifecycle events under the tag
Matrix.battery.LifeCycleand periodically dumps a power report with thread stack traces for abnormal Jiffies usage.
Typical analysis steps include examining the reporting window, average Jiffies per minute, thread counts, and identifying top‑consuming threads (e.g., a thread stuck in
mg_mgr_poll).
References
Further reading and source code links:
Matrix BatteryCanary: https://github.com/Tencent/matrix
Android power_profile.xml
BatteryStatsHelper source
Facebook Battery‑Metrics project
WeChat Client Technology Team
Official account of the WeChat mobile client development team, sharing development experience, cutting‑edge tech, and little‑known stories across Android, iOS, macOS, Windows Phone, and Windows.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.