作为前台服务来运行
服务经常被用来执行后台服务,如获取Mail,同步数据,下载内容等。在这些场景中,用户是不关心服务的执行,甚至是这些服务被中断然后在重新开始也不太关注。
但是在播放音乐的服务的场景中。用户能够清晰的感觉到这个一个服务,并且任何中断都会严重影响用户的体验。另外,该服务在执行期间,用户也希望与它进行交互。在这种情况下,服务应该作为前台服务(foreground service)来运行。前台服务在系统中会持有很高的重要性级别---系统几乎不会杀死这样的服务,因为对用户来说,它是非常重要的。当服务在前台运行时,该服务也必须提供状态栏通知,以确保用户能够感知到服务正在运行,并且允许用户能够打开与服务进行交互的Activity。
为了把服务转换成前台服务,必须给状态栏创建一个Notification,并且从Service中调用startForeground()方法,例如:
String songName;
// assign the song name to songName
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
new Intent(getApplicationContext(), MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification();
notification.tickerText = text;
notification.icon = R.drawable.play0;
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample",
"Playing: " + songName, pi);
startForeground(NOTIFICATION_ID, notification);
当服务在前台运行时,配置的通知会显示在设备的通知区域。如果用户选择了该通知,那么系统就会调用你提供的PendingIntent对象。在上面的例子中,它会打开一个Activity(MainActivity)。
图1显示了如何把通知显示给用户:
图1.前台服务通知的截屏,在状态栏(左侧)上显示一个通知图标,以及通知展开后的视图(右侧)。
应该只有在服务是执行某些用户非常关注的动作时才使用前台服务(foreground service)。一旦不在需要,就应该调用stopForeground()方法释放它:
stopForeground(true);
更多的信息,请看“Services”和“Status Bar Notifications”相关的文档。
http://developer.android.com/guide/components/services.html#Foreground
http://developer.android.com/guide/topics/ui/notifiers/notifications.html
处理音频焦点(Audio Focus)
在任何时候,即使只有一个Activity在运行,Android也是一个多任务的环境。这就给使用音频通道的应用带来一个特殊的竞争,因为只有一个音频输出通道,并且有可能会有几个媒体服务来竞争使用它。在Android2.2之前,没有内置的机制来处理这个问题,这样就导致了很坏的用户体验。例如,当用户正在听音乐,而另一个应用程序需要通知用户一些重要的事情,在音乐声音很大时,用户就可能听不到这个通知提示。从Android2.2开始,平台给应用程序提供了一种方式来协调设备音频输出通道使用,这种机制被叫做Audio Focus。
当应用程序需要输出音频,如音乐或通知时,应该始终要请求音频焦点(Audio Focus)。一旦请求到音频焦点,就可以自由的使用声音输出了,但是要始终监听音频焦点的改变。如果接到了失去音频焦点的通知,就应该立即中断该音频或把它降低到安静的级别(叫做ducking---它有一个标识,用于指示那种模式是合适的),并且在再次收到焦点后能够恢复大声播放。
Audio Focus是协商性的,也就是说,我们期望(并且高度保证)应用程序能够遵守Audio Focus的指南,但是这个规则不是被系统所强制的。如果一个应用程序即使在失去音频焦点时也想要大声的播放,那么系统是不会阻止它的。但是这将给用户带来很坏的体验,以至于卸载这种行为不端的应用程序。
要请求音频焦点,必须调用AudioManager中的requestAudioFocus()方法,实例演示如下:
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// could not get audio focus.
}
传递给requestAudioFocus()的第一个参数是AudioManager.OnAudioFocusChangeListener,它的onAudioFocusChange()方法在音频焦点改变时被调用。因此应该在Service和Activity上也实现这个接口。例如:
class MyService extends Service
implements AudioManager.OnAudioFocusChangeListener {
// ....
public void onAudioFocusChange(int focusChange) {
// Do something based on focus change...
}
}
focusChange参数告你音频焦点时如何改变的,并且能够是下面的值之一(它们是所有的在AudioManager类中定义的常量):
1. AUDIOFOCUS_GAIN:你已经获得音频焦点;
2. AUDIOFOCUS_LOSS:你已经失去音频焦点很长时间了,必须终止所有的音频播放。因为长时间的失去焦点后,不应该在期望有焦点返回,这是一个尽可能清除不用资源的好位置。例如,应该在此时释放MediaPlayer对象;
3. AUDIOFOCUS_LOSS_TRANSIENT:这说明你临时失去了音频焦点,但是在不久就会再返回来。此时,你必须终止所有的音频播放,但是保留你的播放资源,因为可能不久就会返回来。
4. AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:这说明你已经临时失去了音频焦点,但允许你安静的播放音频(低音量),而不是完全的终止音频播放。
以下是一个例子的实现:
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// resume playback
if (mMediaPlayer == null) initMediaPlayer();
else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();
mMediaPlayer.setVolume(1.0f, 1.0f);
break;
case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback and release media player
if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// Lost focus for a short time, but we have to stop
// playback. We don't release the media player because playback
// is likely to resume
if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// Lost focus for a short time, but it's ok to keep playing
// at an attenuated level
if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
break;
}
}
要记住:音频焦点只有在API Level8(Android2.2)以上的版本中才有效,因此如果想要支持Android以前的版本,就应该采用向后兼容的策略,如果要使用这个功能,那么就会丧失向后的兼容性。
你可以通过反射调用音频焦点的方法或通过在一个独立的类中实现所有的音频焦点的功能来达到向后兼容的目的。以下就是这样一个例子:
public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
AudioManager mAudioManager;
// other fields here, you'll probably hold a reference to an interface
// that you can use to communicate the focus changes to your Service
public AudioFocusHelper(Context ctx, /* other arguments here */) {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
// ...
}
public boolean requestFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
}
public boolean abandonFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
mAudioManager.abandonAudioFocus(this);
}
@Override
public void onAudioFocusChange(int focusChange) {
// let your service know about the focus change
}
}
你能够创建一个AudioFocusHelper类的实例来检查系统是否运行在API Level8以上版本,例如:
if (android.os.Build.VERSION.SDK_INT >= 8) {
mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
} else {
mAudioFocusHelper = null;
}
执行清理工作
正如前面提到的,MediaPlayer对象会消耗重要的系统资源,因此应该只是在需要的时候才保留它,并且在播放完成后立即调用release()方法来使释放它。明确的调用这种清理的方法是重要的,而不要依赖系统的垃圾回收,因为垃圾回收MediaPlayer对象之前可能需要一些时间,而且垃圾回收只是针对内存资源的,而对于其他媒体相关的资源却不处理。因此,在这种使用服务的场景中,应该始终重写onDestroy()方法来确保你释放了MediaPlayer对象:
public class MyService extends Service {
MediaPlayer mMediaPlayer;
// ...
,
@Override
public void onDestroy() {
if (mMediaPlayer != null) mMediaPlayer.release();
}
}
除了关机以外,应该始终寻找其他机会来释放MediaPlayer对象。例如,如果你不期望在一个较长的时间段内来播放媒体文件(例如,在失去音频焦点之后)就应该明确的释放这个MediaPlayer对象,并且在后续需要时再创建它。在另一方面,如果只是期望它停止播放很短的时间,那么你就应该持有这个MediaPlayer对象以避免反复的重建这个对象。