Android音频焦点管理:音乐会指挥的调度的故事

259 阅读5分钟

通过一个音乐会指挥的故事,为你详细解释四种音频焦点的使用规则和释放机制。想象你是一位交响乐团的指挥(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;
}

总结:指挥家的黄金法则

  1. 所有焦点都需要手动释放

    • 使用abandonAudioFocus()释放不再需要的焦点
    • 就像指挥家需要明确指示乐手停止演奏
  2. 释放后系统自动恢复

    • 应用B不需要重新申请焦点
    • 系统会自动发送AUDIOFOCUS_GAIN回调
    • 类似指挥家自动示意下一个乐手开始演奏
  3. 特殊场景需要重新申请

    • 应用首次启动
    • 被永久拒绝后
    • 需要改变焦点类型时

记住指挥家的核心原则:

"有借有还,再借不难。获得焦点必释放,系统调度有保障。自动恢复是常态,特殊场景需重来。"

通过正确管理音频焦点,你的应用将成为Android音频交响乐团中和谐的一员!