🎵 🎤 Android 麦克风争夺战:音频焦点回调完全解析🎵

158 阅读3分钟

让我们用一场更精彩的"派对麦克风争夺战"故事,结合代码示例,彻底讲明白不同焦点申请对音乐播放器的影响!


🎪 派对场景设定

  • 派对主持人: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停止播放并放弃焦点

💡 注意事项

  1. 回调延迟:系统可能在 AppA 实际播放前就发送回调,AppB 需立即响应

  2. 独占特殊性:虽然 TRANSIENT_EXCLUSIVE 和 TRANSIENT 的回调类型相同,但系统对前者会强制静音

  3. 焦点返还:当 AppA 调用 abandonAudioFocus() 时,AppB 会收到 AUDIOFOCUS_GAIN

  4. 版本差异:Android 8.0+ 对独占焦点处理更严格,建议测试兼容性

  5. Ducking 技巧:降低音量时建议保持原音质,避免重新解码:

    java

    mediaPlayer.setVolume(0.2f, 0.2f); // 优于 mediaPlayer.setAudioStreamType()
    

🎯 终极处理口诀

"短暂打断要暂停,VIP来需静音,导航说话音量低,永久接手就退场!
焦点返还快恢复,用户无感最高明。"

通过这个生动的"麦克风争夺战"模型,配合精准的回调处理,你的应用就能在 Android 音频生态中优雅共存!🎶