A Bound Service

The app’s Activity automatically starts a Service that plays background music. The Service automatically destroys itself when it reaches the end of the audio file. You can raise and lower the volume with the SeekBar, and destroy and re-create the Activity by changing the orientation of the device.

A MediaPlayer has setVolume but no getVolume, so I had to write my own getVolume method in class MediaPlayerServce. The Service must not attempt to read and write the SeekBar, because the SeekBar may no longer exist. The SeekBar will live only as long as the Activity that created it, and the Service may already have outlived that Activity.

In addition to starting the Service, the Activity also binds itself to the Service. This means that they share (i.e., they both have references to) a little object, in this case the MediaPlayerBinder object in the Service. If we wanted to, we could have loaded up this object with fields (e.g., a volume field) that would then be accessible to the Service and to the Activity. Instead, the MediaPlayerBinder has a getService method that simply returns the entire Service. The Activity calls this getService and uses the return value to call the methods of the Service.

Where does the shared MediaPlayerBinder object come from? The Activity calls its own bindService method and passes it a ServiceConnection object (I wish they had named it ServiceConnectionListener) with two methods named onServiceConnected and onServiceDisconnected. The call to bindService stimulates the Service to create the MediaPlayerBinder object. The MediaPlayerBinder is passed to the onServiceConnected method of the Activity’s ServiceConnection.

Source code in Binder.zip

  1. MainActivity.java uses an Intent to start a Service if the Service is not already running. We saw an Intent in MediaPlayerService.
  2. MediaPlayerService.java
  3. activity_main.xml
  4. strings.xml
  5. musette.mid. Bach Musette in D major, BWV Anh. 126.
  6. AndroidManifest.xml. When we created class MediaPlayerService, Android Studio added a <service> element to the <application> element.
  7. build.gradle (Module: app)

Binder in ApiDemos

App → Service → Local Service Binding
See Local Service Sample.

  1. platform_development/samples/ApiDemos/src/com/example/android/apis/app/LocalServiceActivities.java defines class LocalServiceActivities.Binding (lines 82–170).

  2. platform_development/samples/ApiDemos/src/com/example/android/apis/app/LocalService.java defines class LocalService.

  3. platform_development/samples/ApiDemos/res/layout/local_service_binding.xml

  4. platform_development/samples/ApiDemos/res/values/strings.xml
    1. local_service_started (line 244) is displayed in the status bar by the showNotification method of LocalService.
    2. local_service_stopped (line 245) is displayed in a Toast by the onDestroy method of LocalService.
    3. activity_local_service_binding (line 254)
    4. local_service_binding (line 255)
    5. bind_service (line 258)
    6. unbind_service (line 259)
    7. local_service_connected (line 260) is displayed (except for the last word) in a Toast by the onServiceConnected method of the ServiceConnection.
    8. local_service_disconnected (line 261) is displayed by the onServiceDisconnected method of the ServiceConnection.

  5. stat_sample.png is displayed by the showNotification method of LocalService. It’s invisible against the black status bar, but it shoves the other icons to the right.
    1. platform_development/samples/ApiDemos/res/drawable-mdpi/stat_sample.png
    2. platform_development/samples/ApiDemos/res/drawable-hdpi/stat_sample.png

  6. platform_development/samples/ApiDemos/AndroidManifest.xml declares class LocalServiceActivities.Binding in lines 532–538.

Things to try

  1. Suppose the MediaPlayerService changed its own volume, e.g., during a AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK. We’d like the MediaPlayerService to move the SeekBar to a new position.

    Add the following interface to class MediaPlayerService. (Class MediaPlayerService already contains class MediaPlayerBinder.)

    public interface OnVolumeChangedListener {
        public void onVolumeChanged(float newVolume);
    }
    
    Class MainActivity should implement interface MediaPlayerService.OnVolumeChangedListener with the following method.
        @Override
        public void onVolumeChanged(float newVolume) {
            seekBar.setProgress((int)(newVolume * seekBar.getMax()));
        }
    
    Give class MediaPlayerService the following field and a setter for it.
        private OnVolumeChangedListener onVolumeChangedListener;
    
        public void setOnVolumeChangedListener(OnVolumeChangedListener onVolumeChangedListener) {
            this.onVolumeChangedListener = onVolumeChangedListener;
        }
    
    At the end of onServiceConnected,
                mediaPlayerService.setOnVolumeChangedListener(MainActivity.this);
    
    In onServiceDisconnected and onStop, insert
            mediaPlayerService.setOnVolumeChangedListener(null);
    
    immediately before the mediaPlayerService = null;. Finally, insert
                    if (onVolumeChangedListener != null) {
                        onVolumeChangedListener.onVolumeChanged(volume);
                    }
    
    at every place in MediaPlayerService where MediaPlayerService changes the volume. There currently is no such place. To introduce such a place, so we could test out our OnVolumeChangedListener, we should set up an AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK. But we’ll just add the following statements to onCompletion.
                    volume = .25f;
                    if (onVolumeChangedListener != null) {
                        onVolumeChangedListener.onVolumeChanged(volume);
                    }
    
    After all the listeners we have created and attached to objects, it’s exciting that we’ve finally created our own class of listener.