让我们用一场更精彩的"派对麦克风争夺战"故事,结合代码示例,彻底讲明白不同焦点申请对音乐播放器的影响!
🎪 派对场景设定
- 派对主持人:Android 音频系统 (AudioManager)
- 当前表演者:AppB(音乐播放器,正在放歌)
- 新表演者:AppA(想打断音乐)
- 关键道具:唯一的麦克风(音频输出设备)
🚦 四种打断场景分析
1️⃣ 场景:短暂打断 (AUDIOFOCUS_GAIN_TRANSIENT)
📣 故事:AppA 是导航应用,要播报"前方右转"(短暂提示)
💻 代码:java
audioManager.requestAudioFocus( focusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT // 短暂焦点 );🎧 AppB 的回调:
AUDIOFOCUS_LOSS_TRANSIENT
🎯 行为:音乐完全暂停(就像全场安静听导航提示)
2️⃣ 场景:VIP 独占打断 (AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
📣 故事:AppA 是电话应用,有来电铃声(需要绝对安静)
💻 代码:java
audioManager.requestAudioFocus( focusListener, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE // 独占焦点 );🎧 AppB 的回调:
AUDIOFOCUS_LOSS_TRANSIENT⚠️
🎯 行为:音乐立即静音(系统强制静音,连呼吸声都听不到)
3️⃣ 场景:温和打断 (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
📣 故事:AppA 是语音助手,说"明天会下雨"(允许背景音乐)
💻 代码:java
audioManager.requestAudioFocus( focusListener, AudioManager.STREAM_SYSTEM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK // 允许压低音量 );🎧 AppB 的回调:
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
🎯 行为:音乐音量降低(就像调酒师调酒时背景音乐变小)
4️⃣ 场景:永久接管 (AUDIOFOCUS_GAIN)
📣 故事:AppA 是视频应用,开始播放电影(长期占用)
💻 代码:java
audioManager.requestAudioFocus( focusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN // 永久焦点 );🎧 AppB 的回调:
AUDIOFOCUS_LOSS
🎯 行为:音乐完全停止(就像派对结束清场)
📜 回调处理最佳实践(AppB 的代码)
java
public class MusicPlayer implements AudioManager.OnAudioFocusChangeListener {
// 音频焦点变化回调(核心!)
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
// 场景1&2:短暂打断或VIP独占
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
pausePlayback(); // 暂停音乐
break;
// 场景3:允许压低音量
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
lowerVolume(0.3f); // 音量降到30%
break;
// 场景4:永久失去焦点
case AudioManager.AUDIOFOCUS_LOSS:
stopPlayback(); // 停止播放
abandonAudioFocus(); // 放弃焦点
break;
// 焦点重新获得
case AudioManager.AUDIOFOCUS_GAIN:
restorePlayback(); // 恢复播放
restoreVolume(); // 恢复音量
break;
}
}
// 示例辅助方法
private void pausePlayback() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
isPausedByFocusLoss = true; // 标记因焦点暂停
}
}
private void lowerVolume(float ratio) {
mediaPlayer.setVolume(ratio, ratio); // 设置双声道音量
}
}
🧩 关键知识点总结表
| AppA 申请的焦点类型 | 实际场景 | AppB 收到的回调 | AppB 应采取的行动 |
|---|---|---|---|
AUDIOFOCUS_GAIN_TRANSIENT | 短暂提示音 | AUDIOFOCUS_LOSS_TRANSIENT | 暂停播放 |
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE | 电话/录音 | AUDIOFOCUS_LOSS_TRANSIENT | 立即静音 |
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK | 语音助手/导航 | AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK | 降低音量 |
AUDIOFOCUS_GAIN | 开始播放自己的内容 | AUDIOFOCUS_LOSS | 停止播放并放弃焦点 |
💡 注意事项
-
回调延迟:系统可能在 AppA 实际播放前就发送回调,AppB 需立即响应
-
独占特殊性:虽然
TRANSIENT_EXCLUSIVE和TRANSIENT的回调类型相同,但系统对前者会强制静音 -
焦点返还:当 AppA 调用
abandonAudioFocus()时,AppB 会收到AUDIOFOCUS_GAIN -
版本差异:Android 8.0+ 对独占焦点处理更严格,建议测试兼容性
-
Ducking 技巧:降低音量时建议保持原音质,避免重新解码:
java
mediaPlayer.setVolume(0.2f, 0.2f); // 优于 mediaPlayer.setAudioStreamType()
🎯 终极处理口诀
"短暂打断要暂停,VIP来需静音,导航说话音量低,永久接手就退场!
焦点返还快恢复,用户无感最高明。"
通过这个生动的"麦克风争夺战"模型,配合精准的回调处理,你的应用就能在 Android 音频生态中优雅共存!🎶