Dr. Olivier Le Goar -

Problem of interest

Unlike general rules of thumb to avoid battery killers, I am interested in energy smells (a.k.a green bugs) that can be statically checked and, if possible, automatically corrected. Discovering such bugs is quite challenging but they allow continuous code inspection to improve code quality from the energy viewpoint.

Of course, developers are free to code as they please and they often have good reasons for doing so. But the key idea is that if their code does not contain any green bugs, then their code should be labeled "Green" in a automated way.

Catalog of Android-specific energy smells

The majority of bugs comes from the API reference documentation of Android itself. This is an unordered list for which I provide a coarse classification.

Name Technical Description
Optimized API
Fused Location The fused location provider is one of the location APIs in Google Play services which combines signals from GPS, Wi-Fi, and cell networks, as well as accelerometer, gyroscope, magnetometer and other sensors. It is officially recommended to maximize battery life. Thus, developer has to set up Google Play Service in her gradle file with a dependency to, and then to import from instead of the android.location package of the SDK.
Bluetooth Low-Energy In contrast to classic Bluetooth, Bluetooth Low Energy (BLE) is designed to provide significantly lower power consumption. Its purpose is to save energy on both paired devices but very few developers are aware of this alternative API. From the Android client side, it means append android.bluetooth.le.* imports to android.bluetooth.* imports in order to benefits from low-energy features.
Media Leak Creation of a Media Recorder object with new MediaRecorder() is used to record audio and video, while creation of a Media Player object with new MediaPlayer() can be used to control playback of audio/video files and streams. Both classes own a release() method. In addition to unnecessary resources (such as memory and instances of codecs) being held, failure to call this method immediately if a media object is no longer needed may also lead to continuous battery consumption for mobile devices.
Sensor Leak Most Android-powered devices have built-in sensors that measure motion, orientation, and various environmental conditions. In addition to these are the image sensor (a.k.a Camera) and the geopositioning sensor (a.k.a GPS). The common point of all these sensors is that they are expensive while in use. Their common bug is to let the sensor unnecessarily process data when the app enters an idle state, typically when paused or stopped. Consequently, calls must be carefully pairwised: SensorManager#registerListener()/unregisterListener() for regular sensors, Camera#open()/Camera#release() for the camera and LocationManager#requestLocationUpdates()/removeUpdates() for the GPS. Failing to do so can drain the battery in just a few hours.
Everlasting Service If someone calls Context#startService() then the system will retrieve the service (creating it and calling its onCreate() method if needed) and then call its onStartCommand(Intent, int, int) method with the arguments supplied by the client. The service will at this point continue running until Context#stopService() or Service#stopSelf() is called. Failing to call any of these methods can lead to uncontrolled energy leakage.
Internet In The Loop Opening and closing internet connection continuously is extremely battery-inefficient since HTTP exchange is the most consuming operation of the network. This bug typically occurs when one obtain a new HttpURLConnection by calling URL#openConnection() within a loop control structure (while, for, do-while, for-each). Also, this bad practice must be early prevented because it is the root of another evil that consists in polling data at regular intervals, instead of using push notifications to save a lot of battery power.
Wifi Multicast Lock Normally the Wifi stack filters out packets not explicitly addressed to the device. Acquiring a Multicast Lock with WifiManager.MulticastLock#acquire() will cause the stack to receive packets addressed to multicast addresses. Processing these extra packets can cause a noticeable battery drain and must be disabled when not needed with to a call to WifiManager.MulticastLock#release().
Uncompressed Data Transmission Transmitting a file over a network infrastructure without compressing it consumes more energy than with compression. More precisely, energy efficiency is improved in case the data is compressed at least by 10%, transmitted and decompressed at the other network node. From the Android client side, it means making a post HTTP request using a GZIPOutputStream instead of the classical OutputStream, along with the HttpURLConnection object.
Uncached Data Reception Caching all of the application's HTTP responses to the filesystem (application-specific cache directory) so they may be reused, allow to save energy. To that purpose the class HttpResponseCachesupports HttpURLConnection and HttpsURLConnection; there is no platform-provided cache for further clients. Installing the cache at application startup is done with the static method HttpResponseCache.install(File directory, long maxSize).
Dark UI Developers are allowed to apply native themes for their app, or derive new ones throught inheritence. This decision has a significant impact on energy consumption since displaying dark colors is particularly beneficial for mobile devices with (AM)OLED screens. By default Android will set Holo to the Dark theme (parent style Theme.Holo) and hence switching to the light theme (parent style Theme.Holo.Light) within the manifest should be avoided. To a lesser extent, use of bitmap images with too high luminance should be avoided.
In order to support Dark theme (Android 9 and later), you must set your app's theme to inherit from a DayNight theme (parent style Theme.AppCompat.DayNight).
Thrifty Geolocation Location awareness is one of the most popular features used by apps. The first thing is to try to get the best possible provider (LocationManager#getBestProvider()) based on an energy criteria thanks to Criteria#setPowerRequirement(int level), using constant POWER_LOW instead of POWER_HIGH or POWER_MEDIUM. Next, with a call to LocationManager#requestLocationUpdates (long minTime, float minDistance, Criteria criteria, PendingIntent intent), the provider will only send your application an update when the location has changed by at least minDistance meters, AND at least minTime milliseconds have passed. So minTime should be the primary tool to conserving battery life, and, to a lesser extent, minDistance, these two must be imperatively greater than 0.
Thrifty BLE With Bluetooth Low Energy technology (see BLE API bugs), a Bluetooth Smart Ready device (the master) will establish a link with a Bluetooth Smart device (the slave). Most often, the slave is a GATT server and the master is a GATT client. GATT capable devices can be discovered using BLE scan process. So you should always use BluetoothLeScanner#startScan() with the scan settings set to SCAN_MODE_LOW_POWER (Default scan mode). In addition, invoke BluetoothGatt#requestConnectionPriority(int connectionPriority) with the value CONNECTION_PRIORITY_LOW_POWER. Lastly, the default and preferred advertising mode is ADVERTISE_MODE_LOW_POWER when calling AdvertiseSettings.Builder#setAdvertiseMode(int advertiseMode).
Keep CPU On To avoid draining the battery, an Android device that is left idle quickly falls asleep. Hence, keeping the CPU on should be avoided, unless it is absolutely necessary. If so, developers typically use the FLAG_KEEP_SCREEN_ON in their activity. Another way to implement this is in their application's layout XML file, by using the android:keepScreenOn attribute.
Keep Screen On To avoid draining the battery, an Android device that is left idle quickly falls asleep. Hence, keeping the screen on should be avoided, unless it is absolutely necessary. If so, developers typically use a Power Manager system service feature called wake locks by invoking PowerManager.WakeLock#newWakeLock(int levelAndFlags, String tag), along with the specific permission WAKE_LOCK in their manifest.
Durable Wake Lock A wake lock is a mechanism to indicate that your application needs to have the device stay on. The general principle is to obtain a wake lock, acquire it and finally release it. Hence, the challenge here is to release the lock as soon as possible to avoid running down the device's battery excessively. Missing call to PowerManager#release() is a built-in check of Android lint (Wakelock check) but that does not prevent abuse of the lock over too long a period of time. This can be avoided by a call to PowerManager#acquire(long timeout) instead of PowerManager#acquire(), because the lock will be released for sure after the given timeout expires.
Rigid Alarm Applications are strongly discouraged from using exact alarms unnecessarily as they reduce the OS's ability to minimize battery use (i.e. Doze Mode). For most apps prior to API 19, setInexactRepeating() is preferable over setRepeating(). When you use this method, Android synchronizes multiple inexact repeating alarms and fires them at the same time, thus reducing the battery drain. Similarly, setExact() and setExactAndAllowWhileIdle() can significantly impact the power use of the device when idle, so they should be used with care. High-frequency alarms are also bad for battery life but this is already checked by Android lint (ShortAlarm built-in check).
Continous Rendering For developers wishing to display OpenGL rendering, when choosing the rendering mode with GLSurfaceView#setRenderMode(int renderMode), using RENDERMODE_WHEN_DIRTY instead of RENDERMODE_CONTINUOUSLY (By default) can improve battery life and overall system performance by allowing the GPU and CPU to idle when the view does not need to be updated.
Keep Voice Awake During a voice interaction session, VoiceInteractionSession#setKeepAwake(boolean keepAwake) allows to decide whether it will keep the device awake while it is running a voice activity. By default, the system holds a wake lock for it while in this state, so that it can work even if the screen is off. Setting this to false removes that wake lock, allowing the CPU to go to sleep and hence does not let this continue to drain the battery.
Ignore Battery Optimizations An app holding the REQUEST_IGNORE_BATTERY_OPTIMIZATIONS ask the user to allow it to ignore battery optimizations (that is, put them on the whitelist of apps). Most applications should not use this; there are many facilities provided by the platform for applications to operate correctly in the various power saving modes.
Companion in background A negative effect on the device's battery is when an app is paired with a companion device (over Bluetooth, BLE, or Wi-Fi) and that it has been excluded from battery optimizations (run in the background) using the declaration Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND.
Power Awareness It's always good that an app has different behavior when device is connected/disconnected to a power station, or has different battery levels. Therefore, their absence in the code gives hints on the fact that the app is not energy optimized. The first situation requires a broadcast receiver registered on the actions ACTION_POWER_CONNECTED, ACTION_POWER_DISCONNECTED or just ACTION_POWER_SAVE_MODE_CHANGED. The second situation simply requires calls to PowerManager#isPowerSaveMode().
Service@Boot-time Services are long-living operations, as components of the apps. However, they can be started in isolation each time the device is next started, without the user's acknowledgement. This technique should be discouraged because the accumulation of these silent services results in excessive battery depletion that remains unexplained from the end-user's point of view. In addition, end-users know how to kill applications, but more rarely how to kill services. Thus, any developer should avoid having a call to Context#startService() from a Broadcast Receiver component that has specified an intent-filter for the BOOT_COMPLETED action in the manifest.
Sensor Coalesce With SensorManager#registerListener(SensorEventListener, Sensor, int) the events are delivered as soon as possible. Instead, SensorManager#registerListener(SensorEventListener, Sensor, int, int maxReportLatencyUs) allows events to stay temporarily in the hardware FIFO (queue) before being delivered. The events can be stored in the hardware FIFO up to maxReportLatencyUs microseconds. Once one of the events in the FIFO needs to be reported, all of the events in the FIFO are reported sequentially. Setting maxReportLatencyUs to a positive value allows to reduce the number of interrupts the AP (Application Processor) receives, hence reducing power consumption, as the AP can switch to a lower power state while the sensor is capturing the data.
Job Coalesce The Android 5.0 Lollipop (API 21) release introduces a job scheduler API via the JobScheduler class. Compared to a custom Sync Adapter or the alarm manager, the Job Scheduler supports batch scheduling of jobs. The Android system can combine jobs so that battery consumption is reduced. It means that it exist at least one job sheduled through a call to the method JobScheduler#shedule(JobInfo job).
Disable Obfuscation The Proguard tool secure the app for production, including shrinking, code optimization and obfuscation. However, obfucated code will have a sligthly negative impact on power consumption. The good news is, Proguard has the setting -dontobfuscate that you could set in your file. Using that you will get your code optimized but not obfuscated. Then, it could be set in your debug buildTypes > release section of your build.gradle.

Green Linter Tool

As a proof of concept, I have implemented a subset of the aforementionned bugs in Android lint.


Getting started

How to cite this work?

Olivier Le Goaer (2019), "Enforcing Green Code With Android Lint", In proceedings of the 2nd International Workshop on Advances in Mobile App Analysis @ ASE -- To appear