处理音频焦点
尽管某个时刻只有一个activity可以运行,Android却是一个多任务环境.这对使用音频的应用带来了特殊的挑战,因为只有一个音频输出而可能多个媒体都想用它.在Android2.2之前,没有内建的机制来处理这个问题,所以可能在某些情况下导致坏的用户体验.例如,当一个用户正在听音乐而另一个应用需要通知用户一些重要的事情时,用户可能由于音乐声音大而不能听的通知.从Android2.2开始,平台为应用提供了一个协商它们如何使用设备音频输出的途径,这个机制叫做音频焦点.
当你的应用需要输出像乐音和通知之类的音频时,你应该总是请求音频焦点.一旦应用具有了焦点,它就可以自由的使用音频输出.但它总是应该监听焦点的变化.如果被通知丢失焦点,它应该立即杀死声音或降低到静音水平(有一个标志表明应选择哪一个)并且仅当重新获得焦点后才恢复大声播放.
将来的音频焦点是合作的.所以,应用被希望(并被强列鼓励)遵守音频焦点的方针,但是却不是被系统强制的.如果一个应用在丢失音频焦点后依然想大声播放音乐,系统不会去阻止它.然而用户却体验很坏并且很想把这鸟应用卸载.
要请求音频焦点,你必须从AudioManager调用requestAudioFocus(),如下所示:
[java]
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 不能获得音频焦点
}
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 不能获得音频焦点
}
requestAudioFocus()的第一个参数是一个AudioManager.OnAudioFocusChangeListener,它的onAudioFocusChange()方法在音频焦点发改变时被调用.因此,你也应该在你的service和activity上实现此接口.例如:
[java]
class MyService extends Service
implements AudioManager.OnAudioFocusChangeListener {
// ....
public void onAudioFocusChange(int focusChange) {
// Do something based on focus change...
}
}
class MyService extends Service
implements AudioManager.OnAudioFocusChangeListener {
// ....
public void onAudioFocusChange(int focusChange) {
// Do something based on focus change...
}
}
参数focusChange告诉你音频焦点如何发生了变化,它可以是以上几种值(它们都是定义在AudioManager中的常量):
AUDIOFOCUS_GAIN:你已获得了音频焦点.
AUDIOFOCUS_LOSS:你已经丢失了音频焦点比较长的时间了.你必须停止所有的音频播放.因为预料到你可能很长时间也不能再获音频焦点,所以这里是清理你的资源的好地方.比如,你必须释放MediaPlayer.
AUDIOFOCUS_LOSS_TRANSIENT:你临时性的丢掉了音频焦点,很快就会重新获得.你必须停止所有的音频播放,但是可以保留你的资源,因为你可能很快就能重新获得焦点.
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:你临时性的丢掉了音频焦点,但是你被允许继续以低音量播放,而不是完全停止.
下面是一个例子:
[java]
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;
}
}
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仅在APIlevel 8 (Android2.2)及更高版本上可以,所以如果你想支持更早的Android版本,你必须在可能时采取兼容性的策略使用特性,如果不可能,you should adopt a backward compatibility strategy that allows you touse this feature if available, and fall back seamlessly if not.
你可以用反射的方式调用音频焦点方法或自己在一个单独的类(叫做AudioFocusHelper)中实现所有的音频焦点功能来达到向前兼容.下面是一个这样的类:
[java]
public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
AudioManager mAudioManager;
// 这里是其它的字段,你可能要保存一个接口的引用,这个接口
// 被用于与你的service通讯以报告焦点的变化.
public AudioFocusHelper(Context ctx, ) {
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) {
// 让你的service知道焦点变化了
}
}
你可以仅在检测到系统运行的是API level 8 或更早的版本时才创建AudioFocusHelper类的实例.例如:
if (android.os.Build.VERSION.SDK_INT >= 8) {
mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
} else {
mAudioFocusHelper = null;
}
public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
AudioManager mAudioManager;
// 这里是其它的字段,你可能要保存一个接口的引用,这个接口
// 被用于与你的service通讯以报告焦点的变化.
public AudioFocusHelper(Context ctx, ) {
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) {
// 让你的service知道焦点变化了
}
}
你可以仅在检测到系统运行的是API level 8 或更早的版本时才创建AudioFocusHelper类的实例.例如:
if (android.os.Build.VERSION.SDK_INT >= 8) {
mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
} else {
mAudioFocusHelper = null;
}
清理
前面提到过,一个MediaPlayer对象可以消耗掉大量的系统资源,所以你应该仅在需要它时保持它并在用完时立即释放.明确的调用清理方法而不是依靠系统的垃圾收集机制是很重要的,因为在被收集之前MediaPlayer可能会存在很长时间,虽然此时它只是占用内存而不影响其它的媒体相关的资源.所以,当你使用一个service时,你应该总四重写onDestroy()方法来保证释放MediaPlayer:
[java]
public class MyService extends Service {
MediaPlayer mMediaPlayer;
// ...
@Override
public void onDestroy() {
if (mMediaPlayer != null) mMediaPlayer.release();
}
}
public class MyService extends Service {
MediaPlayer mMediaPlayer;
// ...
@Override
public void onDestroy() {
if (mMediaPlayer != null) mMediaPlayer.release();
}www.2cto.com
}
你也应该寻找其它需要释放你的MediaPlayer的时机.例如,如果你预料到长时间不能播放媒体(比如丢掉音频焦点以后),你应该明确地释放你的MediaPlayer,然后在后面重新创建它.反过来,如果你预测到只会短时间停止播放,你应该保持你的MediaPlayer来避免过多的创建,而不是重新"准备"它.