On Android Services

For the latest release of SharePlay, my media player for network shares, I wanted to get rid of a fairly annoying bug that I was encountering more and more whenever I was using the app for longer periods of time: sometimes, after playing a number of songs, playback would just stop without any indication of errors in the log.

The app itself consists of an activity which starts a service containing an instance of the MediaPlayer class. The activity submits lists of songs to that service, and controls it to start and stop playback. The service posts notification to the status window using the NotificationManager, so that the user can see what song is currently playing, and also return to the activity while in another activity.

In trying to reproduce the issues, I soon found that once the activity gets released by Android’s ActivityManager, the service is killed as well. This can happen in a number of situations, for example when you open up a resource heavy website which contains flash elements or large images. All of this is in accord with the Android process lifecycle, which aims to free unused resources whenever necessary to provide the user with the best overall experience possible. Or, in more succinct terms: “A background activity (an activity that is not visible to the user and has been paused) is no longer critical, so the system may safely kill its process to reclaim memory for other foreground or visible processes.”

My initial solution for this was to move my service out into a separate process, thinking that would allow Android to get rid of the resource-heavy activity, but to continue to play the audio files. By default, all activities of your app run within a single process, but it is possible to spawn specific parts out into their own process. The way to do this is to add the android:process attribute to the service description in the manifest file:

<service android:name=".MyService" android:enabled="true" android:process=":mediaplayer">

Note the “:” in front of the process name – in this case, the service will run in a private process only accessible to your activity. If you omit that character, the process will be accessible by all other apps in the system.

This solution worked fine for a while, yet the playback would just still stop sometimes. The tricky part was reproducing it, as it happened at random points, without any user interaction. Also, the process for the service continued to run, it was just that the media player no longer produced any audible output.

After adding some more logging to my service, I noticed that after playback stopped, the ActivityManager killed and recreated my service. As it turns out, the Android OS does a lot under the covers if you look at the DDMS logs, so there are plenty of scenarios in which this could happen, but again it was related to resource shortages. Aside from the fact that playback was interrupted, what I found problematic with this was, that the onDestroy method was never called in these situations. Therefore, I had no chance to clean up and remove the notifications I had added to the status bar.

Finally, after further research, I came upon the solution to my issue: it turns out, it is not necessary to spawn another process, so the manifest entry could be safely removed. What I needed to do was to indicate to the ActivityManager that despite my service running in the background, it was doing something that the user wanted, so that it would not be killed. The way to do this is to call startForeground whenever the service plays a song. This will both keep the required resources allocated, and add a notification icon to the status bar. Once playback is finished, I call the stopForeground method, which then marks the service as ready to be released to the OS.

To sum it all up: know what your service is intended to do, and how long it is supposed to be run. Once that is clear, pick one of the following mechanisms:

  1. Background service
    Started from an activity to perform some task using the bind / unbind methods. It will stay alive until unbind is called, or its parent activity is destroyed to allocate more resource.
  2. Foreground service
    A service which performs a task the user wants to continue in spite of it not being visible. Create as usual, but make sure to call startForeground / stopForeground when applicable.
  3. Scheduled service
    A service that is called at specified intervals without user interaction using the AlarmManager service. The OS can release it in between these calls, and will recreate it as it is needed.
 

Author: Ben

Leave a Reply

Your email address will not be published. Required fields are marked *