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.
MainActivity.java
uses an
Intent
to start a
Service
if the
Service
is not already running.
We saw an
Intent
in
MediaPlayerService.
MediaPlayerService.java
activity_main.xml
strings.xml
musette.mid.
Bach Musette in D major, BWV Anh. 126.
AndroidManifest.xml.
When we created class
MediaPlayerService,
Android Studio added a
<service>
element to the
<application>
element.
build.gradle
(Module: app)
App → Service → Local Service Binding
See
Local
Service Sample.
platform_development/samples/ApiDemos/src/com/example/android/apis/app/LocalServiceActivities.java
defines class
LocalServiceActivities.Binding
(lines 82–170).
platform_development/samples/ApiDemos/src/com/example/android/apis/app/LocalService.java
defines class
LocalService.
platform_development/samples/ApiDemos/res/layout/local_service_binding.xml
platform_development/samples/ApiDemos/res/values/strings.xml
local_service_started
(line 244)
is displayed in the status bar by the
showNotification
method of
LocalService.
local_service_stopped
(line 245)
is displayed in a
Toast
by the
onDestroy
method of
LocalService.
activity_local_service_binding
(line 254)
local_service_binding
(line 255)
bind_service
(line 258)
unbind_service
(line 259)
local_service_connected
(line 260)
is displayed (except for the last word)
in a
Toast
by the
onServiceConnected
method of the
ServiceConnection.
local_service_disconnected
(line 261)
is displayed by the
onServiceDisconnected
method of the
ServiceConnection.
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.
platform_development/samples/ApiDemos/AndroidManifest.xml
declares class
LocalServiceActivities.Binding
in
lines 532–538.
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.