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.