Search code examples
androidserviceandroid-lifecycleactivity-lifecyclebattery-saver

Android Service Running only when UI is active


It seems Android in recent version have introduced multiple restrictions on running services in the Background, which of course it good news for battery life of our devices. My goal is to create an app which adheres to this by only running a service as long as the User is interacting with the app (which seems to be what they are aiming for). But it's anything but clear to me how you can implement this properly. My requirements are as follows:

  • The Service should be started as soon as any Activity becomes visible to the user.

  • The Service should stay uninterrupted in the running state while the user is interacting with the app (browsing between activities).

  • When the UI (regardless of what activity was active) is sent to the background, the Service should run for 2-3 seconds and then stop itself. The 2-3 seconds are needed for a clean shutdown of the service.

  • The service can be started via a Push message when the app is in the background (or closed), in order to handle incoming events at any time. The service then registers to the Remote Server, and checks for updates. If there are updates, a notification is given to the user. Then the service once more shuts back down after 2-3 seconds of inactivity.

To me, it seems a Bound Service is what is intended to be used. But it's not clear to me how my requirements will fit with the Bound Service model. Is there anyone who have any experience with this, who can point me in the right direction?

EDIT: The "Service" in this case is a Local, In-Process service, which is not intended to be accessed externally.


Solution

  • (This answer presumes a local, "in-process" Service, which is no doubt what you intend to use.)

    For your use case, you actually use a combination of techniques to keep the Service running.

    Use the "bound Service model" to keep the Service around while any one of your Activities are visible. It's simple enough; call bindService() in Activity.onStart(), and unbindService() in Activity.onStop(). If you're worried about the Service being destroyed in the brief moment of transition between two of your Activities, don't be; Android is smart enough to wait for the lifecycle changes of different application components to "settle" before it decides a Service is unreferenced/unneeded.

    You should use the BIND_AUTO_CREATE flag for all your bindService() calls. Keep in mind that the Service isn't created immediately upon calling bindService(); it takes a few milliseconds, and you must be careful to return control to the framework, by returning from whatever lifecycle method (e.g., onStart()) you are currently in. Only then will you get a call to onServiceConnected().

    You'll need to manually keep track of how many Activities are bound to your Service, in order to determine when to begin your 2-3 second cleanup logic. See this answer for a valid approach. Don't worry about the Service being destroyed synchronously during your last unbindService() call -- just as with bindService(), the actual lifecycle state change is "delayed."

    Now the question is, how do you keep the Service alive for this extra 2-3 seconds, at this point (the point where you've determined the number of open Activities has dropped to 0)? Well, you can simply call startService(). You can even call it from a method in your Service subclass; as long as you have a valid Context available, it doesn't really matter. startService() indicates to the system that you want to keep the Service around, irrespective of how many Activities (or other clients) might be bound to it. In this case, the Service won't be restarted -- it's already running!

    Once your cleanup is complete, you can call stopService() or, better yet, stopSelf(). That effectively cancels the startService() call, and tells the OS, "I'm done". Expect a call to Service.onDestroy() shortly thereafter.

    Keep in mind that one of your Activities might pop up asynchronously, and re-bind to the Service, before cleanup completes. It is an edge case, but one that is easily handled. The Service will only be destroyed when both these conditions are true: 1.) No clients are binding/bound, and 2.) no un-cancelled calls to startService() exist.

    Note that on Oreo and later, the system can be quite aggressive about killing apps with background Services. According to this doc, interacting with the user puts you on the whitelist for "several minutes", so I think 2-3 seconds is going to be OK. Similarly, if you're handling a "high-priority FCM message" (which is what I assume you mean by "push" message), you'll be placed on the whitelist, and allowed another few minutes in which to execute the Service (this time using the startService()/stopSelf() approach).