通过一个音乐会指挥的故事,为你详细解释四种音频焦点的使用规则和释放机制。想象你是一位交响乐团的指挥(Android系统),需要协调不同乐手(应用)的演奏时间。
故事背景:数字音乐会
- 🎻 应用A:首席小提琴手(主要音频应用)
- 🎺 应用B:小号手(其他音频应用)
- 🎼 指挥家:Android音频系统
- 🎵 乐谱:音频焦点规则
第一章:四种焦点类型详解
1. AUDIOFOCUS_GAIN(长期焦点) - 首席小提琴手的独奏
// 应用A请求长期焦点
int result = audioManager.requestAudioFocus(
focusChangeListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 开始播放音乐
musicPlayer.start();
}
指挥规则:
- 应用A获得永久性焦点,直到主动释放
- 其他应用必须完全停止播放(如音乐暂停)
- 需要手动释放:当应用A完成播放时
// 应用A播放结束时释放焦点
audioManager.abandonAudioFocus(focusChangeListener);
焦点释放后:
- 系统会自动通知之前失去焦点的应用
- 应用B不需要重新申请焦点,系统会自动恢复其播放
- 应用B会收到
onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN)回调
2. AUDIOFOCUS_GAIN_TRANSIENT(短暂焦点) - 小号手的独奏片段
// 应用A请求短暂焦点(导航提示)
int result = audioManager.requestAudioFocus(
focusChangeListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 播放导航提示
navigationPlayer.play();
}
指挥规则:
- 应用A获得临时独占焦点
- 其他应用必须暂停播放(如音乐暂停)
- 需要手动释放:在提示音结束后立即释放
// 导航提示播放完成后释放
navigationPlayer.setOnCompletionListener {
audioManager.abandonAudioFocus(focusChangeListener);
}
焦点释放后:
- 系统会自动恢复之前暂停的应用
- 应用B会收到
onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN)回调 - 应用B不需要重新申请焦点,可直接恢复播放
3. AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK(短暂焦点,允许降低音量) - 轻柔的背景提示
// 应用A请求可降低音量的焦点(通知音)
int result = audioManager.requestAudioFocus(
focusChangeListener,
AudioManager.STREAM_NOTIFICATION,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 播放通知音
notificationPlayer.play();
}
指挥规则:
- 应用A获得临时非独占焦点
- 其他应用可以降低音量继续播放
- 需要手动释放:在提示音结束后立即释放
// 通知音播放完成后释放
notificationPlayer.setOnCompletionListener {
audioManager.abandonAudioFocus(focusChangeListener);
}
焦点释放后:
- 系统会自动通知之前降低音量的应用
- 应用B会收到
onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN)回调 - 应用B不需要重新申请焦点,会自动恢复原音量
4. AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE(短暂独占焦点) - 重要独奏
// 应用A请求独占焦点(语音通话)
int result = audioManager.requestAudioFocus(
focusChangeListener,
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 开始语音通话
voiceCall.start();
}
指挥规则:
- 应用A获得完全独占焦点
- 其他应用必须立即停止所有音频
- 需要手动释放:在通话结束后释放
// 通话结束时释放焦点
voiceCall.setOnCallEndedListener {
audioManager.abandonAudioFocus(focusChangeListener);
}
焦点释放后:
- 系统会自动恢复之前停止的应用
- 应用B会收到
onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN)回调 - 应用B不需要重新申请焦点,可直接恢复播放
第二章:焦点管理总结表
| 焦点类型 | 是否自动释放 | 需要手动释放 | 释放后应用B行为 | 应用B是否需要重新申请 |
|---|---|---|---|---|
| AUDIOFOCUS_GAIN | ❌ | ✅ | 自动恢复播放 | ❌ |
| AUDIOFOCUS_GAIN_TRANSIENT | ❌ | ✅ | 自动恢复播放 | ❌ |
| AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK | ❌ | ✅ | 自动恢复音量 | ❌ |
| AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE | ❌ | ✅ | 自动恢复播放 | ❌ |
第三章:焦点释放的黄金法则
1. 所有焦点都需要手动释放
// 释放音频焦点的正确方式
public void releaseAudioFocus() {
if (hasAudioFocus) {
audioManager.abandonAudioFocus(focusChangeListener);
hasAudioFocus = false;
}
}
2. 释放时机
- 播放完成时
- 用户主动停止时
- Activity/Fragment销毁时
- 服务停止时
3. 应用B如何优雅处理焦点恢复
private AudioManager.OnAudioFocusChangeListener focusListener =
new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS:
// 永久失去焦点
stopPlayback();
releaseResources();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// 短暂失去焦点(暂停)
pausePlayback();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 允许降低音量
lowerVolume(0.3f);
break;
case AudioManager.AUDIOFOCUS_GAIN:
// 焦点恢复(自动触发)
if (wasPlayingBeforeLoss) {
resumePlayback();
}
restoreVolume();
break;
}
}
};
第四章:应用B主动获取焦点的场景
虽然大多数情况下系统会自动恢复焦点,但在某些特殊场景下应用B需要重新申请:
场景1:应用B首次启动
// 应用B启动时请求焦点
int result = audioManager.requestAudioFocus(
focusListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
startPlayback();
}
场景2:应用B被永久拒绝后
// 在永久失去焦点后重新请求
public void onAudioFocusChange(int focusChange) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
// 保存状态
wasPlaying = isPlaying();
stopPlayback();
// 稍后尝试重新请求
new Handler().postDelayed(() -> {
int result = audioManager.requestAudioFocus(
focusListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
resumePlayback();
}
}, 5000); // 5秒后重试
}
}
场景3:多应用协作场景
// 在会议应用中的特殊处理
public void joinConference() {
// 请求特殊焦点类型
int result = audioManager.requestAudioFocus(
focusListener,
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
startConference();
}
}
第五章:完整的最佳实践示例
应用A:音乐播放器
public class MusicPlayer implements AudioManager.OnAudioFocusChangeListener {
private AudioManager audioManager;
private boolean playAfterFocusGain = false;
public void start() {
audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(
this,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
playMusic();
}
}
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS:
stopMusic();
abandonFocus();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
playAfterFocusGain = isPlaying();
pauseMusic();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
lowerVolume(0.3f);
break;
case AudioManager.AUDIOFOCUS_GAIN:
if (playAfterFocusGain) {
playMusic();
playAfterFocusGain = false;
}
restoreVolume();
break;
}
}
public void stop() {
stopMusic();
abandonFocus();
}
private void abandonFocus() {
audioManager.abandonAudioFocus(this);
}
}
应用B:语音助手
public class VoiceAssistant implements AudioManager.OnAudioFocusChangeListener {
public void speakAnnouncement(String message) {
int result = audioManager.requestAudioFocus(
this,
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
tts.speak(message, TextToSpeech.QUEUE_FLUSH, null, "announcement");
// 设置释放焦点的监听器
tts.setOnUtteranceCompletedListener(id -> {
if ("announcement".equals(id)) {
audioManager.abandonAudioFocus(this);
}
});
}
}
@Override
public void onAudioFocusChange(int focusChange) {
// 处理其他应用请求焦点的情况
}
}
第六章:高级焦点管理技巧
1. 焦点请求队列
// 实现焦点请求队列
private Queue<Runnable> pendingFocusRequests = new LinkedList<>();
public void requestFocusWithQueue(int focusType) {
if (hasAudioFocus) {
abandonFocus(() -> {
requestFocus(focusType);
});
} else {
requestFocus(focusType);
}
}
private void abandonFocus(Runnable callback) {
audioManager.abandonAudioFocus(new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
callback.run();
}
}
});
}
2. 焦点自动超时
// 设置焦点超时自动释放
public void requestTransientFocus(long timeoutMs) {
int result = audioManager.requestAudioFocus(
focusListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
new Handler().postDelayed(() -> {
audioManager.abandonAudioFocus(focusListener);
}, timeoutMs);
}
}
3. 焦点冲突解决策略
// 处理多个应用同时请求焦点
public int requestAudioFocusWithPriority(int focusType, int priority) {
if (currentPriority > priority) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
int result = audioManager.requestAudioFocus(focusListener, stream, focusType);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
currentPriority = priority;
}
return result;
}
总结:指挥家的黄金法则
-
所有焦点都需要手动释放:
- 使用
abandonAudioFocus()释放不再需要的焦点 - 就像指挥家需要明确指示乐手停止演奏
- 使用
-
释放后系统自动恢复:
- 应用B不需要重新申请焦点
- 系统会自动发送
AUDIOFOCUS_GAIN回调 - 类似指挥家自动示意下一个乐手开始演奏
-
特殊场景需要重新申请:
- 应用首次启动
- 被永久拒绝后
- 需要改变焦点类型时
记住指挥家的核心原则:
"有借有还,再借不难。获得焦点必释放,系统调度有保障。自动恢复是常态,特殊场景需重来。"
通过正确管理音频焦点,你的应用将成为Android音频交响乐团中和谐的一员!