代码改变世界

Keeping your Android application running when the device wants to sleep (Updated for Android Oreo)

2020-01-21 16:01  Dorisoy  阅读(427)  评论(0编辑  收藏  举报

Updated October 2018 to include detail about Android Oreo.

Since the introduction of doze mode in Android Marshmallow we have seen a number of enterprise use cases where customers want to ensure their application continues to run even though the device wants to enter a power saving mode.  It is assumed the reader is familiar with doze mode and the other changes made in Android Oreo related to background services, at least at a high level.

From a consumer point of view, getting maximum battery life out of a device is frequently an ever-present consideration, so much so that a slew of snake oil “task manager” and “task killer” applications formerly gained popularity to prevent background apps but in recent Android releases Google has taken a more aggressive approach to what apps can do in the background.

Enterprise applications are written primarily to enhance user efficiency; battery consumption will always be a consideration but may be secondary or tertiary to application responsiveness or performance.

The goal of this blog is to explain your options as an application developer to give you the most control over your device’s power management and what you can do to ensure your application is always available to your users.

There are two fundamental reasons people might not want the device to enter a power saving mode:

  • The application should be available to respond to network requests, for example a push message.  Often Firebase Cloud Messaging (FCM) is not a suitable option for customers because the device is behind a firewall or does not have GMS services available.
  • The application needs to do work continually and it is not acceptable for the Android OS to kill the application’s background services.

Before diving into the detail, it is necessary to understand the concepts around Android power management:

Wake Locks

Wake locks are an Android concept designed to allow an application to indicate that it wants to have the device stay on, for example the YouTube application would take a FULL_WAKE_LOCK to prevent the screen turning off whilst the user is watching a video.  A wake lock can control the status of the CPU, Screen and hardware Keyboard backlight but all but one type of wake lock can be cancelled by the end user simply by pressing the power key.  Since only PARTIAL_WAKE_LOCKs persist when the user presses the power key the remainder of this blog will be concerned exclusively with those, where the CPU continues running but the screen and hardware keyboard backlight is allowed to turn off.

Android documentation is available for the wake lock definition as well as methods to acquire and release wake locks.

Any application requiring a wake lock must request the android permission:

<uses-permission android:name="android.permission.WAKE_LOCK" />

You can detect any wake locks being held by applications on your device through adb:

adb shell dumpsys power

The following screenshot shows a single wake lock is on the device, it is a partial wake lock and has been given the tag ‘WakeLockExampleAppTag1’ by the application which created it:

Partial wake locks will be released either when the application which created it calls release(), or the lock was only acquired for a specified time.  Note that wake locks will be automatically released when the device enters doze mode unless battery optimization is disabled for the application which acquired the lock (see later)

Whilst this blog is concerned primarily with Android Marshmallow, you may notice that earlier Zebra devices’ WiFi service gains its own partial wake lock, even when the WiFi policy is set to not "keep WiFi awake when the device is sleeping".  You can see any wake lock using the technique described above and should bear this in mind if wondering why your device is not sleeping when expected on earlier devices e.g. MC40.

WiFi Locks

WiFi locks are only applicable to Android Nougat devices and below (Marshmallow, Lollipop etc).  From Android Oreo onward the WiFi will always be on when the device is sleeping.

WiFi locks allow an application to keep the WLAN radio awake when the user has not used the device in a while, they are frequently used in conjunction with wake locks since any application doing work in the background would likely need WLAN connectivity (e.g. downloading a large file)

Android documentation for the WifiLock is here and the documentation on how to acquire a WiFi lock is here.

As stated in the official docs, WifiLocks cannot override the user-level “Wifi-Enabled” setting, nor Airplane Mode.  For Zebra devices, we can extend this to WifiLocks not being able to override the Wi-Fi Enable / Disable setting of the WiFi CSP

Some other special considerations for Zebra devices:

  • Out of the box, older Zebra devices come pre-loaded with the AppGallery client.  AppGallery will be holding its own WifiLock lock and since the radio is only allowed to turn off when no WifiLocks are held, the radio will not turn off by default (until the device enters doze mode).  AppGallery’s lock is defined as follows:
    • WifiLock{Pushy type=1 binder=android.os.BinderProxy@...}
  • You can disable AppGallery in a number of ways:
    • Using the MX AccessManager whitelist feature
    • Using the MX ApplicationManager to disable the application
    • Using Enterprise Home Screen’s <app_disabled> preference
    • Disabling the application manually under Settings --> Apps --> AppGallery --> App info

Note that the current package name is com.rhomobile.appgallery but that may change in the future.

  • Zebra devices also have an additional “Sleep Policy” parameter as part of the MX Wifi Manager
    • You can set the sleep policy via the Settings UI: Settings --> Wi-Fi --> (Menu) Advanced --> “Keep Wi-Fi on during sleep”.  The values are:
      • Always (the WiFi radio will not turn off when the device sleeps)
      • ‘Only when plugged in’ (the WiFi radio will not turn off provided the device is connected to power)
      • Never (the WiFi radio will be allowed to turn off when the device sleeps)
    • The sleep policy does not hold a separate WiFi lock, it is configuring the WiFi policy and is reflected in the mSleepPolicy value.
    • The default value of the sleep policy will vary from device to device.  As a general rule, devices running Marshmallow or earlier will have this value set to 'Never' and devices running Nougat or later will set this value to 'Always' but there will be exceptions (notably the TC8000, a Lollipop device, defaults to 'Always').
    • The device will only turn WiFi off during standby if there are no WiFi locks and it is allowed to do so according to the WiFi sleep policy
  • When the device receives a network request over WiFi, the device obviously needs to do some processing to handle the message.  Although this requires hardware support from the WiFi stack and processor, recent Zebra devices will be able to wake the processor to perform the required packet handling.  If more than simple processing is required when packets are received it may be prudent to also acquire a wake lock for the duration of that processing.  This is true of all Android Marshmallow or later devices.

You can detect any wifi locks being held by applications on your device through adb:

adb shell dumpsys wifi

The following screenshot shows a single wifi lock is on the device (in blue) and is given the tag ‘WifiLockExampleAppTag1’ by the application which created it.  Highlighted in green is the mSleepPolicy variable which can be used to determine the WiFi sleep policy, here 0 is ‘Never’ keep WiFi on during sleep but a value of 2 would indicate ‘Always’ keep WiFi on during sleep.:

 

Device Configuration

Will Wi-Fi turn off when the device first sleeps?

Will Wi-Fi turn off when the device enters doze mode? *

No WiFi Locks held by any applications

Sleep policy set to ‘Always’ keep wi-fi on.

No, the WiFi radio will remain on when the device sleeps Yes, the WiFi radio will remain off in doze mode.

At least 1 application holds a WiFi lock

Sleep policy set to ‘Always’ keep wi-fi on.
No, the WiFi radio will remain on when the device sleeps Yes, the WiFi radio will be turned off when the device enters doze mode.

No Wi-Fi locks held by any applications

Sleep policy set to ‘Never’ keep wi-fi on
Yes, the WiFi radio will turn off when the device sleeps Yes, the WiFi radio will remain off in doze mode.

At least 1 application holds a WiFi lock

Sleep policy set to ‘Never’ keep wi-fi on.
No, the WiFi radio will remain on when the device sleeps Yes, the WiFi radio will be turned off when the device enters doze mode.

* Note that both wake locks and the “Sleep Policy” will be ignored when the device is in doze mode unless battery optimization is disabled for the application which acquired the lock (see later)

Battery Optimization / Doze Mode

Diving into Google’s documentation for doze mode and app standby, any developer wishing to circumvent doze mode is quickly drawn to application whitelisting.

The terminology can get confusing at times so:

Optimized:

  • Application is not whitelisted
  • Application is optimized
  • Battery is optimized
  • Default state for all applications other than some Google system apps

Not Optimized:

  • Application is whitelisted
  • Application is not optimized
  • Application may drain your device battery more quickly
  • Battery optimizations are being ignored

You can whitelist your application in a few different ways but only the final technique described here does not require user or manual intervention:

1. By requesting the permission

<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

And sending an intent with the following action:

Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS

Doing so will present the following warning dialog to the user:

If the user presses 'YES' then the application will be whitelisted, else if the user presses ‘NO’ there will be no change.

Note that requesting this permission and sending this intent is likely to have your application removed from the Google PlayStore, per the following warning in Android Studio, “Use of REQUEST_IGNORE_BATTERY_OPTIMIZATIONS violates the Play Store Content Policy regarding acceptable use cases, as described in …”.  This is particularly problematic for customers planning to distribute their enterprise applications through Google’s managed play store.

2. By sending an intent with the following action

Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS

Doing so is just a shortcut to Settings --> (menu) Battery optimization where the user can choose to manually whitelist your application by selecting ‘All apps’, finding the application and choosing the appropriate option from the dialog

This is of course error prone and the user could select the incorrect option.  There is an API to read the whitelist state of an application so one option might be to nag the user until they accept.

Since this is accessing the standard Android settings UI then considerations around whether or not the user is allowed access to device settings should also be taken into account.  For example, preventing access to the Settings UI with the MX AccessManager would prevent the user from being able to whitelist the application.

Note that in my testing it is not a good idea to mix the two techniques, I often found the UI displaying optimized applications did not get updated after I performed step 1 even though the app itself was whitelisted.

3. Using ADB during provisioning you can manually edit the whitelist, though obviously your device must first have developer options enabled:

Add your application to the whitelist as follows:

adb shell dumpsys deviceidle whitelist +com.yourcompany.yourapp

And remove your application from the whitelist with the following command:

adb shell dumpsys deviceidle whitelist -com.yourcompany.yourapp

4. Use Zebra MX

Recommended approach

Zebra devices running MX7.0 or higher have access to the BatteryOptimization action added to the App Manager.  “Battery Optimization Remove Apps” and “Battery Optimization Add Apps”  can be invoked to add or remove your application from the whitelist respectively.

Whitelisting an application via EMDK (requires MX 7.0+)

Zebra devices running MX 7.2 or higher have access to the "DozeMode Enable / Disable" action added to the Power Manager.  This is a global setting and setting this parameter to disabled will prevent the device from entering doze mode

Disabling doze mode globally on the device (requires MX 7.2+)

Querying the current whitelist state

Within your application you can query the isIgnoringBatteryOptimizations() method within the PowerManager API to determine whether or not your application is currently whitelisted.   If the API returns true then your application is currently on the whitelist.

The code is called as follows and demonstrates asking the user to whitelist the application if it is not already whitelisted:

PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!pm.isIgnoringBatteryOptimizations(getPackageName())) {     //  Prompt the user to disable battery optimization     Intent intent = new Intent();     intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);     startActivity(intent);   } }

Service Stickiness

An Android service lifecycle introduces the concept of service stickiness, i.e. a service which should remain running should return START_STICKY or START_REDELIVER_INTENT from its onStartCommand().  These were introduced way back in API level 5 and were designed primarily for where the Android OS would kill a service because it was consuming too much memory, later restarting that service.

START_STICKY does not circumvent doze mode or keep any kind of locks, the overlap with use cases being described in this blog comes when considering long running background services. If the application wants to perform a long running background task then it is generally a good idea to return START_STICKY or START_REDELIVER_INTENT from the onStartCommand(); for an IntentService then call setIntentRedelivery(true) in the constructor to achieve the same effect.

Although by following the advice on locks and whitelisting in this blog can prevent your device sleeping, it cannot avoid Android stopping your service for other reasons (e.g. memory pressure) so this case should always be considered.

Additional documentation is available for START_STICKY, START_REDELIVER_INTENT and START_NOT_STICKY within Android Service docs, setIntentRedelivery() is documented under Android IntentService.

Android Oreo background restrictions

Android Oreo introduced a number of background restrictions on what applications can do in the background, specifically services belonging to background applications are no longer allowed to be long running (with some exceptions) and are subject to being stopped if doing work for any length of time.  The developer post on "What's New for Android Oreo and the impact on Zebra developers" goes into a lot more detail on this subject and should be read in conjunction with the formal documentation from Google on the subject.  The behaviour of applications running in the background on Oreo devices will differ from that same application's behaviour running on a Nougat or Marshmallow device.

Putting it all together

  • This post has discussed a number of variables:
    • Android wake locks
    • Android wifi locks
    • Application whitelisting and Doze mode (battery optimization)
  • This post has also considered a couple of use cases:
    • An application wants to listen for incoming network requests and do some work when it gets a request
    • An application wants to perform a long running background service regardless of whether the device wants to sleep.

I have written a test application to test some of the concepts described here: https://github.com/darryncampbell/WakeLock_WifiLock_Exerciser.  Whilst not officially supported by Zebra, The UI should be self-explanatory enough to perform the following operations:

  • On launch, if the application is not whitelisted, ask the user if they want to whitelist it
  • A background service is used to test whether the CPU is active or not:
    • Send an HTTP POST to the specified address on that background service
      • Configure that HTTP post address
    • Emit a beep in that background service
  • A web server within the device is used to test whether the WiFi is alive or not and the device is able to respond to an HTTP GET request from a desktop computer.
    • Initialise that background server on port 9000.
  • Acquire or release a wake lock
  • Acquire or release a wifi lock
  • Show the battery optimization settings UI.  As stated earlier, I have found this technique sometimes unreliable when mixed with the automatic whitelisting request on launch.
  • Use MX to add or remove the application from the battery optimization whitelist

Table of behaviour (Nougat and Marshmallow)

Note: For brevity, the following terms should also be considered synonymous for this table:

  • Acquiring a WiFi lock and setting the sleep policy to ‘Always’ keep wi-fi on.
  • Not acquiring a WiFi lock and setting the sleep policy to ‘Never’ keep wi-fi on.

Testing was performed on a GMS TC51 running Android Marshmallow but should be consistent across all Zebra Android Marshmallow and Nougat devices.

Configuration

Application processing can continue?

Application can respond to network requests

Wake lock: Not acquired

Wifi lock: Not acquired

Application whitelisted: No

No.

Application will stop processing a few seconds after the screen turns off.

No.

Application will stop responding to network requests after a few seconds

Wake lock: Acquired

Wifi lock: Not acquired

Application whitelisted: No

Until doze.

Application will continue processing until doze mode kicks in but will not have WLAN access

No.

Application will stop responding to network requests after a few seconds

Wake lock: Not acquired

Wifi lock: Acquired

Application whitelisted: No

No.

Application will stop processing a few seconds after the screen turns off.

Until doze.

Application will respond to network requests until deep doze mode kicks in at which time the application will stop responding to network requests.

Wake lock: Acquired

Wifi lock: Acquired

Application whitelisted: No

Until doze.

Application will continue processing until deep doze mode kicks on.

Until doze.

Application will respond to network requests until deep doze mode kicks in at which time the application will stop responding to network requests.

Wake lock: Not acquired

Wifi lock: Not acquired

Application whitelisted: Yes

No.

Application will stop processing a few seconds after the screen turns off.

No.

Application will stop responding to network requests after a few seconds

Wake lock: Acquired

Wifi lock: Not acquired

Application whitelisted: Yes

Yes.

Application will continue processing indefinitely but will not have WLAN access

No.

Application will stop responding to network requests after a few seconds

Wake lock: Not acquired

Wifi lock: Acquired

Application whitelisted: Yes

No.

Application will stop processing a few seconds after the screen turns off.

Yes.

Application will continue responding to network requests indefinitely.

Wake lock: Acquired

Wifi lock: Acquired

Application whitelisted: Yes

Yes.

Application will continue processing indefinitely.

Yes.

Application will continue responding to network requests indefinitely.

 

Table of behaviour (Oreo)

This table does not consider the 'Wifi lock' since this is not applicable to Oreo, as stated earlier

Testing was performed on a TC57 GMS device running Android Oreo 8.1, security patch level August 1, 2018 but should be consistent across all Zebra Android Oreo devices

Any specified times should not be considered precise and are only given to provide a rough idea of the time the device will remain in a particular state.

ConfigurationApplication processing can continue?Application can respond to network requests?

Wake lock: Not acquired

Application whitelisted: No

No.

Application will stop processing a few seconds after the screen turns off (Same as M/N behaviour)

For 3-4 minutes, after which time the application no longer responds to network requests.

This differs from the M/N behaviour which lasted until Doze mode (15-20 minutes)

Wake lock: Acquired

Application whitelisted: No

No.

Application will stop processing after 3-4 minutes.

This differs from the M/N behaviour which lasted until Doze mode (15-20 minutes)

For 3-4 minutes, after which time the application no longer responds to network requests.

This differs from the M/N behaviour which lasted until Doze mode (15-20 minutes)

Wake lock: Not acquired

Application whitelisted: Yes

Not continuously.

Application will stop continuous processing about 30 seconds after the screen turns off but will continue to be allowed to process in intermittent windows

(Differs from M/N behaviour - strange result)

Yes.

Application will continue responding to network requests indefinitely.

(Same as M/N behaviour)

Wake lock: Acquired

Application whitelisted: Yes

Yes.

Application will continue processing indefinitely.

(Same as M/N behaviour)

Yes.

Application will continue responding to network requests indefinitely.

(Same as M/N behaviour)

Conclusion: It is still possible for your application to run when the device is in standby

Using Alarms whilst in Doze Mode

This document has so far focused on the ability of an application to do work and respond to network requests whilst the device is in doze mode, an additional limitation of doze mode is that the standard AlarmManager alarms (including setExact() and setWindow()) are deferred to the next maintenance window, i.e. you cannot rely on the alarms firing at the exact required time.  To be clear, disabling battery optimizations for your application will NOT circumvent the alarm restrictions present when the device is in doze mode.

You have a number of options:

  1. Make use of the new APIs introduced in MarshMallow, setAndAllowWhileIdle and setExactAndAllowWhileIdle. These alarms will fire at the desired time even whilst the device is in doze mode but should be used sparingly to avoid excessive battery drain.  The primary restriction of these alarms is the system will throttle how often they are allowed to fire so you may not be able to trigger more than 1 alarm every 9 minutes (or 15 minutes, both values can be found in the documentation).
  2. If you want an alarm to reliably fire more frequently than every 9 minutes with the device in doze mode then you cannot use the methods detailed in 1, above.  It is recommended that the application wanting to trigger frequent alarms not use the alarm API under these circumstances but instead take a wake lock and manage a timer internally in its own thread.
  3. Use the setAlarmClock API which will wake the system from doze mode shortly before the alarm fires.  The down side of this API for many developers will be that Android displays information about the alarm to the user, typically as a small alarm clock icon in the status bar.

For more information, please see the official Google documentation for Doze mode.

Final Thoughts

Whilst Zebra will continue to support and advocate enterprise use cases which depend on the device remaining awake, any application wishing to keep the device awake will face an uphill battle since the industry as a whole, including iOS, are continuing to extend the device battery life by restricting background capabilities of applications. Where possible it is recommended to consider alternatives to keeping the device awake, for example:

  • Use Firebase Cloud Messaging (FCM) to wake the device up to perform work at a specified time.
  • Using Android's Job Scheduler to only perform work when required, in a power efficient way